指针
1.内存与地址
1.1 指针
我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何⾼效的管理呢?
其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。
计算机常见的单位:
>bit——比特位 1Byte=8bit
Byte——字节 1KB=1024Byte
KB 1MB=1024KB
MB 1GB=1024MB
GB 1TB=1024GB
TB 1PB=1024TB
PB
1.2指针
指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量,在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。
那我们如何该如何理解指针呢?
其中,每个内存单元,相当于⼀个学⽣宿舍,⼀个字节空间⾥⾯能放8个⽐特位,就好⽐同学们住的⼋⼈间,每个⼈是⼀个⽐特位。每个内存单元也都有⼀个编号(这个编号就相当于宿舍房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。⽣活中我们把⻔牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字叫:指针。
最简单理解的方法:
可以看到这个机带RAM就是我的内存16GB=161024MB=1610241024KB=161024*1024Byte,有这么多个字节,一个字节有一块地,每块地都有一个唯一的地址,如果有n个字节,那么地址的数就有0~n-1个,换句话说指针就是地址
1.3如何理解编址
用我的理解来解释的话就是
在三维空间中,每一块的地址早早的就固定好了只是没使用如某块地如津市西青区宾水西道399号这块地,我们是不是知道它的地址了但是这块地是用来干嘛呢,当然在现在是建了一个大学。而在内存中就是这样的每块地都能知道它的指针,但是用来干嘛呢我们没决定罢了。
2.指针变量和地址
2.1取地址操作符
理解了内存和地址的关系,我们再回到C语⾔,在C语⾔中创建变量其实就是向内存申请空间,⽐如:
那我们如何得到a的地址呢?
这里就要用到取地址操作符(&)
值得注意的是在内存中我们默认每一个指针与一个字节的空间去搭配,举例来说就是一个指针我们不去考虑它的类型的情况下默认是与一个字节的空间去搭配,而一个类型是int的指针我们认为它包含四个默认的指针。
2.2指针变量和解引用操作符(*)
2.2.1指针变量
那我们通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:0x006FFD70,这个数值有时候也是需要存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。
比如:
2.2.2 如何拆解指针类型
大家可以看到上面指针变量的类型是int*,大家在进行指针这块的练习一定要了解优先级(今天会更新)的问题不然很难去理解后面的指针数组,数组指针,函数指针的内容。
指针的学习肯定是一个较大的挑战。
可以看的时候做一下
2.2.3解引用操作符
2.3指针变量的大小
当然有关指针大小这方面的内容深挖下去肯定会有更多的知识,但现在作者能力浅薄往后如果能理解肯定也会讲解一下
3.指针变量类型的意义
指针变量的⼤⼩和类型⽆关,只要是指针变量,在同⼀个平台下,⼤⼩都是⼀样的,为什么还要有各种各样的指针类型呢?
其实指针类型是有特殊意义的,我们接下来继续学习。
3.1指针的解引用
规范使用,一定要养成一个严谨的代码风格,这对你的程序帮助是巨大的
3.2指针±整数
我们看到*前面的int,char这些不就是我前面讲的高度吗(也就是几个字节长度),当然还有其他后面也会提到
3.3 void*指针
正如艾伦·佩利所说的在探究难以实现的问题时,简化是唯一的方法。在如此繁多的指针中解决类型问题,难道我们接受一个指针就要用一个相应的类型吗,未免太多繁琐重复,我们都用void来接收再动用void返回不就行了吗?学到后面大家就会发现如此的便利
4.const修饰指针
4.1const修饰变量
4.2 const修饰指针变量
如何理解const对指针的作用我觉得归根结底还是要回到结合性的内容
const int*p,*右结合性因此const修饰的是(*p),int*const p,此时const修饰的是p,此时解引用操作符结合的是(const p)
5.指针运算
指针的基本运算有三种,分别是:
- 指针±整数
- 指针-指针
- 指针的关系运算
5.1指针±整数
因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。
指针±整数一定要结合类型来看是int就跳过四个字节,是char就跳过一个字节
5.2指针-指针
指针-指针得到的是元素个数,值得注意的是指针-指针一定是相同的类型,比如对int*p的指针变量只能用同样的指针变量去减
5.3指针的关系运算
当然指针和指针之间也可以比较大小,因为本身指针也是一串十六进制的数字
6.野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
6.1野指针成因
6.1.1 指针未初始化
这一点其实放在其他变量也是一样的,可能有时候我们写代码图方便就对相同类型的变量进行统一的声明,如果后面进行赋值了那没问题但如果你没去赋值(实际上如果你后面赋值程序编译器依然会认为你的指针变量可能指向NULL),它指向的就是一个随机值只不过没野指针危害大不至于让程序崩溃,因此不管后面是否重新对指针变量赋值一定要初始化指针变量
6.1.2指针越界访问
6.1.3指针的空间释放
test调用之后return &n(返回了一个指针),但是函数时存放在栈上的当它返回时栈的空间就被回收了因此此时你虽然得到了一个指针,但这指针没有开辟空间。
6.2如何规避野指针
- 指针初始化
- 小心指针越界
- 避免返回局部变量的地址
- 指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性
7.assert断言
往往程序出错误我们可以通过assert的办法去排除是不是指针的问题,可惜作者学习的不到位现在还不太能理解它的深刻作用
8.传值调用和传址调用
通过这两的区别就能体会到指针的作用
在函数那块的内容博主已经简单提及过了
9.数组名的理解
- sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节
- &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地址。
10.使用指针访问数组
这里简单来讲一下下标访问操作符[]和解引用操作符简单来说就是
arr[i] == *(p+i);大家可以自己替换一下啊效果是一样的
11.一维数字传参的本质
所以当我们sizeof(arr),一定要想这里arr是数组名还是一个形参。
12.二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥?
这就是⼆级指针 。
当然也有三级指针和四级指针。
13.指针数组
14.指针数组模拟二维数组
解释一下parr[i][j]
parr[i][j]等于*( *(parr+i)+j)等于(arri+j)
15.字符指针变量
16.数组指针变量
16.1数组指针变量是什么
之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。
数组指针变量也就是指向数组的指针变量
这块内容还是要回到优先级的内容来,首先[]的优先级优于*同时[]是左结合性的,其次[]是左结合性的
当然我们就这样代入公式也就是(*p)就是告诉你我这里是指针
17.二维数组传参的本质
也就是可以改成void test(int (*p)[5], int r, int c)
4.函数指针变量
4.1函数指针变量的创建
4.2函数指针变量的使用
可能大家这里会觉得直接用函数名调用函数不就行了吗,就如同电势的概念一样会有一个这样的概念往往都是为了方便我们去解决实际问题。等到后面讲到qsort函数时大家就会理解函数指针的方便
5.函数指针数组
6.转移表
函数指针数组的⽤途:转移表
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void menu()
{
printf("**********计算器***************");
printf("**1.add*******************2.sub**");
printf("**3.mul*******************4.div**");
printf("************0.exit***************");
printf("*********************************");
printf("*********************************");
}
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int div(int x, int y)
{
return x / y;
}
int main()
{
int(*clac[4])(int x, int y) = { add,sub,mul,div };
int x, y;
int input = 1;
int ret = 0;
do
{
menu();
printf("请输入\n");
switch (input)
{
case 1:
{
scanf("%d %d\n", &x, &y);
int ret = clac[0](x, y);
printf("%d\n", ret);
break;
}
case 2:
{
scanf("%d %d\n", &x, &y);
int ret = clac[1](x, y);
printf("%d\n", ret);
break;
}
case 3:
{
scanf("%d %d\n", &x, &y);
int ret = clac[2](x, y);
printf("%d\n", ret);
break;
}
case 4:
{
scanf("%d %d\n", &x, &y);
int ret = clac[3](x, y);
printf("%d\n", ret);
break;
}
case 0:
{
break;
}
default:
{
printf("输入错误,请重新输入\n");
}
}
} while (input);
return 0;
}
/
int main()
{
int(*clac[4])(int x, int y) = { add,sub,mul,div };
int input = 0;
int ret = 0;
while(scanf("%d", &input)!=EOF)
{
if (input >= 1 && input <= 4)
{
scanf("%d%d",&x,&y);
int ret = clac[input - 1](x, y);
printf("%d\n", ret);
}
else if(input == 0)
{
return 0;
}
else
{
printf("输入错误,请重新输入\n");
}
}
return 0;
}
以上就是有关指针的内容了,当然要写学会指针还得学习其他更多方面的内容包括qsort函数的实现等等,当然后面博主会将这些作为练习来来讲解。希望这些东西对你的C语言学习有所帮助
将近万字的博客还希望大家多多点赞关注评论,若有问题还劳烦大家多多指正