C++入門系列博客四 const define static關鍵字

C++ 關鍵字 Const Define Static


作者:AceTan,轉載請標明出處!


今天來討論一下在C++中很常見的三個關鍵字 Const Define Static.

0x00: const

const 限定符:有時候我們需要定義這樣一種變量,它的值是不可改變的。這時候,我們就需要用到const這個關鍵字了。

const 關鍵字在各大考試和C++筆試中經常遇到。比如下面這一道面試題:

說出const關鍵字在下列語句中的作用。

  1. const int a;
  2. int const a;
  3. const int *a;
  4. int const *a;
  5. int *const a;
  6. const int * const a;
  7. const char* func1();
  8. int GetX() const;

看完是不是一臉懵逼?看完下面的解讀,上面的問題將迎刃而解。

首先需要知道的是const修飾符所修飾的變量值不可改變。

另外需要注意的是,在用const定義關鍵字時, const int 和 int const 這兩種寫法是等價的(沒有*),它表示所定義的整型變量的值是不可改變的。

現在我們的關鍵問題是,const修飾符到底是修飾的誰。是這個整型變量,還是指針,還是指針所指向的值。

默認狀態下,const 對象僅在文件內有效。 如果想讓它在其他文件內也有效,需要添加extern關鍵字。

引用和const

const 的引用 可以把引用綁定到const對象上,就像綁定到其他對象上一樣,我們稱之為對常量的引用(reference to const)。 與普通引用不同的是,對常量的引用不能被用作修改它所綁定的對象。

舉個栗子:

int v = 123;
const int& r1 = v;        // 允許將const int& 綁定到一個普通的int對象上
const int& r2 = 123;    // 正確:r2是一個 常量引用
const int& r3 = r1 * 10;// 正確:r3是一個常量引用
int& r4 = r1 * 5;        // 錯誤:r4是一個普通的非常量引用

int& r5 = v;            // r5綁定對象v
r5 = 1;                    // 正確,r5并非一個常量
r1 = 1;                    // 錯誤:r1是一個常量引用,值不可更改

在實際編程中,如果寫的語法有錯誤,IDE一般都是會提示的,根據提示修改就好。 但我們應該記住這些常見的特性。

指針和const

指針和const鬼混在一塊,那才是噩夢的開始。

指針是對象,而引用不是。因此,可以像其他對象類型一樣,允許指針本身定為常量。常量指針(const pointer)必須要初始化。

int a = 0;
int* const p = &a;                // p是一個常量指針,指向整型數a
const double pi = 3.14159;
const double* const pPi = π    // pPi是一個指向常量對象pi的常量指針

是不是看著有點頭暈,理清這些關系的最好方法是從右往左讀

拿上面的第四個來解釋一下。離pPi最近的是const,說明pPi是一個常量對象。對象的類型是什么呢,繼續往左讀,對象的類型由聲明符的其余部分所決定的,聲明符的下一個是*, 意思是pPi是一個常量指針。該聲明語句的基本數據類型(double)確定了常量對象(pPi)指向了一個double型對象。再往左讀還是個const,說明這個double型對象也是個常量。

C++ 中有兩個術語對指針是否是常量,以及指針指向的對象是否是常量加以區分。

頂層const(top-level const) 表示指針本身是個常量。

底層const(low-level const) 表示指針所指的對象是一個常量。

這兩個問題是獨立的。

const的常見用法

  • const可以用來定義常量。但它更大的魅力是它可以修飾函數的參數、返回值,甚至函數的定義體。這也是實際應用中用的比較多的。

use const whenever you need

  • const修飾函數參數。const修飾參數,是防止傳入的參數被意外的修改。前面講到過,可以用引用或者指針作為參數來作為輸出。 這樣是不需要加const的。其他一些情況,如果傳入的參數不需要被改變,則需要加const修飾。基本類型一般是不需要const進行修飾的,因為它采取值專遞的方式。如果是用戶自定義類型,我們一般采用引用傳遞,像 Function(A& a),其中A是用戶自定義的類型。 Function(A a)的效率不如Function(A& a),因為函數體內將產生A類型的臨時對象用于復制參數a,而臨時對象的構造、復制、析構過程都將消耗時間。但這樣聲明有一個缺點,它會使調用者誤認為出入的引用時可修改的,解決的方法就是加const進行修飾。

