数组
一维数组
数组名
int a;
是一个标量变量,代表单个整数值。
int b[10];
是一个数组,b是数组名。
数组可以通过数组名和下标访问元素,在C语言中,数组名实际是指向数组第一个元素的常量指针。b
的类型可以被认为是 const int *
(如果数组是 int
类型的话)。尽管数组名在某些情况下表现得像指针,但数组和指针并不是完全相同的概念。数组有固定的大小,而指针只是一个存储内存地址的标量值。
在两个特殊情况下,数组名不被视为指针常量:
- 当数组名用作
sizeof
操作符的操作数时,sizeof
返回的是数组的总字节数,而不是指针的大小。 - 当数组名是取址操作符
&
的操作数时,结果是一个指向数组的指针,而不是指向数组首元素的指针。
int a[10];
int b[10];
int *c;
c=&a[0];
c = &a[0];
和 c = a;
,两者等价,因为 a
实际上是 &a[0]
的简写,即指向数组首元素的指针。
直接赋值一个数组给另一个数组(如 b = a;
)或者将一个指针变量的值赋给一个数组名(如 a = c;
)是非法的,数组名是一个指针常量,它的值是不可更改的。
下标引用
假设 b
是一个指向整型 (int
) 的指针
*(b+3)
因为b是一个指针,b + 3
根据整型值的长度(通常是4个字节)进行调整,结果是一个新的指针,它指向数组中从 b
开始向后移动三个整数长度的位置。然后通过对这个新指针执行间接访问操作(*),可以访问该位置的值(作为右值,用于读取),或者将一个新值存储在该位置(作为左值,用于写入)。
array[subscript]
*( array +( subscript ))
使用下标来引用数组元素的地方,你可以用相应的指针表达式来替换它
intarray[10];
int *ap = array + 2;
加深理解的例子:
写出下面关于ap表达式的等价表达式(数组名为array)。
表达式(默认为右值) | 等价表达式 |
ap | &array[2] |
*ap | array[2] |
ap[0] | ap[0]等价与*(ap+0)=> array[2] |
ap+6 | &array[8] |
*ap+6 | array[2]+6 |
*(ap+6) | array[8] |
ap[6] | ap[6]等价于*(ap+6)=> array[8] |
&ap | &&array[2]但是值是位置的。 |
ap[-1] | 等价于*(ap-1)=>array[1] |
ap[9] | 等价于*(ap+9)=>array[11] 数组越界 |
检测数组下标越界在C语言中是一个困难的事情。
2[array]
=>*(2 +( array ))
=>array[2]
//只要有下标引用就会转换成对等的间接访问表达式。
指针与下标
指针和下标都可以访问元素,那么该如何选择呢?
假定两种方法都正确,下标绝不会比指针更有效率,但指针会比下标更有效率。
int array[10], a;
for(a= 0; a< 10; a +=1 )
array[a] = 0;
使用下标访问数组时,编译器会在程序中插入指令来计算下标的值,并将其与整型值的长度(例如4个字节)相乘,以找到正确的内存位置。这个乘法运算在每次循环迭代时都需要执行,因此需要额外的时间和空间。
int array[10],*ap;
for( ap = array; ap< array + 10; ap++ )
*ap = 0;
使用指针遍历时,尽管在初始化指针时也需要涉及乘法运算(例如 array + 10
),但在循环体内每次增加指针时,实际上只是将一个固定的数值(如4)加上指针。由于这个乘法是与固定值进行的,编译器可以在编译时优化这个操作,从而在运行时只需要执行简单的加法操作。
当在数组中以固定步长(如每次加1)移动时,使用指针方式比使用下标的方式更加高效,因为在运行时减少了乘法运算的需求,从而使程序更快、更小。
array[a] = 0;
指针的效率
1.使用指针遍历数组:当你按照固定步长(如每次加1)在数组中移动时,使用指针变量通常会产生更高效的代码。
2.寄存器变量:将频繁使用的指针声明为寄存器变量(register)可以提高效率,具体提升程度取决于具体的机器架构。
3.循环终止条件:如果可以通过检查已初始化并适当调整过的变量来决定循环是否应该结束,那么就不需要额外的计数器变量来控制循环。
4.运行时求值的成本:在循环或其他频繁执行的代码中,运行时求值的表达式(如计算数组下标)通常比那些可以在编译时确定的常量表达式(如 &array[SIZE]
或 array + SIZE
)要消耗更多的资源。
效率与可读性的权衡:为了微小的性能提升(如几十微秒的执行时间),是否值得牺牲代码的可读性和可维护性?虽然在某些情况下(如实时系统)性能至关重要,但在大多数情况下,答案是否定的。
复杂的代码增加了错误的可能性,且维护人员可能不如原作者经验丰富,这会增加维护成本。
而且现代计算机可能内置了专门针对数组下标操作的优化指令,在这些平台上,使用下标可能比使用指针更高效,即使编译器可能不会同样优化指针操作。但是理解复杂的、效率更高的代码仍然是必要的,因为有时你可能会遇到这种代码。在大多数情况下,重要的是识别出程序中最耗时的部分,然后集中精力优化这些部分。这比广泛地应用各种优化技巧更有效。
数组和指针
int a[5];
int *b;
段内存的起始地址就是数组的名字a。数组名a是指向数组第一个元素的常量指针,并且这个地址是固定,不能重新给数组名赋一个新的地址。指针变量声明只为指针本身分配内存,用于存储一个地址。
指针可以被初始化为指向任何有效的内存位置,也可以通过赋值操作指向不同的地址。
*a是合法的,*b 却是非法的,*b将访问内存中某个不确定的位置。
b++可以通过编译,但 a++却不行,因为a的值是个常量。
作为函数参数的数组名
/* 把第2个参数中的字符串复制到第1个参数指定的缓冲区. */
void strcpy( char *buffer, char const *string ){/* 重复复制字符,直到遇见 NUL字节。*/while((*buffer++ = *string++) != '\0′);
}
char const *string这样的类型声明时,string是一个指向字符的指针,这个字符是指向的内容是不可修改的。通过string指针所访问到的字符串是只读的。
声明数组参数
int strlen(char *string);
int strlen(char string[]);
上面两个声明等价,数组当作参数传入函数本质是传入指针,但是它传递的只是数组第一个元素的指针,但是这种方法无法让函数知道数组的确切长度,所以需要把数组长度显示传入函数中。
初始化
数组初始化如下:
int vector[5]={ 10,20,30,40,50 };
静态数组的初始化:
局部变量数组的初始化:
自动计算数组长度:
int vector[] ={ 1,2,3,4,5 };
字符数组的初始化:
char message[] = "Hello";
多维数组
如果数组的维度不止1,就是多维数组。
int a;
int b[10];
int c[6][10];
int d[3][6][10];
a是整数。
b就是一个向量,包含10个整型元素。
c在b的基础上增加一维,可以把c看作是一个包含6个元素的向量,每个元素本身是一个包含10个整型元素的向量。c是个一维数组的一维数组。
d是一个包含3个元素的数组,每个元素都是包含6个元素的数组,6个元素中的每一个又都是包含10个整型元素的数组。d是一个3排6行10列的整型三维数组。
存储顺序
int array[3][6];
C中多维数组的元素存储顺序按照最右边的下标率先变化的原则,称为行主序。
数组名
一维数组名的值是一个指针常量,类型是指向元素类型的指针,指向数组的第1个元素。
多维数组类似,多维数组第1维的元素是另一个数组。
下标
matrix[1][5]
下标引用实际上只是间接访问表达式的一种伪装形式。
matrix:指向包含10个整型元素的数组的第一个元素的指针。
matrix+1:指向包含10个整型元素的数组的第二个元素的指针。
*(matrix + 1):指向第二行数组的第一个元素
*(matrix+1)+5
整个表达式的结果是一个指针,指向的位置比原先那个表达式所指向的位置向后移动了5个整型元素。
*(*(matrix +1)+ 5) 对其间接引用,访问的是图中的整型元素。
*(matrix+1)改写为matrix[1]:*( matrix[1]+5)。
再次用下标代替间接访问:matrix[1][5]。
指向数组的指针
int vector[10], *vp = vector;//合法
int matrix[3][10], *mp = matrix;//非法
matrix 并不是一个指向整型的指针,而是一个指向整型数组的指针。
声明指向整型数组的指针:
int (*p)[10];
p是个指针,然后指向一个整型数组。
int (*p)[10] = matrix; //合法
作为函数参数的多维数组
一维数组作为参数
void funcl(int *vec);
void funcl(int vec[]);
二维数组作为参数,可以传入
void func2(int (*mat)[10]);
void func2(int mat[][10]);
编译器必须知道第2个及以后各维的长度才能对各下标进行求值。
void func2( int **mat);不正确,指向整型数组的指针不等于指向整数指针的指针。
初始化
int matrix[2][3] ={ 100,101,102,110,111,112 };int two_dim[3][5] = {
{ 00,01,02,03,04 },
{ 10,11,12,13,14 },
{ 20,21,22,23,24 }
};int two_dim[][5] = {
{ 00,01,02 },
{ 10,11 },
{ 20,21,22,23 }
};
指针数组
in *api[10] ;
数组中每个元素都是指向整型的指针。