《C++ Primer Plus》第9章:内存模型和名称空间
吭哧吭哧继续学习~越来越觉得博客是个督促自己学习的好方法耶
1. 单独编译
1.1 预处理器编译指令#ifndef
避免多次包含同一个头文件
//coordin.h #ifndef COORDIN_H_ #define COORDIN_H_ .... #endif
1.2 程序的组织方式
一般将程序分为三个部分
头文件:包含函数原型、使用#define或const定义的符号常量、结构声明、类声明、模板声明、内联函数。
源代码文件:包含与结构、类、函数有关的代码。
源代码文件:包含与调用结构、类、函数有关的代码。
2.存储持续性、作用域和链接性
2.1 存储持续性:数据保留在内存中的时间
自动存储持续性:在函数定义中声明的变量(包括函数参数)
静态存储持续性:在程序整个运行过程中都存在,如在函数外定义的变量和用关键字static创建的变量。
线程存储持续性:生命周期与所属的线程一样长,用关键字thread_local声明的变量。
动态存储持续性(*存储或堆):由new分配的内存,一直存在,直到使用delete删除。
2.2 作用域:描述了名称在文件(翻译单元)的多大范围内可见(局部或者全局)
2.3 链接性:描述了名称如何在不同单元之间共享(内部或者外部)
2.4 不同的C++存储方式是通过存储持续性、作用域和链接性来描述的。
2.5 自动存储持续性
在函数中声明的变量和函数参数的存储持续性为自动,作用域为局部,没有链接性。
利用堆栈管理自动变量的增减。
2.6 静态持续变量
在整个程序执行期间一直存在。可以有三种链接性:外部链接性(可在其他文件中访问,声明方法是在代码块的外面声明),内部链接性(只能在当前文件中访问,声明方法是在代码块的外面声明并且使用static限定符),无链接性(只能在当前函数或者代码块中访问,声明方法是在代码块内部声明并且使用static限定符)
... int global = 1000; static int one_file = 50; int main() { ... } void func1(int n) { static int count = 0; int a = 0; }
动态初始化:在编译后初始化。
2.7 静态持续性、外部链接性
2.7.1 链接性为外部的变量简称为外部变量,也称为全局变量。
2.7.2 单定义规则:变量只能有一次定义。
2.7.3 C++提供了两种变量声明
定义声明(或简称定义 definition):给变量分配存储空间
引用声明(或简称声明declaration):不给变量分配存储空间,使用关键字extern,且不进行初始化(如果初始化了,则为定义,即使有关键字extern)。
2.7.4 如果在多个文件中使用一个外部变量,只需要在一个文件中包含该变量的定义,但在其他使用该变量的文件中 使用extern关键字声明它。
//file01.cpp double up; //definition,up = 0 int dogs = 1; //definition external int cats = 2; //definition //file02.cpp extern int dogs; //declaration extern double up; //declaration extern int cats; //declaration
2.8 静态持续性,内部链接性
链接性为内部的变量只能在其所属的文件中使用。
2.8.1 如果在一个文件中定义了一个静态外部变量,其名字与另一个文件中定义的常规外部变量同名,则在该文件中,静态外部变量将隐藏常规外部变量。
//file1.cpp int error = 20; //external definition ... //file2.cpp static int error = 30; //known to file2 only { cout<
2.8.2 静态存储持续性、无链接性
将static限定符用于代码块中的变量,将导致该变量虽然只在该代码块中可用,但是它在该代码块不处于活动状态时仍然存在。静态局部变量只初始化一次。
2.9 说明符和限定符
被称为存储说明符和cv-限定符的C++关键字提供了有关存储的信息。
2.9.1 存储说明符
auto:在C++11之前auto指出变量为自动变量,C++11之后表示自动类型推断
register:指示寄存器存储
static:用于作用域为整个文件的变量时表示内部链接性,用于局部声明时表示该局部变量的存储持续性是静态的
extern:表示是引用声明
thread_local:指出变量的持续时间和其所属线程的持续时间相同,C++11新增的
mutable:即使结构(或类)为const,其某个成员被声明为mutable,则该成员可以被修改
struct data { char name[30]; mutable int access; } const data veep={"Jane",20}; veep.access++;
2.9.2 c-v限定符
2.10 函数和链接性
2.11 语言链接性
extern "C" void sniff(int); //use C protocal for name look-up extern "C++" void sniff(int); //use C++ protocal for name look-up extern void sniff(int); //use C++ protocal for name look-up
2.12 存储方案和动态分配
double * pd = new doubel (9.9); //C++98 double * p1 = new double {9.9}; //C++11 struct where {double x, double y}; where * p2 =new where {1.2, 3.4}; //C++11 int * p3 = new int[3] {1,2,3}; //C++11
3. 名称空间
3.1 传统的C++名称空间
3.2 名称空间
- 名称空间提供一个声明名称的区域。名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中,因此名称空间的默认链接性为外部的。
- 全局名称空间:对应于文件级声明区域。
- 名称空间是开放的,可以把名称加入到已有的名称空间中。
namespace Jill{ int pal; double num; } namesapce Jill{ char * a; //add a to namespace Jill }
- 在名称空间Jack中提供了函数fetch()的原型,可以在该文件的后面再次使用Jack名称空间来提供该函数的代码。
namespace Jack{ void fetch(); } namespace Jack{ void fetch(){ ... } }
- 作用域解析运算符::,访问给定名称空间中的名称
3.3 using声明
namespace Jill{ double fetch; int a; } char fetch; int main(){ using Jill::fetch; double fetch; //error cin>>fetch; //read a value into Jill::fetch cin>>::fetch; //read a value into global fetch }
3.4 using编译指令
int main(){ using namespace jack; ... }
3.5 using编译指令和using声明的比较
- 如果名称空间和声明区域定义了相同的名称。如果试图使用using声明将名称空间的名称导入该声明区域,则两个名称会发生冲突,编译器会报错。如果试图使用using编译指令将名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。
- 虽然函数中的using编译指令将名称空间中的名称视为在函数之外声明的,但它不会使得在该文件中的其它函数能够使用这些名称。
namespace Jill{ double bucket(double n){...} double fetch; struct Hill {...}; } char fetch; int main() { using namespace Jill; Hill Thrill; double water = bucket(2); doubel fetch; cin>>fetch; cin>>::fetch; cin>>Jill::fetch; } int foom() { Hill top; //ERROR Jill::Hill crest; //valid }
3.6 名称空间的其它特性
- 可以将名称空间声明进行嵌套。
- 可以在名称空间中使用using编译指令和using声明。
- 可以给名称空间创建别名。
namesapce MEF = myth::elements::fire; using MEF::flame;
3.7 未命名的名称空间
3.8 关于名称空间的一些编程指导原则
- 使用已命名的名称空间中声明的变量,而不是使用外部全局变量或者静态全局变量。
- 如果开发了一个函数库或者一个类库,将其放在一个名称空间中。事实上,C++当前提倡将标准函数库放在名称空间std中,这种做法扩展了来自C语言的函数,例如头文件math.h是与C语言兼容的,没有使用名称空间,但C++头文件cmath应将各种数学库函数放在名称空间std中。
- 不要在头文件中使用using编译指令,如果非要使用,则应放在所有预处理器编译指令#include之后。
- 导入名称时,首选作用域解析符和using声明指令,对于using声明,应将其作用域设置为局部而不是全局。