外观
01.变量的本质
约 2584 字大约 9 分钟
变量C个人随笔
2020-10-04
一、基本数据类型
- 变量是一段实际连续存储空间的别名
- 程序中通过变量来申请并命名存储空间
- 通过变量的名字可以使用存储空间
- 数据类型代表需要占用的内存大小
- 变量的本质是一段内存的别名
- 变量隶属于某一种数据类型
- 变量所在的内存大小取决其所属的数据类型

二、有符号与无符号
1、数据类型的最高位用于标识数据的符号
- 最高位为1,表明这个数为负数
- 最高位为0,表明这个数为正数
2、如何知道一个数是不是正数?
#include <stdio.h>
int main(void)
{
int sign = 0;
char i = -5;
short j = 5;
int k = -1;
printf("%d\r\n", (i & 0x80)!=0);
printf("%d\r\n", (j & 0x800)!=0);
printf("%d\r\n", (k & 0x80000000)!=0);
}利用操作符&(全为1才是1),将数据的最高位与1进行&运算,若为1,则返回1,说明原数为负数。

3、在计算机内部用补码表示有符号数
- 正数的补码为正数本身
- 负数的补码为负数的绝对值各位取反后加1
4、在计算机内部用原码表示无符号数
- 无符号数默认为正数
- 无符号数没有符号位
5、对于固定长度的无符号数
- MAX_VALUE+1 →MIN_VALUE
- MIN_VALUE-1 →MAX_VALUE
6、当有符号数撞见无符号数
#include <stdio.h>
int main()
{
unsigned int i = 5;
int j = -10;
if(i +j > 0 )
printf("i + j > 0\n");
else
printf("i + j <= 0\n");
return 0;
}
运算中,有符号数会变成一个正数。
7、错误地使用了unsigned
#include <stdio.h>
int main()
{
unsigned int i = 0;
for (i = 9; i >= 0; i--)
{
printf("i = %u\n", i);
}
return 0;
}
8、小结
(1)有符号数用补码表示
- 正数的符号位为0
- 负数的符号位为1
(2)无符号数用原码表示
- 无符号数没有符号位
- 无符号数只用于表示正数
(3)unsigned只能修饰整数类型的变量
- 当无符号数与有符号数混合计算时,会将有符号数转换为无符号数后再进行计算,结果为无符号数。
三、浮点数的秘密
1、内存中的浮点数
(1)浮点数在内存的存储方式为∶符号位,指数,尾数

float与double类型的数据在计算机内部的表示法是相同的,但由于所占存储空间的不同,其分别能够表示的数值范围和精度不同。
(2)浮点数的转换
- 将浮点数转换成二进制
- 用科学计数法表示二进制浮点数
- 计算指数偏移后的值
注意:
计算指数时需要加上偏移量,而偏移量的值与类型有关。
示例:实数8.25在内存中的float表示
8.25的二进制表示:1000.01 --> 1.00001*(2^3)
符号位:0
指数:127+3 --> 130 --> 10000010
小数:00001
内存中8.25的float表示:
0 10000010 00001000000000000000000 --> 0x41040000
验证:
#include <stdio.h>
int main(void)
{
float f = 8.25;
unsigned int* p = (unsigned int*)&f;
printf("0x%X\r\n", *p);
return 0;
}
2、浮点类型的秘密
- float能表示的具体数字的个数与int相同
- float可表示的数字之间不是连续的,存在间隙
- float只是一种近似的表示法,不能作为精确数使用
- 由于内存表示法相对复杂,float的运算速度比int慢得多(浮点计算太慢)
注意∶
double与float具有相同的内存表示法,因此double也是不精确的。由于double占用的内存较多,所能表示的精度比float高。
验证:
#include <stdio.h>
int main(void)
{
float f = 3.1415f;
float f1 = 123456789;
printf("%0.10f\r\n",f);
printf("%0.10f\r\n", f1);
return 0;
}
3、总结
- 浮点类型与整数类型的内存表示法不同
- 浮点类型的内存表示更复杂
- 浮点类型可表示的范围更大
- 浮点类型是一种不精确的类型
- 浮点类型的运算速度较慢
四、类型之间的转换
1、C语言中的数据类型可以进行转换
一强制类型转换
一隐式类型转换