如果傳入的參數不需要更改,那么加const修飾在實際編程中幾乎是必須的。

  • const修飾函數的返回值。const一般用來修飾以"指針傳遞"方式的函數返回值,它表示函數的返回值(即指針)的內容是不可修改的。該返回值只能被賦給加const修飾的同類型指針。修飾以"值傳遞"方式的函數返回值是沒有意義的。

const  char* GetStr();
const char* someStr = GetStr();        // 正確
char* str2 = GetStr();                // 錯誤
  • const修飾成員函數。 任何不會修改數據成員的函數都應該聲明為const類型。確切的說,const是修飾this指向的對象的。

class A 
{
private:
    int num;                // 成員變量

public:
    void Func(int x);        // 其實原型是兩個參數 Func(A* const this, int x)

    void Func2(int y) const;// 原型是Func2(const A* const this, int y)

};

void A::Func(int x)
{
    num = num * x;        // 正確:this所指向的對象沒有const修飾,可以更改
}

void A::Func2(int y) const
{
    num = num * y;       // 錯誤:this所指向的對象被const修飾,無法更改這個對象的數據。
}

至于const為什么放在后面,想必各位看官已經猜到了。原型的第一個參數是被省略了,無法修飾,大概也只能放到函數的后面了,雖然看起來怪怪的。


0x01: define

準確的說應該是#define。 它繼承自C語言,是一個預處理指令。所謂的預處理,就是在編譯之前執行一段程序,可以部分的改變我們寫的程序。 之前我們見過的#include就是一條預處理命令,它將所包含的文件替換到所用指令的地方。#define指令是用來把一個名字定義預處理變量的。預處理變量有兩種狀態:已定義與未定義。#define是用來定義它的。與之對應的是另外兩個指令分別檢查預處理變量是否已經定義。#ifdef 當且僅當變量已定義時為真,#ifndef當且僅當變量未定義時為真。一旦檢查為真,則執行后續的操作直到遇到#endif 指令為止。

預處理變量無視C++語言中關于作用域的規則

#define 的常見用法

  • 頭文件保護(header guard)。在定義一個頭文件時,建議習慣性地加上頭文件保護,沒必要太在乎你的程序到底需不需要。這是一個比較好的編程習慣。 一般預處理變量名我們用所要定義的類名的大寫來命名。示例如下:

#ifndef _FORM_RIDE_H_
#define _FORM_RIDE_H_

// 你的代碼

#endif // _FORM_RIDE_H_

加了頭文件保護,別人就不用擔心是否重復引入你的文件了。

  • 改變程序執行流程。 平臺的差異性,客服端和服務器的差異性,有時候為了讓代碼具有更好的兼容性,通常定義這些宏來執行不同的邏輯代碼。這時候只需要一個總的控制文件,就能讓代碼在不同的情形下執行不同的邏輯。

#ifdef _CLIENT_
    if (pEntity == NULL)
    {
        return false;
    }
    string pathname = string(pEntity->GetCore()->GetResourcePath()) + "share/rule/ridestrength/ride_strength_info.xml";
#elif defined(_SERVER_STUB_)
    string pathname = string(pKernel->GetResourcePath()) + "share/rule/ridestrength/ride_strength_info.xml";
#elif defined(_SERVER_MEMBER_) || defined(_SERVER_ROOM_)
    string pathname = string(pKernel->GetResourcePath()) + "ini/rule/ridestrength/ride_strength_info.xml";
#endif
  • 充當函數的作用。說是充當函數,其實和函數還是有挺大差別。只是在用到的地方進行替換,使程序看起來更簡潔。下面的代碼是用來檢查循環的,可以避免死循環。預設一個循環最大值,然后調用這個宏讓它檢查。

#include <iostream>

using namespace std;

int g_nMaxCirculateCount = 100;

void SetMaxCirculateCount(int count)
{
    if (g_nMaxCirculateCount != count)
    {
        g_nMaxCirculateCount = count;
    }
}

#define  LoopBeginCheck(name) \
     int nCheck##name##Count = 0;

