数据类型
基本数据类型
- 整型(Integer Types)
- char:
- 占用字节数:通常占用 1 个字节(8 位)。
- 表示范围:有符号
char
的取值范围是 - 128 到 127,无符号char
的取值范围是 0 到 255。它主要用于存储字符的 ASCII 值,例如char c = 'A';
('A’的 ASCII 值为 65)。
- short:
- 占用字节数:一般占 2 个字节。
- 表示范围:有符号
short
的范围是 - 32768 到 32767,无符号short
的范围是 0 到 65535。
- int:
- 占用字节数:在不同的编译器和系统中,其占用的字节数可能不同。在 16 位系统中通常占 2 个字节,在 32 位和 64 位系统中通常占 4 个字节。
- 表示范围:在 32 位系统中,有符号
int
的范围是 - 2147483648 到 2147483647。
- long:
- 占用字节数:在 32 位系统中通常占 4 个字节,在 64 位系统中通常占 8 个字节。
- 表示范围:相应地,其表示范围也随字节数变化。
- long long:
- 占用字节数:通常占 8 个字节。
- 表示范围:有符号
long long
的范围是 - 9223372036854775808 到 9223372036854775807。
- char:
- 浮点型(Floating - Point Types)
- float:
- 占用字节数:占用 4 个字节。
- 精度:提供大约 7 位有效数字的精度,例如
float f = 3.14f;
(注意在 C 语言中给float
类型变量赋值时,最好在数字后面加上f
,以区分double
类型)。
- double:
- 占用字节数:占用 8 个字节。
- 精度:提供大约 15 位有效数字的精度,
double
类型在没有特别指定的情况下,是 C 语言中浮点数的默认类型,如double d = 3.1415926;
。
- float:
- void 类型
- 含义:void
表示没有类型或者未知类型。
- 主要用途:
- 作为函数的返回类型,表示函数不返回任何值,例如void function();
。
- 作为指针类型,表示通用指针,例如void *ptr;
,可以用来存储任何类型变量的地址,但在使用时需要进行类型转换。
派生数据类型
- 指针类型(Pointer Types)
- 定义:指针是一种特殊的变量,它存储的是另一个变量的内存地址。
- 示例:
int *ptr;
声明了一个指向int
类型变量的指针ptr
。char *str;
声明了一个指向char
类型变量的指针str
,常用于字符串操作。
- 大小:在 32 位系统中,指针类型变量通常占用 4 个字节;在 64 位系统中,通常占用 8 个字节。
- 数组类型(Array Types)
- 定义:数组是由相同类型的元素组成的集合。
- 一维数组示例:
int arr[5];
定义了一个包含 5 个int
类型元素的一维数组arr
。- 数组在内存中是连续存储的,对于
int arr[5]
,假设数组的起始地址为0x1000
,且在 32 位系统中int
类型占 4 个字节,那么arr[0]
存储在地址0x1000 - 0x1003
,arr[1]
存储在地址0x1004 - 0x1007
,以此类推。
- 二维数组示例:
int matrix[3][4];
定义了一个二维数组matrix
,它有 3 行 4 列。在内存中,二维数组也是按行顺序存储的。
- 结构体类型(Struct Types)
- 定义:结构体是一种用户自定义的数据类型,它可以将不同类型的数据组合在一起。
- 示例:
c struct student { char name[20]; int age; float score; };
- 定义了一个名为
student
的结构体类型,它包含了一个字符数组name
、一个整型age
和一个浮点型score
。 - 可以通过以下方式声明结构体变量:
struct student s1;
,然后可以对结构体变量的成员进行赋值和访问,如s1.age = 20;
。
- 定义了一个名为
- 共用体类型(Union Types)
- 定义:共用体也称为联合体,它允许不同类型的数据共享同一块内存空间。
- 示例:
c union data { int i; char c; };
- 定义了一个名为data
的共用体,它包含一个整型i
和一个字符型c
。
- 声明共用体变量并使用:
c union data d; d.i = 10; printf("%d", d.c);
- 由于共用体的成员共享内存,修改其中一个成员的值会影响到其他成员的值。
- 枚举类型(Enumerated Types)
- 定义:枚举类型是一种用户自定义的整数类型,它通过列举所有可能的值来定义一个新的类型。
- 示例:
c enum Weekday {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday};
- 这里
enum Weekday
是一个枚举类型,Monday
、Tuesday
等是枚举常量,默认从 0 开始依次赋值(Monday
的值为 0,Tuesday
的值为 1,以此类推),也可以手动指定枚举常量的值,如enum Weekday {Monday = 1, Tuesday, Wednesday};
,此时Monday
的值为 1,Tuesday
的值为 2,Wednesday
的值为 3。
- 这里
内存对齐
1. 内存对齐的概念
- 内存对齐是计算机系统为了提高内存访问效率而采用的一种数据存储方式。它要求数据存储的起始地址必须是某个特定值(通常是数据类型大小的整数倍)的倍数。
2. 为什么要进行内存对齐
- 提高内存访问效率
- 计算机的内存是以字节为单位进行编址的,而处理器在读取内存时,通常不是按单个字节来读取,而是按字长(如 32 位系统的字长为 4 字节,64 位系统的字长为 8 字节)进行读取。当数据按照内存对齐的方式存储时,处理器可以一次读取到完整的数据,减少了内存访问的次数,从而提高了程序的运行效率。
- 硬件设计的要求
- 很多硬件设备在进行数据传输和处理时,要求数据的存储地址必须是特定的对齐方式。例如,某些硬件总线在传输数据时,如果数据没有按照对齐方式存储,可能会导致硬件无法正确识别和处理数据,从而引发错误。
3. 内存对齐的规则
- 基本数据类型的对齐
- 对于基本数据类型(如
char
、short
、int
、double
等),其对齐方式通常是其自身大小的倍数。例如,在 32 位系统中,int
类型通常占用 4 字节,那么int
类型变量的存储地址应该是 4 的倍数;char
类型占用 1 字节,其存储地址可以是任意地址;double
类型占用 8 字节,其存储地址应该是 8 的倍数。
- 对于基本数据类型(如
- 结构体的对齐
- 结构体中各个成员的存储顺序按照定义的顺序依次排列。
- 每个成员的起始地址必须是该成员类型大小的整数倍。
- 结构体的大小必须是其最大成员类型大小的整数倍(或者是某些特定编译器规定的对齐模数的整数倍)。例如:
struct example { char a; int b; short c; };
- 在这个结构体中,
a
的起始地址可以是任意地址(因为char
类型占用 1 字节),假设a
的地址为 0。b
是int
类型,占用 4 字节,其起始地址必须是 4 的倍数,所以b
的起始地址为 4(跳过了 3 个字节)。c
是short
类型,占用 2 字节,其起始地址必须是 2 的倍数,所以c
的起始地址为 8。整个结构体的大小为 10 字节,但由于最大成员b
的类型int
占用 4 字节,结构体的大小必须是 4 的倍数,所以编译器会在结构体末尾填充 2 个字节,使结构体的最终大小为 12 字节。
4. 内存对齐的影响
- 对结构体大小的影响
- 如前面结构体示例所示,内存对齐会导致结构体的实际大小可能大于其成员所占字节数的总和,因为编译器会在成员之间或者结构体末尾添加填充字节来满足对齐要求。
- 对程序性能的影响
- 合理的内存对齐可以提高程序的运行速度,因为它可以减少内存访问的次数。但是,如果过度追求内存对齐,可能会导致内存空间的浪费,尤其是在内存资源有限的情况下。
- 对跨平台编程的影响
- 不同的计算机系统和编译器可能有不同的内存对齐规则。在跨平台编程时,需要注意这些差异,以确保程序在不同的平台上都能正确运行。
数据类型的意义
- 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
- 如何看待内存空间的视角。
整型在内存中的存储
- 对于整形来说:数据存放内存中其实存放的是补码。
- 原因:在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理; 同
时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
原码,反码,补码
以下是计算机中原码、反码、补码的详细介绍:
原码
- 定义:原码是一种简单的机器数表示法。在原码表示中,最高位为符号位,0表示正数,1表示负数,其余位表示数值的绝对值。
- 示例:
- 对于8位二进制数,+5的原码为00000101,-5的原码为10000101。
- 特点:
- 原码表示简单直观,易于理解。但在进行加减运算时,需要根据符号位判断是做加法还是减法,运算规则相对复杂。例如,当两个数的符号不同时,需要先判断它们的绝对值大小,然后用大的绝对值减去小的绝对值,最后根据大的数的符号确定结果的符号。
反码
- 定义:
- 正数的反码与原码相同。
- 负数的反码是在原码的基础上,符号位不变,其余各位按位取反。
- 示例:
- 对于8位二进制数,+5的反码为00000101, - 5的原码为10000101,其反码为11111010。
- 用途:
- 反码在计算机中主要用于一些特定的运算和逻辑操作,它为补码的计算提供了中间步骤。
补码
- 定义:
- 正数的补码与原码相同。
- 负数的补码是在其反码的基础上加上1。
- 示例:
- 对于8位二进制数,+5的补码为00000101, - 5的原码为10000101,反码为11111010,补码为11111011。
- 优势:
- 使用补码可以将减法运算转化为加法运算,简化了计算机硬件的设计。例如,计算5 - 3时,可以用5的补码加上 - 3的补码来实现,即00000101+11111101 = 00000010(忽略最高位的进位),结果为2。
- 补码能够统一处理正数和负数的运算,并且在表示范围上,对于n位二进制数,补码可以表示的范围是 - 2^(n - 1)到2^(n - 1)-1。例如,8位二进制数的补码表示范围是 - 128到127。
练习
- 输出什么?
#include <stdio.h>
int main()
{
char a= -1;
signed char b=-1;
unsigned char c=-1;
printf("a=%d,b=%d,c=%d",a,b,c);
return 0;
}
//c: 是按%d 整型格式打印,会发生整型提升
//c的原码:1000 0001
//反码: 1111 1110
//补码: 1111 1111
//因为是unsigned char 所以整型提升至 int 的时候,前面补零。
//00000000000000000000000011111111
//所以是 255
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}
//先写出 -128 的原码:
//10000000000000000000000010000000
//11111111111111111111111101111111
//11111111111111111111111110000000-补码
//整型截断:保留最低8位:
//10000000
//是按 %u 打印 所以再发生:整型提升补符号位。
//11111111111111111111111110000000 ,就是一个很大的数字
char类型变量的取值范围
以下是关于char
类型变量取值范围的详细介绍:
1. 有符号char
类型
- 在 C 语言中,
char
类型在大多数系统上占用 1 个字节(8 位)存储空间。对于有符号char
类型,使用二进制补码表示数值。 - 在补码表示法中,最高位(最左边的位)被用作符号位,0 表示正数,1 表示负数。
- 正数的范围是从 0(二进制为 00000000)到 127(二进制为 01111111)。
- 对于负数,要得到其补码表示,需先取绝对值的二进制表示,然后按位取反,最后加 1。例如,- 1 的绝对值是 1,二进制为 00000001,按位取反得到 11111110,再加 1 得到 11111111,所以 - 1 在有符号
char
中的二进制表示为 11111111。这样,负数的范围是从 - 128(二进制为 10000000)到 - 1(二进制为 11111111)。 - 综上,有符号
char
类型的取值范围是 - 128 到 127。
2. 无符号char
类型
- 当
char
类型被声明为无符号类型时,所有的 8 位都用来表示数值。 - 最小的值是 0(二进制为 00000000)。
- 最大的值是 255(二进制为 11111111)。
- 所以无符号
char
类型的取值范围是 0 到 255。
- -129会发生进位。
练习
int main()
{
int i= -20;
unsigned int j = 10;
printf("%d\n", i+j);
return 0;
}
//-20 的原反补:
//10000000000000000000000000010100
//11111111111111111111111111101011
//11111111111111111111111111101100
//10的原反补:
//00000000000000000000000000001010
//补码相加:
//11111111111111111111111111101100
//00000000000000000000000000001010
//11111111111111111111111111110110 - 补码
//求出原码:
//11111111111111111111111111110101
//10000000000000000000000000001010 -> -10
unsigned int i;
for(i = 9; i >= 0; i--)
{
printf("%u\n",i);
}
//当 i= 0 的时候,-1的补码放在内存中会当成无符号数的补码运算,所以i恒大于等于0
int main()
{
char a[1000];
int i;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a));
return 0;
}
//当i = -127 的时候,-1就是 -128 了
//i = 128 -128-1 就是 127了。
//参考那个取值范围图片
//1- 127 127个数
//-1 - -128 128个数
//所以长度应该是 255
unsigned char i = 0;
int main()
{
for(i = 0;i<=255;i++)
{
printf("hello world\n");
}
return 0;
}
//i= 255 的时候,+1 产生个进位 1 又变成了0 了 恒小于等于 255
//所以死循环打印。
浮点数据的存储
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
return 0;
}
- 根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
- (-1)^S * M * 2^E
- (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
- M表示有效数字,大于等于1,小于2。
- 2^E表示指数位。
- 举例来说: 十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。 那么,按照上面V的格式,可以得出s=0,
M=1.01,E=2. - 十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,s=1,M=1.01,E=2。
- IEEE 754规定: 对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
- 第一个比特位 是->符号位S
- 接着8个比特位 是用来表示->指数位E
- 剩下的23个比特位 是用来表示->有效数字M。
- 对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
- EEE 754对有效数字M和指数E,还有一些特别规定。 前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
- IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。
- 比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。
- 以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
- 对于指数E
- 首先,E为一个无符号整数(unsigned int) 这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的 取值范围为0~2047。
但是,我们知道,科学计数法中的E是可以出现负数的,、- 所以IEEE 754规定,存入内存时E的真 实值必须再加上一个中间数,
对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。
比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
- E不为全0或全1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前 加上第一位的1。
比如: 0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位, 则为1.0*2^(-1),
其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位 00000000000000000000000
则其二进制表示形式为:0 01111110 00000000000000000000000
- E为全0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,
有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
- E为全1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)。
例题详解
先写出9 的补码
%f 打印 就会当作这存的是个浮点数,按浮点数的方式解析:
%d 打印 会当作这是个有符号的整型来看待: