C ++ primer
2 變量和基本類型
c++ 定義了一套包括算術類型和空類型(void)在內的基本數據類型。
算術類型。
算術類型分為整型和浮點型。字符和布爾都算作整型。
C++規定了尺寸的最小值, char:1 short int:2 int:4 long int 4; float:4; double:8;long long int 8. bool類型最小尺寸未定義
帶符號和無符號類型。
字符類型被分為了三種,char、signed char、unsigned char。 char的類型和編譯器有關,有時候是無符號,有時候是有符號。建議使用char的時候指定有符號,無符號。不要用char。
使用規則:
1. 明確知道結果不可能為負數時,使用無符號數。
2. 使用int進行整數計算。
3. 算術表達式中不要使用char或bool,存放時才使用。
4. 執行浮點數運算選用double.float精度不夠而且與double計算代價相差無幾。
類型轉換
1. 非布爾—>布爾,0則為false,其他為true
2. 布爾–>非布爾,false 為0,true為1
3. 浮點數->整數, 取小數點前面的那個
double a = 2177.20,a實際上可能為2177.1999,尤其是在qt里面使用double_Spinbox,需要進行精度補充。
5. 整數->浮點數,小數部分設置為0
6. 給無符號數一個超出他表示范圍的值時,結果是對無符號類型表示的總數取mod。
浮點數比較:fabs(x)<1e-6
字面值常量:
形如42的值叫做字面值常量。每個字面值常量都對于一種數據類型。
整型:十進制:20 八進制:024 16進制:0X14
浮點型:3.14 3.14E0 0. 0e0 .001
字符:‘a’ ‘0’----48 ‘\0’ ---- 0 ‘a’----97 ‘A’-----65
字符串:“hello world” 末尾加一個空字符’\0’
轉義序列:不可打印(換行,回車等) 或特殊含義的字符
單引號('),雙引號("),反斜線(\),問號(?)
\123 八進制,超過3個其他不算 \x12 16進制
布爾字面值:true false
指針字面值:nullptr
c中是這樣定義NULL: #define NULL ((void *)0)
字符常量,用單引號括起來的單個普通字符或轉義字符
字節數:4 int val = ‘p’
初始化和賦值:
列表初始化:
用{}來初始化變量。
默認初始化:
如果定義時沒有初始化,則是默認初始化,默認值由變量類型以及定義變量的位置決定。 定義在函數體內的內置類型變量不被初始化,外的被初始化為0. 沒有被初始化,錯誤不可知。
變量聲明和定義。
程序分為多個文件,為了在文件間共享代碼,需要支持分離式編譯。聲明使得名字為程序所知,一個文件如果想要使用別處定義的名字則必須包含對那個名字的聲明。而定義負責創建與名字關聯的實體。
聲明:多個cpp文件,用同一個變量。
extern int i; 聲明 規定類型和名字,
extern int i = 0; 定義。 分配空間和初值。
類聲明:class 類名;
2.不完全類型只能在非常有限的情況下使用:可以定義指向這種類型的指針或引用,也可以作為一個已經聲明(但沒有定義)的函數的參數或返回類型。
3.對于一個類來說,在創建它的對象前必須首先完成類的定義,而不能僅僅被聲明。否則編譯器就無法了解這樣的對象需要多少存儲空間。類似的,類也必須首先被定義,然后才能用引用或者指針訪問其成員。
4.對于類的靜態成員,直到類被定義之后數據成員才能被聲明成這種類類型。我們必須首先完成類的定義,然后編譯器才能知道存儲該數據成員需要多少空間。因為只有當類全部完成后類才算被定義,所以一個類的成員類型不能是該類自己。然而,一旦一個類的名字出現后,它就被認為是聲明過了(但尚未定義),因此類允許包含指向它自身類型的引用或指針。
函數聲明:
與定義的區別是沒有函數體,用分號替代。
可以不包含形參名字,但為了可讀性,最好還是寫上。
如果使用的僅僅是一個類的指針,沒有使用這個類的具體對象(非指針),也沒有訪問到類的具體成員,那么前置聲明就可以了。
復合類型:
引用
一般指左值引用,為對象起的別名。
右值引用:c++ 11新特性,用&&來獲得,只能綁定即將銷毀的對象。常數、表達式、函數返回值。主要是為了提高效率。相當于延長了生命周期。
疑惑:return 做了什么? https://blog.csdn.net/jeff_/article/details/48915759
左值:變量等,等號左邊,長久保存。
右值:臨時值,等號右邊,短暫存在。
右值引用可以減少拷貝的次數,提高效率。
std::move 將右值引用綁定到左值上。我們可以銷毀一個移后源對象,也可以賦予新值,但不能使用一個移后源對象的值。
對右值進行淺拷貝,右值對象中的指針直接賦值給新的對象,然后將右值對象的指針賦NULL.
指針:一個特殊的變量,存儲地址
遇到指針,都應該問三個問題:指針的類型,指針所指向的類型,指針的值
nullptr指針作用:1、解決函數調用歧義 2、構造函數重載
2個指針比較 == 指針的值比較。
復合類型的聲明:
從右往左讀,離變量名最近的符號對變量類型有最直接的影響。
指針數組 : *p[n]
數組指針 :(*p)[n] 可以指向一維數組,也可以指向二維數組,此時+1 表示到了下一行
二級指針:指向指針數組
sizeof 數組: 數組的大小 指針:4或8
const 限定符
const變量和普通的變量一樣,特殊的是初始化后值不能改變。
當以編譯時初始化的方式定義一個const變量時,如const int bufSize = 512; 編譯的時候,會把所有用到該變量的地方替換對應的值。默認僅在當前文件里有效,當多個文件中出現了同名的const變量時,其實等同于在不同文件中分別定義了獨立的變量。
要多文件共享,需要定義和聲明的時候加extern。
const引用:通常情況下,引用只能綁定到對象上,而不能與字面值或某個表達式的結果綁定在一起。const 引用特殊,當綁定到另一個類型時,會構造一個臨時變量,而綁定到臨時變量上。
const 指針:int * const p; 從右往左讀,就近原則。
指向常量的指針: const int *p; 指針不能修改里面的值。
頂層const
頂層const表示指針本身是個常量,底層const表示指針所指向的對象是個常量。
頂層const可以表示任意對象是常量,底層const與指針和引用等有關。
拷貝操作時,底層const的限制不能忽視。拷入和拷出的對象必須有相同的底層const資格,或者2個數據類型必須能夠轉換。非常量可以轉換為常量,反過來不行。
頂層const不影響拷貝操作。
類型別名:
typedef;
定義類型別名。 與const結合會有意想不到的效果。 新的const 對象。
typedef char * pstring. const pstring cstr; cstr是一個指向char的常量指針。
##可以這樣來理解:typedef int integer;將typedef去掉,那就是個變量的定義,這兒即定義了一個int型的變量integer,考慮這個integer是什么類型的,那么這個typedef語句就是將integer定義為這個類型的。將typedef int (*func_pointer)(const&, int);中的typedef去掉,就成了一個函數指針定義,即func_pointer被定義為函數指針類型變量,那么原來的typedef即將func_pointer定義為函數指針類型.
auto:
讓編譯器通過初始值來推斷變量的類型(發生在編譯期)。 特殊:當引用作為初始值時,真正參與的是引用對象的值。
1. 聲明為auto(不是auto&)的變量,忽視掉表達式頂層的const。即對有const的普通類型(int,double)忽視const,對常量指針(頂層const)變為普通指針,對指向常量(底層const)的常量指針(頂層const)變為指向常量的指針(底層const)。
2. 聲明為auto&的變量,保持表達式的頂層const或volatile屬性
3. 若希望推導的是頂層const,加上const,即const auto。
容器:
特定類型對象的集合。
自定義數據結構:
數據結構就是把一列相關的元素組合起來,然后使用它們的策略和方法。
定義類的時候,類體}后面要加上分號。定義一個類,也就是定義了一個類型。定義類型時一般不進行存儲分配。定義對象時將為其分配存儲空間。命名空間不需要加。
C++ 11新標準,可以為類內的數據成員提供一個類內初始值。花括號或=,不可圓括號。
頭文件:預處理變量無視作用域。 ifndef,一般以類名大寫表示頭文件 _H.
#define 和 const區別:
1. define是在編譯的預處理階段起作用,而const是在 編譯
2. const有類型檢查,可以調試。
結構體
1. 對齊字節:
32位:4 64位:8 #progma pack(n) 指定對齊字節值
起始地址必須能整除成員變量的大小
結構體的大小 必須是最大元素的整數倍
2. 共用體(聯合體) union 取最大值,所有成員相對于基址地址的偏移量為0。可以用來判斷大小端。大端:高字節放在低地址。小端,低字節放在低地址。
3. 枚舉 缺省值為0,1,2…;若賦值,自動加1
賦值時,需要賦枚舉里面的值,不能直接1,2
3 字符串、向量和數組
using聲明。
使用using聲明后,每次調用時就不需要再加前綴了。
using std::cin;
頭文件不應該using聲明,可能會產生難以預料的名字沖突。
標準庫類型 string
string 表示可變長的字符序列。
1. 直接初始化和拷貝初始化。使用等號的是拷貝初始化,否則是直接初始化。
2. 讀寫string 對象。 cin >> str;
cin 時,string會自動忽略開頭的空白(空格,換行,制表符),遇到下一個空白停止。string對象返回運算符左側的運算對象作為結果,因此可以連續輸入。
cin >> str1 >> str2;
while ( cin >> str) 遇到文件結束符或非法輸入結束。
3. getline,遇到換行符結束,換行符會從緩沖區刷掉,不會寫入string里。
4. string::size_type, size的返回值,無符號類型。 不要和有符號比較。
5. string對象比較。 相同,短的< 長的。
6. string對象相加,與字面值混用時,+ 兩側必須有一個string。 字符串字面值和string是不同的類型。
7. 使用C++版本的C標準文件 #include cname 而不是name.h
8. 下標訪問,[]返回的是該位置上字符的引用
9. size, length返回string真實長度,即便里面有\0,使用strlen可以截斷,去掉\0
標準庫類型vector
vector存放的是某種給定類型對象的可變長序列。
1. vector是類模板,需要傳入額外信息。 vector
2. 列表初始化 {} 如果不能列表初始化,則是值初始化。
vector v(10, “hi”);
3. 值初始化 () vector vec(10, -1); 生成10個-1的元素,如果沒寫-1,則為0. string則默認初始化。
二維數組:vector<vector> vec(m, vector(n,0)); m*n的二維數組,所有元素都為0
4. []只能訪問,不能去添加元素。
5. 插入時寫emplace_back,而不是push_back;
push_back()右值時就會調用構造函數和轉移構造函數。需要先構造臨時對象。
emplace_back在插入的時候直接構造,就只需要構造一次即可。不需要構造臨時對象,右值引用參數。
若push_back/emplace_back傳遞的參數為已被構造的對象,則沒有差別。
6.vec.size() 當前容器所存儲的元素個數
7.vec.capacity() 容器在分配新的存儲空間之前能存儲的元素總數
6. 內存擴充策略:滿了的時候,成倍擴充,然后拷貝原有數據到新內存,釋放原內存。
7. 內存泄漏:clear()和erase(),resize()只改變size,不改變capacity。防止:ivec.swap(vector(ivec)); 定義一個臨時變量,交換內容
8. vec.insert(vec.begin()+i,a);在第i個元素后面插入a;
9. 當n大于當前元素個數,resize和reserve都會capacity。根據分配策略,可能會有更大的一塊。resize未指定參數,按類型默認初始化,添加元素。而reserve不會添加元素。
n < 當前元素個數,resize刪除多余的,capacity不改變。而reserve什么也不做。
10. find(vec.begin(), vec.end(), i) != vec.end();
11. vec.erease(vec.begin(), vec.begin()+1); 刪除第一個元素 左閉右開
12. pop_back() 刪除最后一個元素,盡量不要從中間刪除
測試網站:https://cpp.sh/
迭代器
1. 所有標準庫容器都可以使用迭代器,只有少數幾種支持下標運算符。
2. v.end();表示尾元素的下一位置,當容器為空時,begin==end
3. *iterator 返回所指元素的引用
4. iterator->mem 等價于 (*iterator).mem
5. 迭代器類型 iterator(讀寫) const_iterator(只讀) cbegin返回const_iterator.
6. erase刪除容器后,返回下一個迭代器。
數組
1. 定義的時候,數組維度必須可知。為常量表達式。
2. 數組未初始化的時候,為默認初始化
3. 部分初始化的時候,類似vector值初始化,其他值采用默認值
4. 數組不允許拷貝和賦值
5. 復雜的數組聲明,從數組的名字按照從內向外的順序讀,先右后左。
6. c風格字符串。strlen返回p的長度,空字符不計算在內,必須有空字符,不然會有錯誤。
使用string比c風格更安全和高效。
7. 使用c_str(),返回指向一個空字符結尾的字符數組的常量指針。string內部的,以size計算。盡量使用標準庫類型而非數組。
8. 多維數組,指的是數組的數組,按照名字從內到外的順序閱讀。
4. 表達式
表達式由一個或多個運算對象組成,對表達式求值將得到一個結果。字面值和變量是最簡單的表達式,結果是他們的值。
左值和右值
右值的時候,用的是對象的內容;左值的時候,用的是對象的身份。
算術運算符滿足左結合律,如果優先級相同,按照從左往右的順序。
括號無視優先級與結合律。
運算對象的求值順序和優先級和結合律無關。優先級只是規定了運算對象的組合方式。
int i = f1() * f2(); 未說明f1還是f2先計算
cout << i << ++ i << endl; 未定義
算術運算符—取模:
早期允許m%n的符號匹配n的符號,并且商向負無窮一側取整。C++新標準已經禁用,除了-m導致溢出的特殊情況,其他時候:(-m)/n ,m/(-n) == -(m/n); m%(-n)=m%n;(-m)%n=-(m%n). 商一律向0取整。
邏輯和關系運算符
與和或都是先算左邊再算右邊,如果左邊可以確定整個的值,則不會計算右邊的(短路求值)。
關系運算符的求值結果是布爾值,if (i < j < k)
賦值運算符
賦值運算滿足右結合律,返回的是左側運算對象。
ival = ijal = 0; 都為0
優先級低于關系運算符,if里面需要加括號
遞增和遞減運算符
前置版本,返回改變后的對象。
后置版本,返回改變前的對象。 (會生成一個副本,減低效率 j++,一般用前置版本)
使用*p++; 簡潔,遞增高于解引用。
*beg = toupper(*beg++); 未定義錯誤,不知道先算左邊的還是右邊的。
成員訪問運算符
p->mem 等價于(*p).mem 加括號是因為解引用優先級低于.
條件運算符
cond ? exp1 : exp2
首先求cond的值,如果為真對exp1求值并返回其值,否則對exp2求值并返回其值。
在輸出表達式中運用條件運算符時,需要加括號,優先級較低。
逗號運算符
常用于for循環中。
強制類型轉換
static_cast:把 expression 轉換為 type-id 類型,但沒有運行時類型檢查來保證轉換的安全性。
??主要用法如下:
????(1)用于類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換。
????????進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的;
????????進行下行轉換(把基類指針或引用轉換成派生類表示)時,由于沒有動態類型檢查,所以是不安全的。
????(2)用于基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。
????(3)把空指針轉換成目標類型的空指針。
????(4)把任何類型的表達式轉換成void類型。
const_cast: static_cast 不能將 const int* 轉成 int*,const_cast 就可以. static_cast不支持底層const。
C語言的強制類型轉換有時候會有問題,推薦使用C++類型的。
5.語句
空語句
;加了空語句需要加注釋
條件語句
多層嵌套,可以提邏輯,減少嵌套層數
else與離他最近的未匹配的if匹配
switch語句
1. switch對表達式求值,然后值轉變為整型
2. case標簽必須是整型常量表達式
3. 如果表達式和某個case匹配,直到switch結尾或遇到break結束。接著執行switch之后的語句。
4. 一般加default,表示我們考慮到了這個情況。
5. switch內部定義的變量,如果沒有初始化,其他分支可以用。初始化了其他分支不可以用。
迭代語句
1. while語句,定義在條件部分和循環體內的變量,每次迭代都經歷創建和銷毀的過程。
2. 傳統for語句。 for (init;condition;expression)
3. 范圍for語句。
for(declaration: expression).
expression必須是一個序列。
declaration定義一個變量,每次迭代都重新定義變量,并將其初始化序列中的下一個值。
對范圍for語句,不能增加vector對象的元素。因為for(auto r : v) 等價于 for(auto beg = v.begin(), end = v.end(); beg != end; ++beg)。
跳轉語句:
1. break語句,終止離最近的while for do while switch語句,并執行其之后的第一條語句。
2. continue,終止里最的的循環中的當前迭代并立即開始下一次迭代。對于傳統for,繼續執行for語句頭的expression;對于范圍for,用序列中的下一個元素初始化循環控制變量。
只有switch嵌套在迭代語句里,才能用continue。
6.函數
函數是一個命名了的代碼塊,通過調用函數執行對應的代碼。函數可以重載,同一個名字可以對應幾個不同的函數。
參數傳遞:
每次調用函數時會創建它的形參,并用傳入的實參對形參進行初始化,形參的初始化和變量的初始化一樣。
使用引用可以避免拷貝,提高效率,最好用常量引用。
盡量使用常量引用:1. 不使用常量引用會誤導讀者:函數可以改變引用的值 2. 非常量引用會縮小范圍,引發錯誤 3. 函數內調用另一個函數,無法直接使用。
數組形參:
int * int[] int[10] 都等價的,10只是表示期望,實際不影響
二維: int(*p)[10]; int p[][10] 第二個不可省略,一定要相等。
main命令行參數:
prog -d -o -ofile daa0 argc = 5, argv[0]表示程序的名字,argv[1]表示實參。
返回值
void函數,使用return; 末尾會自動執行return;
有返回值的函數,要寫return 1;不然會有未定義的錯誤.主函數main例外,如果結束的時候沒有,編譯器會隱式的插入一條返回0的return語句。
返回一個值的方式和初始化一個變量或形參的方式完全一樣,返回的值用于初始化調用點的一個臨時變量。
#### 不要返回局部對象的引用或指針,存儲空間被釋放,指向為無效的內存區域。
調用運算符的優先級和點和箭頭的相同,符合左結合律。
auto sz = shortString(s1, s2).size();
數組和函數無法拷貝。
聲明一個返回數組指針的函數:Type (*fun(para))[length].
使用decltype: int odd[] = {1,2,1,3};
decltype(odd) *func(int i);
函數重載:
函數名相同,形參列表不同。返回值不同不構成重載。
編譯器優化,傳遞一個非常量對象的指針時,編譯器優先選用非常量函數。。
如果在內層作用域聲明名字,將隱藏外層作用域中聲明的同名實體,在不同作用域內無法重載函數名。變量名也可隱藏函數名。
默認實參:合理設計順序,默認值的形參出現在后面。一般出現在聲明中,可以添加默認實參(需要保證該右側的形參都有默認值),但不能修改一個已經存在的值。
string func(int, int, char = ‘c’);
string func(int, int, char = ‘a’); //錯誤,重復聲明
string func(int = 80, int = 80, char = ‘a’); // 正確
內聯函數:可避免函數調用的開銷。
是對編譯器發出的一個請求,編譯器可以忽略請求。
適用于規模較小,流程直接,頻繁調用的函數。一個75行的不太可能內聯展開。
constexpr函數:
在編譯的時候就能得到其返回值的函數。編譯器constexpr函數直接轉換成其返回值,因此,constexpr函數都是被隱式地定義為內聯函數。使用constexpr關鍵字來修飾constexpr函數。
如constexpr int func(int i);
函數的返回類型和形參的類型必須是字面型類型,函數體內有且只有一條return語句。
函數體內可以包含其他語句,但需要不執行任何操作,如空語句,類型別名等。
允許函數的返回值并非一個常量,當實參非常量時。
調試幫助:
assert預處理宏,當表達式結果為假時,輸出信息并終止程序的執行。為真時,什么也不做。
NDEBUG預處理變量,assert行為依賴了這個變量,如果定義了這個,則assert什么也不做。可以使用這個變量編寫自己的條件調試代碼。
C++編譯器定義:
_ _ func _ 當前函數的名字,const char 數組
C++預處理器定義:
_ _ FILE _ 存放文件名
_ _ LINE_ _ 存放當前行號
_ _ TIME_ _ 存放文件編譯時間
_ _ DATE_ _ 存放文件編譯日期
函數匹配:
1. 選定本次調用的重載函數集,集中的函數稱為候選函數。需要與調用函數同名,聲明在調用點可見。
2. 根據實參,選出可行函數。需要形參數量與實參數量相同,實參與形參類型相同,或者能轉換為形參類型。
3. 從可行函數中尋找最佳匹配。
基本思想:實參類型與形參類型越接近,匹配越好。
含有多個形參的函數匹配。
1. 該函數每個實參的匹配性都不劣于其他可行函數需要的匹配
2. 至少有一個實參的匹配優于其他可行函數的匹配。
如果檢查了所有實參之后沒有一個函數可以脫穎而出,則調用錯誤,編譯器會報二義性錯誤。
實參類型轉換等級
精確匹配:1)實參和形參類型相同。2)實參從數組類型或函數類型轉換為對于的指針類型。3)向實參添加/刪除頂層const.
通過const轉換實現的匹配 將指向非常量類型的指針或引用轉換成對應的常量類型的指針或引用。
非常量對象實參優先使用非常量形參函數。
通過類型提升實現的匹配。 整型提升
通過算術類型轉換或指針轉換實現的匹配。
算術轉換是把一種算術類型轉換成另一種算術類型。
指針轉換:0或nullptr能轉換成任意指針類型。指向任意非常量的指針能轉換成void*.指向任意對象的指針能轉換成const void *;繼承關系間指針的轉換。
通過類類型轉換實現的匹配。
類型提升
ff(int); ff(short);
ff(‘c’) 直接調用ff(int);
算術類型:所有算術類型轉換的級別都一樣。
ff(long); ff(float);
ff(3.14); 錯誤,二義性調用。
函數指針
指向函數,函數的類型與返回類型和形參類型共同決定,與函數名無關。
返回指向函數的指針
using f = int(int*,int); f是函數類型,不是指針
using pf = int (*p)(int *, int) pf是指針類型。
返回類型不會自動地轉換為指針,需要我們顯式的轉換。
pf f1(int);正確
f f1(int); 錯誤,f是函數類型
f * f1(int); 正確,函數指針。
7.類
類的基本思想是數據抽象和封裝。
1)數據抽象是一種依賴于接口和實現分離的編程技術。類的接口包括用戶所能執行的操作;類的實現包括類的數據成員、負責接口實現的函數體以及各種私有函數。
2)封裝實現了類的接口和實現的分離。封裝后的類隱藏了它的實現細節,即用戶只能使用接口而無法訪問實現部分。
類想要實現數據抽象和封裝,首先需要定義一個抽象數據類型。在抽象數據類型中,由類的設計者負責類的實現過程;使用該類的程序員則只需要抽象地思考類型做了什么,而無需了解類型的工作細節。-----數據抽象。
定義在類內部的函數是隱式的內聯函數。
成員函數含有this指針,this是一個常量指針,指向這個對象,無法修改。頂層const ,const靠近指針。
1)const成員函數,const關鍵字隱式修改this指針的類型。
由data * const p 變為 const data * const p;
2)const成員函數無法改變對象的內容
3)常量對象,以及常量對象的引用和指針都只能調用常量成員函數。
4)const成員函數的定義也要在后面加const
構造函數:
1)構造函數負責類的初始化
2)構造函數沒有返回值
3)構造函數不能聲明為const。 當創建一個const對象時,直到構造函數完成初始化后,對象才獲得const屬性。
默認構造函數
1)默認構造函數就是在調用時不需要顯示地傳入實參的構造函數。無參數的”和“帶缺省參數的都是,不要同時出現。一般情況下,默認構造函數是一個空的函數體。
2)如果類沒有顯示的定義構造函數,則編譯器會隱式的定義一個默認構造函數,稱為“合成的默認構造函數”。
a.如果存在類內的初始值,則用它來初始化
b.否則,默認初始化該成員。內置類型在函數體內未知值。如果類的對象沒有顯式地初始化,則其值由類確定。
不能依賴于合成的默認構造函數
想要合成的默認構造函數。
sales_data() = defaultl;
如果沒有在構造函數的初始化列表中顯示地初始化成員,則該成員將在構造函數體之前執行默認初始化。
如果成員是const、引用或者屬于某種未提供默認構造函數的類類型,則必須通過構造函數初始化列表為這些成員提供初值。
建議使用構造函數初始值:1.底層效率問題 2.一些數據成員必須初始化,可以避免某些意想不到的編譯錯誤。
成員初始化順序與在類內定義的出現順序一致,與初始化列表的順序無關。
sales_data obj(); //聲明了一個函數而非對象
只接受一個實參的隱式轉換機制:
class默認訪問權限是私有,struct默認訪問權限是公有。
友元:
類可以允許其他類或者函數訪問它的非公有成員。友元,增加一條以friend開頭的函數聲明。
一般最好在類定義開始結束前的位置集中聲明友元。
友元的聲明僅僅指定了訪問權限,而非一個通常意義上的函數聲明。如果希望類能夠調用函數,則要再加個函數聲明。
友元類:友元類的成員函數可以訪問包括非公有成員在內的所有成員。
友元關系不具備傳遞性。
每個類負責控制自己的友元類或友元函數。
類的其他特性:
可變數據成員:變量聲明成mutable,任何時刻都可以更改它,即便是在const成員函數里。
類的聲明:
可以只聲明類而暫時不定義它,稱為前向聲明。稱為不完全類型,可以用于:定義指向這種類型的指針或引用,也可以聲明(不可以定義)以不完全類型作為參數或者返回類型的函數。
類的作用域:
一個類就是一個作用域,在類外定義成員函數時必須同時提供類名和函數名。在類的外部,成員的名字被隱藏起來了。
一旦遇到類名,定義的剩余部分就在類的作用域之內的。這里指參數列表和函數體。
函數的返回類型在函數名之前,所以要加作用域。
類的靜態成員:
8. IO庫
9. 順序容器
順序容器:
通常,使用vector是最好的選擇,除非有更好的理由選擇其他容器。
迭代器:左閉右開區間。
類型別名:使得使用和機器無關
size操作返回是string::size_type類型的值。
difference_type使用來表示兩個迭代器之間的距離的。
對于+1,-1問題,如果2邊是閉區間,則是right-left+1;如果有一端是開的,則right-left。
value_type:元素類型。
每個STL容器類,都有一句相同的代碼:typedef T value_type;
typedef關鍵字就是給這些數據類型起一個別的名字,然后用這個自己起的名字來定義變量。
用處:1. 應該使用指示了大小和符號typedef來代替基本類型,見名知意。
reference:元素左值引用。
begin和end
begin返回第一個元素的迭代器,end返回尾元素之后位置的迭代器。
r返回反向迭代器,c返回const迭代器。
當不需要寫訪問時,應使用cbegin和cend.
vector a (10); 10個元素值為0
array
array<int, 10>,使用時必須指定元素類型和大小。
內置數組無法進行拷貝和對象賦值操作,但是array可以。需要保證元素類型和大小一致。
賦值和swap:
assign僅適用于順序容器,會替換原來的值,不能傳遞給調用assign容器的迭代器。
swap交換2個相同類型容器的內容。不會進行元素拷貝,除array外,不會導致指向容器的迭代器、引用和指針失效。
容器元素時拷貝,當我們用一個對象初始化容器時,或講一個對象插入到容器時,我們實際上放入的是對象值的拷貝,而不是對象。
insert將元素插入到所指定的位置之前。
同一次插入多個值時,不會改變順序。
iter = lst.insert(iter, word); 多次push_front;
返回的是新插入元素的迭代器
front,back,下標和at返回的是引用。
c.front() = 42;
auto &v = c.back(); 可以改變
auto v2 = c.back(); 無法改變
at可以對越界進行檢查,越界會拋出out_of_range異常。下標訪問不會。
c.erease(p);刪除p所指定的元素,返回一個指向被刪除元素之后元素的迭代器。
如果迭代器失效,為安全考慮,不要再使用它。
管理容量的成員函數:
c.shrink_to_fit(); 只適用于vector。string和deque。將capacity減少為size大小。只是一個請求,標準庫并不保存歸還內存。
capacity和reserve只適用于vector和string。
c.capacity();不重新分配內存空間,c可以保存元素個數。
c.reserve(n);分配至少能容納n個元素的內存空間。
resize只改變size,不改變capacity。
額外的string操作:
substr, 返回一個string,是原始string的一部分或全部的拷貝。
傳遞一個開始位置和計數值。
string s1 = s.substr(0, 5);如果開始位置大于s大小,會拋出out_of_range異常。如果開始位置+計數值大于s大小,則只會拷貝到結尾。
find返回一個npos,定義為-1;
auto f = s.find(“name”);
數值轉換:
如果string不能轉換為一個數值,會拋出invalid_argument異常。如果轉換得到的數值無法用任何類型來表示,會拋出out_of_range異常。
11.關聯容器
關聯容器的元素是按照關鍵字來保存和訪問的。
2個主要的關聯容器:map和set.
map中的元素是一些關鍵字-值(key-value)對,關鍵字起到索引的作用,值則表示與索引相關聯的數據。
set中每個元素只包含一個關鍵字,支持高效的關鍵字查詢操作:檢查一個給定關鍵字是否在set中。
標準庫提供了8個關聯容器,1. 關鍵字是否可以重復 2. 是否有序。
multimap表示可以重復,unordered_map表示無序。
map是關鍵字-值對的集合,set是關鍵字的集合。
pair類型:
一個pair保存2個數據成員。
pair<T1, T2> p;進行值初始化
pair<T1, T2> p(v1, v2); first是v1, second是v2
make_pair(v1, v2); 返回一個用v1和v2初始化的pair,類型通過v1和v2的類型推斷得到。
p1 relop p2; relop表示關系運算符,只有first和second同時滿足時,才滿足。
關聯容器操作:
key_type: 關鍵字類型
mapped_type:關鍵字關聯的類型,只適用于map
value_type: set是key_value;
map是pair<const key_value, mapped_type>因為關鍵字不能改,所以是const.
當解引用一個迭代器時,返回的是value_type類型的引用。
map的first不可更改。
set的迭代器都是const,不可更改值。
通常不對關聯容器使用泛型算法。
添加元素:
刪除元素:
1 . erase函數,返回刪除元素的個數。
map下標操作:
map和unordered_map容器提供了下標運算符和一個對應的at函數。set不支持下標,因為沒有與關鍵字對應的值。multimap也不支持小標操作,因為有多個值與關鍵字對應。
map進行下標操作時,如果關鍵字不再map里,則會添加到map中,并進行值初始化。所以只能對非const的map使用下標操作。
c[k]; 返回關鍵字為k的元素;如果k不在c中,添加一個關鍵字為k的元素,并進行值初始化。
c.at(k);訪問關鍵字為k的元素,帶參數檢查;若k不在c中,拋出一個out_of_range異常。
訪問元素:
find,count,count會返回次數,如果不需要計數,最好用find.
對map使用find代替下標操作。
無序容器:
不是使用比較運算符來組織元素,而是使用一個哈希函數和關鍵字類型的==運算符。
無序容器在存儲上組織為一組桶,每個桶保存零個或多個元素。無序容器使用一個哈希函數將元素映射到桶。無序函數的性能依賴于哈希函數的質量和桶的數量和大小。
12. 動態內存
在C++中,動態內存管理是通過一對運算符完成的。new,在動態內存中為對象分配一個空間并返回一個指向該對象的指針。delete,接受一個動態對象的指針,銷毀該對象,并且釋放與之相關的內存。
shared_ptr允許多個指針指向同一個對象。unique_ptr則獨占所指向的對象。weak_ptr是伴隨類,是一種弱引用,指向shared_ptr所管理的對象。
shared_ptr類
與vector一樣,使用的時候需要傳入指針可以指向的類型。
每個shared_ptr都有一個引用計數,當我們用一個shared_ptr初始化另一個shared_ptr時,或將它作為一個參數傳遞給一個函數,以及作為函數的返回值時,它所關聯的引用計數就會遞增。當我們給一個shared_ptr賦予新值或者shared_ptr被銷毀時,引用計數會遞減。
每次shared_ptr銷毀時,會調用shared_ptr的析構函數,將引用計數-1,如果此時變為0,則會銷毀對象并釋放所占用的內存。
使用動態內存的一個常用原因是允許多個對象共享相同的狀態。
直接管理內存
使用new動態分配內存和初始化對象。
int *p1 = new int; 默認初始化,值未定義
int *p2 = new int(); 值初始化為0;
int *p3 = new (nothrow) int; 如果分配失敗,會返回一個空指針,不會拋出bad_alloc異常。
釋放動態內存
delete會銷毀給定指針指向的對象;釋放對應的內存。
傳遞的指針必須指向動態分配的內存,或者是一個空指針。釋放一塊并非new分配的內存或者將相同的指針值釋放多次,行為是未定義的。
delete之后,指針變為懸空指針,指向一塊曾經保存數據對象但現在已經變得無效的內存的指針,應該delete后將其賦nullptr。
接收指針參數的智能指針構造函數是explicit的,必須使用直接初始化的方式。
shared_ptr p1= new int(1024); 錯誤;
shared_ptr<int p2(new int (1024)); 正確。
不要混合使用普通指針和智能指針
如果混合使用的話,智能指針自動釋放之后,普通指針有時就會變成懸空指針,當將一個shared_ptr綁定到一個普通指針時,我們就將內存的管理責任交給了這個shared_ptr。一旦這樣做了,我們就不應該再使用內置指針來訪問shared_ptr所指向的內存了。
也不要使用get初始化另一個智能指針或為智能指針賦值
unique_ptr
某個時刻只能有一個unique_ptr指向一個給定對象,由于一個unique_ptr擁有它指向的對象,因此unique_ptr不支持普通的拷貝或賦值操作。
不能拷貝unique_ptr有一個例外:我們可以拷貝或賦值一個將要被銷毀的unique_ptr.最常見的例子是從函數返回一個unique_ptr.
weak_ptr
weak_ptr是一種不控制所指向對象生存期的智能指針,它指向一個由shared_ptr管理的對象,將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數。一旦最后一個指向對象的shared_ptr被銷毀,對象就會被釋放,即使有weak_ptr指向對象,對象還是會被釋放。
weak_ptr的操作
由于對象可能不存在,我們不能使用weak_ptr直接訪問對象,而必須調用lock,此函數檢查weak_ptr指向的對象是否存在。如果存在,lock返回一個指向共享對象的shared_ptr,如果不存在,lock將返回一個空指針。
動態數組:
進度:看到423頁。
需要融合:
4. 在可以使用const的時候,都要添加const
5. const函數定義的時候,也要加const
總結
以上是生活随笔為你收集整理的C ++ primer的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1叉叉加速器分析_加速分析
- 下一篇: 与你分享五句话(转贴)