《c++ Primer Plus 第6版》读书笔记(4)
第9章 内存模型和名称空间
本章内容包括:
- 单独编译
- 存储持续性、作用域和链接性
- 定位new运算符
- 名称空间
9.1 单独编译
C++也允许将组件函数放在独立的文件中。可以打拿督编译这些文件,然后将它们连接成可执行的程序。
C++编译器既编译程序,也负责链接器。
- 头文件:包含结构声明和使用这些结构的函数的原型
- 源代码文件:包含与结构有关的函数的代码
- 源代码文件:包含调用与结构相关的函数的代码
请不要将函数定义或变量声明放到头文件中。如果头文件中包含一个函数定义,有两个文件进行了include,那么一个程序中将包含同一个函数的两个定义(除非函数是inline)。
头文件中常包含的内容包括:
- 函数原型
- 使用#define和const定义的符号常量
- 结构声明
- 类声明
- 模板声明
- 内联函数
被声明为const的数据和内联函数有特殊的链接属性,因此可以放在头文件中,不会引起问题。
另外,在包含头文件时,如果文件名被包含在尖括号,比如<stdio.h>,那么编译器将会在存储标准头文件的文件系统中查找;如果包含在双引号中,编译器会首先查找当前的工作目录或源代码目录,如果未找到,再去标准位置查找。因此包含自己的头文件的时候,应该使用引号而不是尖括号。
#include指令用来管理头文件,不应该使用include包含源代码文件。
在同一个文件中只能将同一头文件包含一次,但是很可能在不知情的状况下将头文件包含多次。使用预处理编译器指令#ifndef可以解决问题。
#ifndef COORDIN_H_
#define COORDIN_H_// place include file contents here#endif
编译器首次遇到该文件时,名称COORDIN_H_(一般用文件名加下划线)没有定义,此时编译器将会查看#ifndef和#endif之间的内容,并读取到定义#define一行。如果在同一文件中另一位置发现已经定义了COORDIN_H_,那么编译器就会跳到#endif后面的一行上。
使用这种方法并不能防止编译器将文件包含两次,只是会忽略掉第一次包含之外的所有内容。
C++每个编译器都会用自己的方式进行名称修饰,不同编译器创建的二进制模块可能会无法正确链接。所以在链接编译模块时,请确保所有对象文件或库都是由同一个编译器生成的。
9.2 存储持续性、作用域和链接性
C++使用三种(C++11为四种)不同的方式来存储数据,区别在于数据保留在内存中的时间。
- 自动存储连续性:在函数定义中声明的变量(包括函数参数)的存储连续性是自动的。函数执行时创建,完成时释放。C++有两种存储持续性为自动的变量。
- 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量,存储持续性都为静态,在程序整个运行过程中都会存在。C++有三种存储持续性为静态的变量。
- 线性存储持续性(C++11):多核处理使CPU同时处理多个执行任务,如果变量是使用关键字thread_local声明的,那么其生命周期与所属的线程一样长。
- 动态存储持续性:使用new运算符分配内存将会一直存在,直到使用delete运算符将其释放或者程序结束为止。这种内存的存储持续性为动态,有时候会被称为自由存储或者堆存储。
9.2.1 作用域和链接
作用域描述了名称在文件(翻译单元)的多大范围内可见。比如函数中定义的变量在该函数中可见。
链接性描述了名称如何在不同单元间共享。链接性为外部的名称可在文件之间共享,链接性为内部的名称只能有一个文件中的函数共享。自动变量的名称没有链接性,因为不能共享。
C++变量作用域有很多种。
作用域为局部的变量只在定义它的代码块(花括号括起来)中可用;作用域为全局(文件作用域)的变量在定义位置到文件结尾之间都可用;在名称空间中声明的变量的作用域为整个名称空间。
9.2.2 自动存储持续性
在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有磁链性。所以在不同函数中声明的相同名称的变量是独立的。
如果在代码块中声明了变量,在该代码块中又定义了新代码块,声明了相同的变量,那么新的定义会隐藏旧的定义,新定义可见,就定义暂时不可见,程序离开里代码块后,旧定义重新可见。
在旧版的C中,旧版本中的关键字auto,是于显示指出变量为自动存储的,但是用的人很少,所以在新版本C中用新的含义代替了老的使用方法。
自动变量和栈
C编译器运行时会对自动变量进行管理,留出一段内存,将其视为栈,用来管理变量的增减。
寄存器变量
关键字register最初是由C语言引入的,他建议编译器使用CPU寄存器来存储自动变量。
register int count_fast;
但是现在的register已经没有了作用,和以前的auto关键字作用是相同的。
9.2.3 静态持续变量
C++为静态存储持续性变量提供了3种链接性:
- 外部链接性:可在其他文件中访问
- 内部链接性:只能在当前文件中访问
- 无链接性:只能在当前函数或代码中访问
静态变量名称的意义在于,静态变量在整个程序运行期间数目是不变的,因此程序不需要使用特殊的装置(比如栈)来进行管理。
编译器会将没有显式初始化的静态变量设置为0。
进行三种静态变量举例:
...
int global = 100; // static, external linkage
static int one_fle = 50; // static, internal linkage
int main()
{
...
}void funct1(int n)
{static int count = 0; // static , no linkageint llama = 0;
...
}void funct2(int q)
{
...
}
三种静态变量在整个程序执行期间都存在。
- count作用域为局部,没有磁链性,所以只能在funct1中使用。但是和llama不一样的是,count在程序最开始运行时就已经存在在内存了
- global作用域为整个文件,链接性为外部,在程序的其他文件中可以使用
- one_file作用域为整个文件,磁链性为内部,只能在当前文件中使用
5种变量存储方式
存储描述 | 持续性 | 作用域 | 磁链性 | 如何声明 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 无 | 在代码块中 |
寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用register |
静态,无磁链性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字static |
静态,外部磁链性 | 静态 | 文件 | 外部 | 不在任何函数内 |
静态,内部磁链性 | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字static |
9.2.4 静态持续性、外部链接性
C++存在“单定义规则”,即变量只能有一次定义。
所以C++提供了两种变量声明:一种是定义声明,会给变量分配存储空间;一种是引用声明,不会分配空间,只是引用已有变量。
引用声明使用extern进行定义:
double up; // 定义声明
extern int blem; // 引用声明
extern char gr = 'z'; // 定义声明,因为包含了初始化// 如果要在其他文件中使用另一个文件定义的变量,使用extern// file01.cpp
int process_status = 0;
int main()
{...
}// file02.cpp
extern int process_status;
int work()
{std::cout << ::process_status << endl;...int process_status = 1;std::cout << process_status << endl;}
说明,定义与全局变量同名的局部变量后,局部变量将会隐藏全局变量。
C++提供了作用域解析运算符(::)。放在变量名前面时,表示该变量为全局版本变量。
外部存储尤其适用于表示常量数据。
9.2.5 静态持续性、内部链接性
将static作用于作用域为整个当前文件的变量时,磁链性为内部。
如果在一个文件中创建一个全局静态变量,另一个文件中想要创建一个同名变量,那么仅仅省略extern是不够的,这样会违反C++的单定义规则。
正确的做法是声明一个static变量,这样静态变量会隐藏常规外部变量。这样做,声明的变量磁链性为内部,不会违法单定义规则。
9.2.6 静态存储持续性、无磁链性
使用static作用于代码块内的变量,就可以创建无磁链性的局部变量。
变量会在函数运行之前存在,在函数运行结束之后也不会被回收。因此在两次函数调用之间,静态局部变量的值将会保持不变。
另外如果定义时进行初始化,只会在程序启动时进行一次初始化,之后再调用函数时,不会再次进行初始化。
9.2.7 说明符和限定符
存储说明符:
- auto(C++11中不再是说明符)
- register(C++11显式指出是自动类型变量)
- static
- extern
- thread_local(C++11新增)
- mutable
cv-限定符:
- const
- volatile
关键字volatile表示,即使程序代码没有对内存单元进行修改,值也可能会发生变化。比如硬件修改内容、两个程序共享数据等等。该关键字作用是为了改善编译器的优化能力。
关键字mutable表示,即使结构或类变量为const,其某个成员也可以被修改。
struct data
{char name[30];mutable int accesses;...
};const data veep = {"aiky",0,...};strcpy{veep.name, "john"}; //not allowed
veep.accesses++; // allowed
关键字const。在默认情况下全局变量的链接性为外部的,但是const全局变量的链接性为内部的。会和static一样。
可以把const变量放到头文件中,由不同的文件包含。这时由于内部连接性,所以每个文件都会有自己的一组常量,而不是所有文件都共享一组常量。
如果希望某个常量的链接性为外部,那么可使用extern关键字:
extern const int states = 50; // definition with external linkage
此时常量为外部链接性定义。
9.2.8 函数和链接性
函数和变量一样也有链接性,但是范围比较少。
由于C++和C默认不能再函数中定义另外一个函数,所以所有函数的存储持续性都自动为静态。
链接性上,函数默认链接性为外部,可以在文件之间共享。也可以使用extern关键字来指出函数为另一文件中定义,可选。
也可以使用static将函数的链接性默认为内部链接。此时意味着该函数只在当前文件中可见,其他文件中可以定义同名函数。
函数大多只能有一个定义,内联函数除外,所以内联函数可以放在头文件之中。
9.2.9 语言链接性
C语言中,一个名称只对应一个函数,spiff翻译成_spiff。
但是C++中,一个名称可能对应多个函数,spiff(int)可能翻译成_spiff_i,spiff(double,double)可能翻译成_spiff_d_d。
那么此时如果在C++程序寻找C中的函数,就找不到了。
为了解决这种问题,可以使用函数原型来指出要使用的约定。
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
C和C++的链接性是C++标准指定的说明符,但实现可提供其他语言链接性的说明符。
9.2.10 存储方案和动态分配
C++为变量分配内存的5种方案是不适用于动态内存分配的。C++使用new,C使用malloc分配的内存为动态内存分配。
动态内存由new和delete控制,因此可以在一个函数中分配内存,在另一个函数中释放。
通常情况下,编译器使用三块独立的内存:一块用于静态变量(可再细分),一块用于自动变量,一块用于动态存储。
使用new运算符
C++98提供了内置类型的内存分配和初始化方法。
int * pi = new int (6); // *pii set to 6
double *pd = new double (99.9); // *pd set to 99.9
C++11提供了常规结构或者数组的初始化。
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++11int * pin = new int {6}; // C++11
如果使用new失败,找不到请求的内存量。在之前C++会返回空指针,但现在会引发异常std::bad_alloc。
在C++内部中,new会调用void * operator new(std::size_t); 而new []会调用void * operator new[](std::size_t);这些函数被称为分配函数。delete会调用 void operator delete(void *); 而delete[]会调用 void operator delete[] (void *);这些函数被称为释放函数。
在代码中调用new和delete关键字时会被替换:
int * pi = new int ;
// 会被转化为
int * pi = new(sizeof(int));int * pa = new int[40];
// 会被转化为
int * pa = new(40 * sizeof(int));delete pi;
// 会被转化为
delete (pi);
C++将这些函数称为可替换,可以根据需求,对其进行定制和替换函数。
通常,new会在堆中找到一个符合条件的内存块。new运算符还有另一种变体,被称为定位new运算符。
使用定位特性,需要包含头文件<new>。举例:
#include <new>
struct staff
{char dross[20];int slag;
};char buffer1[50];
char buffer2[500];
int main()
{chaff *p1, *p2;int *p3, *p4;// first ,the regular forms of newp1 = new chaff; // place structure in heapp3 = new int[20]; // place int array in heap// now , the two forms of placement newp2 = new (buffer1) chaff; //place structure in buffer1p4 = new (buffer2) int[20]; // place int array in buffer2
...
}
示例中,使用两个静态数组来为new运算符提供内存空间。从buffer1中分配空间给chaff;从buffer2中分配空间给int数组。
但是不能够使用delete进行内存释放,因为buffer是静态内存,delete只能够用于new分配的堆内存。
但如果buffer是使用new来创建的,就可以通过使用delete来释放了。
定位new运算符的另一种用法是,初始化后,将信息放在特定的硬件地址位置。
标准定位new会调用一个接收两个参数的new()函数:
int * p2 = new int; // -> new(sizeof(int))
int * p2 = new (buffer) int; // -> new(sizeof(int), buffer)
int * p3 = new (buffer) int[40]; // -> new(40*sizeof(int), buffer)
9.3 名称空间
随着项目越来越大,名称相互冲突的可能性也在增加。
使用多个厂商的类库时,可能会导致名称冲突。这种冲突被命名为名称空间问题。
C++提供了名称空间工具,方便更好的控制作用域。
9.3.1 传统的C++名称空间
声明区域:是可以在其中进行声明的区域。
潜在作用域:从声明点开始,到声明区域的结尾。潜在作用域比声明区域小。
9.3.2 新的名称空间特性
C++新增功能,通过定义一种新的声明区域来创建命名的名称空间。
名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。
namespace Jack {double pail; void fetch();int pal;struct Well { ... };
}namespace Jil{double bucket(double n){ ... }double fetch;int pal;struct Hil {...};
}
另一种名称空间——全局名称空间。对应于文件级声明区域。
名称空间是开放的,可以将新的名称加入到已有的名称空间中。也可以在一个文件中,使用名称空间Jil提供函数原型,然后在另一个文件中使用名称空间为其提供定义。
需要有一种方法来访问给定名称空间的名称,最简单的方式是,通过作用域解析运算符::
比如:
Jack::pail = 12.34;
Jill::Hill mole;
未被装饰的名称被称为未限定名称;包含名称空间的名称被称为限定名称。
using声明和编译指令
using声明能够使特定标识符可用,using编译指令能够使整个名称空间可用。
// using声明 --------------
// file1
namespace Jill{double bucket(double n) { ... }double fetch;struct Hill { ... };
}// file2
char fetch;
int main()
{using Jill::fetch;cin >> fetch ; //read a value into Jill::fetchcin >> ::fetch; //read a value into global fetch
}// using编译指令-------------
using namespace Jack; // make all the names in Jack available
using编译指令不能同时使用,会导致二义性。
通常情况下,使用using声明会比编译指令更加安全。
名称空间的其他特性
可以将名称空间进行嵌套
namespace elements
{namesapce fire{int flame;...}float water;
}// 此时以下命令都是有效的
using namespace elements::fire;elements::fire::flame = 10;// 在命名空间内也是可以包含using编译指令和using声明的
namespace myth
{using Jill::fetch;using namespace elements;using std::cin;
}
using编译指令是可以传递的,如果A op B and B op C,那么A op C
未命名的名称空间
通过省略名称空间的名称来创建未命名的名称空间。
namespace // unnamed namespace
{int ice;int bandycoot;
}
不能够在未命名名称空间所属文件之外的其他文件中,使用该名称空间中的名称。可以作为static的替代品。
9.3.4 名称空间及其前途
当前的一些指导原则:
- 不使用外部全局变量,使用已命名的名称空间中声明的变量
- 不使用静态全局变量,使用已命名的名称空间中声明的变量
- 开发的函数库或类库,将其放在一个名称空间中。
- 仅将using编译指令作为权宜之计
- 不要在头文件中使用using编译指令。非要使用可以放在include之后
- 导入名称时,首选使用作用域解析运算符或是using声明的方法
- 对于using声明,将其作用域设置为局部而不是全局
《c++ Primer Plus 第6版》读书笔记(4)相关推荐
- Think in Java第四版 读书笔记9第15章 泛型
Think in Java第四版 读书笔记9第15章 泛型 泛型:适用于很多很多的类型 与其他语言相比 Java的泛型可能有许多局限 但是它还是有很多优点的. 本章介绍java泛型的局限和优势以及ja ...
- 《Java编程思想》读书笔记
前言:三年之前就买了<Java编程思想>这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第十七章到第十八章的内容,这一次 ...
- 深入分析Java Web技术内幕读书笔记(一)浅析Web请求过程
随着Web技术的快速发展,互联网的网络架构已经从传统的C/S架构转变为B/S架构,B/S架构相较于传统的C/S架构,有诸多优点,例如:提供了统一的操作方式,简化了用户的学习成本:便捷的开发方式大大提高 ...
- 《Java编程思想》读书笔记 第十三章 字符串
<Java编程思想>读书笔记 第十三章 字符串 不可变String String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以及包含修 ...
- Think in Java第四版 读书笔记10 第16章 数组
Think in Java第四版 读书笔记10 第16章 数组 数组和容器很像 但他们有一些差别 16.1 数组为什么特殊 数组与容器的区别主要在效率和存储类型 效率:数组是简单的线性序列 使得数组的 ...
- 鸟哥的linux私房菜-基础学习篇 读书笔记
从事linux工作一年多,算是能够熟练运用linux服务器,但仍觉得自己对Linux的原理,理论缺乏空洞,潜下心来认真阅读尘封的鸟哥经典,知识点很全,收获颇多,实践与知识结合,知行合一,对linux开 ...
- Unreal Engine 4 学习总动员读书笔记
Unreal Engine 4 学习总动员读书笔记 参考<Unreal Engine 4 学习总动员>,按照里面的录制的视频一步一步的操作,并将操作的流程和关键截图保存下来,方便后面的再次 ...
- 鸟哥的LINUX私房菜 基础学习篇 读书笔记 -- 第零章 计算机概论 (一)
鸟哥的LINUX私房菜 基础学习篇 读书笔记 -- 第零章 计算机概论 (一) 第零章 计算机概论 0.1 电脑:辅助人脑的好工具 0.1.1 计算机硬件五大组成部分 0.1.2 一切设计的起点: C ...
- 深入分析Java Web技术内幕读书笔记(二)浅析DNS域名解析过程
上一篇文章<浅析Web请求过程>讲述的是如何发起HTTP请求,对于请求发起过程中很重要的一个步骤--DNS解析过程的描述是一带而过,本篇文章将跟着DNS解析过程来分析域名是如何解析的. 一 ...
- 《Java核心技术卷一》读书笔记
<Java核心技术卷一>读书笔记 对象与类 类 类是构造对象的模板.蓝图.由类构造对象的过程称为类的实例. 对象的数据叫做实例域,操作数据的过程叫做方法 对于每个特定的类实例(对象)都要一 ...
最新文章
- 何恺明、吴育昕最新成果:用组归一化替代批归一化
- 功能整合(二):轮播图(可控)、事件流
- C 整数反转
- Traceroute笔记
- .NET Core开发实战(第18课:日志框架:聊聊记日志的最佳姿势)--学习笔记(下)...
- code iban 是有什么组成_EAN-128码和Code-128码的区别
- 微软公司等数据结构+算法面试100题2010版全部出炉
- Invoke and BeginInvoke BeginInvoke和EndInvoke方法 (转)3
- php 如果判断是utf,php 判断网页是否是utf8编码的方法_PHP教程
- 迭代器,生成器,三元表达式,列表解析式
- mysql三台部署_使用三台主机部署LNMP
- java的编译原理_Javac编译原理 - Martiny的个人空间 - OSCHINA - 中文开源技术交流社区...
- Build Setting 之 Code Signing 详解
- VSCode 返回上一个光标 (上一个浏览位置)
- 【生活】驾照C1-科一手册
- 神奇黑八传奇再现金州 勇士大胜小牛光荣晋级
- android oreo 老机型,Android Oreo 通知新特性,这坑老夫先踩了
- 使用NFC模拟校园卡门禁功能 【Mac, Windows, Android, 手环】
- Mac新手必看教程 Mac系统基本设置 苹果电脑的基本操作
- 画论65 方薰《山静居画论》