C语言之深入指针
在学习这篇博客之前建议先看看这篇博客C语言之深入指针(二)
里面详细介绍了指针的
- 传值调用和传址调用
- 数组名的理解
- 使用指针访问数组
- ⼀维数组传参的本质
文章目录
1 二级指针
指针变量也是一种变量,只要是变量,在内存中就会创建一个地址给它
存放指针变量的地址的变量就是二级指针
1.1 二级指针的介绍
#include <stdio.h>
int main()
{
int num = 10;
int* p = #
int** pp = &p; //二级指针
int*** ppp = &pp;//三级指针(不经常使用)
//......
return 0;
}
图解:
假设num的地址为 0x0012ff50 ,指针变量p存放了num的地址 0x0012ff50 ,同时变量p也有自己的地址 0x0012ff48 ,指针变量pp存放了指针变量p的地址 0x0012ff48 ,同时也有自己的地址,pp就是二级指针,再创建一个指针变量来存放pp的地址的话,这个指针变量就是三级指针,语法支持,同理还有四级指针,但是三级指针就不常用了,使用三级之后就没有什么必要了
1.2 二级指针的使用
#include <stdio.h>
int main()
{
int num = 10;
int* p = #
int** pp = &p;
**pp = 30;
printf("%d\n", num);
return 0;
}
上述代码中的**pp = 30; 等价于*p = 30; 也等价于a = 30;
**pp 先通过 *pp 找到 p,然后再对 p 进行解引用操作,即 *p ,找到 a
2 指针数组
整型数组是存放整型数据的数组
字符数组是存放字符数据的数组
那么指针数组就是存放指针的数组
int* arr[5]; //存放整型指针的数组
char* str[10]; //存放字符指针的数组
//......
2.1 通过指针数组模拟二维数组
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
二维数组在内存中是连续存放的,可以理解为是一维数组的拼接,arr[3][5]可以理解为有三个一维数组,arr[0] arr[1] arr[2] 拼接在一块组成的,每个数组中存放5个元素
那么我们就可以使用指针数组来存放arr[0] arr[1] arr[2]第地址,然后通过指针偏移来打印这三个数组,模拟实现二维数组
#include <stdio.h>
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int* arr[3] = { arr1,arr2,arr3 };
int i = 0;
for (i = 0; i < 3; i++)//依次访问arr1,arr2,arr3
{
int j = 0;
for (j = 0; j < 5; j++)//依次访问arr[i]中第一个元素至最后一个元素
{
printf("%d ", arr[i][j]);
//printf("%d ", *(*(arr + i) + j));
//*(*(arr + i) + j)等价于arr[i][j]
}
printf("\n");
}
return 0;
}
代码运行结果如下:
arr[i] 是访问arr数组的元素,指向了一个整型一维数组,其中 arr[i][j] 就是整型一维数组的元素
其中 arr[i] 可以写为*(arr+i) , arr[i][j] 就可以写为*(*(arr+i)+j)
3 字符指针变量
字符指针变量: char*
3.1 常量字符串
#include <stdio.h>
int main()
{
const char* pch = "Hello World!";
printf("%s\n", pch);
printf("%c", "Hello World!"[6]); //打印出第七个字符 ' W '
return 0;
}
在上述代码中:
将一串常量字符串的第一个字符的地址存到了字符指针变量中,并非整个常量字符串都存到了字符指针变量中,其次常量字符串是不可以修改的,所以我们可以使用const修饰,这样如果在下面的代码中我们修改了字符串就会提示
常量字符串可以想象成是一个字符数组,字符数组通过访问其下标来使用,常量字符串也可以通过下标来使用,上述代码中的"Hello World!"[6],就是通过下标6来打印常量字符串中的第7个元素,也就是下标为6的 ’ W ‘
3.2 有趣的笔试题
在《剑指offer》中有这么一道笔试题:
#include <stdio.h>
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指向的地址不同
而str3 和 str3 这两个字符指针变量指向一串相同的常量字符串"Hello World!",常量字符串在内存中会存储在一块相同的空间,所以str3 和 str4 指向相同的地址
str1 和 str2 都是数组名,数组名就是地址,所以在下面比较str1 和 str2的地址是不同的,所以打印not same
在3.1中提到常量字符串只有第一个字符的地址存入字符指针变量中,所以str3 和 str4 指向相同的地址 ,都是第一个字符的地址,所以打印same
4 数组指针变量
指针数组是存放指针的数组
整型指针变量 存放的是整型变量的地址 指向整型数据的指针
浮点型指针变量 存放的是浮点型变量的地址 指向浮点型数据的指针
那么数组指针变量 存放的就是数组的地址 指向数组的指针变量
4.1 数组指针变量的创建
int (*p)[10];
p 是一个指针变量,指向一个大小为10个整型元素的数组
由于 [ ] 操作符的优先级大于 * 操作符,我们需要用 ( ) 将指针变量括起来
int arr[10] = { 0 };
int (*p)[10] = &arr;
int (*p) [10] = &arr;
| | |
| | |
| | p指向数组的元素个数
| p是数组指针变量名
p指向的数组的元素类型
5 二维数组的传参
#include <stdio.h>
void test(int arr[3][5], int x, int y)
{
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; j++)
{
printf("%d ", arr[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} };
test(arr, 3, 5);
return 0;
}
在一维数组中,函数的形参可以写为数组名也可以写为指针,那么同理二维数组的传参也可以写为数组名或者指针
#include <stdio.h>
void test(int (*arr)[5], int x, int y)
{
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; j++)
{
printf("%d ", arr[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} };
test(arr, 3, 5);
return 0;
}
二维数组是由多个一维数组组成的,二维数组的数组名也是首元素的地址,但是在二维数组中的首元素指的是arr[0][j],也就是第一个一维数组,所以在函数传参的时候,传递的地址,是第一行这个一维数组的地址
总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式