说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!
C语言关键字共有 32 个
| 数据类型关键字(12) | 控制语句关键字(12) | 存储类关键字(5) | 其他关键字(3) |
|---|---|---|---|
char,short,int,long,float,double,unsigned,signed,struct,union,enum,void | if,else,switch,case,default,for,do,while,break,continue,goto,return | auto,extern,register,static,const | sizeof,typedef,volatile |
auto :声明自动变量
short :声明短整型变量或函数
int: 声明整型变量或函数
long :声明长整型变量或函数
float:声明浮点型变量或函数
double :声明双精度变量或函数
char :声明字符型变量或函数
struct:声明结构体变量或函数
union:声明共用数据类型
enum :声明枚举类型
typedef:用以给数据类型取别名
const :声明只读变量
unsigned:声明无符号类型变量或函数
signed:声明有符号类型变量或函数
extern:声明变量是在其他文件正声明
register:声明寄存器变量
数据类型的作用:编译器预算对象(变量)分配的内存空间大小。
通俗的讲就是:告诉编译器我这个数据在内存中需要多大的空间

| 整型常量 | 100,200,-100,0 |
|---|---|
| 字符型常量 | ‘a’,‘b’,‘1’,‘\n’ |
| 字符串常量 | “a”,“ab”,“12356” |
| 浮点型常量 | 3.14,0.125 |

举例:
#include
int main(){
//extern 关键字只做声明,不能做任何定义
//声明一个变量a,a在这里没有建立存储空间
extern int a;
a = 10; //err, 没有空间,就不可以赋值
int b = 20; //定义一个变量b,b的类型为int,b赋值为20
return 0;
}
运行程序,提示错误:“error LNK2005: _main 已经在 hello.obj 中定义”,这里的错误是因为博主在hello.c中已经存在了main方法导致,C程序只能有一个main方法。

注释或命名掉hello.c中的main方法即可,再次运行程序,程序出错

从广义的角度来讲声明中包含着定义,即定义是声明的一个特例,所以并非所有的声明都是定义:
一般的情况下,把建立存储空间的声明称之为“定义”,而把不需要建立存储空间的声明称之为“声明”。
错误示例一:声明一个常量,名MAX,值为10,给MAX赋值100

正确示例一:声明一个常量,名MAX,值为10;定义一个int类型变量a,将MAX的值赋给a变量,这是可以的

错误示例二:定义一个const常量,名为叫b,值为10;给b赋值11

正确示例二:定义一个const常量,名为叫b,值为10;定义一个int类型变量c,将常量b的值赋给c变量,再给变量c赋值

进制也就是进位制,是人们规定的一种进位方法。 对于任何一种进制—X进制,就表示某一位置上的数运算时是逢X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,以此类推,x进制就是逢x进位。
| 十进制 | 二进制 | 八进制 | 十六进制 |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 1 | 1 | 1 | 1 |
| 2 | 10 | 2 | 2 |
| 3 | 11 | 3 | 3 |
| 4 | 100 | 4 | 4 |
| 5 | 101 | 5 | 5 |
| 6 | 110 | 6 | 6 |
| 7 | 111 | 7 | 7 |
| 8 | 1000 | 10 | 8 |
| 9 | 1001 | 11 | 9 |
| 10 | 1010 | 12 | A |
| 11 | 1011 | 13 | B |
| 12 | 1100 | 14 | C |
| 13 | 1101 | 15 | D |
| 14 | 1110 | 16 | E |
| 15 | 1111 | 17 | F |
| 16 | 10000 | 20 | 10 |
二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。
当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。
| 术语 | 含义 |
|---|---|
| bit(比特) | 一个二进制代表一位,一个位只能表示0或1两种状态。数据传输是习惯以“位”(bit)为单位。 |
| Byte(字节) | 一个字节为8个二进制,称为8位,计算机中存储的最小单位是字节。数据存储是习惯以“字节”(Byte)为单位。 |
| WORD | (双字节) 2个字节,16位 |
| DWORD | 两个WORD,4个字节,32位 |
| 1b | 1bit,1位 |
| 1B | 1Byte,1字节,8位 |
| 1k,1K | 1024B |
| 1M(1兆) | 1024k, 1024*1024 |
| 1G | 1024M |
| 1T | 1024G |
| 1Kb(千位) | 1024bit,1024位 |
| 1KB(千字节) | 1024Byte,1024字节 |
| 1Mb(兆位) | 1024Kb = 1024 * 1024bit |
| 1MB(兆字节) | 1024KB = 1024 * 1024Byte |
十进制转化二进制的方法:用十进制数除以2,分别取余数和商数,商数为0的时候,将余数倒着数就是转化后的结果。

十进制的小数转换成二进制:小数部分和2相乘,取整数,不足1取0,每次相乘都是小数部分,顺序看取整后的数就是转化后的结果。

