目录
基本数据类型的介绍
类型的意义
修饰符类型:
整型数据类型
int:
short int(通常写short):
long int(通常写long):
long long(通常写long long):
char
使用多种整数类型的原因
整型常量的存储
1.字面常量:
2.后缀:
3.字节顺序:
字符常量的存储
整数溢出
有符号整数溢出:
无符号整数溢出:
位操作整数溢出
危险实例:
大小端
高低位
高低地址
区分高低地址和高低位
小端字节序存储(Little Endian):
大端字节序存储·(Big Endian):
为什么会有大小端?
如何确定大小端?
共用体确定大小端
指针确定大小端
基本数据类型的介绍
C语言中常见的数据类型有:
- int(整型):用于表示整数,例如:int a = 10;
- float(浮点型):用于表示小数,例如:float b = 3.14;
- char(字符型):用于表示单个字符,例如:char c = 'A';
- double(双精度浮点型):用于表示较大范围和精度的小数,例如:double d = 3.1415926;
- void(无类型):通常用于函数的返回值类型,表示无返回值。
类型的意义
1.使用这个类型开辟内存空间的大小(大小决定了使用范围)
2.编译器如何看待这内存空间存了什么(比如说int,编译器就认为这里面存了整数)
修饰符类型:
- short(短整型):用于表示较小范围的整数,例如:short e = 100;
- long(长整型):用于表示较大范围的整数,例如:long f = 100000;
- unsigned(无符号类型):用于表示非负数,例如:unsigned int g = 10;
- signed(有符号类型):用于表示有正负号的整数,例如:signed int h = -10;
整型数据类型
c语言把不含小数点和指数的数作为整数。因此
C语言中的整型数据类型包括int、short、long和long long,实际上还包括char
int:
大小:通常为4个字节,在32位系统中范围为-2,147,483,648(-2^31)到2,147,483,647(2^31-1)。
修饰符:signed(int默认为signed int)或unsigned。signed int \ int可以表示正负整数,而unsigned int只能表示非负整数。
需要注意的是,int类型的大小可能会因为不同的编译器和操作系统而有所变化。因此,在编写程序时要考虑到这一点,以保证程序的可移植性。
short int(通常写short):
大小:通常为2个字节,在32位系统中范围为-32,768(-2^15到32,767。
修饰符:signed(short默认为signed short)或unsigned。signed short \ short可以表示正负整数,而unsigned short只能表示非负整数。
long int(通常写long):
大小:通常为4个字节,在32位系统中范围与int相同,即-2,147,483,648到2,147,483,647。
修饰符:signed(long默认为signed long)或unsigned。
long long(通常写long long):
大小:通常为8个字节,在32位系统中范围为-9,223,372,036,854,775,808到9,223,372,036,854,775,807。
修饰符:signed(long long默认为signed long long)或unsigned。
注意:long至少要和int一样长,int至少要和short int一样长
char
C语言中的char用于存字符,但是从技术层面,char是整数类型。
char类型用于表示单个字符,本质是存储了该字符的ASCII码值(所以char也可以被视为整型,大小为-2^7到2^7-1)。它通常为1个字节大小。标准的ASCII码的范围是0-127,用char类型存储绰绰有余。
char类型可以用于表示字母、数字、标点符号和特殊字符等。每个字符都有一个对应的ASCII码值。
char ch; // 声明一个char变量
ch = 'A'; // 将字符常量赋值给变量
char ch = 'B'; // 声明并初始化char变量
char可以使用单引号将字符常量括起来。例如:'a'、'A'、'1'、'@'等。
char ch1 = 'A';
char ch2 = 'B';
int result = ch1 + ch2; // 字符相加,结果为整数
char类型还可以通过转义序列来表示特殊字符,如'\n'表示换行符,'\t'表示制表符。
char newline = '\n'; // 换行符
char tab = '\t'; // 制表符
char backslash = '\\'; // 反斜杠
char类型可以被赋值为整数,因为每个字符都有一个对应的ASCII码值。字符型变量也可以与整型变量进行运算。
char a=65;
printf("%c",a);//结果是A
需要注意的是,C语言中的char类型既可以被视为字符型数据类型,也可以被视为整型数据类型。这是因为char类型实际上是一个整数类型,它可以表示字符的ASCII码值。
#include <stdio.h>int main() {unsigned char d= 220;printf("%d",d);//结果是220return 0;}
实际上char也有signed char 和unsigned char,至于char是signed char 还是unsigned char,取决于编译器,因此我们需要将char变量控制在signed char和unsigned char的交集
使用多种整数类型的原因
C语言提供了多种整数类型,主要是为了满足不同的需求和优化程序性能。下面是使用不同整数类型的一些常见原因:
-
空间优化:不同整数类型在存储空间上有不同的大小,选择合适的整数类型可以节省内存空间。例如,如果只需要存储0到255之间的整数,可以使用无符号字符型(unsigned char),它只需要1个字节的存储空间。
-
平台兼容性:不同的计算机体系结构和操作系统可能有不同的整数大小和范围限制。选择适当的整数类型可以保证程序在不同平台上的兼容性。
-
运算精度:某些类型的整数提供了更高的精度或更大的范围,以满足特定的数值计算需求。例如,长整型(long)提供了更大的范围,可以表示更大的整数值。
-
数据类型匹配:在处理某些特定数据或接口时,可能需要使用特定的整数类型以便与其他软件系统或硬件设备进行有效的数据交换。
-
优化性能:在某些情况下,使用较小的整数类型可以提高程序的性能。例如,使用无符号整数类型可以避免运行时的符号扩展操作,从而提高计算效率。
总之,选择适当的整数类型可以帮助提高程序的效率、节约内存,并保证程序的可移植性和兼容性。在选择整数类型时,需要根据具体的需求和特定的应用场景来做出合理的选择。
整型常量的存储
C语言中整数常量的存储方式取决于常量的值和类型。下面是一些常见的整数常量存储方式:
1.字面常量:
在源代码中直接写明的整数常量,如5、100、-10等,存储方式通常是根据常量的值和类型决定的。c语言把大多数整型常量视为int类型,但是非常大的整数除外。如果程序中使用的数字(比如2345)超过int的大小范围,编译器会尝试使用unsigned int,如果还不够大,编译器将会依次使用long,unsigned long,long long和unsigned long long类型(前提是编译器能识别这些类型)
我们不免会在程序中使用整数常量,就像下面一样
1111111111111 /*编译器先将这个数存为int,发现不够大,
改存为unsigned int,发现还不够大,依次类推到unsigned long long,
直到能存下这个数的类型为止*/
int a=10000000000000;//编译器先把这个数转化为二进制,然后交付给int类型的时候,直接截断
2.后缀:
整数常量的存储方式还可以通过使用后缀来指定具体的类型。
常见的后缀包括u或者U表示无符号整数、l或者L表示长整数,ll或者LL表示长长整数。例如,常量10u表示无符号整数类型的10,常量1000000000l表示长整数类型的1000000000。
3.字节顺序:
对于占据多个字节的整数常量,存储方式还与字节顺序有关。通常采用的是小端字节顺序,即低位字节存储在低地址,高位字节存储在高地址。
我们后面会介绍
总之,整数常量的存储方式取决于常量的值和类型。编译器会根据常量的大小和后缀来选择合适的整数类型进行存储,同时也会根据字节顺序来存储多字节的整数常量。
字符常量的存储
在c语言中,用单括号括起来的单个字符被称为字符常量。编译器一旦发现字符常量,就会将其转换未相应的ASCII码值并存储起来
整形提升
C语言中的整型提升是指在表达式中使用不同类型的整数时,会自动将较低精度的整数类型转换为较高精度的整数类型。
整型提升的规则如下:
1. 如果操作数中存在有符号和无符号整数,那么有符号整数会自动转换为无符号整数进行运算。
2. 如果操作数中存在不同大小的整数类型,那么小的整数类型会自动转换为大的整数类型进行运算。
3. 如果操作数中存在有符号整数和浮点数,那么有符号整数会自动转换为浮点数进行运算。
4. 如果操作数中存在不同大小的浮点数类型,那么小的浮点数类型会自动转换为大的浮点数类型进行运算。
整型提升的目的是为了避免数据丢失和提高运算精度。需要注意的是,整型提升只发生在运算符和赋值语句中,不会改变变量的类型。如果想要将提升后的整数类型赋值给原来的整数类型变量,需要进行强制类型转换。
例如,将一个signed char类型和一个int类型相加:
signed char a = 10;
int b = 20;
int c = a + b;
在这个例子中,signed char类型会被自动提升为int类型,然后进行相加运算。
整形提升可以避免数据丢失和不一致的问题,确保运算结果的正确性。但是需要注意的是,整形提升并不改变变量的实际类型,只是在表达式中临时提升为更高精度的类型。
整数溢出
我们知道每种数据类型都是有它的取值范围的,int的取值范围是-2^31到2^31-1,那如果我们用int类型存一个超出这个范围的值会怎么样呢?
下面是一些展示C语言整数溢出的示例:
有符号整数溢出:
#include <stdio.h>int main() {int num = 2147483647; // int类型最大值num = num + 1; // 整数溢出printf("溢出后的值:%d\n", num);return 0;
}
输出结果: 溢出后的值:-2147483648
由于num
已经达到了int类型的最大值,再加1就超过了它的表示范围,导致整数溢出。溢出后的值变成了-2147483648,即int类型的最小值。
事实上c标准没有定义有符号类型的溢出规则,以上描述的溢出行为比较具有代表性,但是也会出现其他情况
无符号整数溢出:
#include <stdio.h>int main() {unsigned int num = 4294967295; // unsigned int类型最大值num = num + 1; // 无符号整数溢出printf("溢出后的值:%u\n", num);return 0;
}
输出结果: 溢出后的值:0
由于num
已经达到了unsigned int类型的最大值,再加1就超过了它的表示范围,导致整数溢出。由于unsigned int类型没有负数表示,溢出后的值变成了0。
位操作整数溢出
下面是一个在位操作中导致整数溢出的例子:
#include <stdio.h>int main() {unsigned int a = 1;unsigned int b = a << 32; // 左移32位printf("b: %u\n", b);return 0;
}
在这个例子中,我们定义了一个无符号整数变量a
,并将其左移32位赋值给变量b
。根据C语言标准,对于一个32位的无符号整数,左移32位将导致整数溢出,结果是未定义的。
实际运行该程序时,输出结果可能是任意的,因为溢出后的行为是未定义的。在某些编译器上,可能会得到期望的结果0,但在其他编译器上,可能会得到不同的结果。这种结果的不确定性是因为溢出后的行为是未定义的,编译器可以自由选择溢出后的行为。因此,在位操作中使用正确的数据类型以及进行边界检查是非常重要的,以避免产生不确定的行为和潜在的错误。
危险实例:
#include <stdio.h>int main() {unsigned char num ; for(num=0;num<=255;num++)printf("%u\n", num);return 0;
}
我们会发现上面这个例子已经陷入了死循环
由于unsigned char类型的范围是0-255,在num为255的情况下加1会导致循环溢出,使得num的值变为0。
C语言中整数溢出的情况:
-
有符号整数溢出导致的未定义行为:当有符号整数溢出时,C语言标准规定这种情况会导致未定义行为。这意味着编译器不需要定义溢出后的行为,可能会产生任意的结果。例如,对于有符号整数类型int,当将其最大值加1时,它可能变成最小值,也可能导致未定义行为。
-
无符号整数溢出的模运算特性:对于无符号整数类型,溢出时会采用模运算的特性。例如,使用
unsigned int
类型,该类型的取值范围为0到4294967295。当达到最大值4294967295并再加1时,它将经过模运算,结果是0。这意味着溢出后的值将回到类型的最小值。 -
位操作中的整数溢出:在进行位操作时,如果操作数超过了数据类型的位宽度,会发生整数溢出。例如,对于
unsigned int
类型,其位宽度为32位。如果将其左移33位,将会发生整数溢出,结果是未定义的。
总体来说,C语言中的整数溢出是一个需要注意的问题。在编写代码时,应该特别关注使用有符号整数的情况,以及对无符号整数进行位操作的情况。为了避免整数溢出,应该合理选择适当的数据类型,并进行边界检查。
大小端
C语言中的大小端(Endianness)指的是字节顺序的不同方式,即如何将多字节的数据类型(如整数、浮点数)在内存中存储。
高低位
先来了解数字的高低位
0x12345678
越靠近1这边的位就叫高位,越靠近8那边的位叫低位
高低地址
什么是高地址,什么是低地址,举举例说明?
可以把主存看成一本空白的作业本,你现在要在笔记本上记录一些内容,他的页码排序是
第一页 : 0x0000001
第二页 : 0x0000002
…
最后一页: 0x0000092
1 如果你选择从前向后记录
(用完第一页,用第二页,类推)这就是先使用低地址,后使用高地址.
0x0000001 -> 0x0000002-> … -> 0x0000092
业内有这样表述:动态分配内存时堆空间向高地址增长,说的就是这种情况.
这个向高地址增长就是先使用低地址,后使用高地址的意思.
2 如果你选择从后往前记录
(先用笔记本的最后一页,用完后使用倒数第二页,类推) 这就是先使用高地址,后使用低地址
0x0000092 -> … ->0x0000002 -> 0x0000001
业内表述:0xbfac 5000-0xbfad a000
是栈空间,其中高地址的部分保存着进程的环境变量和命令行参数,低地址的部分保存函数栈帧,栈空间是向低地址增长的.
这个向低地址增长就是先使用高地址,后使用低地址的意思.
区分高低地址和高低位
这个高地址
与低地址
容易与高位低位
产生混淆.
比如我这个月工资为1234
(一千二百叁拾肆块),那么这串数字的左边我们称呼为高位,右边称为低位.
(这个高低来自于人类的阅读习惯,数字从左向右,表示由大到小)
在计算机中以int
类型存储工资,假设int
占用四个字节,每个字节地址如下
0x00008
0x00009
0x0000a
0x0000b
把工资加载到内存中时,就会有两种存储方式,如下:
// 大端法
0x00008 => 1
0x00009 => 2
0x0000a => 3
0x0000b => 4
或者
// 小端法
0x00008 => 4
0x00009 => 3
0x0000a => 2
0x0000b => 1
内存中的低地址存储工资中的高位这种方式称为大端法
.如果把上边的存储方式反过来,内存中的高地址存储工资中的高位,则称为小端法 little endian
.
(注释:可以采用异或方法来记忆 低地址存低位为小端法-> 弟弟小
O-O).
主机采用大端还是小端表示数据由CPU的架构决定,如果两个主机只见交互数据,但是字节序表示不同,需要同化.
小端字节序存储(Little Endian):
在小端字节序中,最低有效字节存储在最低地址,最高有效字节存储在最高地址。例如,整数值0x12345678在内存中的存储方式如下:
低地址 ─────> 高地址
78 56 34 12
这种字节顺序在x86架构的计算机上被广泛使用,包括大部分的个人电脑和服务器。
大端字节序存储·(Big Endian):
在大端字节序中,最高有效字节存储在最低地址,最低有效字节存储在最高地址。例如,整数值0x12345678在内存中的存储方式如下:
低地址 ─────> 高地址
12 34 56 78
这种字节顺序在一些嵌入式系统和网络协议中使用较多。
为什么会有大小端?
简单点说就是硬件厂商各有所好,并没有统一的约定制作制作哪一个,大端的优势在于第一个字节就是高位,很容易判断正负性。小端的优势在于第一个字节是低位,最后一个字节是高位,可以依次取出相应的字节进行运算,并且最终会把符号位刷新,这样运算起来更高效。
如何确定大小端?
当我们不知道当前换将是大端存储还是小端存储的时候,就需要用代码来确定当前环境的大小端,下面给出了两种确定大小端的方式:
共用体确定大小端
共用体里面的变量是公用一块空间的,int a = 0x11 22 33 44占据了四个字节,假设是小端第一个字存的就是数据的低位0x44 ,char c只占据了第一个字节
int IsSmallEnd1()
{union U u;u.a = 0x11223344;if (u.c == 0x44) {return 1;}elsereturn 0;
}int main()
{int i = IsSmallEnd1();if (i == 1){printf("小端模式\n");}else{printf("大端模式\n");}return 0;
}
指针确定大小端
强制类型转换会发生截取,下面用char*强制类型转换,截取了第一个字节的地址,然后解引用读取了第一个字节的数据。
int IsSmallEnd2()
{int i = 0x11223344;i = 0x11223344;if (*(char*)(&i) == 0x44){return 1;}elsereturn 0;
}int main()
{int i = IsSmallEnd2();if (i == 1){printf("小端模式\n");}else{printf("大端模式\n");}return 0;
}