C++ primer Plus 第九章:内存模型和名称空间

9.1单独编译

C++鼓励程序员将组件函数放在独立的文件中。可以将结构的声明放入头文件中,使用和改写的时候只需要载入头文件和修改头文件即可。

于是原来的程序就被分成三部分:

头文件:包含结构声明和使用这些结构的函数的原型。

源代码文件:包含与结构相关的函数的代码

源代码文件:包含调用与结构相关的函数的代码

头文件一般放:

函数原型,使用#define和const定义的符号常量,结构声明,类声明,模板声明,内联函数

简单来讲,第二个源代码文件放函数的定义,第三个源代码文件放主函数。

载入头文件

如果是系统自带的头文件,使用<>来,如#include<cmath>,若是自己编写的头文件则使用双引号代替<>。

原因就是对于文件名包含尖括号的头文件,编译器将在标准头文件的主机系统的文件系统中查找;若文件名包含在双引号中,编译器首先查找当前目录或源代码目录,如果没有找到,则在标准头文件的文件系统中找。

头文件管理

在同一个文件中只能将同一个头文件包含一次。记住这个规则很简单,但是很可能在不知情的情况下多次包含。

#ifndef 和#endif 可以用来解决这个问题

#ifndef FUN_H//如果没有定义这个FUN_H则执行下列语句一直到#endif
#define FUN_H//宏定义


class fun
{
    public:
        fun();
        virtual ~fun();

    protected:

    private:
};

#endif // 不执行任何操作,因为下面没有代码

就类似于if 和else。

9.2存储持续性、作用域和链接性

自动存储持续性:在函数定义中声明的变量的存储持续性是自动的。在执行完指定函数或代码块后,它们使用的内存会被释放。

静态存储持续性:在函数定义外定义的变量(全局变量)和使用static定义的变量的存储持续性为静态。它们在程序执行的整个过程中都存在。

动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete释放内存。有时候也成为自动存储或堆。

9.2.1作用域和链接

作用域:描述了名称在文件的多大范围内可见。

作用域为局部的变量只能在定义它的代码块中可用,作用域为全局的变量在定义变量的位置到文件结尾之间可用。

链接性:描述了名称如何在不同的单元间共享。

链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为它们不能共享。

9.2.2自动存储持续性

如果有一个全局变量fun和一个happy函数,而happy函数里有个局部变量fun,那么在happy里所有的fun都是整个局部变量,这种情况叫做全局变量fun被隐藏了。

但是C++允许我们使用::(作用域解析符)放在变量前,来指出使用的是全局变量,而不是局部变量。

自动变量和栈

简单来讲就是程序使用两个指针一个指向栈顶一个指向栈底,把函数的参数压入栈中,然后对其进行操作(如果不对其进行返回或者不是修改指针),对局部变量的操作影响不到主函数,因为函数结束,指向栈顶的指针重新指向未调用函数前的位置,这也就意味着局部变量被释放了。

9.2.3静态持续变量

静态持续变量有三种链接性:外部链接性,内部链接性,无链接性。

外部链接性,需要在代码块外声明他

内部链接性,需要在代码块外声明它,并使用static修饰

无链接性,代码内声明,加static

由于静态变量的数目是在整个程序运行期间是不变的,因此程序不需要使用栈来管理他们,编译器将分配固定的内存块来存储所有的静态变量,另外,如果没有显示的初始化静态变量,编译器将把它设置为0(在编译器处理文件时,就将其初始化)

9.2.4静态持续性和外部链接性

单定义规则:每个使用外部变量的文件中,都必须先声明它。

该规则指出变量只能有一次定义。为满足这种需求,C++提供了两种变量声明。

1.定义声明

它给变量分配存储空间

2.引用声明

它不给变量分配存储空间,因为它引用已有变量。(静态成员变量初始化不能再类的声明种初始化静态成员变量,这是因为声明中描述了如何分配内存,初始化的时候还要加上作用域解析符)只有静态常量成员才可以在类内初始化

引用声明需要使用关键字extern,且不进行初始化

例如:

double up; //定义声明
extern int blem;//引用声明
extern char gr = 'z';//定义声明

为什么要用引用声明呢,

  总的一点来说,extern有两个作用

一是由于const和typedef在默认情况下是内部链接(静态链接)的,我们用extern去修饰可让它变成外部链接,让其他程序文件可见。
二是用extern修饰后的变量名可表示是一个变量声明,且仅仅是声明,它的定义和声明不在一起,可能是在别的文件中已经定义了该变量,我们在本文件中使用extern声明仅仅是告诉编译器,我们有这么个名字的变量要用到,它的定义来自于别的文件中。

下面是个例子

//main.cpp
#include <iostream>
using namespace std;
extern int a;
int main()
{
    cout <<a<< endl;
    return 0;
}
//aa.cpp
#include<cstdio>
using namespace std;
extern int a=1;//int a=1也行

输出结果就是

1

9.2.5静态持续性和内部链接性