二进制转化十进制的方法:先把二进制从低位(最右边的“1”)开始按从右到左按0位开始顺序写出 ,整数二进制用数值乘以2的幂次(位数)依次相加,小数二进制用数值乘以2的负幂次然后依次相加!
例如:101011

八进制,Octal,缩写OCT或O,一种以8为基数的计数法,采用0,1,2,3,4,5,6,7八个数字,逢八进1。一些编程语言中常常以数字0开始表明该数字是八进制。
八进制的数和二进制数可以按位对应(八进制一位对应二进制三位),因此常应用在计算机语言中。

十进制转化八进制的方法:
用十进制数除以8,分别取余数和商数,商数为0的时候,将余数倒着数就是转化后的结果。

八进制转化十进制的方法:与二进制转十进制一样,只是一个乘以2的位幂,一个是乘以8的位幂
例如:256

十六进制(英文名称:Hexadecimal),同我们日常生活中的表示法不一样,它由0-9,A-F组成,字母不区分大小写。与10进制的对应关系是:0-9对应0-9,A-F对应10-15。
十六进制的数和二进制数可以按位对应(十六进制一位对应二进制四位),因此常应用在计算机语言中。

十进制转化十六进制的方法:
用十进制数除以16,分别取余数和商数,商数为0的时候,将余数倒着数就是转化后的结果。

十六进制转化十进制:跟前面不同的是十六进制中的子目A-F对应10-15去计算
例如:23b

有符号数是最高位(左边第一个)为符号位,0代表正数,1代表负数。
有符号的用 signed 表示,默认不用写。
举例:如定义 char num; 就是表示有符号的整形变量 num 即表示 signed char num; 而定义有符号的变量 signed 可以默认不写。

无符号数最高位不是符号位,而就是数的一部分,无符号数只能表示正数,不可能是负数。

当我们写程序要处理一个不可能出现负值的时候,一般用无符号数,这样可以增大数的表达最大值。
| 数据类型 | 占用空间 | 取值范围 |
|---|---|---|
| char | 1字节 | -128 到 127 (-2^7 ~ 2^7-1) |
| short | 2字节 | -32768 到 32767 (-2^15 ~ 2^15-1) |
| int | 4字节 | -2147483648 到 2147483647 (-2^31 ~ 2^31-1) |
| long | 4字节 | -2147483648 到 2147483647 (-2^31 ~ 2^31-1) |
| unsigned char | 1字节 | 0 到 255 (0 ~ 2^8-1) |
| unsigned short | 2字节 | 0 到 65535 (0 ~ 2^16-1) |
| unsigned int | 4字节 | 0 到 4294967295 (0 ~ 2^32-1) |
| unsigned long | 4字节 | 0 到 4294967295 (0 ~ 2^32-1) |
取值范围的计算,以 char 类型举例说明:
signed char
负:1 0000000 到 1 1111111 即:- (0*2^6+0*2^5+0*2^5+0*2^5+0*2^5+0*2^5+0*2^5) 到 - (1*2^6+1*2^5+1*2^4+1*2^3+1*2^2+1*2^1+1*2^0)
-0 ~ -127
正:0 0000000 到 0 1111111 + (0*2^6+0*2^5+0*2^5+0*2^5+0*2^5+0*2^5+0*2^5) 到 + (1*2^6+1*2^5+1*2^4+1*2^3+1*2^2+1*2^1+1*2^0)
+0 ~ +127
// 规定将 -0 表示 - 128
所以 char整形取值范围为 -128 ~ 127 即:-2^7 ~ 2^7-1
unsigned char
00000000 到 11111111
0 ~ 255 即:0 ~ 2^8-1
通过以上举例可以得出:有符号数和无符号数能够表示数的个数相同(都是256个),只是表示数的范围不一样。
原码:数的最原始的二进制码。
一个数的原码(原始的二进制码)有如下特点:
下面数值以 char 类型 1 字节的大小描述:
| 十进制数 | 原码 |
|---|---|
| +15 | 0000 1111 |
| -15 | 1000 1111 |
| +0 | 0000 0000 |
| -0 | 1000 0000 |
| -1 | 1000 0001 |
| +1 | 0000 0001 |
试想:1 + -1?(在计算机中只有加法没有减法)如果用原码去存,进行相加的结果是
-0:1000 0000
+0:0000 0000
-1:1000 0001
1: 0000 0001
1 + -1?
0000 0001
1000 0001
1000 0010 = -2
结论: 负数如果在计算机中用原码存,会导致两个问题 ,负数运算结果不正确,0的状态还有两种。
| 十进制数 | 反码 |
|---|---|
| +15 | 0000 1111 |
| -15 | 1111 0000 |
| +0 | 0000 0000 |
| -0 | 1111 1111 |
| -1 | 1111 1110 |
| +1 | 0000 0001 |
试想:1 + -1?如果用反码去存,进行相加的结果是
// 原码
-0:1000 0000
+0:0000 0000
-1:1000 0001
1: 0000 0001
1 + -1?
0000 0001
1000 0001
1000 0010 = -2
// 反码
-0:1111 1111
+0:0000 0000
-1:1111 1110
1: 0000 0001
1 + -1?
0000 0001
1111 1110
1111 1111 = -0
结论: 负数如果计算机用反码去存,负数运算结果正确,但是 0的状态还是有两种
在计算机系统中,数值一律用补码来存储。
补码特点:
| 十进制数 | 补码 |
|---|---|
| +15 | 0000 1111 |
| -15 | 1111 0001 |
| +0 | 0000 0000 |
| -0 | 0000 0000 |
| -1 | 1111 1111 |
| +1 | 0000 0001 |
试想:1 + -1?如果用补码去存,进行相加的结果是
// 原码
-0:1000 0000
+0:0000 0000
-1:1000 0001
1: 0000 0001
1 + -1?
0000 0001
1000 0001
1000 0010 = -2
// 反码
-0:1111 1111
+0:0000 0000
-1:1111 1110
1: 0000 0001
1 + -1?
0000 0001
1111 1110
1111 1111 = -0
// 补码
-0:0000 0000
+0:0000 0000
-1:1111 1111
1: 0000 0001
1 + -1?
0000 0001
1111 1111
0000 0000 = 0
结论: 如果计算机用补码去存,负数运算结果是正确的,0的状态只有一种
在计算机系统中,数值一律用补码来存储,主要原因是:
当超过一个数据类型能够存放最大的范围时,数值会溢出。
有符号位最高位溢出的区别:符号位溢出会导致数的正负发生改变,但最高位的溢出会导致最高位丢失。
| 数据类型 | 占用空间 | 取值范围 |
|---|---|---|
| char | 1字节 | -128到 127(-2^7 ~ 2^7-1) |
| unsigned char | 1字节 | 0 到 255(0 ~ 2^8-1) |
数据溢出示例:
#include
int main() {
char num = 127 + 2;
printf("num=%d", num);
return 0;
}
运行结果:数据溢出,结果打印出来为 -127

