「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。
前言:
指针的主题,我们之前有一章已经接触过了,我们知道了指针的概念:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型的,指针的类型决定了指针的 + - 整数步长,指针解引用操作时的权限。
4. 指针的运算。
这个章节,我们将继续探讨指针的高级主题。
一、字符指针
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;
}