9.2.6静态持续性和无链接性

在函数体内定义无链接性的静态持续性变量时,当且仅当程序第一次执行该函数的时候,初始化该变量,后续无论无论再调用多少次该函数,该变量也不会被初始化了,而且,函数执行完后,该变量存储内存不会被释放。

9.2.7说明符和限定符

auto C++11之前指出变量是自动变量,之后是用于自动推断

register 用于声明中指示寄存器存储,在C++11中,它只是显式的指出变量是自动的

static 用在作用域为整个文件的声明中,表示内部链接性,用在局部声明中,表示局部变量的存储持续性为静态的

extern 表明是引用声明即声明引用在其他地方定义的变量

thread_local 指出变量的持续性与其所属属性线程的持续性相同。thread_local之于线程,犹如常规静态变量之于整个程序

mutable 即使类为const 用mutable修饰的成员也可以修改

const

在默认条件下全局变量的链接性是外部的,但是const修饰的全局变量的链接性为内部的。const 前面加上extern可以将其链接性变为外部的

9.2.8函数和链接性

函数也有链接性。C++不允许在一个函数里定义另一个函数,因此所有函数的存储持续性都自动为静态的。

可以使用extern声明来声明一个函数是另一个文件中的函数。

多文件编译不允许函数重名(内联函数除外),可以是用static 限定函数的链接性是内部的。

函数的这种情况与全局变量和局部变量一样,本文件的函数会覆盖掉同名的其他文件中声明的全局函数。

C++在哪里寻找函数呢?

如果该文件中的函数原型指出函数是静态的,编译器会在该文件中查找函数定义,否则编译器将在所有程序文件中查找,如果找到两个定义,编译器将报错,如果没有找到,编译器将在库中国搜索,如果定义了一个与库函数同名的函数,编译器将使用程序员定义的函数版本,而不是库函数。

9.2.9语言链接性

在C中一个名称对应一个函数,为满足内部需求,编译器可能将spiff这样的函数名翻译为_spiff。在C++中一个名称可能对应多个函数,因此编译器可能将spiff(int)翻译成_spoff_i,而将spiff(double)翻译成_spiff_d_d,这种方法成为C++语言链接

如果在C++的程序中使用C库预编译的函数,需要加限定词

extern "C" void spiff(int);

9.2.10存储方案和动态分配

1.new 运算符初始化

new的返回类型是void*

int *p= new int(6)分配一个存放int型变量的内存,把6存进去,并使p指向它

也可以初始化数组或者常规结构

int *ar= new int [4]{1,2,3,4}

2.new 失败时

如果申请内存失败,最初10年内,C++会返回NULL,后来会引发异常std::bad_alloc

3.new运算符、替换函数

int *p = new int 实际上时int *p = new (sizeof(int))

4.定位运算符

常规new负责在堆里分配内存,定向new可以允许我们从特定的地方去分配内存。

首先要包含new头文件

语法 new(位置)类型

例如

char a[100];

int *p=new(a) int[4];

含义是从a数组里分配存储四个整型变量的空间。

使用new分配的内存,要用delete释放,如果是数组,需要用 delete []来释放,如果使用的是定向new分配的内存并且是从静态持续性变量里分配的内存,这种情况不能使用delete,因为这部分内存不能被手动释放

9.3名称空间

当程序使用多个厂的类库时候,可能导致名称冲突,例如两个类库,都有一个叫LIST的类,那么就存在冲突了,这时候就需要名称空间,使用名称空间可以限定使用的哪一个类库里的LIST类。

C++可以通过namespace来创建命名的名称空间。

namespace jack
{
    int i;
}
namespace jill
{
    int i;
}

两个i并不会冲突,使用时候加上作用域解析符::来限定使用的是哪一个i,如jack::i和jill::i

名称空间可以是全局的,也可以是局部的,名称空间里可以包含另一个名称空间。

using声明和using编译指令

using声明由被限定的名称和它前面的关键词组成

如using jill::i;

using声明会将特定的名称添加到它所属的声明区域中。完成声明后,用i就可以代替jill::i。

using 编译指令由名称空间吗和他前面的关键字using namespace 组成,它使名称空间中的所有名称都可用,而不需要作用域解析运算符。

如 using namespace jill;

在全局声明区域声明,将使该名称空间的名称全局可用,在局部则是局部可用。

如果试图使用using申明将名称空间的名称导入该声明区域,则这两个名称会发生冲突,从而出错,如果使用using编译指令将该名称空间的名称导入该声明区域,则局部版本将隐藏名称空间版本。

使用名称空间里的名称空间 只需要在前面加上作用域解析符

如 using namespace jack::jill

namesapce jack1=jack 的含义是jack1是jack的别名。

未命名的名称空间

namespace {

int i;

}

这样,它的作用域就与全局变量的作用域相同了。

发布了37 篇原创文章 · 获赞 3 · 访问量 2382

猜你喜欢

转载自blog.csdn.net/Stillboring/article/details/105205500
今日推荐