/********************************
C++筆記
自己記錄的覺得關鍵 容易忘得東西
內容雜亂沒有次序不適合學習
參考內容來自《C++primer》第4版
《C++編程思想》第二版 第一卷第二卷
僅供參考
!!!!!!!!!!有錯誤的地方見諒可能抄錯了!!!!!!!!!!!!!
QQ710055071 十二年/DZF
********************************/
0001. int func2() 對於帶空參數表的函數:
C語言中表示“一個可帶任意參數(任意數目,任意類型)的函數”
C++不帶參數的函數
0002. <>和"" 用尖括號來指定文件時,預處理器是以特定的方式來尋找文件,一般是環境中或編譯器命令行指定的某種尋找路徑。
這種設置尋找路徑的機制隨機器、操作系統、C++實現的不同而不同,要視情況而定。
用雙引號時,預處理器以“由實現定義的方式”來尋找文件,它通常是從當前目錄開始尋找,如果沒找到,那麼include命令就按與尖括號同樣的方式重新開始尋找。
0003. break和continue
break 退出循環,不再執行循環中剩餘的語句
continue 停止執行當前的循環,返回到循環開始處開始新的一輪循環;
0004.強制轉換
1.static_cast包含的轉換類型包括典型的非強制變換、窄化變換,使用void*的強制變換、隱式類型變換和類層次的靜態定位
int i=0x7fff
long l;
float f;
l=static_cast<int>(i);
f=static_cast<int>(i);
const_cast 常量轉換非常量 volatile 轉換成非volatile
reinterpret_cast 重解釋轉換 使用之前必須轉換回去
dynamic_cast 用於類型安全的向下轉換
type(data);類型名(變量)//被稱爲僞構造函數
(type)date;//C語言強制類型轉換
0005. typedef 名命別名
0006. struct enum union
0007.複雜的聲明和定義
1).void *(*(*fp1)(int))[10];
fp1是一個指向函數的指針,該函數接受一個整型參數並返回一個指向含有10void指針數組的指針
2).float (*(*fp2)(int,int,float))(int);
fp2是一個指向函數的指針,該函數接受3個參數(int,int ,float),且返回一個指向函數的指針,該函數接受一個整型參數並返回一個float
3).typedef double (*(*(*fp3)())[10])();
相當於typedef double (*(*(*)())[10])() fp3;
fp3是一個指向函數的指針,該函數無參數,且返回一個指向含有10個指向函數指針數組的指針,這些函數不接受參數且返回double值
4).int (*(*fp4())[10])();
fp4是一個返回指針的函數,該指針指向含有10個函數指針的數組,這些函數返回整型值
5)int (*ff(int))(int*,int)
int board[8][8];//int 數組的數組
int **ptr;//指向int的指針的指針
int *risks[10]//具有10個元素的數組,每個元素是指向int的指針
int (*risks)[10]//一個指針,指向具有10個元素的int數組
int *oof[3][4]//一個 3X4的數組,每個元素是一個指向int的指針
int (*uuf)[3][4]//一個指針,指向3X4的int數組
int (*uof[3])[4]//一個具有3個元素的數組,每一個元素指向具有4個元素的int數組的指針
0008. delete []a;刪除指針數組
0009. #define和#undef
#define 定義宏
#undef 取消宏定義
0010.在使用默認參數時必須記住兩條規則:
第一,只有參數列表的最後部參數纔是可默認的,也就是說,不可以在一個默認參數後面又跟一個非默認的參數。
第二,一旦在一個函數調用中開始使用默認參數,那麼這個參數後面的所有參數都必須是默認的。
0011.ctrl+z 輸入文件結束符 Unix,Mac,OS-X通常用ctrl+d;
0012.左值和右值
左值:左值可以出現在賦值語句的左邊和右邊;
右值:右值只能出現在賦值的右邊,不能出現在賦值語句的左邊;
0013.複製初始化和直接初始化
複製初始化:複製初始化語法用等於號;
直接初始化:直接初始化語法用括號;
0014.namespace a=b;定義一個名字空間的別名
0015.未名命的名字空間
這個實際上就是用來取代全局範圍的靜態變量和函數;
0016.數組
在函數體外定義的內置數組,初始化爲0
在函數體內定義的內置數組,無初始化;
一個數組不能用另外一個數組初始化
也不能將一個數組賦值給另一個數組
size_t數組下標類型
0017.void*指針支持的操作
向函數傳遞void*指針或從函數返回void*指針
給另一個void*指針賦值
不允許使用void*指針操縱它所指向的對象
0018.指針和常量
const 限定符放在類型前面 也可以放在類型後面
*const 要將指針本身而不是被指的對象聲明爲常量
*const 也是一個類型 即常量指針類型
char *const cp;到char 的const指針
char const *pc;到const char 的指針
const char *pc1;到const char 的指針
typedef char* A;
const A b;A const b;char *const b;
int const * nx等價於const int * nx;
const int * nx; //左指針 const 在 * 號左邊, nx運算時可以放在=號左邊
int * const nx //右指針 const在 * 號右邊,nx在運算時放在=號右邊(不能放在左邊)
const int * const ccnx; // 獨立指針,禁止參與運算
int const * const ccnx;
int * px; 自由指針,允許指針的所有操作
const double pi=3.14;
double *ptr=π//error
const double *cptr=π//ok
把一個const 對象的地址賦值給一個普通的、非const對象的指針會導致編譯時錯誤
不能使用void*指針保存const對象的地址,而必須使用const void*類型保存const對象的地址;
允許把非const對象的地址賦給指向const對象的指針
0019可變的:按位const 和按邏輯const
如果想要建立一個const 成員函數,但仍然想在對象裏改變某些某些數據
這關係到按位const和按邏輯const的區別
按位const 意思是對象中的每個字節都是固定的,所以對象的每個位映像從不改變
按邏輯const意思是 雖然整個對象從概念上講是不變的,但是可以以成員單位改變
當編譯器被告知一個對象是const對象時,它將絕對包含對象按位的常量性
要實現按邏輯const屬性 ,有兩種由內部const 成員函數改變成員數據的方法
方法一 const成員函數改變成員變量
這種方法已經成爲過去,稱爲強制轉換常量性
const 成員函數 this 是const的
class Y{
int i;
public:
Y(){i=0};
void f()const;
};
void Y::f()const
{
//i++;error
((Y*)this)->i++;//ok
const_cast<Y*>(this)->i++;//ok
}
int main()
{
const Y yy;
yy.f();
return 0;
}
方法二
使用 mutable關鍵字 可以指定一個特定的數據成員
可以在一個const成員函數裏改變數據成員的值
0020 const對象、指向const對象的指針或者引用,只能調用const成員函數 非const對象可以調用任意成員
類的const 數據成員必須在構造函數的初始化列表裏初始化
static const 必須在定義它的地方初始化;
0021對於一個類型T的前綴++和後綴++,一般是這種原型:
T& operator++()
{
*this += 1;
return (*this);
}
T operator++(int)
{
T tmp = *this;
*this += 1;
return tmp;
}
0022
一:C語言中的內存機制
在C語言中,內存主要分爲如下5個存儲區:
(1)棧(Stack):位於函數內的局部變量(包括函數實參),由編譯器負責分配釋放,函數結束,棧變量失效。
(2)堆(Heap):由程序員用malloc/calloc/realloc分配,free釋放。如果程序員忘記free了,則會造成內存泄露,程序結束時該片內存會由OS回收。
(3)全局區/靜態區(Global Static Area): 全局變量和靜態變量存放區,程序一經編譯好,該區域便存在。並且在C語言中初始化的全局變量和靜態變量和未初始化的放在相鄰的兩個區域(在C++中,由於全局變量和靜態變量編譯器會給這些變量自動初始化賦值,所以沒有區分了)。由於全局變量一直佔據內存空間且不易維護,推薦少用。程序結束時釋放。
(4)C風格字符串常量存儲區: 專門存放字符串常量的地方,程序結束時釋放。
(5)程序代碼區:存放程序二進制代碼的區域。
二:C++中的內存機制
在C++語言中,與C類似,不過也有所不同,內存主要分爲如下5個存儲區:
(1)棧(Stack):位於函數內的局部變量(包括函數實參),由編譯器負責分配釋放,函數結束,棧變量失效。
(2)堆(Heap):這裏與C不同的是,該堆是由new申請的內存,由delete或delete[]負責釋放
(3)自由存儲區(Free Storage):由程序員用malloc/calloc/realloc分配,free釋放。如果程序員忘記free了,則會造成內存泄露,程序結束時該片內存會由OS回收。
(4)全局區/靜態區(Global Static Area): 全局變量和靜態變量存放區,程序一經編譯好,該區域便存在。在C++中,由於全局變量和靜態變量編譯器會給這些變量自動初始化賦值,所以沒有區分了初始化變量和未初始化變量了。由於全局變量一直佔據內存空間且不易維護,推薦少用。程序結束時釋放。
(5)常量存儲區: 這是一塊比較特殊的存儲區,專門存儲不能修改的常量(如果採用非正常手段更改當然也是可以的了)。
三:堆和棧的區別
3.1 棧
具體的講,現代計算機(馮諾依曼串行執行機制),都直接在代碼低層支持棧的數據結構。這體現在,有專門的寄存器指向棧所在的地址(SS,堆棧段寄存器,存放堆棧段地址);有專門的機器指令完成數據入棧出棧的操作(彙編中有PUSH和POP指令)。
這種機制的特點是效率高,但支持數據的數據有限,一般是整數、指針、浮點數等系統直接支持的數據類型,並不直接支持其他的數據結構(可以自定義棧結構支持多種數據類型)。因爲棧的這種特點,對棧的使用在程序中是非常頻繁的 。對子程序的調用就是直接利用棧完成的。機器的call指令裏隱含了把返回地址入棧,然後跳轉至子程序地址的操作,而子程序的ret指令則隱含從堆棧中彈出返回地址並跳轉之的操作。
C/C++中的函數自動變量就是直接使用棧的例子,這也就是爲什麼當函數返回時,該函數的自動變量自動失效的原因,因而要避免返回棧內存和棧引用,以免內存泄露。
3.2 堆
和棧不同的是,堆得數據結構並不是由系統(無論是機器硬件系統還是操作系統)支持的,而是由函數庫提供的。基本的malloc/calloc/realloc/free函數維護了一套內部的堆數據結構(在C++中則增加了new/delete維護)。
當程序用這些函數去獲得新的內存空間時,這套函數首先試圖從內部堆中尋找可用的內存空間(常見內存分配算法有:首次適應算法、循環首次適應算法、最佳適應算法和最差適應算法等)。如果沒有可用的內存空間,則試圖利用系統調用來動態增加程序數據段的內存大小,新分配得到的空間首先被組織進內部堆中去,然後再以適當的形式返回給調用者。當程序釋放分配的內存空間時,這片內存空間被返回到內部堆結構中,可能會被適當的處理(比如空閒空間合併成更大的空閒空間),以更適合下一次內存分配申請。 這套複雜的分配機制實際上相當於一個內存分配的緩衝池(Cache),使用這套機制有如下幾個原因:
(1)系統調用可能不支持任意大小的內存分配。有些系統的系統調用只支持固定大小及其倍數的內存請求(按頁分配);這樣的話對於大量的小內存分配來說會造成浪費。
(2)系統調用申請內存可能是代價昂貴的。 系統調用可能涉及到用戶態和核心態的轉換。
(3)沒有管理的內存分配在大量複雜內存的分配釋放操作下很容易造成內存碎片。
3.3 棧和堆的對比
從以上介紹中,它們有如下區別:
(1)棧是系統提供的功能,特點是快速高效,缺點是由限制,數據不靈活;而堆是函數庫提供的功能,特點是靈活方便,數據適應面廣,但是效率有一定降低。
(2)棧是系統數據結構,對於進程/線程是唯一的;堆是函數庫內部數據結構,不一定唯一。不同堆分配的內存無法互相操作。
(3)棧空間分靜態分配和動態分配,一般由編譯器完成靜態分配,自動釋放,棧的動態分配是不被鼓勵的;堆得分配總是動態的,雖然程序結束時所有的數據空間都會被釋放回系統,但是精確的申請內存/釋放內存匹配是良好程序的基本要素。
(4)碎片問題:對於堆來講,頻繁的new/delete等操作勢必會造成內存空間的不連續,從而造成大量的碎片,使程序的效率降低;對於棧來講,則不會存在這個問題,因爲棧是後進先出(LIFO)的隊列。
(5)生長方向:堆的生長方向是向上的,也就是向這內存地址增加的方向;對於棧來講,生長方向卻是向下的,是向着內存地址減少的方向增長。
(6)分配方式:堆都是動態分配的,沒有靜態分配的堆;棧有兩種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配則由alloca函數進行分配,但是棧的動態分配和堆不同,它的動態分配是由編譯器進行釋放,無需我們手工實現。
(7)分配效率:棧是機器系統提供的數據結構,計算機在底層提供支持,分配有專門的堆棧段寄存器,入棧出棧有專門的機器指令,這些都決定了棧的高效率執行。而堆是由C/C++函數庫提供的,機制比較複雜,有不同的分配算法,易產生內存碎片,需要對內存進行各種管理,效率比棧要低很多。
0023使用引用是有一定規則
1)當引用被創建時, 它必須初始化(指針則可以在任何時候被初始化)
2)一旦一個引用被初始化爲一個對象,它就不能改變位另一個對象的引用
指針則可以在任何時候指向另一個對象
3)不可能有NULL引用,必須確保引用是和一塊何方的存儲單元關聯
const int &a=1;//const 會爲1分配內存 這是創建一個臨時的整形變量並被A引用
int &A = 10; //Error 肯定不正確,類型不匹配,不能保證常量10不被修改。可以修改爲下面
int b = 10;
int &A = b;
const int &A = 10;//OK 把A定義成一個int的常引用,這時A的值不能再被修改,保證常量10不被修改。如下則提示錯誤
int b = 20;
const int &A = 10;
A = b;
0024 string 對象的定義和初始化
string s1;//默認構造函數,s1爲空;
string s2(s1);//將s2初始化爲s1的一個副本
string s3("111111");//將s3初始化爲一個字符串常量的副本
string s4(n,'c');//將s4初始化爲n個‘c’
string s;
cin>>s;
//讀取並忽略開頭的所有空白字符(如空格 換行符 製表符)
//讀取字符直至再次遇到空白字符,讀取結束;
getline(cin,line)//不忽略行開頭的換行符 遇到換行符停止讀入並返回 讀取不包括換行符
s.empty();如果s爲空串則返回真 否則返回假
s.size();返回s中字符的個數
s[n],返回位置爲n的字符
string 的size操作返回類型必須爲string::size_type類型
下標類型也是string::size_type類型
空白字符— 空格 ,製表符,垂直製表符,回車符,換行符,進紙符
標 點 符— 除了數字、字母、或(可打印的)空白符以外的其他可打印的字符
isalnum(c);//如果c是字母或數字,則返回真
isalpha(c);//如果c是字母,則返回真
iscntrl(c);//如果c是控制字符,則返回真 ???
isdigit(c);//如果c是數字,則返回真
isgraph(c);//如果c不是空格,但可以打印,則返回真???
islower(c);//如果c是小寫字母,則返回真
isprint(c);//如果c是可打印字符,則返回真
ispunct(c);//如果c是標點符號,則返回真
isspace(c);//如果c是空白字符,則返回真
isupper(c);//如果c是大寫字母,則返回真
isxdigit(c);//如果c是十六進制數,則返回真
tolower(c);//如果c是大寫則返回小寫 否則返回c
toupper(c);//如果c是小寫則返回大寫 否則返回c
strlen(s) //返回s的長度,不包括字符串結束符null
strcmp(s1,s2)//比較字符串是否相同 相等返回0,s1>s2返回整數,否則返回負數
strcat(s1,s2)//將字符串s2鏈接到s1後返回s1
strcpy(s1,s2)//將s2複製給s1,並返回s1
strncat(s1,s2,n)//將s2的前n個字符鏈接到s1後面,並返回s1
strncpy(s1,s2,n)//將s 2的前n個字符複製給s1,並返回s1
s1.c_str()//轉換成C風格的字符串
0025
iterator
const_iterator//不能改變元素
difference_type
size_t數組下標類型
ptrdiff_t兩個指針減法操作的結果類型
0026 通過引用傳遞數組
void abc(int (&a)[10])
嚴格檢查形參和實參大小是否匹配
0027
%c 字符
%d 帶符號整數
%i 帶符號整數
%e 科學計數法, 使用小寫"e"
%E 科學計數法, 使用大寫"E"
%f 浮點數
%g 使用%e或%f中較短的一個
%G 使用%E或%f中較短的一個
%o 八進制
%s 一串字符
%u 無符號整數
%x 無符號十六進制數, 用小寫字母
%X 無符號十六進制數, 用大寫字母
%p 一個指針
%n 參數應該是一個指向一個整數的指針
指向的是字符數放置的位置
%% 一個'%'符號
這個是scanf
%c 一個單一的字符
%d 一個十進制整數
%i 一個整數
%e, %f, %g 一個浮點數
%o 一個八進制數
%s 一個字符串
%x 一個十六進制數
%p 一個指針
%n 一個等於讀取字符數量的整數
%u 一個無符號整數
%[] 一個字符集
%% 一個精度符號
0028順序容器
順序容器
vector 支持快速訪問
list 支持快速插入/刪除
deque 雙端隊列
順序容器適配器
stack 後進先出
queue 先進先出
priority_queue 有優先級管理的隊列
C<T> c;//定義一個C類型的容器,容器的元素類型爲T
C<T> c(c2);//創建容器c2的副本c,c和c2必須具有相同的容器類型,並存放相同類型的元素
C<T> c(b,e);//創建c, 其元素是迭代器b和e標示範圍內元素的副本//應該不包括e
C<T> c(n,t);//用n個值爲t的元素創建容器c,只適用於順序容器
C<T> c(n);//創建有n個值初始化元素的容器c;
容器內元素
元素類型必須支持賦值運算
元素類型的對象必須可以複製
不支持引用元素
//表9-5
size_type 無符號整型,足以存儲此容器類型的最大可能容器的長度
iterator 此容器類型的迭代器類型
const_iterator 元素的只讀迭代器類型
reverse_iterator 按逆序尋址元素的迭代器
const_reverse_iterator 元素的只讀逆序迭代器
difference_type 足夠存儲兩個迭代器差值的有符號整型,可爲負數
//??????下面不理解 ?????????????????????????
vaule_type 元素類型
reference 元素的左值類型,是vaule_type &的同義詞
const_reference 元素的常量左值類型,等效於const value_type&
//表9-6
c.begin() 返回一個迭代器 指向第一個元素
c.end() 指向最後一個元素下一個位置
c.rbegin() 指向最後一個元素
c.rend() 指向第一個元素的前一個位置
//表9-7
c.push_back(t) 尾部添加元素
c.push_front(t)頭部添加元素 list deque
c.insert(p,t) 在p前面添加t元素
c.insert(p,n,t)在p前面添加n個t元素
c.insert(p,b,e)在p前面添加 b到e的元素
//表9-8
c.size() 返回C::size_type類型 返回元素的個數
c.max_size();返回類型同上 ,返回可容納的最多元素個數
c.empty()返回容器大小是否爲0的bool值
c.resize(n)調整容器大小 多餘刪除 否則才用初始化新元素
c.resize(n,t)調整大小 所有新元素爲t
//表9-9
c.back()返回容器最後一個元素的引用
c.front()返回容器第一個元素的引用
c[n]適用於vector deque
c.at[n]返回下標爲n的引用適用於vector deque
//表9-10
c.erase(p)刪除p指向的元素返回p後面的元素的迭代器
c.erase(b,e)刪除b到e的元素,返回e後面的迭代器
c.clear()刪除c的所有元素返回void
c.pop_back()刪除最後元素 返回void
c.pop_front()刪除第一個元素,返回void
//表9-11
c1=c2;刪除c1的所有元素,然後將c2的元素複製給c1
c1.swap(c2)交換內容 調用該函數之後c1存放的是c2原來的元素c2中存放的是c1原來的元素 比執行復制操作快
c.assign(b,e) 重新設置c的元素 將迭代器b到e的範圍所有元素複製到c中
//????????
capacity
reserve
0029 string
string s;定義一個新的空string的對象
string s(sp);定一個新的string對象,用sp所指向的C風格字符串初始化//以空字符null結束 \0
string s1(s2);初始化爲s2的副本
getline(is,s);讀取一行字符
s1+s2;把s1、s2串起來產生一個新的string
s1+=s2;把s2拼接在s1後面
string 支持的操作
表9-5列出的typedef 包括迭代器類型
表9-2列出的容器構造函數,但包括只需要一個長度參數的構造函數
表9-7列出的vector容器所提供的添加元素的操作。注意無論vector和string都不支持push_front
表9-8列出的長度操作
表9-9列出的下標和at操作;但string類型不提供該表列出的back或front操作
表9-6列出的begin和end操作
表9-10列出的erase和clear操作;但string類型不提供pop_back或pop_front操作
表9-11列出的賦值操作
string的字符也是連續存儲的
string s1;
string s2(5,'a');//s2="aaaaa"
string s3(s2);
string s4(s3.begin(),s3.begin()+s3.size()/2);//s4=="aa";
在c++中string類的構造函數有六種方式
分別是:
1.string(const char * s)
說明:將string對象初始化爲s指向NBTS。NBTS爲null-byte-temnated string的縮寫,表示以空字符結束的字符串------傳統的C字符串。
2.string(size_type n,char c)
說明:創建一個包含n個元素的string對象,其中每個元素都被初始化爲字符c
3.string(const string & str,string size_type n = npos)
說明:將string對象初始化爲對象str中從位置pos開始到結尾的字符,或從位置pos開始的n個字符
4.string()
說明:創建一個的string對象,長度爲0
5.string(const char * s, size_type n)
說明:將string對象初始化爲s指向的NBTS中的前n字符,即使超過了NBTS端
6.template<clas Iter> string(Iter begin,Iter end)
說明:將string對象初始化爲區間[begin,end]內的字符,其中begin和end的行爲就像指針,用於指定位置,範圍包括begin在內,但不包括end
//和容器共有的操作
s.insert(p,t)
s.insert(p,n,t)
s.insert(p,b,e)
s.assign(b,e)
s.assign(n,t)
s.erase(p)
s.erase(b,e)
//string 類型特有的版本
s.insert(pos,n,c)
s.insert(pos,s2)
s.insert(pos,s2,pos2,len)
s.insert(pos,cp,len)
s.insert(pos,cp)
s.assign(s2)
s.assign(s2,pos2,len)
s.assign(cp,len)
s.assign(cp)
s.assign(pos,len)
//容器不支持的操作
s.substr(pos,n);
s.substr(pos);
s.substr();
s.append(args);
s.replace(pos,len,args);
s.replace(b,e,args);
append和replace支持
s2
s2,pos2,len2
cp
cp,len2
n,c
b2,e2
s.find(args)
s.rfind(args)
s.find_first_of(args)
s.find_last_of(args)
s.find_first_not_of(args)
s.find_last_not_of(args)
find支持的操作
c,pos
s2,pos
cp,pos
cp,pos,n
find_last()
find_first()
s.compare(s2)
以上具體用法查找C++primer第9章
0030 類
在普通非const成員函數中,this的類型是一個指向類類型的const指針
可以改變this所指向的值,但不能改變this所保存的地址
在const成員函數中,this的類型是一個指向const類類型對象的const指針,
既不能改變this所指向的對象也不能改變this所保存的地址
/////?????????????????????????
不能從const成員函數返回指向類對象的普通引用。const成員函數只能返回*this作爲一個const引用
/////////////////////
隱式類類型轉換
抑制由構造函數定義的隱式轉換
explicit
只能用於構造函數
static類成員
每個static數據成員是與類關聯的對象,並不是與該類的對象相關聯
static成員函數沒有this形參,它可以直接訪問所屬類的static成員,但不能直接使用非static成員
使用類的static成員的優點
1)static成員的名字是在類的作用域中,因此可以避免與其他類的成員或全局對象名字衝突
2)可以實施封裝。static成員可以是私有的,而全局對象不可以
3)通過閱讀程序容易看出static成員是與特定類關聯的,這種可見性可清晰地顯示程序員的意圖
static成員函數 不能被聲明爲const 也不能被聲明爲虛函數
const static數據成員在類的定義體中初始化時,該數據成員仍必須在類的定義體之外定義
因爲類的靜態數據成員有着單一的存儲空間而不管產生多少個對象,所以存儲空間必須在一個單獨的地方定義
編譯器不會分配存儲空間。如果一個靜態數據成員被聲明但沒有定義時,鏈接器會報告一個錯誤
定義必須出現在類的外部(不允許內聯),而且只能定義一次,因此它通常放在一個類的實現文件中
class A{
static int i;//這是聲明
public:
//...
};
int A::i=1;//這纔是定義
在這裏,類和作用域運算符用於指定了A::i.
有些人對A::i是私有的這點感到疑惑不解,可是在這裏似乎在公開地直接對它處理
這不是破壞了類結構的保護性嗎? 有兩個原因可以保證它絕對的安全
第一、這些變量的初始化唯一合法的地方是在定義時。事實上,如果靜態數據成員
是一個帶構造函數的對象時,可以調用構造函數來代替"="操作符;
第二、一旦這些數據被定義了,最終的用戶就不能再定義它,否則連接器會報告錯誤
而且這個類的創建者被迫產生這個定義,否則這些代碼在測試時無法連接
這就保證了定義只纔出現一次並且它是由類的製造者來控制
其它 數組等見編程思想P239頁
0031標準庫bitset類型
#include<bitset>
using std::bitset
bitset<n> b;b有n位,每位都爲0
bitset<n> b(u);b是unsigned long 型u的一個副本
bitset<n> b(s);b是string對象s中含有的位串的副本
bitset<n> b(s,pos,n);b是s中從位置pos開始的n個位的副本
其他具體用法見C++primer第3章
0032操作符優先級別見C++primer147頁
0033刷新緩衝區P248頁
0034 定義智能指針類//沒懂 421頁
0035標準IO庫//未看
0036泛型算法
find(s.begin(),s.end(),sum)使用迭代器 或者指針標記範圍查找元素
#include<algorithm>泛型算法頭文件
#include<numeric>算術算法 頭文件
accumulate(s.begin(),s.end(),sum)累加算法 sum是初始值
find_first_of()
fill() 寫入 操作已經存在的元素
fill_n()
back_inserter插入迭代器
copy()
replace()
replace_copy()
unique()
stable_sort()
sort()
count_if()
0037插入迭代器 iostream 迭代器 反向迭代器 const迭代器
back_inserter
front_inserter
inserter
0038操作符重載
操作符重載[]
C++編程思想 第12章 一元 二元操作符例子
0039 函數對象 P449///?????????????????????????
0040 指針和引用
C++中的引用與指針的區別
指向不同類型的指針的區別在於指針類型可以知道編譯器解釋某個特定地址(指針指向的地址)中的內存內容及大小,而void*指針則只表示一個內存地址,編譯器不能通過該指針所指向對象的類型和大小,因此想要通過void*指針操作對象必須進行類型轉化。
★ 相同點:
1. 都是地址的概念;
指針指向一塊內存,它的內容是所指內存的地址;
引用是某塊內存的別名。
★ 區別:
1. 指針是一個實體,而引用僅是個別名;
2. 引用使用時無需解引用(*),指針需要解引用;
3. 引用只能在定義時被初始化一次,之後不可變;指針可變;
引用“從一而終” ^_^
4. 引用沒有 const,指針有 const,const 的指針不可變;
5. 引用不能爲空,指針可以爲空;
6. “sizeof 引用”得到的是所指向的變量(對象)的大小,而“sizeof 指針”得到的是指針本身(所指向的變量或對象的地址)的大小;
typeid(T) == typeid(T&) 恆爲真,sizeof(T) == sizeof(T&) 恆爲真,但是當引用作爲類成員名稱時,其佔用空間與指針相同4個字節(沒找到標準的規定)。
7. 指針和引用的自增(++)運算意義不一樣;
1. 引用在語言內部用指針實現(如何實現?)。
2. 對一般應用而言,把引用理解爲指針,不會犯嚴重語義錯誤。引用是操作受限了的指針(僅容許取內容操作)。
引用是C++中的概念,初學者容易把引用和指針混淆一起。一下程序中,n 是m 的一個引用(reference),m 是被引用物(referent)。
int m;
int &n = m;
n 相當於m 的別名(綽號),對n 的任何操作就是對m 的操作。例如有人名叫王小毛,他的綽號是“三毛”。說“三毛”怎麼怎麼的,其實就是對王小毛說三道四。所以n 既不是m 的拷貝,也不是指向m 的指針,其實n 就是m 它自己。
引用的一些規則如下:
(1)引用被創建的同時必須被初始化(指針則可以在任何時候被初始化)。
(2)不能有NULL 引用,引用必須與合法的存儲單元關聯(指針則可以是NULL)。
(3)一旦引用被初始化,就不能改變引用的關係(指針則可以隨時改變所指的對象)。
以下示例程序中,k 被初始化爲i 的引用。語句k = j 並不能將k 修改成爲j 的引用,只是把k 的值改變成爲6.由於k 是i 的引用,所以i 的值也變成了6.
int i = 5;
int j = 6;
int &k = i;
k = j; // k 和i 的值都變成了6;
上面的程序看起來象在玩文字遊戲,沒有體現出引用的價值。引用的主要功能是傳遞函數的參數和返回值。C++語言中,函數的參數和返回值的傳遞方式有三種:值傳遞、指針傳遞和引用傳遞。
1)以下是“值傳遞”的示例程序。由於Func1 函數體內的x 是外部變量n 的一份拷貝,改變x 的值不會影響n, 所以n 的值仍然是0.
void Func1(int x)
{
x = x + 10;
}
int n = 0;
Func1(n);
cout << “n = ” << n << endl;// n = 0
2)以下是“指針傳遞”的示例程序。由於Func2 函數體內的x 是指向外部變量n 的指針,改變該指針的內容將導致n 的值改變,所以n 的值成爲10.
void Func2(int *x)
{
(* x) = (* x) + 10;
}
⋯
int n = 0;
Func2(&n);
cout << “n = ” << n << endl; // n = 10
3)以下是“引用傳遞”的示例程序。由於Func3 函數體內的x 是外部變量n 的引用,x和n 是同一個東西,改變x 等於改變n,所以n 的值成爲10.
void Func3(int &x)
{
x = x + 10;
}
⋯
int n = 0;
Func3(n);
cout << “n = ” << n << endl; // n = 10
對比上述三個示例程序,會發現“引用傳遞”的性質象“指針傳遞”,而書寫方式象“值傳遞”。實際上“引用”可以做的任何事情“指針”也都能夠做,爲什麼還要“引用”這東西?
答案是“用適當的工具做恰如其分的工作”。
指針能夠毫無約束地操作內存中的如何東西,儘管指針功能強大,但是非常危險。
就象一把刀,它可以用來砍樹、裁紙、修指甲、理髮等等,誰敢這樣用?
如果的確只需要借用一下某個對象的“別名”,那麼就用“引用”,而不要用“指針”,以免發生意外。比如說,某人需要一份證明,本來在文件上蓋上公章的印子就行了,如果把取公章的鑰匙交給他,那麼他就獲得了不該有的權利。
——————————
摘自「高質量c++編程」
指針與引用,在More Effective C++ 的條款一有詳細講述,我給你轉過來
條款一:指針與引用的區別
指針與引用看上去完全不同(指針用操作符‘*’和‘->’,引用使用操作符‘。’),但是它們似乎有相同的功能。指針與引用都是讓你間接引用其他對象。你如何決定在什麼時候使用指針,在什麼時候使用引用呢?
首先,要認識到在任何情況下都不能用指向空值的引用。一個引用必須總是指向某些對象。因此如果你使用一個變量並讓它指向一個對象,但是該變量在某些時候也可能不指向任何對象,這時你應該把變量聲明爲指針,因爲這樣你可以賦空值給該變量。相反,如果變量肯定指向一個對象,例如你的設計不允許變量爲空,這時你就可以把變量聲明爲引用。
“但是,請等一下”,你懷疑地問,“這樣的代碼會產生什麼樣的後果?”
char *pc = 0; // 設置指針爲空值
char& rc = *pc;// 讓引用指向空值
這是非常有害的,毫無疑問。結果將是不確定的(編譯器能產生一些輸出,導致任何事情都有可能發生),應該躲開寫出這樣代碼的人除非他們同意改正錯誤。如果你擔心這樣的代碼會出現在你的軟件裏,那麼你最好完全避免使用引用,要不然就去讓更優秀的程序員去做。我們以後將忽略一個引用指向空值的可能性。
因爲引用肯定會指向一個對象,在C++裏,引用應被初始化。
string& rs; // 錯誤,引用必須被初始化
string s("xyzzy");
string& rs = s; // 正確,rs指向s
指針沒有這樣的限制。
string *ps; // 未初始化的指針
// 合法但危險
不存在指向空值的引用這個事實意味着使用引用的代碼效率比使用指針的要高。因爲在使用引用之前不需要測試它的合法性。
void printDouble(const double& rd)
{
cout << rd; // 不需要測試rd,它
} // 肯定指向一個double值
相反,指針則應該總是被測試,防止其爲空:
void printDouble(const double *pd)
{
if (pd)
{ // 檢查是否爲NULL
cout << *pd;
}
}
指針與引用的另一個重要的不同是指針可以被重新賦值以指向另一個不同的對象。但是引用則總是指向在初始化時被指定的對象,以後不能改變。
string s1("Nancy");
string s2("Clancy");
string& rs = s1; // rs 引用 s1
string *ps = &s1; // ps 指向 s1
rs = s2; // rs 仍舊引用s1,
// 但是 s1的值現在是
// "Clancy"
ps = &s2; // ps 現在指向 s2;
// s1 沒有改變
總的來說,在以下情況下你應該使用指針,
一是你考慮到存在不指向任何對象的可能(在這種情況下,你能夠設置指針爲空),
二是你需要能夠在不同的時刻指向不同的對象(在這種情況下,你能改變指針的指向)。如果總是指向一個對象並且一旦指向一個對象後就不會改變指向,那麼你應該使用引用。
還有一種情況,就是當你重載某個操作符時,你應該使用引用。
最普通的例子是操作符[].這個操作符典型的用法是返回一個目標對象,其能被賦值。
vector<int> v(10); // 建立整形向量(vector),大小爲10;
// 向量是一個在標準C庫中的一個模板(見條款35)
v[5] = 10; // 這個被賦值的目標對象就是操作符[]返回的值
如果操作符[]返回一個指針,那麼後一個語句就得這樣寫:
*v[5] = 10;
但是這樣會使得v看上去象是一個向量指針。因此你會選擇讓操作符返回一個引用。(這有一個有趣的例外,參見條款30)
當你知道你必須指向一個對象並且不想改變其指向時,或者在重載操作符併爲防止不必要的語義誤解時,你不應該使用指針。而在除此之外的其他情況下,則應使用指針假設你有
void func(int* p, int&r);
int a = 1;
int b = 1;
func(&a,b);
指針本身的值(地址值)是以pass by value進行的,你能改變地址值,但這並不會改變指針所指向的變量的值,
p = someotherpointer; //a is still 1
但能用指針來改變指針所指向的變量的值,
*p = 123131; // a now is 123131
但引用本身是以pass by reference進行的,改變其值即改變引用所對應的變量的值
r = 1231; // b now is 1231
儘可能使用引用,不得已時使用指針。
當你不需要“重新指向”時,引用一般優先於指針被選用。這通常意味着引用用於類的公有接口時更有用。引用出現的典型場合是對象的表面,而指針用於對象內部。
上述的例外情況是函數的參數或返回值需要一個“臨界”的引用時。這時通常最好返回/獲取一個指針,並使用 NULL 指針來完成這個特殊的使命。(引用應該總是對象的別名,而不是被解除引用的 NULL 指針)。
注意:由於在調用者的代碼處,無法提供清晰的的引用語義,所以傳統的 C 程序員有時並不喜歡引用。然而,當有了一些 C++ 經驗後,你會很快認識到這是信息隱藏的一種形式,它是有益的而不是有害的。就如同,程序員應該針對要解決的問題寫代碼,而不是機器本身。
////
1、引用在定義時就必須初始化(和另一個量相關聯),而指針定義時可以不初始化;
2、引用就是一個別名,本身不佔用內存地址,而指針是一個獨立的量,本身要佔用內存地址,在32位系統中佔四個字節;
3、引用的實質仍然是一個指針,它和指針常量的行爲相似——本身不可修改,只可修改指向內容。
0041運算符重載//編程思想第12章
使用全局重載運算符而不用成員運算符的最便利的原因之一是在全局版本中的自動類型轉換可以針對左右任一操作數
而成員函數版本必須保證左側操作數已處於正確的形式
一元運算符
class Integer{
long i;
Integer* This(){
return this;
}
public:
Integer(long ll=0):i(ll){} //構造函數後面 有分號也通過???DEV編譯通過
friend const Integer& operator+(const Integer& a ){
return a;
}//這個爲啥要用友元 ?
friend const Integer operator-(const Integer& a ){
return Integer(-a.i);
}
friend const Integer operator~(const Integer& a ){
return Integer(~a.i);
}
friend Integer* operator&(Integer& a ){
return a.This();
}
friend int operator!(const Integer& a ){
return !a.i;
}//這個返回值怎麼不是bool型?
friend const Integer& operator++(Integer& a ){
a.i++;//++a.i;
return a;
} //++前綴
friend const Integer operator++(Integer& a,int ){
Integer before(a.i);
a.i++;//++a.i;
return before;
}//++後綴
friend const Integer& operator--(Integer& a ){
a.i--;//--a.i;
return a;
} //--前綴
friend const Integer operator--(Integer& a ,int){
Integer before(a.i);
a.i--;//--a.i;
return before;
}//--後綴
}; //沒有使用this指針 需要參數
class Byte{
unsigned char b;
public:
Byte(unsigned char bb=0):b(bb){}
const Byte& operator+()const{
return *this;
} //怎麼是const成員函數????
const Byte operator-()const{
return Byte(-b);
} //??
const Byte operator~()const{
return Byte(~b);
}//??
Byte operator!()const{
return Byte(!b);
} //??
Byte* operator&(){
return this;
}
const Byte& operator++(){
b++;
return *this;
}
const Byte operator++(int){
Byte before(b);
b++;
return before;
}
const Byte operator--(){
--b;
return *this;
}
const Byte operator--(int){
Byte before(b);
--b;
return before;
}
}; //使用this指針 所以沒參數 不需要友元
二元運算符
class Integer{
long i;
public:
Integer(long ll=0):i(ll){}
friend const Integer& operator+(const Integer& left,const Integer& right)
{
return Integer(left.i+right.i);
}
friend const Integer& operator-(const Integer& left,const Integer& right)
{
return Integer(left.i-right.i);
}
friend const Integer& operator*(const Integer& left,const Integer& right)
{
return Integer(left.i*right.i);
}
//#include<./require.h>
friend const Integer& operator/(const Integer& left,const Integer& right)
{
require(right.i!=0,"divide by zero");
return Integer(left.i/right.i);
}
friend const Integer& operator%(const Integer& left,const Integer& right)
{
require(right.i!=0,"modulo by zero");
return Integer(left.i%right.i);
}
friend const Integer& operator^(const Integer& left,const Integer& right)
{
return Integer(left.i^right.i);
}
friend const Integer& operator&(const Integer& left,const Integer& right)
{
return Integer(left.i&right.i);
}
friend const Integer& operator|(const Integer& left,const Integer& right)
{
return Integer(left.i|right.i);
}
friend const Integer& operator<<(const Integer& left,const Integer& right)
{
return Integer(left.i<<right.i);
}
friend const Integer& operator>>(const Integer& left,const Integer& right)
{
return Integer(left.i>>right.i);
}
/////////////////////
friend Integer& operator+=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身賦值*/
}
left.i+=right.i;
return left;
}
friend Integer& operator-=(Integer& left,const Integer& rihgt)
{
if(&left==&right){
/*self-assignment身賦值*/
}
left.i-=right.i;
return left;
}
friend Integer& operator*=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身賦值*/
}
left.i*=right.i;
return left;
}
friend Integer& operator/=(Integer& left,const Integer& right)
{
require(right.i!=0,"divide by zero");
if(&left==&right){
/*self-assignment身賦值*/
}
left.i/=right.i;
return left;
}
friend Integer& operator%=(Integer& left,const Integer& right)
{
require(right.i!=0,"modulo by zero");
if(&left==&right){
/*self-assignment身賦值*/
}
left.i%=right.i;
return left;
}
friend Integer& operator^=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身賦值*/
}
left.i^=right.i;
return left;
}
friend Integer& operator&=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身賦值*/
}
left.i&=right.i;
return left;
}
friend Integer& operator|=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身賦值*/
}
left.i|=right.i;
return left;
}
friend Integer& operator>>=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身賦值*/
}
left.i>>=right.i;
return left;
}
friend Integer& operator<<=(Integer& left,const Integer& right)
{
if(&left==&right){
/*self-assignment身賦值*/
}
left.i<<=right.i;
return left;
}
////////////////////////
friend int operator==(const Integer& left,const Integer& right)
{
}
friend int operator!=(const Integer& left,const Integer& right)
{
return left.i==right.i;
}
friend int operator<(const Integer& left,const Integer& right)
{
return left.i<right.i;
}
friend int operator>(const Integer& left,const Integer& right)
{
return left.i>right.i;
}
friend int operator<=(const Integer& left,const Integer& right)
{
return left.i<=right.i;
}
friend int operator>=(const Integer& left,const Integer& right)
{
return left.i>=right.i;
}
friend int operator&&(const Integer& left,const Integer& right)
{
return left.i&&right.i;
}
friend int operator||(const Integer& left,const Integer& right)
{
return left.i||right.i;
}
};
class Byte{
unsigned char b;
public:
Byte(unsigned char bb=0):b(bb){}
const Byte operator+(const Byte& right) const
{
return Byte(b+right.b);
}
const Byte operator-(const Byte& right) const
{
return Byte(b+right.b);
}
const Byte operator*(const Byte& right) const
{
return Byte(b*right.b);
}
const Byte operator/(const Byte& right) const
{
require(right.b!=0,"divide by zero");
return Byte(b/right.b);
}
const Byte operator%(const Byte& right) const
{
require(right.b!=0,"modulo by zero");
return Byte(b%right.b);
}
const Byte operator^(const Byte& right) const
{
return Byte(b^right.b);
}
const Byte operator&(const Byte& right) const
{
return Byte(b&right.b);
}
const Byte operator|(const Byte& right) const
{
return Byte(b|right.b);
}
const Byte operator<<(const Byte& right) const
{
return Byte(b<<right.b);
}
const Byte operator>>(const Byte& right) const
{
return Byte(b*>>right.b);
}
Byte& operator=(const Byte& right)
{
if(this==&right) return *this;
b=right.b;
return *this;
}
Byte& operator+=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b+=right.b;
return *this;
}
Byte& operator-=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b-=right.b;
return *this;
}
Byte& operator*=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b*=right.b;
return *this;
}
Byte& operator/=(const Byte& right)
{
require(right.b!=0,"divide by zero");
if(this==&right){/*self-assignment*/}
b/=right.b;
return *this;
}
Byte& operator%=(const Byte& right)
{
require(right.b!=0,"modulo by zero");
if(this==&right){/*self-assignment*/}
b%=right.b;
return *this;
}
Byte& operator^=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b^=right.b;
return *this;
}
Byte& operator&=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b&=right.b;
return *this;
}
Byte& operator|=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b|=right.b;
return *this;
}
Byte& operator>>=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b>>=right.b;
return *this;
}
Byte& operator<<=(const Byte& right)
{
if(this==&right){/*self-assignment*/}
b<<=right.b;
return *this;
}
int operator==(const Byte& right) const
{
return b==right.b;
}
int operator!=(const Byte& right) const
{
return b!=right.b;
}
int operator<(const Byte& right) const
{
return b<right.b;
}
int operator>(const Byte& right) const
{
return b>right.b;
}
int operator<=(const Byte& right) const
{
return b<=right.b;
}
int operator>=(const Byte& right) const
{
return b>=right.b;
}
int operator&&(const Byte& right) const
{
return b&&right.b;
}
int operator||(const Byte& right) const
{
return b||right.b;
}
};
#ifndef REQUIRE_H
#define REQUIRE_H
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
inline void require(bool requirement,
const std::string& msg = "Requirement failed"){
using namespace std;
if (!requirement) {
fputs(msg.c_str(), stderr);
fputs("\n", stderr);
exit(1);
}
}
inline void requireArgs(int argc, int args,
const std::string& msg =
"Must use %d arguments") {
using namespace std;
if (argc != args + 1) {
fprintf(stderr, msg.c_str(), args);
fputs("\n", stderr);
exit(1);
}
}
inline void requireMinArgs(int argc, int minArgs,
const std::string& msg =
"Must use at least %d arguments") {
using namespace std;
if(argc < minArgs + 1) {
fprintf(stderr, msg.c_str(), minArgs);
fputs("\n", stderr);
exit(1);
}
}
inline void assure(std::ifstream& in,
const std::string& filename = "") {
using namespace std;
if(!in) {
fprintf(stderr, "Could not open file %s\n",
filename.c_str());
exit(1);
}
}
inline void assure(std::ofstream& out,
const std::string& filename = "") {
using namespace std;
if(!out) {
fprintf(stderr, "Could not open file %s\n",
filename.c_str());
exit(1);
}
}
#endif
class A{
float a;
public:
A(float aa=0.0f):a(aa){ }
//下面是重載一個float()轉換類型 把A類型轉換爲float類型;
operator float()const{
return a;
}
};
0042 一個類重載new delete
見編程思想P328頁
0043 一個數組類重載new delete
見編程思想P320頁
0044定位new delete
見編程思想P333頁
0045
int(47)
int *p=new int(44)
////////////////////////////////////////// /////
0047未看
《C++編程思想》 異常處理、防禦性編程、輸入輸出流、通用算法、通用容器 模板
高級篇8 9 10 11章 模板
0048引用計數
0049智能指針
////////////////////////////////////////////////
0050模板
懶惰初始化
#include "../require.h"
#include <iostream>
using namespace std;
template<class T,int size=100>
class Array{
T array[size];
public:
T& operator[](int index){
require(index>=0&&index<size,"Index out of range");
return array[index];
}
int length()const { return size;}
};
class Number{
float f;
public:
Number(float ff=0.0f):f(ff){ }
Number& operator=(const Number& n){
f=n.f;
return *this;
}
operator float()const{ return f;}
friend ostream& operator<<(ostream& os,const Number& x){
return os<<x.f;
}
};
template<class T,int size=20>
class Holder{
Array<T,size>* np;
public:
Holder():np(0){ }
T& operator[](int i){
require(0<=i&&i<size);
if(!np) np=new Array<T ,size>;
return np->operator[](i);
}
int length() const{ return size; }
~Holder(){ delete np; }
};
Array是被檢查的對象數組,並且防止下標越界。類Holder 很像Array ,只是它有一個指向Array的指針,
而不是指向類型Array的嵌入對象。該指針在構造函數中不被初始化,而是推遲到第一次訪問時。這
稱爲:懶惰初始化。如果創造大量的對象,但不訪問每一個對象,爲了節省存儲,可以用懶惰初始化技術。
0051 編程思想 14章
。派生類成員函數重載基類成員函數 基類成員函數將會被隱藏
。構造函數 析構函數 operator= 不能被派生類所繼承
。繼承和靜態成員函數
靜態成員函數與非靜態成員函數的共同點:
1 它們均可被繼承到派生類中
2 如果我們重新定義了一個靜態成員,所有在基類中的其他重載函數會被隱藏
3 如果我們改變了基類中一個函數的特徵,所有使用該函數名字的基類版本都將會被隱藏。
。確定應當用組合還是用繼承,最清楚的方法之一是詢問是否需要從新類型向上類型轉換。
0052編程思想 15章
C++如何實現晚捆綁 P369頁
293頁 301頁
虛函數 三種訪問 對象 指針 引用
#include<iostream>
using namespace std;
class father{
public:
virtual void run() const {
cout<<"父親可以跑萬米"<<endl;
}
};
class son :public father{
public:
void run() const{
cout<<"兒子可以跑十萬米"<<endl;
}
};
class daughter:public father{
public:
void run()const{
cout<<"女兒可以跑五萬米"<<endl;
}
};
void one(father);
void two(father*);
void three(father&);
int main()
{
father *p1=0;
father *p2=0;
father *p3=0;
p1=new son;
one(*p1);
p2=new daughter;
two(p2);
p3=new father;
three(*p3);
delete p1,p2,p3;
p1=0,p2=0,p3=0;
return 0;
}
void one(father one){
one.run();
}
void two(father* two){
two->run();
}
void three(father& three){
three.run();
}
//輸出
//父親
//女兒
//父親
#include<iostream>
using namespace std;
class A{
public:
virtual void print(){
cout<<"A"<<endl;
}
};
class B:public A{
public:
void print(){
cout<<"B"<<endl;
}
};
class c:public A{
public:
void print(){
cout<<"C"<<endl;
}
};
int mai()
{
A a;
B b;
C c;
A* p=&a;
B* P1=&b;
C* p2=&c;
p->print();
p1->print();
p2->print();
return 0;
}
純虛函數
virtual void f()=0;
當繼承一個抽象類時,必須實現所有的純虛函數,否則繼承出的類也將是一個抽象的類。
純虛函數禁止對抽象類的函數以傳值方式調用 即函數參數使用指針或者引用
在基類中,對純虛函數提供定義是可能的 但是不允許創建對象 如果要創建對象必須在派生類裏定義
#include<iostream>
using namespace std;
class Pet{
public:
virtual void speak() const=0;
virtual void eat() const=0;
};
void Pet::eat() const{
cout<<"Pet::eat()"<<endl;
}
void Pet::speak() const{
cout<<"Pet::speak()"<<endl;
}
class Dog:public Pet{
void speak() const { Pet::speak();}
void eat() const { Pet::eat();}
};
int main()
{
Dog simba;
simba.eat();
simba.speak();
}
運行時類型辨認(Run-Time Type Identification,RTTI)
不能在重新定義過程中修改虛函數的返回類型。但是返回一個
指向基類的指針或引用 則該函數的重新定義版本將會從
基類返回的內容中返回一個指向派生類的指針或者引用
編程思想P383頁
虛機制在構造函數裏不發生作
構造函數
構造函數
先看看構造函數的調用順序規則,只要我們在平時編程的時候遵守這種約定,
任何關於構造函數的調用問題都能解決;構造函數的調用順序總是如下:
1.基類構造函數。如果有多個基類,則構造函數的調用順序是某類在類派生表中出現的順序,
而不是它們在成員初始化表中的順序。
2.成員類對象構造函數。如果有多個成員類對象則構造函數的調用順序是對象在類中被聲明的順序,
而不是它們出現在成員初始化表中的順序。
3.派生類構造函數。
析構函數
析構函數的調用順序與構造函數的調用順序正好相反,將上面3個點反過來用就可以了,首先調用派生類的析構函數;其次再調用成員類對象的析構函數;最後調用基類的析構函數。
析構函數在下邊3種情況時被調用:
1.對象生命週期結束,被銷燬時(一般類成員的指針變量與引用都i不自動調用析構函數);
2.delete指向對象的指針時,或delete指向對象的基類類型指針,而其基類虛構函數是虛函數時;
3.對象i是對象o的成員,o的析構函數被調用時,對象i的析構函數也被調用。
下面用例子來說說構造函數的的調用順序:
#include "stdafx.h"
#include "iostream"
using namespace std;
class Base
{
public:
Base(){ std::cout<<"Base::Base()"<<std::endl; }
~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
};
class Base1:public Base
{
public:
Base1(){ std::cout<<"Base1::Base1()"<<std::endl; }
~Base1(){ std::cout<<"Base1::~Base1()"<<std::endl; }
};
class Derive
{
public:
Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
};
class Derive1:public Base1
{
private:
Derive m_derive;
public:
Derive1(){ std::cout<<"Derive1::Derive1()"<<std::endl; }
~Derive1(){ std::cout<<"Derive1::~Derive1()"<<std::endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Derive1 derive;
return 0;
}
運行結果是:
Base::Base()
Base1::Base1()
Derive::Derive()
Derive1::Derive1()
Derive1::~Derive1()
Derive::~Derive()
Base1::~Base1()
Base::~Base()
那麼根據上面的輸出結果,筆者稍微進行一下講解,構造函數的調用順序是;
首先,如果存在基類,那麼先調用基類的構造函數,如果基類的構造函數中仍然存在基類,
那麼程序會繼續進行向上查找,直到找到它最早的基類進行初始化;如上例中類Derive1,
繼承於類Base與Base1;其次,如果所調用的類中定義的時候存在着對象被聲明,
那麼在基類的構造函數調用完成以後,再調用對象的構造函數,
如上例中在類Derive1中聲明的對象Derive m_derive;
最後,將調用派生類的構造函數,如上例最後調用的是Derive1類的構造函數。
virtual析構函數
下面來說一說爲多態基類聲明virtual析構函數:
在C++中,構造函數不能聲時爲虛函數,這是因爲編譯器在構造對象時,
必須知道確切類型,才能正確的生成對象,因此,不允許使用動態束定;
其次,在構造函數執行之前,對象並不存在,無法使用指向此此對象的指針來調用構造函數,
然而,析構函數是可以聲明爲虛函數;C++明白指出,當derived class對象經由一個base class指針被刪除,
而該base class帶着一個non-virtual析構函數,
其結果未有定義---實際執行時通常發生的是對象的derived成分沒被銷燬掉。
看下面的例子:
class Base
{
public:
Base(){ std::cout<<"Base::Base()"<<std::endl; }
~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
};
class Derive:public Base
{
public:
Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Base* pBase = new Derive();
//這種base classed的設計目的是爲了用來"通過base class接口處理derived class對象"
delete pBase;
return 0;
}
輸出的結果是:
Base::Base()
Derive::Derive()
Base::~Base()
從上面的輸出結果可以看出,析構函數的調用結果是存在問題的,
也就是說析構函數只作了局部銷燬工作,這可能形成資源泄漏敗壞數據結構等問題;
那麼解決此問題的方法很簡單,給base class一個virtual析構函數;
class Base
{
public:
Base(){ std::cout<<"Base::Base()"<<std::endl; }
virtual ~Base(){ std::cout<<"Base::~Base()"<<std::endl; }
};
class Derive:public Base
{
public:
Derive(){ std::cout<<"Derive::Derive()"<<std::endl; }
~Derive(){ std::cout<<"Derive::~Derive()"<<std::endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
Base* pBase = new Derive();
delete pBase;
return 0;
}
輸出結果是:
Base::Base()
Derive::Derive()
Derive::~Derive()
Base::~Base()
可能上面的輸出結果正是我們所希望的吧,呵呵!由此還可以看出虛函數還是多態的基礎,
在C++中沒有虛函數就無法實現多態特性;因爲不聲明爲虛函數就不能實現“動態聯編”,
所以也就不能實現多態啦!
0053關聯容器
map 關聯數組,元素通過鍵來存儲和讀取
set 大小可變的集合,支持通過鍵實現的快速讀取
multimap 支持同一個鍵多次出現的map
multiset 支持同一個鍵多次出現的set
pair 類型 #include<utility>
一種模板類型
pair<T1,T2> p1; 創建一個空的pair對象,它的兩個元素分別是T1和T2類型,採用值初始化。
pair<T1,t2> p1(v1,v2); 創建一個pair對象,它的兩個元素分別是T1和T2類型,其中first成員初始化爲v1,
而second成員初始化爲v2.
make_pair(v1,v2) 以v1和v2值創建一個新的pair對象,其元素類型分別是v1和v2的類型
p1<p2 兩個pair對象之間的小於運算,其定義遵循字典次序:p1.first<p2.first
或者!(p2.first<p1.first)&&p1.second<p2.second 則返回true.
p1==p2 如果兩個pair對象的first和second成員依次相等,則這兩個對象相等。該運算使用其元素的==操作符
p.first 返回p中名爲first的(公有)數據成員
p.second 返回p中名爲second的(公有)數據成員
pair 類型的使用相等繁瑣,因此,如果需要定義多個相同的pair類型對象,可以用typedef簡化聲明
typedef pair<string,string> Autor;
Autor proust("Marcel","Proust");
pair類型支持的操作見 《C++Primer》308頁
map 類型 #include<map>
map 是鍵-值對的集合,map類型通常可理解爲關聯數組,可使用鍵作爲下標來獲取一個值,正如內置數組類型一樣。
而關聯的本質在於元素的值與某個特定的鍵相關聯,而並非通過元素在數組中的位置來獲取
map<k,v> m; 創建一個名爲m的空map對象,其鍵和值的類型分別爲k和v。
map<k,v> m(m2); 創建m2的副本m,m與m2必須有相同的鍵類型和值類型。
map<k,v> m(b,e); 創建map類型的對象m,存儲迭代器b和e標記的範圍內所有元素的副本。
元素的類型必須能轉換爲pair<const k,v>.
在使用關聯容器時,它的鍵不但有一個類型,而且還有一個相關的比較函數。默認情況下,標準庫使用鍵類型定義
的<操作符來實現鍵的比較。
所用的比較函數必須在鍵類型上定義嚴格的弱排序見 《C++Primer》309頁
map容器額外定義的類型別名
map<K,V>::key_type 在map容器中,用做索引的鍵的類型
map<K,V>::mapped_type 在map容器中,鍵所關聯的值的類型
map<K,V>::value_type 一個pair類型,它的first元素具有 const map<K,V>::key_type類型,而second元素則爲
map<K,V>::mapped_type類型//???
value_type是pair類型 它的值成員可以修改,但鍵成員不能修改。
map迭代器進行解引用將產生pair類型的對象
給map添加元素
使用insert成員實現或者先用下標操作符獲取元素,然後給獲取的元素賦值
map<string,int> word_count;
word_count["Anna"]=1;
1)在word_count中查找鍵爲Anna的元素,沒有找到
2)將一個新的鍵-值對插入到word_count中。它的鍵是const string類型對象,保存Anna。
而它的值則將
3)將這個新的鍵-值對插入到word_count中
4)讀取新的插入元素,並將它的值賦爲1.
注:使用下標訪問不存在的元素將導致map容器中添加一個新的元素,它的鍵即爲該下標值。
如同其他下標操作符一樣,map的下標也使用索引(其實就是鍵)來獲取該鍵所關聯的值
如果該鍵已在容器中,則map的下標運算與vector的下標運算行爲相同:返回該鍵所關聯的值
只有在所查找的鍵不存在時,map容器才爲該鍵創建一個新元素,並將它插入到此map對象中。
此時,所關聯的值採用值初始化:類類型的元素用默認構造函數初始化,而內置裂隙的元素則
初始化爲0
map迭代器返回value_type類型的值-包含const key_type和mapped_type類型成員的pair
下標操作符則返回一個mapped_type類型的值
map<string ,int > word_count;
string word;
while(cin>>word)
++word_count[word];
創建一個map對象用於記錄每個單詞出現的次數
下標操作符是先初始化後賦值
m.insert(e) e是一個用在m上的value_type類型的值,如果(e.first)不在m中,則插入一個值爲
e.second的新元素;如果該鍵在m中已存在,則保持m不變。該函數返回一個pair類型
包含指向鍵爲e.first的元素的map迭代器 ,以及一個bool類型的對象表示是否插入了該元素
m.insert(beg,end) beg和end是標記元素範圍的迭代器,其中的元素必須爲m.value_type類型的鍵-值對,
對於該範圍內的所有元素,如果他的鍵值在m中不存在,則將該鍵值及其關聯的值插入到m返回void
m.insert(iter,e) e是一個用在m上的value_type類型的值。如果見(e.first)不在m中.則創建新元素,並以迭代器
iter爲起點搜索新元素的存儲位置。返回一個迭代器,指向m中具有給定鍵的元素
word_cout.insert(map<string,int>::value_type("Anna",1));
word_cout.insert(make_pair("Anna",1));
typedef map<string,int>::value_type ValType;
word_cout.insert(ValType("Anna",1));
帶一個鍵-值pair形參的insert版本將返回一個值:包含一個迭代器和一個bool值的pair對象,其中迭代器指向map中具有
相應鍵的元素,而bool值則表示是否插入了該元素,如果該鍵已在容器中則其關聯的值保持不變,返回的bool值爲false
如果該鍵不在容器中,則插入新元素,且bool值爲true
m.count(k) 返回m中K的出現次數 返回值只能是0或者1 鍵是唯一的
m.find(k) 如果m容器中存在按k索引的元素,則返回指向該元素的迭代器,如果不存在,則返回超出抹點的迭代器
m.erase(k) 刪除m中鍵爲k的元素,返回size_type類型的值,表示刪除元素的個數
m.erase(p) 從m中刪除迭代器p所指向的元素,p必須指向m中確實存在的元素,而且不能等於m.end()。返回void類型
m.erase(b,e) 從m中刪除段範圍內的元素,該範圍由迭代器b和e標記,b和e必須標記m中的一個段有效範圍:
即b和e都必須指向m中的元素或者最後一個元素的下一位置。而且b和e要麼相等(此時刪除範圍爲空)
要麼b所指向的元素必須出現在e所指向元素之前 返回void類型
0054異常處理
關鍵字throw 將導致一系列事情發生
首先,它將創建程序所拋出的對象的一個拷貝
然後,實際上,包含throw表達式的函數返回了這個對象,即使該函數原先並未設計爲返回這種類型的對象類型。
一種簡單的考慮異常處理的方式是將其看做是一種交錯返回機制
當然可以通過拋出的一個異常而離開正常的作用域。在任何一宗情況下都會返回一個值,並且退出函數或作用域
異常發生前創建的局部對象被銷燬。這種對局部對象的自動清理通常被稱爲“棧反解(stack unwinding)”
class Rainbow{
public:
Rainbow(){
cout<<"調用構造函數"<<endl;
}
~Rainbow(){
cout<<"調用析構函數"<<endl;
}
};
void oz(){
Rainbow rb;
for(int i=0;i<3;++i)
cout<<"這是函數調用"<<endl;
throw 47;
}
int main()
{
try{
cout<<"發生異常"<<endl;
oz();
}catch(int){
cout<<"異常處理"<<endl;
}
return 0;
}
//結果
//調用構造函數
//這是函數調用
//這是函數調用
//這是函數調用
//調用析構函數
//異常處理
enum class Colors { Black, Blue, White, END_OF_LIST };
// Special behavior for ++Colors
Colors& operator++( Colors &c ) {
c = static_cast<Colors>( static_cast<int>(c) + 1 );
if ( c == Colors::END_OF_LIST )
c = Colors::Black;
return c;
}
/*
const float a=3.f;
int n=8;
(float&)a=6.f;
float ff=(float&)n;
*/
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
template <typename T> class print
{
public:
void operator()(const T& elem)//沒有被調用
{
cout<<elem<<" ";
}
};
int main()
{
int a[6]={0,1,2,3,4,5};
vector<int> i(a,a+6);
for_each(i.begin(),i.end(),print<int>());
return 0;
}
/*
死循環用for(;;)比while(1)好 這兩者在編譯器優化後可能是一樣的 ,但沒優化的優化的DEBUG模式下 前
者比後者性能高
*/
#include<stdio.h>
#include<windows.h>
void main()
{
float *fp[3];
float a=0.1,c=0.3;
fp[0]=&a;
fp[1]=(float*)malloc(sizeof(float));
*fp[1]=0.2;
*(fp+2)=&c;
printf("%f\n",*fp[0]);
printf("%f\n",*fp[1]);
printf("%f\n",*fp[2]);
free(fp[1]);
system("pause");
}
/*
* 主要測試該頭文件<stdlib.h>中的如下函數(參數中的const表示保護源指針指向的字符串不能修改):
* double atof(const char *nptr)
* atof()會掃描參數nptr字符串,跳過前面的空格字符,直到遇上數字或正負符號纔開始做轉換
* 而再遇到非數字或字符串結束時('\0')才結束轉換,並將結果返回。參數nptr字符串可包含正負號
* 小數點或E(e)來表示指數部分,如123.456或123e-2。
* 與strtod(nptr,(char **)NULL)結果相同。
* int atoi(const char *nptr)
* atoi()會掃描參數nptr字符串,跳過前面的空格字符,直到遇上數字或正負符號纔開始做轉換,
* 而再遇到非數字或字符串結束時('\0')才結束轉換,並將結果返回。
* 與使用strtol(nptr,(char**)NULL,10)結果相同。
* long atol(const char *nptr)
* atol()會掃描參數nptr字符串,跳過前面的空格字符,直到遇上數字或正負符號纔開始做轉換,
* 而再遇到非數字或字符串結束時('\0')才結束轉換,並將結果返回。
* atol()與使用strtol(nptr,(char**)NULL,10)結果相同。
* char *gcvt(double number,size_t ndigits,char *buf)
* gcvt()用來將參數number轉換成ASCII碼字符串,參數ndigits表示顯示的位數。
* gcvt()與ecvt()和fcvt()不同的地方在於,gcvt()所轉換後的字符串包含小數點或正負符號。
* 若轉換成功,轉換後的字符串會放在參數buf指針所指的空間。返回一字符串指針,此地址即爲buf指針。
* double strtod(const char *nptr ,char **endptr,int base)
* strtod()會掃描參數nptr字符串,跳過前面的空格字符,直到遇上數字或正負符號纔開始做轉換,
* 到出現非數字或字符串結束時('\0')才結束轉換,並將結果返回。若endptr不爲NULL,
* 則會將遇到不合條件而終止的nptr中的字符指針由endptr傳回。參數nptr字符串可包含正負號、小數點或E(e)來表示指數部分。如123.456或123e-2。
* long int strtol(const char *nptr,char **endptr,int base)
* strtol()會將參數nptr字符串根據參數base來轉換成長整型數。參數base範圍從2至36,或0。參數base代表採用的進制方式,如base值爲10則採用10進制,
* 若base值爲16則採用16進制等。當base值爲0時則是採用10進製做轉換,但遇到如'0x'前置字符則會使用16進製做轉換。一開始strtol()會掃描參數nptr字符串,
* 跳過前面的空格字符,直到遇上數字或正負符號纔開始做轉換,再遇到非數字或字符串結束時('\0')結束轉換,並將結果返回。若參數endptr不爲NULL,
* 則會將遇到不合條件而終止的nptr中的字符指針由endptr返回。
*
* unsigned long int strtoul(const char *nptr,char **endptr,int base)
* strtoul()會將參數nptr字符串根據參數base來轉換成無符號的長整型數。參數base範圍從2至36,或0。
* 參數base代表採用的進制方式,如base值爲10則採用10進制,若base值爲16則採用16進制數等。
* 當base值爲0時則是採用10進製做轉換,但遇到如'0x'前置字符則會使用16進製做轉換。一開始strtoul()會掃描參數nptr字符串,
* 跳過前面的空格字符串,直到遇上數字或正負符號纔開始做轉換,再遇到非數字或字符串結束時('\0')結束轉換,並將結果返回。
* 若參數endptr不爲NULL,則會將遇到不合條件而終止的nptr中的字符指針由endptr返回。
* 返回轉換後的長整型數,否則返回ERANGE並將錯誤代碼存入errno中。ERANGE指定的轉換字符串超出合法範圍。
*
* int toascii(int c)
* toascii()會將參數c轉換成7位的unsigned char值,第八位則會被清除,此字符即會被轉成ASCII碼字符。
*
* int tolower(int c)
* 若參數c爲大寫字母則將該對應的小寫字母返回。
*
* int toupper(int c)
* 若參數c爲小寫字母則將該對映的大寫字母返回。
*
*/
055 運算符轉換//???不理解
C++編程思想 307頁到311頁
class Three{
int i;
public:
Three(int ii=0,int =0):i(ii){
}
};
class Four{
int x;
public:
Four(int xx):x(xx){
}
operator Three()const { return Three(x);}
};
void g(Three) {
}
int main()
{
Four FOUR(1);
g(four);
g(1);
}
可以創建一個成員函數,這個函數通過在關鍵字operator 後面跟隨想要轉換到的類型方法,當前類型轉換
爲希望的類型,這種形式重載很是獨特,因爲沒有指定一個返回類型-返回類型就是正在重載的運算符的名字
056
位運算
位與運算 a&b 同時爲1 則 1 其他全是0
位或運算 a|b 同時爲0 則 0 其他全是1
位異或運算a^b 相同爲0 不同爲1
57模板
P583頁
模板類型的模板參數
template<class T,template<class> class Seq> class A{};
template<class T,template<class U> class Seq> class A{};
typename 標示是類型而不是變量
template<class T> class Outer{
public:
template<class R> class Inner{
public:
void f();
};
};
template<class T> template<class R>
void Outer<T>::Inner<R>::f(){
}
P598
template<class Seq,class T,class R>
void apply(Seq& sq,R (T::*f)()const){
typename Seq::iterator it = sq.begin();
while(it!=sq.end())
((*it++)->*f)();
}
template<class Seq,class T,class R,class A>
void apply(Seq& sq,R(T::*f)(A)const, A a){
typename Seq::iterator it = sq.begin();
while(it != sq.end())
((*it++)->*f)(a);
}
template<class Seq,class T,class R,class A1,class A2>
void apply(Seq& sq, R(T::*f)(A1,A2)const,A1 a,A2 a2)
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
((*it++)->*f)(a1,a2);
}
template<class Seq,class T,class R>
void apply(Seq& sq,R(T::*f)())
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
((*it++)->*f)();
}
template<class Seq,class T,class R,class A>
void apply(Seq& sq,R(T::*f)(A),A a)
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
((*it++)->*f)(a);
}
template<class Seq,class T,class R,class A1,class A2>
void apply(Seq& sq,R(T::*f)(A1,A2),A1 a1,A2 a2)
{
typename Seq::iterator it = sq.begin();
while(it != sq.end())
((*it++)->*f)(a1,a2);
}
P601
模板特化
一個模板定義就是一個實體一般化的過程,以爲它在一般條件下描述了某個範圍內的一族函數或類
給定模板參數時,這些模板參數決定了這一族函數或類的許多可能的實例中的一個獨一無二的實例
因此這樣的結果就被稱做模板的一個特化。
template<class T> const T& min(const T& a,const T& b){
return (a>b)?a:b;
}
template<>
const char* const& min<const char*>(const char* const& a,
const char* const& b){
return (strcmp(a,b)<0)?a:b;
}
template<> 告訴編譯器接下來的是一個模板的特化。
模板特化的類型必須出現在函數名後緊跟的尖括號中,就像通常在一個明確指定參數類型的函數調用中一樣。
用const char* 代替T
如果一旦在最初模板定義時使用const T 特化時必須用const char* const 代替const T
沒看懂