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