C++内存模型与名称空间总结,看这一篇就够了

9 内存模型和名称空间

9.1 分开编译

9.1.1 C++文件管理

如何将一个源文件改成多个源文件编译:

  1. 头文件:包括结构声明和使用这些结构的函数原型。
  2. 源代码文件:包含与结构相关的函数代码。
  3. 源代码文件:包含调用与结构相关的函数代码

头文件里面包含什么:

  1. 函数原型
  2. 使用#define或const定义的符号常量
  3. 结构声明
  4. 类声明
  5. 模板声明
  6. 内联函数

头文件里面不应该包含函数定义,因为如果两个头文件里面都定义了某函数,一个源文件包含这两个头文件,那么函数定义就重复了,会报错。

结构声明和模板声明是只是告诉编译器如何创建结构变量或实例化模板函数,因此可以放在头文件;const和内联函数有特殊的链接属性,也可以放在头文件中。

#include <file>
//包含的是系统文件
#include "file.h"
//包含的是当前工作目录或源代码目录

如果文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找;但是如果文件名包含在双引号中,则编译器将首先查找当前工作目录或源代码目录(或其他目录,这取决于编译器);因此在包含自己的头文件时,应使用引号而不是尖括号。

Unix系统中如何生成可执行文件.exe:

img

9.1.2 头文件管理

9.1.2.1 防止多次包含头文件出现的问题

你应该只在一个源文件中包含某一个头文件一次。但是你很可能在不知情的情况下包含了两次头文件,例如,您可以使用包含另一个头文件的头文件。因此使用一种方法避免这种情况:

#ifndef COORDIN_H_
#define COORDIN_H_
// place include file contents here
//这里就是写头文件里面的内容
#endif

编译器首次遇到该文件时,名称为COORDIN_H_ 没有定义(根据include文件名来选择名称,并加上一些下划线,以创建一个在其他地方不太可能被定义的名称。)。这种方法不能放置编译器将文件包含两次,而只是让它忽略除第一次包含之外的所有内容。

9.1.2.2 多个库链接

不同的编译器对同一个代码可能生成不同的解释,这样可能导致在调用别的编译器提供的函数时不匹配的问题。因此当尝试链接已编译模块时,要保证每个对象文件和库产出自同一个编译器。如果有源文件,那么只需要重新编译链接即可。

9.2 存储类型、作用域和链接

9.2.1 存储时间分类

动态存储—Automatic storage duration:在函数定义中定义—包括形参—被自动存储。他们在程序开始执行其所述的函数或代码块时被创建,在执行完函数和代码块时,他们的内存被释放。

静态存储—Static storage duration:在函数外定义或使用关键字static定义的变量—被静态存储。他们在程序整个运行过程中都存在。

线程存储(c++ 11)—Thread storage duration (C++11):当前,多核处理器很常见;这些CPU可同时执行多个任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量使用关键字thread_local声明的,则其生命周期与所属的线程一样长。

动态存储—Dynamic storage duration:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。使用动态存储(存储在自由存储区或堆中)

9.2.2 作用域和链接

作用域:描述名称在文件中可见的范围(翻译单元)(可见就是在程序中可用)。函数中定义的变量只可在函数中使用,在文件中函数定义之前定义的变量可以在所有函数中使用。

链接:描述了如何在不同的单元中共享名称。链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享;自动变量没有链接性,因为他们不能共享。

关于作用域:
局部作用域:就是在{}中定义的变量。

全局作用域(or 文件作用域):在定义位置到文件结尾之间都可用。

动态变量具有局部作用域,静态变量可以具有任意一种作用域,这取决于它是如何定义的。

函数原型作用域:函数原型作用域中使用的名称仅在参数列表的括号中已知。(这就是为什么它们是什么或者它们是否存在并不重要。)

类作用域:类中声明的成员具有类作用域。

命名空间作用域:在名称空间中声明的变量具有名称空间作用域。(全局作用域为命名空间作用域的一种特殊情况)

C++函数的作用域可以是整个类或整个名称空间,但不能是局部的(因为不能在代码块中定义函数)

9.2.3 自动存储详解

如果在代码块中定义了变量,则该变量的存在时间和作用域将被限制在该代码块内。

//teledeli在大块里面都有效,websight只在小块里面有效
int main()
{
    int teledeli = 5;
    { // websight allocated
        cout << "Hello\n";
        int websight = -2; // websight scope begins
        cout << websight << ' ' << teledeli << endl;
    } // websight expires
    cout << teledeli << endl;
    ...
} // teledeli expires

如果在两个嵌套的块中定义了两个同名的变量,那么按下面的规则:

img

