(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)

    有两个类都需要使用共同的变量,我们将这些变量定义为全局变量。比如,res.h和res.cpp分别来声明和定义全局变量,类ProducerThread和ConsumerThread来使用全局变量。(以下是QT工程代码)
 
  1. /**********res.h声明全局变量************/
  2. #pragma once
  3. #include <QSemaphore>
  4. const int g_nDataSize = 1000; // 生产者生产的总数据量
  5. const int g_nBufferSize = 500; // 环形缓冲区的大小
  6. extern char g_szBuffer[]; // 环形缓冲区
  7. extern QSemaphore g_qsemFreeBytes; // 控制环形缓冲区的空闲区(指生产者还没填充数据的区域,或者消费者已经读取过的区域)
  8. extern QSemaphore g_qsemUsedBytes; // 控制环形缓冲区中的使用区(指生产者已填充数据,但消费者没有读取的区域)
  9. /**************************/
上述代码中g_nDataSize、g_nBufferSize为全局常量,其他为全局变量。
  1. /**********res.cpp定义全局变量************/
  2. #pragma once
  3. #include "res.h"
  4. // 定义全局变量
  5. char g_szBuffer[g_nBufferSize];
  6. QSemaphore g_qsemFreeBytes(g_nBufferSize);
  7. QSemaphore g_qsemUsedBytes;
  8. /**************************/
在其他编译单元中使用全局变量时只要包含其所在头文件即可。
  1. /**********类ConsumerThread使用全局变量************/
  2. #include "consumerthread.h"
  3. #include "res.h"
  4. #include <QDebug>
  5. ConsumerThread::ConsumerThread(QObject* parent)
  6. : QThread(parent) {
  7. }
  8. ConsumerThread::ConsumerThread() {
  9. }
  10. ConsumerThread::~ConsumerThread() {
  11. }
  12. void ConsumerThread::run() {
  13. for (int i = 0; i < g_nDataSize; i++) {
  14. g_qsemUsedBytes.acquire();
  15. qDebug()<<"Consumer "<<g_szBuffer[i % g_nBufferSize];
  16. g_szBuffer[i % g_nBufferSize] = ' ';
  17. g_qsemFreeBytes.release();
  18. }
  19. qDebug()<<"&&Consumer Over";
  20. }
  21. /**************************/
    也可以把全局变量的声明和定义放在一起,这样可以防止忘记了定义,如上面的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控制台应用程序代码示例:
 
  1. /***********res.h**********/
  2. static char g_szBuffer[6] = "12345";
  3. void fun();
  4. /************************/
  1. /***********res.cpp**********/
  2. #include "res.h"
  3. #include <iostream>
  4. using namespace std;
  5. void fun() {
  6. for (int i = 0; i < 6; i++) {
  7. g_szBuffer[i] = 'A' + i;
  8. }
  9. cout<<g_szBuffer<<endl;
  10. }
  11. /************************/
  1. /***********test1.h**********/
  2. void fun1();
  3. /************************/
  1. /***********test1.cpp**********/
  2. #include "test1.h"
  3. #include "res.h"
  4. #include <iostream>
  5. using namespace std;
  6. void fun1() {
  7. fun();
  8. for (int i = 0; i < 6; i++) {
  9. g_szBuffer[i] = 'a' + i;
  10. }
  11. cout<<g_szBuffer<<endl;
  12. }
  13. /************************/
  1. /***********test2.h**********/
  2. void fun2();
  3. /************************/
  1. /***********test2.cpp**********/
  2. #include "test2.h"
  3. #include "res.h"
  4. #include <iostream>
  5. using namespace std;
  6. void fun2() {
  7. cout<<g_szBuffer<<endl;
  8. }
  9. /************************/
  1. /***********main.cpp**********/
  2. #include "test1.h"
  3. #include "test2.h"
  4. int main() {
  5. fun1();
  6. fun2();
  7. system("PAUSE");
  8. return 0;
  9. }
  10. /************************/
运行结果如下:

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

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

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

    原文链接:http://blog.csdn.net/candyliuxj/article/details/7853938 (1)编译单元(模块) 在VC或VS上编写完代码,点击编译按钮准备生成exe文 ...

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

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

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

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

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

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

  5. c++声明与定义

    c++声明与定义 声明是将一个名称引入程序.定义提供了一个实体在程序中的唯一描述.声明和定义有时是同时存在的. 如 int  a; extern int b=1; 只有当extern中不存在初始化才是 ...

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

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

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

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

  8. C语言的声明和定义

    在程序设计中,时时刻刻都用到变量的定义和变量的声明,可有些时候我们对这个概念不是很清楚,知道它是怎么用,但却不知是怎么一会事. 下面我就简单的把他们的区别介绍如下: 变量的声明有两种情况: (1)一种 ...

  9. C语言,函数的声明与定义

    函数声明与定义 变量: 在讲变量前,先讲一下变量的声明和定义这两个概念. 声明一个变量,意味着向编译器描述变量的类型,但不为变量分配存储空间. 定义一个变量,意味着在声明变量的同时还要为变量分配存储空 ...

随机推荐

  1. Spring MVC 之拦截器(八)

     在springMVC中实现拦截器有两种方式 1.实现HandlerInterceptor接口 2.继承HandlerInterceptorAdaptor类 编写拦截器: package com.cy ...

  2. VC++NMAKE

    目录 第1章 NMAKE    1 1.1 运行NMAKE    1 1.1.1 NMAKE的实质    2 1.2 描述块    3 1.2.1 定义    3 1.2.2 多个描述块    3 1 ...

  3. word-break:break-all和word-wrap:break-word的区别

    了解word-break属性 /* 关键字值 */ word-break: normal; word-break: break-all; word-break: keep-all; /* 全局值 */ ...

  4. python 练习 10

    #!/usr/bin/python # -*- coding: UTF-8 -*- i = int(raw_input('净利润:')) arr = [1000000,600000,400000,20 ...

  5. [Hadoop 周边] 浅谈大数据(hadoop)和移动开发(Android、IOS)开发前景【转】

    原文链接:http://www.d1net.com/bigdata/news/345893.html 先简单的做个自我介绍,我是云6期的,黑马相比其它培训机构的好偶就不在这里说,想比大家都比我清楚: ...

  6. jquery学习笔记1

    (1) jQuery的Id选择器: $("#btnShow") (2) 事件绑定函数 bind() $("#btnAdd").bind("click& ...

  7. python中的异常处理

    主要用到 try...except...raise...finally... 1. try...except... try: for i in range(1, 1000): print i time ...

  8. 选择列表中的列无效,因为该列没有包含在聚合函数或 GROUP BY 子句中

    选择列表中的列无效,因为该列没有包含在聚合函数或 GROUP BY 子句中 T-SQL核心语句形式: SELECT     --指定要选择的列或行及其限定  [INTO ]      --INTO子句 ...

  9. 20145236 《Java程序设计》第4周学习总结

    20145236 <Java程序设计>第4周学习总结 教材学习内容总结 第六章 继承与多态 一.继承 •继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类.继承可以理解 ...

  10. 9. shell环境

    • printenv –打印部分或所有的环境变量 • set –设置 shell 选项 • export —导出环境变量,让随后执行的程序知道. • alias –创建命令别名 1.shell环境:s ...