数据类型
在初C语言中,我们已经学习过基本内置类型以及它们所占存储空间的大小。我们再回顾一下。
每一个类型所占空间的大小都不一样,这么多丰富的类型,那它们存在的意义是什么呢?
类型的意义:
- 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
- 如何看待内存空间的视角。
这样我们就会在合适的场景选择合适的数据类型。
整形家族
char unsigned char signed char short unsigned short [int] signed short [int] int unsigned int signed int long unsigned long [int] signed long [int] long long //关于long long我们酌情使用。 signed 有符号的 unsigned 无符号的
为什么char字符会归为整形家族?
字符在内存中的存储的是字符的ASCII码值,ASCII码值是整形。
signed和unsigned 修饰的整形有什么区别吗?
signed是有符号的;unsigned int 是无符号的。
int a;//== sined int a signed int a; unsigned int a;
我们平时在编译器上创建一个变量,相当于创建一个有符号的变量。
当然short long long long 均是创建变量相当于创建一个有符号signed 的变量。(除了char)
那char呢?
char是否是相当于signed char?C语言标准并没有规定,取决于编译器。
大部分编译器是,不排除小部分编译器是unsigned int
ASCII码表中规定的ASCII码值的范围是0~127(字符均是正数)。
只有小部分负数是可以存储在 signed char
浮点型家族
float double long double //建议酌情使用
构造类型
//自定义类型 > 数组类型 > 结构体类型 struct > 枚举类型 enum > 联合类型 union
指针类型
int *pi; char *pc; float* pf; void* pv;//无具体类型的指针
空类型
void test(void) //第一个void 表示test函数不会返回任何值 //第二个void 表示test函数没有参数 { // } int main(void)// int main(int argc,char* argv[],char* envp[])
整形在内存中的存储(原反补)
一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。
NO1.
那接下来我们来谈谈数据在所开辟内存中到底是如何存储的呢?
计算机能够处理的是二进制的数据。
整型和浮点型数据在内存中也是以二进制的形式进行存储的。
整型在内存中存储的是补码的二进制序列。
NO2.
整型的二进制表示形式是怎样的呢?
计算机中的整型有三种二进制表示:原码,反码,补码。
- 分为有 有符号整数 和 无符号整数。
- 无符号整数:原码,反码,补码相同。
- 有符号整数:三种表示方法均有 符号位 和 数值位 两部分。
- 正数:原码,反码,补码相同。
- 负数:原码,反码,补码要进行计算的。
- 符号位:0 表示正
- 符号位:1 表示负
- 数值位:正数的原,反,补码相同。
- 数值位:负数的原,反,补表示方法各不同。
NO3.
signed int 和 unsigned int?
在内存中,对于存储一个整数需要4个字节,也就是32个比特位。
一个整数写出二进制序列的时候,也就是32个比特位。
有符号整数:最高位就是符号位。符号位是1,则表示是负数。符号位是0,则表示是正数。
无符号整数:没有符号位,所有位都是数值位。
同一个数无论是有符号整数,还是无符号整数的 数值均相同。
有符号整数的最高位符号位还是会参与运算。
如下:
#include<stdio.h> int main() { int a = 10;//== signed int //0 0000000 00000000 00000000 00001010 unsigned int b =10; //00000000 00000000 00000000 00001010 return 0; }
站在a的角度,第一个0就是符号位。
站在b的角度,第一个0就是数值位。
NO4.
负整数的原反补怎样转化呢?
NO5.
进制间的转化?这里我们不专门去讲解,可以自己学一学。
对于整形来说,数据存放内存中其实存放的是补码。为什么呢?
- 在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号和数值域统一处理。
- 同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码和原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
NO6.
#include<stdio.h> int main() { int a = -10;//== signed int //1 0000000 00000000 00000000 00001010 //1 1111111 11111111 11111111 11110101 //1111 1111 1111 1111 1111 1111 1111 0110——补码 //十六进制 //0x ff ff ff f6 unsigned int b =-10; //10000000 00000000 00000000 00001010 return 0; }
整形在内存中是以二进制的补码存储的,在编译器中为了观瞻,是以十六进制的方式去展示给我们,请问为什么是以十六进制倒叙放置的呢?那接下来我们就要介绍我们的大小端字节序存储。
大端小端字节序
关于内存中二进制/十六进制,单位 比特位和字节的转换,如下图。
NO.1
大端小端产生?
int a=0x11223344 //1 2 3 //百位 十位 各位 //高位 低位
根据字节存储来解释。
我们可以有无数中存储方式,只要按照存进去的方式,在需要使用时拿出来按照原来的顺序放置还原。怎么存都可以!
但是为了简便我们只采用了两种存储方式:
大端字节序存储:把一个数据的低位字节序的数据存放在内存的高地址处,高位字节处的数据存放在内存的低地址处。
小端字节序存储:把一个数据的高位字节序的数据存放在内存的低地址处,高位字节处的数据,存放在内存的高地址处。
除了字符整型,都是这两种存储方式,char1个字节不需要顺序。
NO.2
什么是大端小端?
NO.3
为什么有大端小端?
NO.4
笔试题:请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序?
根据上图就有如下代码:
#include<stdio.h> int main() { int a = 1;// char* p = (char*)&a;//强制转化 if (*p == 0) printf("大端\n"); if(*p == 1) printf("小端\n"); return 0; }
//用函数包装呢? #include<stdio.h> int check_sys(void) { int a = 1; char* p =(char*) &a;//强制类型转化 if (*p == 0) return 0; if (*p == 1) return 1; } int main() { int ret = check_sys(); if (ret == 0) printf("大端\n"); if (ret == 1) printf("小端\n"); return 0; } //简化 #include<stdio.h> int check_sys() { int a = 1; return *(char*)&a; } int main() { int ret = check_sys(); if (ret == 0) printf("大端\n"); if (ret == 1) printf("小端\n"); return 0; }
练习题
//以下主要讲解的是char类型的signed 和unsigned //可以推广到short/long等等 %d 是十进制的形式打印有符号整数 %u 是十进制的形式打印无符号整数
NO1.
请问下面程序a,b,c分别是多少?
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; }
#include <stdio.h> int main() { char a = -1; //有符号的 负数 char类型 // 10000000 00000000 00000000 00000001原码 // 11111111 11111111 11111111 11111110反码 // 11111111 11111111 11111111 11111111补码 //存储char 11111111补码 signed char b = -1; //有符号的 负数 char类型同上 //存储char 11111111补码 unsigned char c = -1; //无符号的 负数 char类型 //无符号——没有符号位的概念 // 无符号整数一般放置正数,如果放置负数还是按照负数的原反补来计算 // 10000000 00000000 00000000 00000001原码 // 01111111 11111111 11111111 11111110反码 // 01111111 11111111 11111111 11111111补码 // 存储char 11111111补码 //存储char 11111111 printf("a=%d,b=%d,c=%d", a, b, c); //%d 是十进制的形式打印有符号整数 // 即便没有符号,当作有符号去打印 //整型提升 // 有符号位提升符号位 // 无符号位补0,提升0 // 有符号ab //11111111 11111111 11111111 11111111补码 //10000000 00000000 00000000 00000001原码 //打印-1 //b同理-1 //无符号c //1111111 //00000000 00000000 00000000 11111111补码原码反码 //正数的原码反码补码相同 //打印-c //225 return 0; }
NO2.
请问下面三端程序分别输出什么?
2. #include <stdio.h> int main() { char a = -128; printf("%u\n",a); return 0; } // #include <stdio.h> int main() { char a = 128; printf("%u\n",a); return 0; } // #include <stdio.h> int main() { char a = 384;//128+256 printf("%u\n",a); return 0; }
//2. #include <stdio.h> int main() { char a = -128; //有符号整型 char 负数 //10000000 00000000 00000000 10000000原码 //11111111 11111111 11111111 01111111反码 //11111111 11111111 11111111 10000000补码 //存储a char 10000000 printf("%u\n", a); //%u 是十进制打印无符号整型 //整型提升——变量的类型(有符号/无符号) //1111111 111111111 11111111 10000000补码 // 打印——看(u/d)——(u_原反补相同/d_正原反补相同_负的计算) //%u把a看成无符号整数 // 1就不是a的符号位了 //打印 //无符号整型原反补相同 //4,294,967,168 return 0; } // #include <stdio.h> int main() { char a = 128; //原反补相同 //00000000 00000000 00000000 10000000补码 //存储 10000000 printf("%u\n", a); //提升 11111111 11111111 11111111 10000000补码 //打印——看成无符号的——原反补相同 //所以还是同上 return 0; }
经过我们计算和分析,发现上面三段 代码输出的结果是一样的。why?
- 有符号的char类型(signed char)取值范围:-128~127
- 无符号的char类型(unsigned char)取值范围:0~225
- 截断:超过以上的范围,有一部分数据会被截断,只要存储在内存中的数据必须是在以上范围内。
NO3.
请问下面这段代码输出什么?
3. int i= -20; unsigned int j = 10; printf("%d\n", i+j); //按照补码的形式进行运算,最后格式化成为有符号整数
#include<stdio.h> int main() { int i = -20; //10000000 00000000 00000000 00010100 //11111111 11111111 11111111 11101011 //11111111 11111111 11111111 11101100补码 unsigned int j = 10; //00000000 00000000 00000000 00001010原反补相同 printf("%d\n", i + j); //11111111 11111111 11111111 11110110补码 //10000000 00000000 00000000 00001010原码 //-10 return 0; }
NO4.
下面这段代码输出什么? unsigned int的范围
4. unsigned int i; for(i = 9; i >= 0; i--) { printf("%u\n",i); }
NO5.
下面这段代码输出什么?signed char的范围
5. int main() { char a[1000]; int i; for(i=0; i<1000; i++) { a[i] = -1-i; } printf("%d",strlen(a)); return 0; }
NO6.
下面这段代码输出什么?unsigned char的范围
6. #include <stdio.h> unsigned char i = 0; int main() { for(i = 0;i<=255;i++) { printf("hello world\n"); } return 0; }
总结
有符号整型signed char
无符号整型unsigned int
- 有符号整型signed char的取值范围:127 ~ -128
- 无符号整型unsigned char的取值范围:0~255
同理我们可以将以上代码应用于其他整型short/int/long等等。总结出它们的取值范围。特别注意千万别掉进无符号整型的陷阱了!!🆗🆗🆗
例如:short两个字节 16个比特位
- 有符号整型signed short的取值范围:-32768~32767
- 无符号整型unsigned short的取值范围:0~65635
同理大家自己动手总结一下整型类型的取值范围。
关于浮点数的存储会在下篇博文讲解,同时也会去总结二进制中的知识和易错混点。
✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正。
代码-----------------→【gitee:https://gitee.com/TSQXG】
联系-----------------→【邮箱:2784139418@qq.com】