1. 数组和指针的访问
初接触C语言时,常发现在很多情况下数组和指针的使用是可以互换的,因此对两者的区分相对含混不清。但在某些情况下却编译报错无法运行,比如在一个文件中定义为数组int s[100];在另一个文件中声明为指针extern int *s;两者间将会因为类型不匹配而报错。
首先,从数组与指针名区分,数组名是一个不可修改的常量,而指针名是一个可以修改的变量,因此如下图所示,数组名不可作为被赋值对象,而指针可以:
其次,数组名相当于地址,其内存放该数组的首个元素,而数组元素是挨个存放在连续内存中的,可以通过数组名 + 偏移量(下标)直接访问某个特定元素;指针名虽然也相当于地址,但其内存放的仍是地址,通过指针名 + 下标访问某个元素是先取出指针名对应地址内存放的地址,然后再加上下标偏移量得出存放该元素的地址,属于间接访问。
最后,数组与指针的其他区别如下表示:
指针 | 数组 |
---|---|
保存数据的地址 | 保存数据 |
间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据;如果指针有下标,p[i]是把指针p的内容加上i作为地址并从中取得数据 | 直接访问数据,a[i]只是简单的以(a+i)为地址取得数据 |
通常应用于动态数据结构 | 通常用于存储固定数目且数据类型相同的元素 |
相关的函数为malloc(), free() | 隐式分配和删除 |
通常指向匿名数据 | 自身即为数据名 |
数组和指针虽然都可以在它们的定义中用字符串常量进行初始化,但定义指针时,编译器并不为指针多指向的对象分配空间,而只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化(初始化指针时所创建的字符串常量被定义为只读,无法通过指针修改字符串的值,且只能通过字符串常量初始化指针,而不能通过数值常量初始化指针)。
2. 数组和指针的可交换性
数组主要有声明(定义算是分配内存空间的特殊声明)和使用两种场景,一般情况下声明(想了解关于声明的解析,可参考《分析C语言的声明》)不可以混用,在表达式中的使用可以混用,但是C编译器在编译时都把作为函数参数的数组名转换为指针,所以在数组作为函数参数声明时跟指针形式是等效的。
上表源于ANSI C的三条规则:
- 表达式中的数组名(与声明不同)被编译器当作一个指向该数组第一个元素的指针;
- 下标总是与指针的偏移量相同; 规则3.规则2. 下标总是与指针的偏移量相同;
- 在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。
上面谈到的都是数组可以转换为指针的情况,比如在表达式中和在函数参数的声明中数组名与指针是等效的,但在其他情况下定义与声明必须匹配;需要强调的一点是,指针始终就是指针,你虽然可以用下表形式访问指针,但绝不可以改写为数组。
3. 多维数组与指针数组
- 严格说C语言并不直接支持多维数组,而是通过“数组的数组”的形式实现多维数组的效果。比如三维数组char array[i][j][k]不能写为char array[i,j,k]。
- 数组标识符[]优先级基本处于最高级,且遵循从左到右的结合顺序,所以最右侧的下标在连续内存块儿中是最先变化的。
- 由于多维数组也是以线性排列在连续内存块儿中,故多维数组可以通过表达式运算降维访问,比如访问array[i][j][k]也可以通过array[i*j_max*k_max + j*k_max + k]访问(j_max, k_max分别是j, k所在维度的元素总数)。
- 数组的数组array[i][j]可以表示为指针数组*array[i]的形式,它们都被C编译器解释为 *(*(array+i)+j)。但正如指针与数组不完全等效一样,数组的数组与指针数组也不完全等效,两者的访问过程如下所示:
- 数组的数组每行长度需要一致(需要以行长度作为判断每行是否结束的依据),灵活性受限;字符串指针数组则每行的长度可以不一样(字符串指针可以以每行字符串末尾的NUL判断该行是否结束),使用比较灵活。
- C语言并不能直接把数组的数组作为函数参数传递给一个函数,函数参数也是被编译为指针形式传递给函数的,常见的作为参数传递多维数据的形式有字符串指针数组my_function(char *my_array[]),或指针的指针my_function(char **my_array),二者是等价的,作为函数参数编译结果一致。
- C语言也不能通过函数调用返回一个数组,而只能返回一个指针,可以是任何数据类型的指针,比如数组指针int (*array)[i],结构体指针struct *p等,但除了函数返回值类型匹配外,需要注意的是要返回的指针不能是函数体内定义的局部指针变量(可以是全局变量或static变量),因函数返回后在函数体内定义的局部变量生命周期已经结束,后续引用返回指针会导致错误。
- 最后强调的一点是,指针可以通过malloc(), free()在运行时动态分配内存空间(数组在定义时分配内存空间),而且可以根据后续需要通过realloc()函数进行动态空间扩容或缩减,给出一段代码示例如下:
#include <stdio.h>
#include <stdlib.h>
int current_element = 0;
int total_element = 12;
char *dynamic,*ptemp;
int add_element(char c)
{
if(current_element == total_element - 1){
total_element *= 2; //如果表满了,自动将表元素总数翻倍
ptemp = (char *)realloc(dynamic,total_element); //使用一个临时指针接收返回值,防止realloc()失败导致原表数据丢失
if(dynamic == NULL){
printf("Coundn't expand the table.\n");
return -1;
}else{
dynamic = ptemp; //若realloc()返回非空,则将其返回值赋给原表的指针
printf("The table is expand to %d.\n",total_element);
}
}
current_element++;
dynamic[current_element] = c;
return 0;
}
void main(void)
{
dynamic = (char *)malloc(total_element); //全局变量dynamic通过函数返回赋值需要在函数体内进行,在函数外只能用常量初始化
if(dynamic == NULL){
printf("Insufficient memory.\n");
return;
}
while(!add_element(getchar())){ //循环添加元素,如果表长度不够自动扩展,直到因内存空间受限表扩展失败
}
return;
}