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

(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工程代码)

[cpp] view plaincopy

  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为全局常量,其他为全局变量。

[cpp] view plaincopy

  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. /**************************/

在其他编译单元中使用全局变量时只要包含其所在头文件即可。

[cpp] view plaincopy

  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控制台应用程序代码示例:

[cpp] view plaincopy

  1. /***********res.h**********/
  2. static char g_szBuffer[6] = "12345";
  3. void fun();
  4. /************************/

[cpp] view plaincopy

  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. /************************/

[cpp] view plaincopy

  1. /***********test1.h**********/
  2. void fun1();
  3. /************************/

[cpp] view plaincopy

  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. /************************/

[cpp] view plaincopy

  1. /***********test2.h**********/
  2. void fun2();
  3. /************************/

[cpp] view plaincopy

  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. /************************/

[cpp] view plaincopy

  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一样。

[cpp] view plaincopy

  1. extern const char g_szBuffer[];      //写入 .h中
  2. const char g_szBuffer[] = "123456"; // 写入.cpp中

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

时间: 2024-11-08 21:16:05

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

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

原文链接:http://blog.csdn.net/candyliuxj/article/details/7853938 (1)编译单元(模块) 在VC或VS上编写完代码,点击编译按钮准备生成exe文件时,编译器做了两步工作: 第一步:将每个.cpp和相应的.h文件编译成obj文件: 第二步:将工程所有的obj文件进行Link,生成最终的.exe文件. 这样,错误可能在两个地方产生: 一个是在编译的时候发生的错误,主要是语法错误: 一个是在链接的时候的错误,主要是重复定义变量等. 编译单元指在编

Java 全局变量 声明与定义

JAVA全局变量(或称成员变量)可分两种,一种是静态变量,另一种是实例变量,即在类体中定义的变量,有三点得注意: 一.成员变量不能在类体中先声明(定义)后赋值,但静态变量可以先在类体中声明,然后在方法中赋值(当然实例变量是不行的): 1)如以下程序会出问题:1public class Test { static int a; //在类体中声明整型静态变量a. int b; //在类体中声明整型实体变量b. a=3; //在类体中对静态变量a赋初值. b=5; //在类体中对实体变量b赋初值. p

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

函数声明与定义 变量: 在讲变量前,先讲一下变量的声明和定义这两个概念. 声明一个变量,意味着向编译器描述变量的类型,但不为变量分配存储空间. 定义一个变量,意味着在声明变量的同时还要为变量分配存储空间,在定义变量的同时为变量初始化. 局部变量:通常只定义不声明. 全局变量:通常在源文件中定义,在头文件中声明. 在一个函数内部定义的变量成为局部变量,它在本函数内有效. 函数中的局部变量,如果不定义类型,其缺省是自动变量auto, 例如:int a,b=2; 其等价于auto int a,b =

局部变量与全局变量在声明时的注意事项

/* ============================================================================ Name : TestVariable.c Author : lf Version : Copyright : Your copyright notice Description : 局部变量与全局变量在声明时的注意事项 全局变量只能要常量赋值,但是局部变量可用常量和表达式以及函数赋值. 这是为什么呢? 程序在开始执行时需要用适当的值来初

声明和定义的区别

声明部分的作用是对有关的标识符(如变量?函数?结构体?共用体等)的属性进行说明.对于函数,声明和定义的区别是明显的,函数的声明是函数的原型,而函数的定义是函数功能的确立.对函数的声明是可以放在声明部分中的,而函数的定义显然不在函数的声明部分范围内,它是一个文件中的独立模块. 在声明部分出现的变量有两种情况:一种是需要建立存储空间的(如int a;):另一种是不需要建立存储空间的(如extern int a;).前者称为定义性声明(defining declaration),或简称为定义(defi

内存四域,变量声明和定义,寄存器,c内嵌汇编,auto,堆栈,常量,静态变量

 1.内存四大区域 2.在程序中,变量的声明可以有多份,定义只能有一份 3.寄存器在cpu里面,没有地址 4.c语言内嵌汇编语言 5.auto变量 自动分配内存,自动释放内存. 6.栈的大小由编译器决定 修改栈的方式,右击项目à属性à配置属性à链接器à系统à堆栈保留大小 7.卡死cpu,卡死内存的程序 8.在堆上申请空间后赋值后,不可以释放内容.要通过free方法进行释放对空间. 9.常量字符串在代码区.代码区是智能读的. 10.常量本质 10.静态变量,全局变量的差别 A全局变量可以跨文件

C++中声明和定义的区别

声明 这有一个与这个名字相关的东西,并且它是这个类型的,告诉编译器我要使用它,并期待它定义在某一个地方. 定义 定义是指提供所有必要的信息(占用内存大小),使其能够创建整个实体. 我们必须明白的: 一旦定义了也就是声明了,所以可以同时声明和定义一个函数. 类或者变量,但是不一定非得这样做. 定义一个函数意味着提供函数体: void func() {}; 定义一个类意味着给出类的方法以及成员: class A {}; 定义一个变量意味着告诉编译器变量的类型以及在程序的哪儿去创建这个变量: int

C语言的声明和定义

在程序设计中,时时刻刻都用到变量的定义和变量的声明,可有些时候我们对这个概念不是很清楚,知道它是怎么用,但却不知是怎么一会事. 下面我就简单的把他们的区别介绍如下: 变量的声明有两种情况: (1)一种是需要建立存储空间的(定义.声明). 例如:int a在声明的时候就已经建立了存储空间. (2)另一种是不需要建立存储空间的(声明). 例如:extern int a其中变量a是在别的文件中定义的. 分析:前者是"定义性声明(defining declaration)"或者称为"

C语法归约之变量声明和定义(0)

0前言 闲来无事,在屋里多看了一眼<编译原理>(2V),有练练手的冲动--采用LR(1)技术,写了一个C语言的语法归约器!之所以用LR(1)是因为gcc,ucc,tcc和lcc跟商量好似的,清一色的用的LL归约技术,书上也是讲的跟天花一样漂亮,那么就说明它已经十分成熟了,如果再使用LL技术实在是炸不出价值来,源码一抓一大把,看看就够了,重写必要性不大!另外,书中的LR技术讲的项集又是大的没谱,工作量太大:如果使用工具yacc生成,又不知道它咋搞的猫腻,所以就没有踩它们的脚印,自己整套方法hig