9.2.3.1 使用C++11的auto

auto关键字:它用于显式地将一个变量标识为具有动态存储。----这是以前的版本

int froob(int n)
{
    auto float ford; // ford has automatic storage
    ...
}

因为程序员只能对默认情况下已经是自动的变量使用auto关键字,所以他们很少费心去使用它。它的主要功能是记录您确实想使用的局部动态变量。

所以在c++ 11中,这种用法不再有效。制定标准的人不愿意引入新的关键字,因为这样做可能会使已经将该词用于其他目的的现有代码失效。在这种情况下,我们觉得旧的auto用法已经很少见了,所以最好是重新利用这个关键词,而不是引入一个新的关键词。

现在的c++11中auto是用来作为动态变量类型的标识符。

auto ppb = arp; // C++11 automatic type deduction
9.2.3.2 自动变量的初始化

可以使用任何在声明时其值为已知的表达式来初始化自动变量。

int w; // value of w is indeterminate
int x = 5; // initialized with a numeric literal
int big = INT_MAX – 1; // initialized with a constant expression
int y = 2 * x; // use previously determined value of x
cin >> w;
int z = 3 * w; // use new value of w
9.2.3.3 自动变量和堆栈

通常的方法是留出一部分内存,并将其作为一个堆栈来管理变量的流动和衰退。

之所以称其为堆栈,是因为新数据形象地堆叠在旧数据之上(即,在相邻的位置,而不是在同一位置),然后在程序完成时从堆栈中移除。堆栈的默认大小取决于实现,但编译器通常提供更改大小的选项。程序通过使用两个指针来跟踪堆栈。一个指向堆栈的底部,这里是为堆栈预留的内存开始的地方,另一个指向堆栈的顶部,这是下一个可用的内存位置。当一个函数被调用时,它的自动变量被添加到堆栈中,指向顶部的指针指向变量后面的下一个可用的空闲空间。当函数终止时,顶部指针被重置为调用函数之前的值,有效地释放了用于新变量的内存。

就是一个后进先出的栈。— LIFO (last-in, first-out)

img

9.2.3.4 寄存器变量

关键字register最初是由C语言引入的,他建议编译器使用CPU寄存器来存储自动变量—这样的目的是提高访问速度

register int count_fast; // request for a register variable

在c++ 11之前,c++以同样的方式使用关键字,随着硬件和编译器的发展,这种提示表明变量用得很多,编译器可对其做特殊处理。在c++ 11中,即使是这样的提示也被弃用了,寄存器只是一种显式地标识变量为自动变量的方法。考虑到register关键字只能用于自动变量,使用这个关键字的一个原因是表明您确实想使用一个自动变量,可能是一个与外部变量同名的变量。这与auto最初的用途是一样的。然而,保留register关键字更重要的原因是避免使用该关键字的现有代码失效。

就是说没啥用,基本上不用了,需要被取缔的状态。

9.2.4 静态变量详解

9.2.4.1 三种静态变量

external linkage:外部链接,可跨文件链接

internal linkage:内部链接(可访问单个文件中的函数)

no linkage:没有链接(只能访问一个函数或函数中的一个块)

静态变量比动态变量生存时间长,它也不需要使用堆栈来分配内存,编译器分配一个固定的内存块来保存所有的静态变量。如果你没有初始化定义的静态变量,它将被编译器设置为0,静态数组和静态结构体的所有元素都会被设置为0.

三种静态变量的实现:

...
//To create a static duration variable with external linkage, you declare it outside any block.   
int global = 1000; // static duration, external linkage
static int one_file = 50; // static duration, internal linkage
int main()
{
	...
}
void funct1(int n)
{
    static int count = 0; // static duration, no linkage
    int llama = 0;
    ...
}
void funct2(int q)
{
    ...
}

img

9.2.4.2 初始化静态变量

静态初始化:零初始化和常量表达式初始化,这意味着编译器处理文件时初始化变量。

动态初始化意味着变量将在编译后初始化。

那么初始化形式由什么因素决定呢?首先,所有静态变量都被零初始化,而不管程序员是否显式地初始化了它;接下来,如果使用常量表达式初始化了变量,且编译器仅根据文件内容(包括被包含的头文件)就可计算表达式,编译器将执行常量表达式初始化;必要时,编译器将执行简单计算;如果没有足够地信息,变量将被动态初始化。

常量表达式并非只能使用字面常量的算数表达式,例如,它还可以使用sizeof运算符。

int enough = 2 * sizeof (long) + 1; // constant expression initialization
#include <cmath>
static int x; // zero-initialization
static int y = 5; // constant-expression initialization
static long z = 13 * 13; // constant-expression initialization
const double pi = 4.0 * atan(1.0); // dynamic initialization

