第一章 文件頭及聲明
關於extern
使用extern 聲明而不定義,它是說明變量定義在程序其他地方
全局不初始化的extern int i; 是聲明不定義;只要聲明並且有初始化式,那麼就是定義;帶有extern且有初始化的聲明(也是定義),比如extern float fval =2.34; 這種必須放在函數外面,否則出錯
文件B要訪問另外一個文件A中定義的變量,那麼在B中必須先extern聲明一下,並且不需要include A。另外,A中定義的變量一定是全局變量。
Extern C
const常量在函數外定義默認是文件級,別人不可訪問。要想成爲程序級(被其他文件訪問)必須要加extern。A.pp中extern const int ival=23;而在B.cpp中extern const int ival;聲明一下就可以使用(聲明時const不是必須,但最好加上)。另外,const常量在聲明的時候必須初始化,如果是使用常量表達式初始化,最好放在頭文件去定義(頭文件特殊的可以放定義的三個之一)。否則只能放在源文件中定義,並加上extern以能被多個文件共享。
struct MSGMAP_ENTRY {
UINT nMessage;
void (*pfn)(HWND, UINT, WPARAM,LPARAM);
};
struct MSGMAP_ENTRY _messageEntres[] = {
WM_LBUTTONDOWN, OnLButtonDown,
WM_RBUTTONDOWN, OnRButtonDown,
WM_PAINT, OnPaint,
WM_DESTROY, OnDestroy
};
關於結構體的說明:
上面定義了一種結構體類型後,後面要定義數據類型時就要struct MSGMAP_ENTRY 變量名。 藍色部分就是當變量類型使用
第二章 變量和基本類型
1. 只有內置類型存在字面值,沒有類類型或標準庫類型的字面值(可以這樣理解,是內置類型組成了其他類型)。
C++中有整型字面值,浮點字面值,布爾字面值和字符字面值,字符串字面值,轉義序列,多行字面值
2. 下面
a) ‘數字1’===0x31 (49)
b) ‘A’===0x41 (65)
c) ‘a’=====0x61(97)
3. 由空格,製表,換行連接的字符串字面值可以連接成一個新的字符串
但是連接字符串與寬字符串的結果就不可預料了
代碼續行 \後面不能有空格,只能是回車,接下來的一行要從頭開始,沒有縮進 字符串換行要加/ ===》小線傾斜的方向不一樣
4. 1024f有錯,整數後面不能有f
5. 2.34UL有錯,浮點數後面不能有U
6. 標識符不能以數字開頭
7. 直接初始化與複製初始化 int ival = 123; int ival(34);
8. int ival = 09; 錯! 八進制數,不能有大於等於8的數字
9. 函數外定義的變量初始化爲0,函數內的變量不進行初始化(可能是一些無意義的值但是合法的值,所以造成錯,所有編譯器難以發現所有這類錯)
所以,建議每個內置類型對象都要初始化
在函數外定義的類類型對象使用默認構造函數初始化,對於沒有默認構造函數的類型,要顯式初始化,即使用帶參構造函數
10. 初始化不是賦值。初始化要分配空間並給初值,而賦值則是要替換當前值
11. 聲明與定義的區別
a) 定義要分配空間,一個變量程序中只能定義一次
b) 聲明用於向程序表明自己的名字和類型,程序中可以出現多次;定義也是聲明,定義的時候也聲明瞭它的名字和類型
c) 使用extern 聲明而不定義,它是說明變量定義在程序其他地方
函數內或外int i; 都是定義(都分配空間,但函數內沒有初始化爲0) // 不能放在頭文件中。
d) 全局不初始化的extern int i; 是聲明不定義;只要聲明並且有初始化式,那麼就是定義;帶有extern且有初始化的聲明(也是定義),比如extern float fval = 2.34; 這種必須放在函數外面,否則出錯
e) 文件B要訪問另外一個文件A中定義的變量,那麼在B中必須先extern聲明一下,並且不需要include A。另外,A中定義的變量一定是全局變量。
f) 在正式編寫程序語句前定義的一些全局變量或局部變量,在C中爲聲明,C++中爲定義 ( int a;//在標C中爲聲明,是不可執行語句;在C++中爲定義)
12. 定義在函數外的變量有全局作用域
13. 局部同名變量屏蔽了上層作用域的變量,爛程序,不易讀。在局部中要想使用全局中的同名變量在變量前使用域作用符::
14. 在內建數據類型的情況下,++i與i++效率沒區別,在自定義數據類型的情況下++i的效率高
15. c++中,定義和聲明可以放在任何可以放語句的位置,所以,通常把一個對象定義在首次使用它的地方是一個很好的辦法
16. const int pi = 3.14; 採用const易維護(在多次出現3.14的地方使用const變量來代替)
再比如:
const int max = 23;
for (int i = 0; i != max; ++i)
標準C++中,for中定義的i只在語句作用域中(for 語句),出了for就不可見了
17. 全局變量的程序級與文件級, const全局變量
a) 普通變量在函數外定義就是程序級,別的文件要使用只先extern聲明一下就行。並且不用include變量定義的文件
b) const常量在函數外定義默認是文件級,別人不可訪問。要想成爲程序級(被其他文件訪問)必須要加extern。A.pp中extern const int ival=23;而在B.cpp中extern const int ival;聲明一下就可以使用(聲明時const不是必須,但最好加上)
c) const常量在聲明的時候必須初始化,如果是使用常量表達式初始化,最好放在頭文件去定義(頭文件特殊的可以放定義的三個之一)。否則只能放在源文件中定義,並加上extern以能被多個文件共享。
18. 關於const引用與非const引用與它們所綁定的對象的關係:(const引用是指向const對象的引用,非const引用是指向非const對象的引用)
a) 非const引用類型必須要與它所引用的變量的類型一致(非const引用只能綁定到與該引用同類型的對象,而不能是右值)
int ival =12;
int &ref = dval 或34; //error 初始化的時候只能綁定到同類型的對象(不能是右值)。 非初始化的時候可以被賦值,引用與其綁定的對象的值都會改變
b) const引用可以綁定到不同但相關(即可以轉化)的類型的對象(可以是非const對象)或右值
比如[const] double dval= 3.14; // 這裏要不要const都行
const int &refVal = dval; // warning,編譯器會中間把3.14轉成int temp的3,然後再給了refVal,這裏編譯不出錯,但是會給出警告
constint &refVal2 = 2.33; // warning,這裏const不可少,不然不使用右值
refVal2 = dval; // error, const引用的值不可改變
19. enum color {red, blue=4, white};其中white是5
枚舉成員是常量而不能改變,所以初始化時要用常量表達式,const常量與整形字面值都是常量表達式
擴展color black = red; // 必須使用裏面定義的來作爲右值進行初始化
color pink = 3; // error
20. 設計類:從操作開始設計類。先定義接口,可以決定需要哪些數據以及是否需要私有函數來支撐公有函數
21. class與struct定義類僅僅影響的是默認訪問級別,struct爲public,class爲private
22. c++支持分別編譯separatecompilation,頭文件和源文件。將main放在其他的源文件中.
頭文件中有類定義,extern變量聲明,變量和函數聲明,帶來的莫大的好處,一是統一性:保證所有文件使用的同一聲明,二是易維護:需要修改時,只改頭文件就行了,易維護
設計頭文件時注意:聲明最好放在一起。編譯頭文件需要一定的時間。有些C++編譯器支持預編譯頭文件,需要查手冊。
頭文件用於聲明,而不是定義——有三個例外:類定義,用常量表達式初始化的const變量和inline函數的定義。它們可以在多個源文件中定義(相當於頭文件被include到了多個源文件中),只要在多個源文件中的定義時相同的。
解釋:允許在頭文件中定義類和inline函數是因爲編譯器需要它們的定義來產生代碼,對於類類型需要知道類對象的數據成員和操作才能分配空間。但是對於單個源文件A.cpp爲了避免多重包含,在定義時須加#ifndef與#endif(頭文件應該有保護符,會被其他文件包含)
如果將inline函數的定義放在某源文件中,那麼其他源文件將永遠訪問不到
允許在頭文件中定義const常量的原因:const常量是文件級作用域的,多個文件包含它互不影響,不算是重複定義。所以可以在頭文件中定義。多個文件共享const變量的前提是必須保證多個文件使用的是相同的名稱和值,將它放在頭文件中,誰需要的時候就include它就行,安全方便。
另外要注意:在實際中,源文件裏大多編譯器只是像宏替換一樣的使用常量表達式來替換const變量,而沒有分配空間來存儲用常量表達式初始化的const變量。
如果const變量不是用常量表達式初始化的,這就不應該放在頭文件中定義。此時它應該放在源文件中定義並初始化,加上extern以使它能夠被多個文件共享。
23. //Page58頁小字部分:編譯和鏈接多個源文件組成的程序
24. 關於inline函數
inline函數目的是:爲了提高函數的執行效率(速度)。以目標代碼的增加爲代價來換取時間的節省。
非內聯函數調用有棧內在的管理,包括棧創建和釋放的開銷。函數調用函數調用前保護好現場,返回後恢復現場,並按原來保存的地址繼續執行。對於短小且頻繁執行的函數,將影響程序的整體性能
在C中可以用#define,編譯器用複製宏代碼的方式取代函數調用,但沒有參數類型檢查。
有兩點特點注意的:
(1) 內聯函數體中,不能有循環語句、if語句或switch語句,否則,函數定義時即使有inline關鍵字,編譯器也會把該函數作爲非內聯函數處理。
(2) 內聯函數要在函數被調用之前定義,否則內聯失效。將它的定義放在頭文件中就很好的可以做到這點,由於是在源文件中先include,再後面調用的,就保證了調用之前定義
(3)關鍵字inline必須與函數定義體放在一起才能使函數真正內聯,僅把inline放在函數聲明的前面不起任何作用。因爲inline是一種用於定義的關鍵字,不是一種用於聲明的關鍵字。(根據高質量C/C++指南,聲明前不應該加,因爲聲明與定義不可混爲一談)
25. 複合類型compoundtype,引用,數組,指針
第三章 標準庫類型
1.#include<string>
usingstd::string;
using std::cout;
2.頭文件中定義確定需要的東西
3. 注意區分string類型與字符串字面值
4. 在Windows下用命令行要編譯和運行.cpp文件
打開vs2005命令提示
cl /EHsc simple.cpp 生成exe文件//EHsc 命令行選項指示編譯器啓用 C++ 異常處理
若要運行 simple.exe 程序,請鍵入 simple 並按 Enter
要加入程序變量: simple < data\book_sales
vs2005中同樣也可以加入程序變量,就在命令行參數裏
5.
a) cin.getline(char*, int, char) // 是istream流
// 可以接收空格並輸出,它的參數有三個,第三個是結束符,默認爲'\n'
char ch[20];
cin.getline(ch,5);
cout<< ch << endl; // 輸入abcdefg,輸出abcd最後一個爲'\0'
//這個\0是自動加上的,所以可以cout << ch;
char ch[20];
cin.getline(ch,5,'a'); // 遇到a結束,並加上\0
cout<< ch << endl; //輸入xyamnop,輸出xy
b) getline(cin, str), 須加#include <string> // 是string流
#include <string>
istream& getline( istream& is, string& s, char delimiter = '\n' ); // delimiter分隔符
接收一行字符串,可以接收空格並輸出,遇到回車返回,並丟掉換行符。
不忽略行開頭的換行符,如果第一字符是回車,那麼str將是空string
逐行輸出:
string str;
while(getline(cin, str)) {
cout << line << endl; // 由於不含換行符,需要endl刷新輸出緩衝區
}
逐詞輸出:
string str;
while (cin>> str) {
cout << str << endl;
}
c) cin.get()吃掉沒有用的字符,讀到回車才退出
d) char ch1[100],ch2[100];
cin >> ch1 >> ch2; //接收字符串,忽略有效字符前的空白字符,以空白符(空格,TAB,回車)作爲輸入結束
而getline(cin,str) 不忽略開頭的空白字符,讀取字符直到遇到換行符,讀取終止並丟掉換行符
e) gets(str) 接收一個字符串,可以接收空格並輸出。加#include<iostream>
與getline(cin,str)類似
f) getchar()無參數,須加#include <string>,是C中的函數,儘量少用或不用
6. str.size()的實現(返回有效字符的個數,不含最後的空字符,它的類型是string::size_type)
constchar *st = "The expense of spirit\n";
int len = 0;
while (*st) { ++len; ++st; }
str.size的應用
for (string::size_type ix = 0; ix !=str1.size(); ++ix)
7. string對象可以==, >=, <= !=操作
8. string的賦值操作:str1 = str2;須先把str1的內存釋放,然後再分配能存放副本大小的空間,再複製過來
9. string s2 = “hello” + s1; //error,開頭必須是變量
10. #include <cctype> p77頁
isalnum(c) 字母數字
isalpha(c) 字母
iscntrl 控制字符
isdigit 數字
isgraph 不是空格但是可以打印
islower 小寫
isprint 可打印
ispunct 標點
isspace 空格
isupper 大寫
isxdigit 十六進制數
tolower 變小寫
toupper
11. p78 使用C標準庫頭文件,使用#include <cname>不要用#include <name.h>,這樣保證標準庫文件中的名字與std中的一致
12. string s;
cout << s[0] << endl; // error
13. t3_10,輸入一個字符串,去掉裏面的標點:
ispunct(ch)
result_str+= ch;
14. string是數據類型,vector是模板 vector<string>是數據類型
15. vector<Student> v5(4); // 4個實例,必須有默認初始化構造函數
vector<int>v3(10,5); // 10個5
雖然能預先分配內存,但使用空vector來push_back更好
for(ix!=vector_size) vec.push_back(ix*5);
vector<int>ivec; //是個空vector,沒有分配空間,所以ivec[0]=3;//error
for(vector<int>::size_type ix = 0; ix != ivec.size(); ++ix) //由於是動態變化的,所以這時使用函數
vector<int>ivec(10); // 10個0
for(vector<string>::const_iterator it = ivec.begin();it != ivec.end(); ++it) { // 如果ivec爲空,則for不執行
//cout << *it << endl;
*it = 42;// error遍歷只讀
}
vector的操作p81 , ==, !=, < >=
16. 迭代器可以==或!=操作,若指向同一個元素 則相等
迭代器的算法操作
it+n size_type類型
it-n difference_type類型
it1-it2 difference_type類型 it1或it2可以是vec.end()
記住:push_back()等改變長度的操作,使存在的迭代器全部失效!!
vector<int>::iterator
17. bitset<n> b; // n個0 //n必須是字面值或用常量值初始化的const對象
bitset<n> b(u); // b是unsigned long型u的一個副本
bitset<n> b(s); // 字符串
bitset<n> b(s, pos, n); // 字符串pos開始的n個
如:
bitset<16> bitvec1(0xffff); // 高十六個0去掉, 0到15位設爲1
0xffff代表32位的unsigned long,高十六個0,低十六個1
bitset<128> bitvec2(0xffff); // 31位以上的都是0
string s(“1100”); // s[0]值爲1
bitset<8> bitvec3(s); // 讀入位集的順序是從右向左(用string最大下標元素來賦值給bitvec的最小下標值),結果是0000 1100, 這樣bitvec[0]的值是最右的那個0
size_t sz = bitvec1.size(); // #include <cstddef>
//一個與機器相關的unsigned類型,其大小足以保證存儲內存中對象的大小
if (bitvec.test(i))
if (bitvec[i])
bitvec.reset(); 全爲0
bitvec.set(); 全爲1
bitvec.flip(1);
bitvec[1].flip();
bitvec.flip(); // 全部取反
uLong = bitvec.to_ulong(); 取回unsigned long值
// 若bitvec的位數128超過了unsigned long長度,那以會產生overflow_error異常
t3_24:
1,2,3,5,8,13,21的相應位置爲1
bitset<32> b;
int x = 0, y = 1, z = x + y;
while (z <= 21) {
b.set(z);
x = y;
y = z;
z = x + y;
}
第四章 數組和指針
1. int arr[32]; // 維數必須是字面值,枚舉常量,常量表達式初始化的const變量
上面這句話在函數外,則初始化爲0
若定義在函數內,沒有初始化,但分配了空間
int arr[5] = {1,2,3}; // 剩下的初始化0,若是類類型就使用默認初始化構造函數, java中不行,不能寫維數的
vector<int> ivec = {1,2,3}; // error,vector沒有這樣的初始化
2. 數組不能直接複製和賦值
3. 數組下標越界導致:buffer overflow
4. 比較兩個vector,先比較長度
5. 現代C++使用vector替換數組,使用string替換C風格字符串
6. 關於指針:
a) string* s1, *s2看上去很不好,所以儘量將*與變量名放在一塊
b) 避免使用未初始化的指針
C++沒有辦法檢查出未初始化的指針,使用它可能會導致基礎數據
如果要指向的對象還沒有存在,那麼就先不要定義這個指針。如果要定義,就=0或NULL(#include <cstdlib>)
預處理器變量NULL不是在std命名空間中定義的,所以不是std::NULL
c) void *
void* 這不叫空指針,這叫無確切類型指針.這個指針指向一塊內存,卻沒有告訴程序該用何種方式來解釋這片內存.所以這種類型的指針不能直接進行取內容的操作.必須先轉成別的類型的指針纔可以把內容解釋出來;
'\0'不是空指針所指的內容,而是表示一個字符串的結尾,不是NULL;
真正的空指針是說,這個指針沒有指向一塊有意義的內存,比如說:
char* p;
這裏這個p就叫空指針.我們並未讓它指向任意地點,又或者 char* p = NULL;這裏這個p也叫空指針,因爲它指向NULL 也就是0,注意是整數0,不是'\0'
一個空指針我們也無法對它進行取內容操作.
空指針只有在真正指向了一塊有意義的內存後,我們才能對它取內容.也就是說要這樣p = "hello world!"; 這時p就不是空指針了
後面會看到:不能使用void*指向const對象,必須使用const void *類型的指針保存const對象的地址
void *p; 只支持以下操作:
與另一指針比較
作參數或函數返回值
給另一個void *賦值
7. 指針與引用的比較:
引用定義時必須初始化!
引用被賦值後改變的是所引用的對象
指針被賦值後將會指向另一個變量的地址,不專一
8. int arr[5];
int *p = arr;
int *p2 = p + 2;// 那麼,p的算術運算要求原 指針與計算出的新指針都要指向同一數組的元素,結果範圍必須在[0,5],//注意這裏5也算合法結果
注意:C++允許計算數組或對象的超出末端的地址,但不允許對此地址進行解引用操作
可以把指針看成是數組的迭代器
ptrdiff_tn = p1 – p2; // #include <cstddef>
// 與機器相關:size_t是unsigned, ptrdiff_t是signed
注意,ptrdiff_t只保證指向同一數組的兩個指針間的距離,若是指向不同的指針,那麼error
t4_17:
p1 += p2– p1; 若p1與p2指向同一數組那麼始終合法,讓p1指向p2所指向的地址
9. 關於const指針
a) 指向const對象的指針
const double pi = 3.14; // 不能用普通指針來指向它 但是相反地,可以用const double*的指針指向非const的變量
const double *cptr = 0或NULL; // 這裏const限定的是所指向的對象,而不是cptr本身,所以定義的時候可以不初始化, 藍const是必須的,防止通過cptr來改變所指對象的值
cptr = pi; // 也可以在定義的時候初始化
指向const對象的指針可以指向非const對象
注意:不能保證cptr指向的對象的值一定不可修改!它完全有可能指向一個非const對象,將它賦給普通指針就可以改
double dval = 2.34; // 非const
cptr = &dval; //可以指向一個非const對象,但仍不可通過cptr來修改
const void *cpv = pi; // ok ,不能使用void*指向const對象,必須使用const void *類型的指針保存const對象的地址
t5_33: 將一個指向const對象的指針成void *
const string *ps;
void *pv;
pv = (void *)ps; // error!!!應該按下面寫
pv = static_cast<void*>(const_cast<string*>(ps));
把ps變成string*之後再轉成void*,就是說,先去掉const特性
static_cast<void*>跟(void *)差不多,就相當於強制轉化
//============找回原來指針值,與上例t5_33沒關係
對於void *pv;
小括號強制轉化pc = (char*)pv; // 找回原來指針值
或者pc =static_cast<char*>(pv);
b) const指針 //不能再指向別的對象,但能改變所指向對象的值
int ival = 32;
int *const ptr = &ival; // 從右向左讀:指向int對象的const指針
*ptr = 34; 則ival的值也變成34了。
但ptr = &ival2; //error指針值不可變
c) typedef與const指針
typedef string *pstring;
const pstring cstr; // 這裏不要簡單的理解爲const string *cstr, 這裏const限定的是指針,而不是所指對象!! string* const cstr 最好寫成pstring constcstr; 好理解
10. t4_19:
const int ival; // error必須初始化
int *const ptr1; // error必須初始化
const int *ptr2; // ok,可以不用初始化
const int ival2 = 23; // 不能由指向非const對象的指針來指向它
int *const ptr2 = &ival2; // error,指向非const對象的指針不能指向const對象
11. p113講:C風格字符串儘量少用,因爲它常常帶來很多錯誤,是導致大量安全問題的根源!
c-style character string
const char *cp = “hello world”;
while (*cp) {
//TODO..
++cp;
}
c風格字符串的庫函數:#include <cstring>
strlen(s)// 返回有效長度
strcmp
strcat
strcpy
strncat(s1, s2, n) // 前n個字符, 返回s1
strncpy(s1, s2, n)
實參指針不能是空指針,即指向0或NULL的指針。且指向以NULL結束的字符數組中的指針
必須確保目標字符串足夠大,否則造成安全漏洞
char ca[] = {‘a’,’b’,’c’};
strlen(ca); // error!!!
使用strn比strcat strcpy更安全
strncat連接時,第二個字符串要覆蓋掉第一個字符串的NULL
12. t4_25
兩個string類型的比較可以使用>,< 而兩個C風格字符串比較使用strcmp
它們讀入:t4_26
string str;
cin >> str;
size_t str_size = 90;
char ch[str_size];
cin >> ch;
13. 創建動態數組, 在運行時確定長度
int *p = new int[3]; //內置類型分配空間 無初始化
int *p = new int[4](); 值初始化爲0, 但沒有初始化值列表的形式
const int *p = new const int[22](); //最後的()是必須的,不然就沒有初始化const元素 ,雖然vs2005通過
string *pstr = new string[4]; // 分配空間 使用默認構造函數初始化
const char *p4 = s2.c_str();
14. int arr[0]; // error,不能爲0
int *parr = new int[0]; // ok, 可以爲0,但是parr不能進行解引用操作
15. delete[] parr; // 漏掉[]編譯器不會發現錯,運行時錯
16. 使用string比C風格字符串的程序執行速度要快很多
17. 可以使用C風格字符串對string對象賦值或初始化
C風格字符串可以作爲左操作數與string對象相加
char*ch = "hello ";
strings("good");
strings2;
s2 =ch +s; // ok
s2= “abc” + ch + s; // error
18. 使用數組初始化vector
in iarr[6] = {0,1,2,3,4,5};
vector<int> ivec(iarr+1, iarr+4); //[a,b),不包括iarr[4]
19. t4_34
vector<string> ivec;
string str;
while (cin >> str) { // 讀入一組string放在vector中
ivec.push_back(str);
}
//下面是將vector這組string拷到一個字符指針數組中。
char **pa = new char*[ivec.size()];
size_t k = 0; // #include <cstddef>
for(vector<string>::iterator it = ivec.begin(); it != ivec.end(); ++it) {
char *p = new char[(*it).size() +1]; // 分配空間
strcpy(p, (*it).c_str()); //拷貝 #include <cstring>
pa[k++] = p;
}
20. int *p[3];
int (*p)[3];
21. 使用typedef簡化多維數組
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
typedef iint int_array[4];
int_array *p; // p也可以定義爲 int (*p)[4];
for (p = ia; p != ia + 3; ++p;) {
for(int *q = *p; q != *p+ 4; ++q) {
cout<< *q << endl;
}
}
第五章 表達式
1. 指針不能轉化爲浮點類型
2. short [-32768,32767]
unsigned short: [0, 65535]
int: [-21.4億, 21.4億]
3. -21 % -8 值爲 -5
4. 對於位操作,由於系統不能確保如何處理符號位,所以建議使用unsigned數!
左移補0
unsigned char ch = 0277;
ch = ~ch; 全部取反 flip操作
ch << 1; // 左移,右邊補0
//右移左邊補0; 右移個數要小於8(操作數的位數)
對於signed,右移補0還是符號位根據機器而定
bitset優於整數數據的低級直接位操作
bitset_quiz1.set(27);
int_quiz1 |= 1UL << 27;
bitset_quiz1.reset(27);
int_quiz1 &= ~(1UL << 27);
<<優先級高於<
所以 cout << (10 < 32); // ok
cout << 10 < 32; // error
5. i + j = k; // error
int i;
const int ci = i; // ok, 將來ci無意義
6. int a; int *p;
a = p = 0; // error 不能將指針類型賦給int
7. a += b; a計算了一次, a = a + b; a計算了兩次
8. 使用++i而不用i++:
工作量少啊,後置操作要先保存操作數原來的值,以便返回未加1之前的值作爲操作的結果。
9. .>* 取成員操作大於解引用操作
(*pstu).name = “leiming”;
pstu->name = “leiming32”;
++>* ++也大於解引用操作
*iter++ 相當於 *(iter++)
10. t5_18:定義一個string指針的vector,輸入內容,然後輸出
vector<string *> vec;
string str;
cout << "enter strings:" ;
while (cin >> str) {
string *pstr = new string; // 必須新分配一塊地方
// string *pstr = &str;不對,指向同一空間啦
*pstr = str; // 每個指針單獨指向一塊空間,賦值
vec.push_back(pstr);
if(vec.size()==4) cout <<"vec[1]:" << *vec[1] << endl;
}
for (vector<string *>::iterator it =vec.begin(); it != vec.end(); ++it) {
cout <<**it << ", len: " << (**it).size()<< endl;
}
11. 三種語法:
sizeof (expr)
sizeof (type name) // 必須加括號
sizeof expr : 並沒有計算表達式的值
Sales_item item, *p;
sizeof item;
sizeof *p; // 並沒有解引用操作, p指向一塊沒有意義 的地址
sizeof (Sales_item)
12. 逗號表達式的是其最右邊表達式的值
13. int *p = new int; //ok 沒有初始化
int *p = new int();// ok,p所指的對象內容初始化爲0
int *p = 0;
delete p; // ok,可以刪除空指針
delete p;之後,刪掉了它所指向的對象,p此時成了懸掛指針,應該p = 0;
如果你使用delete是未加括號,delete便假設刪除對象是單一對象。否則便假設刪除對象是個數組。delete[] 相比 delete 會多做一件事,就是在刪除這個內存塊前,析構每個元素,特別是對於對象數組
萬一需要重載 new, delete,new[], delete[], 我們必須全部都重載,而不能僅僅重載 new, delete, 然後希望 new[],delete[] 會調用它們
string*stringPtr1 = new string;
string *stringPtr2 = newstring[100];
……
delete stringPtr1;
delete [] stringPtr2;
如果你對着stringPtr1使用“[]”形式,其結果未定義。如果你對着stringPtr2沒有使用“[]”形式,其結果亦未定義。猶有進者,這對內建型別如int者亦未定義,即使這類型別並沒有destructors。
遊戲規則很簡單,如果你在調用new時使用了[],則你在調用delete時也使用[],如果你在調用new的時候沒有[],那麼你也不應該在調用時使用[]。
14. a[][5]; // 第一維可缺省 ,5不可省
15. 動態創建const對象:
const int *pci = new const int(32); // 定義的時候必須初始化,初始化後就不可以修改 pci指向一個const對象,所以它也要是const的
1) 普通指針不能指向const對象;
void reset(int *pi) //實參不能是指向const int對象的指針
// 爲了保護所指向的值,一般地將定義爲指向const對象的指針
void reset(const int *cpi);
2) 指向const對象的指針可以用普通指針來初始化
3) 指向const對象的指針可以指向非const對象,但不能修改它,可以賦給非指向const對象的指針來改;
4) 可以使用普通指針初始化const指針
5) 不能用const對象來初始化非const的引用
6) 可以使用const對象來初始化非const對象,反之亦然
所以const形參的時候,實參可以是const對象也可以是非const對象
C語言中,const形參與非const形參沒有區別,爲了支持與C的兼容,編譯器將const形參看作是普通的形參,所以const不能看作是函數重載的根據
const string *pcs = new conststring; // ok
內置類型的以及沒有默認構造函數的類型必須顯式初始化
可以刪除const指針:
delete pci; // ok
delete pcs; // ok
16. t5_30:
vector<string> svec(10); // 十個空串元素
vector<string> **pvec = new vector<string>[10]; // error
//右邊返回的是一個指向元素類型是vector<string>的指針
17. 算術轉換:
char, signed char, unsigned char, short, unsignedshort如果能包容在int內則轉換爲int,否則轉成unsigned int(主要針對unsiged short)
bool 提升爲int, false轉爲0, true轉爲1
inta = 2 + true;
cout<< a << endl; // 3
向上提升:int->unsigned int->long->unsignedlong
signed int + unsigned int,則signed int會轉爲unsigned int
unsignedchar b = -1; // 1000 0001,符號位不變所有位取反,再加1 : 1111 1111
cout<< (int)b << endl; // 255
inti = 32;
constint &iref = i; // 使用非const對象初始化const對象的引用,將非const對象轉成const對象
i= 33;
//iref= 34; // error
cout<< iref << endl; //ok, iref要隨着i而變 = 33
const int *cpi = &i; // 非const對象的地址(或非const指針)轉成指向相關const類型的指針
int *pi = &i;
const int *cpi2 = pi; // 可以使用非const指針初始化const指針
18. 顯式轉換
1) 什麼時候使用
i. 強制轉換
ii. 存在多種類型轉換時,需要選擇一種特定的類型轉換
2) cast-name<type>(expression)
dynamic_cast
運行時識別指針或引用所指向的對象
3) const_cast
int*cpi3 = const_cast<int *>(cpi);// 是指針或對象
//*cpi= 100; // error
*cpi3= 43;
cout<< "i=" << i << endl;
const int ci = 32;
int ival = const_cast<int>(ci);//error不能從const int轉int
int &iref2 = const_cast<int&>(ci); // 可以將const轉換掉
iref2 = 35;
cout << ci << endl; // 32
cout << iref2 << endl; // 35
4) static_cast
int ival = static_cast<int>(2.4); // 關閉警告,不關心精度丟失
可以找回放在void*中的地址值:
double dval = 2.33;
void *p = &dval;
double *pd = static_cast<double*>(p);
5) reinterpret_cast
爲操作數的位模式提供較低層次的重新解釋,依賴於機器,要閱讀編譯器細節。
int *ip;
char *pc = reinterpret_cast<char*>(ip);//不能把pc看成是普通字符指針,程序員必須記住pc指向真正指向的是int
string str(pc); // compile error
6) 最後:避免使用強制類型轉換,也能寫出好程序
使用const_cast拋棄cast屬性意味着設計缺陷
有些強制轉換有危險
如果必須要用,則限制強制轉換值的作用域,記錄所有假定涉及的類型
7) 在新編譯器上寫程序,最好不要用()式的老強制轉換
19. dval = ui * fval; // ui先轉成float再乘,結果是double
第六章 語句
1. if (int i = 3) // i轉化成bool值true; 迄今爲止,在所有用過的類型中,IO類型可以用作條件,vector和string類型一般不可用作條件
上面,出了if後,i不可訪問,下面出了while之後同樣不可訪問
while (int i = fun(j)) // 每次循環要執行i的創建和撤銷過程
2. 每個case不一定非要另起一行
case 1: case 2: case 3: case 4: case 5:
++i;
break;
case值必須是整型常量表達式
有兩個case值相同則編譯錯
良好的程序要定義default: 即使沒有什麼要做的,;空語句也行
switch內部的變量定義:規定只能在最後一個case之後或default之後定義變量,要爲某個case定義變量,那麼要加{}。這樣是爲保證這個變量在使用前被定義一初始化(若直接執行後面case,且用到了這個變量 ,由於作用域可見,但未定義就使用了)
3. t6_12.cpp 尋找出現次數最多的單詞,要求程序從一個文件中讀入
4. do{}while();以;結束
5. 在設計良好的程序中, 異常是程序錯誤處理的一部分
異常是運行時出現的異常
#include<stdexcept>
try {
if (true)
throwruntime_error(“error!”);
} catch (runtime_error err) {
cout << err.what() << endl;
}
先在當前代碼中找catch,若沒有匹配的,則一上層調用函數中尋找,若仍沒找到,則一直找下去,最終由#include <exception>中定義的terminate終止程序執行
若程序在沒有try catch的地方出現異常,則由terminate自動終止程序的執行
bitset的to_ulong操作,若提供的位數大於unsigned long的長度32位時,就異常overflow_error,查看t6_23
bitset<33> ib;
ib[ib.size()-1] = 1; // 最高位是1
try{
ib.to_ulong();
}catch (overflow_error err) {
cout<< "overflow" << endl;
cout<< err.what() << endl; // what返回const chat*
}catch (runtime_error err) {
cout<< "runtimeerror" << endl;
cout << err.what() << endl;
}
6. 異常類 #include <stdexcept>
exception // 最常見異常類
runtime_error // 下面幾個運行時錯
range_error // 生成的結果超出了有意義的值域範圍
overflow_error // 上溢
underflow_error // 下溢
logic_error // 可在運行前檢測到的問題,下面幾個邏輯錯
domain_error // 參數的結果值不存在
invalid_argument // 不合適的參數
length_error //超出該類型最大長度
out_of_range //使用一個超出有效範圍的值
#include <new>
bad_alloc
#include <type_info>
bad_cast
以上紅色的三個只有默認構造函數,不使用string初始化
7. 使用預處理器編寫調試代碼
#ifndef NDEBUG
cerr << “oh,shit!” << endl;
#endif
很多編譯器執行時
$ CC –DNDEBUG main.C
相當於在main.c開頭提供#define NDEBUG預處理命令
8. 預處理宏assert #include <cassert>
assert(expr)與NDEBUG配合使用的
只要NDEBUG沒有定義,則執行assert,若爲false則輸出信息並終止程序的執行
測試不可能發生的條件
異常是處理預期要發生的錯誤,而斷言是測試不可能發生的事情
一旦開發和測試完成,程序就已經建立好,並定義了NDEBUG。成品代碼中,assert不執行,因此沒有運行時代價,沒有運行時檢查。
assert僅用於檢查確定不可能的條件,只對程序的調試有幫助,不能用來代替運行時的邏輯檢查logic_error,也不能代替對程序可能產生的錯誤runtime_error的檢查
9. 百度百科中:
編寫代碼時,我們總是會做出一些假設,斷言就是用於在代碼中捕捉這些假設,可以將斷言看作是異常處理的一種高級形式。斷言表示爲一些布爾表達式,程序員相信在程序中的某個特定點該表達式值爲真。
使用斷言可以創建更穩定,品質更好且易於除錯的代碼。當需要在一個值爲FALSE時中斷當前操作的話,可以使用斷言。單元測試必須使用斷言
除了類型檢查和單元測試外,斷言還提供了一種確定各種特性是否在程序中得到維護的極好的方法。
使用斷言使我們向按契約式設計更近了一步。
我這樣理解,一座橋,有護欄,人們正常行走在上面,
assert(有個人翻越護欄跳進河)
assert(橋斷了) // 這些都是我們認爲不可能的,假設真的
上面是不可能發生的情況;
try {
if (護欄壞掉)
} catch(runtime_error err) {
cout << err.what() << endl;
}
while(cin >> s) {
assert(cin); // 多餘的
}
string s;
while(cin >> s && s != sought) {}
assert(cin);
解釋:在打開調試器的情況下,該循環從標準輸入讀入一系列單詞,直到讀入的單詞與sought相等或遇到文件結束符。如果遇到EOF,則執行assert並終止程序。
如果關閉調試器,則assert不做任何操作
第七章 函數
1. 最大公約數
int a,b;
while (b) {
inttemp = b;
b =a % b;
a =temp;
}
return a;
2. 函數必須指定返回類型
3. 指針形參,不改變實參指針值,但可以改變它們共同指向的對象
4. 形參使用引用可以避免複製。有些類不能複製。應該將不需要修改的引用形參定義爲const引用。普通的非const引用形參不能用const對象初始化,也不能用字面值或產生右值的表達式,以及需要進行類型轉換的對象作爲實參初始化,只能與完全同類型(short給int引用會出錯)的非const對象關聯!
5. void ptrswap(int *&p1, int*&p2) // 傳值(指針值)調用,函數裏面交換兩個指針的值,這樣的話,實參指針也交換了值
6. 什麼時候應該將形參定義爲引用:修改實參,大型對象不能複製,無法複製的類
7. 通常函數不應該有vector或其他容器類型的形參,傳遞迭代器就可以了(模板實現)
8. 數組形參
由於不能複製數組,所以無法編寫使用數組類型形參的函數。數組可以自動轉化成指針 ,所以通常通過操縱指向數組中的元素的指針來處理數組。
以下三個等價:都看作是指向數組元素的指針
void fun(int*)// 最好
void fun(int[]) // ok
void fun(int[10])
數組實參
數組實參若以非引用形式傳遞,則會悄悄的轉成指針,非引用類型的形參會初始化爲相應的的副本,實參是第一個元素的地址,而形參複製的是這個地址的副本,操作的是這個副本,所以不會修改實參指針的值,但是可以修改它們共同指向的對象內容。
所以,數組非引用實參是指針的值傳遞!
如果不修改數組元素(保護數組)時,那麼應該定義爲指向const對象的指針
void fun(const int*)
數組引用形參
void printValues(int (&arr)[10]) { ...... } // 括號是必須的
這個時候,數組實參不會轉成指針了,而是傳遞數組引用本身
必須寫大小,實參的大小必須與10匹配
t16_16,模板實現,允許傳遞指向任意大小的引用形參
多維數組的傳遞
void printValues(int (*matrix)[23], int rowSize) { .... } void printValues(int matrix[][23], introwSize); // 實際上是指針
9. t7_15
#define NDEBUG // 必須放在第一行
#include <iostream>
using namespace std;
//#define NDEBUG // 移到第一行去
int main(int argc, char** argv) {
assert(argc== 33);
cout<< "sum=" << (atof(argv[1]) + atof(argv[2])) <<endl;
system("pause");
return0;
}
10. 非void函數必須返回的特殊情況,main函數最後的reutn 0不是必須的
main函數不能遞歸,不能調用自身
main函數不能重載
#include<cstdlib>定義了兩個預處理器變量,說明程序執行成功或失敗:
EXIT_FAILURE
EXIT_SUCCESS
11. 函數返回值
返回值用於初始化在調用函數處創建的臨時對象temporary object
如果返回非引用類型,使用局部對象來初始化臨時對象
如果返回引用類型,則不要返回局部對象的引用,
也不要返回局部對象的指針
返回引用的函數:沒有複製返回值,返回的是對象本身
const string &shorterStr(const string&, conststring&);
如果不希望返回的對象被修改,加const(不能作爲左值了)
引用返回左值:返回引用的函數返回一個左值
inlinensaddr_t& pkt_src() { return pkt_src_; }
main函數中:
pkt_src()= 實參值;
如果不希望引用值被修改,返回值應該聲明爲const:
inline cosnt nsaddr_t& pkt_src() { return pkt_src_; }
該引用是被返回元素的同義詞
12. 考題
int &max(int a, int b) { // 返回引用.cpp
returna > b ? a : b;
}
int main() {
inti = 1, j = 2;
max(i,j)= 3;
cout<< "i=" << i << ",j=" << j <<endl; // i=1,j=2
// 因爲形參不是引用,不會改變j的值,改變的是局部b的值
system("pause");
return0;
}
13. 函數聲明
變量和函數都聲明在頭文件中,定義在源文件中
源文件包含聲明函數的頭文件,進行匹配檢查
聲明在頭文件中的好處是確保在所有文件中聲明一致,修改方便
14. 默認實參,:如果有一個形參有默認實參,那麼它後面的所有形參都要有
使用最頻繁使用的排在最後
默認實參可以是表達式或有返回值的函數調用
在頭文件中聲明默認實參(別人包含後纔是有效的),在源文件中定義的時候就不要寫默認實參了,否則出錯,因規定一個文件中默認實參只能聲明一次(定義也是聲明呀)
15. 自動對象
只有當定義它的函數調用時才存在的對象
一般指形參和局部變量,它們在定義它們的語句塊結束時撤銷
靜態局部對象
在第一次經過它的定義語句時創建,程序結束時撤銷
size_t count() {
static size_t c = 0; // 只執行一次
return++c;
}
int main() {
for(size_t ix = 0; ix != 20; ++ix) {
cout<< count() << “ ”;
}
return 0;
}
輸出1,2,3,4,5.....
16. const string &shorterStr(const string&,const string&);
定義它的好處:易讀,易改,保證統一,code reuse
壞處,慢!怎麼辦 inline!!!!
對於內聯函數,編譯器要展開代碼,僅看到原型是不夠的,
內聯函數應該在頭文件中定義,在頭文件中加入或修改內聯函數時,使用該頭文件的所有源文件必須重新編譯!
17. 成員函數
每個成員函數都有一個this形參,是調用該函數的對象的地址
對象實例調用自己的成員函數,將自己傳給這個成員函數與之進行綁定
this指針是隱含的,不能明式出現在形參表中
常成員函數bool same_isbn(constSales_item&)const;
total.same_isbn(trans);調用時,則這個this是指向total的const Sales_item*類型的指針, 是個指向const對象的指針,所以這個函數不能改變數據成員
const對象、指向const對象的指針或引用只能用於調用其const成員函數!!反過來,非const的這幫東西可以調用const的成員函數(並非必須是const的對象才能調用)
換句話:const對象不能調用非const成員函數
對於成員函數,聲明與定義必須一致,如果要const那麼兩者都要同時有const
18. 對象如果在全局作用域或定義爲局部靜態對象,那麼成員初始化爲0,如果對象在局部作用域中定義那麼沒有初始化
如果類成員有內置類型或複合類型(用其他定義的類型,如引用,指針,數組,容器)成員,那麼最好自己定義默認構造函數,不要合成的
19. 作業t7_31 Sales_item.h
20. 函數返回類型,最右邊形參具有默認實參,以及const的非引用形參不能作爲重載的區別標誌
int fuc1(int*);
int fuc1(const int*); //重複聲明 ,沒有error
//對於第二個,實參即可以是const int*也可以是非const int*的普通指針,所以沒區別,都是值傳遞,對副本操作
補充,若形參是const &int那麼區別了,是重載!此時,若實參是const對象則調用帶有const的那個函數,若調用另一個是需要轉換的故而不優,由此區分!若實參是非const對象,則兩個都可以調用,優先選擇調用不帶const的那個(調用帶有const的也需要轉換);指針也類似的情況(如果實參是const對象,則調用帶有const*類型形參的函數,否則如果實參不是const對象,將調用帶有普通指針形參的函數)
f(int*)
f(const int*)// 可以重載
而下面的
f(int*)
f(int*const) //不能重載(值傳遞,副本傳遞) // 這裏是const指針,只有指向const對象的指針作形參才能實現重載
再補充:僅當形參是引用或指針時,形參是否爲const纔有影響
int fuc2(int, int);
int fuc2(int, int b = 2); //重複聲明
p229 建議:何時不重載函數名
21. 函數重載的前提是函數彼此間作用域相同,否則就是屏蔽關係
局部聲明函數是個壞習慣,一般都有要聲明在頭文件中
C++中,名字查找發生在類型檢查之前,所以,在局部作用域內找到名字對應的函數後,不再去外層找了,即使類型不是最合適的。
重載確定
找與實參最佳match
void f(int)
void f(double, double = 3.21)
調用f(3.2); 將選擇下面那個,因爲精確匹配優於需要類型轉換的匹配。編譯器將自動將默認實參提供給被忽略的實參,因此這個調用函數擁有的實參可能比顯式給出的多
void f(int, int);
void f(double,double)
調用f(2, 3.24);有二義性
設計形參儘量避免實參類型轉換
注意,較小的整型提升爲int型。小的提升爲int,優於形式上合適的char類型的匹配!
void f(int)
void f(short)
ff(‘a’) // match上面第一個
還有,類型提升優於其他標準轉換!
例如:char到double優於到unsigned char的轉換
void f(unsigned char) // 若去掉unsigned則選擇第一個
void f(double)
調用f(‘a’) //二義
參數匹配與枚舉類型
枚舉類型的對象只能用同種枚舉類型的另一對象或一個枚舉成員進行初始化
整數對象即使有與枚舉元素相同的值也不能用於調用期望獲得枚舉類型實參的函數,但是可以將枚舉值傳遞給整型形參(129的時候提升爲int,而不是unsigned char)
22. 函數指針:bool (*pf)(int); //指向函數的指針
而指針函數:bool *pf(int); // 返回值是指向bool指針
使用typedef簡化函數指針
typedefbool (*pf)(int); //pf是一個種指向函數的指針的名字,它所指向的函數有一個int參數,返回值是bool類型
函數指針的初始化和賦值: (三種,0、函數名、&函數名)
第一種:pf fuc1 = 0;
//等於bool (*fuc1)(int)= 0; // 藍色部分就是類型
第二種:使用函數名
注意:bool setAge(int); 其中setAge除了用於函數調用外,其他任何使用都將解釋爲指針: bool(*)(int);
pf fuc2 = setAge; // 使用函數名進行初始化
fuc1 = fuc2; //或賦值
上面直接使用函數名setAge等於在函數名上取地址操作
pffuc2 = setAge;
pffuc2 = &setAge; // 兩個等價
而使用 pf fuc3(3); // 直接初始化是錯的
指向不同類型函數的指針不存在轉換
可以通過函數指針調用函數
fuc2 = setAge;
調用:fuc2(32); // 前提是這個函數指針已經初始化,若是0或未初始化不行
函數指針作形參
voidsamplefuction(const string&, bool(int));
//函數類型的形參,對應實參將被自動轉換爲相應的函數指針
voidsamplefuction(const string&, bool (*)(int));
//函數指針類型的形參,與上面聲明等價
返回類型是指向函數的指針(返回類型可以是函數指針類型,但不可以是函數類型) 後補充:函數ff的返回類型是一個指向”外圍”函數的指針
bool (*ff(int))(int*, int) // 藍色部分是類型 這句話用在這裏是在聲明ff這個函數
ff(int)函數返回一個函數指針,它的類型是bool (*)(int*, int)
使用typedef使用該定義更加簡明易懂
typedefbool (*PF)(int*, int)
PFff(int); // 聲明ff這個函數
舉例:
typedef int func(int*, int);
void f1(func); // ok,函數類型可以作形參,對應實參將自動轉成函數指針
func f2(int); // error!! 返回類型不能是函數類型,不能自動轉成函數指針
func *f3(int); // ok, 返回類型是函數指針
指向重載函數的指針
必須精確匹配
extern void ff(double);
extern void ff(int); // ok,重載
void (*pf)(double)= &ff; // ok, 精確匹配,對應第一個
int(*pf)(double) = &ff; // error, 返回值不匹配
第八章 IO
1. #include <iostream>
#include <sstring> // 從iostream繼承
#include <fstream> // 從iostream繼承
2. 國際字符支持
char有相應的wchar_t
wostream wistream wiostream <iostream>
wifstream wofstream wfstream <fstream>
wistringstream wostingstream wstringstream <sstream>
3. IO對象不能複製或賦值
不以放在vector或其他容器中
不能作爲形參,必須是引用或指針(返回值也要是引用)
形參也是非const的,因爲IO對象的讀寫會改變狀態
4. 條件狀態:
strm::iostate
strm::good 0 000
strm::badit 1 001 系統級
strm::failbit 2 010 可恢復
strm::eofbit 3 011
s.eof()
s.bad()
s.fail()
s.good()
s.clear() // 恢復所有
s.clear(f)
s.setstate(f) // 置爲某條件發生
s.rdstate() // 返回當前條件 返回類型strm::iostate
5. testFile.cpp
作業
t8_3.cpp get.hpp
t8_6.cpp get.hpp
cout << “abc” << flush;
cout << “abc” << ends;
cout << “abc” << endl;
調bug時,根據最後一條輸出確定崩潰的位置。多使用endl而不是’\n’,儘可能地讓它在崩潰前將該刷新的輸出出來
6. 交互式程序cin與cout綁定:任何cin讀操作都會首先刷新cout關聯的的緩衝區
tie函數,一個流調用tie將自己綁定在傳過來的實參流上,則它的任何IO操作都會刷新實參所關聯的緩衝區
cin.tie(&cout);
ostream *old_tie = cin.tie();
cin.tie(0); // 釋放tie
cin.tie(&cerr);
cin.tie(0);
cin.tie(old_tie); // 重新與cout綁定
7. 文件流
ifstream infile(filename.c_str());
或
ifstream infile;
infile.open(filename.c_str());
if (!infile)
infile.close(); // 打開另一個前要關閉
infile.open(filename2.c_str());
如果要重用文件流讀寫多個文件,則在讀另一個文件前要
infile.close();
infile.clear();
如果infile定義在while之中,那麼每次都是乾淨的狀態,不用clear都可以
8. vector中存放在着多個文件的名字
讀出來
t8_8.cpp
if (!input) {
cerr<< "error: can not open file: " << *it << endl;
input.clear();
++it;
continue;
}
//在vs提示符下d:/dev_projects/c++primer>c++primer < data/8_8
9. t8_9.cpp讀取文件內容,每個作爲一個元素放到vector<string>中
ifstreaminfile(fileName.c_str());
if(!infile) {//打開文件失敗
return1;
}
stringstr;
while(getline(infile, str)) { // 行作爲元素
//while(infile >> str) { // 單詞作爲元素 8-10
svec.push_back(str);
}
10. 文件模式
in // ifstream和fstream , ifstream默認以in打開
out // ofstream fstream 會清空數據相當於也打開了trunc
ofstream默認以out打開
app // ofstream 追加
ate 打開後定位在尾
trunc // ofstream 清空數據
binary // 打開後以字節的形式重,不解釋字符
ofstream outfile("data/testfile", ofstream::app);
fstream inout;
inout.open(“data/filename”, fstream::in |fstream::out);
ifstream& open_file(ifstream &infile,const string &filename) {
infile.close();
infile.clear();
infile.open(filename.c_str());
//if(!infile) // 不作判斷
return in;
}
調用open_file()
ifstream infile;
if(! open_file(infile, fileName)) {
cout<< "error: can not open file:" << fileName << endl;
system("pause");
return-1;
}
get(infile);// 讀取輸出
get的實現
while (in >>ival, !in.eof()) {
if (in.bad()) {
throw runtime_error("IO streamcorrupted");
}
if (in.fail()) {
cerr << "bad data, tryagain!";
in.clear();
continue;
}
cout << ival << "";
}
11. 字符串流
while (getline(cin, line)) {
istringstreamistrm(line);
while(istrm >> word) { // 處理這行中的每個單詞
.....
}
}
stringstream strm;
stringstream strm(s);
strm.str(); // 返回那個string對象
strm.str(s);
stringstream可以提供轉換和格式化
可使自動格式化
teststringstream.cpp
int val1 = 21, val2 = 23;
ostringstream out ;
out << "val1:" << val1<< "\nval2:" << val2 << "\n";
cout << out.str() << endl;
cout <<"=======================\n" << endl;
istringstream in(out.str()); // 關聯到這行字符val1:21\nval2:23
string dump;
int a,b;
in >> dump >> val1 >> dump>> val2; // 換成a和b就不行
//忽略所取數據周圍的符號,用dump
cout << val1 << " "<< val2 << endl;
cin.clear(istream::failbit); 在我機器上不管用
要使用cin.clear()
12. 文件讀入,每行作爲一個元素放到vector<string>中,再istringstream對象初始化爲每個元素,處理每個單詞,輸出; 這都是很簡單的程序
istringstream istr;
string word;
for (vector<string>::const_iterator it= svec.begin();
it != svec.end(); ++it) {
istr.str(*it);
while (istr >> word) {
cout << word << “ ” ;
}
istr.clear(); // 定義在外面,所以將流設置爲有效狀態
}
第九章 順序容器
1. vector.swap(): 兩個人換家,家裏東西不搬動
vector<string>sv1(10);
vector<string>sv2(32);
sv1.swap(sv2);// 不會使迭代器失效, 還指向原來的元素,常數時間內交換,沒有移動元素
cout<< sv1.size() << endl; // 32
cout<< sv2.size() << endl; // 10
2. 9-28 不同類型容器間的賦值
char *sa[] = {"hello","good", "nihao", "leimng"};
list<char*> slst(sa, sa + 4);
vector<string>svec;
svec.assign(slst.begin(), slst.end());
3. size是已經有的,capacity是再分配之前的容量,通常大於size, reserve(n), 其中n>capacity,則改變的是capacity=n不會改變size
while (ivec.size() != ivec.capacity()) ivec.push_back(0); // 只要還有剩餘的容量就不會重新分配
ivec.push_back(0); 此時再查看一下size和capacity,知道庫的分配策略了。
4. 刪除大寫字母
for (string::iterator it = str.begin(); it !=str.end(); ++it) {
if(isupper(*it)) {
str.erase(it); // erase操作使it後移到被刪元素的下一位置,而for中有++it,所以,下一步還原回來
--it; // 使迭代器指向被刪除元素的前一位置
}
}
用while寫可提高效率:
string::iterator it = str.begin();
while (it != str.end()) {
if(isupper(*it)) {
str.erase(it);
} else {
++it;
}
}
5. const vector<int> ivec(12);
vector<int>::iterator it = ivec.begin();// error,const類型迭代器不能賦給非const類型的it
改正:vector<int>::const_iteratorit = ivec.begin();
const vector<int>::iterator it =&ivec[0]; //error迭代器雖是指針,但不能這樣賦值
6. 任何無效迭代器的使用都會導致運行時錯誤 ,無法檢查出來
向vector添加元素,如果空間不夠,則重新加載,原迭代器全部失效。
容器可以調整大小resize(n),有可能使迭代器失效。 對於vector,deque,有可能全部失效; 對所有的容器,如果壓縮,則可能使被刪除元素的失效
7. 元素類型size_type value_type, reference等同於value_type& 引用 ,寫泛型程序非常用有
const_reference: value_type&
例子:vector<int>::size_type size = ivec.size(); / ivec.max_size();
ivec.resize(100,3); // 調整大小爲100,新加元素爲3
8. 向容器添加元素都是添加原值的副本
9. list容器前插法
while (cin >> word)
iter= ilst.insert(iter.begin(), word);
等同於:ilst.push_front(word);
10. 兩個容器可以大小比較,其實是其元素在作比較,如果元素不支持,則容器也不支持
ivec1 < ivec2
要求完全匹配,即容器類型與元素類型都相同,且元素類型支持<操作
11. 訪問元素
list<int>::referenceval = *ilst.begin(); // begin操作返回迭代器,其實是指針
list<int>::reference val2 =ilst.front(); // front和back操作返回元素的引用
ivec<int>::reference val3 = ivec[3]; 或 ivec.at(3); 隨機訪問類型的容器
12. 刪除元素:
c.erase(p);
c.erase(b, e); // 返回下一元素的迭代器
pop_back(); 刪除最後一個元素並返回void
pop_front 不返回被刪除元素的值,想拿到這個值,就要在刪之前調用front或back函數
清除clear
13. list是雙向鏈表實現的 deque: 可以隨機訪問,中間插入元素代價高,但是在首尾刪增元素代價小,且在首部增刪元素不會使迭代器失效(當然刪掉的就失效了)
如果讀入的時候要在中間插,則可以先讀入到list中,再拷到vector中,排一下序(書上說在拷之前排序)
14. string與vector不同的是,不支持棧方式操作容器,沒有front, back, pop_back
15. vector<char> cvec初始化string str(cvec.begin(), cvec.end())
對於一個string對象,要一次輸入一百個字符,則先str.reserve(101);改一下capacity
16. 以下是string的一些操作,構造,assign, erase, insert, append, replace, substr, find等等,使用的時候查
string構造另外方式:
string(c_array, n); //c_array可以是char *和char []的C風格字符串
string(s, pos2); //string類型s的下標pos2開始
string(s, pos2, len2);
替換str.assign(b, e); str.assign(n, t); //使用副本替換 返回string, 對於vector返回void
str.assign(s);// 以s的副本替換str
str.assign(c_array);
str.assign(s, pos2, len)
str.assign(cp, len)
str.erase(pos,len); // pos就是下標
str.insert(pos,cp); // 插入以NULL結尾的C風格串
str.insert(pos, s); // 插入s
str.insert(pos, s, pos2, len);
str.insert(pos, cp, len);
子串
str.substr(pos, n);
str.substr(pos);
str.substr(); // 返回str的副本
一系列的append和replace
查找:各種find, 返回類型string::size_type
find() // 找第一次出現
rfind() // 反向查找
find_first_of() // 任意字符的第一次出現 “0123456789” , 可以用來查找串中所有數字
find_last_of
find_first_not_of
find_last_not_of
沒有找到if (find() != string::npos)
17. 容器適配器
#include <stack>
#include <queue>
stack與queue默認是建立在deque之上,這種情況可以改變,通過第二類型實參來指定:
stack<string, vector<string> > str_stk; // 前後string肯定一樣啦
queue是隊列,不能建立在vector之上,因爲沒有pop_front()操作
priority_queue默認建立在vector之上,也可以用deque,不能用list,它要求隨機訪問
適配器可以比較 > < =
優先隊列也像棧一樣有top操作,它是返回具有最高優先級的值,但沒有back和front操作
優先隊列在push操作的時候,自動插入到適當位置
對於容器這幾章,建議使用的時候認真閱讀primer書和參考文檔
補充第九章:
1. 容器元素類型約束:賦值和複製運算。 不能是引用類型,不能IO類型
2. 支持隨機訪問的容器(vector, deque)的迭代器,支持it與數字的加減、it間的加減,it間的<>比較
3. 逆序輸出ilist元素:
Ilist<int>::iterator it = ilist.end();
While (it != ilist.begin()) {
Cout << *(--it) << endl;
}
第十章 關聯容器
1.pair在#include <utility>
pair<int, string> p1 = make_pair(2, “good”);
pair<int, string> p2(3, “hello”);
p1 < p2; 先比第一個,若相等,則比較第二個
p1.first 這兩個成員都是公有成員
p1.second
利用typedef pair<int, string> auther;
auther p3(4, “nihao”);
由於是公胡成員,所以可以cin >> p1.first >> p1.second;
2. map類型
map的元素類型map<k, v>::value_type是一個pair<constk, v>類型, first元素是const map<k,v>::key_type, second是map<k,v>::mapped_type
map<string, int>::iterator map_it =word_count.begin();
map<string, int>::key_type str = map_it->first; // 鍵值const只能讀不能改,跟set一樣
word_count[“leiming”] = 1; // 直接插入
while(cin >> name) {
++word_count[name];// 如果沒有,則直接++爲1
}
pair<map<string,int>::iterator, bool> mp;
mp = word_count.insert(p1); //返回的pair是pair<map<string,int>::iterator,bool>類型
if (!mp.second) {
cout << “沒有插入” << endl;
++mp.first->second;
}
在map中讀而不插入元素
if (word_count.count(“fool”)){ // 返回0或1
occurs= word_count[“fool”];
}
或者
mpa<string, it>::iterator it =word_count.find(“fool”); // find返回指向按k索引的迭代器
if (it != word_count.end()) {
occurs= word_count[“fool”];
}
erase操作
erase(k) // 返回0,1
erase(p) // void
erase(b,e) // void
遍歷輸出word_count的時候,按字典順序輸出單詞,很神奇!在使用迭代器遍歷map容器時(別的時不行),迭代器指向的元素按key的升序排列
第十一章 算法
1. size_t <cstddef>
back_inserter <iterator>
2. 提到的算法
find(vec.begin(), vec.end(), ival);
find_first_of(vec1.begin(), vec1.end(),vec2.begin(),vec2.end());
accumulate(vec.begin(), vec.end(), ival); // 第三個實參提供累加初始值和關聯的類型
//寫入運算不檢查目標的大小是否滿足寫入的數目
fill(vec.begin(), vec.end(), 0); // 是寫入安全的
fill_n(vec.begin(), 2, 0); // 前兩個元素賦爲0 ,必須保證vec至少有2個元素,否則運行錯
fill_n(back_inserter(vec),2, 0); // 相當於在vec後面push_back兩個0
back_inserter是迭代器適配器
copy(ilst.begin(), ilst.end(),back_inserter(ivec));
replace(ilst.begin(), ilst.end(), 0, 42); // 等於0的元素換成42
replace_copy(ilst.begin(), ilst.end(),back_inserter(vec), 0, 42); // ilst不變,複製到vec中,其中0換成42
sort(vec.begin(), vec.end());
vector<string>::iterator end_unique = unique(words.begin(),word.end()); // 把重複單詞移動到後面去,返回無復下一位置
算法不改變容器大小,要刪掉重複元素必須使用容器操作 words.erase(end_unique,words.end());
count_if(words.begin(), words.end(), GT6); //函數要求謂詞函數只能一個參數,即迭代器範圍內的元素
stable_sort(words.begin(), words.end(),isShorter);
以上四五行參考代碼test統計六個及以上字母組成的單詞.cpp
3. front_inserter使用push_front,這個算法不能用在Vector上,它沒有這個操作
inserter(ilst, it); //指定插入位置, 在it之前插
注意:copy(ilst.begin(), ilst.end(), inserter(ilst2,ilst2.begin()));//正序插入
//每次都在固定位置插入
copy(ilst.begin(),ilst.end(), front_inserter(ilst1));//逆序插入
4. d
list<int> ilst;
for (inti=0; i<5; ++i) {
ilst.push_back(i);
ilst.push_back(i);
}
ilst.sort();
printList(ilst);
ilst.unique(); // 刪掉,而不是後移重複元素,這不是算法函數,所以真正修改了基礎容器
// 真正修改基礎容器的還有remove,算法函數中remove是把等於指定值的元素們
// 前移,返回第一個不等於val的元素的位置
printList(ilst);
5.
6. ‘
類
1. 非static成員函數有this. Const必須同時出現在定義和聲明中,不然出錯
2. 只有類中有很少的方法且有公有數據時,使用Struct,或只有公有數據時。
3. 封裝好處:一,防止用戶級破壞,二,只要接口不變,不須改動用戶級代碼。 Cpp文件纔是類定義文件
4. 前向聲明是不完全類型,只能用於定義指針或引用(只是定義,不能使用它們來訪問成員),或聲明使用該類型作爲形參或返回值的函數。 可以定義該類型的static對象
5. this的類型是一個指向類類型的const指針。
const成員函數只能返回*this作爲一個const引用:對於返回*this或this的函數,const若有則必同時有
const Dog* run1() const { return this; } // const成員函數中,this是指向const對象的const指針,函數返回類型必須是const的
const Dog& run2() const { return *this; } // const成員函數中,*this是const對象,返回值只能是一個const的引用
6. 基於成員函數是否const可以重載,基於形參是否指向const對象可以重載。Const對象使用const成員。
7. 可變數據成員mutable,這樣const成員函數可以改變它或它是const對象的成員時也可改
8. 形參表和函數體處於類作用域中
Char Screen::get(indexr, index c) const { // index前不用加Screen
Indexrow = r*width;
return...
}
9. Student *sp = new Student();
Student s = Student;
構造函數不能是const的。 創建const對象時,運行一個普通構造函數來初始化const對象
構造函數的初始化在冒號之後,在執行構造函數之前。初始化列表中沒有寫出來的成員的初始化情況:對於類類型則調用默認構造函數,對於內置或複合類型的初始化:(如果對象定義在局部作用域中,則不進行初始化,如果定義在全局作用域中,初始化爲0),所以,含有內置類型或複合類型的類應該定義自己的構造函數來初始化這些成員,不應該依賴合成的構函。初始化順序是成員的定義順序,而非初始化列表寫的順序
必須在初始化列表中初始化:無默認構函的對象成員,const或引用成員
如果沒有自己定義了構函,且都是單參並有各自的默認實參:error
explicit:避免了單參構函的形參類型到該類類型的隱式轉換 它只能使用在類定義體內的構函前面的聲明上,類定義體外此構函定義時前面不能加explicit
Studentstu = stu1; 執行復制構函
StudentStu(“leiming”, 24); // 直接初始化
Studentstu(stu1); // 複製構函
10. 對於沒有定義構函的且成員全是public的類可以使用Data val2 = {23, “abc”}的方式初始化
struct Data {int ival; char *p;};
11. 友元可以是非成員函數,可以是已知類的成員函數,也可以是類
12. 若要將成員函數定義爲inline,則聲明的時候可以不必寫inline,它是用於定義的關鍵字。定義爲inline
13.