一、指针基础知识
在开始我们指针的进阶内容之前,我们先来回顾一下与指针相关的基础知识:
1、什么是指针
- 指针是内存中一个最小单元的编号,也就是地址。
- 我们一般口语中说的指针,通常指的是指针变量,也就是用来存放内存地址的变量。
2、指针变量的大小
在32位的机器上,地址由32个0/1组成二进制序列组成,所以地址需要用4个字节的空间来存储,则一个指针变量的大小就应该是4个字节。在64位机器上,地址由64个0/1组成二进制序列组成,所以地址需要用8个字节的空间来存储,则一个指针变量的大小就应该是8个字节。
总结:在 X86 (32位平台) 环境下,一个指针变量的大小 (一个地址的大小) 是四个字节;在 X64 (64位平台) 环境下,一个指针变量的大小 (一个地址的大小) 是八个字节
3、指针类型的意义
- 指针的类型决定了指针进行解引用操作时的访问权限 (解引用时能向后访问几个字节的空间) ;
- 指针的类型决定了指针 ± 整数时候的步长 (+1跳过几个字节) ;
4、指针的运算
- 指针 ± 整数:指针移动整数个元素的大小;
- 指针 - 指针:得到指针之间元素的个数;
- 指针的关系运算:比较两个地址的大小;
5、野指针的成因及规避方法
野指针的成因
- 指针未初始化;
- 指针越界访问;
- 指针指向的空间被释放;
野指针的规避方法
- 使用已初始化的指针;
- 小心指针越界;
- 当指针指向的空间被释放的同时把该指针置为 NULL;
- 避免返回局部变量的地址 (离开该变量的生命周期该变量就会被销毁);
- 指针使用之前检查其有效性;
二、指针进阶知识
1、字符指针
什么是字符指针
顾名思义,字符指针就是用来存放字符地址的指针。
字符指针的两种使用方法
第一种:
int main() { char ch = 'w'; char *pc = &ch; *pc = 'w'; return 0; }
第二种:
int main() { const char* pstr = "hello world"; //这里是把一个字符串放到pstr指针变量里了吗? printf("%s\n", pstr); return 0; }
第一种使用方法很简单,这里我不再赘述;难点是第二种使用方法:在第二个例子中,我们并不是把 “hello world” 这整个字符串放到 pstr 指针变量中,而且 pstr 是指针变量,只能存放四个字节的内容,也存不下这整个字符串;
其实我们是把 “hello world” 这个字符串中首字符的地址,即 ‘h’ 的地址放入 pstr 中,然后我们可以以 %s 的形式把整个字符串打印出来;
同时,“hello world” 这样的字符串被我们称为常量字符串,它是存储在字符常量区的,我们可以通过 pstr 来访问它,但是不能修改它的内容 (因为它是常量),所以这里我们用 const 关键字来修饰 char*,防止有人误该 “hello world” 中的内容。
笔试题练习
下面程序的输出结果是什么?
int main() { char str1[] = "hello world"; char str2[] = "hello world"; const char* str3 = "hello world"; const char* str4 = "hello world"; 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 分别分配一块空间,并把空间里的内容初始化为 “hello world”,同时数组名代表首元素地址,所以 str1 != str2;
对于 str3 和 str4 来说,由于 “hello world” 存放在字符常量区,所以 “hello world” 只会存在一份,只需要让它们同时指向 “hello world” 的空间即可,所以 str3 和 str4 其实存放的都是字符常量区中 “hello world” 中 字符 ‘h’ 的地址,所以 str3 == str4;
2、指针数组
指针数组是什么
顾名思义,指针数组是一个数组,而且是用来存放指针变量的数组;所以指针数组就是存放指针的数组。
指针数组的定义
int* arr[10]; # arr的类型:int* [10] //去掉变量名剩下的就是变量类型 # arr先和[10]结合,表示arr是一个数组,数组里面有10个元素,每个元素的类型是int* char* str[10]; # str的类型 char* [10] //去掉变量名剩下的就是变量类型 # str先和[10]结合,表示str是一个数组,数组里面有10个元素,每个元素的类型是char*
指针数组的使用
int main() { int a = 10; int b = 20; int c = 30; int* arr[3] = { &a, &b, &c }; int i = 0; for (i = 0; i < 3; i++) { *(arr[i]) = i; } printf("%d %d %d\n", a, b, c); return 0; }
3、数组指针
指针数组是什么
顾名思义,数组指针就是一个指针,这个指针指向的是一个数组;所以数组指针就是指向数组的指针。
数组指针的定义
int (*arr)[10]; # arr的类型:int (*)[10] //去掉变量名剩下的就是变量类型 # arr首先和*结合,表示它是一个指针,然后和[10]结合,表示它指向的是一个数组,数组里面有10个元素,每个元素的类型是int; char* (*arr)[10]; # arr的类型:int* (*)[10] //去掉变量名剩下的就是变量类型 # arr首先和*结合,表示它是一个指针,然后和[10]结合,表示它指向的是一个数组,数组里面有10个元素,每个元素的类型是char*;
数组名和&数组名的区别
#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; }
如上所示:数组名和&数组名所得到的地址的起始位置是相同的,但是数组名加1跳过的是一个整形,即4个字节;而&数组名加1跳过的是一个数组,即40个字节;
所以:数组名表示首元素的地址,+1 跳过一个数组元素;而&数组名表表示整个数组的地址,+1 跳过整个数组。
数组指针的使用
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; int(*p)[10] = &arr; //把整个数组的地址赋值给数组指针变量p int i = 0; for (i = 0; i < 10; i++) { //*p找到整个数组,而数组名代表整个数组,所以*p相当于得到数组名, //而数组名又表示首元素的地址,所以*p最终的效果是得到数组首元素的地址 //首元素的地址 +i 再解引用得到数组的各个元素 printf("%d ", *(*p) + i); } return 0; }
虽然上面的使用是正确的,但是我们通常不这样用,因为我们可以直接用 arr[i] 来得到数组的每个元素;数组指针通常用于二维数组:
void print_arr(int(*arr)[5], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { //arr[i] 相当于 *(arr+i) //所以 arr[i][j] 相当于 *(*(arr+i)+j) //arr[i] 找到二维数组具体的某一行,而行号代表那一行,同时行号又表示那一行首元素的地址 //所以 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 }; //数组名arr,表示首元素的地址 //但是二维数组的首元素是二维数组的第一行 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址 //一维数组的地址用数组指针来接收 print_arr(arr, 3, 5); return 0; }
练习题
下面的代码分别表示什么意思?
int arr[5]; # arr和[5]结合,表示arr是一个数组,数组里面有5个元素,每个元素的类型是int;所以这里表示正常的一维整形数组; int *parr1[10]; # parr1和[10]结合,表示parr1是一个数组,数组里面有10个元素,每个元素的类型是int*;所以这里表示指针数组; int (*parr2)[10]; # parr2首先和*结合,表示它是一个指针,然后和[10]结合,表示它指向一个数组,数组里面有10个元素,每个元素的类型是int;所以这里表示数组指针; int (*parr3[10])[5]; # parr3和[10]结合,表示这是一个数组,数组里面有10个元素,每个元素的类型是int (*)[5];所以这里是存放数组指针的数组;