文章目录
所有内容的参考资料,我都会放到最后
指针
关于指针的概念就不多赘述,可以把它理解成是地址,比如旅馆门牌号、家庭住址。
指针的声明形式为:变量类型* 指针变量名
指针变量: 是存储着指针(地址)的变量,变量本身也具有一个地址,只不过它存放的内容是某类型指针。类比一下,比如整型变量,是存储整型数字的变量,本身有一个地址。
指针类型: 是指针所需要指向的内容的类型加上’*‘,这就是指针的类型啦。比如: int *,就是int指针类型
地址的长度 一般都是由主机位数决定,而每个地址对应的是1个字节的地址。为什么要有指针类型这个东西呢,这里需要考虑到地址增减的问题,比如 int类型占4字节,也就意味着它的内容需要占4个字节,如果以1个字节为单位的话,地址只会增减1个字节,那就无法保证内容的完整性;所以这个时候就需要声明指针的类型,这样当你增减指针变量的时候,编译器就知道你1次增减对地址来说实际需要横跨多少个字节。所以如果你声明的指针所指向的变量类型与所指向的变量类型不一致时,会报错。
int num1[2] = {0,1}; //长度为2的数组,每个元素占4字节
int *p = num1; //将首元素的地址赋值给指针p
printf("%d\n",(p+1)-p); //p为0的首地址,p+1为1的首地址,得到的结果是4
double num2[2] = {0.0,1.0}; //长度为2的数组,每个元素占8字节
double *p = num2; //将首元素的地址赋值给指针p
printf("%d\n",(p+1)-p); //p为0的首地址,p+1为1的首地址,得到的结果是8
int num3 = 0;
double *p = &num3; //会报错,类型不匹配
数组
数组: 就是一块连续的、顺序的存储空间。至于空间的大小,这就要有数组的类型以及申请的数组的长度来决定了。即使是多维数组也一样,都是在一块顺序的存储空间中,只是逻辑结构上的表现不一样而已,也就是一维通过某种映射,映射成多维。
数组的变量名: 也就是数组名 ,通俗的来说他就是代表着首元素的地址嘛。
数组首元素和数组的地址、空间大小
当你取数组的地址的时候,你会发现他还是首元素地址。如下代码所示:
int num[2] = {0,1};
prinf("%d %d %d\n", &num[0], &num); //输出的值一样
单论地址,它们是一样的,都是首元素地址。关键在哪?关键在于他们的类型、空间大小是不一样的,看代码:
int num[2] = {0,1};
printf("%d %d\n", sizeof(num[0]), sizeof(num)); //输出为:4 8
也就是说,num[0]的空间为int大小,而num的空间为int[2]大小。
也就是说,它们所指的空间大小是不一样的;它们的地址一样,是因为它们的空间的起始地址都是这个地址,所以才会一样。当你取地址进行递增的时候,如代码:
int num[2] = {0,1};
printf("%d %d\n", (&num[0]+1)-&num[0], (&num+1)-&num); //输出为:4 8
你就会发现,一个是按照int的空间大小增减,一个按照int[2]的空间大小增减。为啥咧?因为:
&num[0]是int指针类型的地址,&num是int[2]指针类型的地址 ,好好记住!!!
数组名
细心一点的小伙伴,就会想到了,哪为什么数组名可以直接赋值给指针使用呢?不要和上面搞混哦,这里是用处。
int num[2] = {0,1};
int *p = num; // 可行 与 &num[0]是一样的,可以理解为num就是首元素地址的别名
int **pp = num; //报错,int*不能赋值给int**
就把它当成首元素地址的别名就好啦,就像身份证和姓名一样,都指代你这个人,姓名对应不就是这个身份证号嘛(不考虑重名)。这个不要纠结,一维数组的数组名在作为右值的使用上和指针一样,注意我说的是使用上。其实编译器会把数组名解释成指针,试试故意让他报错就知道了。我知道你开始绕了,哈哈哈哈哈哈!
等我再深入了解,再来写哈!!!
大致就到这啦,请记住这个,后面会用到!!!
指针和数组
在这里,我先下结论:数组和指针不一样,数组不是指针,他们不是一个概念的东西。只是在某些使用方式上,以及在某种形势下编译器把它们解释成一样的东西。
相同点
使用上
- 都可以通过解引用符号 ’*‘,在解引后 ,可以取值、赋值
- 都可以直接把变量名当作指针来加减
- 都可以通过下标来索引
int num[2] = {1,2};
int *p = num;
int *pt = p;
printf("%d %d\n", p[0], num[0]);
printf("%d %d\n", *p, *num);
printf("%d %d\n", *(p+1), *(num+1));
// 以上p和num输出的结果是一样的。
作为函数形参上
结论:作为形参时,它们是一样的,都是指针
多说无益,代码伺候:
void fun1(int a[]) { //以数组形式定义形参
printf("%d %d\n", a, &a);
}
void fun2(int *p) { //以指针形式定义形参
printf("%d %d\n", p, &p);
}
int main() {
int num[2] = {0,1};
fun1(num); // 输出:数组首元素的地址、a指针本身的地址
fun2(num); // 输出:数组首元素地址、p指针本身的地址
return 0;
}
从输出上看,a和p都拥有自己的地址,并且存储的都是首元素的地址,并且存储的也是地址。无论是那种定义形式,它们都是指针。还不信?好,上大招:
void fun1(int a[]) { //以数组形式定义形参
printf("%d %d %d\n", *a, a, &a);
}
void fun2(int *p) { //以指针形式定义形参
printf("%d %d %d\n", *p, p, &p);
}
int main() {
int num = 0;
fun1(&num); // 输出:2、变量num的地址、a指针本身的地址
fun2(&num); // 输出:2、变量num的地址、p指针本身的地址
return 0;
}
我直接把变量的地址传给了以数组定义的形参,也是可以的,不会报错。因为他其实是指针,套着羊皮的狼罢了,本质还是狼。就算你在方括号里写上数字,也不会报错,因为这个不重要,编译器会忽略掉。
不同点
- 指针变量名本身会有一个地址,如指针p,通过&p就可以得到p的地址了;而数组的话,都是首元素的地址,只不过对应类型变了。
- 数组名虽然可以当作指针来使用,但是不可以作为左值,被赋值
int num1[2] = {0, 1};
int num2[2];
num2 = num1; // 会报错
这里大家应该都学过的,只能在声明的时候被赋值。
- 数组和指针它们取值的索引方式不一样。前者是直接索引,后者是间接索引,看图:
过程大概是酱紫:
指针 | 数组 |
---|---|
存储数据的地址 | 存储数据 |
间接访问数据:p[i],首先获得指针内容,把内容加上 i 当作地址,从这个地址中提取数据; | 直接访问数据,a[i] 只是简单以 a+i 为地址获得数据 |
下标其实主要是为了方便,编译器最后还是会转换成p+i,num+i的方式。
二维数组
在这只解释结构哈,其他特点参考一维数组就好
其实二维数组的存储结构还是一维的,只不过通过逻辑结构把它映射成了二维而已,看图:
在这里,解答一个小问题:为什么声明一定要指定第二维度的长度呢?因为编译器通过类型所占字节,只知道最里面那个维度元素的地址递增的大小,而不知道第一维度的,所有你得告诉它:我有这么、、、长,你忍一下(bushi),它就可以通过这个长度和类型所占字节的大小来计算,第一维度的跨度了。这一条在数组指针原理差不多哈,自个思考一下,后面我就不说了。
数组指针
顾名思义,就是指向一个数组的指针,也就是说数组指针变量,就是存放一个数组的地址,巧了,是不是想到了什么!?刚讲完,不会吧不会吧(一维数组:那我走?),这个后面说哈,挖个坑,看你们想不想得到。捋一捋:
- 数组指针是指针,指向数组的指针,它的变量是存储一个指向数组的指针(地址)
- 数组指针,这个指针对应的类型是数组类型。
我们明确这两点就行。
如果我现在声明: int (*p)[4]
,看图:
这个图虽然和前面的有点像,但是指针的类型是不一样的, 一个是int(*)[4], 令一个是int*
二维数组和数组指针
老规矩,先说结论:二维数组和数组指针,就类似一级指针和一维数组一样
相同点
相同点的话,其实和一维的差不多
- 它们都必须要声明第二维的长度
- 都可以通过解引’*‘来取值
int main(){
int arr[2][2] = {0, 1, 2, 3};
int (*p)[2] = arr;
printf("%d %d\n", **p, **arr); // 都输出0
printf("%d %d\n", **(p+1), **(arr+1)); //输出2
printf("%d %d\n", *(*(p+1)+1), *(*(arr+1)+1)); //输出3
//哒咩,不建议用上面的方法输出元素,太难过
printf("%d %d\n", p[0][0], arr[0][0]); //下标他不香么
}
不同点
巧了,这个也可以参考一维数组
int main(){
int arr[2][2] = {0, 1, 2, 3};
int (*p)[2] = arr;
int (**pp)[2] = &p;
printf("%d %d %d\n", &p, pp, &arr); //从左到右的类型是:int(**)[2]、int(**)[2]、int(*)[2][2]
printf("%d %d\n", p, arr); // 类型都是int(*)[2],也就是数组类型的指针
printf("%d %d\n", *p, *arr); // 类型变成了:int*
printf("%d %d\n", **p, **arr); // 类型:int
/*
输出:
6487560 6487560 6487568
6487568 6487568
6487568 6487568
0 0
*/
}
看输出:
- 第一行:数组指针p本身是有一个地址的,而&p的类型属于数组指针的指针。而&arr的类型则是属于二维数组的指针,也就是数组的数组的指针。
- 第二、三行:虽然它们的地址是一样的,但是它们的类型变了。
- 第四行:最后变成了整型,也就是数字了。
再往高维度,各位要自己脑补了哈,哈哈哈哈,掌握二维就差不多了!!!
一维数组和数组指针
让我看看,谁能想起来关联,能想起来的就很棒呀,想不起来,也不要灰心,让我娓娓道来:
int main(){
int num[2] = {0, 1};
int (*p)[2] = # // 前面我们说过&num是int(*)[2]类型的地址,p也是,所以当然可以赋值啦。
printf("%d %d \n", p, &num); // 类型都是int(*)[2]
printf("%d %d \n", *p, num); // 类型都是int*
printf("%d %d \n", **p, *num); // 类型都是int
// 注意看,下面开始不一样了
printf("%d %d \n", p, num); // int(*)[2]、int*
printf("%d %d \n", *p, *num); // int* 、 int
printf("%d %d \n", p[0][0], num[0]); // int、int
printf("%d\n", p[1]); // 相当于 p+1
/*
输出:
6487568 6487568
6487568 6487568
0 0
6487568 6487568
6487568 0
0 0
6487576
*/
}
看输出:
- 通过前面三行:就可以知道p需要两级解引,才能取得元素。
- 第四行:虽然地址一样,但是地址的类型变了
- 第六行: 通过下标,让大家更直观的看到,如果把p当作二维数组,那么num[0]就位于p[0][0],也就是说!!! num数组是p的第一维的第一行。
- 第七行:当我们把p的第一维度下标变成1时,也就是索引到第二行。地址变了!!!结合num数组的起始地址,它们之家的差值刚好是8,刚好是int[2]空间大小。但是从这个地址就与num无关了,不知道存什么鬼。这里开始他就是个野指针了。
指针数组
举个例子:整型数组,存着什么东东,是不是存着整型数字的数组。对!!!没错,所以指针数组,就是存着指针的数组,看图(一个存放着int*类型的数组):
- 它其实就是一个数组,声明形式 :
数据类型* 变量名[]
指针数组和数组指针
为什么它们只差一个括号,就会不一样呢?这么像,怎么分辨呢?
int* p_arr[2]; //指针数组
int (*p)[2]; //数组指针
- 优先级:指针数组的声明中,因为 [ ] 的优先级比 * 高,所以变量名先和 **[]**结合运算了,所以是数组
- 结合性:数组指针的声明中,() 和 [] 的优先级是一样的,但是 因为都是 左结合性(从左到右),所以是先执行 () 内的表达式,得到运算结果,也就是指针。所以是指针。
总结来说,就是和谁先结合,那么就是什么
不够?那再看,当我故意让程序报错时:
二级指针
也就是指针的指针,说白了就是:二级指针变量存放的内容是一级指针变量的地址(毁灭吧,不想写了!!!)看图:
int num = 2;
int *p; // p的类型是int*
int **pp; //pp的类型是int**
p = #
pp = &p;
printf("%d %d\n", pp, &p); //输出的是p的地址
printf("%d %d\n", *pp, p); //输出的是num的地址
printf("%d %d\n", **pp, *p); //输出的是2
重点在于,类型环环相扣,一个都不能错,否则就会报错,比如:把一个float指针类型的地址,赋值给int二级指针类型
总结
难顶鹅,写了一整天终于写完了,这些是这几天学习指针与数组的领悟,可能描述的没那么专业,也会有错,但是我觉得还是可以给各位提供参考。随着我深入学习,我会进行修改的,也欢迎各位大佬斧正,我一定虚心接受!!!有什么疑问随时提哦,我看到会回复的,谢谢各位啦~ 嘻~
如需转载,请附上原文链接,谢谢!!!
https://blog.csdn.net/weixin_44108562/article/details/120986811