当前位置: 首页 > article >正文

C#基础学习(二)C#数组生存手册:从入门到“血压拉满“的奇妙旅程

        作为一只C#萌新,当你试图用数组装下整个世界时,系统可能会温柔地弹出一句**"Index was outside the bounds of the array."**。别慌!这份求生指南将用段子教你玩转数组

一、数组是什么

        数组简单来说就是由相同元素组成的一个集合,数组里面不一定是数,还可能是bool,string等类型组成的集合。那么他有些什么特点呢:

  • 本质:装着相同类型元素的集装箱(比如一箱肥宅快乐水)
  • 特性:长度固定(集装箱容量不可变)、有编号(每个可乐罐都有座位号)
  • 致命诱惑:访问速度极快(毕竟不用像ArrayList那样扭扭捏捏扩容)
//数组是存储一组相同类型数据的集合
//数组分为 一维 多维 交错数组
//一般情况下 一维数组简称数组

二、C#数组三大门派

        1.一维数组:直男式集装箱

怎么申明呢,

            //变量类型[] 数组名;//只是申明 并没有初始化
            //变量类型 可以是 学过的和没学过的所有变量类型

示例1:

            int[] arr1;

            //变量类型[] 数组名 = new 变量类型[数组的长度];
            int[] arr2 = new int[5];//这种方式 相当于开了5个房间 但是默认的int值是0

            //变量类型[] 数组名 = new 变量类型[数组的长度]{内容1,内容2,内容3,....};
            int[] arr3 = new int[5] {1,2,3,4,5};

            //变量类型[] 数组名 = new 变量类型[]{内容1,内容2,。。。。};
            int[] arr4 = new int[] {2,3,4,5};//后面的内容决定数组的长度

            //变量类型[] 数组名 = {内容1,内容2,内容3,....};
            int[] arr5 = { };

示例2:

// 声明方式1:班主任点名式
int[] scores = new int[5]; // 5个空座位,默认值0

// 声明方式2:暴力填鸭式
int[] scores = {90, 80, 85, 0, 0}; // 第一个座位90分,第二个80分...

// 使用姿势:
scores[0] = 100; // 第一个座位改成100分
Console.WriteLine(scores[2]); // 访问第三个座位

⚠️危险行为scores[5] = 60; (试图打开不存在的第6个座位,系统将对你发动"越界异常攻击"),所以务必注意在使用数组是,请处理好下标的各种情况。

示例3:数组的实际应用

            int[] array = new int[5] { 1, 2, 3, 4, 5 };
            //1.数组的长度
            //数组变量名.Length
            Console.WriteLine(array.Length);

            //2.获取数组中的元素
            //数组中的下标和索引 他们都是从0开始的
            //数组的范围 0~Length - 1
            Console.WriteLine(array[0]);

            //3.修改数组中的元素
            array[0] = 99;


            //4.遍历数组 通过循环 快速获取数组中的每一个元素
            for (int i = 0; i < array.Length; i++)
            {
                Console.WriteLine(array[i]);
            }

            //5.增加数组的元素
            //数组初始化以后 是不能 直接添加新的元素
            int[] array2 = new int[6];
            for (int i = 0; i < array.Length; i++)
            {
                array2[i] = array[i];
            }
            array = array2;
            //先搬家 先把原有数据储存 等新家建好后 在搬回来 自动把房子变大了

            //6.删除数组的元素
            //数组初始化以后 是不能 直接删除新的元素
            //搬家的原理
            int[] array3 = new int[5];
            for (int i = 0; i < array3.Length; i++)
            {
                array3[i] = array[i];
            }
            array = array3;

            //7.查找数组中的元素
            //遍历即可

         2. 多维数组:强迫症の矩形方阵​(比如Excel表格)

既然数组是排成一排的一组元素,你有没想过,将他拼成两排,三排或者更多排呢

下面先来看看简单二维数组是怎么定义的:

            //二维数组的申明
            int[,] arr3 = new int[,] {
                { 1, 2 },
                { 3, 4 },
                { 5, 6 },
                { 7, 8 } };
            //PLUS
            int[,] arr4 =  {
                { 1, 2 },
                { 3, 4 },
                { 5, 6 },
                { 7, 8 } };

好的,现在你已经知道怎么定义二维数组了,是时候该传授你该如何使用这个东西:

二维数组的使用方法(简单版):

//二维数组的使用
//1.二维数组的长度
int[,] array = new int[,] { {8,9,55 },
                            { 48,85,95} };
Console.WriteLine(array.GetLength(0));//得到多少行
Console.WriteLine(array.GetLength(1));//得到多少列

//2.获取二维数组中的元素
Console.WriteLine(array[0,1]);

//3.修改二维数组中的元素
array[0, 0] = 55;
Console.WriteLine(array[0,0]);
//4.遍历二维数组
for (int i = 0; i < array.GetLength(0); i++) //行
{
    for (int j = 0; j < array.GetLength(1); j++) //列
    {
        Console.WriteLine(array[i, j]);
    }

}
//5.增加数组的元素
int[,] array2 = new int[3, 3];

        值得关注的主要是一个函数,两个输入值:GetLength(0),当是0的时候表示获取到的是二维数组的行数,当然咱们就一个二维数组。自然而然行数就为2哈。好像没啥用,嘿嘿。不过,我们下面即将介绍的交错数组,那个时候就可以发挥巨大作用了!里面的参数为1时获取的为列数,这个显而易见。

        示例:

// 二维数组声明:
int[,] matrix = new int[3,4]; // 3行4列的数学课座位表

// 三维数组声明(慎用,容易晕车):
int[,,] cube = new int[2,3,4]; // 2层楼,每层3行4列

// 使用姿势:
matrix[0,1] = 42; // 第一行第二列塞入42
Console.WriteLine(matrix.GetLength(0)); // 获取行数:3

⚠️雷区警示:所有行必须一样长!就像军训队列,不允许有人偷懒少站一列。

        3. 交错数组(锯齿数组):俄罗斯套娃の千层套路

        除了前面规规整整的数组,每一行的元素数目都是一样的以外。其实C#还给我们提供了一个不用像这样必须每行元素都要一样长,每行元素任意coder操作的数组,那就是交错数组!你可以自由决定你每一行的元素个数,就相当于你将很多个不同长度的一维数组从上往下依次放好。下面咱们来看看其怎么声明的:

//数组的申明
//变量类型[][] 交错数组名;
int[][] arr1;
//变量类型[][] 交错数组名 = new 变量类型[行数][];
int[][] arr2 = new int[3][];
//变量类型[][] 交错数组名 = new 变量类型[行数][]{一维数组1,一维数组2,一维数组3......};
int[][] arr3 = new int[3][] { new int[] { 1,2,3},
                              new int[] { 1,2},
                              new int[] { 1} };
int[][] arr4 = new int[][] { new int[] { 1,2,3},
                              new int[] { 1,2},
                              new int[] { 1} };
int[][] arr5 = { new int[] { 1,2,3},
                              new int[] { 1,2},
                              new int[] { 1} };

 ✨优势:每行长度可随意变化,像乐高积木一样自由(适合处理不规则数据,比如CSV文件)

// 声明方式:先造外层箱子,再给每个箱子塞内层箱子
int[][] jagged = new int[3][]; 
jagged[0] = new int[] {1,3,5}; // 第一个箱子装3个元素
jagged[1] = new int[] {2,4};   // 第二个箱子装2个元素(参差不齐才是美)
jagged[2] = new int[] {666};   // 第三个箱子只装一个卷王

// 使用姿势:
Console.WriteLine(jagged[1][0]); // 输出2(第二箱子的第一个元素)

实际使用:

int[][] arr6 = { new int[] {1,2,3,5},
                 new int[] { 4,5}};
