✨✨ 欢送大家来到贝蒂大讲堂✨✨
🎈🎈养成好习惯,先赞后看哦~🎈🎈
所属专栏:C 语言学习
贝蒂的主页:Betty‘s blog
1. 引言
后面给大家介绍了一些指针的基本概念,明天就让咱们持续深刻指针的世界,和贝蒂一起战胜指针大魔王吧
2. 二级指针
指针变量也是变量,是变量就有地址,那咱们就把寄存 指针变量地址 的指针称为 二级指针。
可能了解起来有点绕,咱们能够通过上面示意图演示一下
代码如下:
int a = 10;
int* pa = &a;// 一级指针,寄存 a 的地址
int** ppa = &a;// 二级指针,寄存指针变量 p 的地址
- 不能间接把 &&a 赋值给 ppa 哦,因为 && 在 C 语言中是且的意思”
(1)对 ppa 解援用,找到 pa,也就是说 *ppa==pa
(2)对 pa 解援用,找到 a,也就是说 **ppa==a
int* b = *ppa;// 找到 a 的地址
int c = **ppa;// 找到 a
- 顺次内推咱们能够衍生出三级指针,四级指针。
3. 数组与指针的关系
3.1 数组名的了解
咱们在后面学习数组时就明确,数组名是首元素地址,然而解说的不够深刻,明天就让咱们深刻理解一下吧~
首先让咱们察看一下如下代码
#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};
//&arr[0],arr,&arr 的区别
printf("&arr[0] = %p\n", &arr[0]);// 首元素地址
printf("arr = %p\n", arr);// 一维数组数组名
printf("&arr = %p\n", &arr);// 对整个数组取地址
return 0;
}
从后果来说 &arr[0],arr,&arr 到底有什么区别呢?
让咱们再看看上面这段代码
#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0] + 1);
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr + 1);
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr + 1);
return 0;
}
输入后果:
- &arr[0]与 arr+ 1 都是跳过 4 个字节,相当于跳过 1 个整型元素。
-
&arr+ 1 跳过 40 个字节,相当于 10 个整型,也就是整个数组。
总结:arr 与 &arr[0]都是首元素地址,指向数组第一个元素。&arr 以首元素地址示意,然而指向的是整个数组。
3.2 sizeof 与数组名
咱们晓得 sizeof 实际上是获取了数据在内存中所占用的 存储空间 ,单位是 字节。
让咱们看看上面这段代码吧
#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n", sizeof(arr));// 计算大小
return 0;
}
输入后果:40
不晓得大家有没有纳闷? 如果数组名是首元素地址的话,咱们晓得在 32 位机器上大小为 4,在 64 位机器上大小为 8。那为什么是 40 呢?
其实数组名就是数组⾸元素 (第⼀个元素) 的地址,然而有 两个例外:
• sizeof(数组名),sizeof 中独自放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节
• & 数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)
3.3 数组与指针等价关系
假如有一个一维数组和一个二维数组
int arr[5]={1,2,3,4,5};int arr[3][3]={{1,2,3},{4,5,6},{7,8,9}}
咱们要拜访它的每个元素,有哪些办法呢~
- 数组拜访
int arr1[5] = {1,2,3,4,5};
for (int i = 0; i < 5; i++)
{printf("%d", arr1[i]);
}
int arr2[3][3] = {{1,2,3},{4,5,6},{7,8,9} };
for (int i = 0; i < 3; i++)
{for (int j = 0; j < 3; j++)
{printf("%d", arr2[i][j]);
}
printf("\n");
}
- 指针拜访
for (int i = 0; i < 5; i++)
{printf("%d", *(arr1+i));
}
for (int i = 0; i < 3; i++)
{for (int j = 0; j < 3; j++)
{printf("%d", *(*(arr2 + i) + j));
}
}
通过对下面代码的察看,咱们能够总结如下法则
- arr[i]与 *(arr+i)等价。
- arri 与 ((arr+i)+j) 等价。
3.4 指针数组
(1) 指针数组的概念
指针数组顾名思义就是寄存指针的数组,数组中每个元素都是指针,寄存的都是 地址。
int*parr1[5];// 寄存五个整型指针变量
char*parr2[5];// 寄存五个字符指针变量
float*parr3[5];// 寄存五个浮点数指针变量
代码示例
int arr1[] = { 1,2,3};
int arr2[] = { 4,5,6};
int arr3[] = { 7,8,9};
// 将每个数组的首元素地址都存进去
int* parr[3] = {arr1,arr2,arr3};
示意图:
(2) 指针数组的了解
int main()
{int arr1[] = {1,2,3};
int arr2[] = { 4,5,6};
int arr3[] = { 7,8,9};
int* parr[3] = {arr1,arr2,arr3};
printf("%p\n", parr);// 打印指针数组首元素地址,也就是打印寄存 arr1 空间的地址
printf("%p\n", parr[0]);//arr1 数组首元素地址
printf("%p\n", *parr);//arr1 首元素地址
printf("%d\n", **parr);// 相当于对 arr1 首元素地址解援用,指的的是 1
printf("%d\n", *parr[0]);// 也相当于对 arr1 首元素地址解援用,为 1
printf("%d\n", *parr[1]);// 相当于对 arr2 首元素地址解援用,为 4
return 0;
}
输入后果:
012FFE30
012FFE6C
012FFE6C
1
1
4
(3) 模仿二维数组
通过上述咱们对 指针数组 的了解,咱们能够间接来模拟出二维数组。
代码如下:
int main()
{int arr1[] = {1,2,3};
int arr2[] = { 4,5,6};
int arr3[] = { 7,8,9};
// 将每个数组的首元素地址都存进去
int* parr[3] = {arr1,arr2,arr3};
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{for (j = 0; j < 3; j++)
{printf("%d", parr[i][j]);
}
printf("\n");// 换行)
}
return 0;
}
- 模仿的二维数组并不是真正的二维数组,因为二维数组在内存中是间断存储的,而模仿进去的数组内存存储并不间断
3.5 数组指针
(1) 数组指针的概念
同理,指针数组的实质是一个数组;那么数组指针的实质就是个指针,指向一个数组的指针。
int(*parr1)[5];// 指向一个有五个元素的整型数组
char(*parr2)[5];// 指向一个有五个元素的字符数组
float(*parr3)[5];// 指向一个有五个元素的浮点数数组
(2) 数组指针的了解
int main()
{int arr[5] = {1,2,3,4,5};
int(*parr)[5] = &arr;
// 对数组名取地址代表整个数组的地址
printf("%p\n", parr);// 整个数组的地址个别用数组首元素地址示意
printf("%p\n", parr[0]);// 相当于 *(parr+0)==arr,首元素地址
printf("%p\n", *parr);// 首元素地址
printf("%d\n", **parr);// 相当于对首元素地址解援用,指的的是 1
printf("%d\n", *parr[0]);// 也相当于对首元素地址解援用,为 1
printf("%d\n", *parr[1]);// 等价于 *(*(parr+1)),parr+ 1 跳过一个数组大小的地址,越界拜访
return 0;
}
输入后果:
012FF6F0
012FF6F0
012FF6F0
1
1
-858993460(越界拜访,随机数)
示意图:
3.6 指针数组与数组指针的区别
可能有许多小伙伴区别不分明指针数组与数组指针,然而如果写成指针的数组,数组的指针,可能更好了解。接下来让咱们具体分析一下吧?
首先咱们要分明一个优先级程序:()>[]>*
- 在 int*parr[]中,parr 先与 [] 联合(数组),而 parr 后面申明的变量类型是 int*。所以这是一个数组,数组中每个元素的类型是 int* 的指针,这一类咱们统称为 指针数组。
- 在 int(*parr)[]中,parr 先与 联合(指针),而后除开(parr)是一个 int []的数组类型。所以这是一个指针,这个指针指向的是一个数组,这一类咱们称为 数组指针。
3.7 字符串
咱们先看一下上面这段代码
char arr1[]="im betty";
char arr2[]={'a','b','c','\0'};
这是一种常见的字符串的示意模式,以 ’\0’ 作为其结尾标记。
然而还有另外一种示意模式,代码如下
//const 能够省略
const char* p1 = "im betty";
const char* p2 = "abc";
咱们晓得 const 润饰在 * 前,不能扭转指针变量所指向的值,所以这个字符串是不能扭转的,这种字符串咱们称为 常量字符串
- 实质上是将字符串中的首元素地址寄存进指针变量。
晓得这些之后,让咱们来看一道题吧
输入什么?
#include <stdio.h>
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 雷同。
3.8 数组传参
(1) 一维数组传参
咱们在之前学习函数时候就讲过一维数组传参,让咱们来温习一下吧。
代码如下
void print(int arr[])// 写成数组的模式
{
int i = 0;
for (i = 0; i < 10; i++)
{printf("%d", arr[i]);
}
}
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};
print(arr);// 将数组传递给 print 函数
return 0;
}
咱们传参是传的数组名,咱们晓得 数组名是首元素的地址,既然是地址,天然就能用指针来承受,所以就有了另外一种写法。
void print(int*p)// 用指针来接管
{
int i = 0;
for (i = 0; i < 10; i++)
{printf("%d",*(p+i));
}
}
(2) 二维数组的传参
先让咱们看看个别二维数组是如何传参的吧
void print(int arr[][3])// 行能够省略,列不能够
{
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 3; j++)
{printf("%d", arr[i][j]);
}
printf("\n");
}
}
int main()
{int arr[3][3] = {{1, 2, 3}, {4,5,6}, {7,8,9}};
print(arr);// 将数组传递给 print 函数
return 0;
}
那么指针接管如何写呢,还是 int* p 吗,咱们晓得二维数组能够看成把每一行当做一个元素的一维数组,数组名首元素地址天然是第一行元素的地址,所以要用数组指针来接管哦~
代码如下:
void print(int(*p)[3])// 明确元素个数
{
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 3; j++)
{printf("%d",*(*(p+i)+j));
}
printf("\n");
}
}