2、强制类型转换的语法
强制类型转换的语法
- (Type)var_name
- (Type)value
强制类型转换的结果
一目标类型能够容纳目标值︰结果不变
一目标类型不能容纳目标值 :结果将产生截断
注意:
不是所有的强制类型转换都能成功,当不能进行强制类型转换时,编译器将产生错误信息。
示例:
#include <stdio.h>
int main()
{
short s = 0x1122;
char c = (char)s; // 0x22
int i = (int)s; // 0x00001122
int j = (int)3.1415; // 3
printf("s = %x\n", s);
printf("c = %x\n", c);
printf("i = %x\n", i);
printf("j = %x\n", j);
return 0;
}
3、隐式类型转换
一 编译器主动进行的类型转换
注意∶
低类型到高类型的隐式类型转换是安全的,不会产生截断
高类型到低类型的隐式类型转换是不安全的,导致不正确的结果
隐式类型转换的发生点
一算术运算式中,低类型转换为高类型。(char+int,首先会将char变成int类型,再计算)
一赋值表达式中,表达式的值转换为左边变量的类型。(char = int ,首先将int变为char在赋值)
一函数调用时,实参转换为形参的类型。(在调用的时候就已经转换了)

一函数返回值,return表达式转换为返回值类型。
安全的隐式类型转换

示例:
#include <stdio.h>
int main()
{
char c = 'a';
int i = c; // safe
unsigned int j = 0x11223344;
short s = j; // unsafe
printf("c = %c\n", c);
printf("i = %d\n", i);
printf("j = %x\n", j);
printf("s = %x\n", s);
printf("sizeof(c + s) = %d\n", sizeof(c + s));
return 0;
}
4、小结
(1)强制类型转换由程序员负责完成
– 转换可能产生截断
– 转换不区分类型的高低
– 转换不成功时,编译器给出错误信息
(2)隐式类型转换由编译器自动完成
– 低类型向高类型的转换是安全的
– 高类型向低类型的转换是不安全的
标准C编译器的类型检查是比较宽松的,因此隐式类型转换可能带来意外的错误。
五、变量属性
“属性”关键字指明变量的特有意义
1、auto关键字
- auto即C语言中局部变量的默认属性;
- auto表明将被修饰的变量存储于栈上;
- 编译器默认所有的局部变量都是auto的。
void test(void)
{
int i; // 局部变量默认属性为 auto
auto int j; // 显示声明 auto 属性
}2、register关键字
- register关键字指明将局部变量存储于寄存器中;
- register只是请求寄存器变量,但不一定请求成功(同时寄存器也是有限的);
- register变量的必须是CPU寄存器可以接受的值;
- 不能用&运算符获取register变量的地址;
- C语言的规定:register不能用于全局变量;
- 优点:寄存器访问速度非常的快。
#include <stdio.h>
register int g_v; // error C语言的规定:register不能用于全局变量。
int main()
{
register char c; // 能不能成功,无法确定
printf("0x%08X\n", &c); // 不能用&运算符获取register变量的地址
return 0;
}3、static关键字
(1)static关键字指明变量的“静态”属性。
- static修饰的局部变量存储在程序静态区。
(2)static关键同时具有“作用域限定符”的意义。
- static修饰的全局变量作用域只是声明的文件中。
- static修饰的函数作用域只是声明的文件中。
#include <stdio.h>
int g_v; // 全局变量,程序的任意地方均能访问
static int g_v; // 静态全局变量,只有当前文件中可访问
int main()
{
int c; // 局部变量,在栈上分配空间
static int svar; // 静态局部变量,在静态数据区分配空间
return 0;
}4、实例分析:auto,register, static对比分析
#include <stdio.h>
int ALL_1 ()
{
int r = 0;
r++;
return r;
}
int ADD()
{
static int r = 0;
r++;
return r;
}
int main()
{
auto int i = 0; // 显示声明 auto 属性,i 为栈变量.等同于 int i = 0;
static int k = 0; // 局部变量 k 的存储区位于静态区,作用域位于 main 中
register int j = 0; // 向编译器申请将 j 存储于寄存器中
printf("%p\n", &i);
printf("%p\n", &k);
// printf("%p\n", &j); // error 不能用&运算符获取register变量的地址
for (i = 0; i < 5; i++)
{
printf("%d\n", ALL_1());
}
for (i = 0; i < 5; i++)
{
printf("%d\n", ADD());
}
return 0;
}
5、extern关键字
(1)extern用于声明“外部”定义的变量和函数
- extern变量在文件的其它地方分配空间;
- extern函数在文件的其它地方定义;
(2)extern用于“告诉”编译器用C方式编译
C++编译器和一些变种C编译器默认会按“自己”的方式编译函数和变量,通过extern关键可以命令编译器“以标准c方式进行编译”。
extern "c"
{
int f(int a,int b)
{
return a + b;
}
}6、示例:static和extern
(1)extern
报错:g_i未定义 编译通过

(2)static 和 extern 互斥关系
7、小结
- auto变量存储在程序的栈中,默认属性
- static变量存储在程序静态区中
- register变量请求存储于CPU寄存器中
- extern变量在文件的其它地方分配空间
- extern能够指示编译器按照标准C方式编译程序
