数组
声明数组
数组是一种可以声明多个同类型变量的数据结构,能替你声明多个变量。
并且其中的值可以通过索引动态访问,可以搭配循环批量处理这些值。
数组类型的写法是,在目标类型后加上一对中括号。
数组值没有字面量,需要构造出来。
int[] arr1 = new int[10];//构造一个int数组,它里面有10个int
var arr2 = new int[10];//var能识别出数组类型,不需要在var后加中括号
访问内容
通过int访问
数组的元素通过变量后加一对中括号,并指示索引来访问他的第n个元素。
获取到的元素跟普通的int类型值是一样的。
可以为int变量赋值,可以接收int值,也可以进行数学运算。
int[] arr3 = new int[4];
arr3[0] = 12;
arr3[1] = 16;
arr3[2] = 64;
arr3[3] = arr3[0] + arr3[1];
Console.WriteLine(arr3[3] - arr3[2]);
他的索引不仅能使用字面量,也可以使用变量。
int[] arr5 = new int[10];
for (int i = 0; i < arr5.Length; i++)
{
arr5[i] = i * i;
}
for (int i = 0; i < arr5.Length; i++)
{
Console.WriteLine(arr5[i]);
}
通过Index访问
通过Index可以从后往前访问数组的元素。他的写法是在数字前写一个^
。
int[] arr6 = new int[10];
arr6[^1] = 18;
arr6[^2] = 16;
arr6[^3] = 14;
arr6[^9] = 2;
arr6[^10] = 1;
Console.WriteLine(arr6[0]);
Console.WriteLine(arr6[1]);
Console.WriteLine(arr6[7]);
Console.WriteLine(arr6[8]);
Console.WriteLine(arr6[9]);
- 用
var
可以知道^1
的类型是Index
。^1
不是运算符,而是语法糖。它要求编译器构造一个Index
类型的值。^1
不是字面量,而是指令。所以无法声明Index
的常量
也可以配合变量来构造一个Index
int[] arr7 = new int[2];
arr7[0] = 18;
Console.WriteLine(arr7[^arr7.Length]);
数组长度
索引越界
对数组元素的访问,只能访问有效索引。
有效索引从0开始数,总数量等于数组长度。所以最大的有效索引为数组长度-1
数组长度可以通过访问数组的
.Length
属性获取。
而对于Index
类型来说,^1
表示倒数第一个元素。相当于arr[arr.Length - 1]
。
Index
的倒数最大有效值为数组的Length
。
无论使用int
还是Index
,如果访问了非法的索引,那么或报错索引越界
。
c#是类型安全语言。数组控制之外的元素依旧按照相同的逻辑访问,可能会访问到其他程序的内存。
数组长度可以为0,此时任何索引都是非法的。
长度固定
之前的类型都是简单类型。他们没有内容。
任何对值的修改都是通过修改变量本身来完成的。
而数组的元素,是数组的内容
。可以在不修改他的变量的情况下,来改变它的值。
也因为数组的元素是数组的
内容,所以你在使用他们前,不需要像变量一样先赋值初始值。
数组是一个基本的数据结构。它高效,但一旦完成声明,就不能再改变他的长度。
这是说你不能在不修改变量的情况下修改长度
。你仍然可以新建一个数组,重新对数组变量赋值。
指针
引用类型
如果你直接用一个数组变量来给另一个数组变量赋值,你会发现他们会窜号。
int[] arr8 = new int[1];
int[] arr9 = arr8;
Console.WriteLine(arr8[0]);//0
Console.WriteLine(arr9[0]);//0
arr8[0] = 12;
Console.WriteLine(arr8[0]);//12
Console.WriteLine(arr9[0]);//12
这是因为数组是引用类型
。引用类型是一些无法预测大小,内容过大,和一些躺枪的数据。
这些的数据(数组元素)是存放在堆
内存上的。
·栈
内存储存了程序的运行到哪的指令信息,和一些临时信息。
在你构建完数组元素后,这个数组的指针
会作为临时信息(数组变量)创建到栈
上。
区分一个数据是在
堆
上还是在栈
上是不能靠他是引用类型
还是值类型
来分辨的。
例如一个int数组
的元素int
。他们都是固定大小的值类型int
,但他们在堆上。
托管指针
什么是指针,你可以理解为:快捷方式。
就是你桌面上那个1kb大小的QQ,它记录的一个地址。
在你双击快捷方式的时候,会顺着它记录的地址,找到QQ那个500MB的安装路径,
然后打开那个真实的QQ程序。这个过程叫解指针
。
托管是指.Net可以控制的东西,比如分配,监视,回收,释放。
因此不同于c++的指针,c#用到的(给你用的)指针是安全的。
c#可以启用不安全代码,来使用和c++同款的不安全指针。
指针在32位程序占32bit,在64位程序占64bit。
32位程序只能用4G内存,因为同为32位的int也是能表示4G个数(正数21亿,负数21亿)。
64位程序中,指针占用双倍大小。所以天然占用更多内存。
引用类型特点
复制
一个赋值操作就是一个复制操作,数组直接用数组赋值时,就是在复制这个指针。
之后访问数组元素时,访问到的就是同一组元素。
只读
引用类型的变量设置只读,只是不能修改这个变量。
这个变量指向的实际数据是可以修改的。
比较
数组变量的值是一个指针。给变量做比较只会判断他们指向的地址是不是相同的。
int[] arr10 = new int[1];
int[] arr11 = new int[1];
Console.WriteLine(arr10[0] == arr11[0]);//True
Console.WriteLine(arr10.Length == arr11.Length);//True
Console.WriteLine(arr10 == arr11);//False
引用变量
引用变量可以创建对变量的指针。对引用变量的任何操作都会解指针后对原变量进行操作。
int i1 = 6;
ref int ri1 = ref i1;//必须声明时赋值
Console.WriteLine(i1);//6
Console.WriteLine(ri1);//6,相当于访问i1
ri1 = 10;//直接赋值会作用到原变量上
Console.WriteLine(i1);//10,会被修改
Console.WriteLine(ri1);//10
int[] arr12 = new int[6];
ri1 = ref arr12[0];//可以重新赋值
Console.WriteLine(arr12[0]);//0
ri1 = 50;
Console.WriteLine(arr12[0]);//50
默认值
数组创建后,它里面的元素是默认值。
对于指定的类型可以使用字面量default获得默认值。
对于没有指定的类型,可以在default后加个括号指定类型。
bool b = default;
Console.WriteLine(default(char));
默认值的逻辑是,对这个类型所占据的内存都设置为0。
- 对于数字类型都是0
- char也是数字类型,会被解读为一个字符编码为0的字符。
- 对于bool类型是false
- 对于引用类型,值为
null
null
也是一个字面量,表示不引用任何数据。所有的引用类型都可以使用null值。
int[][] arr13 = new int[5][];//数组是一个有效的类型,所以数组也可以有数组
for (int i = 0; i < arr13.Length; i++)
{
arr13[i] = new int[6];//数组的默认值是null,无法使用
}
arr13[2][3] = 8;
null值自身可以被访问,如使用参与比较。但不能访问他的任何内容。
string s1 = null;//字符串也是引用类型
Console.WriteLine(s1 == null);//仅访问变量
Console.WriteLine(s1 == "hello");//仅访问变量
Console.WriteLine("hello".Length);//获取内容,字符串的长度
Console.WriteLine("hello"[2]);//获取内容,第3个字符
Console.WriteLine(s1.Length);//尝试获取null的内容,报错
Console.WriteLine(s1[2]);//尝试获取null的内容,报错