char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整形
float //单精度浮点数
double //双精度浮点数
long
类型所占字节大小,一般和指针变量所占空间大小一致(即对应不同位的机器,所占空间大小不一致)
char
unsigned char
signed char
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long [int]
signed long [int]
float
double
数组类型 // int [10] int [8]
结构体类型 struct
枚举类型 enum
联合类型 union
int *pi;
char *pc;
float *pf;
void *pv;
void
表示空类型(无类型)
主要用于函数的返回类型,函数的参数,指针类型
//函数的返回类型
void print(int num);
{
printf("数字是%d", num);
}
//函数的参数
void greet(void)
{
printf("Hello, World!\n");
}
//指针类型
void *pv = NULL; //定义void指针类型,方便对其进行强制类型转换
一个变量的创建是要在内存中开辟空间的.空间的大小是根据不同的类型而决定的.
short
,int
等等)是怎么在内存中存储的呢?比如:
int a = 20;
int b = -10;
这里的变量a
和变量b
都是占据4
个字节的空间的,具体在内存中是如何存储的呢?
首先先要了解大小端模式的概念
这个对象的地址是什么?
我们在存储器如何对这些字节排序?
大端(存储)模式:数据的低位保存到内存的高地址上,数据的高位保存到内存的低地址上.
小端(存储)模式:数据的低位保存到内存的低地址上,数据的高位保存到内存的高地址上.
假设变量x
类型是int
,位于地址0x100
处,有一个十六进制值为0x01234567
.地址范围0x100 - 0x103
的字节顺序依赖于机器的类型:
注意:在字0x01234567
中,高位字节是0x01
,而低位字节是0x67
.
#include
typedef unsigned char* byte_pointer;
void show_bytes(byte_pointer start, int len)
{
int i;
for (i = 0; i < len; i++)
{
printf("%.2x ", start[i]);
}
printf("\n");
}
void show_int(int x)
{
show_bytes((byte_pointer)&x, sizeof(int));
}
void show_char(char c)
{
show_bytes((byte_pointer)&c, sizeof(char));
}
void show_float(float f)
{
show_bytes((byte_pointer)&f, sizeof(float));
}
int main(void)
{
char a = 0x98;
int b = 0x01234567;
float c = 1.12;
show_char(a);
show_int(b);
show_float(c);
return 0;
}
这里发现,我的机器是小端存储模式,C语言是将该变量的低地址作为变量的首地址,低位0x67
存放在了低地址上,即小端存储模式.
4. 同样还可以写出更多类型的打印函数,这里就写了三个类型.
#include
int check_sys()
{
short a = 0x0011;
return (*(unsigned char*)&a);
}
int main(void)
{
int ret = check_sys();
if (ret)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
check_sys()
直接进行判断,定义了一个占据两个字节的变量a
,存储数据为0x0011
(*(unsigned char*)&a)
先将a
对应存储的内容看成是一串字节串,随后将首地址上的一个字节元素进行返回.0x00
的则为大端,返回低位0x11
的则为小端知道了类型在机器中排列的字节顺序,那么如何表示正负呢,下面就是原码,反码,补码相关的知识.
绝大多数计算机存储整数,都是以补码形式存储在内存空间的.补码是有符号位的.
正整数的原,反,补码都相同.
负整数的原,反,补码各不相同.
原码
直接将数值按照正负数的形式翻译成二进制就可以得到原码
反码
原码的符号位不变,其他位按位取反就可以得到反码
补码
反码+1就得到补码
为什么计算机会使用补码呢?
在计算机系统中,数值一律用补码来表示和存储.原因在于,使用补码,可以将符号位和数值域统一处理.
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程和是相同的,不需要额外的硬件电路(符号位不变,数值位取反加1).
例如:
#include
int main(void)
{
int a = 10;
//补码: 0000 0000 0000 0000 0000 0000 0000 1010
// 0x0000000a
int b = -10;
//原码: 1000 0000 0000 0000 0000 0000 0000 1010
//补码: 1111 1111 1111 1111 1111 1111 1111 0110
// 0xfffffff6
int c = a + b;
printf("%d\n", c);
return 0;
}
1.
//输出什么?
#include
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;
}
char
类型在C语言中默认是signed
,有符号的.
类型提升,将符号位扩展至对应类型长度(例如:(char)0xf1
-> (short)0xfff1
),原数值大小不变
所谓的转换,转换的是真值的值,在计算机内部,二进制是不变的.
a
大小为-1,b
大小为-1,c
大小为-1 + 2^8 = 255
程序运行结果:
2.
//输出什么?
#include
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}
char
类型占据内存1
个字节大小的空间,可存储数字范围为-128 - 127
%u
代表以unsigned int
打印该数字a
在内存中为0x10
,整型提升后为0xffffff10
,该数表示无符号数则为2^32 - 127
即4294967168
3.
//输出什么?
#include
int main()
{
char a = 128;
printf("%u\n",a);
return 0;
}
每个类型所能存储的数据范围可以认为是一个圈,如超出了存储范围,将该值与该类型所能存储最值的差的补码形式加上该最值.即该变量在内存中真实存储的值.
128
超出了-128 - 127
的范围,他比127
大1
,0x7f + 0x01 = 0x10
0x10
整型提升为0xffffff10
,该数表示无符号数则为2^32 - 127
即4294967168
4.
//输出什么?
#include
int main(void)
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
return 0;
}
有符号类型和无符号类型进行计算,隐式转换将有符号类型转换为无符号类型
无符号数转换为二进制补码(有符号数),若该数二进制位有w
位,若 无符号数大小 =< 2^(w-1)
,则大小不变;若无符号数大小 < 2^(w-1)
,则将改值减去2^w
计算结果为-20 + 2^32 + 10 = 2^32 - 10
,此值为无符号数的值.最后再将无符号数转换为有符号数的值2^32 - 10 - 2^32 = -10
程序运行结果为:
当然也可以将两数都先转换为补码形式,再进行计算,那样还是没有这样直接.
5.
//输出什么?
unsigned int i;
for(i = 9; i >= 0; i--)
{
printf("%u\n",i);
}
此程序为死循环,先打印9 8 7 6 5 4 3 2 1 0
,随后打印-1 + 2 ^ 32
即4294967295
,继续不断减小,死循环
6.
//输出什么?
int main()
{
char a[1000];
int i;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a));
return 0;
}
strlen()
从该地址向后遍历,每次增加一个元素的所占的内存空间,同时计数加1
,直至该元素为0
,返回无符号整数,返回值为计数的结果.
数组每个元素都是char
类型的,即每个元素所能存储范围为-128 - 127
可得结果255
程序运行结果为:
7.
//输出什么?
#include
unsigned char i = 0;
int main()
{
for(i = 0;i<=255;i++)
{
printf("hello world\n");
}
return 0;
}
常见的浮点数:
3.14159
1E10
浮点数家族包括:float
,double
,long double
类型
浮点数表示的范围:中定义
根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制数浮点数V
可以表示成下面的形式:V = (-1)^S * M * 2^E
举例来说:
IEEE754规定浮点数是由三个部分组成的,分别由上面求出的S,M,E
经过转换得到:
尾数的符号位S
:
0
,负数为1
.有偏移的指数E
:
E
为一个unsigned int
,这意味着,如果E
是8
位,取值范围是0-255
;如果E
是11
位,取值范围是0-2047
.E + Bias
,这里的Bias
就是偏移量.对于8
位的E
,Bias = 127 = 0111 1111
;对于11
位的E
,Bias = 1023 = 011 1111 1111
2^10
中E
为10
,要将其保存成32
位浮点数时,必须保存成10 + 127 = 137
即10001001
.规格化的尾数M
:
1 =< M < 2
,M
可以写成1.XXXXXX
的形式,其中XXXXXX
是小数部分.M
保存为有效数字位的时候,默认这个数第一位总是1
,因此可以被舍去,只保存小数部分XXXXXX
.1.01
,只保存01
,等到读取的时候,再将第一位的1
加上去.这样可以节省一位有效数字.IEEE 754规定
对于32位的浮点数,最高的1位是符号位
S
,接着的8位是指数E
,剩下的23位为有效数字M
.
对于64位的浮点数,最高的1位是符号位
S
,接着的11位是指数E
,剩下的52位为有效数字M
.
E
从内存中取出还可以分成三种情况:这时,浮点数就采用下面的规则表示,即指数
E
的偏移值减去偏移量127
或者1023
,得到真实值,再将有效数字M
加上第一位的1.
例如:
0.5
整数部分: 0 = 0
小数部分: 0.5 = 1
0.5 = 0.1 = 1 * 2^(-1)
sign:0
biased exponent: -1 + 127 = 126 = 0111 1110
normalised mantisa: 0
单精度浮点数表示为:0 0111110 00000000000000000000000
E全为0
这时,浮点数的指数
E
等于1-127
(或者1-1023
)即为真实值
有效数字M
不再加上第一位的1
,而是还原成0.XXXXXX
的小数.这样做是为了表示+0
和-0
,以及接近于0
的很小的数字.
E全为1
这时,如果有效数字
M
全为0
,表示正负无穷大,正负取决于符号为S
//打印结果是什么?
int main(void)
{
int n = 9;
float *pFloat = (float *)&n; //以单精度浮点数格式读取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;
}
9
以int
类型存储到n
所在空间中,即为0x00000009
float
同样是32位,所以*pFloat
存放的也是0x00000009
printf("n的值为:%d\n",n);
即将0x00000009
按int
类型打印,结果为9
printf("*pFloat的值为:%f\n",*pFloat);
即将0x00000009
按单精度浮点数打印,0 00000000 00000000000000000001001
,表示一个接近0的浮点数,V = (-1)^0 * 0.00000000000000000001001 * 2^(-126) = 1.001 * 2^(-146)
,%f
只显示6
位小数,结果为0.000000
9.0
以float
存储到指针pFloat
所指空间中9.0
整数部分: 9 = 1001
小数部分: 0 = 0
9.0 = 1001.0 = 1.001 * 2^3
sign: 0
biased exponent: 3 + 127 = 130 = 1000 0010
normalised mantisa: 001
单精度浮点数: 0 10000010 00100000000000000000000
所以该空间中存放了0100 0001 0001 0000 0000 0000 0000 0000
即0x61100000
printf("num的值为:%d\n",n);
即将0x61100000
按int
类型打印,结果为2^30 + 2^24 + 2^20 = 1091567616
,该数在int
类型范围内,打印1091567616
printf("*pFloat的值为:%f\n",*pFloat);
即将0x61100000
按float
类型打印,保留小数点后6位,打印9.000000
本章完.