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

时间:2022-08-28 23:30:22

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