原文链接:http://blog.csdn.net/candyliuxj/article/details/7853938

(1)编译单元(模块)

在VC或VS上编写完代码,点击编译按钮准备生成exe文件时,编译器做了两步工作:

第一步:将每个.cpp和相应的.h文件编译成obj文件;

第二步:将工程所有的obj文件进行Link,生成最终的.exe文件。

这样,错误可能在两个地方产生:

一个是在编译的时候发生的错误,主要是语法错误;

一个是在链接的时候的错误,主要是重复定义变量等。

编译单元指在编译阶段生成的每个obj文件。

一个obj文件就是一个编译单元。

一个.cpp和它对应的.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)全局变量
       有两个类都需要使用共同的变量,我们将这些变量定义为全局变量。比如,res.h和res.cpp分别来声明和定义全局变量,类ProducerThread和ConsumerThread来使用全局变量。
 /**********res.h声明全局变量************/
#pragma once #include <QSemaphore> const int g_nDataSize = ; // 生产者生产的总数据量
const int g_nBufferSize = ; // 环形缓冲区的大小 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 = ; 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)静态全局变量(static)

  注意使用static修饰变量,就不能使用extern来修饰,即static和extern不可同时出现。
    static修饰的全局变量的声明与定义同时进行,即当你在头文件中使用static声明了全局变量,同时它也被定义了。
    static修饰的全局变量的作用域只能是本身的编译单元。在其他编译单元使用它时,只是简单的把其值复制给了其他编译单元,其他编译单元会另外开个内存保存它,在其他编译单元对它的修改并不影响本身在定义时的值。即在其他编译单元A使用它时,它所在的物理地址,和其他编译单元B使用它时,它所在的物理地址不一样,A和B对它所做的修改都不能传递给对方。
    多个地方引用静态全局变量所在的头文件,不会出现重定义错误,因为在每个编译单元都对它开辟了额外的空间进行存储。
以下是Windows控制台应用程序代码示例:
 /***********res.h**********/
static char g_szBuffer[] = "";
void fun();
/************************/
 /***********res.cpp**********/
