单独编译
对于一个多文件程序,编译器首先合入引用的文件,然后编译每个源文件,生成目标文件,最后将目标文件链接到一起生成可执行文件。
头文件中常包含的内容:
1 函数原型
2 使用#define或者const定义的符号常量
3 结构声明
4 类声明
5 模板声明
6 内联函数
此处可以这样理解,头文件问包含的代码,只是用来告诉编译器,编译后续代码需要使用到的东西,例如函数原型,类、结构声明,模板声明。这些代码并不是在编译后真正生成的代码。内联函数和#define或者const定义的符号常量例外。
程序编译的步骤:
1 预处理器将包含的文件与源代码文件合并。
2 编译器创建每个源代码文件的目标代码文件。
3 连接程序将目标代码文件、库代码和启动代码合并,并生成可执行文件。
存储持续性、链接性、作用域
存储类别: 自动存储持续性、静态存储持续性、线程存储持续性、动态存储持续性。
作用域:描述变量在文件的多大范围内是可见的。
链接性:描述变量在不同文件间共享。
自动存储持续性:
在函数内(大括号内)申请的参数和变量为自动,其作用域为局部,函数内。没有链接性。
{
int a = 1;
{
int a = 2;
}
}
此种情况下,运用内部代码块时,同名a会屏蔽外部a,程序使用的是内部a = 2。
退出内部代码块时,外部a = 1变为可见
auto 关键字在c语言中,只是标记此变量是一个局部自动变量,在C++11 后,才是建立一个auto 类型的变量。
自动变量和栈:
编译器为函数创建一个LIFO栈,有一个栈顶指针和栈底指针,栈底指针指向栈开始位置;栈顶指针指向下一个可用内存单元,当执行函数时,创建的自动变量被加入到栈顶指针位置,栈顶指针移动到变量的后一个可用单元。函数调用结束后,栈顶指针被重置到调用前位置。
下图摘自原书:
寄存器变量:
c中寄存器变量 register int conut; 表示建议编译器使用CPU寄存器存储该变量。
C++中取消此用法,只是显式指出是自动变量。
静态持续变量:
静态存储变量包括全局变量、static 标记的外部变量和static标记的内部变量
提供了三种链接型:1 外部链接性(可在其他文件访问)2 内部链接性(只能文件内访问)3 无连接性(局部变量)
int a = 10; // 全局变量,具有静态存储性,生存周期为整个程序,在静态存储区保存。外部文件可
// 以访问
static int b = 10;// static 全局变量,外部文件不能访问。
void text_fun()
{
static int c = 10; //无链接性,具有静态存储性,生存周期为整个程序,在静态存储区保存
}
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
自动(局部变量) | 自动 | 代码块 | 无 | 在代码块中 |
寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用关键字register |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字static |
静态,外部链接性 | 静态 | 文件 | 内部 | 不在任何函数中 |
静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字static |
静态持续性与外部链接性:
单定义规则:C++ 具有单定义规则,变量只能定义一次。
两种声明规则:
定义声明(定义):就是定义一个变量,会分配存储空间。
引用声明( 引用):不分配空间,只是引用已有的。
引用声明使用关键字extern: extern int a; 声明int型a是一个外部变量,再其他文件。
且此声明不能被初始化,extern int a = 1;就不是引用声明了,会分配个新的存储空间。
用法: 在一个文件定义变量,其他文件使用关键字extern 声明。
局部变量会隐藏同名全局变量,此时局部变量用extern修饰时,表示的是全局变量
int a = 10;
void text_fun()
{
int a = 2;
cout << a; //结果为2 ,因为a此时是一个局部自动变量,会屏蔽全局a,
}
void text_fun2()
{
extern int a = 2;
cout << a; //结果为10 ,因为a代表全局变量a。
}
静态持续性与内部链接性:
如果两个文件都有一个同名变量int a = 1;是不合法的,违背了单定义规则;
如果变量被static修饰,static int a = 1;说明变量是一个链接性为内部的全局变量,将屏蔽其他文件定义的变量a。
总结:
1 如果需要一个变量被其他文件引用,定义为普通全局变量int a = 10;其他文件中引用此变量 extern int a;(注意不能初始化);
2 如果定义变量不需要其他文件引用,定义为static int a = 10;这样其他文件无法引用,不用考虑是否冲突。
3 函数中的临时自动变量用extern 修饰,表示显式使用外部全部变量,不会把全局变量覆盖。
说明符和限定符:
说明符:
auto: 自动类型推断。
register:C中说明建议使用寄存器存储,C++中,显式说明为自动变量
static: 说明变量为静态的,存储再静态存储区,如果是全局变量,链接性为文件内部,外部不可见。
extern:声明变量是外部的。(注意extern 声明的变量C++中不能被初始化)
thread_local: 指出变量的持续性与其所属的线程的持续性相同。
mutable:
限定符:
const:说明变量为常量。在C++中,const会改变变量的链接性,链接性将是内部的,外部变量将引用不到(和static一样效果)。C++这样做的目的是可以将const声明的常量放到头文件中,不同的文件都可以引用,因为链接性为内部,所以不违背单定义规则。注意,每个文件中定义的const常量都占用不同的内存空间。
另外可以使用extern关键字覆盖默认的内部链接性 extern const int a = 0;其他文件可已通过extern const int a;引用。
volatile:用于告诉编译器,一个变量的值可能会在意料之外的情况下发生改变,因此在编译器优化过程中,不应该对这个变量进行某些特定的优化。 表明即使代码没有改变内存单元的值,其值也可能改变(外部系统,如串口)。针对此种变量,编译器不会主动进行优化。例如编译器不会将值放在临时寄存器中,因为此值每次使用可能不同。
mutable:用于修饰类的非静态成员变量。当一个成员变量被声明为mutable
时,意味着即使在一个const
成员函数中,该成员变量的值仍然可以被修改。通常情况下,const
成员函数表示该函数不会修改对象的状态,即它不会修改任何非静态成员变量。但是,有时候我们可能需要在const
成员函数中修改某些特定的成员变量,这时就可以使用mutable
来标记这些成员变量。
函数和链接性:
普通函数的链接性
void text_fun(); 链接性是外部的:
外部文件可以通过引用声明引用此函数 extern void text_fun(); 注意 extern 可以省略。
外部文件想引用此函数,每个文件都需要声明,之后才可以使用。
函数内部链接性:
如果适用static 修饰函数: static void text_fun(); 则此函数将只具有内部链接性,外部文件不能引用,将会屏蔽外部同名函数。具有单定义规则,其他文件可以定义相同名字函数。
内联函数不受单定义规则影响,所以可以放在头文件中,引用到不同的源文件。
编译器查找函数的规则:如果是静态函数,将在本文件中查找,如果是非静态函数,将在整个函数中查找,如果找到两个同名的非静态函数,则报错,如果都没有将在库文件中查找。
语言链接性:
如果再C++中使用C语言,需要显式声明。
extern "C" void spiff(int);
存储方案和动态分配:
基础类型可以用小括号或者大括号初始化。
cout << "start_new_fun_text" << endl;
int* ia = new int(6); // 用小括号初始化
delete ia;
cout << "start_new_fun_text ia:" << *ia << endl;
int* ib = new int{7}; 用大括号初始化
delete ib;
cout << "start_new_fun_text ib:" << *ib << endl;
数组或者结构必须使用大括号初始化
int* ip = new int[3] {1, 2, 4};
cout << "start_new_fun_text *ip:" << *ip << endl;
cout << "start_new_fun_text *(ip+1):" << *(ip+1) << endl;
cout << "start_new_fun_text *(ip+2):" << *(ip+2) << endl;
delete ip;
定位new运算符:
如果希望new运算符申请指定内存块中的内存,可以使用定位new运算符。
int arr[100] = {0};
int* p = new(arr)int(8); //从arr中分配内存
cout << "start_new_fun_text arr:" << arr << endl;
cout << "start_new_fun_text p:" << p << endl;
cout << "start_new_fun_text *p:" << *p << endl;
cout << "start_new_fun_text arr[0]:" << arr[0] << endl;
运行结果:
start_new_fun_text arr:0x62fc30
start_new_fun_text p:0x62fc30
start_new_fun_text *p:8
start_new_fun_text arr[0]:8
名称空间
C++ 使用namespace声明名称空间,可以理解成将代码块起了一个名字。
名称空间不能位于代码块中,所以链接性是外部的。(代码块中外部就引用不到了)
namespace apple {
int color;
int weight;
void apple_eat() {
cout << "fun apple eat ";
}
}
namespace orange {
int color;
int weight;
}
void namespace_text_1() {
apple::color = 1; //使用作用域解析符::来引用名称空间的名称,
orange::weight = 2;
}
void namespace_text_2() {
using namespace apple; using 编译引用名称空间中的全部定义
color = 1;
using apple::weight; //unsing 声明引用单个定义
weight =2 ;
}