1 单独编译

程序分为三份

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

不要#include源代码文件,否则会多重声明

1.1 头文件

内容

不要把函数定义或者变量声明放到头文件中

头文件中通常包含:

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

结构声明,类声明,模板声明可以放在头文件中,因为它们不创建变量,

而是指示编译器生成和创建。

const数据和内联函数有特殊的链接属性,所以可以放在头文件中。

可以将成员放在名称空间中

引用方式

尖括号:编译器在存储标准头文件的主机系统的文件系统中查找

双引号:首先查找当前的工作目录和源代码目录,如果没找到,在标准位置找

同一个文件只能包含同一个头文件一次,否则编译错误

#ifndef COORDIN_H_
#define COORDIN_H_
//头文件内容
#endif

1.2 编译流程

以UNIX执行C为例

  1. 预处理器合并源码和包含的头文件
  2. 编译器创建合并代码的目标代码文件.o
  3. 汇编
  4. 链接程序将目标代码文件,库代码,启动代码合并,生成可执行文件.out

1.3 名称修饰

(71条消息) C+±-名字修饰_Emma-Zhang的博客-CSDN博客_c++名称修饰

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。

名字修饰(Name Mangling)是一种在编译过程中,将函数、变量的名称重新改编的机制,简单来说就是编译器为了区分各个函数,将函数通过一定算法,重新修饰为一个全局唯一的名称。

两个编译器对同一个函数可能生成不同的修饰名称,所以不同的名称会使链接器无法将编译器生成的函数调用与另一个编译器生成的函数定义匹配。

因此在链接编译模块时,请确保所有对象文件或库都是由同一个编译器生成的。

2 存储持续性,作用域,链接性

C++存储方式是通过存储持续性,作用域和链接性来描述的

2.1 存储持续性

数据保存在内存中的时间

  1. 自动存储持续性:两种:函数定义中声明的变量(包括函数参数)。

    1. 创建时机:开始执行所属函数或者代码块时
    2. 释放:执行完函数或者代码块
  2. 静态存储持续性:三种:函数定义外定义的变量和static定义的变量。
    1. 在程序整个运行过程中都存在
  3. 线程存储持续性(C++11):用关键词thread_local声明的变量。
    1. 生命周期与所属线程一样长
  4. 动态存储持续性:new分配的内存,存放在自由存储区或堆中。
    1. new分配后到delete将其释放或者程序结束

2.2 作用域

作用域(scope)描述名称在文件的多大范围可见。

局部

作用域为局部只在定义的代码块可用。

全局

作用域为全局(也叫文件作用域)的变量在定义位置到文件结尾都可用。

变量作用域

  1. 自动变量的作用域为局部
  2. 静态变量的作用域是全局还是局部取决于它是如何被定义的
  3. 函数原型作用域中使用的名称只在包含参数列表的括号内可用。
  4. 类中声明的成员的作用域为整个类
  5. 名称空间中声明的变量的作用域为整个名称空间(名称空间已经引入到了C++语言中,因此全局作用域是名称空间作用域的特例)

函数作用域

作用域可以是类或者名称空间(包括全局),但不能是局部的,不然只对自己可见。

2.3 链接性

链接性(linkage)描述了名称如何在不同单元之间共享。

链接性为外部的名称可以在文件间共享

链接性为内部的名称只能由一个文件中的函数共享

自动变量的名称没有链接性,因为它们不能共享。

3 变量的存储方式

这里介绍引入名称空间之前的情况

3.1 自动存储持续性

函数中声明的函数参数和变量:

存储持续性:自动

作用域:局部,从声明位置到代码块结束

链接性:无

分配内存的时机:执行到该代码块

同名自动变量

当两个同名的变量一个位于外部,一个位于内部时,内部的将被解释为局部代码块变量,为新定义。新定义隐藏了以前的定义,内部代码块中新定义可见,旧定义不可见。

auto关键字

C++11开始,auto用于自动类型推断,但是在C中或者C++11之前,auto用于显式地指出变量为自动存储。

register关键字

寄存器变量:register关键字是由C语言引入的,建议编译器使用CPU寄存器来存储自动变量,旨在提高访问变量的速度

在C++11以前,register表明变量用的很多,编译器可以对其做特殊处理。

C++11后只限于指出变量是自动的

保留关键字的重要原因是避免使用了该关键字的现有代码非法

3.2 静态持续变量

静态变量的数目在程序运行期间不变,所以不需要用栈管理,只需要固定内存分配,在程序执行期间一直存在。

如果没有初始化,编译器将静态变量设为0,称为零初始化的(zero-initialized)