#include "res.h"
#include <iostream>
using namespace std; void fun() {
for (int i = ; i < ; 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 = ; i < ; 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 ;
}
/************************/

运行结果如下:

按我们的直观印象,认为fun1()和fun2()输出的结果都为abcdef,可实际上fun2()输出的确是初始值。然后我们再跟踪调试,发现res、test1、test2中g_szBuffer的地址都不一样,分别为0x0041a020、0x0041a084、0x0041a040,这就解释了为什么不一样。
 
    注:一般定义static 全局变量时,都把它放在.cpp文件中而不是.h文件中,这样就不会给其他编译单元造成不必要的信息污染。
 
(6)全局常量(const)
      const单独使用时,其特性与static一样(每个编译单元中地址都不一样,不过因为是常量,也不能修改,所以就没有多大关系)。
    const与extern一起使用时,其特性与extern一样。
 extern const char g_szBuffer[];      //写入 .h中
const char g_szBuffer[] = ""; // 写入.cpp中
 
 
 
 
 
 
 

C++中全局变量的声明和定义的更多相关文章

  1. C++全局变量的声明和定义

    (1)编译单元(模块)     在VC或VS上编写完代码,点击编译按钮准备生成exe文件时,编译器做了两步工作: 第一步,将每个.cpp(.c)和相应的.h文件编译成obj文件: 第二步,将工程中所有 ...

  2. 【C++】C++中变量的声明与定义的区别

    声明(declaration):意味着告诉编译器关于变量名称.变量类型.变量大小.函数名称.结构名称.大小等等信息,并且在声明阶段不会给变量分配任何的内存. 定义(definition):定义就是在变 ...

  3. 今天才知道原来我还没弄清楚js中全局变量和局部变量的定义...

    查资料看到这段还不错,来源:原文:https://blog.csdn.net/czh500/article/details/80429133 粘过来记录一下... 1.使用var声明变量,在方法内部是 ...

  4. 变量声明和定义及extern 转载

    在讨论全局变量之前我们先要明白几个基本的概念: 1. 编译单元(模块):    在IDE开发工具大行其道的今天,对于编译的一些概念很多人已经不再清楚了,很多程序员最怕的就是处理连接错误(LINK ER ...

  5. 关于C++的变量和类的声明和定义

    什么是变量?变量或者叫对象,是一个有具名的.可以供程序操作的存储空间.这里具名是指变量是有名字的,可供操作是指能进行加减乘除或者输入输出等操作,存储空间则是指有一块属于它的内存空间. 为了便于说明,标 ...

  6. 多个".h"文件中声明及定义 全局变量和函数

    一.".h"文件必须以如下格式书写 例:文件<CZ_efg_hi.h"> ------------文件内容----------- #ifndef CZ_Efg ...

  7. C++中重定义的问题——问题的实质是声明和定义的关系以及分离式编译的原理

    这里的问题实质是我们在头文件中直接定义全局变量或者函数,却分别在主函数和对应的cpp文件中包含了两次,于是在编译的时候这个变量或者函数被定义了两次,问题就出现了,因此,我们应该形成一种编码风格,即: ...

  8. [转载]C++中声明与定义的区别

    C++学了这么多年你知道为什么定义类时,类的定义放在.h文件中,而类的实现放在cpp文件中.它们为什么能够关联到一起呢?你知道什么东西可以放在.h文件中,什么不能.什么东西又可以放在cpp文件中.如果 ...

  9. MFC中全局变量的定义及使用

    用MFC制作的工程由很多文件构成,它不能象一般C++程序那样随意在类外定义全局变量,在这里要想定义能被工程内多个文件共享的全局变量和函数必须用一些特殊方法才行.实际上有多种方法可以实现,这里只介绍两种 ...

随机推荐

  1. 兼顾php和jquery新版本的checkbox的写法

    <input type="checkbox" class="scate_item" name="scategories[]" valu ...

  2. 洛谷——P2615 神奇的幻方 【Noip2015 day1t1】

    https://www.luogu.org/problem/show?pid=2615 题目描述 幻方是一种很神奇的N*N矩阵:它由数字1,2,3,……,N*N构成,且每行.每列及两条对角线上的数字之 ...

  3. POJ——T2117 Electricity

     http://poj.org/problem?id=2117 Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 5459   ...

  4. hadoop-15-Ambari进行HDP、zookeeper安装

    hadoop-15-Ambari进行HDP.zookeeper安装 1,登陆server_1:8080 admin/admin 2,命名:Hdp_cluster 3,输入HDP.HDP_Utils 地 ...

  5. Ruby创建命令

    Ruby创建命令

  6. 3、Python字典集合

    2.3字典 字典是键值对的无序可变序列.键值之间用冒号隔开,相邻元素之间用逗号隔开,所有元素放在大括号之间{},键可以是Python中所有不可变的数据,不能用列表.元组.字典作为字典的键,键不可重复, ...

  7. JNI 资源释放

    JNI 编程实现了 native code 和 Java 程序的交互,因此 JNI 代码编程既遵循 native code 编程语言的编程规则,同时也遵守 JNI 编程的文档规范.在内存管理方面,na ...

  8. hdoj--5567--sequence1(水题)

    sequence1 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total ...

  9. MySQL优化-存储引擎

    MySQL优化-存储引擎 标签(空格分隔): mysql 存储引擎 查看存储引擎 show engines Myisam: 表锁 全文索引 Innodb: 行锁 事物 外键 Memory: 内存存储引 ...

  10. 11.字符,字符常见开发,_itoa函数

    各种字符所占字节 wchar_t wch = L'我'; //占4个字节 char ch;//占1个字节 printf("%d\n", sizeof("A")) ...