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.無鏈接性:只能在當前代碼塊中訪問
- 作用域描述了名稱在文件的多大範圍可見
自動存儲持續性:
自動指的是在函數聲明的函數參數和變量的存儲性爲自動,作用域爲局部,沒有鏈接性- 自動變量的初始化:可以使用任何在聲明時其值爲已知的表達式來初始化自動變量。由於自動變量的數目隨函數的開始和結束而增減,因此程序必須在運行時對自動變量進行管理。
- 棧
- 解決自動變量管理的常用方法是留出一段內存,將其視爲棧,管理變量的增減。之所以被稱爲棧是因爲新數據被象徵性地放在原有數據的上面,當程序使用完之後從棧中刪除.
- 跟蹤棧的實現方法:程序中通常使用2個指針來跟蹤棧,一個指向棧底(棧開始的位置),另一個指針指向堆頂——下一個可用的內存單元。當函數被調用時,該函數的自動變量將加入到棧中,棧頂指針指向變量後面的可用內存單元,當函數調用結束之後棧頂指針被重置爲函數被調用之前的位置。
- 棧空間的大小,在linux中是使用ulimit來指定的,編譯時確定。在windows中,棧的大小是在鏈接是確定的。一般都是十幾兆到幾十兆之間不會太大。
總結各個區
動態存儲區(堆):(動態分配)
- malloc,new動態分配在heap堆區。
- 動態存儲區(堆),程序員自己分配自己釋放。
- 使用
delete 指針
釋放完內存後,指針還是存在的,並且還是之前的位置,只是它指向的內容已經變了。
動態存儲區(棧):(動態分配)
- 自動變量、const變量在stack棧區。
- 動態存儲區(棧),系統自動分配釋放。
靜態存儲區:(靜態分配)
- extern,全局變量,在static靜態存儲區。
- 靜態存儲區,一旦分配,不會被回收,可讀可寫
程序代碼區:(靜態分配)
- main函數、其他函數在code程序代碼區。
- 程序代碼區,一旦分配,可讀不可寫,不可改變
說明符與限定符
- 下面的屬於存儲說明符
- auto
- register 寄存器
- static 靜態
- extern
- thread_local C++11新增的
- mutable
- 這個說明符是const的一個延伸,在使用const對結構進行修飾時,該結構不可被改變,但是他的成員還是可以被修改的,用這個修飾結構中的成員,則該成員不可被修改
struct data
{
char a[30];
mutable int access;//該成員變量不可被修改
}
- 這個說明符是const的一個延伸,在使用const對結構進行修飾時,該結構不可被改變,但是他的成員還是可以被修改的,用這個修飾結構中的成員,則該成員不可被修改
cv限定符
c表示const,它表明內存被初始化後,程序便不能再對它進行修改。它對於默認存儲類型稍有影響。在默認情況下,全局變量的鏈接性爲外部的,但const全局變量的鏈接性是內部的,也就是用了const就相當於加上了static。
v表示volatile,該關鍵字表明:即使程序代碼沒有對內存單元進行修改,其值也可能會發生變化(大多是硬件改變某些取值,也有可能是兩個程序共享一塊內存)這個關鍵字的作用是爲了改善編譯器的優化能力,假設編譯器發現程序在幾條語句中兩次使用了某個變量的值,則編譯器可能不是讓程序查找這個量兩次,而是將這個值緩存到寄存器中,假設變量的值在這兩次使用之間都不會變化,如果不設置這個volatile則告訴編譯器不進行這種優化。
- 下面的屬於存儲說明符
函數的鏈接性:
- 函數在存儲的持續性上都自動爲靜態的,因爲函數定義的時候不允許在一個函數中定義另一個函數。
- 函數加上一個static表示只允許在當前代碼文件中運行,一般來說,如果一個函數只在某個文件中執行,則要把他定義成static。
- 使用extern來指出函數是在另一個文件中定義的。如果使用extern則該函數只能在一個地方定義。
查找函數的方式
- 假設在程序的某個文件中調用一個函數,則按照一下順序進行查找
- 如果該文件中的函數原型指出該函數是static,則編譯器只在該文件中查找函數定義,否則將在所有文件中查找。
- 如果找到兩個定義,編譯器將會發出錯誤消息,每個外部函數只能有一個定義。如果在文件中找不到,則會到庫中查找。這就意味着如果定義了一個與庫函數同名的函數,編譯器將使用自己寫的函數,如果沒有才去找庫函數。
- 假設在程序的某個文件中調用一個函數,則按照一下順序進行查找
2.名稱空間
- 名稱空間可以是全局的,也可以位於另一個名稱空間中,但是不能位於代碼塊中,因此他的鏈接性爲外部的
using聲明和using編譯指令
- using聲明使得特定的標識符可用;(局部性)
- using編譯指令使得整個名稱空間可用。(所有)
對比:
- 使用using編譯指令導入一個名稱空間中的所有名稱與使用多個using聲明是不一樣的。如果某個名稱已經在函數中被聲明,則不能使用using聲明導入相同的名稱,
- 使用using編譯指令時,如果導入了一個已經在函數中聲明的名稱,則局部名稱將隱藏名稱空間名,就像隱藏同名的全局變量一樣,不過仍可以用作用域解析運算符來調用。
- 一般來說,使用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修飾的在函數之外聲明的變量,只能在當前的文件中使用,不能在其他文件中使用。