C++內存與命名空間

C++ Primer Plus讀書筆記

內存與命名空間

1.頭文件

  • 一種組織程序的策略,就是:一個文件(頭文件)包含了用戶定義類型的定義,另一個文件包含操縱用戶定義類型的函數的代碼。這兩個文件組成一個軟件包,可以應用在各種程序中。

  • 不把函數定義放在頭文件中,原因是:如果在頭文件中包含了一個函數的定義,然後在其他兩個文件中include這個文件,則同一個程序中將包含同一個函數的兩個定義,除非函數是內聯的,否則將會出錯。

  • 下面是頭文件中常包含的內容:

    函數原型
    使用#define或const定義的符號變量
    結構聲明
    類聲明
    模板聲明
    內聯函數
  • 頭文件管理
//假設頭文件名稱爲zmyyq.h
#ifndef ZMYYQ_H_
......
#endif

2.存儲持續性

  • 存儲持續性(用來確定數據在內存中留存的時間)

    • 1.自動存儲持續性:在函數定義中聲明的變量(包含函數參數)的存儲持續性爲自動的,他們在程序開始執行其所屬的函數或代碼塊時被創建,在執行完函數或代碼塊時,他們使用的內存就被釋放

    • 2.靜態存儲持續性:在函數定義外定義的變量和使用關鍵字static定義的變量的存儲持續性都爲靜態,他們在整個程序運行過程中都存在。

      __static修飾的內容__
      1. 如果是局部變量,則在用static修飾局部變量後,該變量只在初次運行時進行初始化工作,且只進行一次。
      
          局部變量是存放在棧區的,並且局部變量的生命週期在該語句塊執行結束時便結束了。但是如果用static進行修飾的話,該變量便存放在__靜態數據區__ ,其生命週期一直持續到整個程序執行結束。但是在這裏要注意的是,雖然用static對局部變量進行修飾過後,其生命週期以及存儲空間發生了變化,但是其作用域並沒有改變,其仍然是一個局部變量,作用域僅限於該語句塊。
      2. 通常情況下對於一個全局變量,它既可以在本源文件中被訪問到,也可以在同一個工程的其它源文件中被訪問(只需用extern進行聲明即可)。例如:
      file1.c
      int a=1;
      
      file2.c
      
      #include<stdio.h>
      
      extern int a;
      int main(void)
      {
          printf("%d\",a);
          return 0;
      }
      則執行結果爲 1
      但是如果在file1.c中把int a=1改爲static int a=1; 那麼在file2.c是無法訪問到變量a的。原因在於用static對全局變量進行修飾改變了其作用域的範圍,由原來的整個工程可見變爲本源文件可見。
      
      3. 用static修飾函數的話,情況與修飾全局變量大同小異,就是改變了函數的作用域。
      extern
      在C語言中,修飾符extern用在變量或者函數的聲明前,用來說明“此變量/函數是在別處定義的,要在此處引用”。
      
      注意extern聲明的位置對其作用域也有關係,如果是在main函數中進行聲明的,則只能在main函數中調用,在其它函數中不能調用。其實要調用其它文件中的函數和變量,只需把該文件用#include包含進來即可,爲啥要用extern?因爲用extern會加速程序的編譯過程,這樣能節省時間。
    • 3.線程存儲持續性:使用關鍵字thread_local聲明的變量,則其生命週期與所屬的線程是一樣的,這部分屬於並行編程。

    • 4.動態存儲持續性:用new運算符分配的內存將一直存在,知道使用delete運算符將其釋放或程序結束爲止,這種內存的存儲持續性爲動態的,有時候被稱爲自由存儲或堆

  • 作用域和鏈接

    • 作用域描述了名稱在文件的多大範圍可見
      • C++函數的作用域可以是整個類或者整個名稱空間,但不可能是局部的,也就是說代碼塊中不可以定義函數
    • 鏈接性描述了名稱如何在不同單元間共享。
      • 1.外部鏈接性:可在其他文件中訪問
      • 2.內部鏈接性:只能在當前文件中訪問
      • 3.無鏈接性:只能在當前代碼塊中訪問
  • 自動存儲持續性:
    自動指的是在函數聲明的函數參數和變量的存儲性爲自動,作用域爲局部,沒有鏈接性

    1. 自動變量的初始化:可以使用任何在聲明時其值爲已知的表達式來初始化自動變量。由於自動變量的數目隨函數的開始和結束而增減,因此程序必須在運行時對自動變量進行管理。

      • 解決自動變量管理的常用方法是留出一段內存,將其視爲棧,管理變量的增減。之所以被稱爲棧是因爲新數據被象徵性地放在原有數據的上面,當程序使用完之後從棧中刪除.
      • 跟蹤棧的實現方法:程序中通常使用2個指針來跟蹤棧,一個指向棧底(棧開始的位置),另一個指針指向堆頂——下一個可用的內存單元。當函數被調用時,該函數的自動變量將加入到棧中,棧頂指針指向變量後面的可用內存單元,當函數調用結束之後棧頂指針被重置爲函數被調用之前的位置。
      • 棧空間的大小,在linux中是使用ulimit來指定的,編譯時確定。在windows中,棧的大小是在鏈接是確定的。一般都是十幾兆到幾十兆之間不會太大。
  • 總結各個區

    動態存儲區(堆):(動態分配)

    • malloc,new動態分配在heap堆區。
    • 動態存儲區(堆),程序員自己分配自己釋放。
    • 使用delete 指針釋放完內存後,指針還是存在的,並且還是之前的位置,只是它指向的內容已經變了。

    動態存儲區(棧):(動態分配)

    • 自動變量、const變量在stack棧區。
    • 動態存儲區(棧),系統自動分配釋放。

    靜態存儲區:(靜態分配)

    • extern,全局變量,在static靜態存儲區。
    • 靜態存儲區,一旦分配,不會被回收,可讀可寫

    程序代碼區:(靜態分配)

    • main函數、其他函數在code程序代碼區。
    • 程序代碼區,一旦分配,可讀不可寫,不可改變
  • 說明符與限定符

    • 下面的屬於存儲說明符
      1. auto
      2. register 寄存器
      3. static 靜態
      4. extern
      5. thread_local C++11新增的
      6. mutable
        • 這個說明符是const的一個延伸,在使用const對結構進行修飾時,該結構不可被改變,但是他的成員還是可以被修改的,用這個修飾結構中的成員,則該成員不可被修改

          struct data
          {
          char a[30];
          mutable int access;//該成員變量不可被修改
          }
    • cv限定符

      1. c表示const,它表明內存被初始化後,程序便不能再對它進行修改。它對於默認存儲類型稍有影響。在默認情況下,全局變量的鏈接性爲外部的,但const全局變量的鏈接性是內部的,也就是用了const就相當於加上了static。

      2. v表示volatile,該關鍵字表明:即使程序代碼沒有對內存單元進行修改,其值也可能會發生變化(大多是硬件改變某些取值,也有可能是兩個程序共享一塊內存)這個關鍵字的作用是爲了改善編譯器的優化能力,假設編譯器發現程序在幾條語句中兩次使用了某個變量的值,則編譯器可能不是讓程序查找這個量兩次,而是將這個值緩存到寄存器中,假設變量的值在這兩次使用之間都不會變化,如果不設置這個volatile則告訴編譯器不進行這種優化。

  • 函數的鏈接性:

    • 函數在存儲的持續性上都自動爲靜態的,因爲函數定義的時候不允許在一個函數中定義另一個函數。
    • 函數加上一個static表示只允許在當前代碼文件中運行,一般來說,如果一個函數只在某個文件中執行,則要把他定義成static。
    • 使用extern來指出函數是在另一個文件中定義的。如果使用extern則該函數只能在一個地方定義。
  • 查找函數的方式

    • 假設在程序的某個文件中調用一個函數,則按照一下順序進行查找
      1. 如果該文件中的函數原型指出該函數是static,則編譯器只在該文件中查找函數定義,否則將在所有文件中查找。
      2. 如果找到兩個定義,編譯器將會發出錯誤消息,每個外部函數只能有一個定義。如果在文件中找不到,則會到庫中查找。這就意味着如果定義了一個與庫函數同名的函數,編譯器將使用自己寫的函數,如果沒有才去找庫函數。

2.名稱空間

  • 名稱空間可以是全局的,也可以位於另一個名稱空間中,但是不能位於代碼塊中,因此他的鏈接性爲外部的
  • using聲明和using編譯指令

    • using聲明使得特定的標識符可用;(局部性)
    • using編譯指令使得整個名稱空間可用。(所有)
    • 對比:

      1. 使用using編譯指令導入一個名稱空間中的所有名稱與使用多個using聲明是不一樣的。如果某個名稱已經在函數中被聲明,則不能使用using聲明導入相同的名稱,
      2. 使用using編譯指令時,如果導入了一個已經在函數中聲明的名稱,則局部名稱將隱藏名稱空間名,就像隱藏同名的全局變量一樣,不過仍可以用作用域解析運算符來調用。
      3. 一般來說,使用using聲明比使用using編譯指令要安全,它只導入指定的名稱,如果與局部的名稱發生衝突,則編譯器會發出指示。
        
        #include<iostream>
        
        using namespace std;
        
        namespace ZhuMengYanYiQuan
        {
            double fetch = 3.14;
        }
        
        double fetch = 5.16;
        
        int main()
        {
            double fetch = 4.15;
            using namespace ZhuMengYanYiQuan;
            cout << fetch << endl;    //4.15
            cout << ::fetch << endl;  //5.16
            cout << ZhuMengYanYiQuan::fetch << endl; //3.14
            return 0;
        }
  • 名稱空間的其他特性
    • 可以將名稱空間聲明進行嵌套,調用的時候就按着嵌套的順序使用::進行解析即可。

      namespace ZhuMengYanYiQuan
      {
      namespace QuanQuanQuan
      {
      float ChunJie = 5.5;
      }
      double fetch = 3.14;
      }
    • 未命名的名稱空間:表示在該名稱空間中聲明的名稱的潛在作用域爲:從聲明點到該聲明區域末尾,與全局變量類似。因爲它沒有名字,所以不能讓他在其他的地方使用。但是它提供了鏈接性爲內部的靜態變量的替代品。

      namespace
      {
      int ice;
      int hot;
      }
  • 名稱空間及其用途:下面是一些指導性的準則

    • 使用在已命名的名稱空間中聲明的變量,而不是使用外部全局變量。
    • 使用在已命名的名稱空間中聲明的變量,而不是使用靜態全局變量。
    • 如果開發了一個函數庫或類庫,將其放在一個名稱空間中。
    • 僅將編譯指令作爲一種將舊代碼轉換爲使用名稱空間的權宜之計。
    • 在頭文件中不要使用using編譯指令,首先這樣會掩蓋要讓那些名稱可用哪些不可用,另外,包含頭文件的順序可能會影響程序的行爲。如果非要加using編譯指令,則應將其放在所有預處理編譯指令#include之後。
    • 導入名稱時,首選使用作用域解析運算或者using聲明的方法。

    • 對於using聲明,首選將其作用域設置爲局部而不是全局。

3.總結

  • 一種代碼的組織策略:

    • 使用頭文件來定義用戶類型、函數原型;

    • 並將函數定義放在一個獨立的源代碼中;

    • 頭文件和源代碼文件一起用來定義和實現用戶定義的類型和方法;
    • 調用這些函數的函數放在第三個文件之中。
    • 也就是一個.h和.cpp用來定義和實現功能,對他們的調用放在另一個.cpp中
  • C++的存儲方案

    • 靜態變量存在於整個程序執行期間,對於在函數外面定義的變量,其所屬的文件中位於該變量定義之後的所有函數都可以使用,並且可以在程序的其他文件中使用(外部鏈接性),另一個文件要使用它,必須使用extern關鍵字來聲明。
    • 對於文件之間共享的變量,應在一個文件包含其定義聲明,也可進行初始化。在其他文件中包含聲明(使用extern且不初始化)
    • 使用static修飾的在函數之外聲明的變量,只能在當前的文件中使用,不能在其他文件中使用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章