批量组织相同数据类型------数组
- C语言将数组看作派生类型(建立在其他类型的基础上)
- []:变地址运算符,表示将指针移动多少个存储单元。如a[n]=a[0]+n*sizeof(a[0]);
- 数组的索引,即为下标(变地址运算符中的偏移量)。数组索引范围: [ 0 , 数组大小 − 1 ] [0,数组大小-1] [0,数组大小−1]。数组越界(索引超出范围,索引为负数等)是未定义行为(访问了未经申请的内存)。
- C99之后增加了变长数组(VLA),允许用变量表示数组的维度(包括用const修饰符限定的变量)。注意,VLA并非在运行时可以自动改变数组大小。VLA必须为自动(auto)存储类别,不能为static或extern,且不能在声明时进行初始化。
- 数组的初始化分为完全初始化与不完全初始化。对于非字符数组类型,初始化数组采用序列表的方式(即为{}的形式),序列表仅允许在初始化时使用。对于不完全初始化,编译器会将未初始化的部分设为0。如果在块作用域中没有初始化数组且为自动数组(auto),会被填充随机值,一般将随机值视为垃圾数据。
- 指定初始化器:可以在初始化列表中指定待初始化的元素。例:
int arr[6]={[5]=0};
指定初始化器的用法可以与普通初始化的方法混用。例:int a[6]={1,[3]=5,6,7};
中,由于中间使用逗号运算符进行分隔,故执行顺序为从左到右依次进行,a[0]=1,a[3]=5,a[4]=6,a[5]=7;其余为0。若对同一元素出现了多次修改,则以最后面的作为最终结果(逗号运算符特性,后面的会覆盖前面的)。这种下标继承方式类似于枚举中的用法。
注:指定初始化器的方式进行初始化为C99的扩展,标准C++不支持这种写法,具体实现取决于C++编译器(g++不允许,clang++允许)。 - 求数组长度:(sizeof(数组名)/sizeof(数组名[0]))(再次注意:sizeof是标识符而非函数)
- 常量数组:加上const修饰符的数组,一旦创建之后,数组将为只读,内容不可被修改。
- 不允许直接将一个数组通过赋值表达式给其他数组。
- 数组名:即为整个数组的地址,同时也是数组中首元素的地址(即数组的基址)。
-
基址是指用于定位数据结构中元素位置的地址。它通常指向数据结构的起始位置或者某个特定元素的位置。基址通常与偏移量结合使用,通过基址加上偏移量来计算访问数据结构中的特定元素的内存地址。
在数组中,基址指的是数组的起始地址,即数组的首元素的地址。数组名在大多数情况下可以被视为数组的基址,通过数组名可以访问数组中的元素,时间复杂度为 O ( 1 ) O(1) O(1)。因此,数组中变地址运算符可视为数组的偏移量。
在函数调用中,基址指的是当前函数栈帧的基址,也就是栈帧中局部变量和参数的起始地址。基址指针在函数调用过程中用于帮助定位局部变量和参数。
总的来说,基址是用于定位数据结构中元素位置的地址,它是访问数据结构中元素的起点。
- 数组是一种顺序表,在内存中存储是连续的
多维数组
以最简单的多维数组------二维数组举例:
C语言遵循高维度优先原则,在二维数组中体现为行优先原则。二维数组的初始化,为避免混淆,一般采用嵌套序列表的方式进行(但并不绝对)。
c
int a[6][4];//定义了一个6行4列的二维数组
二维数组是若干个一位数组的数组,二维数组的每一个元素都是一维数组。对于上述二维数组a,其本身是一个有6个元素的一维数组(a[3])称为主数组。主数组中的每个元素是包含4个元素的一维数组。
注意:数组是顺序表,在内存中始终是顺序存储的,行和列只是为了方便理解而创造出来的,在内存中存储并不涉及行和列的概念。
从指针角度看二维数组:
- 数组名a是整个主数组a的地址,也是主数组中首元素a[0]的地址,a[0]是大小4个int的数组,因此a是占有4个int大小对象的地址。
- a[0]元素本身是一个数组,因此a[0]是a[0]中首元素a[0][0]的地址,因此a[0]是占用1个Int大小对象的地址。
- 一句话总结:a=&a[0],a[0]=&a[0][0];*a=*(&a[0])=a[0]=&a[0][0],**a=*a[0]=*(&a[0][0])=a[0][0]
- 以上地址的地址(二级地址)、指针的指针(二级指针),是双重间接
例:
c
a+1//a=&a[0],a[0]是4个int大小的数组,因此a+1为+4个int大小,即为行指针下移,变为a[1]
a[0]+1//a[0]=&a[0][0],列指针下移,变为a[0][1]
(a+1)[2]//a=&a[0],行指针下移1个存储单元,变为a[1],之后通过变地址运算符行指针再下移2行,变为a[3]
(a[0]+1)[2]//a[0]=&a[0][0],列指针下移1个存储单元,变为a[0][1],之后通过变地址运算符列指针再右移2个存储单元。再次注意:数组是顺序表,在内存中存储是连续的,因此可跨行移动
(a+1)[0][2]//a=&a[0],行指针下移1个存储单元变为a[1],之后通过变地址运算符转换为列状态,列指针再下移2个存储单元
从存储单元角度理解,会发现容易很多。
- 二维数组在函数定义中做函数形参时,行可以缺省,但列不能缺省。即使行未缺省,编译器也会自动忽略该值。对于多维数组来说,在函数定义中做形参时,只能省略最左侧方括号中的值。(因为最左侧方括号表明其为一个指针)。
- VLA的二维数组在函数声明时若要省略维度形参,必须用*来代替省略的维度。在函数定义时则只能缺省最左侧的维度形参,其余维度形参在函数形参表中必须比数组先进行声明。(注意形参永远只能被看作是声明而非定义,因为形参只发生在函数调用时而非函数定义时)
指针数组
注意优先级[]>*
c
int *p[2];//定义了大小为2的数组,每个元素都是int*(int型指针)
//其实应该相当于:
int* p[2];
数组指针
注意优先级[]>*
c
int (*p)[2];//定义了一个可以指向含有2个int型元素的一维数组的指针
在二维数组中应用数组指针则相当于行指针。
以上内容可以用指针函数和函数指针数组理解