静态存储持续性变量有三种链接性:

  1. 外部链接性:

    1. 必须在代码块的外面声明
    2. 可以在其他文件中访问
  2. 内部链接性:
    1. 必须在代码块的外面声明,必须使用static限定符
    2. 只能在当前文件中访问
  3. 无链接性:
    1. 必须在代码块中声明,并使用static限定符
    2. 只能在当前函数或代码块中访问
int global=1000;        //外部链接性静态变量
static int one_file=50;    //内部链接性静态变量
int main()
{...}
void func1()
{static int count =0;//无链接性静态变量
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuPN0hEO-1672942524360)(C:/Users/l/AppData/Roaming/Typora/typora-user-images/image-20221230205803189.png)]

关键字重载

代码块内static指存储持续性,代码块外部static表示内部链接性,这种关键词含义取决于上下文的现象叫关键词重载

静态变量初始化

静态变量都必须先零初始化,如果显式初始化了,那就有两种初始化方式

  1. 常量表达式初始化(与零初始化统称静态初始化)
  2. 动态初始化

要调用函数,必须等到函数被链接且程序执行时,是动态初始化。

但是常量初始化可以使用sizeof()和C++11关键字constexpr,这增加了创建常量表达式的方式。

3.3 外部变量

外部链接性的静态持续性的变量

链接性为外部的变量称为**(常规)外部变量**,又称为全局变量(相对于局部的自动变量),每个使用外部变量的文件必须声明它。

单定义规则

C++有单定义规则:(one definition rule,ODR)变量只能有一次定义。

C++提供两种变量声明:

  1. 定义声明/定义:给变量分配存储空间
  2. 引用声明/声明:不给变量分配存储空间,引用已有的变量,使用extern,且不能进行初始化

要在多个文件使用外部变量,需要在其他文件用extern声明。

定义与全局变量同名的局部变量后,局部变量将隐藏全局变量。

作用域解析运算符

C++提供作用域解析运算符::,放在变量名前,表示使用变量的全局版本

3.4 静态外部变量

指内部链接性的静态持续性的变量

static限定符用于作用域为整个文件的变量,该变量的链接性变为内部,称为静态外部变量

如果在文件中定义了一个静态外部变量,与另一个文件的常规外部变量重名,则该文件中隐藏常规外部变量

用处:在同一个文件中的多个函数间共享数据

3.5 无链接的静态变量

指无链接性的静态持续性的变量

函数内的静态变量只在程序开始运行时被设置为0,当再次调用函数时不会再次初始化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TcGnLnFB-1672942524361)(C:/Users/l/AppData/Roaming/Typora/typora-user-images/image-20221231162728980.png)]

int a=1000;     //外部链接性静态变量
static int b=50;   //内部链接性静态变量
extern int c=20;
int main()
{...}
void func1()
{static int g =0;//无链接性静态变量
}

4 存储说明符和cv-限定符

存储说明符(storage class specifier)有

  1. auto
  2. register
  3. static
  4. extern
  5. thread_local
  6. mutable

cv-限定符(cv-qualifier)(cv取两个单词的首字母)有

  1. const
  2. volatile

static关键字

作用域为整个文件时,表示内部链接性;局部声明中,表示存储持续性。

  1. 修饰局部变量时,使得该变量在静态存储区分配内存;只能在首次函数调用中进行首次初始化,之后的函数调用不再进行初始化;其生命周期与程序相同,但其作用域为局部作用域,并不能一直被访问;
  2. 修饰全局变量时,使得该变量在静态存储区分配内存;在声明该变量的整个文件中都是可见的,而在文件外是不可见的;
  3. 修饰函数时,在声明该函数的整个文件中都是可见的,而在文件外是不可见的,从而可以在多人协作时避免同名的函数冲突;
  4. 修饰成员变量时,所有的对象都只维持一份拷贝,可以实现不同对象间的数据共享;不需要实例化对象即可访问;不能在类内部初始化,一般在类外部初始化,并且初始化时不加static;
  5. 修饰成员函数时,该函数不接受this指针,只能访问类的静态成员;不需要实例化对象即可访问。

C++中static作用和使用方法

extern关键字

引用声明,声明引用在其他地方被定义的变量,见”外部变量“。

thread_local关键字

同一个声明中不能使用多个说明符,除了thread_local,可以和staticextern结合使用

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

const关键字

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

const char * const months[12]=
{"January","February",···
};

第一个const防止字符串被修改,第二个const确保数组每个指针始终指向它最初指向的字符串。

const全局变量

C++中,const全局变量的链接性为内部的,如同使用了static。这样每个文件都有头文件中定义的const变量但是不会因为单定义规则而冲突。

如果希望定义在函数外部的const变量的链接性为外部的,需要用extern关键字来覆盖。而且必须在所有使用变量的文件中用extern声明。

不管如何,只有一个文件能初始化。

//使用const
//1.cpp
extern const int states=50;
//2.cpp
extern const int states;//不使用const
//1.cpp
int states=50;
//2.cpp
extern int states;

代码块中的const

作用域为代码块

volatile关键字

volatile:不稳定的

关键词volatile表明:即使程序代码没有对内存单元进行修改,值也可能变化。

使用场景:

  1. 指针指向硬件,取的值为串行端口的信息,硬件可能对其进行修改
  2. 两个程序可能相互影响共享数据
  3. 防止编译器错误优化:如果程序短期多次使用某个变量,编译器可能不会查找两次,而是把值存在寄存器,加上volatile则编译器不会进行这种优化

mutable关键字

mutable:会变的,可变的

关键词mutable指出:即使结构/类为const,某个成员也可以被修改

struct data
{char name[30];mutable int accesses;
};
const data a ={"hi",0};
strcpy(a.name,"hello");   //不允许
a.accesses++;         //允许

5 其他链接性

5.1 函数的链接性

存储持续性

C++和C都不允许在一个函数中定义另一个函数,因此所有函数的存储持续性都自动为静态的,在程序执行期间一直存在。

默认链接性

  1. 默认情况下,函数链接性为外部,可以在文件间共享
  2. 可选:在函数原型中用extern,表示函数的定义在另一个文件
  3. 要让程序在另一个文件查找函数,另一个文件必须作为程序组成部分被编译,或者是链接程序搜索的库文件

静态链接性

  1. static可以将函数的链接性设为内部,必须在原型和函数定义中使用该关键词
  2. 在定义静态函数的文件中,静态函数将覆盖外部同名定义
  3. 使用函数的文件都应该包含函数原型,只有一个能包含函数定义,除了内联函数。所以内联函数的定义能放在头文件。

如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数,但是C++保留了标准库函数的名称。

5.2 语言链接性

C语言中一个名称只对应一个函数,C++同一个名称可能对应多个函数,必须翻译为不同的符号名称,因此通过C++语言链接来实现。

C++语言链接:为函数名称翻译成不同的符号名称,执行名称矫正或者名称修饰。

涉及链接程序对函数的处理。见书262

6 动态内存

动态内存由new和delete控制,而不是由作用域和链接性规则控制。可以在一个函数中分配,另一个函数中释放。

存储方案不适用于动态内存,但适用于用来跟踪动态内存的自动和静态指针变量。

float * p_fees =new float [20];

由new分配的动态内存会一直保留,但是声明的语句块执行完后,指针将消失,所以必须在代码结束后返回地址,不然无法使用该内存。

6.1 分配内存