#define  LoopDoCheck(name) \
        nCheck##name##Count++;\
        if((g_nMaxCirculateCount > 0) && (nCheck##name##Count > g_nMaxCirculateCount))\
        {\
            cout << "文件路徑:" << __FILE__ << endl \
                 << "函數:" << __FUNCTION__<< endl \
                 << "所在行:" << __LINE__ << endl \
                 << "循環次數:" << nCheck##name##Count << endl; \
            break;\
        }


int main()
{
    int n = 77 * 88;            // 實際需要執行的循環次數

    SetMaxCirculateCount(10);    // 設置的最大循環次數
    LoopBeginCheck(var);
    for (int i = 1; i < n; i++)
    {
        LoopDoCheck(var);

        cout << "循環結束" << endl;
    }
 
    return 0;
}

這里的 \ 起鏈接換行的作用,##name## 是拼接參數。 __FILE__等則是一些內置宏。

0x02: static

static定義的變量存儲在靜態數據區,在靜態數據區,內存中所有的字節默認值都是0x00。

靜態變量作用范圍在一個文件內,程序開始時分配空間,結束時釋放空間,默認初始化為0,使用時可以改變其值。與全局變量不同的是,靜態變量或靜態函數只有本文件內的代碼才能訪問它,它的名字在其它文件中不可見。

static的主要有以下三個特性:

  • 表示代碼退出一個塊后,仍然能夠存在的局部變量。因為數據會存儲在靜態數據區。

  • 用來表示不能被其它文件訪問的全局變量和函數。

  • 表示屬于一個類而不是屬于此類的任何特定對象的變量和函數。這個和Java中static關鍵字的意義相同。

有了static關鍵字,變量就變的有些復雜了。是時候理清一下各類型變量的作用域范圍了。

常見的變量分為如下幾種:全局變量、靜態全局變量、靜態局部變量和局部變量。

  • 全局變量。存儲:靜態存儲區域。 作用域:在整個工程文件內都有效。

  • 靜態全局變量。存儲:靜態存儲區域。 作用域:在定義它的文件內有效。

  • 靜態局部變量 存儲:靜態存儲區域。 作用域:只在定義它的函數內有效。程序僅分配一次內存,函數返回后,改變量不會消失。

  • 局部變量 存儲:內存棧中。 作用域:只在定義它的函數內有效。程序返回后局部變量被回收。

static函數在內存中只有一份,普通函數在每個被調用中維持一份拷貝。


#include <iostream>

using namespace std;

int n = 100;            // 全局變量
static int m;            // 靜態全局變量

class MyClass
{
public:

    void ChangeX(int x);        // 普通成員函數
    static void ChangeY(int y);    // 靜態成員函數

    int GetY();                    // 獲取全局變量的值

private:
    int m_x;                    // 普通成員變量
    static int m_y;                // 靜態成員變量 

};


void MyClass::ChangeX(int x)
{
    ++m_x;

    static int times = 0;
    ++times;
    cout << "第" << times << "次調用該函數" << endl;
}

void MyClass::ChangeY(int y)
{
    //m_x = y;        // 報錯:靜態成員函數只能引用靜態成員變量
    m_y = y;
}

int MyClass::GetY()
{
    cout << "靜態成員變量y的值為:" << m_y << endl;
    return m_y;
}


int MyClass::m_y = 0;//定義并初始化靜態數據成員 

int main()
{
    n = 88;
    cout << "全局變量改為" << n << endl;

    cout << "靜態全局變量的初始值為" << m << endl;

    MyClass cls1;            // 創建第一個類
    cls1.ChangeY(111);        // 改變了靜態成員的值

    MyClass cls2;            // 創建第一個類
    cls2.ChangeX(1);        // 連續調用3次
    cls2.ChangeX(2);
    cls2.ChangeX(3);

    cls2.GetY();            // 直接輸出靜態成員變量看看

    return 0;

}

輸出結果:
全局變量改為88

靜態全局變量的初始值為0

第1次調用該函數

第2次調用該函數

第3次調用該函數

靜態成員變量y的值為:111

輸出結果印證了上面所討論的內容。

0x03: 結束語

const define static在實際編程中應用的非常多,各位看官應多加理解,靈活應用。

C++
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容