一维数组与指针
int a[10];
int *pa;
一维数组的数组名代表的就是该数组第一个元素的地址,所以复制语句pa=&a[0]等价于pa=a。对数组元素a[i]的引用等价于*(a+i)。在计算数组元素a[i]的值时,C语言实际上先将*(a+1)
后再进行求值,所以用指针编写的程序比用数组下标编写的程序执行速度快。
数组名和指针之间有一个不同之处,指针是一个变量,pa=a和pa++都是合法的。但数组名不是变量,因此,类似于a=pa和a++形式的语句是非法的。
当把数组名传递给一个函数时,实际传递的是该数组第一个元素的地址。在被调用函数中,该参数是一个局部变量,因此,数组名参数必须是一个指针。
在函数定义中,形式参数char s[]和char *s是等价的。通常使用后一种形式,因为它比前者更直观地表明了该参数是一个指针。
字符数组和字符指针
char a[] = "Hello";
char *p = "Hello";
a是一个仅仅足以存放初始化字符串以及空字符’\0’的一维数组。数组中的单个字符可以进行修改,但a始终指向同一个存储位置。
p是一个指针,其初值指向一个字符串常量,之后它可以被修改以指向其他地址,但是不能修改字符串的内容。
char *p = “Hello”,”Hello”保存在静态数据区,该数据不能修改。由指针m指向,不能通过指针m来修改静态数据区的值。
char a[] = “Hello”,”Hello”保存在栈空间数组里。 数组名为a, 变量名为数组的首地址。可以通过a[i]=’a’或*(a+i)=’a’的形式来修改数组内容。
指针与多维数组
比较两个定义
int a[10][20];
int *b[10];
从语法角度讲,a[3][4]和b[3][4]都是对int对象的合法引用。但a是一个真正的二维数组,它分配了200个int类型长度的存储空间,并且通过常规的矩阵下标计算公式20*row+col计算得到元算a[row][col]的位置。
但是对b来说,该定义仅仅分配了10个指针,并且没有对它们初始化。假定b的每个元素都指向一个具有20个元素的数组,那么编译器就要为它分配200个int类型长度的存储空间以及10个指针的存储空间。
指针数组的优点
数组的每一行长度可以不同,b的每个元素不一定都指向一个具有20个元素的数组。
二维数组和二维指针
二维数组和二维指针的概念很容易混淆,请看下面这段代码。
#include <stdio.h>
void print_str(char** strs, int i)
{
printf("strs[%d]=%s", i, strs[i]);
}
int main(int argc, char **argv)
{
char strs[10][100] = {"Hello", "World"};
print_str(strs, 0);
return 0;
}
执行这段程序,会得出现Segmentation Fault。
Segmentation Fault是由于程序试图访问不该访问的内存,详细介绍见Wikipedia
https://en.wikipedia.org/wiki/Segmentation_fault
变量strs实际为一个类型为char (*)[100]
的指针,指向二维数组的第一个元素,即一个包含100个char的数组,指针的值实际上也等于二位数组第一个char元素的地址。print_str函数的参数声明为char**
,即二级指针,参数传递不会改变指针的值。所以strs的值跟main中定义的strs的值是一样的,不过print_str中的strs是二级指针。那么str[0]则是一级指针,strs[0]=*(strs+0),那么strs[0]的值为字符串“Hell”所占四个字节的值对应的地址,“Hell”对应的ASCII码为0x48 0x65 0x6C 0x6C, 组成的地址为0x6C6C6548(little endian),而此处内存禁止访问,所以出现了Segmentation Fualt。