前4章
void Tele::OnOff(TV& t){t.on_off = (t.on_off == true)? false: true;};
void Tele::OnOff(TV& t){t.on_off = (t.on_off == t.on)? t.off: t.on;};(原文,類聲明中有enum{on,off};)
三目運(yùn)算代替if,置位,復(fù)位目的
注意,on_off是TV類的私有成員,TV類聲明中將自己暴露給Tele,即
friend class Tele;
std::endl與'\n'區(qū)別是前者除了換行還調(diào)用輸出流的flush函數(shù),立即將數(shù)據(jù)寫入文件或屏幕
強(qiáng)制類型轉(zhuǎn)換
(float)5/8;值0.625
<font color=red>std命名空間聲明在此 </font>
D:\Microsoft Visual Studio 14.0\VC\include\yvals.h
#if defined(__cplusplus)
#define _STD_BEGIN namespace std {
#define _STD_END }
#define _STD ::std::
在<iostream>中用到相關(guān)宏
_STD_BEGIN
cout;
cin;這些對象
...
_STD_END
最麻煩
std::cout << ...;
次之
using std::cout;
最偷懶,但最易引起沖突
using namespace std;
因?yàn)橐陨弦来卧卺尫抛饔糜?釋放得越廣,沖突可能越大
宜將using namespace zhou;放到函數(shù)作用域內(nèi),效果就是將相關(guān)名字只釋放到函數(shù)域內(nèi)
沒有誰會將函數(shù)的返回值修飾成& ,同時將函數(shù)局部域變量返回
分割線就是***
分割線也是---
函數(shù)聲明只是告訴編譯器有這么個函數(shù)存在,并不分配內(nèi)存,只有在函數(shù)定義時才分配內(nèi)存
函數(shù)的聲明,定義分開在不同的文件(.h與.cpp),是種好習(xí)慣
原因是:聲明和定義在一起,這種必是在一個文件中,缺點(diǎn)是其他文件,模塊想使用該方法,就需要先包含函數(shù)的聲明,即包含該聲明定義一體的文件,其實(shí)就造成了另一模塊也有該函數(shù)的定義,即重定義
要解決這個,除非讓想使用該函數(shù)的模塊只包含函數(shù)的聲明就得行
任何定義在函數(shù)外部的變量即全局變量
變量初始化的目的,便于檢查,比如年齡不能為0,另者就是作為默認(rèn)值
定義變量才分配內(nèi)存,為變量名分配內(nèi)存地址,聲明不會
#include <locale>
**setlocale9(LC_ALL, "chs"); //設(shè)置中文簡體**
**wchar_t wt[] = L"中"; //L告訴編譯器為"中"分配兩個字節(jié)空間**
wcout << wt;
p27變量所占空間
sizeof()求出數(shù)據(jù)結(jié)構(gòu)占用的內(nèi)存大小
cout << "sizeof int: " << sizeof(i) << endl; //4Byte
cout << "sizeof string: " << sizeof(str) << endl; //32位工程:24Byte,無論string內(nèi)是否有數(shù)據(jù),都是24Byte,按此邏輯,string內(nèi)部對字符的管理是char*;而由于指針在64位工程下是8Byte,所以string在64位工程下是32Byte,string內(nèi)部管理兩個char*
cout << "sizeof pointer: " << sizeof(p_i) << endl; //指針在不同的工程上位寬不一致,64位8Byte,32位4Byte
cout << "sizeof bool: " << sizeof(b) << endl; //1Byte
cout << "sizeof long: " << sizeof(l) << endl; //4Byte
cout << "sizeof double: " << sizeof(d) << endl; //8Byte
cout << "sizeof char: " << sizeof(ch) << endl; //1Byte
溢出不會報錯,又會從最小的開始計(jì)數(shù)
3種float
float; double; long double;
float 精度只有6-7位,即小數(shù)點(diǎn)后6-7,double可達(dá)到15位
設(shè)置輸出精度
#include <iomanip>
float a = 12.3332323123344332232;
cout << setprecision(15) << a;
第5章 語句與邏輯表達(dá)式
用于計(jì)算的操作都可看作表達(dá)式,表達(dá)式實(shí)際是調(diào)用函數(shù),表達(dá)式總能返回一個值
三個典型的表達(dá)式
double PI = 3.1414;
PI; //表達(dá)式
1; //亦表達(dá)式
//空語句
;
//塊,用來存放多條語句
{
;
;
}
求模%,常被放在循環(huán)里,用來間隔n次執(zhí)行,降低了循環(huán)頻率
//常放在循環(huán)里,count亦可在循環(huán)外,就不必聲明成static
static uint32_t count = 0;
if(0 == count++ % 6) //間隔6次滿足條件
{
}
前置與后置的例子
int i = 9 ;
std::cout << ++i; //10
std::cout << i++; //10
關(guān)系運(yùn)算符用來對兩個表達(dá)式比較,根據(jù)比較結(jié)果返回真或假,6種關(guān)系運(yùn)算符
==
!=
<
>
<=
>=
邏輯運(yùn)算符
&&
||
!
三目運(yùn)算符
z = (a>b)?a:b;
//?:三目運(yùn)算符,唯一一個需要三個操作對象的運(yùn)算符
//三目運(yùn)算符執(zhí)行優(yōu)先級,從右到左
z = a>b?a:a>b?a:b;
//等價
z = a>b?a:(a>b?a:b);
大寫字母轉(zhuǎn)小寫字母
char a;
a = ('A' < a && a < 'Z')?(a+32):a;
第6章 面向?qū)ο?/h3>
面向?qū)ο笳Z言初衷:解決c語言的兩大難題-代碼可重用性差;維護(hù)難
四大特征:抽象,繼承,封裝,多態(tài)
類,結(jié)構(gòu)體是數(shù)據(jù)結(jié)構(gòu),所以末尾不是
{
} //這是塊
而是
};
Q:聲明類不會分配內(nèi)存,定義類會不會分配內(nèi)存呢?那類的實(shí)例會分配內(nèi)存吧?
A:聲明和定義類并不分配內(nèi)存,聲明類的實(shí)例才分配內(nèi)存,與定義變量一個道理;類所占用的內(nèi)存大小由它的數(shù)據(jù)成員的內(nèi)存大小決定(p104)
Q:類的內(nèi)存大小?數(shù)據(jù)成員計(jì)作內(nèi),函數(shù)入?yún)?出參是否計(jì)作內(nèi)?
Q:為什么將數(shù)據(jù)成員設(shè)置成私有?
A:提高安全性,防止錯誤的輸入輸出,做訪問控制,比如先檢查對數(shù)據(jù)成員的操作是否合規(guī)
Q:什么函數(shù)宜修飾成內(nèi)聯(lián)函數(shù),函數(shù)體積小且頻繁調(diào)用
如果不想讓成員函數(shù)修改成員變量的值,那么將成員函數(shù)的函數(shù)體修飾為const,對于不應(yīng)該修改數(shù)據(jù)成員的函數(shù)宜多用const,幫助查錯
const{}
如果已經(jīng)創(chuàng)建帶參構(gòu)造函數(shù),又想要一個不帶參的構(gòu)造函數(shù),那么就只有手工創(chuàng)建一個
重要概念辨析:
默認(rèn)構(gòu)造default constructor并不是"沒顯式聲明,編譯器自動提供的構(gòu)造叫默認(rèn)構(gòu)造",而是不帶參數(shù)的構(gòu)造叫默認(rèn)構(gòu)造
它的狀態(tài)是:若程序員不顯式提供,則編譯器自動提供,或者編譯器自動為帶參構(gòu)造添加默認(rèn)的構(gòu)造代碼(比如包含了類,這些默認(rèn)構(gòu)造代碼位于自定義的構(gòu)造代碼前)
第7章
switch結(jié)合到while或if可以持續(xù)循環(huán),直到某case被激活,跳出循環(huán)
第8章
任何被定義的變量都有地址,意思就是任何被定義的變量都是分配了內(nèi)存空間的
類型別名
typedef unsigned short int ut;
指針3大用途
- 處理堆中存放的大型數(shù)據(jù)
- 快速訪問類的數(shù)據(jù)成員和函數(shù)
- 別名方式向函數(shù)傳參
內(nèi)存的幾種形式 - 棧區(qū) 2M大小由編譯器分配,釋放,有函數(shù)參數(shù)值,局部變量
- 堆區(qū) 程序員手工分配釋放
- 寄存器區(qū) 保存棧頂指針和指令指針
- 全局區(qū)(靜態(tài)區(qū)static) 存放全局變量和靜態(tài)變量(包括全局靜態(tài),局部靜態(tài)).初始化的全局和靜態(tài)放一塊,未初始化的全局和靜態(tài)在相鄰的另一塊,程序結(jié)束系統(tǒng)自動釋放
- 文字常量區(qū) 存放常量字符串,系統(tǒng)自動釋放
- 程序代碼區(qū) 存放函數(shù)體二進(jìn)制代碼
throw的是放在棧上的局部對象
棧內(nèi)存有限,效率高(速度快),只在函數(shù)內(nèi)有效,超限會overflow,大數(shù)據(jù)結(jié)構(gòu)宜放在堆中
堆特點(diǎn):存儲較大數(shù)據(jù),生命周期靠new delete控制,堆以鏈表形式由系統(tǒng)管理
函數(shù)入棧簡析:
被調(diào)函數(shù)下一行指令地址入棧
函數(shù)參數(shù)從右至左入棧
函數(shù)局部變量
入棧時,棧頂指針向低地址增長
堆靈活,作者舉例,比如一個對象能被多個函數(shù)訪問,但又不想使其成為全局變量,這時,創(chuàng)建堆中對象較好
堆中內(nèi)存不具名的好處之一:只有特定類型的指針才能訪問特定類型的數(shù)據(jù),避免了會有任何試圖修改它的非法操作
一塊內(nèi)存空間被delete后不要再delete它,否則會崩潰,一般如下操作,釋放掉堆內(nèi)存后為堆指針賦個空值
if(nullptr != p)
{
delete p;
p = nullptr;
//如果p為nullptr,即使delete p是無風(fēng)險的
}
指針未被初始化一個內(nèi)存地址或delete堆中空間后一定要將指針賦nullptr(地址清零)
#include <iostream>
using std::cout;
using std::endl;
int main()
{
int* p = new int(3);
delete p;
long* p1 = new long(9999);
//注意以下操作
*p = 23;
cout << *p1 << endl; //23,重大的內(nèi)存操作錯誤
delete p1;
return 0;
}
由于編譯器默認(rèn)將釋放掉的內(nèi)存空間分配給新開辟的空間,所以p, p1指向的空間一致.解決辦法就是如上以及使用智能指針,雖然使用空指針可能會崩潰,但崩潰總比找不到錯誤所在要好,另外此處看出:delete只是說明內(nèi)存不被程序(指針?biāo)?dú)占),但仍然不能阻止指針通過*間址訪問內(nèi)存
int i ;
int p = &i;
p++; //p值每次增加4,因?yàn)楸4娴氖莍nt變量地址
char ch;
char* p_ch = &ch;
ch++; //ch每次遞增一字節(jié)
常量指針,指向內(nèi)存不能改變,但能改變內(nèi)存上的值
A* const p = new A;
p++; //錯誤的,p++后p保存的地址就變了,也不能對p賦值
*p = 77; //ok的
指向常量的指針,可以改變指向,指向不同的內(nèi)存,但不能修改所指內(nèi)存上的數(shù)據(jù)
const A* p = new A;
p->set(77) ; //錯誤,set()成員函數(shù)是個修改成員變量的操作
p = new A; //再來一個堆中對象,ok
指向的內(nèi)存固定,不允許指向其他內(nèi)存空間;且不允許修改所指內(nèi)存上的數(shù)據(jù)-指向常量的常指針
第9章 引用
引用就是對象別名,就是小老婆,一旦依附某變量就不能改變依附,不能++等改變引用本身的操作
定義引用時需要同時進(jìn)行初始化
引用雖然造成了多個變量共享同一塊內(nèi)存的現(xiàn)象,但是引用是沒有"引用計(jì)數(shù)"的;指針類型的變量沒必要取別名.原對象超出作用域時別名也消失
引用和指針的典型用法是作為函數(shù)入?yún)?這樣起到擴(kuò)充返回參數(shù)的作用
不可能將引用和指針作返回值,同時原對象(或指向的內(nèi)存)是棧中的局部變量(內(nèi)存)
按值傳遞消耗時間及內(nèi)存,入?yún)?出參均會以拷貝的方式
對象按值傳遞會調(diào)用復(fù)制構(gòu)造函數(shù),在返回時,傳遞進(jìn)來的副本會調(diào)用析構(gòu)系函數(shù)來銷毀,而如果要按值返回對象,則又會調(diào)用復(fù)制構(gòu)造函數(shù),返回完,則調(diào)用析構(gòu)函數(shù)銷毀出參的對象
可以對堆中空間取別名
int* p = new int(6);
if(null != p)
int& r = *p; //r即為內(nèi)存空間的別名
r = 10; //即*p = 10;
Q:那么拋出一個問題,p指向的空間被delete后r的狀態(tài)呢?
A:經(jīng)過測試,保存的仍然是原空間地址,但實(shí)際這是不安全的,和指針delete后的情況一致,即仍指向原空間但并不是專有此空間,有新的new時會將這塊已經(jīng)被釋放的空間分配出去
p136
A& r = func();
//fun()函數(shù)并非將返回值聲明成&,此處是新定義一個引用對象,按值接收函數(shù)內(nèi)返回的副本,這個副本的生命周期會跟隨r
//如果引用的是臨時變量,變量的生命周期不小于引用的生存期
另一情況,用指針指向臨時變量,同樣的背景:A func()按值返回
A* p = &func();
//返回的副本立即被銷毀(調(diào)用析構(gòu)函數(shù)),p指向的空間具有不確定性,不是p所獨(dú)占
引用和指針作返回值的用例,按引用或指針返回一個堆中創(chuàng)建的空間
但此法不安全,不滿足在哪創(chuàng)建就在哪銷毀的原則,宜使用智能指針來接收堆中對象
譬如聲明A& func(),使用時需要定義一個別名來接收
A& ra = func();
//值得品味的是下面這種,不會調(diào)用賦值=運(yùn)算符函數(shù),調(diào)用的是復(fù)制構(gòu)造函數(shù),這是編譯器自己發(fā)現(xiàn)了用值接收,所以按值返回
//**如此會造成內(nèi)存泄漏,按拷貝返回出來,a1其實(shí)得不到堆中地址的**
A a1 = func();
引用和原變量關(guān)聯(lián)的地址相同,說明,只是不同的符號,關(guān)聯(lián)相同的地址
int main(int argc, char** argv)
{
uint32_t i = 0;
uint32_t& ri = i;
cout << "&i 表示i這個符號綁定的內(nèi)存,這塊內(nèi)存的地址:" << "\t" << &i << endl;
cout << "&ri表示ri這個符號所關(guān)聯(lián)的內(nèi)存, 這塊內(nèi)存的地址:" << "\t" << &ri << endl;
system("pause");
return 0;
}
第10章 深入函數(shù)
掌握深層復(fù)制,運(yùn)算符重載,要徒手寫幾個運(yùn)算符重載的函數(shù)
一切運(yùn)算執(zhí)行均是調(diào)用函數(shù),要重視
全局函數(shù)的默認(rèn)值參數(shù)初始化
void func(int = 0, int = 0); //聲明
同樣,類的成員函數(shù)也可以有這種聲明
Q:只為一個參數(shù)指定默認(rèn)值可以不?
A:參數(shù)只有一個指定了默認(rèn)值是可以的,但是,該默認(rèn)參數(shù)應(yīng)在后面,否則在它后面還有未指定默認(rèn)值的參數(shù)會報錯
void set(int = 100, int = 200); //ok
//void set(int _j, int _k = 200); //ok
//void set(int _j = 100, int _k); //error
參數(shù)初始化列表,唯三必須用參數(shù)初始化列表的
- 成員變量是引用
- const成員的初始化
- 成員變量是未提供默認(rèn)構(gòu)造函數(shù)的類類型
class A
{
A(int x, int y):total(x), num(y) {}
private:
int& total;
const int num;
};
成員變量的初始化順序與構(gòu)造函數(shù)中初始化順序無關(guān),只與在類中的說明順序有關(guān),析構(gòu)函數(shù)的析構(gòu)順序正好相反
構(gòu)造函數(shù)是在初始化成員變量,所以,如果有包容其他對象,那么,會先執(zhí)行包容對象的構(gòu)造,然后執(zhí)行自身的構(gòu)造,析構(gòu)時順序正好相反,先析構(gòu)自身呢個,再析構(gòu)包容的對象
按值傳遞對象會調(diào)用復(fù)制構(gòu)造函數(shù)
A::A(A& ra);
參數(shù)是類A的引用,那么就可以通過該引用來訪問它的對象,引用只能被初始化,不能被賦值,把引用說明成常量引用是非常好的主意,構(gòu)造函數(shù)不必改變傳進(jìn)來的對象
//實(shí)現(xiàn),淺拷貝,若p為指針
A::A(A& ra)
{
this-> p = ra.p;
}
//深拷貝
A::A(A& ra)
{
*(this-> p) = *(ra.p);
}
每個類都有一個默認(rèn)復(fù)制構(gòu)造函數(shù),使用引用來訪問指定對象的內(nèi)存地址,然后復(fù)制該對象的成員變量到自己的成員變量
默認(rèn)構(gòu)造函數(shù),它不是將數(shù)據(jù)成員初始化成默認(rèn)值,數(shù)據(jù)成員是未初始化的
把數(shù)字當(dāng)作對象賦給另一對象,將數(shù)字進(jìn)行類型轉(zhuǎn)換,一個重要的操作,判斷該類的構(gòu)造函數(shù)的參數(shù)是否與數(shù)字的類型匹配,假如匹配則調(diào)用構(gòu)造函數(shù)創(chuàng)建臨時對象,跟著將該臨時對象賦給=操作符左邊的對象,最后會調(diào)用析構(gòu)函數(shù)銷毀臨時對象
A a(99);
a = 100; //這步會調(diào)用構(gòu)造函數(shù)(參數(shù)類型能與int匹配的構(gòu)造函數(shù),創(chuàng)建臨時對象),operator=()函數(shù)(臨時對象賦給左邊),析構(gòu)函數(shù)(銷毀臨時對象)
a = A(2); //與上例一樣的過程
/*
上兩例執(zhí)行結(jié)果
1. A& operator=(A& ra) { cout << "operator=()函數(shù)執(zhí)行" << endl; return ra; } //參數(shù)必須是A或A&,返回值可以是A&或void或A
構(gòu)造函數(shù)執(zhí)行
構(gòu)造函數(shù)執(zhí)行
operator=()函數(shù)執(zhí)行
析構(gòu)函數(shù)執(zhí)行
析構(gòu)函數(shù)執(zhí)行
2. A operator=(A& ra) { cout << "operator=()函數(shù)執(zhí)行" << endl; return ra; }
構(gòu)造函數(shù)執(zhí)行
構(gòu)造函數(shù)執(zhí)行
operator=()函數(shù)執(zhí)行
復(fù)制構(gòu)造函數(shù)執(zhí)行
析構(gòu)函數(shù)執(zhí)行
析構(gòu)函數(shù)執(zhí)行
析構(gòu)函數(shù)執(zhí)行
3. A operator=(A ra) { cout << "operator=()函數(shù)執(zhí)行" << endl; return ra; }
構(gòu)造函數(shù)執(zhí)行
構(gòu)造函數(shù)執(zhí)行
operator=()函數(shù)執(zhí)行
復(fù)制構(gòu)造函數(shù)執(zhí)行
析構(gòu)函數(shù)執(zhí)行
析構(gòu)函數(shù)執(zhí)行
析構(gòu)函數(shù)執(zhí)行
4. A& operator=(A ra) { cout << "operator=()函數(shù)執(zhí)行" << endl; return ra; }
構(gòu)造函數(shù)執(zhí)行
構(gòu)造函數(shù)執(zhí)行
operator=()函數(shù)執(zhí)行
析構(gòu)函數(shù)執(zhí)行
析構(gòu)函數(shù)執(zhí)行
*/
explicit A(int x){}; 關(guān)鍵字會關(guān)閉這種轉(zhuǎn)換特性
Q:何時調(diào)用=y運(yùn)算符函數(shù),何時調(diào)用復(fù)制構(gòu)造函數(shù)?
A:按值傳遞參數(shù)時調(diào)用復(fù)制構(gòu)造函數(shù)
默認(rèn)的復(fù)制構(gòu)造函數(shù)都是搞的淺拷貝,要實(shí)現(xiàn)深拷貝就要自己創(chuàng)建復(fù)制構(gòu)造函數(shù),深拷貝后兩個對象都有各自的內(nèi)存區(qū)域,互不干擾
第11章運(yùn)算符重載
class A
{
public:
A(int _i): _i(i){}
void operator++(); //前置自加運(yùn)算符函數(shù)
public:
int i = 0;
}
A a;
++a; //調(diào)用operator++()
來幾個實(shí)驗(yàn),operator++()的返回值聲明成A
//對象如上例,只是變A operator++()
1.
A operator++()
{
++i;
A t(i); //返回臨時對象
return t;
}
2.
A operator++()
{
++i;
return A(i); //返回匿名臨時對象
}
3.
A operator++()
{
++i;
return *this; //返回對象自身,減少了上面兩步中的臨時對象,減少資源占用,但它是按值返回,同樣需要調(diào)用復(fù)制構(gòu)造函數(shù)
}
4. 為了解決上面按值返回,將返回值修飾成&,并且不可能執(zhí)行++++a;這樣的操作,所以有必要將返回值定義為常量
const A& operator++(){}
后置自加運(yùn)算符函數(shù)operator++(int _i),帶一個參數(shù)與前置自加相區(qū)別
const A operator++(int _i){A temp(*this); ++i;return temp;}
返回值修飾成const的意義不大,只是說返回值不能立即修改,而接收它的對象可以修改
加法運(yùn)算符函數(shù) operator+()
A a,b;
a + b; //即a.operator(b);
//由此,可以推斷operator+()的定義
const A& A::operator+(A& a)
{
return A(i+a.i);
}
賦值運(yùn)算符函數(shù)operator=(& r),默認(rèn)調(diào)用它,會將對象b的成員變量復(fù)制到對象a中去,即不可避免地調(diào)用復(fù)制構(gòu)造函數(shù)(經(jīng)過驗(yàn)證,確實(shí)如此,默認(rèn)進(jìn)行的是淺拷貝)
A a,b;
a = b; //即a.operator(b);
賦值,涉及到重要的問題,就是淺拷貝還是深拷貝,淺拷貝就是將指針值(地址)拷貝,深拷貝是將內(nèi)存上的數(shù)值拷貝
重要問題,思考下下面這段代碼是否安全
//A.hpp
#pragma once
#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
A() { p = new int; }
A(int _i) { p = new int(_i); }
~A() { delete p; cout << "析構(gòu)函數(shù)執(zhí)行" << endl; }
//升級后的復(fù)制構(gòu)造函數(shù)
A(A& ra) { p = new int; *p = *(ra.p); }
A operator=(A& ra) { *p = *(ra.p); return *this; }
int get() { return *p; }
private:
int* p = 0;
};
//main.cpp
#include "A.hpp"
#include <iostream>
using std::endl;
using std::cout;
int main(int argc, char argv[])
{
A a(5);
A a1 = a; //operator=()執(zhí)行時會調(diào)用默認(rèn)的復(fù)制構(gòu)造函數(shù),然后調(diào)用析構(gòu)函數(shù),析構(gòu)時副本的地址空間和原對象空間一致,所以析構(gòu)出問題
//解決辦法就是升級(重載)復(fù)制構(gòu)造函數(shù),即A(A& ra) { p = new int; *p = *(ra.p); }
cout << a1.get() << endl;
return 0;
}
//A:崩掉,原因在A a1 = a;時,析構(gòu)副本時將原對象中p指向的空間析構(gòu),導(dǎo)致main函數(shù)結(jié)束時無法析構(gòu)原對象p所指空間,崩潰
可見operator=()函數(shù)和A(A& ra)復(fù)制構(gòu)造函數(shù)都必須考慮深淺拷貝的問題,壞就壞在operator=()會不可避免地調(diào)用復(fù)制構(gòu)造函數(shù)(要按值返回的嘛)
解決辦法之二:
A& A::operator=(A& ra){*p = *(ra.p); return *this;}
//將返回值修飾成引用,就不會調(diào)用復(fù)制構(gòu)造函數(shù)
解決辦法之三:
//將返回值修飾成void,不返回,自然不會調(diào)用復(fù)制構(gòu)造函數(shù)
void operator=(A& ra) { *p = *(ra.p);}
以上這波是operator=()要不要調(diào)用復(fù)制構(gòu)造函數(shù)?及復(fù)制構(gòu)造函數(shù)用深淺拷貝的問題?
Q:如果是自己賦給自己a=a;有沒破綻?
A:由于賦值運(yùn)算符必須先釋放掉舊值,然后根據(jù)新值分配數(shù)據(jù),當(dāng)對象a.operator(a),左側(cè)a釋放p指向的內(nèi)存,然后根據(jù)右側(cè)參數(shù)a的指針p指向的內(nèi)存來給左側(cè)a賦值時就出問題了
所以在operator=()中添加一句
if(this == &ra)
return *this;
通過operator關(guān)鍵字進(jìn)行類型轉(zhuǎn)換,operator配合要轉(zhuǎn)換的類型,構(gòu)成一個重載運(yùn)算符函數(shù)
轉(zhuǎn)換運(yùn)算符的重載函數(shù)沒有返回值,這點(diǎn)與構(gòu)造函數(shù),析構(gòu)函數(shù)類似.該函數(shù)雖然沒有返回值,但仍可以返回值,返回類型根據(jù)operator后面的類型符定
A::operator float() { return (float)*p; }
//main.cpp中使用
A a(67);
float f = a;
利用該函數(shù)完成對象->變量的轉(zhuǎn)換
標(biāo)準(zhǔn)的賦值運(yùn)算符函數(shù),看它的返回類型是&
//operator=()函數(shù),深拷貝
movedemo& operator=(const movedemo& r) { cout << "operator=() " << endl; num = new int(*(r.num)); return *this; }
按引用傳入,返回,但其成員變量不是用深拷貝的話,成員變量的賦與以按值的形式
不可被重載的運(yùn)算符
# //預(yù)處理
:: //域限定符
* //指針運(yùn)算符
. //成員選擇符
?: //重載沒意義
第12章 繼承
#include <iostream>
using std::endl;
using std::cout;
class A
{
public:
A() { cout << "A() run " << endl; }
//virtual ~A() { cout << "~A() run " << endl; } //解決辦法將它聲明成virtual 在delete時會多態(tài)性執(zhí)行派生類的析構(gòu)函數(shù)
virtual ~A() = default;
};
class B : public A
{
public:
B() { cout << "B() run " << endl; }
~B() { cout << "~B() run " << endl; }
};
int main(int argc, char* argv[])
{
A* b = new B;
delete b;
return 0;
}
思考:如果基類的析構(gòu)函數(shù)不聲明成virtual 會發(fā)生什么重大錯誤?
A:只會調(diào)用基類的析構(gòu)函數(shù)不會調(diào)用派生類的析構(gòu)函數(shù)
作者言中了,的確基類指針保存派生類對象的后果要考慮析構(gòu);要充分釋放資源
http://www.reibang.com/p/ef66ba66916c
私有成員無論何種繼承(公有,私有)都只留有本類成員和友元接口(方法)
class A public: B
上句中的public是訪問類型
派生類對象可以賦給基類對象,派生類對象地址可以交由基類指針保管(多態(tài)),派生類對象可以初始化基類引用(多態(tài))-以上三種反過來不得行
私有繼承方式中,基類中的public和protected成員在派生類中的訪問權(quán)限變?yōu)閜rivate,對派生類而言,它仍能訪問這些成員(私生子仍能享有這些財(cái)產(chǎn)),但不能將這些接口暴露出去,派生類對象要使用這些接口必須先定義公有成員作為接口,在公有接口中調(diào)用私有成員(使用這些財(cái)產(chǎn)略顯麻煩)
私有派生不利于繼續(xù)派生,實(shí)際使用不多
Q:派生類不顯式構(gòu)造基類部分,基類會如何?會執(zhí)行哪個構(gòu)造函數(shù)?
A:
派生類初始化基類部分兩種方式:
- 派生類構(gòu)造函數(shù)初始化所有數(shù)據(jù)包括基類部分的,這種稱之為多此一舉,因?yàn)榛惖臉?gòu)造函數(shù)已經(jīng)做了這個工作
- 派生類構(gòu)造函數(shù)參數(shù)初始化列表,或以調(diào)用形式調(diào)用基類構(gòu)造函數(shù)
析構(gòu),就不需要顯式地調(diào)用基類的析構(gòu)函數(shù)
多重繼承構(gòu)造按繼承時聲明順序執(zhí)行
多重繼承,基類的成員名相同的話會產(chǎn)生二義性,解決辦法就是作用域運(yùn)算符來起限定域的目的
C c;
c.a::print();
c.b::print();
派生類與基類有同名,或者是同名(同簽名,如const{})且同參函數(shù)時,派生類將基類的同名(同簽名)同參函數(shù)覆蓋,將派生類同名函數(shù)隱藏
覆蓋支持多態(tài),隱藏破壞多態(tài)性
一個類從多個基類派生,而這些基類又有共同的基類,那么在派生類中訪問共同的基類成員時會產(chǎn)生二義性
解決辦法,聲明為虛基類
//將最高基類說明成虛基類
class A : virtual public B
{};
基類指針不宜強(qiáng)制轉(zhuǎn)派生類指針,若要強(qiáng)轉(zhuǎn),需要考慮安全與異常處理,即調(diào)用dynamic_cast<>,轉(zhuǎn)換失敗會拋出異常,虛基類的指針或引用則是不能轉(zhuǎn)派生類指針引用,原因在于虛基類在內(nèi)存中布局所致
第13章 虛函數(shù)
第12章開頭的警示代碼需要注意,思考如何解決
C++多態(tài)性的特征之一就是允許把派生類對象賦給基類指針,并使用該指針訪問基類的數(shù)據(jù)和函數(shù)
virtual表明該函數(shù)有多種形態(tài),該函數(shù)可能被多個對象所擁有
- 指針(一般是基類指針)在編譯時不確定是指向派生類對象,還是基類對象.在運(yùn)行時才確定的叫動態(tài)聯(lián)編或叫運(yùn)行時聯(lián)編;
- 在編譯時就確定好了的叫編譯時聯(lián)編或叫靜態(tài)聯(lián)編
(這種確定以在編譯時確定對象還是在運(yùn)行時確定對象的方式可以取名為對象聯(lián)編)對于靜態(tài)聯(lián)編,對象不用對自身進(jìn)行跟蹤,速度浪費(fèi)比較小,動態(tài)聯(lián)編可跟蹤對象,靈活性強(qiáng),速度浪費(fèi)嚴(yán)重
- 在編譯時就確定好了的叫編譯時聯(lián)編或叫靜態(tài)聯(lián)編
- 不使用virtual的情況下C++對重載的函數(shù)使用靜態(tài)聯(lián)編-調(diào)用函數(shù)和被調(diào)函數(shù)之間的關(guān)系在編譯時就確定好
-
使用virtual后,對重載函數(shù)使用動態(tài)聯(lián)編-跟蹤對象,根據(jù)實(shí)際的對象聯(lián)結(jié)對應(yīng)的函數(shù)
Q:這里講的動態(tài)/靜態(tài)聯(lián)編是什么呢?
A:這里講的是函數(shù)聯(lián)編,將調(diào)用函數(shù)連接上正確的被調(diào)函數(shù)稱函數(shù)聯(lián)編,簡稱聯(lián)編
最準(zhǔn)確的叫法,靜態(tài)聯(lián)編-調(diào)用函數(shù)和被調(diào)函數(shù)的關(guān)系是確定不變的;動態(tài)聯(lián)編(virtual修飾)-調(diào)用函數(shù)和被調(diào)函數(shù)的關(guān)系要跟蹤對象來聯(lián)結(jié)對應(yīng)的函數(shù)
注意:
-
使用virtual后,對重載函數(shù)使用動態(tài)聯(lián)編-跟蹤對象,根據(jù)實(shí)際的對象聯(lián)結(jié)對應(yīng)的函數(shù)
1. 析構(gòu)函數(shù)不允許有參數(shù),即不能實(shí)現(xiàn)重載,一個類只能有一個虛析構(gòu)函數(shù)
2. 基類的析構(gòu)函數(shù)必須聲明為虛函數(shù)(派生類部分才會被析構(gòu)),派生類的析構(gòu)函數(shù)無論說明與否,都自然成為虛函數(shù)
3. 虛構(gòu)造函數(shù)不存在
虛函數(shù)表參考:
https://leehao.me/C-%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8%E5%89%96%E6%9E%90/
至此,牢記一個概念:實(shí)際上所有的函數(shù)都存放在單獨(dú)的一個代碼區(qū)路媚,而函數(shù)對象里面只有成員變量齐饮。
留個問號,何為隱藏/覆蓋?
第14章 數(shù)組
數(shù)組是一組相關(guān)的內(nèi)存位置,他們具有相同的名稱和類型
數(shù)組特征三點(diǎn):類型,數(shù)組名,下標(biāo)
算法:
- 斐波那契數(shù)列,一對兔子,出生后第一個月成年,第二個月有生殖能力,有生殖能力的兔子每個月都生一對兔子,假設(shè)生出的兔子無死亡,那么一年后有兔子好多對?
- 排序問題,實(shí)際就是雙層的遍歷
輸出數(shù)組名就是內(nèi)存地址
int a[4] = {1, 2, 3, 4};
cout << a << endl;
//0013ff70,實(shí)際即 &a[0]
聲明數(shù)組時編譯器會自動生成指向該數(shù)組的指針,該指針通常保存數(shù)組第一個元素的內(nèi)存地址,數(shù)組不能按值傳遞給函數(shù),但卻能將內(nèi)存地址傳遞給數(shù)(C++規(guī)定數(shù)組只能有一個原本)
函數(shù)接收數(shù)組的形參聲明:
//1
void func(int a[])
//2
void func(int a[30])
//3
void func(int* a)
- 在有序數(shù)列中按二分法查找目標(biāo)
二分法缺點(diǎn):1. 數(shù)組中有兩個或兩個以上相同的元素,二分算法無法確定返回哪個值;2. 二分算法要求數(shù)組有序
將對象數(shù)組放在堆中
A* p_a = new A[10];
delete []p_a;
二維數(shù)組
a[rows][cols]
a[2][4] = {{1,2,3,4},{5,6,7,8}};
字符串?dāng)?shù)組
char ch[] = "hello world";
char ch1[20] = {"hello world"};
cout << ch ; //能將字符串完整輸出
字符串輸入,引入下面問題
char a[20];
cin >> a;
//并不會完整接收"hello world",因?yàn)閏in>>遇到空格停止向緩沖區(qū)寫入
解決辦法:
gets(a);
在上例中輸入字符串超過定義的空間會數(shù)組越界,解決辦法:
//手工限制輸入的字符個數(shù)
cin.get(a, 20);
目標(biāo)數(shù)組必須足夠大,容得下經(jīng)過copy或strcat后的字符串
字符串拼接
//1.strcat(目標(biāo),源)
char a = {'"my name is "};
char b = "jack";
strcat(a, b);
//2.copy(目標(biāo),源)
strcpy(a, b);
//3.小寫字母轉(zhuǎn)大寫
strupr(a);
//4.大寫轉(zhuǎn)小寫
strlwr(a);
//5.字符串長度
strlen(a);
注意:二維數(shù)組傳指針到函數(shù)里,涉及到的重要操作
//這種方式取得二級指針
for (int i = 0; i < 5; ++i)
p_array[i] = &maritx[i][0];
int** pp_array = &p_array[0];
int* p_maritx = &maritx[0][0];
printf("num[x]: %d \n", p_maritx[24]);
//下面是錯誤操作
int** pp_maritx = &p_maritx;
printf("num[x][x]: %d \n", pp_maritx[3][3]); //棧溢出
字面值常量不屬于可修改的內(nèi)存區(qū),其實(shí)是符號常量區(qū)
#include <iostream>
using std::cout;
int main(int argc, char** argv)
{
/*char* p = "我是字面值常量";
char* p1 = "我是字面值常量";*/
char* p = "hello word";
char* p1 = "hello word";
cout << "&p: " << &p << "\n";
cout << "&p1: "<< &p1 << "\n"; //兩者地址不同
//*p = "哈哈,已改變"; //不能修改,其實(shí)這塊"內(nèi)存"是特殊的內(nèi)存,不能再寫了,符號常量區(qū)
std::cin.get();
return 0;
}
第15章 動態(tài)鏈表
c語言形式申請動態(tài)內(nèi)存
void* p = malloc(sizeof(struct book));
free(p);
句柄本質(zhì)是什么?是二級指針.為什么一級指針就能索引的內(nèi)存還需要搞成二級指針來索引?
因?yàn)橄到y(tǒng)存在虛擬內(nèi)存(磁盤上某部分swap),內(nèi)存上數(shù)據(jù)可能需要經(jīng)常遷移往返虛擬內(nèi)存,這意味著內(nèi)存地址在經(jīng)常變化,win為解決這個問題,專門騰出來一部分內(nèi)存空間來放內(nèi)存變化的地址,所以,二級指針相對固定,一級指針可能經(jīng)常變化
第16章 多態(tài)性
dynamic_cast基類指針轉(zhuǎn)派生類指針,有風(fēng)險,經(jīng)他轉(zhuǎn)換的指針有風(fēng)險,轉(zhuǎn)換后的基類指針只能訪問派生類部分
數(shù)組也可認(rèn)為是鏈表
父類,母類繼承自共同的基類-人類,子類繼承自父.母類.父母類不說明成虛繼承的話,子類就會執(zhí)行人類的構(gòu)造函數(shù)兩次,析構(gòu)函數(shù)兩次,即有人類部分兩次
很多時候,基類的虛函數(shù)不會實(shí)例化,只是作為向下傳遞的接口,派生類也不一定要實(shí)例化虛函數(shù),有時只是作為向下傳遞的接口
類的靜態(tài)成員函數(shù)不能訪問類的數(shù)據(jù)成員(沒有this指針),只能訪問靜態(tài)變量,靜態(tài)成員變量必須初始化
靜態(tài)成員函數(shù)不能說明成虛函數(shù)
成員函數(shù)指針聲明
float (A:: *pf)(int, int);
//甚至可以說明成抽象類的函數(shù)指針
float (A:: *pf)(int, int) = 0;
cin.get()的結(jié)束標(biāo)志'\n'換行符,會自動為輸入字符串添加'\0'結(jié)束符
第17章 類的特殊成員
第18章 字符串
char型字符數(shù)組
char[5] = {'h', 'e', 'l', 'l', 'o'};
char型字符串
char[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
strlen() //返回sizeof()-1,即不包括結(jié)束符
有了C++就有了類,就有了C++風(fēng)格的字符串,即string類型的字符串
strcmp(); //比較兩字符串,相等返回0
strcpy(dst, src);
operator=(); //直接調(diào)用賦值運(yùn)算符
dst.assign(src, 3, 1) //拷貝源端任意區(qū)間字符串覆蓋dst端src索引為3開始的1個字符串拷貝到src
char dts[] = {};
char src[] = {};
strcat(dts, src); //字符串拼接,char[]型字符串必須保證dts有足夠的空間
dts是string型字符串就沒此問題
//亦可直接調(diào)用
operator+()函數(shù)
//strlen()是C語言函數(shù)不支持對象,所以對象使用它
strlen(str.c_str()); //輸出不包含結(jié)束符的字符個數(shù)(包括可見字符,除結(jié)束符外的控制符)
str.leng();
str.size();
//兩者效果相同,輸出字符串個數(shù),不包括末尾結(jié)束符
//約定:字符串元素個數(shù)即完整包括結(jié)束符的個數(shù)
//字符串的部分合并
strncat(dts, src, n); //src的前n字符拼接到dts尾
字符串部分拼接
//append()第2,3參數(shù)使用
dst.append(src, 2, 3); //src的索引為2,連續(xù)3個字符串拼接到dst尾
//C風(fēng)格的將src中前n字符覆蓋dst
char dst[] = {};
char src[] = {};
strncpy(dst, src, n);
//字符串交換奇偶字符
swab(src, dst, strlen(src));
//string非成員函數(shù)交換字符串
swap(a,b)
字符串無論string抑或char型字符數(shù)組,他們的重要標(biāo)志是有結(jié)束符'\0'
同樣是字符串,但本質(zhì)不一樣, 一個是字符數(shù)組,另一個是對象
string str = "hello";
char* p_ch = str; //錯誤
char* p_ch1 = "hello"; //正確
鮮有將指針作為函數(shù)返回值的
分析:
- 1 不可能是在函數(shù)內(nèi)的棧中對象的地址,函數(shù)結(jié)束時,局部變量被銷毀,自然返回局部變量地址是無意義的
- 2 若是堆中的動態(tài)內(nèi)存,又破壞了在哪創(chuàng)建在哪銷毀的規(guī)則,解決函數(shù)域內(nèi)創(chuàng)建,要傳遞到函數(shù)外的方法是動態(tài)內(nèi)存按智能指針返回
用命令行將輸出重定位到文件a.exe >> out.txt調(diào)試時有用
重載<<運(yùn)算符,將iostream對象引用和自定義的對象引用當(dāng)作參數(shù)傳遞進(jìn)函數(shù),可以將輸出cout或cin重定向
//p405第一次出現(xiàn)友元的說明
ostream& operator<< (ostream& in, A& a) //聲明
//修飾成友元亦可,將該類的成員暴露給ostream對象
friend ostream& operator<< (ostream& in, A& a)
p398 結(jié)構(gòu)體的方式計(jì)算兩天的時間總和
第19章 代碼重用
p449
const 對象只能調(diào)用const函數(shù)
例如
const String a;
String operator+(const String&) const;
//a可以調(diào)用上面函數(shù),因?yàn)樽詈笠粋€const修飾函數(shù)體可以被const對象調(diào)用的函數(shù)
包含對象,則會先調(diào)用數(shù)據(jù)成員的構(gòu)造函數(shù),構(gòu)造包含的對象,析構(gòu)時會先調(diào)用外層的析構(gòu)函數(shù),然后析構(gòu)被包含的對象
兩個case都落到一種情況
//p466
case same:
case larger:
{}
p466
對insert()來說只可能返回兩種指針:
1:指向新節(jié)點(diǎn)的指針
p462 17行
頭節(jié)點(diǎn)接收前插法創(chuàng)建的新節(jié)點(diǎn)的指針
p466 13行
遞歸調(diào)用,下一節(jié)點(diǎn)所創(chuàng)建的新節(jié)點(diǎn)2.this指針
遞歸調(diào)用時同樣在p462 17行及p466 13行接收
這種情況就是,遞歸的不創(chuàng)建新節(jié)點(diǎn),保持原來指向的下一節(jié)點(diǎn)不變
注意,這種遞歸思想學(xué)習(xí)下
p496介紹繼承的幾種分類區(qū)別
第20章 友元類與嵌套類
根據(jù)原值將bool值反轉(zhuǎn),三目運(yùn)算符
t.on_off = (t.on_off == TV_ON)? TV_OFF : TV_ON;
第21章 流
iostream輸入輸出流將輸入或輸出看作逐個字節(jié)的流,輸入時輸入對象將逐個字節(jié)注入到變量或內(nèi)存中,輸出時流對象將逐個字節(jié)輸出到屏幕或文件中
第22章 命名空間
命名空間主要解決命名沖突即重名問題
可以多次的,重復(fù)的,在不同文件的,創(chuàng)建同名的命名空間
盡量在函數(shù)聲明處聲明命名空間,不要在函數(shù)定義處聲明命名空間,目的:1.有利于命名空間的整潔※;2.有利于將命名空間存放在頭文件中,函數(shù)定義部分放在實(shí)現(xiàn)文件中
命名空間也可以嵌套,使用的時候就要使用多重::來釋放命名空間
using關(guān)鍵字能將命名空間從該using聲明處釋放至該作用域結(jié)束
作用域中定義的同名變量會覆蓋命名空間中的變量;類似小域(局部域)變量覆蓋大域(全局域變量)
為命名空間取別名
namespace long_name
{}
namespace short_name = long_name;
//創(chuàng)建的別名不能與其他的命名空間名字重復(fù)
未命名的命名空間
namespace
{}
//命名空間都要顯式地聲明,但名字可以沒有
未命名的命名空間,編譯器會為每個命名空間分配名字,每個文件中名字都不一樣.它有這么兩個特點(diǎn):1.該文件中訪問該未命名的命名空間中對象會自動添加命名空間名來訪問,即直接訪問命名空間中的變量;2.其他文件不能訪問該命名空間中的變量. 基于此,推薦之前用的文件域內(nèi)的static改為namespace{} (未命名命名空間中的所有成員在其他文件中不可見)
1.cpp 2.cpp中分別定義變量int x = 10;會產(chǎn)生變量重定義錯誤
extern與static恰恰相反,static將變量限制在所在域內(nèi)(文件級或某{}內(nèi));而extern是聲明在外部文件定義的對象;static是內(nèi)部鏈接,extern是外部鏈接,未命名的命名空間也是外部鏈接
用namespace而不用static的重要意義在"假如將全局對象和函數(shù)放心地扔進(jìn)匿名空間,并將它地址作為模板參數(shù),那么就要求這個匿名空間必須是外部鏈接,否則模板就沒有多大的意義.由于static是內(nèi)部鏈接,僅在當(dāng)前文件可見,其他文件無法對其進(jìn)行引用,因此,無法將模板實(shí)例化"
第23章 模板
template<typename T>
T max(T a, T b)
{
return(a>b)?a:b;
}
要求:1.元素可排序;2.重載了operator>()運(yùn)算符;對處理數(shù)據(jù)有要求的函數(shù)模板稱為約束函數(shù)模板
p610具體化函數(shù)模板解決重載
template<> void Swap<people>(people& p, people& p1);
//告訴編譯器放棄函數(shù)模板,使用具體化了(首先必須是具體化了參數(shù)類型)的模板,這么做毫無意義,實(shí)際就在這步就會生成函數(shù)實(shí)體,等價于
void Swap(people& p, people& p1)
模板定義在編譯前不會生成函數(shù)實(shí)體,模板只是生成函數(shù)實(shí)體的方案
類型推導(dǎo)
//兩個都實(shí)現(xiàn)了按引用傳入對象的目的
#include <iostream>
template<typename T>
void fun(T _in)
{
_in = 'W';
}
template<typename T>
void fun1(T& _in)
{
_in = 'J';
}
int main(int argc, char* argv[])
{
char in = 'A';
//fun
fun<char&>(in);
std::cout << "in: " << in << std::endl; //W
//fun1
fun1(in);
std::cout << "in: " << in << std::endl; //J
system("pause");
return 0;
}
繼承和模板都可以派生出一個類系,實(shí)現(xiàn)代碼重用的目的,但兩者又有本質(zhì)區(qū)別
模板生成的多個新類.實(shí)現(xiàn)的是對多個數(shù)據(jù)類型的重載,它們處理數(shù)據(jù)的方法是相同的.但繼承方式派生的類,數(shù)據(jù)有增加的可能,而且對數(shù)據(jù)的操作方式也可能會有變化,比如說派生類會提供更多的方法對數(shù)據(jù)進(jìn)行操作,或改變原有方法,使得數(shù)據(jù)的操作更加簡單和快捷.而且這些類之間也存在兄弟,父子之間的關(guān)系
p637 23.14節(jié) 將模板用作參數(shù),這個看代碼好理解
#include <iostream>
#include <string>
using namespace std;
template<class T>
class human
{
public:
human(){}
T GetAge(){return age;}
T GetStr(){return name;}
void SetAge(T &a){age=a;}
void SetStr(T &s){name=s;}
private:
T name;
T age;
};
template<template<class T>class T1>
class people
{
public:
people(){}
int GetAge(){return s1.GetAge();}
void SetAge(int &a){s1.SetAge(a);}
string GetStr(){return s2.GetStr();}
void SetStr(string &s){s2.SetStr(s);}
private:
T1<int>s1;
T1<string>s2;
};
int main()
{
people<human>Jack;
int i=8;
string str="hello";
Jack.SetAge(i);
cout<<Jack.GetAge()<<endl;
Jack.SetStr(str);
cout<<Jack.GetStr()<<endl;
system("pause");
return 0;
}
模板類可以聲明友元,模板類的友元分三種情況:
- 1.非模板友元類或友元函數(shù)
- 2.通用模板類或友元函數(shù)
- 3.特定類的模板友元類或友元函數(shù)
模板類外部聲明(模板友元函數(shù)不搞成內(nèi)嵌函數(shù)),特點(diǎn)模板友元函數(shù)分3個步驟:
- 1.模板類前面聲明模板友元函數(shù)
- 2.模板類中具體化模板友元函數(shù)
- 3.為特定模板友元函數(shù)提供模板定義
23.16多余的臨時對象看代碼領(lǐng)悟
截止現(xiàn)在對前面章節(jié)小結(jié):
- 1.看代碼為主了,看書緩一下
主要代碼章節(jié):
-18章自己實(shí)現(xiàn)的String空了看下,主要是看關(guān)鍵函數(shù),比如賦值運(yùn)算符函數(shù),要深拷貝;要看如何保存基礎(chǔ)的數(shù)據(jù),比如數(shù)據(jù)成員是char* 還是char[]- 20章兩個示例工程:友元類,嵌套類
- 21章結(jié)合到書來看某些流對象的操作
- 23章目錄"用VS2005編譯的程序" 23.14-23.16節(jié)代碼研究清楚 23章后面就是stl的使用了
- 24章主要以看代碼為主,25章概念比較多,先看書吧
關(guān)聯(lián)容器是特殊的順序容器<<C++標(biāo)準(zhǔn)庫>>
- 順序容器-array或list
- 關(guān)聯(lián)容器-binary tree
- 無序容器-hash table
所有順序容器都有
emplace_back()
//因?yàn)橄蝾^或中間添加可能會移動整個array,比如vector,所以向頭添加可能會造成比較大的消耗,是種特殊操作
關(guān)聯(lián)容器的差別在于處理元素種類以及處理重復(fù)元素的方式
set可視作特殊的map即key = value
無序容器的優(yōu)點(diǎn)是,當(dāng)查找某特定值元素,其速度可能快過關(guān)聯(lián)容器
map以key為根據(jù)來排序
所有無序容器都提供若干可有可無的template,用來指明hash函數(shù)和等效比較式
map抑或unodered_map的下標(biāo)[]運(yùn)算符和array的不一樣,接收一個key,返回鍵對應(yīng)的mapped_value,若沒此鍵則建立一個新的元素
也可以用at()來訪問mapped_value,這樣對容器操作是安全的,若沒有這個鍵key_type,用at()訪問的話會拋出out_of_range的異常
推薦使用unodered_map除非想map是有序的
容器適配器提供一定限度的接口,應(yīng)付特殊需求,適配器都是根據(jù)基本容器實(shí)現(xiàn)
Priority queue,所謂優(yōu)先權(quán)是基于程序員提供的排序準(zhǔn)則而定;容器適配器是種特殊的容器
每種容器都將迭代器以嵌套的方式定義在class內(nèi)部,每種迭代器接口相同但是類型不同
如果使用非常量迭代器,并且元素類型也是非常量,則可以使用迭代器修改元素值
向set<,>傳入第二模板參數(shù),即可調(diào)對象,排序?qū)创艘?guī)則,例如
set<int, greater<int>> set_instance;
前向迭代器,迭代器只額能遞增:forward_list;unordered_set;unordered_multiset;unordered_map;unordered_multimap的迭代器都是前向迭代器
雙向迭代器,list;set;multiset;map;multimap
隨機(jī)訪問迭代器:提供了迭代器算術(shù)運(yùn)算的操作符:vector;array;string;deque
template <class T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
//隱式實(shí)現(xiàn)模板
int v1 = 1, v2 = 2;
int ret = compare(v1, v2);
19章19.7之前是一種思路,19.7是另一種思路,而19.8是19.7的進(jìn)階應(yīng)用
- 第24章 異常
注意,在構(gòu)造函數(shù)中拋異常,需要在拋之前先delete this
throw的是放在棧上的局部對象
為了從try塊中檢測出一個異常并跳轉(zhuǎn)到相應(yīng)的catch塊中,C++函數(shù)發(fā)出throw語句,throw語句的數(shù)據(jù)類型與某個catch塊所接受的參數(shù)相匹配,throw用來表示錯誤已經(jīng)發(fā)生.這樣控制語句就從throw語句跳轉(zhuǎn)到catch塊中,接著throw語句通過調(diào)用析構(gòu)函數(shù),刪除try塊中的定義的所有對象
#include <iostream>
using namespace std;
class wrong
{
public:
wrong() { cout << "wrong() run" << endl; i = 55; }
wrong(wrong&) { cout << "wrong() copy construct" << endl; }
wrong& operator=(wrong& _in) { cout << "wrong() assigment construct" << endl; return _in; }
int print() { return i; };
private:
int i;
};
void error()
{
cout<<"出錯\n";
throw wrong();
}
int main()
{
try
{
error();
}
catch (wrong& w) //傳遞的對象以引用形式接收
{
cout<<"該錯誤已經(jīng)解決\n";
cout << "傳遞的wrong對象中i是: " << w.print() << endl;
}
return 0;
}
異常對象是個棧中對象,即throw拋出的對象
自定義assert來處理調(diào)試信息
#define DEBUG
#include <iostream>
using namespace std;
#ifndef DEBUG
#define ASSERT(x)
#else
#define ASSERT(x)\
if (!(x))\
{\
cout<<"錯誤虏冻!ASSERT("<<#x<<")宏函數(shù)執(zhí)行檢測\n";\
cout<<"錯誤代碼出現(xiàn)在第"<<__LINE__<<"行\(zhòng)n";\
cout<<"出錯的文件在:"<<__FILE__<<"\n";\
}
#endif
int main()
{
int x=999;
cout<<"第一次執(zhí)行assert():\n";
ASSERT(x==999);
cout<<"第二次執(zhí)行assert():\n";
ASSERT(x!=999);
cout<<"程序結(jié)束.\n";
return 0;
}
注意,下面一行的#x會將輸入的宏表達(dá)式打印出來
cout<<"錯誤捅暴!ASSERT("<<#x<<")宏函數(shù)執(zhí)行檢測\n";\
調(diào)試級別修改,摘自原文
#include <iostream>
#include <string>
using namespace std;
#define DEBUG 4
#if DEBUG<2
#define ASSERT(x)
#else
#define ASSERT(x)\
if (!(x))\
{\
cout<<"錯誤!ASSERT("<<#x<<")宏函數(shù)執(zhí)行檢測\n";\
cout<<"錯誤代碼出現(xiàn)在第"<<__LINE__<<"行\(zhòng)n";\
cout<<"出錯的文件在:"<<__FILE__<<"\n";\
}
#endif
#if DEBUG<3
#define SHOW(x)
#else
#define SHOW(x)\
cout<<x<<endl;
#endif
#if DEBUG<4
#define PRINT(x)
#else
#define PRINT(x)\
cout<<#x<<endl;
#endif
class Circle
{
public:
double check()const
{
SHOW("進(jìn)行3級檢查");
PRINT("進(jìn)行4級檢查");
return radius;
}
void set(double x)
{
ASSERT(check());
radius=x;
ASSERT(check());
}
double Result()
{
return 3.14*radius*radius;
}
private:
double radius;
};
int main()
{
Circle one;
one.set(14);
cout<<"圓的面積為:"<<one.Result()<<endl;
one.set(0);
cout<<"圓的面積為:"<<one.Result()<<endl;
return 0;
}
try
{
dynamic_cast<派生類&>(基類對象);
}
catch (bad_cast)
{
}
//轉(zhuǎn)換不成功dynamic_cast則會返回NULL
//只有基類用到虛函數(shù)時才用到動態(tài)類型轉(zhuǎn)換,要結(jié)合到C++開啟RTTI
try
{
new A();
}
catch (bad_alloc)
{
}