初始化pi时,必须调用函数atan,这需要等到该函数被链接且程序执行时。

c++ 11引入了一个新的关键字constexpr,用于扩展创建常量表达式的选项。这本书没有深入做介绍。

9.2.5 静态持续性,外部链接性

外部变量也称为全局变量,它可以在项目的所有文件里面使用。

9.2.5.1 单定义原则

一个变量只能被定义一次。

定义声明:给变量分配存储空间

引用声明:不给变量分配存储空间,因为它引用已有的变量。引用声明使用关键字extern,且不进行初始化;否则,声明为定义,导致分配存储空间。

double up; // definition, up is 0
extern int blem; // blem defined elsewhere
extern char gr = 'z'; // definition because initialized

在使用外部变量时,其定义只能在一个文件里面,其他文件要使用该变量就得使用extern关键字,如下所示:

// file01.cpp
//在定义外部变量时,可以不加extern关键字,可以进行初始化
extern int cats = 20; // definition because of initialization
int dogs = 22; // also a definition
int fleas; // also a definition
...
// file02.cpp
// use cats and dogs from file01.cpp
//在使用外部变量时,不能在下面这句中修改该变量的值,只能在后面再更改,目的是为了区分定义和引用
extern int cats; // not definitions because they use extern and have no initialization
cats = 1000;//OK
extern int dogs; 
...
// file98.cpp
// use cats, dogs, and fleas from file01.cpp
extern int cats;
extern int dogs;
extern int fleas;
...

在定义外部变量时可以不使用extern关键字,如下图所示:

img

9.2.5.2 如何处理相同名称的外部变量和动态变量举例

局部(动态变量)会隐藏外部变量。

