※※※大家好!我是同学〖森〗,一名计算机爱好者,今天让我们进入学习模式。若有错误,请多多指教。
👍 点赞 ⭐ 收藏 📝留言 都是我创作的最大的动力!
目录
1、数据类型的介绍
类型的意义:
1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。2. 如何看待内存空间的视角。
1.1 类型的基本归类:
整形家族:
char 虽然是字符类型,但是字符类型存储的时候,存储的字符的ASCII码值,ASCII值是整数
char c1; //char 到底是有符号的?还是无符号的呢?
signed char c2; //有符号的
unsigned char c3; //无符号的
short s1; //有符号的
signed short s2; //有符号的
unsigned short s3; //无符号的
//short 等价于 signed short。 int可省略
既然这样,可能就有小伙伴会问啦!什么是有符号的,无符号的呢?
在我们生活中会有一些数字,如温度:10, -10。
有正负的数据可以存放在有符号的变量中。
也有一些数字,如年龄,只有正数。
只有正数的数据可以存放在无符号的变量中。
那又会有小伙伴问:你这也没讲什么是有符号,无符号呀!
例如:我们都知道short的大小为:short - 2byte - 16bit
所有的存储数据如下表所示:
如果是有符号的数据,最高位为符号位
最高位是0,表示正数。
最高位是1,表示负数。
如果是无符号的数据,最高位也是数据位。
int n1; //有符号的
signed int n2; //有符号的
unsigned int n3;//无符号的
//int 等价于 signed int。
long也同int 和short 即long 等价于 unsigned long [int], int可省略。
那么 char 到底是有符号的?还是无符号的呢?
不确定,c语言标准并未明确说明。取决于编译器,大部分的编译器下,char等价于signed char
浮点型家族:
构造类型: (自定义类型)
在这里可能有同学就有疑问了数组怎么就算自定义类型呢?
例如:int arr[10] ; //arr的类型是: int [10]
我们可以改变[10]为[5]
int arr2[5]; //int [5]
也可以改变int为char
char arr3[10]; //char [10]
所以数组只要它的类型发生变化如arr3;或者大小发生变化如arr2;或者全都发生变化都会变成不同的类型;即arr, arr1与arr2各不相同。
指针类型:
指针变量是用来存储地址的!
空类型:
void
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型。
2、 整形在内存中的存储
2.1 原码、反码、补码
计算机中的整数有三种表示方法,即 原码、反码和补码。三种表示方法均有 符号位 和 数值位 两部分,符号位都是用 0 表示 “ 正 ” ,用 1 表示 “ 负 ” ,而数值位用1和0的二进制来表大小。
负 整数的三种表示方法:
原码: 直接将数值按照正负数的形式翻译成二进制。
反码:将原码的符号位不变,其他位依次按位取反就可以得到了
补码:反码+1就得到补码。
正整数的原码、反码、补码相同
例如:
int a = 10; //正整数的原、反、补一样。
//原码:00000000 00000000 00000000 00001010
//反码:00000000 00000000 00000000 00001010
//补码:00000000 00000000 00000000 00001010
int b = -10; //负整数
//原码: 10000000 00000000 00000000 00001010
//反码: 11111111 11111111 11111111 11110101
//补码: 11111111 11111111 11111111 11110110
负整数由补码变为原码
方法一:补码减一,再符号位不变,其他位按位取反。
方法二:补码符号位不变,其他位按位取反,再加一。
有人可能会有疑问了。同一个补码的通过不同的方式得到的原码会不同吗?总不能有两个原码吧!
究竟结果如何,让我们走着瞧。
int b = -10;
//补码: 11111111 11111111 11111111 11110110
//方法一:
//补码减一:11111111 11111111 11111111 11110101
//按位取反:10000000 00000000 00000000 00001010
//方法二:
//补码取反:10000000 00000000 00000000 00001001
//再加一:10000000 00000000 00000000 00001010
//没错,方法一和方法二的结果相同,都可转化为十进制的10
既然有三种形式,那么计算中存储的是那种类型呢?
计算机内存储的是数据的补码。
那么为什么数据在计算机中存的是补码呢?
例如我们求1-1;
int i = 1 - 1;
//因为CPU中只有加法器,
//所以1-1会被转换成1 + (-1)
// 若计算机里储存的是原码;
// 1的原码: 00000000 00000000 00000000 00000001
//-1的原码: 10000000 00000000 00000000 00000001
//则i的原码:10000000 00000000 00000000 00000010
//1-1变成了-2;
//若是反码
// 1的补码:00000000 00000000 00000000 00000001
// -1的补码:11111111 11111111 11111111 11111111
// 相加: 1 00000000 00000000 00000000 00000000
// 而整型只能保存32bit,所以1丢失,
// 就变成: 00000000 00000000 00000000 00000000
// i = 0;
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
扩展一点:char的取值范围多少,unsigned char呢?
char占1byte = 8bit
char
// 二级制 十进制
//原码 0000 0000 0
// 0000 0001 1
// 0000 0002 2
// …… ……
// 0111 1111 127
// 1000 0000 直接解析为 -128
// 1000 0001 1111 1111 -127
// ……
// 1111 1110 1000 0010 -2
// 1111 1111 1000 0001 -1
// 原码
char的范围为-128到127,unsigned char的范围为0到255
例题:
猜猜打印结果是什么
#include <stdio.h>
int main()
{
unsigned int ch = -10;
//-10
//10000000000000000000000000001010
//11111111111111111111111111110101
//11111111111111111111111111110110
//
printf("%u\n", ch);
printf("%d\n", ch);
return 0;
}
%u是打印无符号数,意思是你要我打印的一定是无符号数,不是无符号数,我也认为是无符号数
//%d 是打印有符号数,意思是你要我打印的一定是有符号数,不是有符号的数,我也认为是有符号数
输出结果
没错这时候你可能又有疑惑了,4294967286是什么鬼,随机数吗?
让我们用计算器算一下-10的补码。
没错和我们打印的一样。正如你想。它就是-10的补码。
2.2 大小端介绍
什么大端小端:
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位 , ,保存在内存的高地址中。
例如 int a = 0x11223344;
为什么有大小端呢?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器)。
另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中,0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
例题:
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。
答案:
#include<stdio.h>
//int check_sys()
//{
// int a = 1;
// char* p = (char*)&a;
// return *p;
//}
int check_sys()
{
int a = 1;
return *(char*)&a;
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
由图可知可用char类型 的p指针来存int的地址,若 *p为1就是小端。若*p为0就是大端
练习
输出打印结果
#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;
}
运行结果:
分析:
-1是整数,占32bit。
-1 原码:10000000 00000000 00000000 00000001
-1反码: 11111111 11111111 11111111 11111110
-1补码: 11111111 11111111 11111111 11111111
因为char占8bit,故只能存 11111111
而打印a,b时,%d是整型,需要整型提升。 而a为有符号的。符号位为1,提升时补1.
整型提升为:11111111 11111111 11111111 11111111 即-1
而打印c时,%d是整型,需要整型提升。c为无符号的,为正数,提升时直接补0。
整型提升为:00000000 00000000 00000000 11111111
因为符号位为0,表示正数。原反补相同。为255
3、浮点型在内存中的存储 ★★★
浮点数的两种形式:3.1415,2E10;
浮点型的种类:float, double, long double。
让我们来看一个代码:
大家可以猜猜结果是多少?
#include<stdio.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;
}
答案是否和你想的一样呢?
从上结果可以看出,整型与浮点型的存储形式是不同的,没办法强制转换。
3.1 浮点数存储规则
(-1)^S * M * 2^E(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。M表示有效数字,大于等于1,小于2。 2^E表示指数位。
现在可能有同学比较疑惑了,这是什么?
让我们先来看道例题
5.5用上述形式表示。
十进制:5.5
转化成二进制:101.101,对吗?
小数点后面的1代表2^-1,第三个1代表2^-3.
101.101转化为十进制为2^2+1=5, 2^-1+2^-3=0.625,相加为 5.625。
而5.5的二进制形式应改为101.1。
因为是正数,所以S = 0,101.1变成1.011*2^2,(可以类比10进制的科学计数法)。
其中1.011是M,第二个2是E
综上:5.5 =(-1)* 0 * 1.011 * 2^2.
十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,s=1,M=1.01,E=2。
5.0 = (-1) * 1 * 1.01 * 2^2。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
而计算机只存储S、E、 M的值。
讲了那麽多,为防止讲错,误人子弟。让我们在编译器上跑一边。
#include<stdio.h>
int main()
{
float a = 5.5;
//101.1
//(-1) * 0 * 1.011 * 2 ^ 2;
//s = 0; 1bit
//E = 2 + 127 = 129; 8bit 加中间数再转换至二进制
//E:1000 0001
//M:1.011 32bit 小数点后面的部分,不够不够补0
//01100000000000000000000
//0 10000001 01100000000000000000000
// 0100 0000 1011 0000 0000 0000 0000 0000
// 0x 4 0 b 0 0 0 0 0 转化为16进制。
// 因为VS处理器是小端,
// 存储为:00 00 b0 40
return 0;
}
由上图所示,我们的推断是正确的。
这时,浮点数就采用下面的规则表示, 即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1. 。比如:0.5 ( 1/2 )的二进制形式为 0.1 ,由于规定正数部分必须为 1 ,即将小数点右移 1 位,则为1.0*2^(-1) ,其阶码为 -1+127=126 ,表示为01111110 ,而尾数 1.0 去掉整数部分为 0 ,补齐 0 到 23 位 00000000000000000000000 ,则其二进制表示形式为 :0 01111110 00000000000000000000000
这时, 浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字 M 不再加上第一位的 1 ,而是 还原为0.xxxxxx的小数 。这样做是为了表示 ±0 ,以及接近于 0 的很小的数字。
这时,如果有效数字 M 全为 0 ,表示 ± 无穷大(正负取决于符号位 s );
这时再让我们再回头看刚开始的题
n的反码为:00000000000000000000000000001001
所以 printf("n的值为:%d\n", n); //n的值为:9
把整形强制转换成单精度浮点型。
*pFloat 0 00000000 00000000000000000001001
因为被转换成浮点数,所以 以浮点数的提取方式来提取。
E = -126, M = 0.00000000000000000001001, S = 0
即:((-1)^0 * 0.00000000000000000001001 * 2^(-126)
我们知道%f默认存储小数点后面6位小数。光一个M就四舍五入为0了,别提再乘上一个
2^(-126)
printf("*pFloat的值为:%f\n", *pFloat); //*pFloat的值为:0.000000
而*pFloat = 9.0;
存储使用浮点数的形式进行储存。
9.0
1001.0
1.001 * 2^3
(-1)^0 * 1.001 * 2^3
S = 0
M = 1.001
E = 3 +127
0 10000010 00100000000000000000000整型打印的话,计算机把它认为称补码。
而0为符号位是正数。
正数,原、反、补相同
而上列数转化成十进制
printf("num的值为:%d\n", n); //num的值为:1,091,567,616
而浮点型再提取处理也必然是9.000000.
printf("*pFloat的值为:%f\n", *pFloat); //*pFloat的值为:9.000000
推测结果:
n的值为:9
*pFloat的值为:0.000000
num的值为:1,091,567,616
*pFloat的值为:9.000000
结果相同, 推测正确。
浮点型在内存中的存储 你会了吗?
若有疑问。或觉得小主有需要改善的地方。欢迎大家在文章末留言。
朝暮与年岁并往,愿与你一同行至天光。