C语言的内置类型
char 字符数据类型 1字节(8bit位)
short 短整型 2字节(16bit位)
int 整型 4字节(32bit位)
long 长整型 4/8字节
long long 更长的整型 8字节
float 单精度浮点型 4字节
double 双精度浮点型 8字节
其实char类型也可以归到整型里面,因为字符在存储表示的时候都用的它的ASCII值.
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; //无具体类型的指针
一个变量的创建是要在内存中开辟空间的,空间的大小是根据不同的类型决定的.
比如:int a=10;
像内存申请4个字节的空间,把10存储进去
1.内存中存储的都是二进制数据
2.计算机中的整数有三种2进制表示方法:原码,反码,补码
3.三种表示方法均有符号位,数值位两部分组成.
符号位0表示正数,1表示负数.
数值位: 正数的原反补码都相同
负整数的三种表示方法各不相同
负整数:
原码:
直接将数值按照正负数的形式翻译成二进制就可以得到原码
反码:
将原码的符号位不变,其他位依次按位取反就可以得到反码
补码:
反码+1得到补码
通过代码的内存窗口验证内存中整型数据存的是补码
#include
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;
}
//输出什么?
#include
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
#include
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}
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
直接打印出来
结果是个很大的数字
#include
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
00000000 00000000 00000000 10000000
放到a里面的二进制序列 10000000
与-128时放到a里面的二进制序列一样
所以接下来的运算和之前的一样
所得到的结果也和-128的一样
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打印.注意算数转换是临时的,变量还是原来的类型.
int main() {
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
}
结果在不断的往下打印:
注意unsigned无符号数都是>=0的,unsigned int能放下的最大二进制序列是
当i=0时,0-1=-1
-1的原码
10000000 00000000 00000000 00000001
反码
11111111 11111111 11111111 111111110
补码
11111111 11111111 11111111 11111111
✨如果-1存到一个无符号数的变量里面去,这个时候的最高位不是符号位,被当成一个无符号数来打印
✨在打印的时候,i是一个无符号型的变量,所以虽然里面放的是-1这样的一个值,但是站在i的角度,i里面不会放-1,他认为里面的每一个位都是有效位,是一个很大的数字,这个数字>=0.所以-1放到i这个变量里面,他不会认为是-1,而认为它是一个很大的数字.接下来再不停-1,就会出现很多数字
所以不管i里面放的是什么数,这个数恒>=0,for循环会进入死循环
✨也会一直死循环,但是可以打印出-1
✨因为在for循环的判断条件,i是无符号类型,所有的位都是有效位,i>=0条件恒成立会进入死循环
✨可以打印出-1是因为打印的时候以%d有符号数的形式打印
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
详解:
这个循环肯定循环1000次
strlen统计的是\0前字符出现的个数
但是要注意的是✨✨✨
a数组里面的每个元素都是char类型,char的取值范围是-128~127,那么数组元素可以放-1000这样的数字吗?
当a[i]是-128时,二进制序列再-1的时候,就会变成127,不会是-129(如上图箭头的指向)
#include
unsigned char i = 0;
int main()
{
for(i = 0;i<=255;i++)
{
printf("hello world\n");
}
return 0;
}
注意:
循环变量用无符号数控制的时候要小心注意啦!
详解:
常见的浮点数
3.14159 字面浮点数
1E10 科学计数法表示的浮点数,意思是1.0*10^10
浮点数家族包括:float,double,long double类型
整型家族的类型的取值范围:
limits.h中定义
int main()
{
int n = 9;
float *pFloat = (float *)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
✨(-1)^S * M * 2^E
✨(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数
✨M表示有效数字,大于等于1,小于2。
✨2^E中的E表示指数位。
10进制的5.5转换成二进制数字是多少?
是101.1
这个数字用科学计数法表示,小数点向左移动
1.011*2^2
第一个2是因为二进制的权重是2,第二个2是因为移动了两位
V=5.5
=1.011* 2^2
=(-1)* 0 *1.011 * 2^2
这里的S=0,M=1.011,E=2
所以只需要把S,M,E这三个数字存起来,就能把原来的浮点数还原出来
double 64位的浮点数
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
有效数字M,实际上在存储的时候,只存小数点后面的位.当往出拿的时候,拿出小数点后面的位,在小数点前面补个1就能还原出M真实的值.
单精度浮点数,双精度浮点数中的精度表示小数点之后的位存的更多,精度更高.(小数点后存的位是二进制位)
✨当存小数的小数点后面的部分时,二进制位可能会为了凑够小数部分而不断的向后扩张,那么对于可以存放23位小数部分的单精度,可以存放52位小数部分的双精度真的够吗?
可能会不够,所以说也有一些浮点数在内存中是不能够精确保存的.
首先,E为一个无符号整数(unsigned int)
1.这意味着,如果E为8位,它的取值范围为0~ 255;如果E为11位,它的取值范围为0~2047。
2.但是,我们知道,科学计数法中的E是可以出
现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,
3.
对于8位的E,这个中间数是127;
对于11位的E,这个中间数是1023。
比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001
(所以即使我的E是负的,经过修复之后变成正的值,这个时候就可以存到E里面去了)
如果E是-130,+127之后认识负数怎么办呢?
float也是有它的取值范围的,就不会让真实的E是-130的.
如果E=-130的话,已经超出float能够表示的最大值和最小值了.所以在能够讨论的范围内讨论.
指数E取出来,分为三种情况:
E全为0,说明原来的E加上127之后为0,原来的E是-127,原来的浮点型数据是一个非常小的数据
这个数据是 ± 1.xxx *2^(-127)
这个数据是接近于0的非常小的数字
8个比特位全为1的话就是255,这是加了127之后,原来的E是255-127=128
数据的存储内容全部总结到这里啦,如果对小伙伴们有所帮助的话,可以点赞收藏博客,关注后续的C语言学习内容哦~