说到C中的
数据类型
,大家都知道,int,short,char等等。
数据类型可以理解为固定内存大小的别名。数据类型是创建变量的模子。
就像做蛋糕一样,我们都会用一个模子去压蛋糕来让它得到期望的样子。
那什么是变量?
![数据类型及auto、static、register详解 数据类型及auto、static、register详解](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
上图是内存图:
当我们讨论某块内存区域存储的值时,我们不可能总是说2000到2002地址上存储的值是多少多少吧,鉴于如此麻烦的表述,于是我们为了方便从而给2000到2002之间的存储区域取了一个名,这个名就是所谓的变量名,例如:变量i中存放3要比2000到2002这个内存地址中存放的是3要来的简易直接。
所以
变量的本质如下:
一、
变量是一段实际连续存储空间的别名。
二、 程序中通过变量来申请并命名存储空间。
三、 通过变量的名字可以使用存储空间。
二、 程序中通过变量来申请并命名存储空间。
三、 通过变量的名字可以使用存储空间。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include int { } |
上述代码其实非常简单,让我们剖析一下第九行代码实际的机制是什么,我们上面讲到数据类型是固定内存大小的别名,那么char数据类型就是一个字节内存大小的别名,所以第九行输出的第一个结果为一。对于第九行第二个输出而言,可以认为是char这个数据类型模子在内存中“挤压”出来的大小,因为char的模子占内存的一个字节,所以c变量受这个模子在内存的“挤压”后得到的字节大小实际和这个模子是相同的,所以也占一个字节。
我认为按照上述的理解可以让我们更客观的去感受C的魅力,
数据类型就是内存大小的别名,是模子。变量就是某连续存储空间的别名。
C语言的关键字有很多,关键字分类如下:
32个关键字 每个都有不同的意义,大体上根据其意义可以分为一下几类(一下数据类型在其他分类中有交集):
1、非常见:auto、register、volatile、goto
2、存储相关:const、extern、register、volatile、static、auto、signed、unsigned
3、数据类型:char、short、int、float、long、double、struct、union、enum、void
4、逻辑控制:if、else、for、while、do、break、continue、return、default、switch、case、goto
5、特殊用途:sizeof、typedef
32个关键字 每个都有不同的意义,大体上根据其意义可以分为一下几类(一下数据类型在其他分类中有交集):
1、非常见:auto、register、volatile、goto
2、存储相关:const、extern、register、volatile、static、auto、signed、unsigned
3、数据类型:char、short、int、float、long、double、struct、union、enum、void
4、逻辑控制:if、else、for、while、do、break、continue、return、default、switch、case、goto
5、特殊用途:sizeof、typedef
了解完数据类型和变量,那让我们对auto、static、register这三个
“属性”关键字
进行分析。
auto关键字:
auto可以被称为“隐形刺客”(DotA中英雄ID),其实在C程序中我们时时刻刻都能够感受到auto关键字,当我们在一个函数体中进行定义
局部变量
的时候,其实隐藏着auto关键字,
auto是C语言中局部变量的默认属性
。例如下图定义a的代码。实际上隐藏着关键字auto,我们可以尽量不去写它,省些力气,但是当被问道这个关键字是干什么的,我们还得能答的上来的,auto这个关键字出来意味着当前变量的作用域为当前函数或当前代码块中的局部变量,意味着当前变量会在内存栈上进行操作。
1
2 3 4 5 6 7 |
#include int { } |
static关键字:
static的意义如下:
static修饰的局部变量存储在程序的静态区。
static存在的另一个意义是文件作用域标示符。
static修饰的局部变量存储在程序的静态区。
static存在的另一个意义是文件作用域标示符。
让我们看下图代码对static进行进一步分析:
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 |
#include void { } void { } int { } |
程序运行结果如下:
1 2 3 4 5 1 2 3 4 5 1 1 1 1 1
这段代码中的
func和func2两个函数的对比说明当函数在栈中存储时,局部变量会随着函数的结束而消失,但是当变量存储在静态区的时候(即静态变量),变量不会随着函数的结束而消失,当然我们也不局限与只用static对变量进行修饰,当然也可以用static去定义函数来实现其文件作用域标示符的功能。(下面我们会细细讨论)。
对比
func和main函数可以看出我们定义了两个静态变量都命名为a,有人可能会提出疑问,他在想如果函数的开始和消亡不会清除静态区的值,那么如果定义了重复的变量名(都为staticinta),难道后定义的静态变量a会覆盖掉先前定义的静态变量a吗?当然不会,正如我们所知道的,如果在主函数和子函数中分别在栈区定义同一变量名的时候不会产生覆盖,那是因为变量作用的代码块不同。静态变量的定义也是一样的,他们作用的代码块不同,一个在main函数中定义,另一个在func中定义,所以不会产生覆盖(由运行结果也可看出)。当然用栈去类比也有不妥的地方,因为
假设一个函数func3中在栈中定义一个变量a= 1,另一个函数func4在静态区定义一个变量a =1,如果两个函数都被调用两次,那么func3再次调用时定义的a与第一次调用时定义的a毫无关系,而调用了两次func4函数时,定义的静态变量a实际是第一次调用的值。
例如上图代码中func函数的重复调用。
来看下面的代码:
1
2 3 4 5 6 7 8 9 |
#include int int { } |
a是
全局变量,b是栈变量,c是堆变量。
static对全局变量的修饰,可以认为是限制了只能是本文件引用此变量。有的程序是由好多.c文件构成。彼此可以互相引用变量,但加入static修饰之后,只能被本文件中函数引用此变量。
static对栈变量的修饰,可以认为栈变量的生命周期延长到程序执行结束时。一般来说,栈变量的生命周期由OS管理,在退栈的过程中,栈变量的生命也就结束了。但加入static修饰之后,变量已经 不在存储在栈中,而是和全局变量一起存储
register关键字:
register关键字定义变量时例如register inta;表达的意思是将变量a放在寄存器中,这样对该变量进行操作时速度会如闪电般快。被register修饰的变量称为
寄存器变量,该变量的访问速度会达到最快,比如一个程序逻辑中有一个很大的循环,循环中有几个变量要频繁进行操作,这些变量可以声明为register类型。寄存器变量:寄存器变量是指一个变量直接引用寄存器,也就是对变量名的操作的结果是直接对寄存器进行访问。寄存器是CPU的亲信,CPU操作的每个操作数和操作结果都由寄存器来暂时保存,最后才写入到内存或从内存中读出,也就是说,变量的值通常保存在内存中,CPU对变量进行读取先是将变量的值从内存中读取到寄存器中,然后进行运算,运算完将结果写回到内存中。为什么要这么设计,而不直接对变量的值从内存中进行运算,而要再借助于寄存器?这是由于考虑到性能的问题才这么设计的。
当然编译器只是抱着试一试的态度去执行register int a;这条语句,不一定能够成功定义为寄存器变量。
使用寄存器变量应注意一下几点:
待声明为寄存器变量类型应该是CPU寄存器所能接收的类型,意味着寄存器变量是单个变量,变量长度应该小于等于寄存器长度,比如寄存器最大能容纳四个字节,而你却定义registerlong a;这样就无法定义成功。
不能对寄存器变量使用取地址符,因为该变量存储在寄存器中而不是内存中,所以不存在内存地址(切记
寄存器变量不能声明为全局变量)。
尽量在大量频繁的操作时使用寄存器变量,且声明的变量个数应该尽力的少。
-------------------------------------------------------------------------------------------
参考资料: