本章内容概览
- 单独编译
- 存储持续性、作用域和连接性
- 定位new运算符
- 名称空间
单独编译
C++鼓励程序员将组件函数放到独立的文件中,可以单独编译这些文件,然后将它们链接成可执行的程序。
我们可以将许多东西放在头文件中,然后再源代码文件中包含这些头文件,头文件常包含以下内容:
- 函数原型
- 使用#define或const定义的符号常量
- 结构声明
- 类声明
- 模板声明
- 内联函数
同一个文件只能包含一个头文件一次,不过大部分时候无法避免,所以我们要在头文件中声明一些预处理命令,有两种方式:
#pargma once
--------------------
#ifndef ***_H_
#define ***_H_ // *** 是头文件名大写
...
#endif
存储持续性、作用域和链接性
C++11标准,使用4种不同的方案来存储数据:
- 自动存储持续性:在函数定义中声明的变量的存储持续性是自动的,它们在程序开始执行其代码块时创建,结束时释放内存。
- 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量,在程序整个运行过程中存在。
- 线程存储持续性:如果变量使用关键字thread_local声明,则其生命周期和所属线程一致。
- 动态存储持续性:使用new分配的内存一直存在,直到使用delete运算符。
作用域和链接
作用域为局部的变量只在定义它的代码块中使用,作用域为全局的变量在定义位置到文件结尾之间都可以使用。自动变量的作用域为局部,静态变量的作用域为全局还是局部取决于它是如何被定义的。在函数原型作用域中使用的名称只在包含参数列表的括号内可用。在类中声明的成员的作用域为整个类,在名称空间中声明的变量的作用域为整个名称空间。
C++函数的作用域可以是整个类或整个名称空间,但不能是局部的(不能在代码块中定义函数)。
自动持续变量
自动变量的数目随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理。常用的方法是留出一段内存,并将其视为栈,以管理变量的增减。新数据象征性地放在原有数据上面,程序使用完后,将其从栈删除。程序使用两个指针来跟踪栈,一个指针指向栈底——栈地开始位置,另一个指向堆顶——下一个可用内存单元。当函数被调用时,其自动变量将被加入到栈中,栈顶指针指向变量后面的下一个可用的内存单元,函数结束后,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存。
静态持续变量
C++为静态存储持续性提供了3种链接性:外部链接性(可在其它文件中访问)、内部链接性(只能在当前文件访问)和无链接性(只能在当前函数或代码块种访问)。
要想创建链接性为外部的静态持续变量,必须在代码块外声明;要创建链接性为内部的静态持续变量,必须在代码块的外面声明,并使用static限定符;要创建没有链接性的静态持续变量,必须在代码块内声明,并使用static限定符(与自动持续变量的区别是,它会一直存在)。
静态持续变量声明后默认是0.
静态持续性、外部链接性
在每个使用外部变量的文件中,都必须声明它,同时C++有单定义规则,即变量只能定义一次。为满足这种需求,C++提供了两种变量声明。一种是定义声明,它给变量分配存储空间;另一种是引用声明,它不给变量分配存储空间,因为它引用已有的变量。
引用声明使用extern关键字,且不进行初始化,否则,声明为定义,导致分配存储空间:
double up; //定义
extern int blem; //引用声明,blem在其它某处定义
extern char gr = 'z'; //定义
如果要在多个文件中使用外部变量,只需在一个文件中包含该便令的定义,在使用该变量的其它所有文件中,都必须使用关键字extern。
说明符和限定符
存储说明符:
- auto (C++11中不再是)
- register
- static
- extern
- thread_local(C++11新增)
- mutable
- cv-限定符
- const
- volatile
volatile:程序代码没有对内存单元进行修改,其值也可能发生变化。说白了其实是让编译器不使用某种优化。
- mutable
用来指出即使结构或类变量为const,其某个成员也可以被修改,如:
struct data
{
char name[30];
mutable int accesses;
...
};
- const
在C++中,默认情况下全局变量链接性为外部,但const全局变量是链接性为内部的。
如果想要让某个常量的的链接性为外部,则可以使用extern关键字来覆盖默认的内部链接性:
extern const int states = 50;
函数和链接性
默认情况下。函数的链接性为外部的,可以在文件间共享,可以使用static关键字来表示其链接性为内部,使其只能在一个文件中使用。静态函数会覆盖外部定义。
存储方案和动态分配
- 使用new运算符初始化
int* pi = new int (6);
double* pd = new double (99.99);
上述方法是C++98的方式,C++11可以这么做:
struct where {double x;double y;double z;};
where* one = new where{2.5,5.3,7.2};
int* ar = new int[4] {2,4,6,7};
int* pin = new int {6};
new失败时
new可能找不到请求的内存量,这是会引发异常std::bad_alloc。new:运算符、函数和替换函数
new和new[]分别调用:
void* operator new(std::size_t);
void* operator new[](std::size_t);
这些函数被称为分配函数,位于全局名称空间中,同时也有由delete和delete[]调用的释放函数:
void* operator delete(std::size_t);
void* operator delete[](std::size_t);
- 定位new运算符
new运算符还有一种变体,定位运算符,它能够指定要使用的的位置。要使用的话,首先要先包含new头文件,然后将new运算符用于提供了所需地址的参数,比如:
cahr buffer1[50];
int* pa;
p1 = new(buffer1) int[20];
名称空间
C++名称空间通过namespace关键字定义:
namespace Jack
{
double pail;
void fetch();
int pal;
struct Well{...};
}
名称空间可以是全局的,也可以位于另一名称空间中,但不能位于代码块中,因此,默认情况下在名称空间中声明的名称的链接性为外部(除非引用了常量)。
访问名称空间中的名称使用::
作用域解析运算符:
Jack::pail = 12.34;
using声明可以让某一特定标识符可用:
using Jack::fetch;
using 编译指令让名称空间中所有名称都可用:
using namespace Jack;
同时C++不允许同名名称使用using声明。
名称空间可以嵌套:
namespace elements
{
namespace fire
{
int flame;
}
float water;
}
flame即elements::fire::flame,还可以:
using namespace elements::fire;
名称空间中可以使用using编译指令和using声明:
namespace myth
{
using Jill::fetch;
using namespace elements;
using std::cout;
using std::cin;
}
如果要访问Jill::fetch,可以使用myth::fetch,同时也可以使用Jill::fetch,无冲突名称还可以直接访问。
名称空间是可以嵌套的,比如
using namespace myth;
<=>
using namespace myth;
using namespace elements;
还可以给名称空间创建别名:
namespace mvft = myth;