C语言 指针大法
基本数据类型大致按照使用的类别的话可以分为大致两类,有符号和无符号两种类型
如下所示:
数据的存储方式:
-1 = 1000 0001
存储的是补码形式
①反码是1111 1110【符号位不变,其他位取反】
②补码是1111 1111【反码加1】
然后std::hex以十六进制显示【1111 1111】
[有符号] [无符号]
int 整数类型 unsigned int
float 单精度 unsigned float
char 字符型 unsigned char
short 短整型 unsigned short
long 长整型 unsigned long
double 双精度 unsigned double
先看有符号和无符号的区别 :
相同点:
首先都是以二进制的方式进行存储的,所以在内存存储单元中存储的数据都是一样的
例如:
unsigned int a= 100;
int b = -100;
(实际上存储的数据只有100,而有无符号是通过补码方式进行区分 )
不同点:
1.如果是无符号类型的数就不在需要补码,如果是有符号类型的数就需要进行补码 (在附录中有关于补码简单的介绍)
2.所表示的数据的极限范围不一样 :
以int和unsigned int为例:
unsigned int的范围是 0 到 2的 32 次方
int 的范围是:
(-2的31次方) 到( 2的31次方+1)
具体算的过程是这样的,同样试用于其他的数据类型范围计算(由于不同的平台内存对齐方式不一样,所以我讲的是最常见的内存对齐方式)
unsigned int 类型为例:
可以分配的内存大小为 4字节,一个字节是8bit ,那什么是8bit,1111 1111,简单理解就是8个二进制数,每个2进制数的可能性是两种,只有0或者1,所以可表示的范围就出来了,0 到 2的32次方(最通俗的讲一共有32个2相乘吗)
基本数据类型的内存对齐方式(由于不同的平台内存对齐方式不一样,所以我讲的是最常见的内存对齐方式)
float, int 分配内存 4个字节 32位
char 1个字节 8位
double, long 8个字节 64位
short 2个字节 16位
数据类型所使用的格式控制:
%c 单个字符
%s 字符串
%d 整数或者短整数
%ld 长整数
%p 地址16进制数
%x 16进制数
%#x 格式控制的16进制数
%f 单精度 %.1f 小数点后1位,以此类推
%lf 双精度 %.1lf 小数点后1位 ,以此类推
枚举,结构体和 联合(联合现在可以直接用强制类型转换替代,不需要多了解)
强制类型转换的作用就是 : 和目标数据类型,进行内存对齐
例子:
int a = 3;
char b = 4;
a = (int) b;
枚举主要是列出了一堆常量,用名字去替代,常量数字 当然也可以使用相关的宏定义,进行宏展开
例如:
enum{
flase=0,
true=1
};
然后在你的程序里,flase就代表着数字0,true就代表着数字 1,bool类型的源码就是这么玩的
在C代码中枚举和结构体用的是最多的
结构体就是对同类操作的数据类型进行了相关的分类,是对象操作的基础 ,可以这样理解,描述了对象的属性
例子:
struct student{
int age;
char name[0];
};
位运算在驱动和服务程序 中的数据解析用的多,所以,基本概念 可以上网查,我说几个关键和经常用的几个掩码:
[16进制] [2进制]
0x1 0001
0x2 0010
0x4 0100
0x8 1000
0xf 1111
0x7 0111
0xd 1110
具体的这个得多练习才能熟练使用,所以没啥好方法
但是 16进制数是最接近2进制的数,在程序中
我说一下换算关系
16进制数 2的4次方 为什么呢,那是2进制数只有两张表示方式0和1, 满16进一位,也就是说一位16进制数单个位上最大是16,你用2进制表示就是 1111 , 所以现在你就可以讲多位的16进制数进行分解,得到2进制
例子:
0xf8 【16进制】
1111 1000 【2进制】
所以这道这个就ojbk了
接下来就是内存管理 ,但是内存管理的前提是,你对所有的数据类型了如指掌才行,没啥好方法,得多写写,多改点bug就知道了,C语言的内存管理模型比较抽象,也没必要非得掌握,硬讲也不是不可以,但是估计不会好理解,先接过去 ,我有时间会在写一片详细的,C的内存管理方法,但是看看汇编语言,会有助于理解c的内存管理模型
接下来最重要的就是指针了
指针第一阶段理解:
先理解这样一个问题
指针就是地址;
C的声明 (注意我注释的顺序,这个顺序就是 ,解读声明的顺序),我讲的例子就是 最基础的声明的例子,复杂声明的分析方法,和下面的分析方法相同;
有一个优先级的关系:
[ ] >() > *
例子:
int a ; //a是一个变量 ,int 类型的
int *a ; //a是一个指针,指向int类型的
int a[0x1000]; //a是一个数组,存储的数据类型是int型的
int *a[0x1000]; //a是一个数组, 存储的是指针,这个指针指向的是int类型的数据
int (*a)[0x1000]: //a是一个指针, 指向的是 int类型的数组
int a(); //a是一个函数,返回值的是一个int类型的数据
int *a();//a是一个函数,返回值是一个指向int类型的数据的一个指针
int (*a)();//a是一个指针,指向的是一个返回值是int类型的一个函数
如果总结出来了,恭喜你,那么,高级声明你就完全没问题了
那么开始进行指针理解:
*取值运算符: 读取内存空间的数据
&取地址运算符: 读取内存空间的地址信息
先看普通的声明定义 ;
int a = 3 ; //a 的内存空间,有两部分组成,标注内存空间的地址和所存储的数据
普通声明和定义的变量的地址信息是只读的 ,是不允许修改的
{那么就不可以修改吗 ,可以的,我喜欢在,直接在内存中修改,二进制代码病毒}
如图所示(大概实际的排列方式类似):
[ addr | data ]
那么再看
int *b = &a ;//b是一个指针变量,那么他的内存空间,也是主要包括两个部分:
标注的内存空间的地址和a的地址信息数据,
存储的是a的地址信息,
那么请看b的内存空间布局:
如下图:
[ b的地址 | a的地址信息 ]
如果理解上面的内容之后,恭喜你对指针进行了基础的理解了
为了帮助你理解 ,你可以这样想:
1.指针和数组很像(但不是完全等价的,为什么呢 ,百度上有很多)
2.如果内存太抽象,还可以这样理解
抽屉柜组 -> 单个抽屉柜 -> 单个抽屉,你在想想数组下标值,在想想指针 , 是不是若有所思了
指针第二阶段理解:
那么指针是如何操作地址读取数据的呢 ,那个是编译器的 责任,我只讲人可以理解的部分,
int *b = &a ;
首先编译器一看是指针变量,第一步先找到变量b,怎么找到b,当然是通过b的地址信息找到b的内存空间
然后读取b的数据空间的数据,然后发现存储的是a的地址信息,紧接着就根据a的地址的信息,找到a的内存空间,
然后再去读取,a的内存空间的存储的数据 ,
所以简单来说就是指针,是间接读取数据的 ,普通变量是直接读取的
第三阶段指针理解 :
多级指针
以2级指针为例:
int **c; //这是一个2级指针,wc,为啥多了一个,不要急,还是慢慢来搞他
2级指针说破天他存储的还是地址信息,但是存储的 一级指针的地址信息,so你懂的分析方法和上面的分析方法是一致的,读取的数据的方法还是不会变的 。
你理解了这个方法之后, 我再说说2级指针的使用方法,
int **c ; // 表明他是一个 二级的
*c //表示存储1级的数据空间
&c // 表示的是2级指针的空间地址信息
3级以上以此类推
第四阶段 指针理解
存储的是某个变量的空间的地址信息