(1)编译单元(模块)
在VC或VS上编写完代码,点击编译按钮准备生成exe文件时,编译器做了两步工作:
第一步,将每个.cpp(.c)和相应的.h文件编译成obj文件;
第二步,将工程中所有的obj文件进行LINK,生成最终.exe文件。
那么,错误可能在两个地方产生:
一个,编译时的错误,这个主要是语法错误;
一个,链接时的错误,主要是重复定义变量等。
编译单元指在编译阶段生成的每个obj文件。
一个obj文件就是一个编译单元。
一个.cpp(.c)和它相应的.h文件共同组成了一个编译单元。
一个工程由很多编译单元组成,每个obj文件里包含了变量存储的相对地址等。
(2)声明与定义
函数或变量在声明时,并没有给它实际的物理内存空间,它有时候可保证你的程序编译通过;
函数或变量在定义时,它就在内存中有了实际的物理空间。
如果你在编译单元中引用的外部变量没有在整个工程中任何一个地方定义的话,那么即使它在编译时可以通过,在连接时也会报错,因为程序在内存中找不到这个变量。
函数或变量可以声明多次,但定义只能有一次。
(3) extern作用
作用一:当它与"C"一起连用时,如extern "C" void fun(int a, int b);,则编译器在编译fun这个函数名时按C的规则去翻译相应的函数名而不是C++的。
作用二:当它不与"C"在一起修饰变量或函数时,如在头文件中,extern int g_nNum;,它的作用就是声明函数或变量的作用范围的关键字,其声明的函数和变量可以在本编译单元或其他编译单元中使用。
即B编译单元要引用A编译单元中定义的全局变量或函数时,B编译单元只要包含A编译单元的头文件即可,在编译阶段,B编译单元虽然找不到该函数或变量,但它不会报错,它会在链接时从A编译单元生成的目标代码中找到此函数。
(4)全局变量(extern)
extern 主要用于全局变量的声明,不建议用于全局变量的定义(如:extern int a =10; 如果放在函数中会报错,放在文件前面编译器不会报错但是会出现警告)。extern不能和局部变量一起使用,局部变量只在函数内部使用,函数退出局部变量声明周期就结束,何来extern之谈,但是全局变量的声明可以放在局部作用域里,其意思是外部有这样一个全局变量,现在这个局部域里可以使用。换句话说,这样的变量一定是在某个文件全局域定义的,但在使用它的那个文件里,可以声明成全局域都可见,也可以声明成只有局部域可见。
有两个类都需要使用共同的变量,我们将这些变量定义为全局变量。比如,res.h和res.cpp分别来声明和定义全局变量,类ProducerThread和ConsumerThread来使用全局变量。(以下是QT工程代码)
extern 主要用于全局变量的声明,不建议用于全局变量的定义(如:extern int a =10; 如果放在函数中会报错,放在文件前面编译器不会报错但是会出现警告)。extern不能和局部变量一起使用,局部变量只在函数内部使用,函数退出局部变量声明周期就结束,何来extern之谈,但是全局变量的声明可以放在局部作用域里,其意思是外部有这样一个全局变量,现在这个局部域里可以使用。换句话说,这样的变量一定是在某个文件全局域定义的,但在使用它的那个文件里,可以声明成全局域都可见,也可以声明成只有局部域可见。
有两个类都需要使用共同的变量,我们将这些变量定义为全局变量。比如,res.h和res.cpp分别来声明和定义全局变量,类ProducerThread和ConsumerThread来使用全局变量。(以下是QT工程代码)
- /**********res.h声明全局变量************/
- #pragma once
- #include <QSemaphore>
- const int g_nDataSize = 1000; // 生产者生产的总数据量
- const int g_nBufferSize = 500; // 环形缓冲区的大小
- extern char g_szBuffer[]; // 环形缓冲区
- extern QSemaphore g_qsemFreeBytes; // 控制环形缓冲区的空闲区(指生产者还没填充数据的区域,或者消费者已经读取过的区域)
- extern QSemaphore g_qsemUsedBytes; // 控制环形缓冲区中的使用区(指生产者已填充数据,但消费者没有读取的区域)
- /**************************/
上述代码中g_nDataSize、g_nBufferSize为全局常量,其他为全局变量。
- /**********res.cpp定义全局变量************/
- #pragma once
- #include "res.h"
- // 定义全局变量
- char g_szBuffer[g_nBufferSize];
- QSemaphore g_qsemFreeBytes(g_nBufferSize);
- QSemaphore g_qsemUsedBytes;
- /**************************/
在其他编译单元中使用全局变量时只要包含其所在头文件即可。
- /**********类ConsumerThread使用全局变量************/
- #include "consumerthread.h"
- #include "res.h"
- #include <QDebug>
- ConsumerThread::ConsumerThread(QObject* parent)
- : QThread(parent) {
- }
- ConsumerThread::ConsumerThread() {
- }
- ConsumerThread::~ConsumerThread() {
- }
- void ConsumerThread::run() {
- for (int i = 0; i < g_nDataSize; i++) {
- g_qsemUsedBytes.acquire();
- qDebug()<<"Consumer "<<g_szBuffer[i % g_nBufferSize];
- g_szBuffer[i % g_nBufferSize] = ' ';
- g_qsemFreeBytes.release();
- }
- qDebug()<<"&&Consumer Over";
- }
- /**************************/
也可以把全局变量的声明和定义放在一起,这样可以防止忘记了定义,如上面的extern char g_szBuffer[g_nBufferSize]; 然后把引用它的文件中的#include "res.h"换成extern char g_szBuffer[];。
但是这样做很不好,因为你无法使用#include "res.h"(使用它,若达到两次及以上,就出现重定义错误;注:即使在res.h中加#pragma once,或#ifndef也会出现重复定义,因为每个编译单元是单独的,都会对它各自进行定义),那么res.h声明的其他函数或变量,你也就无法使用了,除非也都用extern修饰,这样太麻烦,所以还是推荐使用.h中声明,.cpp中定义的做法。
(5)全局函数(extern)
从生存期角度看,函数必然是全局存在的,包括C++的成员函数,从作用域看,C没有命名空间(包括类名)的限制,可以说是全局;但有可以控制函数声明的位置,让某些原文件找不到他。函数和变量不同,变量的全局与局部是有绝对意义的,函数的全局只是个相对概念。
extern 关键字放在函数声明之前:
> test.h
extern int test();
如果这样函数的声明中带有关键字extern,仅仅是暗示这个函数可能在别的源文件里定义。
这样一来,就是在程序中取代include “*.h”来声明函数,在一些复杂的项目中,比较习惯在所有的函数声明前添加extern修饰,以防止遗漏包含头文件而导致的编译错误。
extern 关键字放在函数定义之前:
> test.c
extern int test()
{
return true;
}
如果在函数定义的地方带有关键字extern,表示该函数会提供给外部文件使用,其实有些编译器是默认每个函数都是extern类型的,反之是static类型
(6)静态全局变量(static)
注意使用static修饰变量,就不能使用extern来修饰,即static和extern不可同时出现。
static修饰的全局变量的声明与定义同时进行,即当你在头文件中使用static声明了全局变量,同时它也被定义了。
static修饰的全局变量的作用域只能是本身的编译单元。在其他编译单元使用它时,只是简单的把其值复制给了其他编译单元,其他编译单元会另外开个内存保存它,在其他编译单元对它的修改并不影响本身在定义时的值。即在其他编译单元A使用它时,它所在的物理地址,和其他编译单元B使用它时,它所在的物理地址不一样,A和B对它所做的修改都不能传递给对方。
多个地方引用静态全局变量所在的头文件,不会出现重定义错误,因为在每个编译单元都对它开辟了额外的空间进行存储。
以下是Windows控制台应用程序代码示例:
- /***********res.h**********/
- static char g_szBuffer[6] = "12345";
- void fun();
- /************************/
- /***********res.cpp**********/
- #include "res.h"
- #include <iostream>
- using namespace std;
- void fun() {
- for (int i = 0; i < 6; i++) {
- g_szBuffer[i] = 'A' + i;
- }
- cout<<g_szBuffer<<endl;
- }
- /************************/
- /***********test1.h**********/
- void fun1();
- /************************/
- /***********test1.cpp**********/
- #include "test1.h"
- #include "res.h"
- #include <iostream>
- using namespace std;
- void fun1() {
- fun();
- for (int i = 0; i < 6; i++) {
- g_szBuffer[i] = 'a' + i;
- }
- cout<<g_szBuffer<<endl;
- }
- /************************/
- /***********test2.h**********/
- void fun2();
- /************************/
- /***********test2.cpp**********/
- #include "test2.h"
- #include "res.h"
- #include <iostream>
- using namespace std;
- void fun2() {
- cout<<g_szBuffer<<endl;
- }
- /************************/
- /***********main.cpp**********/
- #include "test1.h"
- #include "test2.h"
- int main() {
- fun1();
- fun2();
- system("PAUSE");
- return 0;
- }
- /************************/
运行结果如下:
按我们的直观印象,认为fun1()和fun2()输出的结果都为abcdef,可实际上fun2()输出的确是初始值。然后我们再跟踪调试,发现res、test1、test2中g_szBuffer的地址都不一样,分别为0x0041a020、0x0041a084、0x0041a040,这就解释了为什么不一样。
注:一般定义static 全局变量时,都把它放在.cpp文件中而不是.h文件中,这样就不会给其他编译单元造成不必要的信息污染。
(7)静态局部变量(static)
- #include<iostream>
- using namespace std;
- int cnt=1;
- void f(){
- static int test = cnt;
- cout<<test<<endl;
- cnt++;
- }
- int main(){
- f();
- f();
- f();
- cout<<cnt<<endl;
- return 0;
- }
1
1
1
4
再看另一段程序- #include<iostream>
- using namespace std;
- int cnt=1;
- void f(){
- static int test;
- test = cnt;
- cout<<test<<endl;
- cnt++;
- }
- int main(){
- f();
- f();
- f();
- cout<<cnt<<endl;
- return 0;
- }
1
2
3
4
两段不同程序差别在哪呢?局部static变量test在函数f()内定义。但是C++规定,局部static变量只会初始化一次!所以第一个程序中后面调用两次f()都不会初始化test。而第二个程序并不是初始化,而是赋值! static int test =cnt与 static int test; test = cnt是不等价的!!
(8)静态函数(static)
在函数的返回类型前加上关键字static,函数就被定义成为静态函数。函数的定义和声明默认情况下是extern的,但静态函数只是在声明它的文件当中可见,不能被其他文件所用。static 的函数只在实现的编译单元(一般来说就是实现的那个.c文件)有效,别的编译单元不能调用。例如,把static void test();放在test.h里面,然后main.c使用这个头文件,由于在main.c文件里出现了static void test();这个声明,这迫使编译器在main.c文件里面寻找static void test()的函数体,结果当然找不到。
定义静态函数的好处:
<1> 其他文件中可以定义相同名字的函数,不会发生冲突
<2> 静态函数不能被其他文件所用。
在函数的返回类型前加上关键字static,函数就被定义成为静态函数。函数的定义和声明默认情况下是extern的,但静态函数只是在声明它的文件当中可见,不能被其他文件所用。static 的函数只在实现的编译单元(一般来说就是实现的那个.c文件)有效,别的编译单元不能调用。例如,把static void test();放在test.h里面,然后main.c使用这个头文件,由于在main.c文件里出现了static void test();这个声明,这迫使编译器在main.c文件里面寻找static void test()的函数体,结果当然找不到。
定义静态函数的好处:
<1> 其他文件中可以定义相同名字的函数,不会发生冲突
<2> 静态函数不能被其他文件所用。
(9)全局常量(const)
const单独使用时,其特性与static一样(每个编译单元中地址都不一样,不过因为是常量,也不能修改,所以就没有多大关系)。
const与extern一起使用时,其特性与extern一样。
- extern const char g_szBuffer[]; //写入 .h中
- const char g_szBuffer[] = "123456"; // 写入.cpp中