日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

【格蕾读C++ Primer Plus】第九章 内存模型和名称空间

發布時間:2023/12/20 c/c++ 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【格蕾读C++ Primer Plus】第九章 内存模型和名称空间 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1 單獨編譯

程序分為三份

  • 頭文件:結構聲明和使用結構的函數原型
  • 源代碼文件:包含與結構相關的函數的代碼
  • 源代碼文件:包含調用與結構相關的函數的代碼
  • 不要#include源代碼文件,否則會多重聲明

    1.1 頭文件

    內容

    不要把函數定義或者變量聲明放到頭文件中

    頭文件中通常包含:

  • 函數原型 prototypes
  • 使用#define或者const定義的符號常量
  • 結構聲明
  • 類聲明 struct templates
  • 模板聲明
  • 內聯函數
  • 結構聲明,類聲明,模板聲明可以放在頭文件中,因為它們不創建變量,

    而是指示編譯器生成和創建。

    const數據和內聯函數有特殊的鏈接屬性,所以可以放在頭文件中。

    可以將成員放在名稱空間中

    引用方式

    尖括號:編譯器在存儲標準頭文件的主機系統的文件系統中查找

    雙引號:首先查找當前的工作目錄和源代碼目錄,如果沒找到,在標準位置找

    同一個文件只能包含同一個頭文件一次,否則編譯錯誤

    #ifndef COORDIN_H_ #define COORDIN_H_ //頭文件內容 #endif

    1.2 編譯流程

    以UNIX執行C為例

  • 預處理器合并源碼和包含的頭文件
  • 編譯器創建合并代碼的目標代碼文件.o
  • 匯編
  • 鏈接程序將目標代碼文件,庫代碼,啟動代碼合并,生成可執行文件.out
  • 1.3 名稱修飾

    (71條消息) C+±-名字修飾_Emma-Zhang的博客-CSDN博客_c++名稱修飾

    在C/C++中,一個程序要運行起來,需要經歷以下幾個階段:預處理、編譯、匯編、鏈接。

    名字修飾(Name Mangling)是一種在編譯過程中,將函數、變量的名稱重新改編的機制,簡單來說就是編譯器為了區分各個函數,將函數通過一定算法,重新修飾為一個全局唯一的名稱。

    兩個編譯器對同一個函數可能生成不同的修飾名稱,所以不同的名稱會使鏈接器無法將編譯器生成的函數調用與另一個編譯器生成的函數定義匹配。

    因此在鏈接編譯模塊時,請確保所有對象文件或庫都是由同一個編譯器生成的。

    2 存儲持續性,作用域,鏈接性

    C++存儲方式是通過存儲持續性,作用域和鏈接性來描述的

    2.1 存儲持續性

    數據保存在內存中的時間

  • 自動存儲持續性:兩種:函數定義中聲明的變量(包括函數參數)。
  • 創建時機:開始執行所屬函數或者代碼塊時
  • 釋放:執行完函數或者代碼塊
  • 靜態存儲持續性:三種:函數定義外定義的變量和static定義的變量。
  • 在程序整個運行過程中都存在
  • 線程存儲持續性(C++11):用關鍵詞thread_local聲明的變量。
  • 生命周期與所屬線程一樣長
  • 動態存儲持續性:new分配的內存,存放在自由存儲區或堆中。
  • new分配后到delete將其釋放或者程序結束
  • 2.2 作用域

    作用域(scope)描述名稱在文件的多大范圍可見。

    局部

    作用域為局部只在定義的代碼塊可用。

    全局

    作用域為全局(也叫文件作用域)的變量在定義位置到文件結尾都可用。

    變量作用域

  • 自動變量的作用域為局部
  • 靜態變量的作用域是全局還是局部取決于它是如何被定義的
  • 函數原型作用域中使用的名稱只在包含參數列表的括號內可用。
  • 類中聲明的成員的作用域為整個類
  • 名稱空間中聲明的變量的作用域為整個名稱空間(名稱空間已經引入到了C++語言中,因此全局作用域是名稱空間作用域的特例)
  • 函數作用域

    作用域可以是類或者名稱空間(包括全局),但不能是局部的,不然只對自己可見。

    2.3 鏈接性

    鏈接性(linkage)描述了名稱如何在不同單元之間共享。

    鏈接性為外部的名稱可以在文件間共享

    鏈接性為內部的名稱只能由一個文件中的函數共享

    自動變量的名稱沒有鏈接性,因為它們不能共享。

    3 變量的存儲方式

    這里介紹引入名稱空間之前的情況

    3.1 自動存儲持續性

    函數中聲明的函數參數和變量:

    存儲持續性:自動

    作用域:局部,從聲明位置到代碼塊結束

    鏈接性:無

    分配內存的時機:執行到該代碼塊

    同名自動變量

    當兩個同名的變量一個位于外部,一個位于內部時,內部的將被解釋為局部代碼塊變量,為新定義。新定義隱藏了以前的定義,內部代碼塊中新定義可見,舊定義不可見。

    auto關鍵字

    C++11開始,auto用于自動類型推斷,但是在C中或者C++11之前,auto用于顯式地指出變量為自動存儲。

    register關鍵字

    寄存器變量:register關鍵字是由C語言引入的,建議編譯器使用CPU寄存器來存儲自動變量,旨在提高訪問變量的速度

    在C++11以前,register表明變量用的很多,編譯器可以對其做特殊處理。

    C++11后只限于指出變量是自動的

    保留關鍵字的重要原因是避免使用了該關鍵字的現有代碼非法

    3.2 靜態持續變量

    靜態變量的數目在程序運行期間不變,所以不需要用棧管理,只需要固定內存分配,在程序執行期間一直存在。

    如果沒有初始化,編譯器將靜態變量設為0,稱為零初始化的(zero-initialized)

    靜態存儲持續性變量有三種鏈接性:

  • 外部鏈接性:
  • 必須在代碼塊的外面聲明
  • 可以在其他文件中訪問
  • 內部鏈接性:
  • 必須在代碼塊的外面聲明,必須使用static限定符
  • 只能在當前文件中訪問
  • 無鏈接性:
  • 必須在代碼塊中聲明,并使用static限定符
  • 只能在當前函數或代碼塊中訪問
  • int global=1000; //外部鏈接性靜態變量 static int one_file=50; //內部鏈接性靜態變量 int main() {...} void func1() {static int count =0;//無鏈接性靜態變量 }

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zuPN0hEO-1672942524360)(C:/Users/l/AppData/Roaming/Typora/typora-user-images/image-20221230205803189.png)]

    關鍵字重載

    代碼塊內static指存儲持續性,代碼塊外部static表示內部鏈接性,這種關鍵詞含義取決于上下文的現象叫關鍵詞重載

    靜態變量初始化

    靜態變量都必須先零初始化,如果顯式初始化了,那就有兩種初始化方式

  • 常量表達式初始化(與零初始化統稱靜態初始化)
  • 動態初始化
  • 要調用函數,必須等到函數被鏈接且程序執行時,是動態初始化。

    但是常量初始化可以使用sizeof()和C++11關鍵字constexpr,這增加了創建常量表達式的方式。

    3.3 外部變量

    外部鏈接性的靜態持續性的變量

    鏈接性為外部的變量稱為**(常規)外部變量**,又稱為全局變量(相對于局部的自動變量),每個使用外部變量的文件必須聲明它。

    單定義規則

    C++有單定義規則:(one definition rule,ODR)變量只能有一次定義。

    C++提供兩種變量聲明:

  • 定義聲明/定義:給變量分配存儲空間
  • 引用聲明/聲明:不給變量分配存儲空間,引用已有的變量,使用extern,且不能進行初始化
  • 要在多個文件使用外部變量,需要在其他文件用extern聲明。

    定義與全局變量同名的局部變量后,局部變量將隱藏全局變量。

    作用域解析運算符

    C++提供作用域解析運算符::,放在變量名前,表示使用變量的全局版本

    3.4 靜態外部變量

    指內部鏈接性的靜態持續性的變量

    static限定符用于作用域為整個文件的變量,該變量的鏈接性變為內部,稱為靜態外部變量

    如果在文件中定義了一個靜態外部變量,與另一個文件的常規外部變量重名,則該文件中隱藏常規外部變量

    用處:在同一個文件中的多個函數間共享數據

    3.5 無鏈接的靜態變量

    指無鏈接性的靜態持續性的變量

    函數內的靜態變量只在程序開始運行時被設置為0,當再次調用函數時不會再次初始化

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TcGnLnFB-1672942524361)(C:/Users/l/AppData/Roaming/Typora/typora-user-images/image-20221231162728980.png)]

    int a=1000; //外部鏈接性靜態變量 static int b=50; //內部鏈接性靜態變量 extern int c=20; int main() {...} void func1() {static int g =0;//無鏈接性靜態變量 }

    4 存儲說明符和cv-限定符

    存儲說明符(storage class specifier)有

  • auto
  • register
  • static
  • extern
  • thread_local
  • mutable
  • cv-限定符(cv-qualifier)(cv取兩個單詞的首字母)有

  • const
  • volatile
  • static關鍵字

    作用域為整個文件時,表示內部鏈接性;局部聲明中,表示存儲持續性。

  • 修飾局部變量時,使得該變量在靜態存儲區分配內存;只能在首次函數調用中進行首次初始化,之后的函數調用不再進行初始化;其生命周期與程序相同,但其作用域為局部作用域,并不能一直被訪問;
  • 修飾全局變量時,使得該變量在靜態存儲區分配內存;在聲明該變量的整個文件中都是可見的,而在文件外是不可見的;
  • 修飾函數時,在聲明該函數的整個文件中都是可見的,而在文件外是不可見的,從而可以在多人協作時避免同名的函數沖突;
  • 修飾成員變量時,所有的對象都只維持一份拷貝,可以實現不同對象間的數據共享;不需要實例化對象即可訪問;不能在類內部初始化,一般在類外部初始化,并且初始化時不加static;
  • 修飾成員函數時,該函數不接受this指針,只能訪問類的靜態成員;不需要實例化對象即可訪問。
  • C++中static作用和使用方法

    extern關鍵字

    引用聲明,聲明引用在其他地方被定義的變量,見”外部變量“。

    thread_local關鍵字

    同一個聲明中不能使用多個說明符,除了thread_local,可以和static和extern結合使用

    指出變量的持續性與其所屬線程的持續性相同,thread_local之于線程,猶如常規靜態變量之于整個程序。

    const關鍵字

    內存被初始化后,程序不能對它進行修改。

    const char * const months[12]= {"January","February",··· };

    第一個const防止字符串被修改,第二個const確保數組每個指針始終指向它最初指向的字符串。

    const全局變量

    C++中,const全局變量的鏈接性為內部的,如同使用了static。這樣每個文件都有頭文件中定義的const變量但是不會因為單定義規則而沖突。

    如果希望定義在函數外部的const變量的鏈接性為外部的,需要用extern關鍵字來覆蓋。而且必須在所有使用變量的文件中用extern聲明。

    不管如何,只有一個文件能初始化。

    //使用const //1.cpp extern const int states=50; //2.cpp extern const int states;//不使用const //1.cpp int states=50; //2.cpp extern int states;

    代碼塊中的const

    作用域為代碼塊

    volatile關鍵字

    volatile:不穩定的

    關鍵詞volatile表明:即使程序代碼沒有對內存單元進行修改,值也可能變化。

    使用場景:

  • 指針指向硬件,取的值為串行端口的信息,硬件可能對其進行修改
  • 兩個程序可能相互影響共享數據
  • 防止編譯器錯誤優化:如果程序短期多次使用某個變量,編譯器可能不會查找兩次,而是把值存在寄存器,加上volatile則編譯器不會進行這種優化
  • mutable關鍵字

    mutable:會變的,可變的

    關鍵詞mutable指出:即使結構/類為const,某個成員也可以被修改

    struct data {char name[30];mutable int accesses; }; const data a ={"hi",0}; strcpy(a.name,"hello"); //不允許 a.accesses++; //允許

    5 其他鏈接性

    5.1 函數的鏈接性

    存儲持續性

    C++和C都不允許在一個函數中定義另一個函數,因此所有函數的存儲持續性都自動為靜態的,在程序執行期間一直存在。

    默認鏈接性

  • 默認情況下,函數鏈接性為外部,可以在文件間共享
  • 可選:在函數原型中用extern,表示函數的定義在另一個文件
  • 要讓程序在另一個文件查找函數,另一個文件必須作為程序組成部分被編譯,或者是鏈接程序搜索的庫文件
  • 靜態鏈接性

  • static可以將函數的鏈接性設為內部,必須在原型和函數定義中使用該關鍵詞
  • 在定義靜態函數的文件中,靜態函數將覆蓋外部同名定義
  • 使用函數的文件都應該包含函數原型,只有一個能包含函數定義,除了內聯函數。所以內聯函數的定義能放在頭文件。

  • 如果定義了一個與庫函數同名的函數,編譯器將使用程序員定義的版本,而不是庫函數,但是C++保留了標準庫函數的名稱。

    5.2 語言鏈接性

    C語言中一個名稱只對應一個函數,C++同一個名稱可能對應多個函數,必須翻譯為不同的符號名稱,因此通過C++語言鏈接來實現。

    C++語言鏈接:為函數名稱翻譯成不同的符號名稱,執行名稱矯正或者名稱修飾。

    涉及鏈接程序對函數的處理。見書262

    6 動態內存

    動態內存由new和delete控制,而不是由作用域和鏈接性規則控制。可以在一個函數中分配,另一個函數中釋放。

    存儲方案不適用于動態內存,但適用于用來跟蹤動態內存的自動和靜態指針變量。

    float * p_fees =new float [20];

    由new分配的動態內存會一直保留,但是聲明的語句塊執行完后,指針將消失,所以必須在代碼結束后返回地址,不然無法使用該內存。

    6.1 分配內存

    int *pi=new int (6); double *pd=new double (99.99);//列表初始化 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 對數組 列表初始化 double *pd =new double {99.99};//C++11 對單值變量 列表初始化

    分配函數 new()

    void * operator new(std::size_t);

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

    使用運算符重載。size_t是個typedef,對應合適的整型

    int * pi=new int;被轉化為int * pi=new(sizeof(int))

    分配函數和釋放函數是可替換的,可以為new和delete提供替換函數

    分配失敗

    最初返回空指針,現在引發異常std::bad_alloc

    6.2 內存釋放

    程序結束后,new分配的內存通常將被釋放,但請求大型內存塊將導致代碼塊在程序結束不會被自動釋放

    這時需要delete

    釋放函數delete()

    void operator delete(void *);

    void operator delete[](void *);

    6.3 定位new運算符

    具體參考書P264

    new運算符處理在堆中找內存塊還有一種變體,讓程序員可以指定要使用的位置,這樣可以處理通過特定地址進行訪問的硬件或者在特定位置創建對象。

    與常規new的區別

    常規new調用一個接受一個參數的new()函數,而定位new調用的接受兩個參數

    頭函數:#include

    使用定位new,變量后面可以有方括號也可以沒有

    可以用于結構和對象等

    用法

    可以用靜態數組為定位new提供內存空間

    char buffer[500]; int main(){int *p;p=new (buffer) int[20]; }

    工作原理

    默認定位new函數:返回傳遞給定位new運算符的地址,并強制轉化為void *,這樣可以賦給任何指針類型

    C++允許重載定位new函數,但定位new是不可替換的

    7 名稱空間

    為了防止兩個庫的名稱沖突,需要用名稱空間控制名稱的作用域

    7.1 傳統的名稱空間

    聲明區域|潛在作用域|作用域

    聲明區域:可以在其中進行聲明的區域

    潛在作用域:變量的潛在作用域從聲明點開始,到其聲明區域的結尾,潛在作用域比聲明區域小

    作用域:變量對程序可見的范圍被稱為作用域

    7.2 名稱空間

    命名的名稱空間

    特征

  • 名稱空間可以是全局的,也可以位于另一個名稱空間中,但不能位于代碼塊
  • 名稱空間中,聲明的名稱的鏈接性默認為外部
  • 任何名稱空間中的名稱都不會與其他名稱空間中的名稱發生沖突
  • 名稱空間是開放的,可以把名稱加入到已有的名稱空間中,可以將函數原型和定義分在兩個名稱空間中寫
  • 用作用域解析運算符來使用名稱空間來限定名稱,未被修飾的叫未限定的名稱,包含名稱空間的名稱叫限定的名稱

    7.3 using聲明

    using聲明由被限定的名稱和前面的關鍵字using組成

    using聲明會把特定的名稱添加到所屬的聲明區域內

    在函數中添加到局部聲明區域,覆蓋同名的全局變量;在函數外添加到全局名稱空間中

    using聲明對于函數如果只給出了

    namespace Jill {double fetch; } char fetch; int main() {using Jill::fetch;//double fetch; //錯的,已經定義了fetchcin>>fetch; //讀取Jill::fetchcin>>::fetch; //讀取全局的fetch }

    7.4 using 編譯指令

    using聲明使得一個名稱可用,而using編譯指令使得所有的名稱都可用

    using編譯指令由名稱空間名和using namespace組成

    全局聲明區域內使用using namespace,使得名稱空間的全部名稱全局可用

    函數中使用using編譯指令,則函數中可用

    函數重載與using

    C++using聲明和using指示_蓮娃的博客-CSDN博客_using

    兩者比較

    同:using編譯指令和using聲明存在可能的二義性,比如兩個名稱空間都定義了同樣的名稱,這時候編譯器不允許同時使用這兩個using聲明


    異:using聲明與聲明類似,如果函數中已經聲明,則不能用其導入相同的名稱

    using編譯指令類似于作用域解析運算符,讓函數內部將其視為函數之外聲明的。即如果已經有局部聲明,則名稱空間名被隱藏,但是仍然可以通過::變量名取得全局的對應變量,名稱空間::變量名取得名稱空間的對應變量

    一般認為using聲明更加安全,因為編譯指令如果重名,編譯器不會警告,而且使用名稱空間的變量也沒有明確特征和說明

    using namespace std; //減少這么使用int x; //用法1 std::cin>>x;using std::cin; //用法2 int x; cin>>x;

    名稱空間的嵌套

    對于:

    namespace elements {namespace fire{int flame;} }

    使用:using namespace elements::fire

    再例如:

    namespace myth {using Jill::fetch; //Jill名稱空間內的fetch變量using namespace elements; }

    直接使用:myth::fetch訪問即可

    using編譯指令可傳遞,使用using namespace myth;相當于同時還使用了using namespace elements;

    名稱空間別名

    namespace m=myth; namespace MEF=myth::elements::fire; using MEF::flame;

    未命名的存儲空間

    相等于鏈接性為內部的靜態變量

    namespace {int counts } //等于 static int counts2;int main() {...}

    8 C++的內存分區

    編譯器使用前三塊獨立內存,程序在此基礎上還需要代碼區:

  • 靜態變量:全局區
  • 自動變量:棧區
  • 動態存儲:堆區
  • 代碼區:用來存放函數體的二進制代碼
  • 數據區包括:堆,棧,靜態存儲區。
    靜態存儲區包括:常量區(靜態常量區),全局區(全局變量區)和靜態變量區(靜態區)。
    常量區包括:字符串常量區和常變量區。
    代碼區:存放程序編譯后的二進制代碼,不可尋址區。

    9 小結

    對于大型編程項目的管理:

  • 盡量使用在已命名的名稱空間中聲明的變量,而不是使用外部/靜態全局變量
  • 類庫和函數庫都放在名稱空間中
  • 少用using編譯指令,并且不在頭文件中用
  • 首選作用域解析運算符和using聲明
  • 對于using聲明,將定義域盡量設置為局部而不是全局
  • 一句話:能用局部using聲明就別用別的
  • 當然,一個文件的程序不受此限制

    10 留給讀者的問題

  • p273 為什么使用using聲明導入了debts對pers的編譯指令?
  • 感謝看到這里,寫到凌晨兩點,麻煩點下贊謝謝

    總結

    以上是生活随笔為你收集整理的【格蕾读C++ Primer Plus】第九章 内存模型和名称空间的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。