前言
指针的主题,我们在初级阶段的 【维生素C语言】第六章 - 指针 章节已经接触过了,我们知道了指针的概念:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型的,指针的类型决定了指针的 + - 整数步长,指针解引用操作时的权限。
4. 指针的运算。
这个章节,我们将继续探讨指针的高级主题。
🚪 【维生素C语言】第十章 - 指针的进阶(下)
一、字符指针
0x00 字符指针的定义
📚 定义:字符指针,常量字符串,存储时仅存储一份(为了节约内存)
0x01 字符指针的用法
💬 用法:
int main() { char ch = 'w'; char *pc = &ch; *pc = 'w'; return 0; }
💬 关于指向字符串:
❓ 这里是把一个字符串放在 pstr 里了吗?
int main() { char* pstr = "hello world"; printf("%s\n", pstr); return 0; }
🚩 hello world
🔑 解析:上面代码 char* pstr = " hello world " 特别容易让人以为是把 hello world 放在字符指针 pstr 里了,但是本质上是把字符串 hello world 首字符的地址放到了 pstr 中;
0x02 字符指针练习
💬 下列代码输出什么结果?
int main() { char str1[] = "abcdef"; char str2[] = "abcdef"; const char* str3 = "abcdef"; const char* str4 = "abcdef"; if (str1 == str2) printf("str1 == str2\n"); else printf("str1 != str2\n"); if (str3 == str4) printf("str3 == str4\n"); else printf("str3 != str4\n"); return 0; }
🚩 运行结果如下:
🔑 解析:
① 在内存中有两个空间,一个存 arr1,一个存 arr2,当两个起始地址在不同的空间上的时候,这两个值自然不一样,所以 arr1 和 ar2 不同。
② 因为 abcdef 是常量字符串,本身就不可以被修改,所以内存存储的时候为了节省空间只存一份,叫 abcdef。这时,不管是 p1 还是 p2,都指向同一块空间的起始位置,即第一个字符的地址,p1 和 p2值又一模一样,所以 arr3 和 arr4 相同。
0x03 Segmentfault 问题
💬 把一个常量字符串的首字符 a 的地址存放到指针变量 pstr 中:
二、指针数组
0x00 指针数组的定义
📚 指针数组是数组,数组:数组中存放的是指针(地址)
[] 优先级高,先与 p 结合成为一个数组,再由 int* 说明这是一个整型指针数组,它有 n 个指针类型的数组元素。这里执行 p+1 时,则 p 指向下一个数组元素。
0x01 指针数组的用法
💬 几乎没有场景用得到这种写法,这个仅供理解:
int main() { int a = 10; int b = 20; int c = 30; int* parr[4] = {&a, &b, &c}; int i = 0; for(i=0; i<4; i++) { printf("%d\n", *(parr[i]) ); } return 0; }
🚩 10 20 30
💬 指针数组的用法:
#include <stdio.h> int main() { int arr1[] = {1, 2, 3, 4, 5}; int arr2[] = {2, 3, 4, 5, 6}; int arr3[] = {3, 4, 5, 6, 7}; int* p[] = { arr1, arr2, arr3 }; // 首元素地址 int i = 0; for(i=0; i<3; i++) { int j = 0; for(j=0; j<5; j++) { printf("%d ", *(p[i] + j)); // j-> 首元素+0,首元素+1,+2... // == p[i][j] } printf("\n"); } return 0; }
🚩 运行结果如下:
🔑 解析:
三、数组指针
0x00 数组指针的定义
📚 数组指针是指针,是指向数组的指针,数组指针又称 行指针,用来存放数组的地址
整形指针 - 是指向整型的指针 字符指针 - 是指向字符的指针 数组指针 - 是指向数组的指针
int main() { int a = 10; int* pa = &a; char ch = 'w'; char* pc = &ch; int arr[10] = {1,2,3,4,5}; int (*parr)[10] = &arr; // 取出的是数组的地址 // parr 就是一个数组指针 return 0; }
💬 试着写出 double* d [5] 的数组指针:
double* d[5]; double* (*pd)[5] = &d;
0x01 数组名和&数组名的区别
💬 观察下列代码:
int main() { int arr[10] = {0}; printf("%p\n", arr); printf("%p\n", &arr); return 0; }
🚩 运行后我们发现,它们地址是一模一样的
🔑 解析:
💬 验证:
int main() { int arr[10] = { 0 }; int* p1 = arr; int(*p2)[10] = &arr; printf("%p\n", p1); printf("%p\n", p1 + 1); printf("%p\n", p2); printf("%p\n", p2 + 1); return 0; }
🚩 运行结果如下:
🔺 数组名是数组首元素的地址,但是有 2 个 例外:
① sizeof ( 数组名 ) - 数组名表示整个数组,计算的是整个数组的大小,单位是字节。
② &数组名 - 数组名表示整个数组,取出的是整个数组的地址。
0x02 数组指针的用法
💬 数组指针一般不在一维数组里使用:
int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int (*pa)[10] = &arr; // 指针指向一个数组,数组是10个元素,每个元素是int型 int i = 0; for(i=0; i<10; i++) { printf("%d ", *((*pa) + i)); } return 0; }
❓ 上面的代码是不是有点别扭?数组指针用在这里非常尴尬,并不是一种好的写法。
int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int *p = arr; int i = 0; for(i=0; i<10; i++) { printf("%d ", *(p + i)); } return 0; }
💬 二维数组以上时使用数组指针:
void print1 ( int arr[3][5], int row, int col ) { int i = 0; int j = 0; for(i=0; i<row; i++) { for(j=0; j<col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void print2 ( int(*p)[5], // 👈 数组指针,指向二维数组的某一行 int row, int col ) { int i = 0; int j = 0; for(i=0; i<row; i++) { for(j=0; j<col; j++) { printf("%d ", *(*(p + i) + j)); // printf("%d ", (*(p + i))[j]); // printf("%d ", *(p[i] + j)); // printf("%d ", p[i][j]); } printf("\n"); } } int main() { int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7}}; // print1(arr, 3, 5); print2(arr, 3, 5); // arr数组名,表示元素首元素的地址 return 0; }
🚩 运行结果如下:
0x03 关于数组访问元素的写法
💡 以下写法全部等价:
int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int i = 0; int* p = arr; for(i=0; i<10; i++) { //以下写法全部等价 printf("%d ", p[i]); printf("%d ", *(p+i)); printf("%d ", *(arr+i)); printf("%d ", arr[i]); //arr[i] == *(arr+i) == *(p+i) == p[i] } }
0x04 练习
💬 分析这些代码的意思:
int arr[5]; int* parr1[10]; int (*parr2)[10]; int (*parr3[10])[5];
💡 解析:
四、数组参数和指针参数
写代码时要把数组或者指针传递给函数的情况在所难免,那函数参数该如何设计呢?
0x00 一维数组传参
💬 判断下列形参的设计是否合理:
void test(int arr[]) //合理吗? {} void test(int arr[10]) // 合理吗? {} void test(int *arr) // 合理吗? {} void test(int *arr[]) // 合理吗? {} void test2(int *arr[20]) // 合理吗? {} void test2(int **arr) // 合理吗? {} int main() { int arr[10] = {0}; int* arr2[20] = {0}; test(arr); test2(arr2); }
🚩 答案:以上都合理
🔑 解析:
0x01 二维数组传参
💬 判断下列二维数组传参是否合理:
void test(int arr[3][5]) // 合理吗? {} void test(int arr[][5]) // 合理吗? {} void test(int arr[3][]) // 合理吗? {} void test(int arr[][]) // 合理吗? {} int main() { int arr[3][5] = {0}; test(arr); // 二维数组传参 return 0; }
🚩 答案:前两个合理,后两个不合理
🔑 解析:
🔺 总结:二维数组传参,函数形参的设计只能省略第一个 [ ] 的数字(行可省略但列不可以省略)
因为对一个二维数组来说,可以不知道有多少行,但是必须确定一行有多少多少元素!
💬 判断下列二维数组传参是否合理:
void test(int* arr) // 合理吗? {} void test(int* arr[5]) // 合理吗? {} void test(int(*arr)[5]) // 合理吗? {} void test(int** arr) // 合理吗? {} int main() { int arr[3][5] = { 0 }; test(arr); return 0; }
🚩 答案:只有第三个合理,其他都不合理
🔑 解析:
0x02 一级指针传参
💬 一级指针传参例子:
void print(int* ptr, int sz) // 一级指针传参,用一级指针接收 { int i = 0; for(i=0; i<sz; i++) { printf("%d ", *(ptr + i)); } } int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int *p = arr; int sz = sizeof(arr) / sizeof(arr[0]); // p是一级指针,传给函数 print(p, sz); return 0; }
🚩 1 2 3 4 5 6 7 8 9 10
❓ 思考:当函数参数为一级指针的时,可以接收什么参数?
💬 一级指针传参,一级指针接收:
void test1(int* p) {} void test2(char* p) {} int main() { int a = 10; int* pa = &a; test1(&a); // ✅ test1(pa); // ✅ char ch = 'w'; char* pc = &ch; test2(&ch); // ✅ test2(pc); // ✅ return 0; }
📌 需要掌握:
① 我们自己在设计函数时参数如何设计
② 别人设计的函数,参数已经设计好了,我该怎么用别人的函数
0x03 二级指针传参
💬 二级指针传参例子:
void test(int** ptr) { printf("num = %d\n", **ptr); } int main() { int n = 10; int* p = &n; int** pp = &p; // 两种写法,都是二级指针 test(pp); test(&p); // 取p指针的地址,依然是个二级指针 return 0; }
🚩 num = 10 num = 10
❓ 思考:当函数的参数为二级指针的时候,可以接收什么参数?
💬 当函数参数为二级指针时:
void test(int **p) // 如果参数时二级指针 { ; } int main() { int *ptr; int** pp = &ptr; test(&ptr); // 传一级指针变量的地址 ✅ test(pp); // 传二级指针变量 ✅ //指针数组也可以 int* arr[10]; test(arr); // 传存放一级指针的数组,因为arr是首元素地址,int* 的地址 ✅ return 0; }
