int *pi=new int (6);
double *pd=new double (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 对数组 列表初始化
double *pd =new double {99.99};//C++11 对单值变量 列表初始化

分配函数 new()

void * operator new(std::size_t);

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

使用运算符重载。size_t是个typedef,对应合适的整型

int * pi=new int;被转化为int * pi=new(sizeof(int))

分配函数和释放函数是可替换的,可以为new和delete提供替换函数

分配失败

最初返回空指针,现在引发异常std::bad_alloc

6.2 内存释放

程序结束后,new分配的内存通常将被释放,但请求大型内存块将导致代码块在程序结束不会被自动释放

这时需要delete

释放函数delete()

void operator delete(void *);

void operator delete[](void *);

6.3 定位new运算符

具体参考书P264

new运算符处理在堆中找内存块还有一种变体,让程序员可以指定要使用的位置,这样可以处理通过特定地址进行访问的硬件或者在特定位置创建对象。

与常规new的区别

常规new调用一个接受一个参数的new()函数,而定位new调用的接受两个参数

头函数:#include

使用定位new,变量后面可以有方括号也可以没有

可以用于结构和对象等

用法

可以用静态数组为定位new提供内存空间

char buffer[500];
int main(){int *p;p=new (buffer) int[20];
}

工作原理

默认定位new函数:返回传递给定位new运算符的地址,并强制转化为void *,这样可以赋给任何指针类型

C++允许重载定位new函数,但定位new是不可替换的

7 名称空间

为了防止两个库的名称冲突,需要用名称空间控制名称的作用域

7.1 传统的名称空间

声明区域|潜在作用域|作用域

声明区域:可以在其中进行声明的区域

潜在作用域:变量的潜在作用域从声明点开始,到其声明区域的结尾,潜在作用域比声明区域小

作用域:变量对程序可见的范围被称为作用域

7.2 名称空间

命名的名称空间

特征

  1. 名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块
  2. 名称空间中,声明的名称的链接性默认为外部
  3. 任何名称空间中的名称都不会与其他名称空间中的名称发生冲突
  4. 名称空间是开放的,可以把名称加入到已有的名称空间中,可以将函数原型和定义分在两个名称空间中写

用作用域解析运算符来使用名称空间来限定名称,未被修饰的叫未限定的名称,包含名称空间的名称叫限定的名称

7.3 using声明

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

using声明会把特定的名称添加到所属的声明区域内

在函数中添加到局部声明区域,覆盖同名的全局变量;在函数外添加到全局名称空间中

using声明对于函数如果只给出了

namespace Jill
{double fetch;
}
char fetch;
int main()
{using Jill::fetch;//double fetch;      //错的,已经定义了fetchcin>>fetch;         //读取Jill::fetchcin>>::fetch;      //读取全局的fetch
}

7.4 using 编译指令

using声明使得一个名称可用,而using编译指令使得所有的名称都可用

using编译指令由名称空间名和using namespace组成

全局声明区域内使用using namespace,使得名称空间的全部名称全局可用

函数中使用using编译指令,则函数中可用

函数重载与using

C++using声明和using指示_莲娃的博客-CSDN博客_using

两者比较

同:using编译指令和using声明存在可能的二义性,比如两个名称空间都定义了同样的名称,这时候编译器不允许同时使用这两个using声明


异:using声明与声明类似,如果函数中已经声明,则不能用其导入相同的名称

using编译指令类似于作用域解析运算符,让函数内部将其视为函数之外声明的。即如果已经有局部声明,则名称空间名被隐藏,但是仍然可以通过::变量名取得全局的对应变量,名称空间::变量名取得名称空间的对应变量

一般认为using声明更加安全,因为编译指令如果重名,编译器不会警告,而且使用名称空间的变量也没有明确特征和说明

using namespace std; //减少这么使用int x;                  //用法1
std::cin>>x;using std::cin;           //用法2
int x;
cin>>x;

名称空间的嵌套

对于:

namespace elements
{namespace fire{int flame;}
}

使用:using namespace elements::fire

再例如:

namespace myth
{using Jill::fetch; //Jill名称空间内的fetch变量using namespace elements;
}

直接使用:myth::fetch访问即可

using编译指令可传递,使用using namespace myth;相当于同时还使用了using namespace elements;

名称空间别名

namespace m=myth;
namespace MEF=myth::elements::fire;
using MEF::flame;

未命名的存储空间

相等于链接性为内部的静态变量

namespace
{int counts
}
//等于
static int counts2;int main()
{...}

8 C++的内存分区

编译器使用前三块独立内存,程序在此基础上还需要代码区:

  1. 静态变量:全局区
  2. 自动变量:栈区
  3. 动态存储:堆区
  4. 代码区:用来存放函数体的二进制代码

数据区包括:堆,栈,静态存储区。
静态存储区包括:常量区(静态常量区),全局区(全局变量区)和静态变量区(静态区)。
常量区包括:字符串常量区和常变量区。
代码区:存放程序编译后的二进制代码,不可寻址区。

9 小结

对于大型编程项目的管理:

  1. 尽量使用在已命名的名称空间中声明的变量,而不是使用外部/静态全局变量
  2. 类库和函数库都放在名称空间中
  3. 少用using编译指令,并且不在头文件中用
  4. 首选作用域解析运算符和using声明
  5. 对于using声明,将定义域尽量设置为局部而不是全局
  6. 一句话:能用局部using声明就别用别的

当然,一个文件的程序不受此限制

10 留给读者的问题

  1. p273 为什么使用using声明导入了debts对pers的编译指令?

感谢看到这里,写到凌晨两点,麻烦点下赞谢谢

【格蕾读C++ Primer Plus】第九章 内存模型和名称空间相关推荐

  1. 2020 我的C++的学习之路 第九章 内存模型与名称空间

    以C++ Primer Plus为参考书籍,自身归纳知识点,加深记忆. 内存模型与名称空间 存储持续性 作用域与链接 自动存储持续性 静态持续变量 静态持续性.外部链接性 静态持续性.内部链接性 静态 ...

  2. 第9章 内存模型和名称空间

    待定 本章内容:  单独编译  存储持续性.作用域和链接性  定位(placement)new运算符  名称空间 9.1 单独编译 9.2 存储持续性.作用域和链接性 9.2.4 静态持续性. ...

  3. 【C++ Primer Plus】第9章 内存模型和名称空间

    9.1 多文件程序 一个文件(头文件)包含了用户定义类型的定义:另一个文件包含操纵用户定义类型的函数的代码.这两个文件组成了一个软件包,可用于各种程序中. 头文件中常包含的内容: 函数原型. 使用#d ...

  4. 【C++】C++PrimerPlus(第6版)中文版 第9章 内存模型和名称空间 编程练习 参考答案

    自己编写的参考答案,在VS2019中都可以编译通过,不是标准答案,也不是最优答案,仅供参考 1.下面是一个头文件 //golf.h – for pr9-1.cpp const int Len = 40 ...

  5. 《C++ Primer Plus》读书笔记之七—内存模型和名称空间

    第九章 内存模型和名称空间 1.不要将函数定义或者变量声明放到头文件中. 2.头文件常包含的内容:函数原型.使用#define或者const定义的常量.结构声明.类声明.模板声明.内联函数. 3.避免 ...

  6. static在内存层面的作用_「C++ Primer plus 心得」9.内存模型和名称空间

    本章内容包括: 单独编译 存储持续性.作用域和链接性 定位new运算符 名称空间 C++ 为在内存中存储数据方面提供了多种选择.可以厅数据保留在内存中的时间长度(存储持续性)以及程序的哪一部分可以访问 ...

  7. 《C++ Primer Plus(第六版)》(13)(第九章 内存模型和命名空间 笔记)

    1.为了保持通用性,C++使用术语翻译单元,而不是文件,文件不是计算机组织信息时的唯一方式. 2.C++允许编译器使用自己的名称修饰,因此不同的编译器编译出来的二进制模块(对象代码文件),通常是无法链 ...

  8. C++ Primer Plus 第九章答案 内存模型和名称空间

    9.5复习题 //1 homer将自动成为自动变量 在一个文件中将secret定义为外部变量,并在第二个文件中使用extern声明它 在外部定义前加上关键字static,将topsecret定义为一个 ...

  9. C++ Primer Plus学习(九)——内存模型和名称空间

    内存模型和名称空间 单独编译 存储持续性.作用域和链接性 名称空间 单独编译 程序一般可以分为三部分: 头文件:包含结构声明和使用这些结构的函数的原型: 源代码文件:包含与结构有关的函数的代码: 源代 ...

  10. C++ Primer Plus 第九章编程题练习

    C++ Primer Plus 第九章编程题练习 第一题 题目描述 下面是一个头文件: const int Len = 40;struct golf {char fullname[Len];int h ...

最新文章

  1. SurfaceView 间取得焦点
  2. WPF的Timer控件的使用
  3. C# 多线程并发锁模式-总结
  4. 【Arduino】按键按下执行不同模式程序
  5. spring事务模板使用
  6. 51Nod 1439 - 互质对(容斥+莫比乌斯函数)
  7. centos mysql pmm_【MySQL】MySQL监控利器PMM
  8. 最近ゲームにはまってる。
  9. 冠榕智能灯光控制协议分析(controller init)
  10. 二叉排序树查找的c语言程序,C语言二叉排序(搜索)树实例
  11. 2019公需科目快速学完_【1017丨话题】励志!69岁大爷驾校学车走红,“科目二有信心一次过quot;...
  12. 用PS为网迅安卓公司设计基于安卓平台的手机应用图标
  13. 【汇编语言与计算机系统结构笔记07】条件码,比较、测试、条件跳转与条件转移指令,结合微体系结构与流水的说明
  14. 关于@Vaild注解的使用
  15. 单片机流水灯三种实现
  16. js屏蔽键盘esc键
  17. 2022-0828-0640,中文分词,逆向最大匹配算法 RMM
  18. 用户行为分析的基本概览和常用名词解释
  19. zigbee基本概念及组网特性
  20. V-REP笔记:重力加速度的修改

热门文章

  1. 音乐转换成16进制写进单片机的方法(音符频率和音符的播放长度时间计算)
  2. hunt和sniffit 的一些用法(非unix人员勿入)
  3. 个人第一次作业:阅读与准备作业
  4. golang使用gorm出现reflect.Value.Addr of unaddressable value [recovered]
  5. 什么软件可以测试网络的稳定性,手机怎么测试网络稳定性
  6. 计算机应用基础教研设想,计算机应用基础、信息技术与课堂教学深度融合,这所学校这样做...
  7. directadmin php5.6,directadmin教程
  8. python写歌词解析器 王菲-传奇
  9. mysql initialize 什么意思_mysql initialize
  10. python中as是什么意思_python中with python中with as 是什么意思刚入门求解释!!!