溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

C++中內(nèi)存模型和名稱空間的示例分析

發(fā)布時(shí)間:2021-09-13 16:29:47 來(lái)源:億速云 閱讀:131 作者:小新 欄目:開(kāi)發(fā)技術(shù)

小編給大家分享一下C++中內(nèi)存模型和名稱空間的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

    1. 單獨(dú)編譯

    和C語(yǔ)言一樣,C++允許甚至鼓勵(lì)程序員將組件放在獨(dú)立的文件中。可以單獨(dú)編譯這些文件,然后將它們連接成可執(zhí)行程序。(通常,C++編譯器即編譯程序,也管理連接器)。如果只修改了一個(gè)文件,則可以只重新編譯該文件,然后將它與其他文件的編譯版本鏈接。這使得大程序的管理更便捷。

    C++開(kāi)發(fā)人員使用 #include 導(dǎo)入頭文件,與其將結(jié)構(gòu)聲明加入到每一個(gè)文件中,不如將其放在頭文件中,然后在每一個(gè)源代碼文件中包含該頭文件。這樣,要修改結(jié)構(gòu)聲明時(shí),只需在頭文件中做一次改動(dòng)即可。另外,可以將函數(shù)原型放在頭文件中。因此,可以將原來(lái)的程序分成三部分。

    • 頭文件:包含結(jié)構(gòu)聲明和使用這些結(jié)構(gòu)的函數(shù)的原型

    • 源代碼文件:包含與結(jié)構(gòu)有關(guān)的函數(shù)的代碼

    • 源代碼文件:包含調(diào)用與結(jié)構(gòu)相關(guān)的函數(shù)的代碼

    請(qǐng)不要將函數(shù)定義或變量聲明放到頭文件中。這樣做對(duì)簡(jiǎn)單情況可能是可行的,但通常會(huì)引來(lái)麻煩。例如,如果在頭文件包含一個(gè)函數(shù)定義,然后在其他兩個(gè)文件中包含該頭文件,則同一個(gè)程序中將包含同一個(gè)函數(shù)的兩個(gè)定義,除非函數(shù)是內(nèi)聯(lián)的,否則將會(huì)出錯(cuò)。下面列出了頭文件中常包含的內(nèi)容。

    • 函數(shù)原型

    • 使用#define或const定義的符號(hào)常量

    • 結(jié)構(gòu)聲明

    • 類聲明

    • 模板聲明

    • 內(nèi)聯(lián)函數(shù)

    將結(jié)構(gòu)聲明放在頭文件中是可以的,因?yàn)樗鼈儾粍?chuàng)建變量,而只是在源代碼文件中聲明結(jié)構(gòu)變量時(shí),告訴編譯器如何創(chuàng)建該結(jié)構(gòu)變量。同樣,模板聲明不是將被編譯的代碼,它們指示編譯器如果和生成與源代碼中的函數(shù)調(diào)用相匹配的函數(shù)定義。被聲明為const的數(shù)據(jù)和內(nèi)聯(lián)函數(shù)有特殊的鏈接屬性,因此可以將其放在頭文件中,而不會(huì)引起問(wèn)題。

    頭文件管理

    在同一個(gè)文件中只能將同一個(gè)頭文件包含一次。有一種標(biāo)準(zhǔn)的C/C++技術(shù)可以避免多次包含同一個(gè)頭文件。它是基于預(yù)處理器編譯指令 #ifndef (即 if not defined)的。下面代碼片段意味著僅當(dāng)以前沒(méi)有使用預(yù)處理器編譯指令#define定義名稱COORDIN_H_時(shí),才處理 #ifndef 和 #endif之間的語(yǔ)句:

    #ifndef COORDIN_H_
    ...
    #endif

    通常,使用#define語(yǔ)句來(lái)創(chuàng)建符號(hào)常量,如下所示:

    #define MAXIMUM 4096

    但只要將#define用于名稱,就足以完成該名稱的定義,如下所示:

    #ifndef COORDIN_H_
    #define COORDIN_H_
    // place include file contents here
    #endif

    多個(gè)庫(kù)的鏈接

    C++標(biāo)準(zhǔn)允許每個(gè)編譯器設(shè)計(jì)人員以他認(rèn)為合適的方式實(shí)現(xiàn)名稱修飾,因此由不同編譯器創(chuàng)建的二進(jìn)制模塊(對(duì)象代碼文件)很可能無(wú)法正確地鏈接。也就是說(shuō),兩個(gè)編譯器將為同一個(gè)函數(shù)生成不同的修飾名稱。名稱的不同將使鏈接器無(wú)法將一個(gè)編譯器生成的函數(shù)調(diào)用與另一個(gè)編譯器生成的函數(shù)定義匹配。在鏈接編譯模塊時(shí),請(qǐng)確保所有對(duì)象文件或庫(kù)都是由同一個(gè)編譯器生成的。如有源代碼,通??梢杂米约旱木幾g器重新編譯源代碼來(lái)消除鏈接錯(cuò)誤。

    2.存儲(chǔ)持續(xù)性、作用域和鏈接性

    接下來(lái)擴(kuò)展存儲(chǔ)類別如何影響信息在文件間的共享。C++使用三種(在C++11中是四種)不同的方案來(lái)存儲(chǔ)數(shù)據(jù),這些方案的區(qū)別就在于數(shù)據(jù)保留在內(nèi)存中的時(shí)間。

    • 自動(dòng)存儲(chǔ)持續(xù)性:在函數(shù)定義中聲明的變量(包括函數(shù)參數(shù))的存儲(chǔ)持續(xù)性為自動(dòng)的。它們?cè)诔绦蜷_(kāi)始執(zhí)行其所屬的函數(shù)或代碼塊時(shí)被創(chuàng)建,在執(zhí)行完函數(shù)或代碼塊時(shí),它們使用的內(nèi)存被釋放。C++有兩種存儲(chǔ)持續(xù)性為自動(dòng)的變量。

    • 靜態(tài)存儲(chǔ)持續(xù)性:在函數(shù)定義外定義的變量和使用關(guān)鍵字static定義的變量的存儲(chǔ)持續(xù)性都為靜態(tài)。它們?cè)诔绦蛘麄€(gè)運(yùn)行過(guò)程中都存在。C++有3中存儲(chǔ)持續(xù)性為靜態(tài)的變量。

    • 線程存儲(chǔ)持續(xù)性:當(dāng)前,多核處理器很常見(jiàn),這些CPU可同時(shí)處理多個(gè)執(zhí)行任務(wù)。這讓程序能夠?qū)⒂?jì)算放在可并行處理的不同線程中。如果變量是使用關(guān)鍵字thread_local聲明的,則其聲明周期與所屬的線程一樣長(zhǎng)。

    • 動(dòng)態(tài)存儲(chǔ)持續(xù)性:用new運(yùn)算符分配的內(nèi)存將一直存在,直到使用delete運(yùn)算符將其釋放或程序結(jié)束為止。這種內(nèi)存的存儲(chǔ)持續(xù)性為動(dòng)態(tài),有時(shí)被稱為自由存儲(chǔ)或堆。

    2.1 作用域和鏈接

    作用域描述了名稱在文件的多大范圍內(nèi)可見(jiàn)。例如,函數(shù)中定義的變量可在該函數(shù)中使用,但不能在其他函數(shù)中使用;而在文件中的函數(shù)定義之前定義的變量則可在所有函數(shù)中使用。鏈接性(linkage)描述了名稱如何在不同單元間共享。鏈接性為外部的名稱可在文件間共享,鏈接性為內(nèi)部的名稱只能由一個(gè)文件中的函數(shù)共享。自動(dòng)變量的名稱沒(méi)有鏈接性,因?yàn)樗鼈儾荒芄蚕怼?/p>

    C++變量的作用域有多種。作用域?yàn)榫植康淖兞恐辉诙x它的代碼塊中可用,代碼塊是由花括號(hào)括起的一系列語(yǔ)句。

    C++函數(shù)的作用域可以是整個(gè)類或整個(gè)名稱空間(包括全局的),但不能是局部的(因?yàn)椴荒茉诖a塊內(nèi)定義的函數(shù),如果函數(shù)的作用域?yàn)榫植?,則只對(duì)它自己是可見(jiàn)的,因此不能被其他函數(shù)調(diào)用。這樣的函數(shù)將無(wú)法運(yùn)行)。

    2.2 自動(dòng)存儲(chǔ)持續(xù)性

    默認(rèn)情況下,在函數(shù)中聲明的函數(shù)參數(shù)和變量的存儲(chǔ)持續(xù)性為自動(dòng),作用域?yàn)榫植?,沒(méi)有鏈接性。也就是說(shuō),如果main()中聲明了一個(gè)名為texas的變量,并在函數(shù)oil()中也聲明了一個(gè)名稱texas的變量,則創(chuàng)建了兩個(gè)獨(dú)立的變量——只有在定義他們的函數(shù)中才能使用它們。堆iol()的texas執(zhí)行的任何操作都不會(huì)影響main()中的texas,反之亦然。另外,當(dāng)程序開(kāi)始執(zhí)行這些變量所屬的代碼塊時(shí),將為其分配內(nèi)存;當(dāng)函數(shù)結(jié)束時(shí),這些變量都將消失。

    使用C++11中的auto

    在C++11中,關(guān)鍵字auto用于自動(dòng)類型推斷。但在C語(yǔ)言和以前的C++版本中,auto的含義截然不同,它用于顯式地指出變量為自動(dòng)存儲(chǔ):

    int froob(int n)
    {
        auto float ford; //ford has automatic stroage
        ...
    }

    由于只能將關(guān)鍵字用于默認(rèn)為自動(dòng)的變量,因此程序員幾乎不使用它。它的主要用途是指出當(dāng)前變量為局部自動(dòng)變量。在C++11中,這種用法不再合法。

    1,自動(dòng)變量的初始化

    可以使用任何在聲明時(shí)其值已知的表達(dá)式來(lái)初始化自動(dòng)變量,下面的示例初始化變量x,y,z:

    int w;
    int x=5;
    int big =INT_MAX -1;
    int y=x *x;
    int y=2*x;
    cin>>w;
    int z=3*w;

    2,自動(dòng)變量的初始化

    了解典型的C++編譯器如何實(shí)現(xiàn)自動(dòng)變量有助于更深入地了解自動(dòng)變量。由于自動(dòng)變量的數(shù)目隨函數(shù)的開(kāi)始和結(jié)束而增減,因此程序必須在運(yùn)行時(shí)對(duì)自動(dòng)變量進(jìn)行管理。常用的方法是留出一段內(nèi)存,并將其視為棧,以管理變量的增減。之所以被稱為棧,是由于新數(shù)據(jù)被象征性地放在原有數(shù)據(jù)的上面(也就是說(shuō),在相鄰的內(nèi)存單元中,而不是在同一個(gè)內(nèi)存單元中),當(dāng)程序使用完后,將其從棧中刪除。棧的默認(rèn)長(zhǎng)度取決于實(shí)現(xiàn),但編譯器通常提供改變棧長(zhǎng)度的選項(xiàng)。程序使用兩個(gè)指針來(lái)跟蹤棧,一個(gè)指針指向棧底——棧的開(kāi)始位置,另一個(gè)指針指向堆頂——下一個(gè)可用內(nèi)存單元。當(dāng)函數(shù)被調(diào)用時(shí),其自動(dòng)變量被加入到棧中,棧頂指針指向變量后面的下一個(gè)可用的內(nèi)存單元。函數(shù)結(jié)束時(shí),棧頂指針被重置為函數(shù)被調(diào)用前的值,從而釋放新變量使用的內(nèi)存。

    棧時(shí)LIFO(后進(jìn)先出)的,即最后加入到棧中的變量首先被彈出。這種設(shè)計(jì)簡(jiǎn)化了參數(shù)傳遞。函數(shù)調(diào)用將其參數(shù)的值放在棧頂,然后重新設(shè)置棧頂指針。被調(diào)用的函數(shù)根據(jù)其形參描述來(lái)確定每個(gè)參數(shù)的地址。下圖中,函數(shù)fib()被調(diào)用時(shí),傳遞一個(gè)2字節(jié)的int和一個(gè)4字節(jié)的long。這些值被加入到棧中。當(dāng)fib()開(kāi)始執(zhí)行時(shí),它將real和tell同這兩個(gè)值關(guān)聯(lián)起來(lái)。當(dāng)fib()結(jié)束時(shí),棧頂指針重新指向以前的位置。新值沒(méi)有被刪除,但不再被標(biāo)記,它們所占據(jù)的空間將被下一個(gè)將值加入到棧中的函數(shù)調(diào)用所使用。

    C++中內(nèi)存模型和名稱空間的示例分析

    3,寄存器變量

    關(guān)鍵字register最初是由C語(yǔ)言引入的,它建議編譯器使用CPU寄存器來(lái)存儲(chǔ)自動(dòng)變量:

    register int count_fast; //request for a rregister variable

    這旨在提高訪問(wèn)變量的速度。

    在C++11之前,這個(gè)關(guān)鍵字在C++中的用法始終未變,只是隨著硬件和編譯器變得越來(lái)越復(fù)雜,這種提示表明變量用的很多,編譯器可以對(duì)其做特殊處理。在C++11中,這種提示作用也失去了,編輯案子register只是顯式地指出變量是自動(dòng)的。鑒于關(guān)鍵字register只能用于原本就是自動(dòng)的變量,使用它唯一的原因是,指出程序員想使用一個(gè)自動(dòng)變量,這個(gè)變量的名稱可能與外部變量相同。這與auto以前的用途完全相同。然而,保留關(guān)鍵字register的重要原因是,避免使用了該關(guān)鍵字的現(xiàn)有代碼非法。

    2.3 靜態(tài)持續(xù)變量

    和C語(yǔ)言一樣,C++也為靜態(tài)存儲(chǔ)持續(xù)性變量提供了3種鏈接性:外部鏈接性(可在其他文件中訪問(wèn))、內(nèi)部鏈接性(只能在當(dāng)前文件中訪問(wèn))和無(wú)鏈接性(只能在當(dāng)前函數(shù)或代碼塊中訪問(wèn))。這3種鏈接性都在整個(gè)程序執(zhí)行期間存在,與自動(dòng)變量相比,它們的壽命更長(zhǎng)。由于靜態(tài)變量的數(shù)目在程序運(yùn)行期間是不變的,因此程序不需要使用特殊的裝置(如棧)來(lái)管理它們。編譯器將分配固定的內(nèi)存塊來(lái)存儲(chǔ)所有的靜態(tài)變量,這些變量在整個(gè)程序執(zhí)行期間一直存在。另外,如果沒(méi)有顯式地初始化靜態(tài)變量,編譯器將把它設(shè)置為0。在默認(rèn)情況下,靜態(tài)數(shù)組和結(jié)構(gòu)將每個(gè)元素或成員的所有位都設(shè)置為0。

    創(chuàng)建外部靜態(tài)持續(xù)變量,在代碼塊外面聲明它;

    創(chuàng)建內(nèi)部的靜態(tài)持續(xù)變量,在代碼塊的外面聲明它,并使用static限定符;

    創(chuàng)建無(wú)鏈接的靜態(tài)持續(xù)變量,在代碼塊中聲明它,并使用static限定符。

    ...
    int global =1000;  // static duration, external linkage
    static int one_file =50; //static duration, internal linkage
    int main()
    {
        ...
    }
    void funct1(int n)
    {
        static int count =0; // static duration, no linkage
        int llama =0; 
    }

    所有的靜態(tài)持續(xù)變量都有下述初始化特征: 未被初始化的靜態(tài)變量的所有位都被設(shè)置為0。這種變量稱為零初始化的(zero-initialized)。

    下表9.1總結(jié)了引入名稱空間之前使用的初始化特征。指出了關(guān)鍵字static的兩種用法,但含義有些不同:用于局部聲明,以指出變量是無(wú)鏈接性的靜態(tài)變量,static表示的是存儲(chǔ)持續(xù)性;而用于代碼塊外的聲明時(shí),static表示內(nèi)部鏈接性,而變量已經(jīng)是靜態(tài)持續(xù)性了。有人稱之為關(guān)鍵字重載,即關(guān)鍵字的含義取決于上下文。

    C++中內(nèi)存模型和名稱空間的示例分析

    2.4 靜態(tài)持續(xù)性、外部鏈接性

    鏈接性為外部的變量通常稱為外部變量,它們的存儲(chǔ)持續(xù)性為靜態(tài),作用域?yàn)檎麄€(gè)文件。外部變量是在函數(shù)外部定義的,因此對(duì)所有函數(shù)而言都是外部的。例如,可以在main()前面或頭文件中定義它們??梢栽谖募形挥谕獠孔兞慷x后面的任何函數(shù)中使用它,因此外部變量也稱為全局變量。

    1,單定義規(guī)則

    一方面,在每個(gè)使用外部變量的文件中,都必須聲明它;另一方面,C++有“單定義規(guī)則”(One Definition Rule, ODR), 該規(guī)則指出,變量只能有一次定義。為滿足這種需求,C++提供了兩種變量聲明。一種是定義聲明或簡(jiǎn)稱定義,它給變量分配存儲(chǔ)空間;另一種是引用聲明或簡(jiǎn)稱聲明,它不給變量分配存儲(chǔ)空間,因?yàn)樗靡延械淖兞俊?/p>

    引用聲明使用關(guān)鍵字extern,且不進(jìn)行初始化;否則,聲明為定義,導(dǎo)致分配存儲(chǔ)空間。

    double up;  //definition, up is 0;
    extern int blem;     //blem defined elsewhere
    extern char gr ='z';  //definition because initialized

    如果要在多個(gè)文件中使用外部變量,只需在一個(gè)文件中包含該變量的定義(單定義規(guī)則),但在使用變量的其他所有文件中,都必須使用關(guān)鍵字extern聲明它。

    全局變量和局部變量

    既然可以選擇使用全局變量或局部變量,那么到底應(yīng)使用哪種呢?首先,全局變量很有吸引力——因?yàn)樗械暮瘮?shù)能訪問(wèn)全局變量,因此不用傳遞參數(shù)。但易于訪問(wèn)的代價(jià)很大——程序不可靠。計(jì)算經(jīng)驗(yàn)表明,程序越能避免對(duì)數(shù)據(jù)進(jìn)行不必要的訪問(wèn),就越能保持?jǐn)?shù)據(jù)的完整性。通常情況下,應(yīng)使用局部變量,應(yīng)在需要知曉時(shí)才傳遞數(shù)據(jù),而不應(yīng)不加區(qū)分地使用全局變量來(lái)使數(shù)據(jù)可用。

    2.5 靜態(tài)持續(xù)性、內(nèi)部鏈接性

    將static限定符用于作用域?yàn)檎麄€(gè)文件的變量時(shí),該變量的鏈接性將為內(nèi)部的。在多文件程序中,內(nèi)部鏈接性和外部鏈接性之間的差別很有意義。鏈接性為內(nèi)部的變量只能在其所屬的文件中使用,但常規(guī)外部變量都具有外部鏈接性,即可以在其他文件中使用。

    可使用外部變量在多文件程序的不同部分之間共享數(shù)據(jù);可使用鏈接性為內(nèi)部的靜態(tài)變量在同一個(gè)文件中的多個(gè)函數(shù)之間共享數(shù)據(jù)(名稱空間提供了另外一種共享數(shù)據(jù)的方法)。另外,如果將作用域?yàn)檎麄€(gè)文件的變量變?yōu)殪o態(tài)的,就不必?fù)?dān)心其名稱與其他文件中的作用域?yàn)檎麄€(gè)文件的變量發(fā)生沖突。

    2.6 靜態(tài)存儲(chǔ)持續(xù)性、無(wú)鏈接性

    無(wú)鏈接性的局部變量是這樣創(chuàng)建的,將static限定符用于在代碼塊中定義的變量。在代碼塊中使用static時(shí),將導(dǎo)致局部變量的存儲(chǔ)持續(xù)性為靜態(tài)的。這意味著雖然該變量只在該代碼塊中可用,但它在該代碼塊不處于活動(dòng)狀態(tài)時(shí)仍然存在。因此在兩次函數(shù)調(diào)用之間,靜態(tài)局部變量的值將保持不變。(靜態(tài)變量適用于再生)。另外,如果初始化了靜態(tài)局部變量,則程序只在啟動(dòng)時(shí)進(jìn)行一次初始化。以后再調(diào)用函數(shù)時(shí),將不會(huì)像自動(dòng)變量那樣再次被初始化。

    2.7 說(shuō)明符和限定符

    有些被稱為存儲(chǔ)說(shuō)明符或cv-限定符的C++關(guān)鍵字提供了其他有關(guān)存儲(chǔ)的信息。下面是存儲(chǔ)說(shuō)明符:

    • auto(在C++11中不再是說(shuō)明符)

    • register;

    • static

    • extern

    • thread_local(C++11新增的)

    • mutable

    其中的大部分已經(jīng)介紹過(guò)了,在同一個(gè)聲明中不能使用多個(gè)說(shuō)明符,但thread_local除外,它可與static或extern結(jié)合使用。前面講過(guò),在C++11之前,可以在聲明中使用關(guān)鍵字auto指出變量為自動(dòng)變量;但在C++11中,auto用于自動(dòng)類型推斷。關(guān)鍵字register用于在聲明中指示寄存器存儲(chǔ),而在C++11中,它只是顯式地指出變量是自動(dòng)的。關(guān)鍵字static被用在作用域?yàn)檎麄€(gè)文件的聲明中時(shí),表示內(nèi)部鏈接性;被用于局部聲明中,表示局部變量的存儲(chǔ)持續(xù)性為靜態(tài)的。關(guān)鍵字extern表明是引用聲明,即聲明在其他地方定義的變量。關(guān)鍵字thread_local指出變量的持續(xù)性與其所屬線程的持續(xù)性相同。thread_local變量之于線程,猶如常規(guī)靜態(tài)變量之于整個(gè)程序。關(guān)鍵字mutable的含義將根據(jù)const來(lái)解釋,因此先來(lái)介紹cv-限定符,然后再解釋它。

    1. cv-限定符

    下面就是cv限定符

    • const

    • volatile

    const是常見(jiàn)的cv-限定符,它表明,內(nèi)存被初始化后,程序便不能再對(duì)它進(jìn)行修改。

    關(guān)鍵字volatile表明,即使程序代碼沒(méi)有對(duì)內(nèi)存單元進(jìn)行修改,其值也可能發(fā)生變化。聽(tīng)起來(lái)似乎很神秘,實(shí)際上并非如此。例如,可以將一個(gè)指針指向某個(gè)硬件位置,其中包含了來(lái)自串行端口的時(shí)間或信息。在這種情況下,硬件(而不是程序)可能修改其中的內(nèi)容?;蛘邇蓚€(gè)程序可能互相影響,共享數(shù)據(jù)。該關(guān)鍵字的作用是改善編譯器的優(yōu)化能力。例如,假設(shè)編譯器發(fā)現(xiàn),程序在幾條語(yǔ)句中兩次使用了某個(gè)變量的值,則編譯器可能不是讓程序查找這個(gè)值兩次,而是將這個(gè)值緩存到寄存器中。這種優(yōu)化假設(shè)變量的值在這兩次使用之間不會(huì)變化。如果不將變量聲明為volatile,則編譯器將進(jìn)行這種優(yōu)化,將變量聲明為volatile,相當(dāng)于告訴編譯器,不要進(jìn)行這種優(yōu)化。

    2. mutable

    現(xiàn)在回到mutable??梢杂盟鼇?lái)指出,即使結(jié)構(gòu)(或類)變量為const。其某個(gè)成員也可以被修改。例如:

    struct data
    {
        char name[30];
        mutable int accesses;
        ...
    };
    const data veep = {"Claybourne Clodde",0, ...};
    strcpy(veep.name, "Joye Joux"); //not allowed
    veep.accesses++;                //allowed

    veep的const限定符禁止程序修改veep的成員,但access成員的mutable說(shuō)明符使得access不受這種限制。

    3. 再談const

    在C++(但不是在C語(yǔ)言)中,const限定符對(duì)默認(rèn)存儲(chǔ)類型稍有影響。在默認(rèn)情況下全局變量的鏈接性為外部的,但const全局變量的鏈接性為內(nèi)部的,也就是說(shuō),在C++看來(lái),全局const定義就像使用了static說(shuō)明符一樣

    const int fingers =10;  //same as static const int fingers =10;
    int main(void)
    {
            ...
    }

    C ++修改了常量類型的規(guī)則,讓程序員更輕松。例如,假設(shè)將一組常量放在頭文件中,并在同一個(gè)程序的多個(gè)文件中使用該頭文件。那么預(yù)處理器將頭文件的內(nèi)容包含到每個(gè)源文件中,所有的源文件都將包含類似下面的定義:

    const int finers =10;
    const char * warning ="Wak!";

    如果全局const聲明的鏈接性像常規(guī)變量那樣是外部的,則根據(jù)單定義規(guī)則,這將出錯(cuò)。也就是說(shuō),只能有一個(gè)文件可以包含前面的聲明,而其他文件必須使用extern關(guān)鍵字來(lái)提供引用聲明。另外,只有使用extern關(guān)鍵字的聲明才能進(jìn)行初始化:

    //extern would be required if ocnst had external linkage
    extern const int fingers;  //can't be initialized
    extern const char *warning;

    因此,需要為某個(gè)文件使用一組定義,而其他文件使用另一組聲明。然而,由于外部定義const數(shù)據(jù)的鏈接性為內(nèi)部的,因此可以在所有文件中使用相同的聲明。

    內(nèi)部鏈接性還意味著,每個(gè)文件都有自己的一組常量,而不是所有文件共享一組常量。每個(gè)定義都是其所屬文件私有的,這就是能夠?qū)⒊A慷x放在頭文件中的原因。這樣,只要在兩個(gè)源代碼文件中包括同一個(gè)頭文件,則他們將獲得同一組常量。

    如果出于某種原因,程序員希望某個(gè)常量的鏈接性為外部的,則可以使用extern關(guān)鍵字來(lái)覆蓋默認(rèn)的內(nèi)部鏈接性。

    在函數(shù)或代碼塊中聲明const時(shí),其作用域?yàn)榇a塊,即僅當(dāng)程序執(zhí)行該代碼塊中的代碼時(shí),該常量才是可用的。這意味著在函數(shù)或代碼塊中創(chuàng)建常量時(shí),不必?fù)?dān)心其名稱與其他地方定義的常量發(fā)生沖突。

    2.8 函數(shù)和鏈接性

    和變量一樣,函數(shù)也有鏈接性,雖然可選擇的范圍比變量小。和C語(yǔ)言一樣,C++不允許在一個(gè)函數(shù)中定義另外一個(gè)函數(shù),因此所有的存儲(chǔ)持續(xù)性都自動(dòng)為靜態(tài)的,即在整個(gè)程序執(zhí)行期間都一直存在。在默認(rèn)情況下,函數(shù)的鏈接性為外部的,即可以在文件間共享。

    實(shí)際上,可在函數(shù)原型中使用關(guān)鍵字extern來(lái)指出函數(shù)是在另一個(gè)文件中定義的,不過(guò)這是可選的(要讓程序在另一個(gè)文件中查找函數(shù),該文件必須作為程序的組成部分被編譯,或者是由鏈接程序搜索的庫(kù)文件)。還可以使用關(guān)鍵字static將函數(shù)的鏈接性設(shè)置為內(nèi)部的,使之只能在一個(gè)文件中使用。必須同時(shí)在原型和函數(shù)定義中使用該關(guān)鍵字。

    static int private(double x);
    ...
    static int private(double x)
    {
        ...
    }

    這意味著該函數(shù)只在這個(gè)文件中課件,還意味著可以在其他文件中定義同名的函數(shù)。和變量一樣,在定義靜態(tài)函數(shù)的文件中,靜態(tài)函數(shù)將覆蓋外部定義,因此即使在外部定義了同名的函數(shù),該文件仍將用靜態(tài)函數(shù)。

    單定義規(guī)則也適用于非內(nèi)聯(lián)函數(shù),因此對(duì)于每個(gè)非內(nèi)聯(lián)函數(shù),程序只能包含一個(gè)定義,對(duì)于鏈接性為外部的函數(shù)來(lái)說(shuō),這意味著在多文件程序中,只能有一個(gè)文件(該文件可能是庫(kù)文件,而不是您提供的)包含該函數(shù)的定義,但使用該函數(shù)的每個(gè)文件都應(yīng)包含其函數(shù)原型。

    內(nèi)聯(lián)函數(shù)不受這項(xiàng)規(guī)則的約束,這允許程序員能夠?qū)?nèi)聯(lián)函數(shù)的定義放在頭文件中。這樣,包含了頭文件的每個(gè)文件都有內(nèi)聯(lián)函數(shù)的定義。然而,C++要求同一個(gè)函數(shù)的所有內(nèi)聯(lián)定義都必須相同。

    2.9 語(yǔ)言鏈接性

    另一種形式的鏈接性——稱為語(yǔ)言鏈接性也對(duì)函數(shù)有影響。鏈接程序要求每個(gè)不同的函數(shù)都有不同的符號(hào)名。在C語(yǔ)言中,一個(gè)名稱只對(duì)應(yīng)一個(gè)函數(shù),因此這很容易實(shí)現(xiàn)。為滿足內(nèi)部需求,C語(yǔ)言編譯器可能將spiff這樣的函數(shù)名翻譯為 _spiff。這種方法被稱為C原因鏈接性。但在C++中,同一個(gè)名稱可能對(duì)應(yīng)多個(gè)函數(shù),必須將這些函數(shù)翻譯為不同的符號(hào)名稱。因此,C++編譯器執(zhí)行名稱矯正或名稱修飾,為重載函數(shù)生成不同的符號(hào)名稱。例如,可能將spiff(int)轉(zhuǎn)換為_(kāi)spoff_i,而將spiff(double, double)轉(zhuǎn)換為_(kāi)spiff_d_d。這種方法被稱為C++語(yǔ)言鏈接。

    鏈接程序?qū)ふ遗cC++函數(shù)調(diào)用匹配的函數(shù)時(shí),使用的方法與C語(yǔ)言不同。但如果要在C++程序中使用C庫(kù)中預(yù)編譯的函數(shù),將出現(xiàn)什么情況呢?例如,假設(shè)有下面的代碼:

    spiff(22); //want spiff(int) from a C library

    它在C庫(kù)文件中的符號(hào)名稱為_(kāi)spiff, 但對(duì)于我們假設(shè)的鏈接程序來(lái)說(shuō),C++查詢約定時(shí)查找符號(hào)名稱 _spiff_i。為解決這種問(wèn)題,可以用函數(shù)原型來(lái)指出要使用的約定:

    extern "C" void spiff(int); //use C protocol for name look-up
    extern void spoff(int);     //use C++ protocol for name look-up
    extern "C++" void spaff(int);  //use C++ protocol for name look-up

    第一個(gè)原型使用C語(yǔ)言鏈接性;而后面的兩個(gè)使用C++語(yǔ)言鏈接性。第二個(gè)原型是通過(guò)默認(rèn)方式指出這一點(diǎn)的,而第三個(gè)顯式地指出了這一點(diǎn)。

    C和C++鏈接性是C++標(biāo)準(zhǔn)制定的說(shuō)明符,但實(shí)現(xiàn)可提供其他語(yǔ)言鏈接性說(shuō)明符。

    2.10 存儲(chǔ)方案和動(dòng)態(tài)分配

    前面介紹C++用來(lái)為變量(包括數(shù)組和結(jié)構(gòu))分配內(nèi)存的5種方案(線程內(nèi)存除外),他們不適用于使用C++運(yùn)算符new(或C函數(shù)malloc())分配的內(nèi)存,這種內(nèi)存被稱為動(dòng)態(tài)內(nèi)存。動(dòng)態(tài)內(nèi)存運(yùn)算符由new和delete控制,而不是由作用域和鏈接性規(guī)則控制。因此,可以在一個(gè)函數(shù)中分配動(dòng)態(tài)內(nèi)存,而在另一個(gè)函數(shù)中將其釋放。與自動(dòng)內(nèi)存不同,動(dòng)態(tài)內(nèi)存不是LIFO,其分配和釋放順序要取決于new和delete在何時(shí)以何種方式被使用。通常,編譯器使用三塊獨(dú)立的內(nèi)存:一塊用于靜態(tài)變量,一塊用于自動(dòng)變量,另外一塊用于動(dòng)態(tài)存儲(chǔ)。

    雖然存儲(chǔ)方案概念不適用于動(dòng)態(tài)內(nèi)存,但適用于用來(lái)跟蹤動(dòng)態(tài)內(nèi)存的自動(dòng)和靜態(tài)指針變量。例如,假設(shè)在一個(gè)函數(shù)中包含下面的語(yǔ)句:

    float * p_free =new float [20];

    由new分配的80個(gè)字節(jié)(假設(shè)float為4個(gè)字節(jié))的內(nèi)存將一直保留在內(nèi)存中,直到使用delete運(yùn)算符將其釋放。但當(dāng)包含該聲明語(yǔ)句塊執(zhí)行完畢時(shí),p_fees指針將消失。如果希望另一個(gè)函數(shù)能夠使用這80個(gè)字節(jié)的內(nèi)存,則必須將其地址傳遞或返回給該函數(shù)。另一方面,如果將p_fees的鏈接性聲明為外部的,則文件中位于該聲明后面的所有函數(shù)都可以使用它。另外,通過(guò)在另一個(gè)文件中使用下述聲明,便可在其中使用該指針:

    extern float * p_fees;

    1. 使用new運(yùn)算符初始化

    如果要初始化動(dòng)態(tài)分配的變量,該如何辦呢?在C++98中,有時(shí)候可以這樣做,C++11增加了其他可能性。

    如果要為內(nèi)置的標(biāo)量類型(如int或double)分配存儲(chǔ)空間并初始化,可在類型名后面加上初始值,并將其用括號(hào)括起:

    int *pi =new int(6); 
    double * pd =new double (99.99);

    這種括號(hào)語(yǔ)法也有可用于構(gòu)造函數(shù)的類,這將在本書(shū)后面介紹。

    然而,要初始化常規(guī)結(jié)構(gòu)或數(shù)組,需要使用大括號(hào)的列表初始化,這要求編譯器支持C++11。C++11允許您這樣做:

    struct where {double x; double y; double z;};
    where * one =new where {2.5, 5.3, 7.2}; //C++11
    int * ar =new int [4] {2,4,6,7}; //C++11

    在C++11中,還可將列表初始化用于單值變量:

    int *pin =new int {};
    double *pdo= new double {99.99};

    2. new 失敗時(shí)

    new可能找不到請(qǐng)求的內(nèi)存量。在最初10年中,C++在這種情況下讓new返回空指針,但現(xiàn)在將引發(fā)一場(chǎng)std::bad_alloc。

    3. new: 運(yùn)算符、函數(shù)和替換函數(shù)

    運(yùn)算符new和new[]分別調(diào)用如下函數(shù):

    void * operator new(std::size_t); //used by new
    void * operator new[] (std:: size_t) //used by new[]

    這些函數(shù)被稱為分配函數(shù),他們位于全局名稱空間中。同樣也有delete和delete[]調(diào)用的釋放函數(shù):

    void operator delete(void *);
    void operator delete [](void *);

    4. 定位new運(yùn)算符

    通常,new負(fù)責(zé)在堆中找到一個(gè)足以能夠滿足要求的內(nèi)存塊。new運(yùn)算符還有另一種變體,被稱為定位new運(yùn)算符,它讓您能夠指定要使用的位置。程序員可能使用這種特性來(lái)設(shè)置其內(nèi)存管理規(guī)程、處理需要通過(guò)特定地址進(jìn)行訪問(wèn)的硬件或在特定位置創(chuàng)建對(duì)象。

    要使用定位new特性,首先需要包含頭文件new,它提供了這種版本的new運(yùn)算符的原型;然后將new運(yùn)算符用于提供了所需地址的參數(shù)。除需要指定參數(shù)外,句法與常規(guī)new運(yùn)算符相同。具體地說(shuō),使用定位new運(yùn)算符,變量后面可以有方括號(hào),也可以沒(méi)有。下面演示了new運(yùn)算符的4種用法:

    #include <new>
    struct chaff
    {
    	char dross[20];
    	int slag;
     } ;
     char buffer1[50];
     int *p3, *p4;
     //first, the regular forms of new
     p1 = new chaff;    //place structure in heap
     p3 = new int [20]; // place int array in heap
     //now, the two forms of placement new
     p2 = new (buffer1) chaff;   //place structure in buffer1
     p4 = new (buffer2) int[20]; //place int array in buffer2
     ...

    出于簡(jiǎn)化的目的,這個(gè)示例使用兩個(gè)靜態(tài)數(shù)組來(lái)為定位new運(yùn)算符提供內(nèi)存空間。因此,上述代碼從buffer1中分配空間給結(jié)構(gòu)chaff, 從buffer2中分配空間給一個(gè)包含20個(gè)元素的int數(shù)組。

    3. 名稱空間

    C++中,名稱可以是變量、函數(shù)、結(jié)構(gòu)、枚舉。類以及類和結(jié)構(gòu)的成員。當(dāng)隨著項(xiàng)目的增大,名稱相互沖突的可能性也將增加。使用多個(gè)廠商的類庫(kù)時(shí),可能導(dǎo)致名稱沖突。例如,兩個(gè)庫(kù)可能都定義了名稱為L(zhǎng)ist、Tree和Node的類,但定義的方式不兼容。用戶可能希望使用一個(gè)庫(kù)的List類,而使用另一個(gè)庫(kù)的Tree類。這種沖突被稱為名稱空間問(wèn)題。

    C++標(biāo)準(zhǔn)提供了名稱空間工具,以便更好地控制名稱的作用域。經(jīng)過(guò)了一段時(shí)間后,編譯器才支持名稱空間,但現(xiàn)在這種支持很普遍。

    3.1 傳統(tǒng)的C++名稱空間

    介紹C++中新增的名稱空間特性之前,先復(fù)習(xí)一下C++中已有的名稱空間屬性,以及概念。

    聲明區(qū)域。聲明區(qū)域是可以在其中進(jìn)行聲明的區(qū)域。例如,可以在函數(shù)外面聲明全局變量,對(duì)于這種變量,其聲明區(qū)域?yàn)槠渌诘奈募?。?duì)于在函數(shù)中聲明的變量,其聲明區(qū)域?yàn)槠渎暶魉诘拇a塊。

    潛在作用域。變量的潛在作用域從聲明點(diǎn)開(kāi)始,到其聲明區(qū)域的結(jié)尾。因此潛在作用域比聲明區(qū)域小,這是由于變量必須定義后才能使用。

    然而,變量并非在其潛在作用域內(nèi)的任何位置都是可見(jiàn)的。例如,它可能被另一個(gè)嵌套聲明區(qū)域中聲明的同名變量隱藏。例如,在函數(shù)中聲明的局部變量將隱藏在同一個(gè)文件中聲明的全局變量。變量對(duì)程序而言可見(jiàn)的范圍被稱為作用域。

    C++關(guān)于全局變量和局部變量的規(guī)則定義了一種名稱空間層次。每個(gè)聲明區(qū)域都可以聲明名稱,這些名稱獨(dú)立于在其他聲明區(qū)域中聲明的名稱。在一個(gè)函數(shù)中聲明的局部變量不會(huì)與在另一個(gè)函數(shù)中聲明的局部變量發(fā)生沖突。

    3.2 新的名稱空間特性

    C++新增了這樣一種功能,即通過(guò)定義一種新的聲明區(qū)域來(lái)創(chuàng)建命名的名稱空間,這樣做得目的之一是提供一個(gè)聲明名稱的區(qū)域。一個(gè)名稱空間中的名稱不會(huì)與另外一個(gè)名稱空間的相同名稱發(fā)生沖突,同時(shí)允許程序的其他部分使用該名稱空間中聲明的東西。

    名稱空間可以是全局的,也可以位于另一個(gè)名稱空間中,但不能位于代碼塊中。因此,在默認(rèn)情況下,在名稱空間中聲明的名稱的鏈接性為外部的。

    除了用戶定義的名稱空間外,還存在另一個(gè)名稱空間——全局名稱空間。它對(duì)應(yīng)于文件級(jí)聲明區(qū)域,因此前面所說(shuō)的全局變量現(xiàn)在被描述為位于全局名稱空間中。

    任何名稱空間中的名稱都不會(huì)與其他名稱空間中的名稱發(fā)生沖突。名稱空間中的聲明和定義規(guī)則同全局聲明和定義規(guī)則相同。

    名稱空間是開(kāi)放的,即可以把名稱空間加入到已有的名稱空間中。下面這條語(yǔ)句將名稱goose添加到Jill中已有的名稱列表中:

    namespace Jill {
        char * goose(const char *);
    }

    同樣,原來(lái)的Jack名稱空間為fetch()函數(shù)提供了原型。可以在該文件后面(或另外一個(gè)文件中)再次使用Jack名稱空間來(lái)提供該函數(shù)的代碼:

    namespace Jack{
        void fetch()
        {
            ...
        }
    }

    當(dāng)然,需要有一種方法來(lái)訪問(wèn)給定名稱空間中的名稱。最簡(jiǎn)單的方法是,通過(guò)作用域解析運(yùn)算符::,使用名稱空間來(lái)限定該名稱:

    Jack:: pail =12.34; //use a variable
    Jill::Hill mole;    //create a type Hill structure
    Jack::fetch();      //use a function

    未被裝飾的名稱稱為未限定的名稱;包含名稱空間的名稱稱為限定的名稱。

    1. using聲明和using編譯指令

    C++提供兩種機(jī)制(using聲明和using編譯指令)來(lái)簡(jiǎn)化對(duì)名稱空間中名稱的使用。using聲明使特定的標(biāo)識(shí)符可用,using編譯指令使整個(gè)名稱空間可用。

    using聲明由被限定的名稱和它前面的關(guān)鍵字using組成,using聲明將特定的名稱添加到它所屬的聲明區(qū)域中。

    using聲明使一個(gè)名稱可用,而using編譯指令使所有的名稱都可用。using編譯指令由名稱空間名和它前面的關(guān)鍵字using namespace組成,它使名稱空間中的所有名稱都可用,而不需要使用作用域解析運(yùn)算符。

    在全局聲明區(qū)域中使用using編譯指令,將使該名稱空間的名稱全局可用。在函數(shù)中使用using 編譯指令,將使其中的名稱在該函數(shù)中可用。

    2. using編譯指令和using聲明之比較

    使用using編譯指令導(dǎo)入一個(gè)名稱空間中所有的名稱與使用多個(gè)using聲明使不一樣的,而更像是大量使用作用域解析運(yùn)算符。使用using聲明時(shí),就好像聲明了相應(yīng)的名稱一樣。如果某個(gè)名稱已經(jīng)在函數(shù)中聲明了,則不能用using聲明導(dǎo)入相同的名稱。然而,使用using編譯指令時(shí),將進(jìn)行名稱解析,就像在包含using聲明和名稱空間本身的最小聲明區(qū)域中聲明了名稱一樣。

    注意:假設(shè)名稱空間和聲明區(qū)域定義了相同的名稱。如果試圖使用using聲明將名稱空間的名稱導(dǎo)入該聲明區(qū)域,則這兩個(gè)名稱會(huì)發(fā)生沖突,從而出錯(cuò)。如果使用using編譯指令將該名稱空間的名稱導(dǎo)入該聲明區(qū)域,則局部版本將隱層名稱空間版本。

    一般來(lái)說(shuō),使用using聲明比使用using編譯指令更安全,這是由于它只導(dǎo)入指定的名稱。如果該名稱與局部名稱發(fā)生沖突,編譯器將發(fā)出指示。using編譯指令導(dǎo)入所有名稱,包括可能并不需要的名稱。如果與局部名稱發(fā)生沖突,則局部名稱將覆蓋名稱空間版本,而編譯器并不會(huì)發(fā)出警告。另外,名稱空間的開(kāi)放性意味著名稱空間的名稱可能分散在多個(gè)地方,這使得難以準(zhǔn)確知道添加了哪些名稱。

    3. 名稱空間的其他特性

    可以將名稱空間聲明進(jìn)行嵌套:

    namespace elements
    {
        namespace fire
        {
            int flame;
            ...
        }
        float water;
    }

    4. 未命名的名稱空間

    可以通過(guò)省略名稱空間的名稱來(lái)創(chuàng)建未命名的名稱空間

    namespace  //unnamed namespace
    {
        int ice;
        int bandycoot;
    }

    這就像后面跟著using編譯指令一樣,也就是說(shuō),在該名稱空間中聲明的名稱潛在作用域?yàn)椋簭穆暶鼽c(diǎn)到該聲明區(qū)域末尾。從這個(gè)方面看,它們與全局變量相似。然而,由于這種名稱空間沒(méi)有名稱,因此不能顯式地使用using編譯指令或using聲明來(lái)使它在其他位置都可用。具體的說(shuō),不能在未命名名稱空間所屬文件之外的其他文件中,使用該名稱空間中的名稱。這提供了鏈接性為內(nèi)部的靜態(tài)變量的替代品。

    3.3 名稱空間及其前途

    隨著程序員逐漸熟悉名稱空間,將出現(xiàn)統(tǒng)一的編程理念。下面是當(dāng)前的一些指導(dǎo)原則。

    • 使用在已命名的名稱空間中聲明的變量,而不是使用外部全局變量。

    • 使用在已命名的名稱空間中聲明的變量,而不是使用靜態(tài)全局變量。

    • 如果開(kāi)發(fā)了一個(gè)函數(shù)庫(kù)或類庫(kù),將其放在一個(gè)名稱空間中。事實(shí)上,C++當(dāng)前提倡將標(biāo)準(zhǔn)函數(shù)庫(kù)放在名稱空間std中,這種做法擴(kuò)展到了來(lái)自C語(yǔ)言中的函數(shù)。例如,頭文件math.h是與C語(yǔ)言兼容的,沒(méi)有使用名稱空間,但C++頭文件cmath應(yīng)將各種數(shù)學(xué)庫(kù)函數(shù)放在名稱空間std中。實(shí)際上,并非所有的編譯器都完成了這種過(guò)渡。

    • 僅將編譯指令using作為一種將舊代碼轉(zhuǎn)換為使用名稱空間的權(quán)宜之計(jì)。

    • 不要在頭文件中使用using編譯指令。首先,這樣做掩蓋了要讓哪些名稱可用;另外,包含頭文件的順序可能影響程序的行為。如果非要使用編譯指令using,應(yīng)將其放在所有預(yù)處理器編譯指令#include之后。

    • 導(dǎo)入名稱時(shí),首選使用作用域解析運(yùn)算符或using聲明的方法。

    • 對(duì)于using聲明,首選將其作用域設(shè)置為局部而不是全局。

    使用名稱空間的主旨是簡(jiǎn)化大型編程項(xiàng)目的管理工作。對(duì)于只有一個(gè)文件的簡(jiǎn)單程序,使用using編譯指令并非什么大逆不道的事。

    以上是“C++中內(nèi)存模型和名稱空間的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    c++
    AI