文章目录
嘿嘿,家人们,在指针初阶呢,我们学习了指针的概念:
1:指针就是个变量,用来存放地址,地址标识一块唯一的内存空间.
2:指针的大小就是固定的4/8个字节(32位平台/64位平台).
3:指针也分类型,指针的类型决定了指针的±整数的步长和指针解引用操作时的权限.
4:指针的概念
这个章节,我们将继续探讨指针更深入滴主题!好啦,废话不多讲,开干!
1:字符指针变量
在指针的类型中我们知道有一种指针类型为字符指针char *,一般按照下面这种方式来使用.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char ch = 'a';
char* pc = &ch;
*pc = 'w';
printf("%c\n", ch);
return 0;
}
通常呢,我们使用char *指针来存储字符型变量的地址,但除了存储字符型变量的地址以外,char *指针还能这样子使用,我们来看下面这段代码.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
const char* pstr = "hello world";
printf("%p\n", pstr);
printf("%p\n", &pstr[0]);
return 0;
}
代码const char * pstr = “hello world”;是不是很多uu会认为是把字符串hello world放入到字符指针ptr了呢?其实不是滴,这段代码的本质是将字符串hello world的首字符地址放到了字符指针ptr中即将一个常量字符串的首字符h的地址存放到了指针变量ptr中.
通过观察,我们能清晰发现,pstr所存储的地址就是常量字符串的首字符h的地址.了解了字符指针后,我们来看下面这样一段代码,同样也是一道面试题.
#define _CRT_SECURE_NO_WARNINGS
#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与str4是指针,指向的是常量字符串,C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,实际上他们会指向同一块内存区域.这里我们通过监视来观察一下.
通过观察监视,我们可以情绪地发现str1与str2的地址不同,指向的内存区域不同,而str3与str4的地址是相同的,说明这两个指针指向的是同一块内存区域.
2:指针数组
在指针初阶呢,博主详细介绍了指针数组,指针数组是一个存放指针的数组,这里博主带着uu们简单复习一下,如果忘了的uu们可以回过头去看看指针初阶那篇文章.
//整形指针的数组,该数组里头存放了10个元素,每个元素都是整型指针类型.
int * arr1[10]
//字符指针的数组,该数组里头存放了10个元素,每个元素都是字符指针类型.
char * arr2[10]
//二级字符指针的数组,该数组里头存放了10个元素,每个元素都是二级字符指针类型.
char ** arr3[10]
3:数组指针
3.1数组指针的定义
数组指针是指针还是数组呢?答案是指针,是一个指向数组的指针.
在之前我们已经熟悉了
> 整型指针:int * ptr;指向整型数据的指针.
浮点型指针:float * ptr;指向浮点型数据的指针.
那么数组指针即指向数组的指针.
下面我们来看一段代码,看看哪个是数组指针的表达方式.
int * p1[10]
int (*p2)[10]
上面这段代码p1和p2分别是什么呢?答案是:相信uu们对于p1很熟悉了,p1是指针数组,数组里头存放的数据类型都是整型指针.
p2是数组指针,指向的是一个int类型的数组.那么为什么数组指针要这样子来表达呢?原因是这样子滴.
在操作符优先级里面,[]的优先级是高于 * 的,因此在这里我们要通过()来保证p2先与 * 结合,p2与 * 结合后,说明p2是一个指针变量,然后指针指向的是一个大小为10个整型的数组.因此p2是一个指针,指向一个数组,名为数组指针.
3.2:&数组名vs数组名
int arr[15];
对于上面的数组,我们只知道arr是数组名,数组名表示的首元素的地址,那么&arr是什么呢?我们来看下面一段代码.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int arr[10];
printf("%p\n",arr);
printf("%p\n",&arr);
return 0;
}
通过观察上面这段代码的结果我们可以发现,数组名和&数组名打印的地址是一样的.那么这两个真的是一样的吗?我们再来看下面这段代码.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int arr[10];
printf("arr = %p\n",arr);
printf("&arr = %p\n",&arr);
printf("\n");
printf("arr + 1 = %p\n",arr + 1);
printf("&arr + 1 = %p\n",&arr+ 1);
return 0;
}
根据上面的代码结果我们可以清晰地发现,&arr和arr,虽然值是一样滴,但是意义是不一样的.
实际上:&arr表示的是整个数组的地址,arr表示的是数组首元素的地址
在上面代码中,&arr的类型是:int (*)[10],是一种数组指针类型.
数组的地址 + 1,跳过的是整个数组的大小,16进制的00EFF780 - 00EFF758 = 16进制的28,转换为10进制就是40,因此&arr + 1与&arr的差值是40.
4:数组参数,指针参数
4.1:一维数组传参的本质
数组之前我们学习过了,数组是可以传递给函数的,那么在这里博主详细讲一下数组传参的本质,首先从一个问题开始,我们之前都是在函数外部计算数组的元素的个数,那么我们可以把数组传给函数后,在这个函数内部计算数组的元素个数吗?我们来看下面这段代码.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(int arr[])
{
int sz2 = 0;
printf("sz2 = %d\n", sizeof(arr) / sizeof(arr[0]));
}
int main()
{
int sz1 = 0;
int arr[10];
printf("sz1 = %d\n", sizeof(arr) / sizeof(arr[0]));
test(arr);
return 0;
}
我们发现在函数内部没有正确地获得数组的元素个数,这个时候就要涉及到数组传参的本质了,在之前我们学习过:数组名是首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是首元素的地址. 因此函数形参的部分理论上应该使用指针变量来接收首元素的地址.那么在函数内部我们写sizeof(arr)计算的是一个地址的大小而不是数组的大小.正是因为函数的参数部分本质是指针,所以在函数内部是没办法求得数组元素的个数的.
那了解了一维数组传参的本质后,我们来看看下面这几个数组传参的场景.
4.1.1:场景一
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int arr[10])
{
}
int main()
{
int arr[10] = { 0 };
test1(arr);
return 0;
}
在这种场景下,实参传递过去,将函数的形参部分写成数组的形式可不可以呢?答案是可以滴,并且在这里传参的时候,指定了形参的数组大小与实参的大小是一致的,其实形参的部分写成数组的形式,实际上本质还是指针.
4.1.2:场景二
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test2(int arr[])
{
}
int main()
{
int arr[10] = { 0 };
test2(arr);
return 0;
}
场景二对比第一种场景区别就在于形参部分没有指定数组的范围,那么可不可以呢?答案是可以滴,为什么呢?在之前数组部分我们学习过,数组在没有指定范围的情况下,是根据数组的内容来确定范围的,也就是说数组在进行初始化的时候是可以省略元素个数的,在这里呢,main函数的arr数组是已经确定了范围的,而且数组在内存中的存储是连续的,因此实参在传递过去后,能够确定数组的范围.
4.1.3:场景三
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test3(int* arr)
{}
int main()
{
int arr[10] = { 0 };
test3(arr);
return 0;
}
这里的形参部分和前面两个有所不同,这里的形参部分是用一级指针来进行接收的,之前我们了解过,一维数组传参的本质是传递的是数组首元素的地址,而指针呢是用来存储地址的,因此在这里用指针变量接收是可以的.
4.1.4:场景四
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int * arr[10])
{
}
int main()
{
int* arr2[10] = { 0 };
test1(arr2);
return 0;
}
这里的arr2这个数组是指针数组即里面的每个元素都是整型指针,在上面场景中我们学习到,一维数组传参的时候,形参的部分可以写成数组的形式,而这里的形参恰好也是指针数组的形式,因此是可以传递过去的.
4.1.5:场景五
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int ** arr)
{
}
int main()
{
int* arr2[10] = { 0 };
test1(arr2);
return 0;
}
这里的形参与场景四的形参部分有所不同了,这里形参部分使用的是二级指针来进行接收,那么可不可以呢?答案是可以的,原因在于,我们说数组传参传递的是首元素的地址,对于arr2这个指针数组,里面的每一个元素都是一级整型指针,那么传递首元素的地址就是将一级整型指针的地址传递传递过去了,在指针初阶我们学习过,对于一级指针的地址,我们可以用二级指针来进行存储,因此形参部分写成二级指针的地址是可以的.
4.2:二维数组传参的本质
讲解完一维数组的本质后,接下来我们来探究二维数组传参的本质,那么首先我们来看下面这段代码.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(int arr[3][5])
{
int row = 3;
int col = 5;
for (int i = 0; i < row; i++)
{
for(int j = 0; j < col;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);
return 0;
}
当我们有一个二维数组需要传递给一个函数的时候,我们可以类比一维数组的传参方式,同样形参的部分写成一个二维数组的形式来进行接收.那么除了这种写法,还有什么其他写法吗?
首先我们再次理解一下二维数组,二维数组的每个元素可以看做是一维数组的数组,也就是说,二维数组里面的每个元素都是一个一维数组.那么二维数组的首元素代表了首元素的地址即一维数组的地址.
所以,根据数组名是数组首元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址.根据上面的例子,第一行的一维数组是int [5],所以第一行的地址的类型就是一个数组指针即int (*)[5].那就意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址.了解了二维数组的传参本质后,我们来看下面这几个场景.
4.2.1:场景一
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int arr[3][5])
{}
void test2(int arr[][])
{}
void test3(int arr[][5])
{}
int main()
{
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
test1(arr);
test2(arr);
test3(arr);
return 0;
test1这里的形参是可以接收形参的,这点是毋庸置疑的,对于test2这里既没有指定二维数组的行又没有指定二维数组的列,在数组那一章节,我们学习到,二维数组在初始化的时候,是能够省略行的但是不能够省略列即知道了多少列,就能够确定一行多少元素,这样子才能够方便运算.因此test2这里是不能够接收的,而test3是可以接收的.
4.2.2:场景二
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int * arr)
{}
int main()
{
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
test1(arr);
return 0;
}
这里的形参部分通过一个一级整型指针来进行接收,那么能否接收呢?答案是不可以的,因为二维数组传参的本质是传递的是首元素的地址也就是第一行元素的地址即一维数组的地址,因此这里使用整型指针来进行接收是不可以的.
4.2.3:场景三
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int * arr[5])
{}
int main()
{
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
test1(arr);
return 0;
}
这里的形参能否接收实参呢?答案是不行的,形参arr首先与[]结合,也就是一个数组,这个数组里面的元素类型是int *即指针,因此形参部分是指针数组,而实参传递的是一维数组的地址,很明显不匹配.
4.2.4:场景四
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int (*arr)[5])
{}
int main()
{
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
test1(arr);
return 0;
}
这里的形参能否接收实参呢?答案是可以的,形参arr首先与*结合,说明arr是个指针,arr指向的是一个int类型的数组即里面存储的是一维数组的地址,因此arr是一个数组指针,而二维数组传参的本质是传递的是一维数组的地址,因此用数组指针来进行接收是可以的.
4.2.5:场景五
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int ** arr)
{}
int main()
{
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
test1(arr);
return 0;
}
这里的形参能否接收实参呢?答案是不可以滴,这里的形参是二级指针,二级指针是用来接收一级指针的地址,而这里是实参传递过去的是一维数组的地址,因此是不可以接收的.
好啦,uu们,关于指针进阶(一)这部分滴详细内容博主就讲到这里啦,如果uu们觉得博主讲的不错的话,请动动你们滴小手给博主点点赞,你们滴鼓励将成为博主源源不断滴动力.