前言
指针的概念在之前的文章里已经讲解过了,其本质就是变量的地址。
- 内存中的每个字节都会有自己的地址,每个字节的地址都能够用指针变量进行接收。
- 指针变量的大小是4/8个字节,32位平台上占用4字节,64位平台上占用8个字节。
- 指针变量是有类型的,指针变量的类型决定了指针±整数的步长,指针类型决定的指针变量的权限。
- 相同类型的指针变量之间的减法运算的结果是:这两个指针指向的两个元素间的元素个数。(与高地址减去低地址还是低地址减去高地址无关)
1.字符指针
首先介绍字符指针,这是一种常见的使用方式:
int main() { char ch = 'w'; char* pc = &ch; *pc = 'h'; return 0; }
还有一种使用方式如下:
int main() { const char* pstr = "hello bit."; printf("%p\n", pstr); printf("%p\n",pstr+1); return 0; }
这里第3行代码是将整个字符串放到了pstr变量中了吗?并不是的。通过打印其地址,可观察到,pstr变量只是存放的字符串首字符的地址。
来个小测试吧:
//这段代码的运行结果是什么? #include <stdio.h> 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都是数组,这两个数组无论其内容是否相同,只要成功被创建,都会在栈区开辟一块空间,由于这两个数组的开辟空间是独立的,所以其地址也是不同的,str1和str2分别存储这两个字符串的首地址,所以str1和str2的值不同。但是str3和str4是两个指针变量,他们都存储同一个字符串的首地址,内存中也没有必要为这个相同的常量字符串开辟两块空间,即C/C++会把常量字符串存储到单独的一个内存区域,所以str3和str4的值是一样的。
2.指针数组
所谓指针数组,本质上就是一个存放指针的数组。
char* arr[10];//存放一级字符指针的数组 int* arr[10];//存放一级整形指针的数组 char** arr[10];//存放二级字符指针的数组
3. 数组指针
数组指针本质上是指针。
int *pint;是指向int数据类型的指针
float *pf;是指向float类型的指针
那么数组指针就是指向数组的指针,存放数组的起始地址。
下面两个哪个是数组指针呢?
int (*parr)[10];
int *parr[10];
其中 int(*parr)[10];才是数组指针的正确写法。而 int *parr[10];这种写法,parr变量会先和[]结合,这样子parr变量就被翻译成了数组名了,明显不符合要求。
数组指针的写法说明:parr先和*结合,说明parr是个指针变量,接着去掉*parr这部分,剩下的部分就是该指针指向的元素的类型,指向有10个元素的整形数组。
注意:[]的结合性是高于*的,所以需要一个小括号保证变量名先与*结合。
4 &数组名和数组名的异同
对于所有的数组而言,数组名代表的是数组首元素的地址
int arr[10];
对于这个整型数组来讲,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是数组名,代表的是数组首元素的地址,也就是int数据类型的地址,即arr的类型是int*型,要用int*接收, int *p = arr;
&arr取出的是整个数组的地址,也就是int [10]的地址,即&arr的类型是 int(*)[10],要用int(*)[10]接收, int (*p)[10] = &arr;
通过警告信息来看,=右边的类型是int(*)[10],可再次佐证我们的说法.
4.1 数组指针的用法
数组指针可以用于接收数组的地址,如下:
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int(*p)[10] = &arr; for (int i = 0; i < 10; i++) { printf("%d ",(*p)[i]); } return 0; }
但是一般不这样使用。通常利用数组指针接收二维数组传参:
void print(int(*p)[3],int col,int row) { int i = 0; for (i = 0; i < col; i++) { int j = 0; for (j = 0; j < row; j++) { printf("%d ", p[i][j]); } printf("\n"); } } int main() { int arr[2][3] = {1,2,3,4,5,6}; print(arr,2,3); return 0; }
我们通常可以将二维数组看做一维数组,这个一维数组的每个元素也是一个一维数组,那么就可以很好的解释上面这段代码了,主函数向print函数传参时,传递的是二维数组的数组名,这个数组名代表的二维数组首元素的地址,也就是一维数组的地址,那么在print函数的形参部分,可以选择使用二维数组接收,即 int arr[2][3];进行接收。也可以选择使用数组指针进行接收,即 int (*p)[3];进行接收。
这里[]内为什么写的是3而不是2呢?因为这里接收的一维数组的地址,该二维数组的每个元素都是含有三个元素的一维数组。
看看下面这段代码的含义吧
int arr[5];//整型数组 int *parr1[10];//整型指针数组 int (*parr2)[10];//整型数组指针 int (*parr3[10])[5];//整型数组指针数组
int (*parr2)[10];:先看变量名,先与*结合,说明parr2是个指针,接着将*parr2去掉,剩下的就是该指针指向的元素类型,即 int [10];即该指针指向一个含有10个元素的整型数组。
int (*parr3[10])[5];:先看parr3这个变量,它先于[]结合,说明parr3是个数组,将parr3[10]去掉,剩下部分就是数组元素类型,即 int(*)[5],所以数组的元素类型就是 int(*)[5];(整型指针数组)。