一、引入
1)我们所有要处理的数据文件(视频,图片,音频等文件)
都是以二进制的形式来保存在内存中的
2)将8bit称为一个字节(byte),并将字节作为最小的可操作单位
我们在保存这些数据的时候,要思考这些数据的属性,比如说这个数据的大小
为了避免一个很大的空间保存一个很小的数据,或者一个很小的空间保存一个很大的数据
那么怎么让操作系统知道开辟多大的空间呢?
就涉及到 数据类型
二、C语言数据类型
1、基本类型
系统分配给基本类型的变量的内存大小是固定的,C语言已经定义好了这个类型
1)整型
int 4个字节(32bits)
unsigned int 取值范围 0 ~ 2^32 - 1
(signed) int 取值范围 -2^31 ~ 2^31-1
short 2个字节(16bits)
unsigned short 取值范围 0 ~ 2^16 -1
(signed) short 取值范围 -2^15 ~ 2^15-1
long
32位机器 4个字节(32bits)
64位机器 8个字节(64bits)
---------------------
unsigned 无符号
代表所有的bit位都是数值
signed 有符号
最高位为符号位(只占1位) + 数值位
1 -> 负数
0 -> 正数
================
当CPU把数据进行运算时,不能直接把变量的数据进行计算
需要先把数据拷贝到 CPU内部的寄存器(32bits)
再对寄存器的值进行计算
当变量的数据小于32bits时,
无符号的数拷贝到寄存器高位补0
有符号的数拷贝到寄存器高位补符号位
2)字符型
char 1个字节(8bits)
unsigned char 取值范围 0 ~ 255
(signed) char 取值范围 -128 ~ 127
例子:
char a = 260;
printf("%d\n", a); // 4
3)浮点型(实型)
float (单精度) 占4个字节(32bits)
如图所示:(参考图示)
31位(最高位)为符号位,占1bit, 1为负数,0为正数
30~23bit 为 指数段 ,占8bits ,指数加上127后 得到二进制数
22~0bit 为 尾数段 ,占23bits
例子:
float d = 3.14;
double(双精度) 占8个字节(64bits)
如图所示:(参考图示)
63位(最高位)为符号位,占1bit, 1为负数,0为正数
62~52bit 为 指数段 ,占11bits ,指数加上1023后 得到的二进制数
51~0bit 为 尾数段 ,占52bits
例子:
将十六进制数 0xC1480000 转换成浮点数
0xC1480000
--》 11000001 01001000 00000000 00000000
--》 1 10000010 1001000 00000000 00000000
符号位 指数段 尾数段
符号位: 1 --》 负数
指数段E: 10000010
1000 0010 --》 130 --》 130-127 --》 3 即实际的指数部分为3
尾数段M: 1001000 00000000 00000000
这里,在尾数的左边 省略存储了一个1,是以实际的尾数为
1.10010000000000000000000
把这三个部分的数据单独拎出来后,
通过指数部分E的值 来调整位数部分M的值
方法:
如果指数E为正数,尾数M的小数点就向右移
如果指数E为负数,尾数M的小数点就向左移
小数点的移动位数有指数的绝对值决定
此时 E为3,是一个正数,尾数M的小数点就向右移3位
1.10010000000000000000000
--》1100.10000000000000000000
至此,上面就是这个浮点数的二进制形式,然后再去转换成十进制
整数部分: 1100 ---》 12
小数部分: 10000000000000000000 --》 1 * 2^(-1) --》 0.5
==》 12.5
由于符号位为1,因此这个浮点数为 -12.5
注意:
在C语言中,整数的默认的类型为int,浮点数的默认的类型为double
2、构造类型
C语言允许用户自定义类型
系统分配给构造类型的变量所占的内存大小 取决于该类型是定义的
数组类型
结构体类型
枚举类型
联合体类型(共用体)
3、指针类型
4、void空类型
void 代表在内存中不占内存空间大小
在C语言中,主要有三大作用
(1) void 作为函数的返回值,表示函数不返回任何数据 void 函数名
(2) void 作为函数的参数,表示函数不带任何参数,此时void可省 函数名(void)
(3) void * 通用指针,在同一个操作系统中,所有指针类型所占的内存大小是一样的
三、变量和常量
1、常量
在程序运行期间 其值不能被改变的量 叫常量
比如:
3,4, 5, 100, …
3 = 5; //error 3是一个常量,其值不能被改变
1)整型常量
(1)二进制常量:由0和1组成
0000 1000 --> 等价于十进制的8
(2)八进制常量:以字符0开头,后面接0个或多个0~7的字符
格式化输出 %o
比如:
0666, 0777, 01234, ...
(3)十进制常量
格式化输出 %d
(4)十六进制常量:以0x或0X开头,后面接0个或多个 0~9 a~f A~F 的字符
格式化输出 %x
比如:
0xff
0x10
================
不同进制之间的转换
二进制 <==> 八进制 : 1个八进制 对应 3个二进制
比如: 八进制 二进制
0 000
1 001
2 010
...
6 110
7 111
二进制 <==> 十六进制 :1个十六进制 对应 4个二进制
比如: 十六进制 二进制
0 0000
1 0001
2 0010
...
9 1001
a/A 1010
...
f/F 1111
二进制 转换成 十进制:
0111 --> 1*2^2 + 1*2^1 + 1*2^0 = 7
八进制 转换成 十进制:
0765 --> 7*8^2 + 6*8^1 + 5*8^0 =
结论:
1)任何进制 转换成 十进制 都是:直接乘以权值 然后全部相加即可
2)十进制 转换成 其他R进制: 除R取余法
3)任何进制 转换成 非十进制:
先把该进制数转换成二进制数,再去转换成对应的其他进制数
比如:
八进制 --》 二进制 ---》 十六进制
0777 --> 111 111 111
--> 0001 1111 1111 --> 0x1FF
2)浮点型常量
单精度 float 格式化输出 %f (默认输出6位小数)
双精度 double 格式化输出 %lf (默认输出6位小数)
printf("%.2f\n",b); //小数部分保留2位
printf("%5.2f\n",b); //整个小数一共占5位(包括小数点),其中小数部分占2位
向右靠齐,左端补空格
当实际位数大于约定输出的位数,则按实际位数输出
printf("%-5.2f\n",b); //整个小数一共占5位(包括小数点),其中小数部分占2位
向左靠齐,右端补空格
当实际位数大于约定输出的位数,则按实际位数输出
3)字符常量
字符常量是 用单引号''引起来的一个或多个字符的序列, 格式化输出 %c
比如:
'a' ~ 'z' '0' ~ '9' 'A' ~ 'Z' ....
在计算机中,保存一个字符,保存的是它的ASCII码,而不是这个字符本身
查看ASCII码:
终端上输入指令 man ASCII
OCT 八进制
DEC 十进制
HEX 十六进制
比如:
'0' --- 48
'A' --- 65
'a' --- 97
例子:
char a = 'A';
<==> char a = 65;
<==> char a = 0101;
<==> char a = 0x41;
(1)普通字符:一般一个字符我们称之为普通字符,可以打印出来,有形状的字符
比如:
'a' ~ 'z' '0' ~ '9' 'A' ~ 'Z' ....
(2)转义字符:一般不可以打印出来的,没有形状,有另外一层含义的字符
比如:
'\n' 换行符
'\t' 制表符
'\\' 反斜杠符
'%%' 百分号符
...
'\ddd' :ddd表示1到3位的八进制,打印效果为该数字对应的ASCII码的字符
putchar('\101'); --> 'A'
'\xdd' :ddd表示1到2位的十六进制,打印效果为该数字对应的ASCII码的字符
putchar('\x41'); --> 'A'
4)字符串常量
用双引号""引起来的一串字符
字符串常量在内存中保存一定会有一个'\0'结尾,我们称之为终止符(结束符)
'\0'的ASCII值为 0
"hello world"
格式化输出 %s
2、变量
1)
在程序运行期间 其值能够被改变的量 叫变量
变量实质上是内存的一个具有特定属性的存储空间,它是用来存储数据的
这块存储空间中的值 就是变量的值,而且这个值是可以改变的
变量名
在定义变量的时候,会在内存中分配空间(空间的大小由数据类型来决定的)
这个变量名就和这块空间的首地址相关联起来了
操作系统由变量名就可以访问到这个内存地址的空间
2)变量的定义语法:
数据类型 变量名;
数据类型 变量名 = 初始值;
int a;
"数据类型":所有C语言合法的类型都可以的
“变量名”:符合C语言标识符的命名规则
由数字、字母、下划线组成,
不能以数字开头,只能以字母或下划线开头,
不能与关键字冲突,且区分大小写
abc 12a sb int ab* c8_9 SB
x x x
3)变量的访问 (读/写)
(1)从变量中去读取值
实际上就是通过变量名找到相应的内存地址,从该地址中去取值
这时变量一般是在赋值符号(=)的右边,表示该变量的值,我们称之为右值(rvalue)
(2)往变量中去写入值
实际上就是通过变量名找到相应的内存地址,从该地址中去写入值
这时变量一般是在赋值符号(=)的左边,表示该变量对应的存储空间,称之为左值(lvalue)
例子:
int a;
a = 10; //a作为左值,代表a的那块存储空间,把10写入到这块空间中
int b = a; //a作为右值,代表变量a本身的值10,把10写入到b对应的空间中
我们在定义变量时,给它赋值,称之为初始化
如果没有给变量赋值,那么这个变量的值是不确定的,未知的
数据类型 变量名 = 初始值;
四、整数的存储问题
整数的存储是以二进制的补码形式 存放的
1)正数
正数的补码 就是其原码本身
例子:
char a = 13;
a ---> 0000 1101 --> 13的原码,也是它的补码
int b = 9;
b ---> 00000000 00000000 00000000 0000 1001 --> 9的原码,也是9的补码
2)负数
负数的补码是 其绝对值的原码 取反 加1 得到的
例子:
char a = -13;
|-13| --> 13 --> 0000 1101 绝对值的原码
1111 0010 取反(0变1,1变0)
1111 0011 +1
(-13的补码,-13在计算机中的存储形式)
已知一个负整数的补码,如何去求这个负整数的十进制数?
补码还原: (逆运算)
负数补码 --》 -1 --》 取反 --》绝对值的原码 --》 得到负数
例子:
1111 1110
---> 1111 1101
---> 0000 0010 2 绝对值的原码
---> -2
-2在计算机中的存储形式 1111 1110
254在计算机中的存储形式 1111 1110
-3 --》 3: 0000 0011 --> 1111 1100 --> 1111 1101
253 ----> 1111 1101
.....
结论:
一个负整数(-X) 会和一个正整数(2^N - X) 的存储形式是一样的
(N代表用多少bit位来存储这个数)
计算机怎么知道这个数到底代表的是正数还是负数呢?
%d :以有符号的(32位)整数形式进行输出
%u :以无符号的(32位)整数形式进行输出
总结:
1)当编译器以有符号(32位)整型输出(%d)时,是以补码还原的方式去解读
2)当编译器以无符号(32位)整型输出(%u)时,就没有符号位概念了
直接把所有位当作数值位转换成十进制 进行输出
3)当CPU进行数据运算时,直接是以内存中的存放形式进行运算的,即补码形式
例子:
1)
int a = -2;
printf("%d\n", a); // -2
printf("%u\n", a); // 2^32 - 2
a 00000000 00000000 00000000 0000 0010 绝对值的原码 2
11111111 11111111 11111111 1111 1101 取反
11111111 11111111 11111111 1111 1110 +1
%d : -2
补码还原
-1 ---> 取反 --》 绝对值 --》 负数
%u : 2^32 - 2
2)
unsigned int b = -1;
printf("%d\n", b); // -1
printf("%u\n", b); // 2^32 - 1
b 00000000 00000000 00000000 0000 0001
11111111 11111111 11111111 1111 1110
11111111 11111111 11111111 1111 1111 b的存放形式
%d : -1
%u : 2^32 - 1
3)
char d = -56;
printf("%d\n", d); // -56
printf("%u\n", d); // 2^32-56
d |-56| 0011 1000
1100 0111
1100 1000 d的存放形式
char --> int
1100 1000 char
--> 11111111 11111111 11111111 1100 1000 int
%d : 补码还原
11111111 11111111 11111111 1100 1000
11111111 11111111 11111111 1100 0111 -1
00000000 00000000 00000000 0011 1000 绝对值的原码 56
--> -56
%u : 2^32 - 56
五、整数之间的赋值问题
在C语言中,允许各个类型的整数之间相互赋值
char --> int
int --> short
unsigned int --> int
...
赋值规则:
1)长的数据类型 --》 短的数据类型
int --> short
int --> char
低字节直接拷贝,高字节全部舍弃
比如:
260 ---> 1 0000 0100 ---> char 0000 0100
2) 短的数据类型 --》 长的数据类型
低字节直接拷贝,高字节要分情况:
如果短的数据是无符号的,那么高字节就直接全部补0
如果短的数据是有符号的,那么高字节就全部补符号位
char --> int
1100 1010 --> 11111111 11111111 11111111 1100 1010
unsigned char --> int
1100 1010 --> 00000000 00000000 00000000 1100 1010
例子:
1)
unsigned char d = -56;
printf("%d\n", d);
printf("%u\n", d);
d |-56| 0011 1000
1100 0111
1100 1000 d的存放形式
unsigned char ---> int
1100 1000
--> 00000000 00000000 00000000 1100 1000
%d : 符号位为0,为正数, 直接输出 --> 200
%u : 直接输出 --> 200
2)
char a = 250;
char d;
d = a + 8;
printf("%d\n",d);
printf("%u\n",d);
a 1111 1010 (char)
8 00000000 00000000 00000000 0000 1000 (int)
a char --> int
11111111 11111111 11111111 1111 1010
+ 00000000 00000000 00000000 0000 1000 (int)
1 00000000 00000000 00000000 0000 0010 a+8 (int)
int --> char
d --> 0000 0010
%d: char --> int
00000000 00000000 00000000 0000 0010
符号位为0,为正数,直接输出 --》 2
%u: char --> int
00000000 00000000 00000000 0000 0010
直接输出 --》 2
2)
unsigned char a = 250;
int d = a + 8;
printf("%d\n",d);
printf("%u\n",d);
a 1111 1010 (unsigned char)
unsigned char --> int
1111 1010
a ---> 00000000 00000000 00000000 1111 1010
+8 00000000 00000000 00000000 0000 1000
00000000 00000000 00000001 0000 0010 258
d 00000000 00000000 00000001 0000 0010 (int)
%d : 符号位为0,为正数,直接输出 --》258
%u :直接输出 --》258
六、练习
1)
short a = 32767;
a = a + 1;
printf("%d\n", a);
printf("%u\n", a);
a --> 32767 --> 01111111 11111111 (short)
short --> int
a --> 00000000 00000000 01111111 11111111
1(int) 00000000 00000000 00000000 00000001
+ 00000000 00000000 10000000 00000000
int --> short
--> 10000000 00000000 a
%d : short --> int
11111111 11111111 10000000 00000000 然后进行 补码还原
11111111 11111111 01111111 11111111 -1
00000000 00000000 10000000 00000000 取反 绝对值原码32768
==> -32768
%u : short --> int
11111111 11111111 10000000 00000000 直接输出
==> 2^32 - 32768
2) unsigned int a = 10;
int b = -30;
int w = a + b;
if( a+b > 5)
{
printf("YES\n");
}
else
{
printf("栓Q \n");
}
if( w > 5)
{
printf("YES\n");
}
else
{
printf("栓Q \n");
}
a --> 00000000 00000000 00000000 0000 1010 (unsigned int)
b --> -30
--> |-30| 00000000 00000000 00000000 0001 1110
--> 取反 11111111 11111111 11111111 1110 0001
--> +1 11111111 11111111 11111111 1110 0010 (int)
a+b unsigned int + int ===> unsigned int
a --> 00000000 00000000 00000000 0000 1010 (unsigned int)
b --> 11111111 11111111 11111111 1110 0010 (int)
a+b --> 11111111 11111111 11111111 1110 1100 (unsigned int)
a+b的结果为无符号数
(1) a+b > 5 ==> YES
w 11111111 11111111 11111111 1110 1100 (int)
w的符号位为1,是一个负数
(2) w < 5 ==> 栓Q