21.1 没有指针的世界
以下不能处理的问题:
- 在呼叫的函式中修改引数
- 直接复制阵列
- 直接复制字串
- 动态改变阵列长度
在被呼叫的函式中修改引数值
- 函式呼叫时,引数值会复制一份到被呼叫的函式内做参数使用。在函式内部对参数做任何的变动不会改变到原本的引数值
指标型别
指标是一种资料型别,用来储存记忆体位址
- 一般情况我们是不需要指针的
- 增加这个型别可以处理的问题
21.2 指标变数宣告
- 指标是C语言的主要特征,是种【储存记忆体位址的资料型别】
- 指标变数宣告语法
资料型别 *变数名称
表示变数内存放的是一个存放这种 【资料型】 值的 【记忆体位址】
取址运算子
变数 依照资料型别会占据一定的记忆体空间。我们可以利用取址运算子(&)去取得变数开头的记忆体位址
1 int count = 9; 2 3 int a; 4 int *b; 5 6 a = count; // (O) (int) = (int) 7 b = count; // (X) (int *) = (int) 8 9 a = &count; // (X) (int) = (int *) 10 b = &count; // (O) (int *) = (int *) 11 12 要记得型别一致
21.3 指标间接运算
间接运算子(*)
相对地,我们可以利用间接运算子(*)从记忆体位址取得开头位于该位址的变数(别跟宣告指标变数时用的*搞混)
1 int count = 9; 2 3 int a; 4 int *b; 5 6 a = count; // (O) (int) = (int) 7 b = count; // (X) (int *) = (int) 8 9 a = &count; // (X) (int) = (int *) 10 b = &count; // (O) (int *) = (int *) 11 12 13 count = *a; // (X) 型别不一致 14 count = *b; // (O) *b 表示记忆体位址的变数是多少,这里相当于复制一份count给count
指标的特殊功能
1 int countA = 9; 2 int countB = 10; 3 int *countAddr; 4 5 countAddr = &countA; 6 7 *countAddr = 0; 8 9 10 countAddr = &countB; 11 *countAddr = 0; 12 13 //相等于 14 15 int countA = 9; 16 int countB = 10; 17 countA = 0; 18 countB = 0;
21.4 更多指标与取址间接运算的细节
指标型别与取址和间接运算
- 指标(type*):可储存记忆体位址的型别
- 取址运算子(&):可取得变数的记忆体起始位址 &变数
- 间接运算子(*):取得以该记忆体位址起始的变数 *记忆体位址
1 //取址与间接运算的关系 2 3 int count = 9; 4 int *countAddr = &count; 5 6 int result = *&count; 7 8 //看到相邻的*&可以抵消 9 10 //所以 11 12 int result = count;
22 指标与函式呼叫
函式呼叫的特性
- 呼叫函式时,作为引数的变数会被复制一份到函式内成为参数。在被呼叫的函式内对参数做任何变动都不会改变到原本的变数
1 #include <stdio.h> 2 3 void addone(int n) { 4 n = n + 1; 5 } 6 7 int main() { 8 int a = 3; 9 addone(a); 10 printf("%d", a); 11 return 0; 12 } 13 14 15 3 16 Process returned 0 (0x0) execution time : 0.976 s 17 Press any key to continue.
函式呼叫时复制记忆体位址
在呼叫函式时,可以将变数的 记忆体位址 作为引数传入函数执行。 此时在函式内部对该参数透过 间接运算子 赋予新的数值时就可以改变原本的变数值。
1 #include <stdio.h> 2 3 void addone(int *n) 4 { 5 *n = *n + 1; 6 } 7 8 int main() 9 { 10 int a = 3; 11 addone(&a); /*复制 a 的记忆体位址给 addone*/ 12 printf("%d", a); /* 4 */ 13 return 0; 14 } 15 16 17 4 18 Process returned 0 (0x0) execution time : 7.710 s 19 Press any key to continue.
22.1 两个变数数值交换(使用函式)
1 #include <stdio.h> 2 3 void swap(int *, int *); 4 5 int main() 6 { 7 int a = 3,b =5; 8 swap(&a, &b); 9 printf("a: %d ", a); 10 printf("b: %d ", b); 11 return 0; 12 } 13 14 void swap(int *a, int *b) { 15 int t = *a; 16 *a = *b; 17 *b = t; 18 } 19 20 21 a: 5 22 b: 3 23 24 Process returned 0 (0x0) execution time : 14.025 s 25 Press any key to continue.
22.2 两个整数的排序(使用函式)
1 #include <stdio.h> 2 3 void sort(int *, int *); 4 void swap(int *, int *); 5 6 int main() 7 { 8 int a = 5, b = 3; 9 sort(&a, &b); 10 printf("a : %d ", a); 11 printf("b : %d ", b); 12 return 0; 13 } 14 15 void sort(int *a, int *b) 16 { 17 if (*a > *b) 18 { 19 swap(&*a, &*b);//swap(a, b) 20 } 21 } 22 23 void swap(int *a, int *b) { 24 int t = *a; 25 *a = *b; 26 *b = t; 27 } 28 29 30 a : 3 31 b : 5 32 33 Process returned 0 (0x0) execution time : 3.630 s 34 Press any key to continue.
22.3 该传变数值还是址
基本原则
- 可以传值就传值
- 复制一份比较安全,不怕被偷改,确保函式间干净的关系。
- 用起来比较方便,可以传一般常数。
例外规则
- 作为引数的变数在呼叫后值会变动的时候(例如数值交换的范例)
- 无法直接复制值的时候(例如阵列和字串)
- 复制成本太高的时候(例如较复杂的结构)
23.1 指标对整数的加减运算
1 int v[5]; 2 3 &v[0] + 1 == &v[1]; 4 &v[1] + 1 == &v[2]; 5 &v[1] - 1 == &v[0]; 6 7 &v[0] + &v[1] // 编译失败(X) 8 &v[2] - &v[1] == 1 //从 v[2] 位址到 V[1] 位址距离 1 个元素
23.2 指标与阵列
指标与阵列的不同
1 int v[5]; // 宣告定义一个有 5 个元素的 int 阵列, 占据 5 个 int 大小的记忆体
1 // 阵列型别可转型为指标 2 3 int v[5]; // 宣告定义一个有 5 个元素的 int 阵列, 占据 5 个 int 大小的记忆体 4 int *n; // 宣告定义一个 int 指标, 占据 1 个 int * 大小的记忆体 5 n = &v[0]; 6 //阵列型别可隐性转型成该阵列第一个元素记忆体位址的指标 7 n = v; // 相当于 n = &v[0] 8 9 10 // 透过指标运算存取阵列元素 11 12 int v[5]; 13 int *n = v; 14 15 n == &v[0]; *n == v[0]; // *n = 0 等值于 v[0] = 0 16 n+1 == &v[1]; *(n+1) == v[1]; // *n(n+1) = 0 等值于 v[1] = 0 17 n+2 == &v[2]; *(n+2) == v[2]; // *(n+2) = 0 等值于 v[2] = 0
23.3 循序存取阵列元素(使用指标)
1 #include <stdio.h> 2 int main() 3 { 4 int v[5] = {1, 2, 3, 4, 5}; 5 int *n = v; // int *n = &v[0]; 6 int i; 7 for (i = 0; i < 5; i++) { 8 printf("%d ", *(n+i)); 9 } 10 return 0; 11 } 12 13 #include <stdio.h> 14 int main() 15 { 16 int v[5] = {1, 2, 3, 4, 5}; 17 int *n = v; // n == &v[0] 18 printf("%d ", *n); 19 n++; // n == &v[1] 20 printf("%d ", *n); 21 n++; // n == &v[2] 22 printf("%d ", *n); 23 n++; // n == &v[3] 24 printf("%d ", *n); 25 n++; // n == &v[4] 26 printf("%d ", *n); 27 return 0; 28 29 30 } 31 32 1 33 2 34 3 35 4 36 5 37 38 Process returned 0 (0x0) execution time : 14.484 s 39 Press any key to continue. 40 41 42 #include <stdio.h> 43 44 int main() 45 { 46 int v[5] = {1, 2, 3, 4, 5}; 47 int *n; 48 for (n = v; n != &v[5]; n++) { 49 printf("%d ", *n); 50 } 51 return 0; 52 } 53 54 55 1 56 2 57 3 58 4 59 5 60 61 Process returned 0 (0x0) execution time : 6.158 s 62 Press any key to continue. 63 64 #include <stdio.h> 65 int main() 66 { 67 int v[5] = {1, 2, 3, 4, 5}; 68 int *n = v; 69 while (n != v+5) { 70 printf("%d ", *n++); 71 } 72 return 0; 73 } 74 75 76 1 77 2 78 3 79 4 80 5 81 82 Process returned 0 (0x0) execution time : 11.702 s 83 Press any key to continue.
23.4 指标与下标运算子([ ])
1 int v[5]; 2 int *n = v; 3 n[0] == *n; n[0] == *v; 4 n[1] == *(n+1); n[1] == *(v+1); 5 n[2] == *(n+2); n[2] == *(v+2);
23.5 在函式间传递阵列(使用指标)
1 #include <stdio.h> 2 int maxv(int *, int N); 3 4 int main() 5 { 6 int a[3] = {3, 8, 7}; 7 printf("Max: %d ", maxv(a, 3)); // 阵列当引数传 8 int b[5] = {3, 9, 7, 1, 2}; 9 printf("Max: %d ", maxv(b, 5)); 10 return 0; 11 } 12 13 int maxv(int *v, int N) { // 起始位置 14 int max = v[0], i; // 参数是个指标 15 for (i = 1; i < 3; i++) { 16 if (v[i] > max) { 17 max = v[i]; 18 } 19 } 20 return max; // 在函式内部指标当作一个阵列在用 21 } 22 23 Max: 8 24 Max: 9 25 26 Process returned 0 (0x0) execution time : 0.817 s 27 Press any key to continue.
23.6 指标与阵列的关系
指标存储某阵列元素位址时的特殊性
- 可以透过加减整数算出同阵列其他元素的记忆体位址
- 加 N 等同于向后移动 N 个元素后的记忆体位址
- 减 N 等同于往回移动 N 个元素后的记忆体位址
- a[b]运算等同于*(a+b),反之亦同
- 在该阵列中从 a 开始往后移动 b 所在的阵列元素
- 当指标储存某阵列第一个元素的记忆体位址后, 用起来就跟该阵列没什么两样了
阵列可以隐性转型成该阵列第一个元素的记忆体位址