本篇文章主要围绕指针和数组的关系以及数组的理解来讲述。下面我们就来看看有关指针和数组有关的内容。
目录
1.数组名的理解
上一章我们对采用&arr[0]的方式拿到了数组第一个元素的地址,但是其实数组名本身就是地址,并且是首元素的地址。
那我们不妨联想到三种取地址的方法如下面代码:
#include <stdio.h>
int main()
{
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int* p1 = &arr[0];
int* p2 = arr;
int* p3 = &arr;
return 0;
}
下面我们就讨论这三个的区别
如下图
我们可知 &arr[0] arr &arr 三个值相等,但是表示的含义不同。
- arr 与 &arr[0] 两者含义基本相同都是表示数组首元素的地址,进行 + 1 操作后,地址跨越一个单位的类型大小
- 而&arr 表示整个数组的地址,所以进行 + 1 操作后,地址跨越整个数组
但是有一个例外,当 sizeof(数组名) 如 sizeof(arr),这里数组名代表整个数组,计算整个数组的大小,如下面图示代码:
sizeof(&arr) 与 sizeof(&arr[0]) 代表的是这个地址的大小,可以看做是一个指针存储该地址,这个指针的大小,而不是数组首元素的大小。
2.结合数组使用指针访问
2.1.数组使用指针访问
如下代码就是用指针简单地进行访问数组的操作。
#include <stdio.h>
int main()
{
int arr[10] = {0};
// 使用指针来访问数组
int sz = sizeof(arr) / sizeof(arr[0]); // 数组的长度
int* p = arr; // 指针p 指向数组arr
// 输入10个数
for (int i = 0; i < sz; i++)
{
// 输入i个值
scanf("%d", p + i); // p + i == &arr[i]
}
// 输出10个数
for (int i = 0; i < sz; i++)
{
printf("%d ", *(p + i)); // *(p + i) == arr[i]
}
return 0;
}
上面代码中指针p 可以完全被数组名arr 所替换,因为数组名arr本身就是首元素的地址。
所以 arr[i] === *(arr + i)
其实编译器在编译arr[i] 时自动将其转换成右边的形式
如果我们多多尝试还可以发现 arr[i] == *(arr + i) == *(i + arr) == i[arr]
大家可能对i[arr] 有点诧异,下面做一个简单的解释:
[ ] 其实就是一个双目操作符,其实下标引用操作符,i[arr]编译器会将其转换成*(i + arr)
2.2.辨析指针和数组的区别
虽然上面说指针p 可以直接代还成数组名arr ,但是指针和数组还是有本质上的区别的。
- 数组就是数组,是一块连续的空间(数组的大小和数组元素个数和元素类型都有关系)
- 指针(变量)就是指针(变量),是一个变量,其大小是4/8个字节(有操作系统的位数有关)。
3.一维数组传参的本质
样例一:
#include <stdio.h>
void Print(int arr[10])
{
int sz = sizeof(arr) / sizeof(arr[0]); // 计算数组的大小
for (int i = 0; i < sz; i++) // 遍历输出
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Print(arr); // 传入数组名arr(本质上是一个地址)
return 0;
}
执行结果:
1
这时就有疑问产生了,为什么输出结果不是1~10,只是1呢?
原因:
数组传参的时候,形参可以写成数组的形式,但是本质上还是指针变量。所以变量sz 在计算数组的长度时,sizeof(arr)只是一个指针变量的大小,并不是整个数组的大小。
数组传参的本质是传递了数组首元素的地址,所以形参访问的数组和实参的数组是同一个数组。
形参的数组是不会单独再创建数组空间的。所以形参的数组可以省略数组大小。
样例二:
#include <stdio.h>
void Print(int* p, int sz) // 用指针p 来接收地址
{
for (int i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
}
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sz = sizeof(arr) / sizeof(arr[0]); // 计算数组的大小
Print(arr, sz); // arr 是数组首元素的地址
return 0;
}
执行结果为:
1 2 3 4 5 6 7 8 9 10
这就可以正常的输出整个数组的数值了,只需要将数组长度在函数外面计算完成后传入函数中就行了。
4.二级指针
变量有地址,指针变量也是变量,所以也有地址,指针变量的地址存放在一个指向指针变量的指针变量,这句话可能有点绕口,其实就是二级指针,这个指针变量存放了另一个指针的地址。
4.1.二级指针的申明
那么二级指针变量如何申明呢?
上章我们学到了指针申明就是变量类型 + ' * ' + 变量名
那么二级指针也是如此
int* *p; 代表指针p指向一个int* 类型的变量,即指针p是一个二级指针
如下面代码就是二级指针的申明以及初始化的方式
int a = 10;
int* pa = &a; // 指向a的一个一级指针变量
int** ppa = &pa; // 指向指针p的一个二级指针变量
4.2.二级指针的运用
由图可知二级指针和一级指针时一样的,通过解引用可以查询地址对应的数据,由此我们可以延伸到多级指针的运用,但是一般在实际编写代码的时候,我们一般会用到二级指针,但是再高级的一般用不上。
5.指针数组
在学习指针数组之前,我们得先明确指针数组是一个数组,而不是指针,指针是作为修饰主语数组的,其含义就是存放指针的数组。
就像int a[10] 是一个整型数组,存放的是整型数据,char s[10]是一个字符型数组,存放的是字符型数据。
5.1.指针数组的申明
指针数组的申明也是一样申明类型 + 变量名
int* a[10]; 这样就是简单的申明了一个指针数组,注意区分指针数组和数组指针
因为[ ] 优先级比 * 高,所以变量名先和[ ]组合成数组,这个数组的类型是int *
5.2.数组指针模拟二维数组
上章我们说过arr[i] = *(arr + i) 所以p[i][j] = *(*(p + i) + j)
故可以用指针数组模拟二维数组的实现。