知识点#
9.1 指针的基本概念详解#
- @ 指针基本概念图
- ![[白板/P23 指针基本概念.canvas|P23 指针基本概念]]
9.1.1 前提知识#
- 变量存储在内存中,不同类型的变量保存在不同存储区(静态存储区、动态存储区)
- 变量的内存在编译时或程序执行时分配
- 各类型占用字节数(x86 平台):
| 类型 | 字节数 |
|---|---|
int | 4 |
char | 1 |
float | 4 |
double | 8 |
9.1.2 地址的概念#
[!tip] 地址
- @ 地址: 计算机中用一个数字来描述内存位置
- 内存地址从上到下从小到大排列
- 每个变量占若干字节,系统通过变量名与地址的对应表来访问变量
9.1.3 直接访问和间接访问#
[!tip] 访问方式
- @ 直接访问: 按变量地址存取变量值(如
printf("%d", i);)- @ 间接访问: 通过存放地址的特殊变量来访问另一个变量的值
- @ 指针变量: 专门用来存放另一个变量地址的变量
- ! 指针变量本身也占内存(x86 下 4 字节,x64 下 8 字节)
- ! 区分"指针"和"指针变量":==指针就是地址==,==指针变量是存放地址的变量==
9.2 变量的指针和指向变量的指针变量#
- @ 指针变量图
- ![[白板/P24 指针变量.canvas|P24 指针变量]]
9.2.1 指针变量的定义#
[!tip] 指针变量定义
- 一般形式:
类型标识符 *标识符;- 三种写法等价:
int *p;/int* p;/int * p;
int i = 7, j = 9;
float k = 12.6f;
int *mypoint1, *mypoint2; // 指向整型变量的指针
float *pm3; // 指向实型变量的指针
char *pm4; // 指向字符型变量的指针
mypoint1 = &i; // mypoint1 指向 i
mypoint2 = &j; // mypoint2 指向 j
定义要点#
- ! 定义时
*表示正在定义指针变量,使用时变量名前没有* - ! 指针变量名是
mypoint1,不是*mypoint1 - ! 一个指针变量只能指向同一类型的变量
9.2.2 指针变量的引用#
[!tip] 两个关键运算符
- @
&: 取地址运算符- @
*: 指针运算符(间接访问运算符),访问指针所指向的变量
int a = 100, b = 200;
int *p1, *p2;
p1 = &a; // p1 指向 a
p2 = &b; // p2 指向 b
int *p3 = &a; // 定义时初始化
printf("%d, %d\n", a, b); // 100, 200
printf("%d, %d\n", *p1, *p2); // 100, 200,*p1 代表 a,*p2 代表 b
易混淆写法#
| 写法 | 含义 |
|---|---|
&*p1 | 等价于 &a,也就是 p1 本身 |
*&a | 等价于 *p1,也就是 a 本身 |
(*p1)++ | 等价于 a++,值加 1 |
*p1++ | 等价于 *(p1++),先取值再移动指针 |
指针自增的含义#
- !
p1++不是地址简单 +1,而是跳过所指向类型占用的字节数 int指针p1++地址 +4,char指针p1++地址 +1
// 交换两个指针指向的值
int *pmax, *pmin, *p, a = 5, b = 8;
pmax = &a;
pmin = &b;
if(a < b) {
p = pmax;
pmax = pmin; // pmax 指向大的值
pmin = p; // pmin 指向小的值
}
printf("max = %d, min = %d\n", *pmax, *pmin); // max = 8, min = 5
9.2.3 指针变量作为函数参数#
[!tip] 指针作函数参数
- 将变量的地址传送到函数中,可以在函数中改变调用者的变量值
- ! 形参和实参依然是指针变量的值传递,但因为传递的是地址,所以可以间接修改原变量
void swap(int *pdest1, int *pdest2) {
int temp;
temp = *pdest1; // 取 pdest1 指向的值
*pdest1 = *pdest2; // 赋值给 pdest1 指向的变量
*pdest2 = temp; // 赋值给 pdest2 指向的变量
}
int main() {
int a = 5, b = 6;
int *p1 = &a, *p2 = &b;
if(a < b) swap(p1, p2);
printf("a=%d, b=%d\n", a, b); // a=6, b=5
return 0;
}
9.3 数组的指针和指向数组的指针变量#
- @ 数组指针图
- ![[白板/P25 数组指针.canvas|P25 数组指针]]
9.3.1 指向数组元素的指针#
int a[5];
int *p;
p = &a[0]; // 等价于 p = a; 数组名就是首地址
// 定义时初始化
int *p = &a[0]; // 等价于 int *p = a;
9.3.2 通过指针引用数组元素#
| 表达式 | 含义 | 类型 |
|---|---|---|
p + i / a + i | a[i] 的地址 | 地址 |
*(p + i) / *(a + i) | a[i] 的值 | 值 |
p[i] | 等价于 *(p + i),即 a[i] | 值 |
int a[5] = {12, 14, 20, 18, 50};
int *p;
// 方法1: 下标法
for(int i = 0; i < 5; i++)
printf("%d\n", a[i]);
// 方法2: 地址法
for(int i = 0; i < 5; i++)
printf("%d\n", *(a + i));
// 方法3: 指针法(最快)
for(p = a; p < (a + 5); p++)
printf("%d\n", *p);
注意事项#
- !
a++不合法,数组名是常量,不能自增 - ! 指针法中循环结束后
p已指向数组后面的内存,不能操作 - !
a[5]虽然有对应地址,但不能操作
指针自增/自减运算#
| 写法 | 含义 |
|---|---|
*p++ | 先取 *p 的值,再 p++ 指向下一元素 |
*++p | 先 p++,再取新指向的值 |
(*p)++ | p 指向的元素值 +1,指针不变 |
9.3.3 数组名作为函数参数#
[!tip] 实参与形参的 4 种对应关系
实参 形参 数组名 数组名 数组名 指针变量 指针变量 指针变量 指针变量 数组名
// 实参用数组名,形参用指针变量
void changevalue(int *p) {
*(p + 2) = 888; // 等价于给 a[2] 赋值 888
}
int main() {
int a[5] = {85, 70, 98, 92, 78};
changevalue(a); // 传递数组首地址
return 0;
}
9.3.4 回顾二维数组和多维数组的概念#
- 二维数组
int a[3][4]可看作 3 个一维数组a[0]、a[1]、a[2] - 每个一维数组包含 4 个元素
- 二维数组在内存中按行连续存放
- 多维数组:最右边维度下标变化最快
9.3.5 指向多维数组的指针和指针变量#
[!tip] 二维数组地址与值
表现形式 含义 类型 a第 0 行首地址 地址 a + i第 i行首地址地址 a[i]/*(a + i)第 i行首地址地址 a[i] + j/*(a + i) + ja[i][j]的地址地址 *(a[i] + j)/*(*(a + i) + j)a[i][j]的值值
- !
a和*a都是地址,值相同但含义不同 - !
a[i]在二维数组中代表地址(一维数组名),不是值
int a[3][4];
int *p;
p = (int *)(a + 1); // 第 1 行首地址
*p = 56; // 相当于 a[1][0] = 56
p++;
*p = 78; // 相当于 a[1][1] = 78
9.3.6 指针数组和数组指针#
[!tip] 指针数组 vs 数组指针
- @ 指针数组: 本质是数组,每个元素都是指针
int *p[10];— 10 个指针变量的数组- @ 数组指针: 本质是指针,指向一个数组
int (*p)[4];— 指向含 4 个元素的一维数组的指针
- ! 记忆技巧:以什么结尾就是什么。“指针数组"是数组,“数组指针"是指针
// 指针数组:适合指向若干字符串
const char *pName[] = {"C++", "Java", "Python", "Go", "CSharp"};
// 交换指针数组中的指针
const char *ptmp;
ptmp = pName[0];
pName[0] = pName[1]; // pName[0] 指向 "Java"
pName[1] = ptmp; // pName[1] 指向 "C++"
9.3.7 多维数组的指针作为函数参数#
- 与一维数组类似,多维数组的地址也可以作为函数参数传递
9.4 字符串的指针和指向字符串的指针变量#
- @ 字符串指针图
- ![[白板/P26 字符串指针.canvas|P26 字符串指针]]
9.4.1 字符串表示形式#
用字符数组实现#
char mystr[] = "I love China"; // 字符串复制到数组中
printf("%s\n", mystr);
用字符指针实现#
const char *pmystr = "I love China"; // 指针指向字符串常量
printf("%s\n", pmystr);
- ! 字符数组:每个数组有独立内存,字符串常量被复制到数组中
- ! 字符指针:指向字符串常量的首地址,多个指针可能指向同一地址
- ! 字符串常量所在内存是只读的,不能修改
字符串复制范例#
// 下标法
char a[] = "I love China!";
char b[100];
for(int i = 0; *(a + i) != '\0'; i++)
*(b + i) = *(a + i);
*(b + i) = '\0';
// 指针法
char *p1 = a, *p2 = b;
for(; *p1 != '\0'; p1++, p2++)
*p2 = *p1;
*p2 = '\0';
9.4.2 字符串指针作为函数参数#
- 用字符数组名或指向字符串的指针变量作为参数,可以在被调函数中改变字符串内容
void copystr(char *from, char *to) {
while(*from) // 不遇到 '\0' 就继续
*to++ = *from++; // 先赋值再各自后移
*to = '\0';
}
9.4.3 字符指针变量与字符数组#
| 对比项 | 字符数组 | 字符指针变量 |
|---|---|---|
| 存储方式 | 各元素存放字符 | 存放字符串首地址 |
| 赋值方式 | 定义时初始化或 strcpy | 可以直接指向字符串常量 |
| 值能否改变 | 数组名值不能改变 | 指针值可以改变 |
// 字符数组:不能直接赋值
char str[100];
// str = "I love China!"; // 不允许!
strcpy(str, "I love China!"); // 正确
// 字符指针:可以直接指向
const char *a = "I love China!";
a = a + 7; // 指针可以改变,现在指向 "China"
printf("%s\n", a); // China
9.5 函数指针和返回指针值的函数#
- @ 函数指针图
- ![[白板/P27 函数指针.canvas|P27 函数指针]]
9.5.1 用函数指针变量调用函数#
[!tip] 函数指针
- @ 函数指针: 指向函数的指针变量,通过它可以调用所指向的函数
- 定义形式:
数据类型 (*指针变量名)(形参列表);
int max(int x, int y) {
if(x > y) return x;
return y;
}
int (*p)(int, int); // 定义函数指针变量
p = max; // 将函数入口地址赋给 p
int c = (*p)(5, 19); // 通过函数指针调用,等价于 c = max(5, 19);
// 也可以写成 c = p(5, 19);
- !
int *p(int, int);是返回指针的函数声明,不是函数指针! - !
(*p)的括号不能省,否则优先级不同含义完全不同 - ! 对函数指针做
p++、p--、p+n等运算没有意义,也不合法
9.5.2 把指向函数的指针变量作为函数参数#
- 将函数地址传递给形参,可以在函数中调用不同的函数
int callmax(int x, int y, int (*idfunc)(int, int)) {
int result = idfunc(x, y); // 通过函数指针调用
return result;
}
int main() {
int c = callmax(5, 19, max); // 传递函数名
// 也可以用函数指针变量
int (*p)(int, int) = max;
c = callmax(45, 21, p);
return 0;
}
9.5.3 返回指针值的函数#
[!tip] 返回指针的函数
- 定义形式:
数据类型 *函数名(参数列表) { ... }- ! 不要返回局部变量的地址!函数调用完毕后局部变量内存被回收
// 错误示范:返回局部变量地址
int *add(int x, int y) {
int sum = x + y;
return ∑ // 致命问题!sum 的内存会被回收
}
// 正确做法:使用全局变量
int sum; // 全局变量,生存期到程序结束
int *add(int x, int y) {
sum = x + y;
return ∑ // 安全
}
9.6 指针数组、指针的指针与 main 函数参数#
- @ 指针进阶图
- ![[白板/P28 指针进阶.canvas|P28 指针进阶]]
9.6.1 指针数组概念回顾#
- 指针数组定义:
类型标识符 *数组名[数组长度]; int *p[4];— 4 个指针元素的数组int (*p)[4];— 指向含 4 个元素的一维数组的指针
9.6.2 指向指针的指针#
[!tip] 指向指针的指针
- @ 指向指针的指针: 一个指针变量指向另一个指针变量
- 定义:
int **p;—p指向一个int *类型的指针变量*p是p指向的指针变量,**p是最终指向的值
9.6.3 main 函数参数#
main函数可以接收命令行参数:int main(int argc, char *argv[])argc: 参数个数argv: 指针数组,每个元素指向一个参数字符串- !
argv[0]是可执行文件的完整路径文件名
int main(int argc, char *argv[]) {
for(int i = 0; i < argc; i++)
printf("argv[%d] = %s\n", i, argv[i]);
return 0;
}
9.7 本章小结#
指针数据类型小结#
| 定义 | 含义 |
|---|---|
int i; | 整型变量 i |
int *p; | 指向整型数据的指针变量 |
int a[n]; | 整型数组,n 个元素 |
int *p[n]; | 指针数组,n 个指向整型的指针 |
int (*p)[n]; | 指向含 n 个元素的一维数组的指针 |
int f(); | 函数 f,返回整型值 |
int *p(); | 函数 p,返回指向整型的指针 |
int (*p)(); | 指向函数的指针,函数返回整型值 |
int **p; | 指向指针的指针 |
指针运算小结#
p++/p--: 地址加减所指向类型占用的字节数- 指针变量不能直接赋数字地址,只能赋已分配的变量地址
- 指针可以指向
NULL(空),表示不指向任何有效内容
void * 型指针#
void *是万能型指针,可以指向任意数据类型- 使用时需要强制类型转换回原类型
void *pvn = NULL;
int a = 3;
int *p = &a;
float f = 5.6f;
float *pf = &f;
pvn = p; // 不报错
pvn = pf; // 不报错
pf = (float *)pvn; // 强制转换回原类型