//1.数组的长度
Console.WriteLine(arr6.GetLength(0));//行
Console.WriteLine(arr6[0].Length);//得到一行的列数
2.获取数组中的元素
Console.WriteLine(arr6[0][3]);
//Console.ReadLine();
//3.修改元素
//4.遍历元素
for (int i = 0; i < arr6.GetLength(0); i++) 
{
    for (int j = 0; j < arr6[i].Length; j++) 
    {
        Console.Write(arr6[i][j] + "66666666 ");

    }
    
}

        看到没,这里又出现了GetLength()这个函数,所以这个函数很重要啊,请快速在心里默念几遍,加深自己的记忆, GetLength(),GetLength(),GetLength,0是行啊,1是列。

        好的,我们已经介绍了C#中数组都长了些什么样子,接下来,我将传授各位一个秘密武器,那就是Array类里面提供的方便函数,接下来,和我一起来看看吧,Look in my eyes!,最后读者是MVP,笔者是躺赢狗。

  三、 Array类——数组的瑞士军刀

        在C#中,Array是所有数组的隐形爸爸(所有数组都继承自它)。它自带一套让数组"旋转跳跃"的魔法方法,但稍有不慎就会踩雷💣

        🎩 核心魔法技能(静态方法)

1、​Sort:一键整理混乱现场,将数组中元素进行排序。

 注意:会修改原数组!就像老妈帮你收拾房间,东西可能再也找不到了,是直接修改原数组

        当你的数组里面存的都是一些整数时,那排序简直就是简简单单,直接排序,那如果是字符呢,字符串呢?

int[] chaos = {5,3,9,1};
Array.Sort(chaos); // chaos变成[1,3,5,9]

        当你的数组是char[]时,Array.Sort()会化身无情的ASCII码判官,严格按照每个字符的Unicode值排序: 

char[] letters = { 'd', 'A', '3', '!', '中' };
Array.Sort(letters);
Console.WriteLine(string.Join(" ", letters)); 
// 输出:! 3 A d 中

排序规则详解: 

        1、特殊符号 < 数字 < 大写字母 < 小写字母 < 汉字
(因为!的Unicode是33,3是51,A是65,d是100,是20013)

        2、大小写敏感:大写字母永远排在小写前面

char[] caseTest = { 'a', 'Z', 'B' };
Array.Sort(caseTest);
// 结果:B Z a (B(66) < Z(90) < a(97))

        字符串数组(string[])排序:文化差异的修罗场排序规则解密:字符串排序是字典序比较,但有个隐藏雷区——文化敏感性(Culture)​。默认情况下:

string[] words = { "apple", "Banana", "cherry", "1", "_" };
Array.Sort(words);
// 结果:_ 1 apple Banana cherry

 排序规则详解

        1、优先级阶梯
符号/数字 → 大写字母 → 小写字母,_的ASCII是95,1是49,但字符串比较时数字被视为整体

        2、逐字符比较

string[] test = { "a", "ab", "abc", "b" };
Array.Sort(test);
// 结果:a, ab, abc, b (像字典一样逐个字母比较长度)

        3、文化差异炸弹陷阱:

        在土耳其等地区,字母i的大写是İ(带点),而I的小写是ı(无点)。若未指定文化:

这个不是很重要,用到了再来了解也是一样的。

Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
string[] turkishTest = { "ı", "i", "İ" };
Array.Sort(turkishTest);
// 默认排序可能与英语环境不同!

常见小坑:

        混合类型数组的迷惑行为

object[] mixedArray = { 5, "3", 'A' };
Array.Sort(mixedArray); // 运行时直接炸裂!
// 错误信息:至少一个对象必须实现IComparable

        自以为是的数字字符串排序 

string[] numbers = { "100", "20", "3" };
Array.Sort(numbers);
// 你以为会排成3,20,100?实际结果是"100","20","3"(按字符逐个比较)

2、Reverse:倒放人生

        核心原则:​只翻转元素顺序,绝不碰元素内容无论数组里装的是数字、字符串还是对象,它都像搬家师傅一样,只管把箱子位置调换,绝不拆箱!

Array.Reverse(chaos); // chaos变成[9,5,3,1]

如果用在二维数组,只会反转第一层容器,内层纹丝不动

        双层嵌套的俄罗斯套娃

string[][] jagged = {
    new[] { "A", "B" },
    new[] { "C", "D", "E" },
    new[] { "F" }
};

Array.Reverse(jagged); // 只反转外层数组
/*
结果变成:
[ ["F"], ["C","D","E"], ["A","B"] ]
*/

// 如果想连内层一起反转
foreach(var arr in jagged){
    Array.Reverse(arr);
}
/*
最终结果:
[ ["F"], ["E","D","C"], ["B","A"] ]
*/
  • Reverse() 默认只操作最外层,像剥洋葱一样需要手动层层深入
  • 深度反转需要递归操作(小心栈溢出!)

 一些常见坑:

        以为字符串内容会被反转:

string[] words = { "hello", "world" };
Array.Reverse(words);
// 数组变成 ["world", "hello"],但每个字符串还是原样
// 想反转字符串本身需要额外操作:
words[0] = new string(words[0].Reverse().ToArray()); // "hello" → "olleh"
  • 它很单纯:只负责调换元素位置,不管元素是啥
  • 它很专一:对多维数组永远只动最高维度
  • 它很快:时间复杂度是 O(n/2),比你自己写循环高效
  • 它很危险:遇到 null 或越界区域会瞬间翻脸

3、Copy:克隆人战争

int[] target = new int[4];
Array.Copy(chaos, target, 2); // 复制前两个元素到目标数组
// target变成[9,5,0,0]

🔍 细节:第三个参数是复制数量,不是索引!

4、IndexOf:寻人启事

int pos = Array.IndexOf(chaos, 5); // 返回1(下标位置)
int notFound = Array.IndexOf(chaos, 666); // 返回-1

💡 技巧:找不到时返回-1,记得判断!否则可能引发连锁反应

5、Resize:整容手术(其实是换头术)

Array.Resize(ref chaos, 6); 
// chaos变成[9,5,3,1,0,0](原数组已被替换!)

 惊天大坑:你以为是在原数组上扩容?实际是创建新数组,杀死旧数组!

        当你在代码中写下 Array.Resize(ref arr, newSize),你以为数组像孙悟空的金箍棒一样自由伸缩?

int[] original = { 1, 3, 5 };
Array.Resize(ref original, 5);
// 发生了什么?
// 1. 偷偷new一个新数组:[0,0,0,0,0]
// 2. 把旧数组元素复制过去:[1,3,5,0,0]
// 3. 把original变量指向新数组
// 4. 旧数组被丢进垃圾回收站

        灵魂拷问:原数组真的被"扩容"了吗?不!它已经死了,现在你看到的只是戴着原数组面具的新数组!阿伟已经死了! 

        Resize()的注意事项:

        1、ref参数,不加相当于你没有将修改的东西传出来,至于这个ref是啥,好像听说他还有个兄弟叫out,我们后面再说。

int[] arr = { 1, 2 };
Array.Resize(ref arr, 3); // 必须加ref!否则白忙活
// 忘记ref的惨案:
Array.Resize(arr, 3); // 原数组纹丝不动!

        2、值类型和引用类型区别

// 值类型数组:新空间填默认值
int[] nums = { 1 };
Array.Resize(ref nums, 3);
// 变成 [1, 0, 0]

// 引用类型数组:新空间都是null
Student[] students = { new Student() };
Array.Resize(ref students, 3);
// 变成 [student实例, null, null]

        3、缩容时的断舍离,当断则断就像杨过断手臂一样。 