思考 ?以上数据溢出示例为何打印出 -127
#include
int main() {
//赋值时(输入),赋的是十进制,给的是原码。如果赋值给的是八进制或者十六进制给的是补码
//打印时(输出),十进制打印要的是原码,如果是十六进制或八进制打印要的数补码
char num = 129;
// 将十进制赋值给num时,给的就是原码,129 的原码,也就是二进制 即 1000 0001
// 129为正数,所以129的原码=补码=反码=1000 0001
// 而num为有符号数,所以最高位为1 计算机就会认为这个是负数的补码
// 但是打印变量num时,%d打印的是十进制,要的就是原码,而计算机存的是补码
// 出现的问题就是如何用补码求原码?
printf("num=%d", num);
return 0;
}
补码符号位不动,其他位求反,最后整个数加1,得到原码
原码:1001 0111
反码:1110 1000
补码:1110 1001
补码求反码(补码符号位不变,其他位取反):1110 1001 —— 1001 0110
反码求原码(反码加1):1001 0110 —— 1001 0111
补码求原码(补码符号位不变,其他位取反,最后整个数加1)1110 1001 —— 1001 0111
知道了如何通过补码求原码,那么回到思考中,再去计算就明白为什么打印出来是-127了
// 补码:1000 0001
// 反码:1111 1110
// 原码:1111 1111 = -127
反码、补码、原码验证:
#include
int main()
{
char num = 0x81; // 十六进制存的是补码 16进制转二进制 补码:1000 0001
printf("%d\n", num); //原码:1 1111111 = -127
unsigned sum = 0x82; //补码:1000 0010
printf("%u\n", sum); //无符号类型,补码=反码=原码:1000 0010 = 130
int num2 = 0x81; // 补码:1000 0001
// int类型转为32位 0000 0000 0000 0000 0000 0000 1000 0001 = 129
printf("%d\n", num2); // 有符号打印, 高位为0,表示正数,所以=129
printf("%u\n", num2); // 无符号打印 全是正数,即也是=129
unsigned int d = 0xffffffff; //定义无符号int变量d,以16进制方式赋值
// 补码:ffffffff
// 反码:1000 0000 0000 0000 0000 0000 0000 0000
// 原码:1000 0000 0000 0000 0000 0000 0000 0001
// unsigned 定义无符号变量d,计算机存在是补码ffffffff,但是打印时是以有符号打印,那么就需要进行转换
printf("有符号方式打印:d = %d\n", d); // %d 打印时会将ffffffff看成有符号数, 十进制打印要的是原码 即= - 1
printf("无符号方式打印:d = %u\n", d); // %u 无符号打印,则原码=补码=反码,所以结果就是本身 0xffffffff 转换成十进制的值为 4294967295
char num3 = 0x81; // 补码:1000 0001
// 将8位补码 1000 0001 转为 8位原码 再转为32原码 转为32位补码
// 8位补码: 1000 0001
// 8位原码: 1111 1111
// 32位原码:1000 0000 0000 0000 0000 0000 0111 1111 (将8位原码的高位拿到32位高位去,原位置变为0)
// 32位补码:1111 1111 1111 1111 1111 1111 1000 0001
printf("%d\n", num3); // 有符号打印, 十进制打印要的是原码:1000 0000 0000 0000 0000 0000 0111 1111 高位为1,表示负数,所以= -127
printf("%u\n", num3); // 无符号打印 全是正数,十六进制打印要的是补码:1111 1111 1111 1111 1111 1111 1000 0001 = ffffff81,所以=4294967169
printf("%x\n", num3); // 32位补码:1111 1111 1111 1111 1111 1111 1000 0001 = ffffff81
char num4 = 0x12;// 补码0001 0010
// 32位补码:0000 0000 0000 0000 0000 0000 0001 0010
printf("%x\n", num4); // 0001 0010 -> 0000 0000 0000 0000 0000 0000 0001 0010 即不变也即是原值 12
printf("%d", num4); // 将0x12转换为十进制得18
return 0;
}

