C++全局變量的聲明和定義


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工程代碼)
/**********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來修飾,即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文件中,這樣就不會給其他編譯單元造成不必要的信息污染。
 
(6)全局常量(const)
    const單獨使用時,其特性與static一樣(每個編譯單元中地址都不一樣,不過因為是常量,也不能修改,所以就沒有多大關系)。
    const與extern一起使用時,其特性與extern一樣。
extern const char g_szBuffer[];      //寫入 .h中
const char g_szBuffer[] = "123456"; // 寫入.cpp中


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM