[c++]變量聲明與定義的規(guī)則詳解
聲明與定義分離
Tips:變量能且僅能被定義一次,但是可以被多次聲明。
為了支持分離式編譯,C++將定義和聲明區(qū)分開。其中聲明規(guī)定了變量的類型和名字,定義除此功能外還會申請存儲空間并可能為變量賦一個初始值。
extern
如果想聲明一個變量而非定義它,就使用關(guān)鍵字extern并且不要顯式地初始化變量:
extern int i; // 聲明i而非定義i extern int i = 1; // 定義i, 這樣做抵消了extern的作用
static
當我們在C/C++用static修飾變量或函數(shù)時,主要有三種用途:
- 局部靜態(tài)變量
- 外部靜態(tài)變量/函數(shù)
- 類內(nèi)靜態(tài)數(shù)據(jù)成員/成員函數(shù)
其中第三種只有C++中有,我們后續(xù)在面向?qū)ο蟪绦蛟O(shè)計中再探討,這里只討論靜態(tài)局部/全局變量。
1. 靜態(tài)局部變量
在局部變量前面加上static說明符就構(gòu)成靜態(tài)局部變量,例如:
// 聲明局部靜態(tài)變量 static int a; static int array[5] = {1, 2, 3, 4, 5};
- 靜態(tài)局部變量在函數(shù)內(nèi)定義,但不像自動變量那樣當函數(shù)被調(diào)用時就存在,調(diào)用結(jié)束就消失,靜態(tài)變量的生存期為整個源程序
- 靜態(tài)變量的生存期雖然為整個源程序,但是作用域與自動變量相同,即只能在定義該變量的函數(shù)內(nèi)使用該變量,退出函數(shù)后雖然變量還存在,但不能夠使用它
- 對基本類型的靜態(tài)局部變量如果在聲明時未賦初始值,則系統(tǒng)自動賦0值;而對普通局部變量不賦初始值,那么它的值是不確定的
根據(jù)靜態(tài)局部變量的特點,它的生存期為整個源程序,在離開定義它的函數(shù)(作用域)但再次調(diào)用定義它的函數(shù)時,它又可繼續(xù)使用,而且保存了前次被調(diào)用后留下的值。因此,當多次調(diào)用一個函數(shù)且要求在調(diào)用之間保留某些變量的值時,可考慮采用靜態(tài)局部變量,雖然用全局變量也可以達到上述目的,但全局變量有時會造成意外的副作用,因此最好采用局部靜態(tài)變量。
例如:
#include <iostream> void foo() { int j = 0; // 普通局部變量 static int k = 0; // 靜態(tài)局部變量 ++j; ++k; printf("j:%d, k:%d\n", j, k); } int main(void) { for (int i = 1; i <= 5; i++) { foo(); } } // 輸出: j:1, k:1 j:1, k:2 j:1, k:3 j:1, k:4 j:1, k:5
2. 靜態(tài)全局變量(C++廢棄,用匿名命名空間替代)
Tips:對于全局變量,不管是否被static修飾,它的存儲區(qū)域都是在靜態(tài)存儲區(qū),生存期為整個源程序。只不過加上static后限制這個全局變量的作用域只能在定義該變量的源文件內(nèi)。
全局變量(外部變量)的聲明之前加上static就構(gòu)成了靜態(tài)的全局變量,全局變量本身就是靜態(tài)存儲變量,靜態(tài)全局變量當然也是靜態(tài)存儲方式。這兩者在存儲方式上并無不同,這兩者的區(qū)別在于非靜態(tài)全局變量的作用域是整個源程序。當一個源程序由多個源程序組成時,非靜態(tài)的全局變量在各個源文件中都是有效的,而靜態(tài)全局變量則限制了其作用域,即只在定義該變量的源文件內(nèi)有效,在同一源程序的其他源文件中不能使用它。
這種在文件中進行靜態(tài)聲明的做法是從C語言繼承而來的,在C語言中聲明為static的全局變量在其所在的文件外不可見。這種做法已經(jīng)被C++標準取消了,現(xiàn)在的替代做法是使用匿名命名空間。
匿名命名空間:指關(guān)鍵字namespace后緊跟花括號括起來的一系列聲明語句,具有如下特點:
- 在匿名命名空間內(nèi)定義的變量具有靜態(tài)生命周期
- 匿名空間在某個給定的文件內(nèi)可以不連續(xù),但是不能跨越多個文件
- 每個文件定義自己的匿名命名空間,不同文件匿名命名空間中定義的名字對應(yīng)不同實體
- 如果在一個頭文件中定義了匿名命名空間,則該命名空間內(nèi)定義的名字在每個包含該頭文件的文件中對應(yīng)不同實體
namespace { int i; // 匿名命名空間內(nèi)定義的變量具有靜態(tài)生命周期, 作用域僅限于當前文件 }
3. 總結(jié)
static這個說明符在不同地方所起的作用域是不同的,比如把局部變量改變?yōu)殪o態(tài)變量后是改變了它的存儲方式即改變了它的生存期,把全局變量改變?yōu)殪o態(tài)變量后是改變了它的作用域,限制了它的使用范圍。
auto
1. C++98中auto用法(C++11已廢棄)
C++98 auto用于聲明變量為自動變量(擁有自動的生命周期),C++11已經(jīng)刪除了該用法,取而代之的是“變量的自動類型推斷方法”。
// c++ 98: int a = 10; // 擁有自動生命期 auto int b = 20; // 擁有自動生命期(C++11編譯不過) static int c = 30; // 延長了生命期
C++11新標準引入了auto類型說明符,讓編譯器通過初始值來自動推斷變量類型(這意味著通過auto定義的變量必須有初始值)。
// c++ 11: int a = 10; auto auto_a = a; // 自動類型推斷為int類型
2. auto會去除變量的引用語義
當引用對象作為初始值時,真正參與初始化的是引用對象的值,此時編譯器會以引用對象的類型作為auto推算的類型:
int main(void) { int i = 10; int &ri = i; auto auto_i = ri; // 去除引用語義, 自動推斷為int }
如果希望推斷出來的auto類型包含引用語義,我們需要用&明確指出:
int main(void) { int i = 10; auto &auto_i = i; // 加上引用語義, 自動推斷為int& }
3. auto忽略頂層const
auto一般會忽略掉頂層const,同時底層const會被保留下來:
int main(void) { const int ci = 10; // 常量int auto auto_ci = ci; // auto_ci被推斷為int類型 auto_ci = 20; // 正確: auto_ci非常量 const int &cr = ci; // cr是指向常量int的常量引用 auto auto_cr = cr; // auto_cr被推斷為int類型: 去除了引用語義 + 去除了頂層const auto_cr = 20; // 正確: auto_cr非常量 const int *cp = &ci; // cp是指向常量int(底層)的常量指針(頂層) auto auto_cp = cp; // auto_cp被推斷為const int*類型(指向常量int的指針): 去除了頂層const + 保留底層const // *auto_cp = 10; // 錯誤: 不能修改auto_cp指向的常量 }
如果希望推斷出來的auto類型是一個頂層const,我們需要通過const關(guān)鍵字明確指出:
int main(void) { const int ci = 10; // 常量int const auto auto_ci = ci; // auto_ci被推斷為const int類型 // auto_ci = 20; // 錯誤: auto_ci是一個常量, 禁止修改 }
const
有時我們希望定義一個不能被改變值的變量,可以使用關(guān)鍵字const對變量類型加以限定。
1. const對象必須初始化
因為const對象一經(jīng)創(chuàng)建后其值就不能再改變,所以const對象必須初始化,但是初始值可以是任意復(fù)雜的表達式:
const int i = get_size(); // 正確: 運行時初始化 const int j = 42; // 正確: 編譯時初始化 const int k; // 錯誤: k是一個未經(jīng)初始化的常量
2. 默認情況下const僅在文件內(nèi)有效
舉個例子,我們在編譯時初始化一個const對象:
const int i = 10;
編譯器會在編譯過程把用到該變量的地方都替換為對應(yīng)的值。為了執(zhí)行這個替換,編譯器必須知道變量的初始值,如果程序包含多個文件,那么每個用了這個const對象的文件都必須得能訪問到它的初始值才行(即每個文件都要定義const對象)。為了避免對同一變量的重復(fù)定義,當多個文件中出現(xiàn)同名的const對象時,其實等同于在不同文件中分別定義了獨立的變量。
/* * 下面是合法的, 不存在變量i重復(fù)定義問題 */ // foo.cpp const int i = 10; // bar.cpp const int i = 5;
如果想在多個文件之間共享const對象,那么必須在變量的定義之前添加extern關(guān)鍵字:
/* * 下面是合法的, main.cpp和foo.cpp中的const int對象是同一個 */ // foo.cpp extern const int i = 10; // main.cpp #include <iostream> int main(void) { extern int i; std::cout << "i:" << i << std::endl; }
3. 允許常量引用綁定非常量對象、字面值甚至一般表達式
一般而言,引用的類型必須與其所引用對象的類型一致,但是有兩個例外:
- 初始化常量引用時允許用任意表達式作為初始值,只要該表達式的結(jié)果能轉(zhuǎn)換成引用類型即可,允許為一個常量引用綁定非常量的對象、字面值甚至是一個一般表達式(如下)
- 可以將基類的指針或引用綁定到派生類對象上(后續(xù)面向?qū)ο笳鹿?jié)再探討)
int i = 10; const int &ri1 = i; // 合法: 綁定到非常量對象 const int &ri2 = 100; // 合法: 綁定到字面值 const int &ri3 = 1 + 1; // 合法: 綁定到一般表達式
4. 頂層const與底層const
指針本身是一個對象,因此指針本身是不是常量與指針所指對象是不是常量是兩個獨立的問題,前者被稱為頂層const,后者被稱為底層const。
Tips:指針類型既可以是頂層const也可以是底層const,其他類型要么是頂層常量要么是底層常量。
頂層const用于表示任意的對象是常量,包括算數(shù)類型、類和指針等,底層const用于表示引用和指針等復(fù)合類型的基本類型部分是否是常量。
int i = 10; int *const p1 = &i; // 頂層const: 不能改變p1的值 const int *p2 = &i; // 底層const: 不能通過p2改變i的值 const int *const p3 = &i; // 底層const + 頂層const const int &r1 = i; // 底層const: 不能通過r1改變i的值
constexpr
C++11引入了常量表達式constexpr的概念,指的是值不會改變并且在編譯期間就能得到計算結(jié)果的表達式。
const int i = 10; // 常量表達式 const int j = i + 1; // 常量表達式 const int k = size(); // 僅當size()是一個constexpr函數(shù)時才是常量表達式, 運行時才能獲得具體值就不是常量表達式
在一個復(fù)雜系統(tǒng)中,我們很難分辨一個初始值是否是常量表達式,通過constexpr關(guān)鍵字聲明一個變量,我們可以讓編譯器來驗證變量的值是否是一個常量表達式。
1. 字面值是常量表達式
算術(shù)類型、引用和指針都屬于字面值類型,自定義類則不屬于字面值類型,因此也無法被定義為constexpr。
Tips:盡管指針和引用都能被定義成constexpr,但它們的初始值卻受到嚴格限制。一個constexpr指針的初始值必須是nullptr、0或者是存儲于某個固定地址中的對象。
2. constexpr是對指針的限制
在constexpr聲明中定義了一個指針,限定符constexpr僅對指針有效,與指針所指對象無關(guān):
const int *pi1 = nullptr; // 底層const: pi1是指向整型常量的普通指針 constexpr int *pi2 = nullptr; // 頂層const: pi2是指向整型的常量指針
我們也可以讓constexpr指針指向常量:
constexpr int i = 10; constexpr const int *pi = &i; // 頂層const + 底層const
到此這篇關(guān)于[c++]變量聲明與定義的規(guī)則詳解的文章就介紹到這了,希望對大家有幫助,更多相關(guān)變量聲明與定義的規(guī)則內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!
相關(guān)文章
QT設(shè)置widget背景圖片不影響widget內(nèi)其他控件背景的方法
這篇文章主要給大家介紹了關(guān)于QT設(shè)置widget背景圖片不影響widget內(nèi)其他控件背景的方法,軟件的界面為了更直觀或美觀,常常需要通過圖片來表達,需要的朋友可以參考下2023-06-06項目之C++如何實現(xiàn)數(shù)據(jù)庫連接池
這篇文章主要介紹了項目之C++如何實現(xiàn)數(shù)據(jù)庫連接池問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03