经验总结: 8位补码如何求32位补码,根据8位补码的最高位的数值(0或1)来补起32位补码的前24位,最后将8位补码拿过来即可。
示例:
8位补码:1000 0001
32位补码:1111 1111 1111 1111 1111 1111 1000 0001
8位补码:0000 0001
32位补码:0000 0000 0000 0000 0000 0000 0000 0001
// 如果最高位是1,向前扩展1,如果最高位是0,则向前扩展0
char num3 = 0x81; // 补码:1000 0001 ->1111 1111 1111 1111 1111 1111 1000 0001
// 将8位补码 1000 0001 转为 8位原码 再转为32原码 转为32位补码
// 8位补码: 1000 0001
// 8位原码: 1111 1111
// 32位原码:1000 0000 0000 0000 0000 0000 0111 1111 (将8位原码的高位拿到32位高位去,原位置变为0)
// 32位补码:1111 1111 1111 1111 1111 1111 1000 0001
printf("%d\n", num3); // 有符号打印, 十进制打印要的是原码:1000 0000 0000 0000 0000 0000 0111 1111 高位为1,表示负数,所以= -127
printf("%u\n", num3); // 无符号打印 全是正数,十六进制打印要的是补码:1111 1111 1111 1111 1111 1111 1000 0001 = ffffff81,所以=4294967169
printf("%x\n", num3); // 32位补码:1111 1111 1111 1111 1111 1111 1000 0001 = ffffff81
char num4 = 0x12;// 补码0001 0010
// 32味补码:0000 0000 0000 0000 0000 0000 0001 0010
printf("%x\n", num4); // 0001 0010 -> 0000 0000 0000 0000 0000 0000 0001 0010 即不变也即是原值 12
printf("%d", num4); // 将0x12转换为十进制得18
| 十进制 | 以正常数字1-9开头,如123 |
|---|---|
| 八进制 | 以数字0开头,如0123 |
| 十六进制 | 以0x开头,如0x123 |
| 二进制 | C语言不能直接书写二进制数 |
#include
int main09()
{
int a = 123; //十进制方式赋值
int b = 0123; //八进制方式赋值, 以数字0开头
int c = 0xABC; //十六进制方式赋值
//如果在printf中输出一个十进制数那么用%d,八进制用%o,十六进制是%x
printf("十进制:%d\n", a);
printf("八进制:%o\n", b); //%o,为字母o,不是数字
printf("十六进制:%x\n", c); // %x 小写打印
printf("十六进制:%X\n", c); // %X 大写打印
printf("十六进制:%#x\n", c); // %#x 带格式0x的小写16进制
printf("十六进制:%#X\n", c); // %#X 带格式0X的大写16进制
printf("+++++++++++++++++++++++++++++++++++++++++++++++++++\n");
// 也可以这样打印
// 数在计算机存的内容没有改变,打印时只是输出的形式改变而已
int d = 056;
printf("十进制:%d\n", d);
printf("八进制:%o\n", d);
printf("十六进制:%x\n", d);
return 0;
}
输出结果:
十进制:123
八进制:123
十六进制:abc
十六进制:ABC
十六进制:0xabc
十六进制:0XABC
+++++++++++++++++++++++++++++++++++++++++++++++++++
十进制:46
八进制:56
十六进制:2e