1、每一个CPP及它所包含的所有头文件称为一个转换单元。
编译器处理每个转换单元的过程与生成目标对象的过程是无关的。
2、编译只负责把每个转换单位生成目标文件。目标文件由链接程序链接成exe,这个链接包括处理一些链接属性,比如extern.
extern用来说明本文件中此变量,已经在另一个文件定义了。因此在本文件同一块作用域中,不再有相同的定义变量,如:
main(){ int K=3;
extern int K; //错误,两个K作用域在同一块中。另外注意:引用外部变量不能再次定义赋值,否则最边链接程序连接时会造成重复定义。
}
当然,如果每都是引用extern,却没人一个文件真正地定义它,那么编译器也会出错。
有时为了定义一个全局变量在本文件中不能更改,因此申明为const,但const只具有内部链接,也就是外部的文件再无法用extern来引用它。怎么办呢?
这时只需在定义前面加上extern并初始化它,表示它既是常量(内部链接),同时也可被其它文件链接。其它文件链接时应extern const同时写上(不能再定义赋值)
file1.cpp extern const PI=3.14159; file2.cpp: extern const PI; //文件2中不能再定义,但需写上extern const
3、了解:链接属性(linkage)
转换单元的名称在编译或链接过程中的处理方式由链接属性决定.
一、编译:严格来说,是指源代码到目的对象的过程。
链接:目标代码到可执行文件exe的过程。
平时我们说编译过程指的是: 编译和链接
二、链接属性只由链接来处理。编译过程保留那些链接属性,最后由链接程序时行处理最终进行链接。
当某名称用于作用域外的代码块访问程序的变量或者函数时,就有了链接属性。否则就无。链接属性有三种:
内部链接属性:在同一个转换单元任何地方可访问:全局变量、const变量
外部链接属性:在另一个转换单元中可以访问的:extern,即整个程序共享和访问
无链接属性:只能在本作用域中访问,如块内定义 的局部变量。
4、file1.cpp:
const int j=6;
file2.cpp:
#include <iostream> using namespace std; int main(int argc, char *argv[]) { extern const int j; cout<<j<<endl; return 0; }
一、多文件应用“创建工程”来进行,这样每新增加一个文件都放到“源文件”中。
二、多个文件,有且只有一个主函数Main()
三、因file1中用了const,说明是内部链接,因此不能在外部file2中进行引用输出。不出错的改法:
file1中前加extern,这样就有外部链接属性,file2中就不会出错。
在file2中把cout<<j<<endl;注释掉也不会出错。为什么呢?原来file2中j就是不引用file1的j中了,而是另起炉灶,定义一个可被外部引用的j。
四、全部变量,如果没有组出初值,其值初始化时自动为0.
5、命名空间。相当于一个结构体的形式。
一、定义:namespac My_Name{ }
右花括号没有分号。把定义的变量、函数等放在其中,它们受My_Name统管。注意不能把main()放在其中。。
同一文件中可有多个同的命名(My_Name),但其内的变量和函数只能唯一定义一次;
namespace OK{int i=3;int j=4}
namespace OK{int m,int n} //这里不能再定义给i,j定义赋值,因为处于同一文件中。
不同文件中可有多个相同的命名(My_Name),同样,其定义赋值只能一次,另一处只能是引用说明是外来的。
file1.cpp: namespace OK{ int i=3; int j=4}
file2.cpp: namespace OK{extern int i;extern int j} //引用是外部变量,不能再赋值。
定义还可以嵌套: namespace A{
.................
namespace B{
}
.............
}
二、使用命名空间:
using namespace My_Name
如果是嵌套:using namespace A::B; 引用时要逐个进行限定如 A::B::max();
内部过程:在使用未限定的变量名(函数名)时,编译器在使用前,首先在本作用域内查找其定义,若无,再到外层块中查找,一直继续查找到全局作用域为止。
最后实在没有定义(或不是外引用extern),则断定其没有定义,没有定义的东西,你说会发生什么呢?!
-----------------------------------------------------------------------------------
一般做法:做一个h文件进行声明,然后单独在另一个CPP中进行详细定义(应include那个头文件),然后在主文件中引用这个头文件。就可以使用这个了,另外在主函数使用时,注意要用作用域符进行限制(在文件首进行限定,或者在块中进行限定)
注意:如果是模板呢?
应把模板定义(含代码)全放进namespace中,于头文件中。具体定义的cpp就不要写了。主函数直接引用头文件,即可自动生成对应函数并使用。
当模板含有特殊情况时,应把特例声明放入头文件中,并在另一个CPP(把头文件含进来)进行详细代码定义。再按正常情况进行使用。
6、命名空间的别名
在多人编写同一项目时,为了使别人更易懂,命名空间定义名往往很长,引用起来很麻烦,于是“定义”它一个别人来引用更快快捷方便:
namespace myName=This_is_another_namespaceName_with_XiaoZhao;//于是后面只要用myName就相当于别人的命名空间一样。
7、没有名字的命名空间:
namespace
{ }
无名的命名空间是可行的,它由系统指定一个内部ID来进行识别。注意:同一个文件中,如果有多个无名的命名空间,则被当作第一个无名的命名空间的扩展,即它们是同一个无名的空间内,故同一个转换单元中所有无名空间是同一空间。不同的文件的无名命名空间是各不相同的。因此,它们只在本地起作用,不能被外部引用。在C中常 用static来说明这个变量或函数是本地转换单元的。
8、预处理:属于编译器的一部分,在编译前处理。其指令前都有#,但有#的并不一定是预处理指令(如#impor)。
#include 头文件包含
#if if
#else else
#elif else if
#endif endif(if的结束)
#if defined(或#ifdef) 如果定义了
#if !defined(或ifndef) 如果没有定义。。。
#define 定义一符号
C中常用,C++时常用const进行定义
#undef 删除前面定义的符号
#line 重新定义当前等号和文件名
#error 输出编译错误消息,停止编译
#progma 提供机器专用的特性,同时保证与C++的完全兼容
9、#define 标识符 字符序列
用给定的“字符序列”来替代“标识符。#define有三个缺点:
不能进行类型检查;
不考虑作用域;
不能限制在同一个命名空间内。
取消已经定义的标识符: #define value //后不接字符序列,就自动取消已经定义的
#undef value
宏置换:复杂的#define定义
#define 标识符(标识符列表) 替换字符串
如:#define print(var) cout<<(var)<<endl //注意语句后无分号,也可有分号,但注意它会将分号也替代进去。
#define print(num,var) cout<<setw(num)<<(var)<<endl //带2参数,亦可多个参数。
宏置换较多,但一般都写成内联的函数或函数模板:
template<class T>inline void print(T num,T x)
{ cout<<setw(num)<<x<<endl; }
宏置换只是“死板”地替换,注意下面的错误:
#define shengfa(m,n) m*n
y=shengfa(y+1,x) //其替换后是:y=y+1*x 故应改进为:#define shengfa(m,n) ( (m)*(n)) 注意为啥最外还要有括号
10、预处理指令的继行: 一般应写在一行中,若一行写不完,应在最后加\ 后 ,在下一行中进行再次写,这样表示是属于上一行的继写。
11、宏字符串的实现:
#define print(var) cout<<#var<<"="(var)<<endl;
lookie=342;
print(lookie); //替换成: cout<<"lookie"<<"="<<(var)<<endl; 即结果:lookie=342
因为宏指令不能实现参数的字符串化,于是用#带参数,实现把这个参数放在引号内。
a##b 把两个字符刻意组合在一起
#a 把参数a放入双引号内
12、#if defined 标识符
................
#endif
--------------------------------------------
#ifdef 标识符
....................
#endif
------------------------------------------------
#if !defined 标识符
....................
#endif
----------------------------------------------
#ifndef 标识符
..............
#endif
------------------------------------------------
#if类似程序中一样,可以判断真假,但必须 是:#if 常量表达式
这个常量表达式必须是整数常量表达式,否则会提示:[Error] floating constant in preprocessor expression 类似的错误
同样,还有#else(同else) #elif(同else if)
13、标准预处理器宏
#include <iostream> using namespace std; int main(int argc, char *argv[]) { cout<<__LINE__<<endl; cout<<__DATE__<<" "<<__TIME__<<endl; cout<<__FILE__<<endl; return 0; }
__LINE__ 前后都是两个下划线,中间大写(下同)。返回当前代码的行号,十进制整数。上例返回5(第5行)
__DATE__ 最后一次编译日期(只要未再编译,其值不变),字符串字面量。格式:mmm dd yyyy(仅月用三个英文表示)例:Sep 23 2012
__TIME__ 最后一次编译时间(同上),字符串字面量。格式:hh:mm:ss 例:09:39:38
__FILE__ 源文件名称(含路径及后缀),如:D:\cfree\show.cpp
__STDC__ 取决编译器,若编译器选项选择标准C,通常就定义它
__cplusplus 小写,仅前面有两下划,编译C++时,定义其值为199711L
-------------------------------------------------------------------------------------------------------------------------------------
通常#line配合上面__LINE__和__FILE__进行。
#line 2000 "look.cpp" //定义从下一行以2000的行号进行计算,定义__FILE__(文件名)为look.cpp
#line __LINE__ "look.cpp //只改变文件名,行号默认
#line 324 //只改变行号,不改变文件名
#line "look.cpp" //出错
14、#error 显示一条信息 通常用于错误调试。注意:此信息显示编译器窗体中,不会显示在输出窗体中。
#pragma 预先定义的宏,若没出现定义过将忽略。(详情百度)
15、断言机制:assert(表达式) 表达式为假时,程序自动中断(系统调用abort()),然后弹出信息(cerr):文件名,行号,出错条件等
在头文件 #include<cassert>
在头文件及assert前如果有:#define NDEBUG 断言机制将关闭(忽略)
--------------------------------------------------------------------------------------------------------
可以用<cassert>中声明的assert()库函数来检查本地C++程序中始终为true的逻辑条件
实际上assert()只在调试版本程序中才会起作用,发布版本不进行编译的;
头文件中,一般是使用预处理实现这个功能,也可以自定义调试的代码;如下所描述
在本地C++程序的中,
预处理符号NDEBUG是在发布版本中默认自动定义的.调试版本中没定义
预处理符号_DEGUG是在调试版本中默认自动定义的,发布版本中没定义
下面这个输出语句只在调试版本编译,不在
发布版本内编译,利用这个特点,可以自己写便于调试的输出信息