目录
一、字符指针:
1.字符指针的使用:
2.常量字符串:
3.相关面试题分析:
二、指针数组:
三、数组指针:
1.数组指针的定义:
2.&数组名VS数组名:
3.数组指针的使用:
四、总结:
🛰️博客主页:✈️张栩睿的博客主页
🛰️欢迎关注:👍点赞🙌收藏✍️留言
🛰️系列专栏:c语言学习
相信各位小伙伴们已经对基本的指针知识有了相当的了解。那么从今天开始,我们将深入的探讨指针,希望对大家的学习能够有所帮助!
初阶指针复习:
1.指针就是变量,用来存放地址,地址唯一标识一块内存空间。
2.指针的大小是固定的4/8个字节。
3.指针的类型决定了指针+-整数的步长和解引用操作时的权限。
一、字符指针:
1.字符指针的使用:
在一众指针类型中,我们知道存在着字符指针这种类型,并且我们一般这样去使用它:
int main()
{
//第一种形式:
char ch = 'w';
char* pc = &ch;
*pc = 'w';
printf("%c\n", *pc);
还有一种使用方式:
//第二种形式:
const char* pstr = "hello bit.";
//这里不是把一个字符串放到pstr指针变量里
///其本质是把字符串 hello bit.的首字符的地址放到了pstr中,即将一个常量字符串的首字符' h '的地址存放到指针变量 pstr 中。
printf("%s\n", pstr);
这里第二种用法的本质是把常量字符串 ' hello bit. ' 的首字符 ' h ' 的地址放到了pstr中。
2.常量字符串:
并且我们要注意,上面说到的常量字符串作为常量,它们不可被修改
int main()
{
char* p = "abcdef";
//使指针指向常量区的常量字符串abcdef
*p = 'A';
//按照我们的理解,*p中存放的是首元素a的地址,我们尝试改变它
//我们运行起来发现无法完成编译运行,程序会直接卡住
//于是我们可以得出,当使用常量字符串时,常量字符串abcdef不可修改
return 0;
}
为什么要加const:
这些常量字符串中的常量字符,是原本就储存在常量区(只读数据区)的一些常量,不同于需要我们输入的字符变量。所以当使用字符指针直接指向常量字符串时,可以直接进行调用但无法对其进行修改。使用 const 对指针变量进行修饰,进行这样的书写操作之后,当我们试图对常量字符串进行改动时,将会直接报错而无法进行编译运行, 便于我们找到问题的所在:
3.相关面试题分析:
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
//创建两个数组、两个字符型指针变量
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
//判断数组内容是否相同:
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;
}
输出结果:
这道面试题中定义了两个字符型数组和两个字符型指针变量,将两个字符型数组进行对比,又将两个字符型指针变量尽行了对比,目的是为了验证它们在存储时内存的开辟。
解释:
这里 str3 和 str4 指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域(在我们的内存中存在着常量区用于存放一些常量),当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。
在这里,我们就可以更深刻的看出为什么要+const:
因为如果我们改变了该字符串常量,就比如str3和str4,我们改变了其中之一,另外一个变量就会受到影响,因为他们都指向的同一块地址。
我们同样可以看出字符数组和字符指针的区别,他们虽然用法相同,但是在内存中开辟空间的方式却截然不同。
二、指针数组:
整型数组 - 存放整型的数组
字符数组 - 存放字符的数组
指针数组 - 存放指针(地址)的数组
指针数组的本质是数组,是用于存放指针变量的数组:
int main() { //存放字符指针的数组 const char* arr[4] = { "abcdef","qwer", "hello bit", "hehe" }; int i = 0; for (i = 0; i < 4; i++) { printf("%s\n", arr[i]); } return 0;
在这里我们不难发现,用法上和二维数组十分相似,但是不同的是,二维数组内存的开辟是连续的,每一行与每一行之间也是连续的,而指针数组的相邻指针却不一定连续。
我们再来看一个例子复盘一下你就会明白:
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 arr4[5] = { 0,0,0,0,0 };
//指针数组
int* arr[4] = {arr1, arr2, arr3, arr4};
int i = 0;
for (i = 0; i < 4; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", *(arr[i] + j));//arr[i][j]
}
printf("\n");
}
三、数组指针:
数组指针的本质是指针。不同于其他指针,数组指针指向的不是地址而是数组:
char ch = 'w';
char* pc = &ch;int num = 10;
int* pi = #int arr[10];
//pa就是一个数组指针
int (*pa)[10] = &arr;
解释:
int (*pa)[10] = &arr;
pa先与*结合,说明pa是一个指针变量,然后指针指向的是一个大小为10个整形的数组,所以pa是一个指针,指向一个数组,叫指针数组。 简单来说,数组指针就是专门用来存放一个数组的地址的指针。
为什么加():
int main()
{
int* p1[10];
//p1是数组,数组指针
int(*p2)[10];
//p2是指针。数组指针
return 0;
}
这里我们知道【】的优先级高于*,所以必须加()来保证pa和*先结合。
2.&数组名VS数组名:
对于上面指针数组的使用,我们为什么把他定义为&arr而不定义为arr呢?不是arr数组名在除了sizeof()括号内以外都是表示首元素地址吗?这里我们就要看一看 +&与不+&的区别了。
我们先来看一段代码:
int main()
{
int arr[5] = { 0 };
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 同时向后走一步,这时我们看到了不一样的结果,没+&时跳过了4个字节,也就是一个整形,而+&以后,地址跳过了40个字节,也就是整个数组的大小。
根据上面的代码我们发现,其实&arr和arr,数组首元素的地址和数组的地址从值的角度来看是一样的,但是意义不一样。实际上,&arr 表示的是整个数组的地址,而不是数组首元素的地址。而 arr 仅表示其中首元素的地址,这就导致了我们在使它们在向后走的时候,它们向后走的步幅不同,&arr 跨过了整个数组的长度而 arr 仅仅只跳过了一个数据元素。
这里&arr的类型就是int(*)[10],+1就加了10*4个字节。
而这里arr的类型是int*,+1就加了4个字节。
这里也就再次证实了指针的那个特点:指针的类型决定了+-整数的步长
3.数组指针的使用:
数组指针的最基础用法就是保存一个数组的指针:
int arr[10] = { 0 };
int (*p2)[10] = &arr;
显然,我们学习数组指针肯定不止有这样的作用,在真正的工程项目中更多的是在我们进行函数调用,进行数组传参时,可以通过数组指针来进行接收:
//使用数组形式接受
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%02d ", arr[i][j]);
}
printf("\n");
}
}
//使用数组指针来进行接收:
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%02d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
printf("\n");
//可以使用数组指针来进行接收:
print_arr2(arr, 3, 5);
return 0;
}
也许这里有很多小伙伴会迷惑,说函数里arr传过去的是arr的首元素地址,怎么可以用数组指针来接受呢,类型不一样呀?
这里我们就需要对二维数组有一个更深入的了解:
实际上,我们之前说过,二维数组与指针数组用法很类似,二维数组的首元素其实指的是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,即传的是一个一维数组维数组的地址,这个时候我们就需要用一个数组指针来接收这个一维数组的地址。
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
第一个,是整形数组,数组有五个元素。
第二个,是指针数组,数组有10个元素,每个元素是int*类型。
第三个,是数组指针,指针指向一个有10个整形的数组,数组的每个元素是int类型的。
第四个,拆分来看:首先()内的parr3[10]是一个10个元素的数组,10个元素的类型都是是int(*)[5],而int(*)[5]是一个数组指针,所以第四个是一个指向数组指针的数组。
学到这里不知道你有没有总结出辨认类型的规律:
我们先根据优先级将变量名与【】或*结合,这就规定了该变量的类型是数组类型还是指针类型,然后再根据第二优先级辨认出该数组内的内容或者指针指向的类型,我们再将这个类型作为形容词加在变量类型的前面即可。
四、总结:
总的来说,数组与指针密不可分,相互联系。本文主要讲解了:
1.字符指针存放的是常量字符串的首元素地址,与字符串的数组开辟内存的方式不同,但是用法类似。
2.常量字符串不能改变,所以要加const。
3.&+数组名与数组名在值上是相同的,都等于首元素地址,但是意义不同,&代表的是整个数组的地址,而不+&代表的是首元素地址。更这加说明了指针的类型不同,步长也不相同。
4.二维数组与指针数组十分类似,他的首元素地址指的是第一行的地址,即一个数组的地址,在函数传参时要用相应的数组指针来接受二维数组的首元素地址(第一行数组地址)。
5.如何辨认类型。
以上就是数组与指针的联系与结合的全部内容,今天我们对指针的知识又有了新的了解,只要大家踏实肯学,我相信到最后,所有的小伙伴都一定会有所收获、有所成就。辛苦各位小伙伴们动动小手,三连走一波 最后,本文仍有许多不足之处,欢迎各位认真读完文章的小伙伴们随时私信交流、批评指正!