本篇内容主要讲解C++项目的三个大方面:多文件编译、内存模型、命名空间
(一)单独编译
首先明白:C++鼓励程序员将组件函数放在独立的文件中,可以单独编译这些文件,然后将它们链接成可执行的程序。
在VS编译器中就是如上图。
头文件一般包含:
①函数原型
②使用#define或者const定义的符号变量
③结构体声明
④类声明
⑤模板声明
⑥内联函数
其他的文件:
函数定义
必要的文件:
①main()函数
②使用 其他文件中定义的函数 的函数
示例
↓ 头文件:coordin.h ↓
//文件名:coordin.h
#ifndef COORDIN_H_ //
#define 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
↓ 文件:函数定义.cpp ↓
//文件名为:函数定义.cpp
#include<iostream>
#include<cmath>
#include"coordin.h"//结构体定义,函数模板
//转换直角坐标系到极坐标系
polar rect_to_polar(rect xypos)//接收直角坐标数据,输出极坐标数据
{
using namespace std;//标准名称空间
polar answer;//定义极坐标结果结构变量
answer.distance = sqrt(xypos.x*xypos.x+xypos.y*xypos.y);//开平方
answer.angle = atan2(xypos.y,xypos.x);//反正切函数
return answer;//返回极坐标形式的数据,以结构形式
}
void show_polar (polar dapos)//显示转换结果,以极坐标形式
{
using namespace std;
const double Red_to_deg = 57.29577951;//角度转换常数
cout << "distance = " << dapos.distance;
cout << ", angle = " << dapos.angle*Red_to_deg;
cout << "degrees\n";
}
↓ 文件:main.cpp ↓
//文件名:main.cpp
#include<iostream>
#include"coordin.h"//本文件中的结构和函数在coordin.h中定义和使用原型
using namespace std;//名称空间
int main()
{
rect rplace;//直角坐标结构变量x and y
polar pplace;//极坐标结构变量
cout << "Enter the x and y values";
while(cin >> rplace.x >> rplace.y)//输入值
{
pplace = rect_to_polar(rplace);
show_polar(pplace);
cout << "next two number (q to quit):";
}
cout << "BYE!\n";
return 0;
}
运行时,就是在IDE中生成项目,然后执行main文件,这三个文件就会自动链接好。
注意:
①我们包含自己编写的头文件时,用的是 “coordin.h” 而不是 <coordin.h>
这两个符号对编译器来说区别很大,当头文件在<>里面时,C++编译器在存储标准头文件的主机系统的文件系统中查找; 如果头文件是在 " "里面,那么编译器将首先查找当前的工作目录或源代码目录
②同一个文件中只能包含同一个头文件一次
为了避免这种情况发生,C++引入了 预处理器编译指令 #ifndef … #endif
(二)存储持续性、作用域、链接性
首先需要了解C++关于内存的一个最基本事实:
编译器使用三块独立的内存:
C++按照数据保留在内存中的时间(存储持续性),将存储分为3类(其实是4类,但有一类这里不做说明)
自动存储:这是最常见的存储方式,就是针对在函数定义中声明的变量,在程序开始执行其所属的函数或代码块时被创建,在程序执行完函数或者代码块时,该内存被释放。
静态存储:在函数定义外定义的变量 和 使用关键字 static 定义的变量,只要程序还在运行,该内存就一直都在
动态存储:用 new 运算符分配的内存一直存在,知道 delete 运算符将其释放。
ok,这样就对C++存储有个基本的了解,现在再继续讲另外两个名词:作用域、链接性
作用域:描述名称在文件的多大范围可见
作用域为局部:该变量只在定义它的代码块中可用(代码块就是由花括号括起来的一系列语句)
作用域为全局:在该变量定义位置到文件结尾都可用
链接性:描述了名称如何在不同单元共享。
链接性为外部:可在文件间共享
链接性为内部:只能在一个文件中的函数共享
好,搞懂了以上的概念,我们接下来对几种存储做分析
①自动存储
作用域 | 链接性 |
---|---|
局部 | 无链接性 |
int main()
{
int a = 5; //a 这个内存在 main() 函数中都存在,且可见
{
int b = 22; // b在他所在这个花括号中可见,花括号结束则内存被释放
int a = 9; // a由于与外面的a重名,此时,这里的a可见,外面的a暂时隐藏,但外面的a仍占有内存
cout << a; //输出 9
}
cout << a; //输出 5
}
上面这个程序很好地解释了作用域与可见性。
②静态存储
作用域 | 链接性 |
---|---|
全局(除无链接性静态变量) | 外部链接性、内部链接性、无链接性 |
...
int a = 1000; //静态存储、具有外部链接性
static b = 50; //(加上static)静态存储、具有内部链接性
int main()
{
...
}
void funct1( int n )
{
static int c = 0; //静态存储、无链接性(只能在该函数中使用它,但即使该函数没有被执行,其内存也一直存在)
}
...
就这个 具有外部链接性的静态变量 展开讲一讲应用:
// file_1.cpp
int dogs = 30; // 定义具有外部链接性的静态变量
int cats = 40;
// file_2.cpp
extern int dogs; //使用关键字 extern 引用file1文件中的静态变量
extern int cats;
static int dogs = 5; //当在这个文件想命名一个同名的静态变量时,应该使用内部链接性的变量,不然违反单定义规则;
C++单定义规则:
变量只能有一次定义。两种变量声明:定义变量(分配内存)、引用声明(不分配内存)
const限定符限定的字符常量: 相当于 内部链接性静态全局变量
函数:默认为 外部链接性静态存储(可以在文件间共享,别的文件引用该函数时可用可不用extern,只能有一个文件包含该函数定义,但每个文件应该有其声明),可用static将函数设为内部链接性。
③动态存储
不受作用域与链接性控制,仅由new和delete控制
(三)名称空间
背景问题:当使用多个厂家提供的类库时,可能导致名称冲突,我如何控制每个名称的作用域
两个术语:
声明区域:可以在其中进行声明的区域
潜在作用域:从声明点开始 到 齐声明区域的末尾
下面程序演示如何利用创建命名空间避免名称冲突
namesapce Jack // 利用 namespace 创建一个命名的 名称空间
{
double dog;
int cat;
void funct1();
struct people
{
...
}
}
int main()
{
Jack::cat = 30; //使用作用域解析运算符 :: 访问命名空间中的名称
using Jack::dog; //using声明,使特定的标识符可用,鼓励的方式
dog = 410.0;
using namespace Jack; //using编译,使该命名空间的所有名称可见,一般不鼓励,会有冲突风险
dog = 300.0;
cat = 40;
}
对于using声明,最好将其作用域设置为局部,而不是全局。这个取决于你的 using Jack::dog; 语句放在哪,是在main()函数前面,还是哪个花括号中。
好文