0318
C++ Primer Plus - 第九章
- 第八章 函数探幽
- 第九章 内存模型和名称空间
- 第十章 对象和类
第八章 函数探幽
第九章 内存模型和名称空间
9.1 单独编译
大多数C++环境都提供了其他工具来帮助管理,UNIX和Linux系统都具有make程序
,可以跟踪程序以来的文件以及这些文件的最后修改时间。运行make时,如果它检测到上次编译后修改了源文件,make将记住重新构建程序所需的步骤。
头文件 #include "123.h"
原来的一个程序可以分成三部分:
- 头文件:包含结构体声明和使用结构体的函数的原型;
- 源代码文件:包含(与结构体有关的)函数的代码(函数定义);
- 源代码文件:包含调用(与结构体有关的)函数的代码。
头文件中常包含的内容:
- 函数原型;
- 使用#define 或 const定义的符号常量;
- 结构体声明;
- 类声明;
- 模板声明;
- 内联函数。
为什么是双引号,不是尖括号?
在包含头文件时,我们使用"123.h"
,而不是<123.h>
。
因为如果文件名包含在尖括号中,则C++编译器将在(存储标准头文件的)主机系统的文件系统中查找;
但如果文件名包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录。
头文件管理 #ifndef
(if not defined)
在同一个文件中,只能将同一个头文件包含一次,但是很可能在不知情的情况下将头文件包含多次。力图,可能使用包含了另外一个头文件的头文件。有一种标准的C/C++技术可以避免多次包含同一个头文件,它是基于预处理器编译指令#ifndef
的(if not defined)。
头文件coorden.h
:
#ifndef COORDIN_H_
#define COORDIN_H_ //定义名称COORDIN_H_
struct polar{
double distance;
double angle;
};
struct rect{
double x;
double y;
};
polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
#endif
编译器首次遇到该文件时,名称COORDIN_H_
没有被定义,编译器将查看#ifndef
和#endif
之间的内容,并读取定义COORDIN_H_
的一行。如果在同一个文件中遇到其它包含coordin.h
的代码,编译器将知道COORDIN_H_
已经被定义了,从而跳到#endif
后面。
注意:这种方法并不能防止编译器将文件包含两次,只是让它忽略除第一次包含之外的所有内容。大多数标准C/C++头文件都是用这种防护方案。
9.2 存储持续性、作用域和链接性
C++笔记3:C++核心编程 --> 1、内存分区模型
C++ Primer Plus(嵌入式公开课)—第4章 复合类型–>4.8.5 自动存储、静态存储和动态存储
9.2.0 存储持续性
- 自动存储持续性:
函数参数
、函数定义中声明的变量(局部变量
) 的存储持续性是自动的。它们在程序开始执行其所属的函数或者代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。栈
- 静态存储持续性:在函数定义外定义的变量(
全局变量
)、使用关键字static定义的变量(静态变量
) 的存储持续性是静态的。它们在程序的整个运行过程中都存在。 - 动态存储持续性:用
new
运算符分配的内存将一直存在,直到使用delete
运算符将其释放或者程序结束为止。这种内存的存储持续性是动态的,被称为自由存储
或堆
。 - 线程存储持续性(C++11):如果变量使用关键字
thread_local
声明,则其生命周期与所属的线程一样长。
9.2.1 作用域与链接
作用域(scope)描述了名称在文件的多大范围内可见:
- 作用域为局部的变量只能在定义它的代码块中使用;—代码块
- 作用域为全局的变量在定义位置到文件结尾之间都可用;—单个文件
- 自动变量的作用域为局部;—代码块
- 静态变量的作用域是全剧还是局部取决于它是如何被定义的;—static
- 在函数原型中定义的名称只在包含参数列表的括号内可用,这就是为什么 这些名称是什么以及是否出现 都不重要的原因;
- 在类中声明的成员的作用域为整个类;
- 在名称空间中声明的变量的作用域为整个名称空间。
链接性(linkage)描述了名称如何在不同单元间共享:
- 链接性为外部的名称可
在文件之间共享
; - 链接性为内部的名称只能由
一个文件中的多个函数共享
; - 自动变量的名称没有链接性,因此他们
不能共享
,只能在它所属的函数或者代码块中使用
。
文件、程序、代码块、函数
代码块:由花括号括起的一系列语句。例如,函数体就是代码块,但可以在函数体中嵌入其他代码块。
文件: 123.h 123.cpp main.cpp 头文件 源程序文件。
程序:一个程序可由多个文件组成。
9.2.2 自动存储持续性(局部变量)
局部变量:函数中声明的函数参数
和变量
。存储在栈
区。
- 存储持续性:自动
- 作用域:局部(函数或代码块内)
- 链接性:没有链接性
C++11 中的 auto
和 register
见9.2.7 说明符和限定符。
局部变量存储在栈区中
程序使用两个指针来跟踪栈:
- 一个指针指向
栈底
—栈的起始位置; - 另一个指针指向
栈顶
—下一个可用内存单元。
当函数被调用时,其自动变量将被加入到栈中,栈顶指针指向变量后面的下一个可用的内存;
函数结束时,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存。这些新变量并没有被删除,只是不再被标记,他们所占据的空间将被后来加入到栈中的局部变量所覆盖。
9.2.3 静态持续变量—关键字static
静态变量:函数中声明的函数参数
和变量
。存储在栈
区。
- 存储持续性:静态,在整个程序执行期间一直存在。
- 作用域:多个文件、单个文件、函数或代码块内
- 链接性:外部链接性、内部链接性、没有链接性
未初始化的静态变量的所有位都被设置为0。
外部链接性:可在其他文件中访问;
内部连接性:只能在当前文件中访问;
无链接性:只能在当前函数或代码块中访问。
创建链接性为外部的静态持续变量,必须在代码块的外面声明它;
创建链接性为内部的静态持续变量,必须在代码块的外面声明它,并使用static
限定符;
创建没有链接性的静态持续变量,必须在代码块内声明它,并使用static
限定符。
变量名 | 作用域 | 链接性 | 存储持续性 |
---|---|---|---|
global | 多个文件 | 外部 | 静态 |
ont_file | 单个文件(当前文件) | 内部 | 静态 |
count | 代码块/函数 | 无链接性 | 静态 |
llama | 代码块/函数 | 无链接性 | 自动(局部变量) |
变量count 和 llama的区别:(9.2.6 静态局部变量 和 局部变量 的区别)
- 相同点:二者都只能在
funct1()
中使用; - 不同点:即使
funct1()
函数没有被调用,count
也留在内存中;而llama
只有在funct1()
函数被调用时才会为其分配内存,当函数结束时,此内存会被释放。
☆☆☆☆☆总结:常规外部变量、静态外部变量、静态局部变量、局部变量
1.常规外部变量(函数外定义,不加static),全局变量,多个文件
可访问—9.2.4
2.静态外部变量(函数外定义,加static),全局变量,当前文件
可访问;—9.2.5
3.静态局部变量(函数内定义,加static),全局变量,代码块内
有效;—9.2.6
4.局部变量(函数内定义,不加static),局部变量,代码块内
有效。
其中123是静态变量,4是自动变量,还有一个9.2.10的动态内存。
全局变量是静态变量,存储在全局区;局部变量是自动变量,存储在栈区。
变量 | 存储描述 | 作用域 | 链接性 | 存储持续性 | 如何声明 |
---|---|---|---|---|---|
局部变量 | 自动 | 代码块/函数 | 无链接性 | 自动 | 在代码块中 |
局部变量 | 寄存器 | 代码块/函数 | 无链接性 | 自动 | 在代码块中,使用关键字register |
静态(局部)变量 | 静态,无链接性 | 代码块/函数 | 无链接性 | 静态 | 在代码块中,使用关键字static |
常规外部变量 | 静态,外部链接性 | 多个文件 | 外部 | 静态 | 代码块的外面 |
静态(外部)变量 | 静态,内部链接性 | 单个文件(当前文件) | 内部 | 静态 | 代码块的外面,使用关键字static |
9.2.4 静态持续性、外部链接性—关键字extern
单定义规则:变量只能有一次定义。
使用关键字extern
前提是静态变量,具有外部链接性,即在代码块的外面声明的变量。看个示例:
9.5.cpp
//程序清单9.5:
#include<iostream>
using std::cout; using std::endl;
double warming = 0.3;//①链接性为外部的静态变量
void update(double dt); //②
void local(); //③
int main(){
cout << "main warming = " << warming << endl;//0.3
update(0.1);//④
cout << "main warming = " << warming << endl;//0.4
local(); //⑤
cout << "main warming = " << warming << endl;//0.4
return 0;
}
9.6.cpp
//程序清单9.6:
#include<iostream>
using std::cout; using std::endl;
extern double warming;//⑥
void update(double dt);
void local();
void update(double dt){
extern double warming;//⑦这行代码可有可无
warming += dt;
cout << "update warming = " << warming << endl;//0.4
}
void local(){
double warming = 0.8;//⑧
cout << "local warming = " << warming << endl;//0.8
cout << "local ::warming = " << ::warming << endl;//⑨0.4
}
分析:
1.定义一个全局变量warming,链接性为外部,即可被多个文件访问;
2.3.函数声明;—这个不可少
4.5.函数调用;
6.使用关键字extern
声明变量warming
,让该文件中的函数都能够使用它;
7.再次使用关键字extern声明变量warming,这句可有可无,因为已经有⑤了。
8.在local()
函数中定义了一个与全局变量warming同名的局部变量
,因此在此函数中,这个局部变量将隐藏全局变量;但依然可以利用::
来调用全局变量(下面第8点);
9.利用作用域解析运算符::
表示使用变量的全局版本–>9.3 的 局部名称隐藏名称空间名?。
结果:
多文件编译 g++ 9.5.cpp 9.6.cpp
☆☆☆
g++ 9.5.cpp 9.6.cpp
./a.cout
如果只在含有main函数的程序中按F5,就会编译出错或者没结果,因为这样只会编译当前文件,而不会链接到其他文件。
作用域解析运算符::
C++提供了作用域解析运算符::
。放在变量名前面时,该运算符表示使用变量的全局版本
。
总结:
在多文件程序中,可以在一个文件(且只能在一个文件)中定义一个外部变量
—单定义规则。
其他文件要使用该变量,必须使用关键字extern
声明它。
9.2.5 静态持续性、内部链接性
如果文件1中定义了一个常规外部变量errors
(外部链接性),
而文件2中定义了一个同名的静态(外部)变量
(内部链接性),则在文件2中,静态(外部)变量将隐藏常规外部变量。
看个示例:
9.7.cpp
//程序清单9.7:
#include<iostream>
using std::cout; using std::endl;
int tom = 3; //常规外部变量---全局变量,多个文件可访问
int dick = 30; //常规外部变量---全局变量,多个文件可访问
static int harry = 300; //静态外部变量---全局变量,当前文件可访问 = 300
void remote_access();
int main(){
cout << "main: tom = " << tom << "; &tom = " << &tom << endl;
cout << "main: dick = " << dick << "; &dick = " << &dick << endl;
cout << "main: harry = " << harry << "; &harry = " << &harry << endl;
remote_access();
return 0;
}
9.8.cpp
//程序清单9.8:
#include<iostream>
using std::cout; using std::endl;
extern int tom; //使用文件1中定义的常规外部变量---全局变量,多个文件可访问
static int dick = 10; //静态外部变量---全局变量,当前文件可访问,并且会覆盖 = 30
int harry = 200; //常规外部变量---全局变量,多个文件可访问
void remote_access(){
cout << "remote_access: tom = " << tom << "; &tom = " << &tom << endl;//
cout << "remote_access: dick = " << dick << "; &dick = " << &dick << endl;
cout << "remote_access: harry = " << harry << "; &harry = " << &harry << endl;
}
分析:
1.在9.7.cpp中定义并初始化的变量tom
是链接性为外部的静态变量,在9.8.cpp中想用,就需要加个关键字extern
;
2.在9.7.cpp中定义并初始化的变量dick
是链接性为外部的静态变量,在9.8.cpp中想用,就需要加个关键字extern
;但是9.8.cpp中也定义了一个同名的链接性为内部的静态变量,所以在9.8.cpp中静态(外部)变量就覆盖了常规外部变量;
3.同理,在9.8.cpp中定义并初始化的变量harry
是链接性为外部的静态变量,在9.7.cpp中想用,就需要加个关键字extern
;但是9.7.cpp中也定义了一个同名的链接性为内部的静态变量,所以在9.7.cpp中静态(外部)变量就覆盖了常规外部变量。
结果:
结论:
如果有作用域范围更小的变量,就把作用于更大的变量给覆盖了,类似于前面的局部变量覆盖全局变量。
9.2.6 静态存储持续性、无链接性
之前的一个示例:(8.8 编程练习 第1题:static变量记录函数被调用的次数)
#include<iostream>
using std::cout; using std::cin; using std::endl;
void func1();//返回函数被调用的次数
int main(){
func1();
func1();
func1();
func1();
func1();
return 0;
}
void func1(){
static int count = 0;//静态局部变量
int num = 0;//局部变量
num += 2;
cout << "num = " << num << "; ";
count++;
cout << "函数被调用第 count = " << count << "次。" << endl;
}
结果: