目录
1 指针的理解
1.1 内存和地址
计算机按字节编址,每一个字节都有自己的地址,如图所示:每一个正方形都是一个字节
1.2 通俗理解指针
很多教材上,指针是“指向”某某某的。初学者最好不要用“指向”二字理解指针。
第一,“指针”是一个变量,用于保存一个地址,这个地址的数据类型在定义指针变量时确定。
第二,指针变量可以赋值。是将其它变量的地址赋值给指针,或者其他指针的值(也是地址)赋值给指针。
例子:
int a = 123; //定义一个变量a,用于保存一个int类型。
int * b; //定义一个指针变量b,用于保存一个地址,这个地址所保存的数据应该是int类型。
b=&a; //变量a的地址赋值给b。“&”操作是取变量的地址。
int * c; //定义一个指针c
c=b; //将b的值赋值给c,b指针的值是a的地址,因此c指针的值是a的地址。
上述代码的图解如下:
可以看到:
- 指针保存了变量a的地址,而由于指针b和指针c是变量,所以也需要内存来保存指针b和指针c,所以指针b和指针c本身也有地址
- 由于指针b和指针c保存了变量a的地址,就相当于指向了变量a,这也是教材“指向”的含义
- 仍然建议不要理解为“指向”,指针的本质是变量,存储的是地址
更深层次理解指针,实际上指针变量存储的是首字节的地址,如图所示:
2 指针步长
指针+1:逻辑上加1,实际上需要根据指针类型而定。char * 类型指针,步长是1个字节;int * 类型指针,步长是4个字节;double * 类型指针,步长是8个字节
void test02()
{
char c = 'a';
char *p1 = &c;
printf("p1 = %d\n", p1);
// 指针可以和整数进行运算,运算结果就是另外一个地址
printf("p1 = %d\n", p1 + 1);
printf("--------------\n");
int a = 10;
int *p2 = &a;
printf("p2 = %d\n", p2);
printf("p2 = %d\n", p2 + 1);
}
结果如下:
int*指针跳了4个字节,char*指针跳了一个字节
实际上这种功能非常适用于对数组的操作,例子如下:
void test03()
{
int arr[] = { 1, 2, 3, 4 }; // 16个字节
// &(arr[0])
// p 指针指向数组的第一个元素
int* p = &(arr[0]);
printf("p = %d %d\n", *p, p);
printf("p = %d %d\n", *(p+1), p+1);//跳到arr[1]
printf("p = %d %d\n", *(p + 2), p + 2);//跳到arr[2]
printf("p = %d %d\n", *(p + 3), p + 3); 跳到arr[3]
}
3 数组指针和指针数组
3.1 指针引用数组
int a[5] = {1,2,3,4,5};
int *p;
p = &a[0]; //将a[0]地址赋值给指针p
数组名是个指针常量,因此 p = a 也是把数组a的首地址赋值给指针p,因此也可以写成
int a[5] = {1,2,3,4,5};
int *p;
p = a; //将a[0]地址赋值给指针p
通过指针步长,可以操作数组,例子如下:
void test03()
{
int arr[] = { 1, 2, 3, 4 }; // 16个字节
// &(arr[0])
// p 指针指向数组的第一个元素
int* p = &(arr[0]);
printf("p = %d %d\n", *p, p);
printf("p = %d %d\n", *(p+1), p+1);//跳到arr[1]
printf("p = %d %d\n", *(p + 2), p + 2);//跳到arr[2]
printf("p = %d %d\n", *(p + 3), p + 3); 跳到arr[3]
for (int i = 0;i < 4;i++) printf("%d",*(p+i));//遍历数组
}
3.2 理解数组指针和指针数组
先明确运算符优先顺序:()>[]>*(如下图所示)
int (*p)[n]:根据优先级,先看括号内,可知p是一个指针,这个指针指向一个一维数组,数组长度为n。称为“数组的指针”,即数组指针;
int *p[n]:根据优先级,先看[],则p是一个数组,该数组是一个int *型,因此数组的元素是指针类型,共n个元素。称为“指针的数组”,即指针数组。
例如:
int *p[5];
int (*q)[5];
对于语句“int*p[5]”,它是一个指针数组,该数组包含 5 个int*型的指针,如图所示
对于语句“int(*p2)[5]”,它是一个数组指针,它指向数组的首地址,数组在这里并没有名字,是个匿名数组,如图所示
运算符优先级如图所示:
3.3 指针数组
指针数组:装着指针的数组。
引用别人的例子:
int main()
{
int x = 1;
int y = 2;
int *p[2];
p[0] = &x;
p[1] = &y;
printf("%p\n", p[0]); // x 的地址
printf("%p\n", &x); // x 的地址
printf("%p\n", p[1]); // y 的地址
printf("%p\n", &y); // y 的地址
printf("%d\n", *p[0]); // p[0] 表示 x 的地址,则 *p[0] 表示 x 的值
printf("%d\n", *p[1]); // p[1] 表示 y 的地址,则 *p[1] 表示 y 的值
return 0;
}
同时,指针数组还可以操作二维数组,如下:
int *p[3]; // 一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2],所以要分别赋值
int arr[3][3];
for (int i = 0; i < 3; i++)
p[i] = arr[i];
通过图形理解,如图所示:
3.4 数组指针
数组指针:指向数组的指针。
引用别人的一个例子:
int main()
{
//一维数组
int a[5] = { 1, 2, 3, 4, 5 };
//数组里有5个元素
int (*p)[5];
//把数组a的地址赋给p,则p为数组a的地址,则*p表示数组a本身
p = &a;
//%p输出地址, %d输出十进制
printf("%p\n", a); //输出数组首元素地址
printf("%p\n", p); //根据上面,p为数组a的地址,输出数组a的地址
printf("%p\n", *p); //*p表示数组a本身,一般用数组的首元素地址来标识一个数组
printf("%p\n", &a[0]); //a[0]的地址
printf("%p\n", &a[1]); //a[1]的地址
printf("%p\n", p[0]); //数组首元素的地址
printf("%d\n", **p); //*p为数组a本身,即为数组a首元素地址,则*(*p)为值,**p表示首元素的值1
printf("%d\n", *p[0]); //根据优先级,p[0] 表示首元素地址,则*p[0]表示首元素本身,即首元素的值1
printf("%d\n", *p[1]); //这是错误的,没有意义
return 0;
}
问题一:为什么p = &a而不是p=a
a 和 &a 的数据类型不同,前者是指向数组首元素的指针,后者是指向数组的指针。但它们的值都是首元素地址,区别在于数据类型不同,而p是数组指针,因此是p=&a。
问题二:*p[0]有意义,而*p[1]没有意义
把数组指针理解成一个装着数组指针的数组,如图所示:
该数组只有一个元素即p[0],它指向了数组a。由于没有第二个元素,所以p[1]没意义
同时,数组指针还可以操作二维数组,如下:
int arr[3][4];
int (*p)[4]; // 定义一个数组指针,指向含有4个元素的一维数组
p = arr; // 将该二维数组的首地址赋给 p,也就是 arr[0] 或 &arr[0],二维数组中 p = arr 和 p = &arr[0] 是等价的
p++; // p = p + 1,该语句执行过后 p 的指向从行 arr[0][] 变为了行 arr[1][]
问题一:为什么p=arr而不是p=&arr
arr是一个二维数组,如果将arr的每一行抽象成一个元素,本质上arr就可以抽象成一个一维数组,所以arr代表首元素-即第一行。简单来说,arr代表首行的地址