在上一篇文章中我们谈到了指针,并给老铁们讲解了什么是指针、指针类型、野指针以及指针运算等知识。在这篇文章中小编将继续带大家了解指针的相关知识点。
1. 指针和数组
指针和数组之间又能有什么联系呢?在谈这个之前,我们先来讲讲指针和数组之间的关系:
1.指针变量就是指针变量,不是数组,指针变量的大小是4/8个字节,专门用来存放地址的。
2.数组就是数组,不是指针,数组是一块连续的空间,可以存放1个或者多个类型相同的数据。
那既然指针变量是指针变量,数组是数组,二者之间能有什么联系呢?来看看下面的例子:
从程序的运行结果中可以得知,数组名和数组首元素的地址是一样的。那么可以得出的结论是:数组名表示的是数组首元素的地址。(2种情况除外,数组部分讲解了)
既然可以把数组名当成地址存放到一个指针中,那我们使用指针来访问数组就成为可能。再看看如下例子:
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr; // 指针用来存放数组首元素的地址
int i = 0;
for (i = 0; i < sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
}
return 0;
}
我们将数组首元素的地址存放在指针p中。从运行结果来看,p+i 其实计算的是数组 arr 下标为i的地址。那我们就可以直接通过指针来访问数组。
因此指针与数组的联系便是:在数组中,数组名其实就是数组首元素的地址(2种情况除外)。当我们知道数组首元素的地址的时候,因为数组又是连续存放的,所以通过指针就可以遍历访问数组,数组是可以通过指针来访问的。
个人理解:数组名 == 地址 == 指针。
2. 二级指针
在次之前,我们谈到的指针都是一级指针,那二级指针是干什么的呢?
对于一级指针来说,一级指针变量也是变量,是变量就会在内存中开辟空间,是变量就会有地址。因此二级指针变量就是用来存放一级指针变量的地址。
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a; // p是一级指针
int** pp = &p; // pp就是二级指针变量
*(*pp) = 100;
printf("%d\n", **pp); // 解引用2次
printf("%d\n", a);
同理
//int*** ppp = &pp;
return 0;
}
对于上述代码,p是一级指针变量,pp是二级指针变量,int** 其中的 int* 是在说明pp指向的是int*的类型变量,即p的完整类型,*是说明pp是指针变量。
由于二级指针变量存储的是一级指针变量的地址,因此我们对二级指针pp解引用(即 *pp ),实则访问的是一级指针变量p;所以如果想通过指针pp来找到变量a,那就需要再次进行解引用(即 **pp )。
换句话说,*pp 等价于 p,对指针 p 解引用找到a,即 *p。所以 *p 等价于 **pp。故 **pp = 100 等价于 *p = 100 等价于 a = 100。
那么三级指针该怎么写呢?讲到这相信很多老铁都会举一反三了,那便是 int*** ppp = &pp;
3. 指针数组
指针数组?那究竟是指针还是数组呢?我们可以联想到整型数组、字符数组指的都是数组,那指针数组自然也是指数组了!
那指针数组是怎样的呢?下面举例来说明:
整型数组:int arr1[5];
字符数组:char arr2[6];
指针数组:int* arr3[5];
所以:
整型数组 —— 存放整型的数组。
字符数组 —— 存放字符的数组。
指针数组 —— 存放指针(地址)的数组。
#include<stdio.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "hello xxx";
char arr3[] = "cuihua";
char* parr[] = { arr1,arr2,arr3 };
printf("%c\n", *parr[1]);
printf("%c\n", *(parr[1] + 1));
printf("%c\n", (*parr)[1]);
return 0;
}
在上述代码中,数组parr便是指针数组了。在前面我们提到,数组名是数组首元素的地址,因此指针数组parr中存储的是三个字符数组首元素的地址,而不是存储的三个数组内的元素。
那可能就有老铁好奇怎么通过指针数组来访问三个字符数组中的元素了,下面我将对上述代码中三个printf语句进行讲解,来帮助大家更进一步了解指针数组的的使用和解引用。
1. 对于 *parr[1],因为 [] 的优先级要比 * 高,所以parr先于 [] 结合,即取出数组parr中下标为1的元素,即是 数组arr2 首元素的地址,再对其进行解引用操作,所以取出的是 数组arr2 的首元素,为 h。
2. 对于 *(parr[1] + 1),同样的 parr[1] 是 数组arr2 首元素的地址。我们可以假设 char* p = parr[1],则 p = arr2,所以 *(parr[1] + 1) 等价于 *(p + 1) 等价于 arr2[1]。故取出的是 数组arr2 中下标为1的元素,即是 e 。
3. 对于 (*parr)[1],这里与第一点不同,因为有(),所以 parr 先于 * 结合。而指针数组也是数组,parr是数组名,故对数组名解引用 (*parr) 则是取出数组首元素,即是 数组arr1 首元素的地址。所以 *parr 等价于 arr1,即 (*parr)[1] 等价于 arr1[1],故结果为 b 。
那指针数组有什么作用呢?如果我们想要打印数组arr1,arr2,arr3,此时指针数组parr就类似于二位数组,通过双层循环便可将三个数组的内容输出出来。
#include<stdio.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 5,6,7,8,9 };
int arr3[] = { 0,2,4,6,8 };
// 指针数组
int* parr[] = { arr1,arr2,arr3 };
// 类似于(模拟)的二维数组
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
//printf("%d ", *(parr[i] + j));
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
4. const 修饰指针的理解
在之前我们谈到const 修饰的变量 变成了常量,即该变量的值不可修改。那么当const修饰指针时,指针是否可修改呢?下面我将通过两个例子来为各位老铁们讲解。
如上两图中,我们用const修饰指针p。此时const 放在 * 的左边,再通过指针修改其指向的内容时,编译器无法编译通过会报错;而修改指针变量指向的对象时,代码正常运行。也就是说,当const 放在 * 的左边时,限制的是指针指向的内容,不能通过指针变量来改变指针指向的内容,但指针变量的本身是可以改变的。
如上两图中,同样用const修饰指针变量p,但此时 const 放在了 * 的右边,我们通过指针修改其指向的内容时,编译器却能正常运行了,而修改指针变量所指向的对象时,编译器却报错。也就是说,当 const 放在 * 右边时,限制的时指针变量本身,指针变量本身是不能改变的,但是指针指向的内容是可以通过指针来改变的。
总结:
1. 当const 放在 * 的左边时,限制的是指针指向的内容,不能通过指针变量来改变指针指向的内容,但指针变量的本身是可以改变的。(限制的是 *p)
2. 当 const 放在 * 右边时,限制的时指针变量本身,指针变量本身是不能改变的,但是指针指向的内容是可以通过指针来改变的。(限制的是 p)
不知道各位老铁们看完这篇文章后,有没有对指针有更进一步的了解呢?那我们下篇文章见吧!