scope-resolution operator (:😃:用于访问全局变量。

code:

// support.cpp -- use external variable
// compile with external.cpp
#include <iostream>
extern double warming; // use warming from another file
// function prototypes
void update(double dt);
void local();
using std::cout;
void update(double dt) // modifies global variable
{
    //在update()函数中使用::warming会更好、更安全
    //下面这句就是可要可不要
	extern double warming; // optional redeclaration
	warming += dt; // uses global warming
	cout << "Updating global warming to " << warming;
	cout << " degrees.\n";
}
void local() // uses local variable
{
	double warming = 0.8; // new variable hides external one
	cout << "Local warming = " << warming << " degrees.\n";
	// Access global variable with the
	// scope resolution operator
	cout << "But global warming = " << ::warming;//scope-resolution operator (::)
	cout << " degrees.\n";
}
// external.cpp -- external variables
// compile with support.cpp
#include <iostream>
using namespace std;
// external variable
double warming = 0.3; // warming defined
// function prototypes
void update(double dt);
void local();
int main() // uses global variable
{
	cout << "Global warming is " << warming << " degrees.\n";
	update(0.1); // call function to change warming
	cout << "Global warming is " << warming << " degrees.\n";
	local(); // call function with local warming
	cout << "Global warming is " << warming << " degrees.\n";
	return 0;
}

运行结果:

Global warming is 0.3 degrees.
Updating global warming to 0.4 degrees.
Global warming is 0.4 degrees.
Local warming = 0.8 degrees.
But global warming = 0.4 degrees.
Global warming is 0.4 degrees.

D:\Prj\C++\external_autimatic_varible\Debug\external_autimatic_varible.exe (进程 4520)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
9.2.5.3 全局VS局部变量

Global Variables可能带来程序不稳定问题。

程序越能避免对数据进行不必要的访问,就越能保持数据的完整性。

Global Variables的使用场景:

让多个函数可以使用同一个数据块(如月份名数组或原子量数组)。外部存储尤其适于表示常量数据,因为这样可以使用关键字const来防止数据被修改。

const char * const months[12] =
{
    "January", "February", "March", "April", "May",
    "June", "July", "August", "September", "October",
    "November", "December"
};

9.2.6 静态持续性、内部链接性—如何处理相同名称的外部变量和内部变量

将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。

如果要在其他文件中使用相同的名称来表示其他变量,该如何办呢?只需省略关键字extern即可吗?

如果文件定义了一个静态外部变量,其名称与另一个文件中声明的常规外部变量相同,则在该文件中,静态变量将隐藏常规外部变量。

// file1
int errors = 20; // external declaration
...
---------------------------------------------
// file2
static int errors = 5; // known to file2 only
void froobish()
{
    cout << errors; // uses errors defined in file2
    ...
}

举例:

Listing 9.7 twofile1.cpp
// twofile1.cpp -- variables with external and internal linkage
#include <iostream> // to be compiled with two file2.cpp
int tom = 3; // external variable definition
int dick = 30; // external variable definition
static int harry = 300; // static, internal linkage
// function prototype
void remote_access();
int main()
{
    using namespace std;
    cout << "main() reports the following addresses:\n";
    cout << &tom << " = &tom, " << &dick << " = &dick, ";
    cout << &harry << " = &harry\n";
    remote_access();
    return 0;
}
// twofile2.cpp -- variables with internal and external linkage
#include <iostream>
extern int tom; // tom defined elsewhere
static int dick = 10; // overrides external dick
int harry = 200; // external variable definition,
// no conflict with twofile1 harry
void remote_access()
{
    using namespace std;
    cout << "remote_access() reports the following addresses:\n";
    cout << &tom << " = &tom, " << &dick << " = &dick, ";
    cout << &harry << " = &harry\n";
}

运行结果:

main() reports the following addresses:
0x0041a020 = &tom, 0x0041a024 = &dick, 0x0041a028 = &harry
remote_access() reports the following addresses:
0x0041a020 = &tom, 0x0041a450 = &dick, 0x0041a454 = &harry

9.2.7 块内的静态变量

举例:

code:

// static.cpp -- using a static local variable
#include <iostream>
// constants
const int ArSize = 10;
// function prototype
void strcount(const char * str);
int main()
{
    using namespace std;
    char input[ArSize];
    char next;
    cout << "Enter a line:\n";
    cin.get(input, ArSize);
    while (cin)
    {
        cin.get(next);
        while (next != '\n') // string didn't fit!
            cin.get(next); // dispose of remainder
        strcount(input);
        cout << "Enter next line (empty line to quit):\n";
        cin.get(input, ArSize);
    }
    cout << "Bye\n";
    return 0;
}
void strcount(const char * str)
{
    using namespace std;
    static int total = 0; // static local variable
    int count = 0; // automatic local variable
    cout << "\"" << str <<"\" contains ";
    while (*str++) // go to end of string
    count++;
    total += count;
    cout << count << " characters\n";
    cout << total << " characters total\n";
}

运行结果:

Enter a line:
nice pants
"nice pant" contains 9 characters
9 characters total
Enter next line (empty line to quit):
thanks
"thanks" contains 6 characters
15 characters total
Enter next line (empty line to quit):
parting is such sweet sorrow
"parting i" contains 9 characters
24 characters total
Enter next line (empty line to quit):
ok
"ok" contains 2 characters
26 characters total
Enter next line (empty line to quit):
Bye

9.2.8 说明符和限定符

9.2.8.1 说明符
auto (eliminated as a specifier in C++11):就是一种自动识别数据类型
register:就是单纯说明该变量是动态变量。
static:就是静态变量
extern:外部变量
thread_local (added by C++11):在线程中的本地变量

9.2.8.2 Cv-限定符

const:内存被初始化后,程序便不能再对它进行修改。

volatile:提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。

9.2.8.3 mutable

即使结构(或类)变量为const,其某个成员也可以被修改。

struct data
{
    char name[30];
    mutable int accesses;
    ...
};
const data veep = {"Claybourne Clodde", 0, ... };
strcpy(veep.name, "Joye Joux"); // not allow
veep.accesses++; // allowed
9.2.8.4 更多关于const

默认情况下全局变量的链接性为外部的,const全局变量的链接性为内部的。再C++看来,全局const定义就类似于使用了static说明符一样。

const int fingers = 10; // same as static const int fingers = 10;
int main(void)
{
	...
}

一点说明的举例:

code:

//const_data_def.h
#pragma once
const int money = 10;
//main.cpp
# include <iostream>
# include "const_data_def.h"
//如果const_data_def.h作为多个头文件的引用,那么其中的money变量是每个文件的内部变量
using namespace std;
//如果一定要money作为一个外部变量,那么需要在每个使用它的文件前面给出extern的说明,
//并且不可二次初始化
const extern int money;

int main()
{
	cout<<money;

	return 0;
}

运行结果:

10
D:\Prj\C++\const_and_Internal_or_Extern_Linkage\Debug\const_and_Internal_or_Extern_Linkage.exe (进程 6316)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

9.2.9 函数作用域

9.2.9.1 内部函数

函数在默认情况下是外部函数,可以在包含有外部函数的情况下,定义相同名称的内部函数,内部函数只在本文件下有效,并且内部函数会掩盖掉外部函数,外部函数和内部函数遵循在相同作用于下"一个函数"的原则。使用方式见以下例子:内部函数可以使用加上static关键字或const关键字。

code:

functions.h
#pragma once
void privates(void);

functions.cpp
# include<iostream>
# include"functions.h"
using namespace std;

void privates(void)
{
	cout << "我是privates()函数!";
}
main.cpp
# include<iostream>
# include"functions.h"
using namespace std;

static void privates(void)
{
	cout << "我是privates()函数的内部形式!";
}

int main()
{
	privates();
	return 0;
}

运行结果:

我是privates()函数的内部形式!
D:\Prj\C++\Functions_and_Linkage\Debug\Functions_and_Linkage.exe (进程 8996)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

内联函数允许有多个定义,但是相同名称相同参数的函数必需是一模一样给的。

9.2.9.2 C++如何找到函数定义?

首先根据函数原型确定是否为static作用域,如果是,直接在当前文件下寻找;如果不是,则按以下顺序查找,直到找到为止:所有项目文件->库函数。

9.2.10 不同编译器的语义链接

c++可以有几个具有相同c++名称的函数,这些函数必须被转换成不同的符号名称。

translate spiff(int) to _spiff_i and translate spiff(double, double) to _spiff_d_d…

但是C编译器和C++编译器的命名规则是不一样的,如果要使用一个已经使用C编译完成的函数,链接到C++的项目里面,C中该函数编译为_spiff()名字,但是C++中寻找的是_spiff_i()名字,这样会出现找不到的问题。采用以下方式解决:

extern "C" void spiff(int); // use C protocol for name look-up
extern void spoff(int); // use C++ protocol for name look-up
extern "C++" void spaff(int); // use C++ protocol for name look-up

9.2.11 存储方案与动态分配

这里动态分配了一个存储容量,然后指针p_fees指向该存储容量的首地址,如果动态分配该存储容量所在的块被执行完成,则p_fees指针的内存将会被释放,如果需要将该存储容量传递到别的函数,则需要return一下首地址。

float * p_fees = new float [20];

建议就是说new和delete成对出现,否则可能引起内存泄漏。

9.2.11.1 动态内存分配的初始化
int *pi = new int (6); // *pi set to 6
double * pd = new double (99.99); // *pd set to 99.99

struct where {double x; double y; double z;};
where * one = new where {2.5, 5.3, 7.2}; // C++11
int * ar = new int [4] {2,4,6,7}; // C++11

//只有一个数据也可以使用{}
int *pin = new int {6}; // *pi set to 6
double * pdo = new double {99.99}; // *pd set to 99.99
9.2.11.2 当new失败时

之前是返回空指针,C++11之后是抛出一个std::bad_alloc异常。

9.2.11.3 new的操作符、函数和替换函数

new的函数原型是这样的:

void * operator new(std::size_t); // used by new
void * operator new[](std::size_t); // used by new[]

delete的函数原型是这样的:

void operator delete(void *);
void operator delete[](void *);

当调用new时:

int * pi = new int;
int * pa = new int[40];

编译为:

int * pi = new(sizeof(int));
int * pa = new(40 * sizeof(int));

当调用delete时:

delete pi;

编译为:

delete (pi);

在C++中,你可以根据需要定义new和delete的替换函数(通过重载实现),针对自定义的一种class,也可以直接调用new和delete来实现动态内存分配和释放。

9.2.11.4 放置new操作符

new操作符就是根据需要寻找堆放置相应的数据,放置new就是将new的数据放置到自定义的存储空间里面。

放置new是这样使用的:

#include <new>
struct chaff
{
    char dross[20];
    int slag;
};
char buffer1[50];
char buffer2[500];
int main()
{
    chaff *p1, *p2;
    int *p3, *p4;
    // first, the regular forms of new
    p1 = new chaff; // place structure in heap
    p3 = new int[20]; // place int array in heap
    // now, the two forms of placement new
    p2 = new (buffer1) chaff; // place structure in buffer1
    p4 = new (buffer2) int[20]; // place int array in buffer2
    ...
}

下面举例说明new和放置new的区别:

code:

// newplace.cpp -- using placement new
#include <iostream>
#include <new> // for placement new
const int BUF = 512;
const int N = 5;
char buffer[BUF]; // chunk of memory
int main()
{
	using namespace std;
    double *pd1, *pd2;
    int i;
    cout << "Calling new and placement new:\n";
    pd1 = new double[N]; // use heap
    pd2 = new (buffer) double[N]; // use buffer array
    for (i = 0; i < N; i++)
    	pd2[i] = pd1[i] = 1000 + 20.0 * i;
    cout << "Memory addresses:\n" << " heap: " << pd1
    << " static: " << (void *) buffer <<endl;//这里使用(void *)是为了让他可以被任意类型的指针指向
    cout << "Memory contents:\n";
    for (i = 0; i < N; i++)
    {
        cout << pd1[i] << " at " << &pd1[i] << "; ";
        cout << pd2[i] << " at " << &pd2[i] << endl;
    }
    cout << "\nCalling new and placement new a second time:\n";
    double *pd3, *pd4;
    pd3= new double[N]; // find new address
    pd4 = new (buffer) double[N]; // overwrite old data
    for (i = 0; i < N; i++)
    	pd4[i] = pd3[i] = 1000 + 40.0 * i;
    cout << "Memory contents:\n";
    for (i = 0; i < N; i++)
    {
        cout << pd3[i] << " at " << &pd3[i] << "; ";
        cout << pd4[i] << " at " << &pd4[i] << endl;
    }
    cout << "\nCalling new and placement new a third time:\n";
    delete [] pd1;
    pd1= new double[N];
    pd2 = new (buffer + N * sizeof(double)) double[N];//指定存储位置
    for (i = 0; i < N; i++)
    	pd2[i] = pd1[i] = 1000 + 60.0 * i;
    cout << "Memory contents:\n";
    for (i = 0; i < N; i++)
    {
        cout << pd1[i] << " at " << &pd1[i] << "; ";
        cout << pd2[i] << " at " << &pd2[i] << endl;
    }
    delete [] pd1;
    delete [] pd3;
    return 0;
}

运行结果:

Calling new and placement new:
Memory addresses:
heap: 006E4AB0 static: 00FD9138
Memory contents:
1000 at 006E4AB0; 1000 at 00FD9138
1020 at 006E4AB8; 1020 at 00FD9140
1040 at 006E4AC0; 1040 at 00FD9148
1060 at 006E4AC8; 1060 at 00FD9150
1080 at 006E4AD0; 1080 at 00FD9158
Calling new and placement new a second time:
Memory contents:
1000 at 006E4B68; 1000 at 00FD9138
1040 at 006E4B70; 1040 at 00FD9140
1080 at 006E4B78; 1080 at 00FD9148
1120 at 006E4B80; 1120 at 00FD9150
1160 at 006E4B88; 1160 at 00FD9158
Calling new and placement new a third time:
Memory contents:
1000 at 006E4AB0; 1000 at 00FD9160
1060 at 006E4AB8; 1060 at 00FD9168
1120 at 006E4AC0; 1120 at 00FD9170
1180 at 006E4AC8; 1180 at 00FD9178
1240 at 006E4AD0; 1240 at 00FD9180

区别1:new的内存首地址是一个新分配的远远的地址,而放置new的地址是指定数组buffer[]的首地址。

区别2:new可以判断哪里的内存已经被分配,分配新内存时避开已分配内存;放置new只根据给定的地址分配内存而不检查该块地址是否已经被分配。

区别3:new的内存可以使用delete释放,而放置new的内存不能使用delete释放。

放置new也可以自定义new函数,使之适用于自定义class。

9.2.11.5 放置new的函数
int * pi = new int; // invokes new(sizeof(int))
int * p2 = new(buffer) int; // invokes new(sizeof(int), buffer)
int * p3 = new(buffer) int[40]; // invokes new(40*sizeof(int), buffer)

placement new的函数不可替换,但是可以重载,任何此类重载函数都被称为placement new,即使附加的形参没有指定位置。

9.3 命名空间

命名空间问题:来自一个库的List和来自另外一个库的Tree,他们都希望使用各自定义的Node,这样就出现了命名空间问题。

命名空间工具可以有效解决该问题。

9.3.1 传统C++命名空间

声明区域:内部变量的声明区域为整个文件,动态变量的声明区域为其所在的块。

有效作用域:内部变量的有效作用域为整个文件(该变量定义之后的部分),动态变量的有效作用域为其所在的块(该变量定义之后的部分)。你不能在变量定义之前就使用它。

img

作用域:就是变量真正有效的程序部分。(同名变量可能会被隐藏)

9.3.2 新的命名空间特性

可以自定义命名空间,各命名空间之间互不冲突,设置了代码使用命名空间中元素的机制。

以下定义了命名空间Jack and Jill:

namespace Jack {
    double pail; // variable declaration
    void fetch(); // function prototype
    int pal; // variable declaration
    struct Well { ... }; // structure declaration
}
namespace Jill {
    double bucket(double n) { ... } // function definition
    double fetch; // variable declaration
    int pal; // variable declaration
    struct Hill { ... }; // structure declaration
}

命名空间可以定义在全局等级或其他命名空间中,但是不可以定义在一个块中,因此,命名空间中的名称的作用域为外部链接。

全局命名空间包含全局变量。

img

命名空间是开放的,可以增添新内容(新名称,函数定义等等):

为Jill添加新名称goose:

namespace Jill {
    char * goose(const char *);
}

为Jack添加函数定义:

namespace Jack {
    void fetch()
    {
    ...
    }
}

访问命名空间的名称:

Jack::pail = 12.34; // use a variable
Jill::Hill mole; // create a type Hill structure
Jack::fetch(); // use a function
9.3.2.1 using声明和using编译指令

using声明:使得一个名称有效

using Jill::fetch; // a using declaration

namespace Jill {
    double bucket(double n) { ... }
    double fetch;
    struct Hill { ... };
}
char fetch;
int main()
{
    using Jill::fetch; // put fetch into local namespace
    double fetch; // Error! Already have a local fetch
    cin >> fetch; // read a value into Jill::fetch
    cin >> ::fetch; // read a value into global fetch
    ...
}

在外部级别放置using声明会将名称添加到全局命名空间:

void other();
namespace Jill {
    double bucket(double n) { ... }
    double fetch;
    struct Hill { ... };
}
using Jill::fetch; // put fetch into global namespace
int main()
{
    cin >> fetch; // read a value into Jill::fetch
    other()
    ...
}
void other()
{
    cout << fetch; // display Jill::fetch
    ...
}

using编译指令:使得所有名称有效

using指令涉及到在命名空间名称前面加上关键字using namespace,它使命名空间中的所有名称都可用,而不需要使用作用域解析操作符::

using namespace Jack; // make all the names in Jack available
#include <iostream> // places names in namespace std
using namespace std; // make names available globally
int main()
{
    using namespace jack; // make names available in main()
    ...
}

可能引发的矛盾:但编译器不会允许同时使用两个相同名称的名字,这种情况不会出现的嘿嘿。

using jack::pal;
using jill::pal;
pal = 4; // which one? now have a conflict
9.3.2.2 using声明 VS using编译指令

假设名称空间和声明区域定义了相同的名称。如果视图使用using声明将名称空间的名称导入该声明区域,则这个名称会发生冲突,从而出错。如果使用using编译指令将该名称空间的名称导入该声明的区域,则局部版本将隐藏名称空间版本。

namespace Jill {
    double bucket(double n) { ... }
    double fetch;
    struct Hill { ... };
}
char fetch; // global namespace
int main()
{
    using namespace Jill; // import all namespace names
    //上面那个没有局部作用域,因此不会覆盖全局变量fetch
    Hill Thrill; // create a type Jill::Hill structure
    double water = bucket(2); // use Jill::bucket();
    double fetch; // not an error; hides Jill::fetch
    cin >> fetch; // read a value into the local fetch
    cin >> ::fetch; // read a value into global fetch
    cin >> Jill::fetch; // read a value into Jill::fetch
    ...
}
int foom()
{
    Hill top; // ERROR
    Jill::Hill crest; // valid
}

相对来说using declaration比using directive安全,因为命名空间之间的相同名称的变量可以互相覆盖,所以直接将命名空间所有的变量引入增加了变量覆盖的风险(而且这种不会收到提示,就很难调试)

9.3.2.3 iostream库和命名空间std
#include <iostream>
int main()
{
    using namespace std;
 }
#include语句将头文件iostream放到名称空间std中。然后,using编译指令使得该名称空间在main()函数中可用。

如果编译器没有命名空间,那么使用以下方式可以达到上述代码一样的效果:

#include <iostream.h>

下面这些都是可以的,只是更推荐using declaration:

using namespace std; // avoid as too indiscriminate
int x;
std::cin >> x;
std::cout << x << std::endl;
using std::cin;
using std::cout;
using std::endl;
int x;
cin >> x;
cout << x << endl;
9.3.2.4 嵌套命名空间
namespace elements
{
    namespace fire
    {
        int flame;
        ...
    }
    float water;
}

如何访问呢:

using namespace elements::fire;
using namespace elements::fire::flame;

也可以这样定义嵌套命名空间:

//Jill和std是命名空间
namespace myth
{
    using Jill::fetch;
    using namespace elements;
    using std::cout;
    using std::cin;
}
//可以直接访问
std::cin >> myth::fetch;

using directive是传递的:

using namespace myth;

和下面这两句是一个含义:

using namespace myth;
using namespace elements;

9.3.2.5 给命名空间取别名
//有这样的命名空间
namespace my_very_favorite_things { ... };
//可以这样给它取别名
namespace mvft = my_very_favorite_things;

//取别名常用于简化using嵌套命名空间
namespace MEF = myth::elements::fire;
using MEF::flame;

9.3.3 没有名称的命名空间

这种命名空间只可以在定义它的文件中使用,而无法在其他地方使用。相当于是个全局链接—内部变量。

namespace // unnamed namespace
{
    int ice;
    int bandycoot;
}

比如以下代码:

static int counts; // static storage, internal linkage
int other();
int main()
{
    ...
}
int other()
{
    ...
}

可以更改为同样功能的以下代码:

namespace
{
    int counts; // static storage, internal linkage
}
int other();
int main()
{
    ...
}
int other()
{
    ...
}

9.3.4 一个命名空间的例子

9.3.4.1 code

第一个文件为头文件,包含常量、结构定义、函数原型。

// namesp.h
#include <string>
// create the pers and debts namespaces
namespace pers
{
    struct Person
    {
        std::string fname;
        std::string lname;
    };
    void getPerson(Person &);
    void showPerson(const Person &);
}
namespace debts
{
    using namespace pers;
    struct Debt
    {
        Person name;
        double amount;
    };
    void getDebt(Debt &);
    void showDebt(const Debt &);
    double sumDebts(const Debt ar[], int n);
}

第二个文件是源代码文件,他提供了头文件中的函数原型对应的定义。在名称空间中声明的函数名的作用域为整个名称空间,因此定义和声明必须位于同一个名称空间中。

// namesp.cpp -- namespaces
#include <iostream>
#include "namesp.h"
namespace pers
{
    using std::cout;
    using std::cin;
    void getPerson(Person & rp)
    {
        cout << "Enter first name: ";
        cin >> rp.fname;
        cout << "Enter last name: ";
        cin >> rp.lname;
    }
    void showPerson(const Person & rp)
    {
    	std::cout << rp.lname << ", " << rp.fname;
    }
}
namespace debts
{
    void getDebt(Debt & rd)
    {
        getPerson(rd.name);
        std::cout << "Enter debt: ";
        std::cin >> rd.amount;
    }
    void showDebt(const Debt & rd)
    {
        showPerson(rd.name);
        std::cout <<": $" << rd.amount << std::endl;
    }
    double sumDebts(const Debt ar[], int n)
    {
        double total = 0;
        for (int i = 0; i < n; i++)
        total += ar[i].amount;
        return total;
    }
}
// usenmsp.cpp -- using namespaces
#include <iostream>
#include "namesp.h"
void other(void);
void another(void);
int main(void)
{
    using debts::Debt;
    using debts::showDebt;
    Debt golf = { {"Benny", "Goatsniff"}, 120.0 };
    showDebt(golf);
    other();
    another();
    return 0;
}
void other(void)
{
    using std::cout;
    using std::endl;
    using namespace debts;
    Person dg = {"Doodles", "Glister"};
    showPerson(dg);
    cout << endl;
    Debt zippy[3];
    int i;
    for (i = 0; i < 3; i++)
    	getDebt(zippy[i]);
    for (i = 0; i < 3; i++)
    	showDebt(zippy[i]);
    cout << "Total debt: $" << sumDebts(zippy, 3) << endl;
    return;
}
void another(void)
{
    using pers::Person;
    Person collector = { "Milo", "Rightshift" };
    pers::showPerson(collector);
    std::cout << std::endl;
}

注意:using声明只使用了名称,例如,第二个using声明没有描述showDebt的返回类型或函数参数,而只给出了名称;因此,如果函数被重载,则一个using声明将导入所有版本。

9.3.4.2 运行结果
Goatsniff, Benny: $120
Glister, Doodles
Enter first name: Arabella
Enter last name: Binx
Enter debt: 100
Enter first name: Cleve
Enter last name: Delaproux
Enter debt: 120
Enter first name: Eddie
Enter last name: Fiotox
Enter debt: 200
Binx, Arabella: $100
Delaproux, Cleve: $120
Fiotox, Eddie: $200
Total debt: $420
Rightshift, Milo

9.3.5 命名空间的编程习语

1.使用命名空间中的变量,而不是使用外部全局变量。

2.使用未命名命名空间中的变量,而不是使用静态全局变量。

3.如果您开发了函数或类库,请将它们放在名称空间中。

4.仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计。

5.不要在头文件中使用using编译指令。首先,这样做掩盖了要让哪些名称空间可用;另外,包含头我呢见的顺序肯影响程序的行为。如果非要使用编译指令using,应将其放在所有预处理器编译指令#include之后。

6.导入名称时,首选使用作用域解析运算符或using声明的方法。

7.对于using声明,首选将其作用域设置为局部而不是全局。

README

此为本人读C++ Primer总结的笔记,如有错误或知识缺口,请在评论区告知。如本文有在实践中帮到您,是本人的荣幸。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jasmine-Lily

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值