• C语言学习之路(基础篇)—— 数据类型 01


    说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!

    常量与变量

    关键字

    C语言关键字共有 32

    数据类型关键字(12)控制语句关键字(12)存储类关键字(5)其他关键字(3)
    char,short,int,long,float,double,unsigned,signed,struct,union,enum,voidif,else,switch,case,default,for,do,while,break,continue,goto,returnauto,extern,register,static,constsizeof,typedef,volatile
    auto :声明自动变量
    short :声明短整型变量或函数
    int: 声明整型变量或函数
    long :声明长整型变量或函数 
    float:声明浮点型变量或函数 
    double :声明双精度变量或函数
    char :声明字符型变量或函数
    struct:声明结构体变量或函数
    union:声明共用数据类型
    enum :声明枚举类型 
    typedef:用以给数据类型取别名
    const :声明只读变量
    unsigned:声明无符号类型变量或函数   
    signed:声明有符号类型变量或函数   
    extern:声明变量是在其他文件正声明   
    register:声明寄存器变量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    数据类型

    数据类型的作用:编译器预算对象(变量)分配的内存空间大小。
    通俗的讲就是:告诉编译器我这个数据在内存中需要多大的空间

    在这里插入图片描述

    常量

    • 在程序运行中不能改变的量
    • 常量一般出现在表达式或赋值语句中
    整型常量100,200,-100,0
    字符型常量‘a’,‘b’,‘1’,‘\n’
    字符串常量“a”,“ab”,“12356”
    浮点型常量3.14,0.125

    变量

    1) 什么是变量

    • 程序运行中可以被改变的量,存在于内存中
    • 变量在使用前必须先定义,定义变量前必须有相应的数据类型

    2) 标识符命名规则

    • 标识符不能是关键字
    • 标识符只能由字母、数字、下划线组成
    • 第一个字符必须为字母或下划线
    • 标识符中字母区分大小写

    3) 变量的特点

    • 变量在编译时为其分配相应的内存空间
    • 可以通过其名字地址访问相应内存
      在这里插入图片描述

    4) 声明和定义区别

    • 声明变量不需要建立存储空间,如:extern int a;
    • 定义变量需要建立存储空间,如:int b;

    举例:

    #include
    
    int main(){
    	//extern 关键字只做声明,不能做任何定义
    	//声明一个变量a,a在这里没有建立存储空间
    	extern int a;
    	a = 10; //err, 没有空间,就不可以赋值
    	int b = 20; //定义一个变量b,b的类型为int,b赋值为20
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行程序,提示错误:“error LNK2005: _main 已经在 hello.obj 中定义”,这里的错误是因为博主在hello.c中已经存在了main方法导致,C程序只能有一个main方法。

    在这里插入图片描述

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

    在这里插入图片描述

    从广义的角度来讲声明中包含着定义,即定义是声明的一个特例,所以并非所有的声明都是定义:

    • int b 它既是声明,同时又是定义
    • 对于 extern b来讲它只是声明不是定义

    一般的情况下,把建立存储空间的声明称之为“定义”,而把不需要建立存储空间的声明称之为“声明”。

    错误示例一:声明一个常量,名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进位。

    十进制二进制八进制十六进制
    0000
    1111
    21022
    31133
    410044
    510155
    611066
    711177
    81000108
    91001119
    10101012A
    11101113B
    12110014C
    13110115D
    14111016E
    15111117F
    16100002010

    二进制

    二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。

    当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。

    术语含义
    bit(比特)一个二进制代表一位,一个位只能表示0或1两种状态。数据传输是习惯以“位”(bit)为单位。
    Byte(字节)一个字节为8个二进制,称为8位,计算机中存储的最小单位是字节。数据存储是习惯以“字节”(Byte)为单位。
    WORD(双字节) 2个字节,16位
    DWORD两个WORD,4个字节,32位
    1b1bit,1位
    1B1Byte,1字节,8位
    1k,1K1024B
    1M(1兆)1024k, 1024*1024
    1G1024M
    1T1024G
    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

    在这里插入图片描述

    有符号和无符号数

    1) 有符号数

    有符号数是最高位(左边第一个)为符号位,0代表正数,1代表负数。
    有符号的用 signed 表示,默认不用写。

    举例:如定义 char num; 就是表示有符号的整形变量 num 即表示 signed char num; 而定义有符号的变量 signed 可以默认不写。
    在这里插入图片描述

    2) 无符号数

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

    在这里插入图片描述

    当我们写程序要处理一个不可能出现负值的时候,一般用无符号数,这样可以增大数的表达最大值。

    3) 有符号和无符号整型取值范围

    数据类型占用空间取值范围
    char1字节-128 到 127 (-2^7 ~ 2^7-1)
    short2字节-32768 到 32767 (-2^15 ~ 2^15-1)
    int4字节-2147483648 到 2147483647 (-2^31 ~ 2^31-1)
    long4字节-2147483648 到 2147483647 (-2^31 ~ 2^31-1)
    unsigned char1字节0 到 255 (0 ~ 2^8-1)
    unsigned short2字节0 到 65535 (0 ~ 2^16-1)
    unsigned int4字节0 到 4294967295 (0 ~ 2^32-1)
    unsigned long4字节0 到 4294967295 (0 ~ 2^32-1)

    取值范围的计算,以 char 类型举例说明:

    signed char
    负:1 00000001 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 00000000 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
    0000000011111111
    0 ~ 255 即:0 ~ 2^8-1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    通过以上举例可以得出:有符号数和无符号数能够表示数的个数相同(都是256个),只是表示数的范围不一样。

    计算机内存数值存储方式

    1) 原码

    原码:数的最原始的二进制码。
    一个数的原码(原始的二进制码)有如下特点:

    • 最高位做为符号位,0表示正,为1表示负
    • 其它数值部分就是数值本身绝对值的二进制数
    • 负数的原码是在其绝对值的基础上,最高位变为1

    下面数值以 char 类型 1 字节的大小描述:

    十进制数原码
    +150000 1111
    -151000 1111
    +00000 0000
    -01000 0000
    -11000 0001
    +10000 0001

    试想:1 + -1?(在计算机中只有加法没有减法)如果用原码去存,进行相加的结果是

    -01000 0000
    +00000 0000
    -11000 0001
    10000 0001
    1 +  -10000 0001
    1000 0001
    1000 0010  = -2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    结论: 负数如果在计算机中用原码存,会导致两个问题 ,负数运算结果不正确,0的状态还有两种。

    2) 反码

    • 对于正数,反码与原码相同(正数的反码不变)
    • 对于负数,符号位不变,其它部分取反(1变0, 0变1)
    十进制数反码
    +150000 1111
    -151111 0000
    +00000 0000
    -01111 1111
    -11111 1110
    +10000 0001

    试想:1 + -1?如果用反码去存,进行相加的结果是

    // 原码
    -01000 0000
    +00000 0000
    -11000 0001
    10000 0001
    1 +  -10000 0001
    1000 0001
    1000 0010  = -2
    
    // 反码
    -01111 1111
    +00000 0000
    -11111 1110
    10000 0001
    1 +  -10000 0001
    1111 1110
    1111 1111  = -0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    结论: 负数如果计算机用反码去存,负数运算结果正确,但是 0的状态还是有两种

    3) 补码

    在计算机系统中,数值一律用补码来存储。
    补码特点:

    • 对于正数,原码、反码、补码相同
    • 对于负数,其补码为它的反码加1
    十进制数补码
    +150000 1111
    -151111 0001
    +00000 0000
    -00000 0000
    -11111 1111
    +10000 0001

    试想:1 + -1?如果用补码去存,进行相加的结果是

    // 原码
    -01000 0000
    +00000 0000
    -11000 0001
    10000 0001
    1 +  -10000 0001
    1000 0001
    1000 0010  = -2
    
    // 反码
    -01111 1111
    +00000 0000
    -11111 1110
    10000 0001
    1 +  -10000 0001
    1111 1110
    1111 1111  = -0
    
    // 补码
    -00000 0000
    +00000 0000
    -11111 1111
    10000 0001
    1 +  -10000 0001
    1111 1111
    0000 0000 = 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    结论: 如果计算机用补码去存,负数运算结果是正确的,0的状态只有一种

    4) 补码的意义

    在计算机系统中,数值一律用补码来存储,主要原因是:

    • 统一了零的编码
    • 将符号位和其它位统一处理
    • 将减法运算转变为加法运算
    • 两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃

    5) 数值溢出

    当超过一个数据类型能够存放最大的范围时,数值会溢出。

    有符号位最高位溢出的区别:符号位溢出会导致数的正负发生改变,但最高位的溢出会导致最高位丢失。

    数据类型占用空间取值范围
    char1字节-128到 127(-2^7 ~ 2^7-1)
    unsigned char1字节0 到 255(0 ~ 2^8-1)

    数据溢出示例:

    #include
    
    
    int main() {
    	char num = 127 + 2;
    	printf("num=%d", num);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    运行结果:数据溢出,结果打印出来为 -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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    6) 补码求原码

    补码符号位不动,其他位求反,最后整个数加1,得到原码

    原码:1001 0111
    反码:1110 1000
    补码:1110 1001
    
    补码求反码(补码符号位不变,其他位取反):1110 1001 —— 1001 0110
    反码求原码(反码加1):1001 0110 —— 1001 0111
    
    补码求原码(补码符号位不变,其他位取反,最后整个数加11110 1001 —— 1001 0111
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    知道了如何通过补码求原码,那么回到思考中,再去计算就明白为什么打印出来是-127了

    // 补码:1000 0001
    // 反码:1111 1110
    // 原码:1111 1111 = -127
    
    • 1
    • 2
    • 3

    反码、补码、原码验证:

    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    在这里插入图片描述

    7) 8位补码转换32位补码

    经验总结: 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
    • 2
    • 3
    • 4
    • 5
    	// 如果最高位是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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    8) C语言如何表示相应进制数

    十进制以正常数字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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
  • 相关阅读:
    操作符(operator)
    【Linux】进程地址空间
    牛客小白月赛54
    React 函数式组件性能优化指南
    如何在机器学习中使用数据集编程
    C语言考试题库之填空题
    放大招:腾讯云5年服务器和3年轻量应用服务器租用价格表
    计算机网络的发展
    vue2升级vue3:Vue Demij打通vue2与vue3壁垒,构建通用组件
    std c++ 编写的 8 窗口出票模拟程序
  • 原文地址:https://blog.csdn.net/qq_41782425/article/details/127502983