程序开发时常使用多个文件。一般组织策略是,使用头文件定义数据类型,以及提供操纵这些数据类型的函数原型;将函数定义放在一个独立的源代码文件中;将主函数和使用操纵这些数据类型的函数放在其它的文件中。然而,将一个程序放在多个文件中会引出一些新的问题,即信息在多文件中是怎样共享的呢?这涉及到三个概念存储持续性、作用域和链接性。
存储持续性描述了名称在文件中持续的时间。
作用域描述了名称在文件中的可见范围。
链接性描述了名称如何在不同单元间共享。链接性为外部的名称可在文件间共享,链接性为内部的名称只能在所属的文件中共享。自动变量没有链接性,因为它们不能共享。
1 存储持续性、作用域和链接性
C++使用三种方案(在C++11中是四种)不同的方案来存储数据,这些方案的区别在于数据保留在内存中的时间。四种存储类型如下:
1、自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
2、静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序的整个运行过程中都存在。C++有三种存储持续性为静态的变量。
3、线程存储持续性(C++11):在多核处理器中,CPU可同时处理多个执行任务。因此,可将计算放在可并行处理的不同线程中。如果变量是使用关键字threa_local声明的,则其生命周期与所属的线程一样长。
4、动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态。
1.1自动存储持续性变量
在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。可以使用任何在声明时其值为已知的表达式来初始化自动变量。自动变量存储在栈中。自动变量的存储方式见下表:
表:自动变量的存储方式
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
自动
|
自动 |
代码块 |
无 | 在代码块中 或使用关键字auto,C++11之后使用register |
寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用关键字register |
#include <iostream>
using std::cout;
using std::endl;
void autoVariableTest(int x);
int main()
{
int test = 7;
cout << "test = " << test << " The address of main-test is "
<< &test << endl;
autoVariableTest(test);
cout << "test = " << test << " The address of main-test is "
<< &test << endl;
return 0;
}
void autoVariableTest(int x)
{
int test = x;
cout << "test = " << test << " The address of auto-f-test is "
<< &test << endl;
cout << "x = " << x << " The address of x is " << &x << endl;
{
int test = 6;
cout << "test = " << test << "The address of auto-f-block-test is "
<< &test <<endl;
}
test = 8;
cout << "test = " << test << " The address of auto-f-test is "
<< &test << endl;
}
1.2静态存储持续性变量
和C语言一样,C++为静态存储持续性变量提供了3种链接性:外部链接性,可在其它文件中访问;内部链接性,只能在当前文件中访问;无链接性,只能在当前函数或代码块中访问。这3中链接性都在整个程序执行期间存在,与自动变量相比,它们的持续时间更长。编译器一般分配固定的静态内存来存储所有的静态变量,如果没有显示初始化静态变量,编译器将它设置为0,但自己进行变量初始化是一个良好的编程习惯。静态变量的存储方式见下表:
表:静态变量存储方式
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字static |
静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数内,代码块中,使用关键字static |
静态,外部链接性 | 静态 | 文件 | 外部 | 不在任何函数内和代码块中
|
测试如下:
file1.cpp
#include <iostream>
using std::cout;
using std::endl;
double add(double x1, double x2);
double test_file1 = 0; //静态变量,外部链接性
double test_file2 = 0; //测试文件2中的内部链接性变量test_file2是否会改变此值
extern double test_file3; //测试与文件2中的外部链接性变量test_file3是否冲突,发现是有冲突的,需加extern
int main(void)
{
int x = 1;
int y = 2;
cout << "x = " << x << " y = " << y << endl;
cout << "test_file1 = " << test_file1 << endl;
cout << "test_file2 = " << test_file2 << endl;
cout << "test_file3 = " << test_file3 << endl;
test_file3 = add(x, y);
test_file3 += add(x, y);
test_file3 += add(x, y);
cout << "test_file3 = " << test_file3 << endl;
cout << "test_file1 = " << test_file1 << endl;
cout << "test_file2 = " << test_file2 << endl;
cout << "test_file3 = " << test_file3 << endl;
return 0;
}
file2.cpp
#include <iostream>
extern double test_file1; //test_file1来自于其他文件
static double test_file2; //静态变量,内部链接性
double test_file3 = 0; //静态变量,外部链接性
double add(double x1, double x2)
{
static int count = 0; //静态变量,无链接性
double y = x1 + x2;
test_file1 = y;
test_file2 = y;
test_file3 += y;
count++;
std::cout << "-------add------" << std::endl;
std::cout << "test_file1 = " << test_file1 << std::endl;
std::cout << "test_file2 = " << test_file2 << std::endl;
std::cout << "test_file3 = " << test_file3 << std::endl;
std::cout << "count = " << count << std::endl;
std::cout << "-------add------" << std::endl;
return y;
}
需要注意一下几点:
1、 在每个使用外部变量的文件中,都必须声明它;此外,需遵循单定义规则,即变量只能定义一次。
2、 如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义(单定义规则),但在使用该变量的其他文件中,都必须使用关键字extern声明它。
3、 C++提供了作用域解析符::,放在变量名称前面表示使用该变量的全局版本。
4、 初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化。
5、 const全局变量默认链接性为内部的。如果需要将const全局变量设置为外部链接性,则在每个文件中都必须在定义或声明之前加上extern,如 extern constint flag = 2;
1.3 语言链接性
链接程序要求每个不同的函数都有不同的符号名。在C语言中,一个名称只对应一个函数,如doubleadd(double, double)翻译为_add, 这种方法称为C语言链接性。而在C++中,由于重载等语言特性,一个名称可能对应多个函数,因此不需将这些函数翻译为不同的符号名称,所以C++编译器执行名称矫正或名称修饰,为重载函数生成不同的符号名称,如doubleadd(double, double)翻译为_add_d_d, double add(int,int)翻译为_add_i,这种方法称为C++语言链接性。可以使用C++标准制定的链接性说明符解决这一问题。
如:
extern “C” double add(int ,int); //按C原型查找
extern “C++” double add(int ,int); //按C++原型查找
extern double add(int ,int); //按C++原型查找
1.4 动态分配
1、在分配动态内存时,可以使用new运算符进行初始化,如:
int* pi = newint(6);
int* pi = newint[4] {2, 3, 4, 5}; //C++11
2、运算符new,new[], delete,delete[]调用一下形式的函数,并可被替换与重载。亦即自己可以对其进行定制。注意:delete只能用于释放指向常规new运算符分配的内存。
void* operator new(std::size_t);
void* operator new[](std::size_t);
void* operator delete(std::size_t);
void* operator delete[](std::size_t);
3、定位new运算符
定位new运算符可以在指定的位置上分配内存,使用定位new运算符需要包含头文件new,注意不能使用delete来释放定位new运算符分配的内存。定位new运算符可被重载,但不能被替换。
如: void* operatornew(std::size_t, void*);