string[] words = { "A", "B", "C", "D" };
Array.Resize(ref words, 2);
// 直接腰斩!变成 ["A", "B"]
// 被抛弃的"C","D":终究是错付了😭
场景推荐方案优势
频繁增删List自动扩容、性能优化
超大数组MemoryPool内存池管理、减少GC压力
需要保留旧引用手动创建新数组复制完全掌控内存生命周期

        记住:Array.Resize() 就像给数组做换头手术,手术成功后,你的数组将拥有新长度、新身体,但——不再是最初的那个TA

        🧙 实例方法(通过数组对象调用)

​1、GetLength:多维数组の测谎仪

int[,] matrix = new int[3,4];
Console.WriteLine(matrix.GetLength(0)); // 3(第一维长度)
Console.WriteLine(matrix.GetLength(1)); // 4(第二维长度)

 错误示范:用在交错数组会报错!交错数组请用array.Length

2、Clone:深度复制?天真!

int[] source = {1,2,3};
int[] clone = (int[])source.Clone(); 
clone[0] = 666; // source不受影响(值类型安全)

// 但如果是对象数组...
Student[] students = { new Student() };
Student[] evilClone = (Student[])students.Clone();
evilClone[0].Name = "张三"; // 原数组里的对象也被修改了!

        深水炸弹:Clone()是浅拷贝!引用类型数组会共享对象

        好的  方法已经传授给你了,该你好好使用了,接下来我将为你展示一些常见的错误,希望你不要再犯了哦

      坑1:  交错数组必须先初始化每一行(这个在上面也说明了,在这里强调是为了加深印象!!!)

int[][] jagged = new int[3][]; // 只创建了外层箱子
jagged[0][1] = 5; // 运行时NullReferenceException!内层箱子还没造

// 正确姿势:逐行初始化
jagged[0] = new int[2]; // 造第一个箱子
jagged[1] = new int[3]; // 造第二个箱子(尺寸不同)
jagged[0][1] = 5; // 安全操作

        坑2:多维数组 vs 交错数组的GetLength

int[,] matrix = new int[3,4];
Console.WriteLine(matrix.GetLength(0)); // 正确:3

int[][] jagged = new int[3][];
Console.WriteLine(jagged.GetLength(0)); // 正确:3
Console.WriteLine(jagged[0].GetLength(0)); // 错误!如果jagged[0]未初始化

        坑3:Clear方法的温柔陷阱

int[] nums = {1,2,3,4,5};
Array.Clear(nums, 0, nums.Length);
// 值类型数组:全变成0
// 引用类型数组:全变成null(可能引发后续空指针异常)

        坑4:自定义对象的Sort需实现IComparable

class Cat{
    public int Age;
}

Cat[] cats = new Cat[3];
Array.Sort(cats); // 运行时炸裂!因为Cat类没有定义比较规则

总结: 

  1. ​交错数组初始化口诀:
    ​先造外箱,再造内箱,逐行填装,小心空箱

  2. ​Array方法使用三连问:

    • 是否会修改原数组?(如Sort、Reverse会)
    • 是否处理引用类型?(Clone浅拷贝需警惕)
    • 返回值是否需要接收?(如Resize要配合ref)
  3. ​多维数组 vs 交错数组选择指南:

    • 需要数学计算 → 多维数组(内存连续效率高)
    • 需要灵活行长度 → 交错数组(但初始化更繁琐)

        好的 现在你已经掌握了极大多数的数组的知识,好好去代码世界闯荡一番吧!芜湖起飞!

补充知识点:自定义比较器:

        为什么要自定义比较器?

  • 想把 ["Apple", "Banana", "cherry"] 按字母无视大小写排序
  • 想让人物对象数组按年龄倒序+身高升序排列
  • 需要把 ["100", "20", "3"] 识别为数字排序(而非字典序)

        这时候就该祭出IComparer接口,让数组按你的节奏摇摆

