1 变量
c++ 变量是一个可容纳整型、浮点型、自定义类型等各种类型的对象。变量有自己的名称,并且也有特定的类型(例如 int 或 string ),类型决定我们将什么赋给变量(例如,123456 可以赋给 int 型,“hello” 可以赋给 string 型)。
1.1 变量的定义与初始化
如下是一个整型变量的定义:
int val;
其中,int 是个数据类型,它表示整型,而 val 是变量名,这里仅仅是定义了一个变量名,并没有为这个变量赋值。假如要为其赋值的话,可以写为:
int val;
val = 0;
也可以将定义变量和赋值合并为一句:
int val = 0;
这样既定义了变量又初始化了 val 的值。注意,这里叫做对 val 的初始化而不能叫做赋值,赋值操作是在定义变量 val 之后进行的。而初始化则是与定义 val 同步进行的。
在 C++ 中,如果定义变量没有初始化,那么变量有可能会被赋值也有可能不会赋值。如果是定义的全局变量或者静态变量,未初始化的话就是 0 ,如果是局部变量,那就是一个随机值。因此,强烈建议在定义变量的同时,给变量初始化。
1.2 变量在内存中的存储
1.2.1 变量的分类
(1)根据变量的位置可以分为全局变量和局部变量
(2)根据变量的静态属性可以分为静态变量和非静态变量。
(3)根据变量的const属性可以分为const变量和非const变量。
如下为具体的例子:
int g_val = 0; // 全局变量
static int s_val = 0; // 静态全局变量
int main()
{
int val1 = 0; // 局部变量
static int val2 = 0; // 静态局部变量
const int val3 = 0; // const变量
return 0;
}
注意:上面程序中的静态局部变量 val2 ,虽然定义在函数内部,但是其实际上还是保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
静态局部变量与静态全局变量的区别在于:静态局部变量始终驻留在全局数据区,直到程序运行结束。但其作用域仍为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的。
静态局部变量的用途有许多:可以使用它确定某函数是否被调用过。使用它保留多次调用的值。
1.2.2 变量名
首先,变量可以分为变量名以及其占据的数据空间,比如针对如下变量:
int val = 0;
int main()
{
......
}
其中的 val 是变量名,该变量是一个全局变量,其数据类型为 int ,占据 4 个字节的内存空间。
对于变量名而言,C++ 实际上对其是不作存储的,在汇编以后不会出现变量名,变量名作用只是用于方便编译器成汇编代码,是给编译器看的,同时也是方便人编写与阅读代码。
还是以上面 val 变量的代码为例,编译器编译它时,产生类似 mov [0x002210DA],0 的指令,即:把 0 放在该内存地址的空间上。其中并没有出现 val , val 只是编译时供编译器识别的名字,是一个高级语言抽象出来的概念,在真实执行的程序中并不存在 val 。至于 val 的具体地址是由连接器( linker )决定的。
因此,变量在内存中的存储只与变量的分类相关。
1.2.2 程序内存分布
(1)栈区 stack :由编译器自动分配释放,存放函数的参数值,局部变量的值等。这个栈的操作方式类似于数据结构中的栈。
(2)堆区 heap :一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收,注意它与数据结构中的堆是两回事,分配方式类似于链表。
(3)全局区(静态区)static : 全局变量和静态变量的存储是放在一块的。初始化的全局变量和静态变量在一块区域,未初始化的全局变量和静态变量又放在相邻的另一块区域中。程序结束后由系统释放。
(4)BSS段 :通常是指用来存放程序中未初始化的全局变量、静态变量(全局变量未初始化时默认为 0 )的一块内存区域
(5)文字常量区: 常量字符串放在这里。程序结束后由系统释放。
(6)程序代码区 : 存放函数体的二进制代码。
1.2.3 栈区(stack)和堆区(heap)的区别
(1)分配方式
栈是由程序自动创建和释放的,通常用于存储函数调用时的临时变量、函数的返回地址等信息。而堆则是由程序员手动申请和释放的,通常用于存储程序中需要动态分配的内存(如动态数组、对象等)。
(2)管理方式
栈的内存分配是按照“后进先出”的原则进行的,即最后一个进入栈的变量最先被释放。栈中的内存管理是由系统自动完成的,而堆的内存管理则需要程序员自行负责,使用完毕后必须手动释放,否则会导致内存泄漏或其他问题。
(3)大小限制
栈是向低地址扩展的数据结构,是一块连续的内存的区域。栈的容量较小,一般只有几百KB到几MB的空间,具体容量由操作系统和编译器决定。
堆是向高地址扩展的数据结构,是不连续的内存区域(不连续的原因是系统用链表来存储的空闲内存地址)。堆的大小受限于计算机系统中有效的虚拟内存。
(4)访问速度
栈的内存分配是系统自动完成的,所以访问速度相对堆更快。栈中的数据直接存放在系统内存中,而访问堆中的数据需要通过指针进行间接访问,会造成一定的时间损耗。
1.2.4 变量按分类占用内存
常规的变量(非全局,非静态)保存在栈上。
动态数据等显示分配的内存在堆上。
全局变量和静态变量保存在数据段(全局区)
const全局变量存放于只读数据段,在第一次使用时为其分配内存。
1.3 变量作用域
变量的作用域可以分为以下几种:
(1)全局作用域
在所有函数体的外部定义的变量,程序的所在部分(甚至其它文件中的代码)都可以使用。全局变量不受作用域的影响(也就是说,全局变量的生命期一直到程序的结束)。如果在一个文件中使用extern关键字来声明另一个文件中存在的全局变量,那么这个文件可以使用这个数据。
(2)局部作用域
一般情况下在代码块{}内部定义的变量都属于局部作用域的变量,它有如下特点:
- 在一个函数内定义,只在函数范围内有效。
- 在复合语句中定义,只在复合语句中有效。
- 随着函数调用的结束或复合语句的结束局部变量的声明声明周期也结束。
- 如果没有赋初值,内容为随机。
注意:如果在内部作用域中声明的变量与外部作用域中的变量同名,则内部作用域中的变量将覆盖外部作用域中的变量。
(3)类作用域
在类内部声明的变量具有类作用域,它们可以被类的所有成员函数访问。类作用域变量的生命周期与类的生命周期相同。
1.4 变量命名规则
1.4.1 常见的变量命名方法
(1)匈牙利命名法
匈牙利命名法由 Microsoft 公司推出,该命名规则的主要思想是在变量和函数名中加入前缀以增进人们对程序的理解。例如所有的字符变量均以 ch 为前缀,所有的 int 类型变量均以 i 为前缀,若是指针变量则追加前缀 p。如果一个变量由 ppch 开头,则表明它是指向字符指针的指针。
(2)驼峰命名法
分为小驼峰法以及大驼峰法。
小驼峰法:除第一个单词之外,其他单词首字母大写(常用于:变量)。例如:
int booksCount;
大驼峰法:每一个单词的首字母都采用大写字母(常用于:类名,函数名,属性,命名空间)。例如:
class BookAttribute {}
1.4.2 变量命名共性规则
(1)变量名最好采用英文单词或其组合,便于记忆和阅读。不要使用汉语拼音来命名。
(2)命名规则尽量保持风格一致:例如当前代码已经使用驼峰命名法,则后续的开发也应当继续使用。
(3)程序中不要出现仅靠大小写区分的相似的命名。例如:
string Name;
string name;
(4)程序中不要出现标识符完全相同的局部变量和全局变量 ,尽管两者的作用域不同而不会发生语法错误,但会使人误解。
(5)尽量避免名字中出现数字编号,如 Value1,Value2 等,除非逻辑上的确需要编号。
2 常量
在C++中,常量(Constants)是指在程序运行期间其值不会改变的变量。常量的值在定义时就被确定,并且不能在程序运行过程中被修改。
2.1 define 定义
用#define定义的常量是在编译前进行文本替换,没有类型限制,可以用于定义任何类型的常量。例如:
#define MAX_VALUE 100
2.2 const 修饰符
使用 const 关键字可以创建命名常量,即具有特定名称的常量变量。const 修饰符用于表示变量的值是不可修改的。例如:
const int val1=1;
2.3 define 与 const 的区别
(1)处理阶段
const 常量是一个Run-Time的概念,他在程序中确确实实的存在可以被调用、传递。
define 常量是预处理器预处理阶段完成的,它的生命周期止于编译期:在实际程序中他只是一个常数、一个命令中的参数,没有实际的存在。
(2)定义不同
define 是宏定义,它最大的特点就是语义替换,它定义的常量值没有类型限定,不做运算,也不做类型检查,在宏出现的地方直接展开。例如:
#define VAL 1+1 //我们预想的 VAL 值是 2 ,我们这样使用 VAL
double a = VAL/2; //我们预想的a的值是1,可实际上 VAL 的值是 2
//实际上就是 a = 1+1/1 = 2
const 是关键字,其实就是一个只读变量,不能更改,它会在编译时检查数据类型。
(3)存储方式
const 常量存在于程序的数据段。
define 常量存在于程序的代码段。
const 定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态区),而#define定义的宏常量在内存中有若干个拷贝。
就空间效率而言:const 优于 define。
总结上述区别:一般推荐使用const关键字定义常量,因为它具有类型限制,更安全、更可读、更易于维护。
2.4 枚举常量
枚举常量是一组具有命名值的符号常量。它们用于定义一个新的数据类型,其中每个枚举常量都可以具有不同的整数值。枚举常量的语法如下:
enum EnumValue
{
EnumValue1,
EnumValue2,
EnumValue3,
};
在上面的代码中,我们使用 enum 关键字定义了一个新的枚举类型 EnumValue ,并列出了一些枚举常量 EnumValue1 、 EnumValue2 、EnumValue3 。在没有设值的情况下,第一个枚举常量的值为0,后续的枚举常量依次递增。
其中,枚举名可以根据需求省略,例如上面代码可以修改为:
enum
{
EnumValue1,
EnumValue2,
EnumValue3,
};
2.5 constexpr 关键字
constexpr 关键字用于声明可以在编译时计算的常量表达式。它在 C++11 中引入,用于声明可以在编译时求值的常量表达式。在 C++11 中引入,它可以应用于变量、函数和类的成员函数。使用 该关键字声明的常量表达式可以在编译时进行计算,而不是在运行时。这样可以提供更好的性能和编译时优化。同时,编译器还可以在编译时对 constexpr 表达式进行类型检查和错误检查。
constexpr int ADD(int a, int b) {
return a+b;
}
constexpr int value = ADD(1,2); // 使用常量表达式计算的常量,在编译时已经求得结果
如上代码所示:在编译阶段将 value 结果替换为计算结果(3)。这样可以避免在运行时进行重复计算,提高程序的执行效率。