C++学习笔记3:内存模型和名称空间

xiaoxiao2021-02-28  68

《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声明,应将其作用域设置为局部而不是全局。

转载请注明原文地址: https://www.6miu.com/read-75916.html

最新回复(0)