1.数据类型介绍
C语言的内置类型
char 字符数据类型 1字节(8bit位)
short 短整型 2字节(16bit位)
int 整型 4字节(32bit位)
long 长整型 4/8字节
long long 更长的整型 8字节
float 单精度浮点型 4字节
double 双精度浮点型 8字节
其实char类型也可以归到整型里面,因为字符在存储表示的时候都用的它的ASCII值.
1.1 类型的基本归类
整型家族:
char
字符存储和表示的时候本质上使用的是ASCII值,ASCII值是整数,字符类型也归类到整型家族.
unsighed char signed char
short
unsigned short[int]
signed short[int]
int
unsigned int
signed int
long
unsigned long[int]
signed long[int]
对于有符号数和无符号数:
温度:由正负
年龄:正数
在C语言中表示有符号的数,用signed,可以是正数,可以是负数;unsigned表示无符号的数,只能是正数.
注意✨:在使用int类型的时候,我们写的int,实际上等价于signed int,在我们书写代码的时候,signed可以省略,但是unsigned不可以省略,必须写出来.
以下这几种定义是等价的:
int可以省略,signed可以省略:
short num;
short int num;
signed short num;
signed short int num;
unsigned short num;
unsigned short int num;
特殊注意的是😶🌫️😶🌫️:
1.char是否等价于signed char 取决于编译器
但是大多数编译器,char就是signed char
2.unsigned类型的数据要用%u来打印,%d是用来打印有符号的数据.比如unsigned int num=-1;如果用%d来打印,打印的是-1,用%u来打印,打印的是1
3.%d 打印有符号的数,结果是10进制
%u 打印无符号数,结果是10进制
4. 如果是%u打印,%u就直接把内存里面的值(也就是补码))当成原码打印出来
5. 不管变量的类型是什么,只要存这个值,就是把它的二进制的补码存进去,最终展示出来的结果是多少按照它的解读方式来定.
浮点型家族
float
double
构造类型(创造出来的类型,也叫自定义类型)
数组类型
结构体类型struct
枚举类型enum
联合类型union
1.去掉数组名,剩下的就是数组类型
指针类型
用这些指针创建一些变量,这些变量专门存储地址
int* pi;
char* pc;
float* pf;
void* pv;
有了地址就可找到内存空间的位置,从这个位置开始向后访问数据,按照什么样的节奏访问取决于数据类型.+1跳过几个字节,取决于指针类型.解引用访问几个字节,也取决于指针类型.
空类型
void表示空类型(无类型)
通常应用于函数的返回类型,函数的参数,指针类型.
函数的返回类型
void test(…){} //函数不需要返回值
void test(coid){} //函数不需要参数
void* p; //无具体类型的指针
2.整型在内存中的存储
一个变量的创建是要在内存中开辟空间的,空间的大小是根据不同的类型决定的.
比如:int a=10;
像内存申请4个字节的空间,把10存储进去
知识要点
1.内存中存储的都是二进制数据
2.计算机中的整数有三种2进制表示方法:原码,反码,补码
3.三种表示方法均有符号位,数值位两部分组成.
符号位0表示正数,1表示负数.
数值位: 正数的原反补码都相同
负整数的三种表示方法各不相同
负整数:
原码:
直接将数值按照正负数的形式翻译成二进制就可以得到原码
反码:
将原码的符号位不变,其他位依次按位取反就可以得到反码
补码:
反码+1得到补码
通过代码的内存窗口验证内存中整型数据存的是补码
#include<stdio.h> int main() { int a = 20; //32个bit位:最高位表示符号位,剩下的31bit位表示数值位 //20的二进制序列: //0 0000000 00000000 00000000 00010100 --原码 //正数的原码反码补码全都相同 int b = -10; // 1 0000000 00000000 00000000 00001010 -10的原码 // 1 1111111 11111111 11111111 11110101 -10的反码 // 1 1111111 11111111 11111111 11110110 -10的补码 }
结论: 对于整型来说,数据存放内存中其实存放的是补码
为什么整型数据在内存中存储的是补码呢?
int main() { 1 - 1; //怎么算?CPU中只有加法器---->转换成加法计算 1 + (-1); //00000000 00000000 00000000 00000001 1的补码 //10000000 00000000 00000000 00000001 -1的原码 //11111111 11111111 11111111 11111110 -1的反码 //11111111 11111111 11111111 11111111 -1的补码 // 00000000 00000000 00000000 00000001 1的补码 // 11111111 11111111 11111111 11111111 -1的补码 //相加之后的结果: // 1 00000000 00000000 00000000 00000000 //对于一个整型来说是32比特位,现在是33位,最高位就丢了 //丢了之后的结果: //00000000 00000000 00000000 00000000 结果是0,证明可以通过补码算出来 //可以通过原码算出来吗? //00000000 00000000 00000000 00000001 1的原码 //10000000 00000000 00000000 00000001 -1的原码 //相加之后的结果: //10000000 00000000 00000000 00000010 结果是-2,我们发现用原码计算不对,而且在用原码进行计算的时候,符号位是否进行相加也不知道 }
大小端存储
在上面的内存图中,我们可以发现在内存中存储的数据是倒着放的,倒着往回读刚好是补码
这是为什么呢?😶🌫️😶🌫️😶🌫️
任何一个数据在存储的时候,它如果大于一个字节,就会有存储顺序的问题.
大端,小端:来自于<<格列夫游记>>中鸡蛋大头小头剥的说法
字节序:注意是以字节为单位,把数据划分成一个一个字节之后讨论字节为单位在内存中存储的顺序
我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
设计一个小程序判断当前机器是大端存储还是小端存储
int main() { int a = 1; char* p = (char*)&a;\ if (*p == 1) { printf("小端\n"); } else { printf("大端\n"); } return 0; }
是否可以将刚刚判断大小端的代码封装成一个函数呢?
可以
int check_sys() { int a = 1; char* p = (char*)&a; //int* if (*p == 1) { return 1; } else { return 0; } } int main() { if (check_sys() == 1) printf("小端\n"); else printf("大端\n"); return 0; }
优化一下
int check_sys() { int a = 1; if (*(char*)&a== 1) { //直接解引用 return 1; } else { return 0; } } int main() { if (check_sys() == 1) printf("小端\n"); else printf("大端\n"); return 0; }
再优化
int check_sys() { int a = 1; return *(char*)&a; //直接返回 } int main() { if (check_sys() == 1) printf("小端\n"); else printf("大端\n"); return 0; }
练习
1.输出什么?
//输出什么? #include <stdio.h> int main() { char a = -1; signed char b = -1; unsigned char c = -1; printf("a=%d,b=%d,c=%d", a, b, c); return 0; }
从运行的结果来看:
char a;就是signed char a;因为两个的结果是一样的
详解:
char a = -1;
signed char b = -1;
unsigned char c = -1;
关于有符号数和无符号数的补充
char c;
char类型是1byte=8bit位
8bit位放到二进制序列所有的可能性:
00000000 0
00000001 1
00000010 2
00000011 3
00000100 4
00000101 5
…
01111111 127
10000000 没有办法-1,直接被翻译成-128 -128
10000001 ->原码11111111 -127
…
11111110 ->原码 10000010 -2
11111111 ->原码 10000001 -1
总共有2^8个这样的二进制序列,即255个
最左边那一列数字是最高位,是符号位,符号位是0表示正数,1表示负数总结发现:
char类型变量的取值范围是: -128~127
简单记忆成: -2 ^ 7~2 ^ 7-1
知道char类型变量的取值范围的意义是什么?
比如说char c=200;这样是错的,因为char类型能表示的最大整数是127
unsigned char
二进制序列是:
00000000 1
00000001 2
00000010 3
00000011 4
…
01111111 127
10000000 128
10000001 129
…
11111111 255
对于无符号数,最高位不是符号位,但是最高位是有效位
总结发现:
无符号数的char取值范围是0~255
简单记忆成0~2^8-1
推广
short
16个bit位
有符号数:1位是符号位,15位是有效位
取值范围:
-2 ^ 15~2 ^15-1
unsigned short
无符号数:
取值范围: 0~2^16-1
2.输出的是什么?
#include <stdio.h> int main() { char a = -128; printf("%u\n",a); return 0; }
详解:
-128
10000000 00000000 00000000 10000000 原码
11111111 11111111 11111111 01111111 反码
11111111 11111111 11111111 10000000 补码
a是char类型,只能放低八位
10000000 a
现在以%u的形式打印,a是char类型,现在需要进行整形提升(注意对a进行整形提升的时候要看a的类型)
提升的时候,a的类型是有符号char,所以最高位是符号位,整形提升的时候高位全部补1
补完之后的结果如下(内存提升之后是个补码)
11111111 11111111 11111111 10000000
以无符号数%u打印,对于无符号数,原码反码补码全都一样
所以原码也是
11111111 11111111 11111111 10000000
直接打印出来
结果是个很大的数字
3…2的变形
#include <stdio.h> int main() { char a = 128; printf("%u\n", a); return 0; }
128
00000000 00000000 00000000 10000000
放到a里面的二进制序列 10000000
与-128时放到a里面的二进制序列一样
所以接下来的运算和之前的一样
所得到的结果也和-128的一样
对于char类型的二进制序列图记忆:
4.输出是什么?
int main() { int i = -20; unsigned int j = 10; printf("%d\n", i + j); //按照补码的形式进行运算,最后格式化成为有符号整数 }
详解:
int类型里面存的下-20和10,只要存的下,这两个变量里面的值就是-20和10,但从值的角度来讲,加起来一定等于-10,又以有符号的形式打印,结果就是-10
如果不敢确定的话,我们来详细分析一下:
就是把这两个数值的原码写出来,换算成补码,相加之后,再换算成原码的形式打印出结果
-20
10000000 00000000 00000000 00010100 原码
11111111 11111111 11111111 11101011 反码
11111111 11111111 11111111 11101100 补码
10
00000000 00000000 00000000 00001010 原码
正数的原码补码反码相同
-20+10
11111111 11111111 11111111 11101100 补码
00000000 00000000 00000000 00001010 补码
相加之后:
11111111 11111111 11111111 11110110
(计算的结果是存在内存中的,是补码)
求结果的原码
11111111 11111111 11111111 11110101 反码
10000000 00000000 00000000 00001010 原码
最后打印的就是原码 -10
注意😶🌫️
不需要考虑强制类型转换成相同的数据类型再进行相加.因为转换成相同的类型之后,存原来的数据还是能存的下的只要是4个直接能存的下就可以,与是否为int类型还是unsigned类型没有关系,只要能存的下,存的数值是不变的.
存的下的情况下不会因为存到别的类型里面值发生变化.
类型转换的用途是什么?
1.相同数据类型的变量进行计算,更方便计算
2.当算术转换一个变量里面的数字为无符号数字时,最好用%u打印.注意算数转换是临时的,变量还是原来的类型.