注:这一章都是理解记忆性的内容,因此笔者在某些知识点会将自己的理解话语写上,便于可读性和方便理解。
本章内容包括:
- 单独编译;
- 存储持续性、作用域和链接性;
- 定位(placement)new运算符;
- 名称空间
9.1 单独编译
1. 头文件中不要放入函数定义或变量声明
解释:如果头文件包含一个函数定义,然后属于同一程序的其他两个文件中都包含了该头文件,则同一程序中就包含了函数的两个定义,除非函数内联(inline),否则会出错。
2. 包含头文件的两种方式:< > vs " "
< >:如果文件名包含在<>中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找; " “:如果文件名包含在”“中,则编译器将首先查找当前工作目录或源代码目录(取决于编译器),如果在那没找到头文件,则将在标准位置查找。 注:因此在包含自己的头文件时,应使用引号”"而不是尖括号<>。
3. #define #ifndef #endif
解释:ifndef - if not defined。这三个如何使用,简要说明如以下三部分。
#define NUMBER 24
#define STUDY_H_
#ifndef STUDY_H_
....
#endif
#ifndef STUDY_H_
#define STUDY_H_
(place include file contents here)
#endif
9.2 存储持续性、作用域和链接性
C++使用三种不同的方案(C++11中是四种)来存储数据,这些方案的区别在于数据保留在内存中的时间。
1. 四种存储方案
1.1 自动存储持续性 在函数定义中声明的变量(包括函数的参数),其存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完成函数或代码块时,他们的内存就被释放。C++有两种存储持续性为自动的变量。 1.2 静态存储持续性 在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态,他们在整个运行过程中都存在。C++有三种存储持续性为静态的变量。 1.3 线程存储持续性(C++11) 如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。 1.4 动态存储持续性 用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时候被称为自由存储或堆。
2.作用域和链接性
作用域:名称在文件中的多大范围可见(作用范围-函数/代码块、全局) 链接性:如何在不同的单元中共享(文件间-外链接、文件中-内链接、只能当前函数/代码块使用-无链接性) 2.1 自动变量:作用域为局部,无链接性
int main()
{
int num;
int testNum = 24;
{
cout << "internal: " << endl;
int testNum = 23;
cout << num << " = num" << endl;
cout << testNum << " = testNum" << endl;
}
cout << testNum << " = testNum" << endl;
}
如上代码所示:①自动变量的初始化是不确定的;②自动变量的数目随着函数的开始和结束而增减,因此程序在运行时对其进行管理,通常是留出一块内存,并将其视栈进行管理变量的增减(这样理解之后,就很好记住自动变量这类数据是存储在栈区当中),栈如下图所示。 2.2 静态持续变量 C++为静态存储持续性变量提供了3种链接性:外部链接性(文件间:可在其他文件中访问)、内部链接性(只能在当前文件中访问)、无链接性(只能在当前函数或代码块种访问)。 2.2.1 与自动变量的区别:区别于自动变量,由于静态变量的数目在程序运行期间时不变的,因此程序不需要使用特殊装置(如栈)来管理它们。编译器通过分配固定的内存块来存储所有的静态变量,这些变量在程序执行期间一直存在(下面例子会说明),如果没有显式地初始化静态变量,其默认值会被设置为0(这也区别于自动变量的“随机数”)。 如下图中展示了静态外部变量、内部变量还有自动变量:a-静态内部变量、b静态外部变量、c自动变量。 2.2.2 静态变量的初始化:分为静态初始化(零初始化、常量表达式初始化)、动态初始化(变量在编译后初始化-个人理解为有如函数调用等类似的情况)。注:常量表达式并非只能使用字面常量的算术表达式,还可以使用sizeof运算符等。
int x;
int o = sizeof(int)
int y = 5;
long z = 13 * 13
const double pi = 4.0 * atan(1.0);
2.2.3 静态持续性、外部链接性 首先了解C++的单定义规则,即在每个使用外部变量的文件中都必须声明它,但变量只能有一次定义。为了这种需求C++提供了两种变量声明:①定义声明(简称定义),它能给变量分配存储空间;②引用声明(简称声明),它不给变量分配存储空间,因为他引用的是已有的变量,其使用关键字extern。 关键字extern的使用:如果在多个文件中使用外部变量,只需在一个文件中包含该变量的定义(单定义规则),但在使用该变量的其他文件中,必须使用extern声明它。
int num;
#include "file1.cpp"
extern int num = 24;
int main()
{
cout << num << " = num " << endl;
}
外部变量与局部变量:覆盖问题、如何选择 由如下代码块可以看出,外部变量在函数中会由局部变量所覆盖。
int num = 24;
extern int num;
void globalNum(int it);
void localNum();
using std::cout;
void globalNum(int it)
{
extern int num;
num += it;
cout << num << " = num" << endl;
cout << &num << " = address of num" << endl;
}
void localNum()
{
int num = 23;
num += it;
cout << num << " = num" << endl;
cout << &num << " = address of num" << endl;
}
那么既然能使用全局、局部,该怎么选择呢?答:通常情况下,应使用局部变量。这是由于:全局变量是所有的函数都能访问,这样造成了程序的不可靠性。程序越能避免对数据的不必要访问,就越能保持数据的完整性。 2.2.4 静态持续性、内部链接性 将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。 如下代码块所示,首先它违反了单定义规则,file2中的定义试图创建一个外部变量,因此程序将包含两个error的定义,错误。
int error = 24;
#include "file1.cpp"
int error = 23;
void errorPrint()
{
cout << error << endl;
}
再如下代码块所示,file2定义了一个静态外部变量,虽然其名称与file1中声明的常规外部变量一样,但是这种情况下静态变量将隐藏常规外部变量,不会出错。(两种链接性)
int right = 24;
#include "file1.cpp"
static int right = 23;
void rightPrint()
{
cout << right << endl;
}
2.2.5 静态持续性、无链接性 如下代码块所示,在代码块中使用static int total时,将导致局部变量的存储持续性为静态的,这意味着该变量只在当前代码块中可用,并且不处于活动状态时仍然存在,因此其与count的结果不一样。 如果初始化了静态局部变量,则程序只在启动时进行了一次初始化,以后再调用函数时,将不会像自动变量那样再次被初始化。
void test()
{
using namespace std;
static int total = 0;
int count = 0;
cout << total++ << " = total" << endl;
cout << count++ << " = count" << endl;
}
int main()
{
for(int i = 0; i < 2; ++i)
test();
}
0 = total
0 = count
1 = total
0 = count
2.2.6 说明符与限定符 auto(在C++11中不再是说明符):以前的auto用于显示地指出变量为自动存储,在C++11中使用auto关键字可以要求编译器对变量的类型进行自动推导 register: static:关键字static被用于整个文件时表示其变量是内部链接性;被用于局部声明中时,表示局部变量的存储持续性为静态的。 extern:关键字extern声明是引用声明,即声明引用在其他地方定义的变量。 thread_local(C++11新增):关键字thread_local指出变量的持续性与其所属线程的持续性相同,其变量之于线程犹如常规静态变量之于整个程序。 mutable:关键字mutable,例如在结构体中可以让const限定符后的结构体的mutable成员完成修改。 在C++中,const限定符对默认存储类型稍有影响,在默认情况下,全局变量的链接性为外部,但const全局变量的链接性为内部。所以如果程序员希望某个常量的链接性为外部,则可以使用extern来覆盖默认的内部链接性:
extern const int state = 50;
2.2.7 函数和链接性 C++不允许在一个函数中定义另一个函数,因此所有函数的持续性都自动为静态的,即在程序执行期间一直存在。**在默认的情况下,函数的链接性为外部,即可以在文件间分享,使用关键字static可以将函数的链接性设置成内部的,即只能在一个文件中使用。**此时,必须同时在原型和函数定义中使用extern。和变量一样,也存在静态函数覆盖外部定义的情况。
static int private(int num);
....
static int private(int num)
{
....
}
实际上,可以在函数原型中使用关键字extern指出函数是另一个文件中定义的,但这是可选的,这需要文件必须作为程序的组成部分被编译,或链接程序搜索的库文件。 单定义规则也适用于非内联函数,每个非内联函数程序只能包含一个定义。 针对内联函数:允许程序员将内联函数的定义放在头文件中,这样包含了头文件的每个函数都有其定义,C++要求同一个函数的所有内敛定义必须相同。 2.2.8 存储方案和动态分配 通常,编译器使用三块独立的内存:用于静态变量(可能再细分)、用于自动变量、用于动态存储。 动态内存是由运算符new、delete控制,而不是由作用域和链接性规则控制。因此,可以在一个函数重分配动态内存,而在另一个函数中将其释放。 注:通常程序结束时,由new分配的内存都将被释放,但是情况并不总是这样,在某些情况下,请求大型内存块将导致代码块在程序结束时不会被自动释放。因为最佳做法是使用delete手动释放。 ①下面介绍使用new运算符的初始化:
int *pi = new int(6);
double *pd = new double(99.99);
struct where {double x; double y; double z};
where *one = new where {1.1, 1.2, 1.3};
int *ar = new int[4] {1, 2, 3, 4};
int *pi = new int{6};
double *pd = new double{99.99};
int *pi = new(sizeof(int));
int *pi = new(40 * sizeof(int));
②new失败:new可能找不到请求的内存量,现在这将引发异常std::bad_alloc。 ③定位new运算符 new负责在堆(heap)中找到一个能够满足要求的内存块,new运算符还有个变体可以指定要使用的位置,被称为定位(placement)new运算符。
#include <new>
char buffer[50];
int main()
{
int offset = 1;
int *p = new (buffer) int[20];
...
delete[] p
}
注:delete只能用于常规new运算发分配的堆内存,而对定位new运算符,不属于其管辖内存范围。
9.3 名称空间
下面举个例子说明下名称空间简单的基本使用
namespace kobe
{
int number;
std::string last_name;
struct lakers
{
std::string fname;
std::string lname;
};
void restInPeace();
}
namespace pau
{
using namespace kobe;
struct LAKERS
{
lakers fname;
double amount;
}
...
}
#include "file1.cpp"
namespace kobe
{
using std::cout;
void restInPeace()
{
cout << "RIP, Kobe Bean Bryant " << endl;
}
}
1. using编译指令和using声明
using namespce std
int x;
std::cin >> x;
std::cout << x << std::endl;
using std::cin;
using std::cout;
using std::endl;
cin >> x;
cout << x << endl;
注: Ⅰ. 不要在头文件中使用using编译指令。首先,这样做掩盖了哪些名称可用;另外包含头文件的顺序可能影响程序的行为。如果非得要使用编译指令using,应该放其在所有预处理器编译指令#include之后。 Ⅱ. 导入名称时,首选使用作用域解析运算符或者using声明的方法。 Ⅲ. 对于using声明,首选将作用域设置为局部而不是全局。
9.4 内容归纳
- 程序多文件便写:头文件、源代码文件、main和其他使用这些自定义类型和自定义函数的函数文件;
- 自动变量、静态变量、动态变量的作用域与链接性。
- 注意extern、static、const;
- new delete(普通new、定位new运算符);
- 名称空间为了减少名称冲突,控制名称的作用域:作用域解析运算符、using声明、using编译指令。
感谢大家的浏览,如有不正确或者需要添加的地方,欢迎评论区或者私信交流。这些都是笔者一点一滴的心血,来个点赞评论收藏吧!哈哈哈如需转载及其他目的,望先告知笔者。 最后,放一张我本科时得到国奖写的个人事迹的其中一块的截图,来标记下我的第一篇博文。也希望你我在学习过程中、成长过程中都永怀一颗曼巴的心?
|