CodeGym /课程 /C# SELF /进阶数组操作

进阶数组操作

C# SELF
第 7 级, 课程 2
可用

1. string类型的数组

简单说下string类型的数组吧。

就像之前说的,数组可以是任何类型。所以你当然可以创建string类型的数组。比如我们要写个程序,“从键盘输入10行字符串,然后倒序输出到屏幕”。代码大概长这样:

string[] array = new string[10];	// 创建一个10个元素的数组对象
for (int i = 0; i < 10; i++)		// 循环从0到9
{
   array[i] = Console.ReadLine();	// 从键盘读取一行字符串,存到数组里
}
for (int i = 9; i >= 0; i--)		// 循环从9到0
{
    Console.WriteLine(array[i]);	// 输出数组的下一个元素
}

代码几乎没变!只是把数组类型从int换成了string。其它都一模一样!

2. 内存中的string数组

还有个有用的知识点。来看三张图:

图1. string对象在内存中的样子:

内存中的string数组

注意,字符串的文本内容不是直接存在变量里的:它会单独分配一块内存。而string类型的变量里存的是指向文本对象的地址(引用)。

图2. 整型数组在内存中的样子:

int array in memory

这张图你应该也见过。

图3. 字符串数组在内存中的样子:

How a string array is stored in memory

左边是string[]类型的数组变量(存着数组对象的地址)。
中间是string类型的数组对象。
右边是存着文本的字符串对象。

string类型数组对象的每个格子里,存的不是字符串(文本)本身,而是它们的地址(引用)。就像string类型变量里存的是字符串(文本)的地址一样。

3. C#里的快速数组初始化

数组超有用,所以C#的开发者尽量让数组用起来更方便。首先,他们简化了数组初始化,也就是给数组填初始值。

很多时候,除了程序从外部读取的数据,还需要一些自己的内部数据。比如我们要存每个月的天数。代码可以这样写:

int[] months = new int[12];
months[0] = 31; // 一月
months[1] = 28; // 二月
months[2] = 31; // 三月
months[3] = 30; // 四月
months[4] = 31; // 五月
months[5] = 30; // 六月
months[6] = 31; // 七月
months[7] = 31; // 八月
months[8] = 30; // 九月
months[9] = 31; // 十月
months[10] = 30; // 十一月
months[11] = 31; // 十二月

不过其实有更短的写法——感谢C#的设计者:

// 一年每个月的天数
int[] months = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

直接用逗号把所有数组值列出来就行!

是不是很方便?但还不止这些。

其实编译器可以根据数组变量的类型自动推断数组对象的类型。数组长度也能直接数大括号里的元素个数。

所以代码还能更短:

// 一年每个月的天数
int[] months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

是不是很优雅?🙂

这种写法叫“快速数组初始化”。而且不仅仅是int类型才行哦……

// 一年每个月的名字
string[] months = { "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"};

4. foreach循环

遍历数组所有元素太常见了,所以专门有个循环!它叫foreach。写法如下:


foreach (int score in scores)
{
    Console.WriteLine($"分数: {score}");
}
        
用 foreach遍历数组元素

这里循环会自动遍历元素——不用手动写索引。不能直接修改元素(循环里只能读),但用来“走一遍”数组超方便。

编译器会把foreach循环的代码变成下面这样:


for (int i = 0; i < scores.Length; i++)
{
    int score = scores[i];
    Console.WriteLine($"分数: {score}");
}
        
foreach“底层”是怎么工作的

所以其实没啥魔法。现在你也明白为啥不能通过score变量改数组元素了吧。

再来个例子:算所有分数的和。


int sum = 0;
foreach (int score in scores)
{
    sum += score;
}
Console.WriteLine($"所有分数之和: {sum}");
        
用 foreach求数组元素之和

什么时候用哪个循环?

  • 如果需要索引或者要修改元素——用for循环。
  • 如果只是遍历值——foreach更快更简洁。

5. 从末尾索引:^操作符

现在C#可以直接从数组末尾取元素了。以前要取最后一个元素,得写成这样:


int lastScore = scores[scores.Length - 1];
        
老方法获取数组最后一个元素

现在简单多了。只要在索引前加个^,就能从末尾数:


int lastScore = scores[^1];        // 最后一个元素
int penultimate = scores[^2];      // 倒数第二个元素
        
用 ^从末尾索引
  • ^1 —— 倒数第一个(最后一个)。
  • ^2 —— 倒数第二个。

可视化

索引 scores数组里的值 普通索引 从末尾索引
0 10 0 ^5
1 15 1 ^4
2 8 2 ^3
3 22 3 ^2
4 17 4 ^1

score[^1] = 17, score[^2] = 22 —— 一目了然。

注意! 从末尾的索引是从1开始的,不是从0。

6. 区间:..操作符(range)

有时候你想从原数组里拿出子数组。C#里可以用专门的区间操作符..,超简洁超方便地拿到你想要的“片段”。

这种子数组有时叫切片(slice),由两个边界决定:起始和结束。
重点: 右边界不包含在结果里!

长啥样


int[] scores = { 10, 15, 8, 22, 17 };
int[] top3 = scores[0..3]; // 取0到2号元素: {10, 15, 8}
        
用 ..获取数组切片

  • scores[0..3] —— 取索引0(包含)到索引3(不包含)。
  • 最终数组包含索引0、1、2的元素。

简写

如果没写左边界,就是从头开始:


int[] firstTwo = scores[..2]; // 等同于scores[0..2],也就是{10, 15}
        

如果没写右边界,就是到最后:


int[] fromThird = scores[2..]; // {8, 22, 17}
        

负索引

用^可以从末尾数索引:


int[] lastTwo = scores[^2..]; // {22, 17}
        

这里^2就是“倒数第二个元素”。

你也可以用负索引两边都写,取“从哪到哪”的区间:


int[] mid = scores[1..^1]; // {15, 8, 22}(从第2个到倒数第二个)
        

要记住啥

  • 右边界永远不包含——写a..b,就是从a到b-1。
  • 如果区间超出数组范围,会抛出IndexOutOfRangeException异常。
  • 切片是新数组,原数组不会变。新数组怎么改都不会影响原来的。

实用例子

int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };

int[] start = arr[..3];      // {1, 2, 3}
int[] end = arr[^2..];       // {6, 7}
int[] middle = arr[2..5];    // {3, 4, 5}
int[] all = arr[..];         // {1, 2, 3, 4, 5, 6, 7}(完整拷贝)
2
任务
C# SELF, 第 7 级, 课程 2
已锁定
使用从末尾索引和区间操作符
使用从末尾索引和区间操作符
2
任务
C# SELF, 第 7 级, 课程 2
已锁定
字符串数组反转与切片
字符串数组反转与切片
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION