C语言数组与指针并不相同---《C专家编程》

1. 数组和指针的访问

初接触C语言时,常发现在很多情况下数组和指针的使用是可以互换的,因此对两者的区分相对含混不清。但在某些情况下却编译报错无法运行,比如在一个文件中定义为数组int s[100];在另一个文件中声明为指针extern int *s;两者间将会因为类型不匹配而报错。

首先,从数组与指针名区分,数组名是一个不可修改的常量,而指针名是一个可以修改的变量,因此如下图所示,数组名不可作为被赋值对象,而指针可以:
地址与地址的内容之间的区别
其次,数组名相当于地址,其内存放该数组的首个元素,而数组元素是挨个存放在连续内存中的,可以通过数组名 + 偏移量(下标)直接访问某个特定元素;指针名虽然也相当于地址,但其内存放的仍是地址,通过指针名 + 下标访问某个元素是先取出指针名对应地址内存放的地址,然后再加上下标偏移量得出存放该元素的地址,属于间接访问。
数组的下标引用
对指针的引用
对指针下标的引用
最后,数组与指针的其他区别如下表示:

指针数组
保存数据的地址保存数据
间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据;如果指针有下标,p[i]是把指针p的内容加上i作为地址并从中取得数据直接访问数据,a[i]只是简单的以(a+i)为地址取得数据
通常应用于动态数据结构通常用于存储固定数目且数据类型相同的元素
相关的函数为malloc(), free()隐式分配和删除
通常指向匿名数据自身即为数据名

数组和指针虽然都可以在它们的定义中用字符串常量进行初始化,但定义指针时,编译器并不为指针多指向的对象分配空间,而只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化(初始化指针时所创建的字符串常量被定义为只读,无法通过指针修改字符串的值,且只能通过字符串常量初始化指针,而不能通过数值常量初始化指针)。

2. 数组和指针的可交换性

数组主要有声明(定义算是分配内存空间的特殊声明)和使用两种场景,一般情况下声明(想了解关于声明的解析,可参考《分析C语言的声明》)不可以混用,在表达式中的使用可以混用,但是C编译器在编译时都把作为函数参数的数组名转换为指针,所以在数组作为函数参数声明时跟指针形式是等效的。
什么时候数组与指针相同
上表源于ANSI C的三条规则:

  1. 表达式中的数组名(与声明不同)被编译器当作一个指向该数组第一个元素的指针;
  2. 下标总是与指针的偏移量相同; 规则3.规则2. 下标总是与指针的偏移量相同;
  3. 在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。

上面谈到的都是数组可以转换为指针的情况,比如在表达式中和在函数参数的声明中数组名与指针是等效的,但在其他情况下定义与声明必须匹配;需要强调的一点是,指针始终就是指针,你虽然可以用下表形式访问指针,但绝不可以改写为数组。

3. 多维数组与指针数组

  1. 严格说C语言并不直接支持多维数组,而是通过“数组的数组”的形式实现多维数组的效果。比如三维数组char array[i][j][k]不能写为char array[i,j,k]。
  2. 数组标识符[]优先级基本处于最高级,且遵循从左到右的结合顺序,所以最右侧的下标在连续内存块儿中是最先变化的。
    多维数组的存储
  3. 由于多维数组也是以线性排列在连续内存块儿中,故多维数组可以通过表达式运算降维访问,比如访问array[i][j][k]也可以通过array[i*j_max*k_max + j*k_max + k]访问(j_max, k_max分别是j, k所在维度的元素总数)。
  4. 数组的数组array[i][j]可以表示为指针数组*array[i]的形式,它们都被C编译器解释为 *(*(array+i)+j)。但正如指针与数组不完全等效一样,数组的数组与指针数组也不完全等效,两者的访问过程如下所示:
    一个数组的数组
    一个字符串指针数组
  5. 数组的数组每行长度需要一致(需要以行长度作为判断每行是否结束的依据),灵活性受限;字符串指针数组则每行的长度可以不一样(字符串指针可以以每行字符串末尾的NUL判断该行是否结束),使用比较灵活。
  6. C语言并不能直接把数组的数组作为函数参数传递给一个函数,函数参数也是被编译为指针形式传递给函数的,常见的作为参数传递多维数据的形式有字符串指针数组my_function(char *my_array[]),或指针的指针my_function(char **my_array),二者是等价的,作为函数参数编译结果一致。
  7. C语言也不能通过函数调用返回一个数组,而只能返回一个指针,可以是任何数据类型的指针,比如数组指针int (*array)[i],结构体指针struct *p等,但除了函数返回值类型匹配外,需要注意的是要返回的指针不能是函数体内定义的局部指针变量(可以是全局变量或static变量),因函数返回后在函数体内定义的局部变量生命周期已经结束,后续引用返回指针会导致错误。
  8. 最后强调的一点是,指针可以通过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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值