《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;
}
静态初始化:零初始化和常量表达式初始化,在编译器翻译单元时初始化。如果没有显示地初始化静态变量,编译器将把它初始化为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限定符
const:被初始化后不能再被修改,而且const对默认存储类型稍有影响。在默认情况下,全局变量的链接性为外部的,但const全局变量的链接性为内部的(就像使用了static一样)。这就是const变量可以放在头文件中的原因。
volatile:即使程序代码没有对内存单元进行修改,其值也可能发生变化。
2.10 函数和链接性
所有函数的存储持续性都自动为静态的。默认情况下,函数的链接性为外部的。可以在函数原型中使用extern指出该函数是在另一个文件中定义的,不过这是可选的。
用static可定义链接性为内部的函数,函数原型和函数定义都必须包含static关键字。
多文件程序中,只能有一个文件包含该函数的定义,使用该函数的每个文件都包含其函数原型。
内联函数不受单定义原则约束,因此内联函数的定义可以放在头文件中。
2.11 语言链接性
C++语言链接性:C++编译器执行名称矫正或名称修饰,为重载函数生成不同的符号名称。
C语言链接性:在C语言中一个名称只对应一个函数。
如果在C++中要使用C库中预编译的函数,需要用到语言的链接性。
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 存储方案和动态分配
编译器使用三块独立的内存:一块用于静态变量,一块用于自动变量,一块用于动态存储。
虽然存储方案概念不适用于动态内存,但适用于用来跟踪动态内存的自动和静态指针变量。
使用new运算符初始化
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声明
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编译指令
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声明,应将其作用域设置为局部而不是全局。