基于C++全局变量的声明与定义的详解

 更新时间:2020年4月25日 17:45  点击:1552
(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工程代码)
复制代码 代码如下:

/**********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)静态全局变量(static)
注意使用static修饰变量,就不能使用extern来修饰,即staticextern不可同时出现。
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文件中,这样就不会给其他编译单元造成不必要的信息污染。
(6)全局常量(const)
const单独使用时,其特性与static一样(每个编译单元中地址都不一样,不过因为是常量,也不能修改,所以就没有多大关系)。
const与extern一起使用时,其特性与extern一样。
[code]
extern const char g_szBuffer[];      //写入 .h中 
const char g_szBuffer[] = "123456"; // 写入.cpp中 
[/code
[!--infotagslink--]

相关文章

  • C#创建自定义控件及添加自定义属性和事件使用实例详解

    这篇文章主要给大家介绍了关于C#创建自定义控件及添加自定义属性和事件使用的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用C#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-25
  • C# 类的声明详解

    本文主要对C# 类的声明进行详细介绍。具有一定的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • JS实现自定义简单网页软键盘效果代码

    本文实例讲述了JS实现自定义简单网页软键盘效果。分享给大家供大家参考,具体如下:这是一款自定义的简单点的网页软键盘,没有使用任何控件,仅是为了练习JavaScript编写水平,安全性方面没有过多考虑,有顾虑的可以不用,目的是学...2015-11-08
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • 自定义jquery模态窗口插件无法在顶层窗口显示问题

    自定义一个jquery模态窗口插件,将它集成到现有平台框架中时,它只能在mainFrame窗口中显示,无法在顶层窗口显示. 解决这个问题的办法: 通过以下代码就可能实现在顶层窗口弹窗 复制代码 代码如下: $(window.top.documen...2014-05-31
  • 自定义feignClient的常见坑及解决

    这篇文章主要介绍了自定义feignClient的常见坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-20
  • Vue 组件复用多次自定义参数操作

    这篇文章主要介绍了Vue 组件复用多次自定义参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-27
  • pytorch 自定义卷积核进行卷积操作方式

    今天小编就为大家分享一篇pytorch 自定义卷积核进行卷积操作方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-05-06
  • PHP YII框架开发小技巧之模型(models)中rules自定义验证规则

    YII的models中的rules部分是一些表单的验证规则,对于表单验证十分有用,在相应的视图(views)里面添加了表单,在表单被提交之前程序都会自动先来这里面的规则里验证,只有通过对其有效的限制规则后才能被提交,可以很有效地保证...2015-11-24
  • jquery自定义插件开发之window的实现过程

    这篇文章主要介绍了jquery自定义插件开发之window的实现过程的相关资料,需要的朋友可以参考下...2016-05-09
  • JavaScript var声明变量背后的原理示例解析

    只要是写过点JS代码,很简单一个var 就完事了。那对于JS编译器背后它又发生了什么呢?那就一步步通过代码来讲起。 复制代码 代码如下: x = 1; alert(x); var y = function() { alert(x); var x = 2; alert(x); } y();...2013-10-13
  • C#自定义事件监听实现方法

    这篇文章主要介绍了C#自定义事件监听实现方法,涉及C#事件监听的实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • 网页头部声明lang=”zh-cn”、lang=“zh”、lang=“zh-cmn-Hans”区别

    我们现在使用的软件都会自动在前面加一个申明了,那么在网页头部声明lang=”zh-cn”、lang=“zh”、lang=“zh-cmn-Hans”区别是什么呢?下面我们就一起来看看吧. 单...2016-09-20
  • c#之用户定义的数据类型转换介绍

    c#允许定义自己的数据类型,这意味着需要某些工具支持在自己的数据类型间进行数据转换。方法是把数据类型转换定义为相关类的一个成员运算符,数据类型转换必须声明是隐式或者显式,以说明怎么使用它...2020-06-25
  • node.js 全局变量的具体使用

    这篇文章主要介绍了node.js 全局变量的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-15
  • 使用BindingResult 自定义错误信息

    这篇文章主要介绍了使用BindingResult 自定义错误信息,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-23
  • 在Vue中获取自定义属性方法:data-id的实例

    这篇文章主要介绍了在Vue中获取自定义属性方法:data-id的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-09
  • SQL 尚未定义空闲 CPU 条件 - OnIdle 作业计划将不起任何作用

    今天在配置sql server 代理服务器的计划任务的时候发现了日志中提示这个SQL 尚未定义空闲 CPU 条件 - OnIdle 作业计划将不起任何作用信息导致无法执行计划任务,那么可以按照下面的方法解决即可...2021-07-16
  • 微信小程序 Toast自定义实例详解

    这篇文章主要介绍了微信小程序 Toast自定义实例详解的相关资料,需要的朋友可以参考下...2017-01-23