1. 二级指针
二级指针就是指针的指针,即存放指针变量地址的一个变量。
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
int** pp = &p;
printf("%d\n", a);
printf("%d\n", *p);
printf("%d\n", **pp);
//打印结果全为10
printf("%p\n", a);
printf("%p\n", *p);
printf("%p\n", **pp);
//上面打印结果为 000000000000000A
printf("%p\n", p);
printf("%p\n", * pp);
//上面打印结果为 000000E9FCFBF954
printf("%p\n", pp);
//上面打印结果为 000000E9FCFBF978
}
我们可以看到,p存储的是变量a的地址,而p本身也是个变量自然也会有地址。
我们在这里用pp来存储指针变量p的地址,上篇文章我们讲过指针运算符*,指针变量p存储a的地址那*p就等同于a,由此可得*pp等同于p的地址,**pp等同于*p的地址等同于a的地址。而pp单独一个地址。
总结 : 二级指针是用来存放一级指针变量的地址的一个变量
2. 指针数组与数组指针
2.1 概念
这个可能是一个容易混淆的地方,我们只要记住指针数组与字符数组,整形数组一样本质上都是数组 字符数组存放字符,整形数组里存放整形数据,那指针数组里自然是存储指针啦 继续往后推,数组也分各种数据类型,以下是三种类型指针数组的定义方式
int *parr1[4];//存放四个整形地址
double *parr2[4];//存放四个double型数据地址
char *parr3[4];//存放四个char类型的地址
而数组指针,就像整型指针,字符指针一样本质上都是指针 整形指针指向整形变量的指针,数组指针是指向数组的指针 以下是三种类型数组指针的定义方式注意区分
int (*parr1)[4];//指向一个整型数组,该数组有4个元素
double (*parr2)[4];//指向一个double型数组,该数组有4个元素
char (*parr3)[4];//指向一个char型数组,该数组有4个元素
2.2 决定因素
我们上篇文章讲到:在定义时*标志这个变量是指针变量但是*优先级较低,所以结合的顺序决定了是指针数组还是数组指针。
1. 指针数组:parr1先与[]结合就说明这个变量是个数组,再与*结合说明是一个指针,所以是指针数组
2.数组指针:parr1先与*结合就说明这个变量是一个指针,再与[]结合说明是一个数组,所以是数组指针
2.3 实践
先来看指针数组的打印输出
int a = 10, b = 10, c = 10;
int* parr1[10] = { &a,&b,&c };
for (int i = 0; i < 3; i++)
{
printf("%d ", *parr1[i]);
}
printf("\n");
int arr1[4] = { 1,2,3,4 };
int arr2[4] = { 2,3,4,5 };
int arr3[4] = { 3,4,5,6 };
int* parr2[3] = { arr1,arr2,arr3 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", parr2[i][j]);
//printf("%d ", *(parr2[i] + j));//与上一行功能相同
}
printf("\n");
}
我们的parr1由于存储的是地址所以第i个数据需要解引用才可转化成指针变量所指向的值。
而parr2则是一个二维数组的模拟,我们之前提过,数组名代表数组的首元素地址,自然就可以被指针数组存储,(但是要注意,parr2与二维数组有本质上的区别:二维数组在内存中的存储一定是连续的,但parr2存储的是三个一维数组的首元素地址,三个一维数组大概率不连续),我们之前说过parr1[1]等价于*(parr1+1);那parr2[i][j]其实就等价于*(*(parr2+i)+j); 这点与二维数组也是一样的
接下来 来看数组指针的打印输出
char(*p1)[6];
char ch[6] = "ctrcv";
p1 = &ch;
printf("%s\n", *p1);
printf("%s\n", p1);
printf("%\n", p2[0]);
int(*p2)[5];
int arr[5] = { 1,6,8,2,4 };
p2 = &arr;
printf("%p\n", p2);//整个数组的地址,一般使用数组首地址表示的
printf("%p\n", *p2);//首元素地址
printf("%p\n", p2[0]);//等价于*(p+0)也是首元素地址
printf("%d\n", **p2);//*p为首元素地址再解引用一次就是首元素的值
printf("%d\n", *p2[0]);//等价于*(*p+0)也是首元素的值
printf("%d\n", *p2[1]);//等价于*(*(p+1))也就是说跨越了一个数组的距离造成越界
printf("%d\n", *(*(p2 + 1)));//与上相同
printf("%d\n", *(*p2+1));//访问了数组的第二个元素
printf("%d\n", *(*p2+2));//访问第三个元素
因为字符串知道首地址即可打印所以上述三种形式均可打印字符串
而为什么指针数组要用二维数组的方式改变呢? 我们推回去即可,p2==&arr,&arr是数组地址所以p2是数组地址,*p2==*&arr,我们之前提过*&相互抵消了,所以*&arr==arr即首元素地址,以此类推**p2==*arr,因为arr[1] ==*(arr+1) 那*p[1]==*(*(&arr+1)),所以就会往前跨越一个数组的距离
3. const在指针上的应用
3.1 概念
const 是常量限定符,告诉编译器此变量不可修改可以我们对不应该修改的变量进行变动
3.2 实践
const int num = 0;
//num = 1;错误
const int* p = & num;
int** pp =(int **) &p;
int b = 1;
printf("%d\n", *p);
**pp = 66;
printf("%d\n", num);
printf("%d", *p);
上述代码,我们将num变为了常量,不可以直接改变但是可以通过指针来改变, 我们不用上述代码改变还有一种改变方式如下
//int *p=(int *)&num
//int** pp =&p;
3.3 定义指针变量时的细节
int n = 3;
int m = 1;
const int * p1 = &n;
//*p1 = 4;错误
p1 = &m;
printf("%d\n", *p1);
int* const p2 = &n;
//p2 = &m;错误
*p2 = 4;
const int* const p3 = &n;
//*p1 = 4;错误
//p2 = &m;错误
由上可得
1.const放在*左边,指针变量指向的对象的值不可以通过指针对象来改变,但指针变量本身的值可以改变
2.const放在*右边,指针变量的值不可以改变,但是指针变量指向对象的值可以通过指针对象解引用赋值改变
3. *左右两边都有就全不可以改变
4. 字符数组指针
4.1
我们来了解一下一些易错的点
const char* p = "abcd";
printf("%s", p);
//*p = 'w'; 错误
我们可以通过const来将常量字符串来赋值给指针变量p,p存储的是第一个字符的地址
4.2 小检测
const char* p1 = "abcdef";
const char* p2 = "abcdef";
char arr1[] = "abcdef";
char arr2[] = "abcdef";
if (p1 == p2)//两个都是常量字符串第一个字符的地址
printf("1");
else
printf("0");
printf("\n");
if (arr1 == arr2)//两个数组的首地址
printf("1");
else
printf("0");
让我们想一想结果分别是什么呢?
答案揭晓~ 第一个输出1 ,第二个输出0
很简单,常量字符串在内存中的位置肯定是相同的,而两个数组的首地址肯定创建在内存中不同的地方
这篇文章就到这里啦,希望可以帮到你❥(^_-)
(づ ̄3 ̄)づ╭❤~爱你们