场景1:字符串长度优先排序(短的在前)
class 长度比较器 : IComparer<string>
{
    public int Compare(string x, string y)
    {
        // 处理null值(null视为无限小)
        if (x == null) return -1;
        if (y == null) return 1;
        
        int lenCompare = x.Length.CompareTo(y.Length);
        return lenCompare != 0 ? lenCompare : string.Compare(x, y);
    }
}

// 使用示例:
string[] words = { "C#", "is", "awesome", null, "!" };
Array.Sort(words, new 长度比较器());
// 结果:null, "!", "C#", "is", "awesome"

最后两句代码的解释: 

// 第一战:比较字符串长度
// CompareTo()返回值:
// - 负数表示x.Length < y.Length(x应该排在前面)
// - 0表示长度相同
// - 正数表示x.Length > y.Length(x应该排后面)
int lenCompare = x.Length.CompareTo(y.Length);

// 第二战:如果长度不同,直接返回长度比较结果
// 如果长度相同,进入字典序加时赛(调用默认字符串比较)
return (lenCompare != 0) 
    ? lenCompare          // 长度不同,按长度排序
    : string.Compare(x, y); // 长度相同,按字母表顺序排

 

场景2:对象数组多条件排序

假设有Student类:

class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
    public double Score { get; set; }
}

// 先按年龄降序,年龄相同按分数升序
class 学霸比较器 : IComparer<Student>
{
    public int Compare(Student x, Student y)
    {
        // 处理null(假设null学生排最后)
        if (x == null) return 1;
        if (y == null) return -1;
        
        int ageCompare = y.Age.CompareTo(x.Age); // 降序
        if (ageCompare != 0) return ageCompare;
        
        return x.Score.CompareTo(y.Score); // 升序
    }
}

// 使用:
Student[] students = { /* 学生数据 */ };
Array.Sort(students, new 学霸比较器());

解释语句 

// 第一步:比较年龄(降序规则)
// 注意这里故意用 y.Age.CompareTo(x.Age) 而不是 x.Age.CompareTo(y.Age)
// 假设:
//   x.Age = 18,y.Age = 20 → y.CompareTo(x) = 20.CompareTo(18) = 1(正数)
// 返回正数表示x应该排在y后面 → 年龄大的排前面
int ageCompare = y.Age.CompareTo(x.Age); 

// 如果年龄不同,直接返回比较结果(年龄大的胜出)
if (ageCompare != 0) 
    return ageCompare;

// 第二步:年龄相同时,比较分数(升序规则)
// 此时用x.Score.CompareTo(y.Score)正常比较
// 假设:
//   x.Score = 85,y.Score = 90 → 85.CompareTo(90) = -1(负数)
// 返回负数表示x应该排在y前面 → 分数低的排前面
return x.Score.CompareTo(y.Score); 


http://www.kler.cn/a/597029.html

相关文章:

  • 人工智能将使勒索软件更加危险
  • 【信息系统项目管理师】【论文分享】【历年真题】​论信息系统项目的成本管理
  • 数字电子技术(三十二)——组合逻辑电路的特点和基本设计方法
  • ubuntu22.04安装搜狗输入法保姆教程~
  • 考研课程安排(自用)
  • Flutter TextFormField 完全手册与设计最佳实践
  • 【MySQL报错】:Column count doesn’t match value count at row 1
  • 只是“更轻更薄”?不!遨游三防平板还选择“更强更韧”
  • 为什么 Redis 选择单线程模型?
  • 基于区块链的 Web3 数据保护技术探索
  • 现代前端开发框架对比:React、Vue 和 Svelte 的选择指南
  • Linux进程信号(下:补充)
  • 数字证书 与 数字签名 介绍
  • Beyond Compare 4注册激活方法
  • 使用HTML5和CSS3实现3D旋转相册效果
  • IoTDB 常见问题 QA 第六期
  • 一条不太简单的TEX学习之路
  • tryhackme——The Lay of the Land
  • 从报错到成功:Mermaid 流程图语法避坑指南✨
  • 嵌入式基础知识学习:UART是什么?