一.前言
指针的主题,我们在初级阶段的【C指针详解】初阶篇 章节已经接触过了,我们知道了指针的概念:
指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
指针的大小是固定的4/8个字节(32位平台/64位平台)。
指针是有类型的,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
指针的运算。
野指针。
二级指针。
这个章节,我们继续探讨指针的高级主题。
1. 字符指针
我们知道在指针的类型中有一种指针类型为字符指针 char* ;
顾名思义,字符指针就是用来存放字符的地址。
一般使用:
int main() { char ch = 'w'; char *pc = &ch; *pc = 'w'; return 0; }
还有一种使用方式如下:
int main() { const char* pstr = "hello bit."; printf("%s\n", pstr); return 0; }
大家思考一下,这里是把一个字符串"hello bit."放到pstr指针变量里了吗?
应该不是的,pstr是一个字符指针,是用来存放字符的地址的,而"hello bit."是一个字符串,即使我们想把它放到pstr中,也是不可行的。
那这句代码的结果是啥呢?
const char* pstr = “hello bit.”; ——本质上是把字符串 “hello bit.” 首字符 h 的地址放到了pstr中,使得指针pstr指向该字符串。
那就有这样的面试题:
int main() { char str1[] = "hello bit."; char str2[] = "hello bit."; const char* str3 = "hello bit."; const char* str4 = "hello bit."; if (str1 == str2) printf("str1 and str2 are same\n"); else printf("str1 and str2 are not same\n"); if (str3 == str4) printf("str3 and str4 are same\n"); else printf("str3 and str4 are not same\n"); return 0; }
大家仔细思考一下,结果是什么?
我们来看一下:
结果说明str1和str2是不一样的,而str3和str4本质上是一样的,那为什么呢?
首先我们来分析一下"hello bit.",这里的"hello bit."是一个常量字符串,而常量字符串是不能被修改的,在内存中仅保留一份。
这也是有时候在代码最前面加上const的原因(const char* str3 = “hello bit.”;)。
C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候会开辟出不同的内存块。
了解了常量字符串的存储机制之后,我相信大家就明白结果是怎么回事了。
1. 我们用相同的常量字符串"hello bit."去初始化不同的数组str1和str2的时候,数组str1和str2会开辟出不同的内存块(相当于我们只是借用常量字符串"hello bit."去初始化了两个数组,但这两个数组各自开辟了自己的空间),而数组名str1和str2表示的是数组首元素的地址,那两个数组的空间是不同的,它们首元素的地址自然也就不同了。
2. 而指针str3和str4指向的是同一个常量字符串,它们存储的都是字符串"hello bit."的首字符’h’的地址,所以str3和str4本质上是一样的
2. 指针数组
先问大家一个问题,指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
我们已经知道了整型数组,字符数组等。
int arr1[5];
char arr2[6];
那指针数组是什么样子的,我们举个例子,定义这样一个指针数组:
int* arr3[5];
应该是这样的意思:arr3是一个数组,有五个元素,每个元素是一个整形指针。
来看几个其它类型的指针数组:
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3 [5]; //二级字符指针的数组
3. 数组指针
3.1 数组指针的定义
数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针,用来存放数组的地址。
下面代码哪个是数组指针?
int *p1[10]; int (*p2)[10];
答案是:int (*p2) [10]是数组指针,int *p1[10]是指针数组。
解释:p先和*结合,说明p是一个指针变量,然后指向一个数组,数组有10个元素,每个元素的类型是 int 。
所以p是一个指针,指向一个数组,叫数组指针。
这里要注意: [ ]的优先级要高于 * 号的,所以必须加上()来保证p先和 * 结合。
当然,我们知道数组有很多类型,那自然就有不同类型的数组指针,比如:
char (*p3) [20]; double (*p4) [5]; float (*p5) [8];
3.2 &数组名VS数组名
对于下面的数组:
int arr[10];
arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:
#include <stdio.h> int main() { int arr[10] = {0}; printf("%p\n", arr); printf("%p\n", &arr); return 0; }
结果会是什么呢?
可见数组名和&数组名以地址的形式打印出来是一样的。
难道两个是一样的吗?
我们再看一段代码:
#include <stdio.h> int main() { int arr[10] = { 0 }; printf("arr = %p\n", arr); printf("&arr= %p\n", &arr); printf("arr+1 = %p\n", arr + 1); printf("&arr+1= %p\n", &arr + 1); return 0; }
这次结果还会一样吗?
我们看到,arr和&arr打印出来虽然是一样,但是arr+1和&arr+1,却完全不一样,这说明,&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上:
&arr 表示的是整个数组的地址,而arr是数组首元素的地址。
所以,arr和&arr打印出来才会不一样。
arr和arr+1的差值是4,即数组arr(整型数组)一个元素的大小,因为arr是一个整型元素的地址,是int * 类型的指针,步长为4个字节。
而本例中 &arr 的类型是: int (*) [10] ,是一种数组指针类型,加1就应该跳过一个该数组的大小,而int arr[10]的大小是40个字节,我们看到&arr和&arr+1的差值是68-40=28,但我们要知道编译器给我们打印出来的地址是以16进制展示的,而16进制数28转换为10进制就是40(2x16 ^ 1+8x16 ^ 0=40) 。
最后,再给大家补充一点:
补充:
1. sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
2. &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
除此1,2两种情况之外,所有的数组名都表示数组首元素的地址。
3.3 数组指针的使用
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
看代码:
#include <stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p return 0; }
但是我们一般很少这样写代码,通常用在二维数组中。
#include <stdio.h> void print_arr1(int arr[3][5], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } //数组指针的方法 void print_arr2(int(*arr)[5], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { printf("%d ", arr[i][j]); //等价于printf("%d ", *(*(arr + i) + j)); } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 }; print_arr1(arr, 3, 5); print_arr2(arr, 3, 5); return 0; }
大家先仔细看看这段代码,然后我们一起来分析一下:
上面的代码我们实现了2个函数,功能都是打印数组arr [3][5]的元素,而且我们传过去的实参也一样,两个函数的不同之处在于接收参数arr 的方式不同:
函数print_arr1用的是还是数组来接收,我们知道这样当然是可以的。
函数print_arr2用的是指针来接收,而且用的就是我们刚学的数组指针。
看到这里大家可能会有一个疑惑,我们传过去的是数组名arr,是数组首元素的地址,但是我们为什么用了一个数组指针(int(*arr)[5])来接收呢?
首先,数组名arr是数组int arr[3][5]首元素的地址,这肯定是没问题的。
但是,我们要注意,这里的int arr[3][5]是一个二维数组,二维数组的首元素是二维数组的第一行(相当于一个一维数组),所以这里传递的arr其实相当于第一行的地址,是一维数组的地址,既然是数组的地址,当然要用数组指针来接收了。
而数组int arr[3][5]的第一行有5个元素,每个元素的类型是int ,所以我们就用一个整型数组指针(int(*arr)[5])来接收。然后我们就可以访问数组 int arr[3][5] 并打印它的元素了。
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
int arr [5] ; int *parr1[10]; int (*parr2)[10]; int (*parr3[10])[5];
我们一起看一下:
int arr [5] ——整型数组
int *parr1[10] ——整型指针数组,10个元素,每个元素类型为整型指针
int (*parr2) [10] ——数组指针,指向一个数组,10个元素,元素类型为int
int (*parr3[10]) [5] ——数组指针数组:parr3是一个存放数组指针的数组,能存放10个数组指针,每个数组指针指向一个整型数组,5个元素。