1.C++函數庫python
Algorithms ios
<algorithm> c++
C Library Wrappers 算法
<cassert> , <cctype>, <cerrno>, <cfenv>, <cfloat>, <cinttypes>, <ciso646>, <climits>, <clocale>, <cmath>, <csetjmp>, <csignal>, <cstdarg>, <cstdbool>, <cstddef>, <cstdint>, <cstdio>, <cstdlib>, <cstring>, <ctgmath>, <ctime>, <cwchar>, <cwctype> 編程
Containers windows
Sequence 設計模式
<array> 、<deque>、<forward_list>、<list>、<vector> 數組
Ordered Associative promise
Unordered Associative
<unordered_map> , <unordered_set>
Adaptor
Error Handling
<exception>、<stdexcept>、<system_error>
Input/Output
<filesystem> , <fstream>, <iomanip>, <ios>, <iosfwd>, <iostream>, <istream>, <ostream>, <sstream>, <streambuf>, <strstream>
Iterators Library
<iterator> 單獨的迭代器一般用於泛型算法中,而不會單用
Localization
<codecvt> , <cvt/wbuffer>, <cvt/wstring>, <locale>
Math and Numerics
<complex> , <limits>, <numeric>, <random>, <ratio>, <valarray>
Memory Management
<allocators> , <memory>, <new>, <scoped_allocator>
Multithreading
<atomic> , <condition_variable>, <future>, <mutex>, <thread>
Other Utilities
<bitset> , <chrono>, <functional>, <initializer_list>, <tuple>, <type_traits>, <typeinfo>, <typeindex>, <utility>
Strings and Character Data
1.C++命名空間
C++只有一個命名空間std,包含了全部的C++庫。關於命名空間,同一個命名空間的名字能夠在不一樣的文件或同一文件中屢次出現,也能夠出如今頭文件裏面,表示同一命名空間的不一樣部分代碼。
2.C++引用與指針
C++的引用通常用於函數入參和函數返回值。引用和指針其實存在本質的區別,引用就是一個對象的別名,沒有分配存儲對象指針的存儲空間;而指針須要分配一個存儲對象指針的存儲空間。因此指針和引用通常狀況下是沒有辦法相互轉換的。在C++中,若是對象沒有名字(如用new分配的對象)那麼它也不可能有別名,而其餘語言(如C#)能夠只有別名。
3.C++的模板:針對類型的一種統一操做
C++的模板分爲函數模板和類模板,二者聲明的形式都同樣。
template <class T1,...,class Tn> 函數或類聲明
{
函數體或類體
}
函數模板在調用時自動實例化,而類模板必須先實例化(指明類型),才能調用。
4.關於throw的棧展開
在throw進行棧展開時會調用不少析構函數或進行不少函數的局部變量回收,直到遇到了catch爲止。
5.操做符重載
操做符的本質是一個函數,其重載有兩種狀況:第一種操做符在類中做爲成員函數重載;第二種操做符做爲全局函數重載。兩種重載方式有一點區別:1)在類中做爲成員函數重載,將默認第一個函數入參(雙目時爲左操做數)爲this指向的本類對象,因此在類中重載操做符時,雙參在形式上變成了單參。2)基於第一條規則,有些左操做數特殊的操做符(如<<或>>等要求左操做數必須爲流對象)不能在類中重載。在衆多的運算符中,大多對左右操做數是敏感的,不容許左右操做數交換。第一個入參是左操做數,第二個入參是右操做數。
6.友元
在類中定義一個友元函數(或重載的操做符)或友元對象,那麼在該友元中能夠以‘.’訪問該類對象的私有成員,這是在其餘非友元做用域裏作不到的。
Class A{private :int a ;friend void myfunc(void);}
void myfunc(void)
{
A myA;
cout<<myA.a;//在非友元做用域中不能這樣訪問。
}
7.容器
全部容器提供的內置方法絕大部分是與迭代器有關的,大部分泛型算法也與迭代器有關。
迭代器和指針是徹底同樣的,可是迭代器更安全。
容器在構造時可使用其餘不一樣類型的容器的迭代器或指針來構造,可是須要保證兩種容器或數組的數據類型是一致的。
8.流
流 |
||
頭文件 |
容器 |
說明 |
<iostream> |
istream |
Wistream,wostream,wiostream 從流中讀寫數據 |
ostream |
||
iostream |
||
<fsteram> |
ifstream |
Wifstream,Wofstream,wiofstream 從文件中讀寫數據 |
ofsteram |
||
fsteram |
||
<sstream> |
istringstream |
Wistringstream,Wostringstream,wstringstream 從字符串流中讀取數據(底層沒有字符串) |
ostringstream |
||
stringstream |
在流對象使用中,若是產生任何失敗,則其對象爲0,不然爲非0,相應的錯誤狀態被記錄在該對象中。
例:Int a;Cin>>a;若是輸入了一個字符,那麼將產生錯誤。則cin==0。If (cin) 將爲false。其它流都有這種用法。
流的做用:->本質上是字符處理。將字符從不一樣的底層以多種格式(或方式)輸入到內存中來,或者相反。如下是例舉的幾種流的做用:
(1)以單個字符、字符串、行任意形式存取字符
(2)以×××、浮點型等形式存取字符
9.lambda 函數
返回值類型[可以使用的全局變量列表](形參列表){函數體}
若是「可以使用的全局變量列表」爲「=」,則能使用全部的全局變量
10.順序容器
特色:根據位置存儲和訪問容器,容器元素的排列次序與元素值無關,而由元素添加到容器裏的次序決定。標準庫只定義了三種順序容器:vector,list,deque.
和三種順序容器適配器(適配器是以基本的容器類型爲底層,經過定義新的操做接口,實現不一樣的數據操做):stack,queue,priority_queue
容器和容器適配器都只提供了少許操做,大部分操做由泛型算法庫提供。
容器的長度都是動態可增加縮短的。
容器元素的類型約束有兩點:元素類型必須支持賦值運算和元素類型對象必須可複製。(即不能爲引用和流)
迭代器的範圍爲[iter.begin,iter.end),其編程意義爲:當iter.begin== iter.end迭代器範圍爲空。
容器內置的操做主要有:
任意位置任意方式增減元素個數的操做,迭代操做,交換元素操做。
11.關聯容器
基本的有4種:map,set,multimap,multiset
在map中有個很差的地方:
Map<string,int>word_count;
Int occurs=word_count[「foobar」];
這樣的查找若是「foobar」鍵不存在於word_count容器中,那麼將會增長一個「foobar」鍵,並返回值爲0。這是編譯語言與解釋語言的區別。.find方法提供了判斷該鍵是否存在的方法。
.count方法提供了判斷map和multimap中鍵個數的方法。
在python中能實現的操做,在C++中基本上都有這些功能。沒有相應的語法支持,可是能夠提供對象內置方法來解決(反射除外)
Set容器提供了insert,count,find,erase等操做。可使用其餘容器的迭代器來構造set容器,只保留不重複的元素集合。
除了容器內置的迭代器之外,C++還提供了3類獨立迭代器:
1)插入迭代器
back_inserter,front_inserter,inserter.使用容器構造
2)流迭代器
istream_iterator,ostream_iterator
3)反向迭代器
reverse_iterator
12.指向類成員的指針
Class screen{;}
定義指向數據成員的指針:Int screen::*ptr;定義了一個指向screen類int類型成員的指針。
定義指向函數成員的指針:void (screen::*ptr)(void);定義了一個指向類函數的指針
指向類成員的指針能夠定義在類中也能夠定義在類外。
使用定義在類中的指向類成員的指針:兩個新的解引用操做符->*和.*,他們可以將成員指針綁定到實際對象。
成員函數指針列表 :typedef void (*action)(void);
screen::action screen::menu[]={&screen::home,...}
13.auto自動數據類型
Auto 和int,double不一樣,他能推斷數據的類型,從而將其轉化爲推斷出的類型。
14.序列for循環
全部能使用iterator的序列都能使用序列for循環
Map<string,int>m{{「a」,1},{「b」,2},{「c」,3}};
For(auto p:m){cout <<p.first<<」:」<<p.second<<endl;}
15.c98和c11的智能指針-爲分配的對象提供異常安全
(1)auto_ptr 可以代理new,能賦值(與unique_ptr的區別),但同一時刻只能有一個auto_ptr管理指針,不帶有引用計數(c98),能管理任意非數組指針對象
(2)unique_ptr 可以代理new[]和new,不能轉讓,不容許拷貝構造和拷貝賦值(不能放在容器中,但unique_ptr能保存容器指針),不帶有引用計數,能夠徹底代替scoped_ptr和scoped_array(c11)
(3)shared_ptr可以代理new的指針,實現了引用計數(應該是new int了一個內存用來保存引用計數,當發生shared_ptr賦值時將引用計數加1並將引用計數指針賦給另一個shared_ptr),支持拷貝和賦值,能夠放入容器中且能夠保存容器指針(make_share模板函數)(最有價值的指針)(c11)
(4)shared_array代理new[]的指針,shared_array能力有限。通常使用shared_ptr<vector>或vector<shared_ptr>來替代(c11)
(5)weak_ptr 該指針是輔助shared_ptr的,shared_ptr給weak_ptr賦值不會增長引用計數(c11)
(6)boost/smart_ptr.hpp
scoped_ptr 接受new的指針,不能轉讓(不支持拷貝和複製(私有拷貝和複製函數實現),不放入做容器),且只能在本做用域內使用,與auto_ptr基本相同
scoped_array接受new[]的指針,不能轉讓(不支持拷貝和複製(私有拷貝和複製函數實現),不能用做容器),且只能在本做用域內使用,能夠用vector替代
16.一些須要注意的C++編程細節
(1)環形緩存:
#define SIZE 100
int Buffer[SIZE];
Buffer[i%SIZE]='X'
(2)類的複製控制:
定義private的複製構造函數(如IO類),能夠保證該對象只有一個副本。經過設計模式還可使得該類只被構造一次。
classfa a;
classfa b=a;//實際上是調用了classfa的默認複製構造函數 classfa(const classfa &)
string c="ancd" //調用了字符構造函數和複製構造函數。
(3)順序容器:string,vector,list均可以使用.erase()和insert()在任意位置刪除和插入指定迭代器的節點。
(4)友元friend 是不支持繼承的。即類A是BASE的友元,但它不會是該BASE 的CHILD的友元。
(5)一個C標準函數:將從流中讀出的數據,從新壓入流的緩存中,能夠經過此函數實現peek()函數
ungetc( char ch, FILE* f); //向IO緩存中壓入數據
一次只能壓入一個字符
(6)遞歸:(鏈接2個鏈表)
已知兩個鏈表head1 和 head2 各自有序,請把它們合併成一個鏈表依然有序,此次要求用遞歸方法進行。
Node * MergeRecursive(Node *head1 , Node *head2)
{
if ( head1 == NULL )
return head2
if ( head2 == NULL)
return head1
Node *head = NULL
if ( head1->data < head2->data )
{
head = head1
head->next = MergeRecursive(head1->next,head2);
}
else
{
head = head2
head->next = MergeRecursive(head1,head2->next);
}
return head
}
(7)引用:
常引用:不能經過引用改變變量的值
int b=1;
const int & a=b;
a=2;//錯
b=2;//對
string foo( );
void bar(string & s);
那麼下面的表達式將是非法的:
bar(foo( ));
bar("hello world");
foo()的返回值必然是const string型的,「hello world」也被編譯器構造爲const string,而把他們用於非const函數入參,編譯器將會報錯。函數的引用入參應儘可能指明爲const,這樣const和非const的實參都不會報錯。
引用做爲函數返回值類型的好處:在內存中不產生被返回值的副本;(注意:正是由於這點緣由,因此返回一個局部變量的引用是不可取的。由於隨着該局部變量生存期的結束,相應的引用也會失效,產生runtime error!
(8)求一個數中包含的二進制1的個數:
def func(x):
cont=0;
while(x):
x=x&(x-1)
cont=cont+1
return cont
求任意數據由二進制表示時包含的1的個數
(9)虛析構函數做用:
將析構函數定義爲virtual 這樣在用delete刪除基類指針時能夠運行子類的析構函數。
(10)mutable關鍵字:在const變量中被改變
c++ 中 mutable關鍵字申明的變量能夠在const申明的函數中改變。
class M
{mutable int a;
void set() const
{a=10;}
}
(11)靜態成員:
class M
{
static M a; //correct
M *b; //correct
M c; //error
}
靜態成員是類的一部分,而不是對象的一部分。
(12)const 用法:
const對象不能調用非const的成員函數和變量;爲了使const對象和非const對象均可以調用一個同名函數,能夠重載該非const和const成員函數。
(13)c數組:
在c/C++中數組是一種完整的數據類型,同結構體,對象同樣:
main( ){
using namespace std;
int num[5]={1,2,3,4,5};
cout <<*((int *)(&num+1)-1) <
}
在C語言中,一維數組名錶示數組的首地址,並且是一個指針.如上例num,
對&num,表示指針的指針.意味着這裏強制轉換爲二維數組指針.
這樣 &num+1 等同於 num[5][1],爲代碼空間. (&num+1)-1表示 num[4][0].即num[4].
char (*a)[3][4] 定義一個數組指針
(14)switch的入參:
switch的參數不能是不能自動轉化爲整型的參數。
由於switch後面只能帶自動轉換爲×××(包括×××)的類型,好比字符型char,unsigned int等,實數型不能自動轉換爲×××.能夠手動強轉實數型(int)double,可是會致使精度的丟失.若是後面要對實數型作選擇的話,能夠乘以10的倍數,而後進行選擇,這樣不會丟失精度.可是這樣的話就要靠你去手動的控制乘以多少了
(15)sizeof
使用sizeof對變量求所佔內存長度時,是對該變量直接求內存長度,須要搞清楚,sizeof的對象是誰。
char *p=「dk4m7」;//sizeof(p),p內存爲4字節
char p[]="dk4m7";//sizof(p),p內存爲6字節,(p符號至關於一個引用,自己不佔內存)
(16)多維數組
char a[2][3][4]={{{1,2,3,4},{5,6,7,8},{9,10,11,12}},{{13,14,15,16},{17,18,19,20},{21,22,23,24}}};
a符號表示一個引用,不佔內存。其指向的地址爲a[0],a[0][0],&a[0][0][0]
a表示一個3維數組,a+1表示最高維加1,至關於a[1]的首地址
*a表示一個2維數組,*a+2,表示2維數組加1,至關於a[0][2]的首地址
*(a+1)+2至關於a[1][2]的首地址
*(*(a+1)+2)+3至關於a[1][2][3]的首地址
c11新特徵
1.列表初始化
任何對象或對象數組均可以使用列表初始化,列表賦值不支持任何形式的類型轉化,包括浮點到×××的轉化。
例子:如下4種等價
int s=0;
int s={0};
int s{0};
int s(0);
vector<int> vi{1,2,3,4,5};
vector<int> *vi=new vector<int>{1,2,3,4,5};
vi={5,6};//任何形式的列表賦值
map<string,string> authors={{"joyce","james"},{"austen","jane"}};//關聯容器的列表初始化
pair<string,string>authors{"james","joyce"};//列表初始化pair
2.constexpr 編譯時驗證,賦值表達式是否爲常量賦值
constexpr int mf=20;
constexpr int linit=mf+1; //正確,編譯時計算
constexpr 也可用於函數,要求函數只用一個return語句,而且形參類型必須是字面值類型
3.類型別名申明
typedef double base,*p; /舊方法。 base是double的別名;p是double*的別名
using double=base
using double*=p //新標準使用USING申明變量別名
4.auto 自動推斷賦值表達式的左值類型
auto b=20;
auto &a=b;
5.decltype 做用同auto,只能推斷表達式的類型。可是能夠靈活的指定表達式
const int ci=0,&cj=ci,p=&ci;
decltype(ci) x=0;
decltype(cj) y=x; //指定推斷cj表達式的類型,推斷的類型爲引用
decltype(*p) z=10; //對×解引用推斷的結果是引用,必須初始化
decltype((ci)) m=ci; //強制推斷爲引用 (3種引用推斷方式)
6.類實例化時,使用()和不使用()的區別
classA a;//不會進行處理爲顯式初始化的成員
classA a();//會將a的未初始化的成員默認初始化爲0
7.範圍for
string str("some string");
for(auto c:str)
8.initializer_list
a.initializer_list函數形參列表
void error_msg(ErrCode e,initializer_list<string> il){
for( const auto &elem:il){
cout<<elem<<" "<<endl;
}
}
調用:
error_msg(ErrCode(42),{"func1","ok","sd"});
b.initializer_list函數返回值
vector<string> process(){
if(expected.empty()){
return {};
}else if(expected==actual){
return {"fun","ok"};
else{
return {"fun","ok","lk"};
}
}
}
9.函數的尾置返回類型
對於函數的返回值類型較長的函數可使用尾置返回類型
auto func(int i) -> int(*)[10] //返回一個指向10個元素的數組指針
10. =default 申明默認構造函數
有時在含有有參構造函數時,也須要默認構造函數,這是就須要使用=default申明
Sales_data()=default;
11.c11支持的因此順序容器
vector,deque,list,forward_list(單向鏈表),array,string
均可以使用列表初始化
12.lambda函數
形式:
[捕獲參數列表](形參列表){...;[return]} //無需指定返回值類型
捕獲參數列表中有2個特殊的符號
&:表示全部外部變量的捕獲使用引用
=:表示全部外部變量的捕獲使用值
13.bind函數
用於綁定函數名及其參數,能夠交換參數位置。bind的返回值通常直接賦給auto
14.智能指針
shared-ptr<?> 實現引用計數,能夠轉移或賦值
unique-ptr<?> 不能轉移或賦值,取代auto-ptr<?> 但容許swap(由於該函數實現的是轉移)
scoped-ptr<?> 能夠轉移或賦值,可是同時只可能有一個對象持有指針
weaked-ptr<?> 解決share-ptr<?>的循環鏈中發生內存泄露的問題。
15.移動構造函數,右值引用,標準庫中的move函數,移動賦值
Quote& operator=(Quote&&){...} //移動賦值
16.final 經過定義類的final來阻止繼承
class NoDerived final{...}
17.模板的類型別名
template<typename T> using twin=pair<T,T>;
twin<string>authors{"jim","ali"};//是一個pair
c11補充
1.異常
(1)throw能夠拋出異常對象和異常對象的指針,當拋出指針是必須保證catch處該指針變量還有效(通常必須在同一函數中處理)。
range_error r("error");
throw r; 拋出異常對象
exception *p=&r;
throw *p;拋出異常對象的指針
(2)棧展開
棧展開:拋出異常時將暫停當前函數的執行,開始查找匹配的catch子句。首先檢查throw自己是否在try塊內部,若是是,檢查與該try相關的catch子句,看是否其中之一能與被拋出的對象匹配,若是找到匹配的catch,就處理異常;找不到,就退出當前函數,而且繼續在調用函數中查找。
(3)棧展開要執行的操做
a.爲局部對象調用析構函數:當一個包含throw的函數因爲異常提前退出時,該函數的棧上的局部存儲會被釋放,將釋放在throw調用前建立的全部棧對象。若是局部對象是類類型的,就直接調用對象的析構函數。new分配的對象不能釋放。
b.析構函數應該從不拋出異常:棧展開期間會常常執行析構函數,若是析構函數再次拋出本身沒有處理的異常,將致使系統調用terminate函數,使整個程序崩潰。因此編程者不要在析構函數中拋出異常,而且必須處理析構函數中的默認異常。
c.構造函數經常拋出異常:若是在構造對象的時候發生異常,則對象可能部分被構建而一些成員能夠沒有被初始化。須要保證適當的撤銷以構造的元素。
d.異常被拋出到線程入口時尚未被捕獲,則程序將調用terminate終止程序。
(4)catch
a.catch中可使用throw;不加對象或rethrow從新拋出異常。被拋出的異常是原來的異常對象而不是形參。若是想改變拋出的異常對象的成員,形參可使用&引用
(5)容器與數組
void fun(){
vector<string> v;
while(cin>>s){
v.push_back(s);
}
string *p=new string[20];
//do something
delete[] p;
}
若是在do something發生了任何異常,那麼Vector的內存可以正常釋放,但數組p的內存沒法釋放。由於無論什麼時候發生異常析構函數老是會被調用的。
(6)用類來分配資源
將分配資源的額動做必定要寫在類中,而不是單個的函數中,這樣在析構函數中寫釋放資源的操做,保證資源總能被釋放。
這種經過定義類來封裝資源的分配和釋放的技術稱爲RAII(資源分配即初始化)。原理就在於不管是否發生異常,類的析構函數老是會被調用的。
(7)auto_ptr類(保證異常安全-new出的對象自動釋放)
auto_ptr類是RAII技術的一個例子。auto_ptr類能夠接收任意一個非數組類型或模版的形參,爲動態分配的對象(new)提供異常安全。auto_ptr能夠保存任何類型指針。auto_ptr類的對象惟一持有指向的對象(不提供引用計數),這也決定了它不能放在容器中。
auto_ptr對象的複製和賦值是破壞性操做,將改變右操做數。
auto_ptr<string>ap1(new string("stg"));
auto_ptr<string>ap2(ap1);//ap1將解除綁定
ap1=ap2; //將對ap1之前指向的對象執行析構(雖然ap1沒有指向對象)
此時將解除右操做數對指針指向的對象的綁定,同時對左操做數原來指向的對象執行析構。(左右操做數都將有影響)
auto_ptr賦值時不能直接將一個地址賦給auto_ptr對象
p_auto=new int(1024);//error
而必須調用auto_ptr的reset函數來從新賦值
if(p_auto.get())//判斷該指針是否爲0
*p_auto=1024;
else
p_auto.reset(new int(1024)); //能夠用0來重置p_auto.reset(0)。調用reset方法可使多個auto_ptr指針指向同一對象。
自動指針賦值(ap1=ap2;)的過程爲:對左邊執行析構;左邊獲得右邊的值;對右邊執行解除綁定
auto_ptr指針釋放對象時使用的是delete而不是deleted[],因此他不能管理數組。
因爲auto_ptr不知足複製和複製操做,因此他不能放置在容器中。auto_ptr做爲形參時,必須使用引用。
(至關於auto_ptr在內存中是靜止的,不能移動)
(8)函數的異常說明
void fun(void) throw (runtime_error,logic_error)
{;}//代表該函數只可能拋出一個runtime_error的類及其子類
void fun(void) throw ()
{;}//代表該函數不會拋出異常
若是函數拋出了其餘沒有說明的異常,那麼系統將會調用unexpected函數,將會異常終止程序。
類繼承時,在虛函數中,子類拋出的異常說明必須比基類可能拋出的異常少,且必須是基類異常的一個子集。
(9)函數指針的異常說明
void (*pf)(int) throw(runtime_error);
代表pf只能指向一個只可能拋出runtime_errorauto_ptr類的函數。
2.運行時類型識別(RTTI)
c++經過一下2個操做符提供RTTI功能
(1)typeid操做符,返回指針或或引用所指向對象的實際類型。(基類和子類是相等的)
(2)dynamic_cast操做符,將基類類型的指針或引用安全的轉換爲派生類型的指針或引用。
3.c++11的移動語義與右值引用
C++ 11中引入的一個很是重要的概念就是右值引用。理解右值引用是學習「移動語義」(move semantics)的基礎。而要理解右值引用,就必須先區分左值與右值。
對左值和右值的一個最多見的誤解是:等號左邊的就是左值,等號右邊的就是右值。【左值和右值都是針對表達式而言的】,左值是指表達式結束後依然存在的持久對象,右值是指表達式結束時就再也不存在的臨時對象。【一個區分左值與右值的便捷方法是:看能不能對錶達式取地址,若是能,則爲左值,不然爲右值。】下面給出一些例子來進行說明。
移動語義體如今2點:
1. g++ -std=c++11 main.cpp c++11標準使用了賦值的移動語義 object a=foo();即將a與foo()返回的零時對象內存指針交換,而後釋放零時變量指向的內存。
2. 定義的&&對零時變量進行引用(右值引用),常做爲函數入參。用於獲取臨時變量中能夠複用的內存。
例如:
container& operator = (container&& other) //移動賦值
{
delete value; //假如value是一個使用new分配的內存指針,此時就能夠複用這個內存。
value = other.value;
other.value = NULL;
return *this;
}
轉移語義的定義
右值引用是用來支持轉移語義的。轉移語義能夠將資源 ( 堆,系統對象等 ) 從一個對象轉移到另外一個對象,這樣可以減小沒必要要的臨時對象的建立、拷貝以及銷燬,可以大幅度提升 C++ 應用程序的性能。臨時對象的維護 ( 建立和銷燬 ) 對性能有嚴重影響。
轉移語義是和拷貝語義相對的,能夠類比文件的剪切與拷貝,當咱們將文件從一個目錄拷貝到另外一個目錄時,速度比剪切慢不少。
經過轉移語義,臨時對象中的資源可以轉移其它的對象裏。
在現有的 C++ 機制中,咱們能夠定義拷貝構造函數和賦值函數。要實現轉移語義,【須要定義轉移構造函數,還能夠定義轉移賦值操做符】。對於右值的拷貝和賦值會調用轉移構造函數和轉移賦值操做符。若是轉移構造函數和轉移拷貝操做符沒有定義,那麼就遵循現有的機制,拷貝構造函數和賦值操做符會被調用。
普通的函數和操做符也能夠利用右值引用操做符實現轉移語義。
char* _data;
size_t _len;
void _init_data(const char *s) {
_data = new char[_len+1];
memcpy(_data, s, _len);
_data[_len] = '\0';
}
MyString(const MyString& str) {
_len = str._len;
_init_data(str._data);
std::cout << "Copy Constructor is called! source: " << str._data << std::endl;
}
MyString(MyString&& str) {
std::cout << "Move Constructor is called! source: " << str._data << std::endl;
_len = str._len;
_data = str._data;
str._len = 0;
str._data = NULL;
}
和拷貝構造函數相似,有幾點須要注意:
a. 參數(右值)的符號必須是右值引用符號,即「&&」。
b. 參數(右值)不能夠是常量,由於咱們須要修改右值。
c. 參數(右值)的資源連接和標記必須修改。不然,右值的析構函數就會釋放資源。轉移到新對象的資源也就無效了。
轉移語義的目的:實現零時對象中申請的資源的複用。
4.c++的成員指針調用成員函數(非virtual函數,實現基類調用子類的函數)
方法1:非靜態map存儲子類的成員函數,並遍歷。原理相似於虛函數(可見:只要基類有子類成員函數的指針就能夠調用到子類的成員函數)
#include "stdafx.h"
#include <iostream>
#include <string>
#include <map>
using namespace std;
class Inr{
public:
virtual void display(void){;}
};
class Myview :public Inr{
public:
typedef void (Myview::*memfun)(void);
map<string,memfun> sav; //若是爲靜態將出現沒法鏈接的編譯錯誤;若是這裏map定義爲靜態的,則必須在類外申明一下,以表示初始化,方式爲map<string,memfun>Myview::sav;便可(複雜類必須在類外申明初始化,分配內存)
public:
void Myview::display(void){
map<string,memfun>::iterator itr=sav.begin();
for(;itr!=sav.end();itr++){
(this->*(itr->second))();
}
}
};
class Childview :public Myview{
public:
Childview(){
sav["disview"]=(memfun)disview;
}
public:
void disview(void){
cout<<"this is child view"<<endl;
}
};
int main(int argc, char* argv[])
{
Childview Am;
Am.display();
return 0;
}
方法2:靜態數組存儲子類的成員函數,並遍歷。
#include <OBJBASE.H>
using namespace std;
interface Base1{
public:
virtual void ExeFunc(void)=0;
};
typedef void (Base1::*Pfunc)(int);
typedef struct typefunclink{
Pfunc * PFmen;
typefunclink *MemLink;
}TypeFuncLink;
class Base2: public Base1{
public:
Base2(){;};
~Base2(){;};
static Pfunc Memfunc[];
static TypeFuncLink LkupList;
void B2Printf(int);
virtual void ExeFunc(void);
};
Pfunc Base2::Memfunc[2]={(Pfunc)(&B2Printf),0};
TypeFuncLink Base2::LkupList={Base2::Memfunc,0};
void Base2::B2Printf(int){
cout<<"This Base2 Class Print"<<endl;
}
void Base2::ExeFunc(void)
{
TypeFuncLink LkLink=Base2::LkupList;
while(LkLink.PFmen||LkLink.MemLink)
{int i=0;
while(LkLink.PFmen[i])
{
Pfunc am=LkLink.PFmen[i];
(this->*am)(0);
i++;
}
if(LkLink.MemLink)
LkLink=*(LkLink.MemLink);
else
break;
}
}
class Base3: public Base2{
public:
Base3(){;};
~Base3(){;};
static Pfunc Memfunc[];
static TypeFuncLink LkupList;
void B3Printf(int);
virtual void ExeFunc(void);
};
Pfunc Base3::Memfunc[2]={(Pfunc)(&B3Printf),0};
TypeFuncLink Base3::LkupList={Base3::Memfunc,&Base2::LkupList};
void Base3::B3Printf(int){
cout<<"This Base3 Class Print"<<endl;
}
void Base3::ExeFunc(void)
{
TypeFuncLink LkLink=Base3::LkupList;
while(LkLink.PFmen||LkLink.MemLink)
{int i=0;
while(LkLink.PFmen[i])
{
Pfunc am=LkLink.PFmen[i];
(this->*am)(0);
i++;
}
if(LkLink.MemLink)
LkLink=*(LkLink.MemLink);
else
break;
}
}
void main(void)
{
Base3 cl;
cl.ExeFunc();
system("pause");
}
5.線程中join和detach的做用
不管在windows中,仍是Posix中,主線程和子線程的默認關係是:不管子線程執行完畢與否,一旦主線程執行完畢退出,全部子線程執行都會終止。這時整個進程結束或僵死(部分線程保持一種
終止執行但還未銷燬的狀態,而進程必須在其全部線程銷燬後銷燬,這時進程處於僵死狀態),須要強調的是,線程函數執行完畢退出,或以其餘很是方式終止,線程進入終止態,但千萬要記住的是,進入終止態後,爲線程分配的系統資源並不必定已經釋放,並且可能在系統重啓以前,一直都不能釋放。終止態的線程,仍舊做爲一個線程實體存在於操做系統中。(這點在win和unix中是一致的。)而何時銷燬線程,取決於線程屬性。一般,這種終止方式並不是咱們所指望的結果,並且一個潛在的問題是未執行完就終止的子線程,除了做爲線程實體佔用系統資源以外,其線程函數所擁有的資源(申請的動態內存,打開的文件,打開的網絡端口等)也不必定能釋放。因此,針對這個問題,主線程和子線程之間一般定義兩種關係:
(1)可會合(joinable)。這種關係下,主線程須要明確執行join等待操做。在子線程結束後,主線程的等待操做執行完畢,子線程和主線程會合。這時主線程繼續執行等待操做以後的下一步操做。主線程必須會合可會合的子線程,Thread類中,這個操做經過在主線程的線程函數內部調用子線程對象的wait()函數實現。這也就是上面加上三個wait()調用後顯示正確的緣由。必須強調的是,即便子線程可以在主線程以前執行完畢,進入終止態,也必需顯示執行會合操做,不然,系統永遠不會主動銷燬線程,分配給該線程的系統資源(線程id或句柄,線程管理相關的系統資源)也永遠不會釋放。
(2)相分離(detached)。顧名思義,這表示子線程無需和主線程會合,也就是相分離的。這種狀況下,子線程一旦進入終止態,系統當即銷燬線程,回收資源。無需在主線程內調用wait()實現會合。Thread類中,調用detach()使線程進入detached狀態。這種方式經常使用在線程數較多的狀況,有時讓主線程逐個等待子線程結束,或者讓主線程安排每一個子線程結束的等待順序,是很困難或者不可能的。因此在併發子線程較多的狀況下,這種方式也會常用。缺省狀況下,建立的線程都是可會合的。可會合的線程能夠經過調用detach()方法變成相分離的線程。但反向則不行。
6.運算符重載
運算符重載的實質是函數重載,它提供了C++的可擴展性,也是C++最吸引人的特性之一。運算符重載是經過建立運算符函數實現的,運算符函數定義了重載的運算符將要進行的操做。運算符函數的定義與其餘函數的定義相似,唯一的區別是運算符函數的函數名是由關鍵字operator和其後要重載的運算符符號構成的。運算符函數定義的通常格式以下:
<返回類型說明符> operator <運算符符號>(<參數表>)
{
<函數體>
}
規則:
(1)不可重載的5個運算符:類屬關係運算符"."、成員指針運算符".*"、做用域運算符"::"、三目運算符"?:" 和sizeof運算符
(2)重載運算符限制在C++語言中已有的運算符範圍內的容許重載的運算符之中,不能建立新的運算符
(3)重載以後的運算符不能改變運算符的優先級和結合性,也不能改變運算符操做數的個數及語法結構。
(4)...
在進行對象之間的運算時,程序會調用與運算符相對應的函數進行處理,因此運算符重載有兩種方式:成員函數重載方式和友元函數重載方式。成員函數的形式比較簡單,就是在類裏面定義了一個與操做符相關的函數。友元函數由於沒有this指針,因此形參會多一個。
class A
{
public:
A(int d):data(d){}
A operator+(A&);//成員函數
A operator-(A&);
A operator*(A&);
A operator/(A&);
A operator%(A&);
friend A operator+(A&,A&);//友元函數
friend A operator-(A&,A&);
friend A operator*(A&,A&);
friend A operator/(A&,A&);
friend A operator%(A&,A&);
bool operator == (const A& );
bool operator != (const A& );
bool operator < (const A& );
bool operator <= (const A& );
bool operator > (const A& );
bool operator >= (const A& );
bool operator || (const A& );
bool operator && (const A& );
bool operator ! ();
A& operator + (); //這裏的+、-是正負的意思,放在對象前面。
A& operator - ();
A* operator & ();
A& operator * (); //取對象運算
A& operator ++ ();//前置++
A operator ++ (int);//後置++ ,這個int是沒有意義的,僅僅與前置作區分
A& operator --();//前置--
A operator -- (int);//後置-- ,返回不能是「引用」,由於返回的是一個右值對象
A& operator ++ ();//前置++ //++和–根據位置的不一樣有四種狀況,均可以重載。
A operator ++ (int);//後置++
A& operator --();//前置--
A operator -- (int);//後置--
A operator | (const A& );
A operator & (const A& );
A operator ^ (const A& );
A operator << (int i);
A operator >> (int i);
A operator ~ ();
A& operator += (const A& );
A& operator -= (const A& );
A& operator *= (const A& );
A& operator /= (const A& );
A& operator %= (const A& );
A& operator &= (const A& );
A& operator |= (const A& );
A& operator ^= (const A& );
A& operator <<= (int i);
A& operator >>= (int i);
void *operator new(size_t size);
void *operator new(size_t size, int i);
void *operator new[](size_t size);
void operator delete(void*p);
void operator delete(void*p, int i, int j);
void operator delete [](void* p);
A& operator = (const A& );
char operator [] (int i);//返回值不能做爲左值
const char* operator () ();
T operator -> ();
//類型轉換符
operator char* () const;
operator int ();
operator const char () const;
operator short int () const;
operator long long () const;
friend inline ostream &operator << (ostream&, A&);//輸出流 這些只能以友元函數的形式重載
friend inline istream &operator >> (istream&, A&);//輸入流
private:
int data;
};
A& A::operator++( ) //前置
{
data++;
return *this;
}
A A::operator++( int ) //後置
{ A temp(*this);
data++;
return temp;
}
//成員函數的形式
A A::operator+(A &a)
{
return A(data+a.data);
}
A A::operator-(A &a)
{
return A(data-a.data);
}
A A::operator*(A &a)
{
return A(data*a.data);
}
A A::operator/(A &a)
{
return A(data/a.data);
}
A A::operator%(A &a)
{
return A(data%a.data);
}
//友元函數的形式
A operator+(A &a1,A &a2)
{
return A(a1.data+a2.data);
}
A operator-(A &a1,A &a2)
{
return A(a1.data-a2.data);
}
A operator*(A &a1,A &a2)
{
return A(a1.data*a2.data);
}
A operator/(A &a1,A &a2)
{
return A(a1.data/a2.data);
}
A operator%(A &a1,A &a2)
{
return A(a1.data%a2.data);
}
運算符函數的特色:
(1)通常都會有返回值
(2)入參通常爲儘可能使用「引用」
(3)儘可能返回*this對象,而不要構造一個對象返回
(4)返回值是「引用」的,通常都返回*this對象
(5)第一個操做數是stream的,只能以友元的形式重載
6.c11多線程與異步任務
(1)線程建立
c11的線程建立比任何一種語言都要簡單.
void foo(int x,int y)
{
// x = 4, y = 5.
}
void main()
{
std::thread t(foo,4,5); //foo能夠是匿名函數
t.join(); //t.detach();
}
(2)異常簡單的mutex和recursive_mutex
std::mutex m; //std::recursive_mutex
int j = 0;
void foo()
{
m.lock(); // 進入臨界區域
j++;
m.unlock(); // 離開
}
void main()
{
std::thread t1(foo);
std::thread t2(foo);
t1.join();
t2.join();
// j = 2;
}
(3)異常簡單的線程本地存儲(線程退出時,不改變該變量的值)
thread_local int j = 0;
void foo()
{
m.lock();
j++; // j is now 1, no matter the thread. j is local to this thread.
m.unlock();
}
void main()
{
j = 0;
std::thread t1(foo);
std::thread t2(foo);
t1.join();
t2.join();
// j still 0. The other "j"s were local to the threads
}
(4)異常簡單的條件變量
std::condition_variable c;
// 咱們使用mutex而不是recursive_mutex是由於該鎖須要一次性獲取和釋放
std::mutex mu;
void foo5()
{
std::unique_lock lock(mu);
c.notify_one();
}
void main()
{
std::unique_lock lock(mu);
std::thread t1(foo5);
c.wait(lock);
t1.join();
}
5.異常簡單的future實現異步任務
int GetMyAnswer()
{
return 10;
}
int main()
{
std::future<int> GetAnAnswer = std::async(GetMyAnswer); // GetMyAnswer starts background execution
int answer = GetAnAnswer.get(); // answer = 10;
// If GetMyAnswer has finished, this call returns immediately.
// If not, it waits for the thread to finish.
}
你也可使用 std::promise。該對象能夠提供一些 std::future之後需求的特性。若是在任何東西放入承諾(promise)以前你調用 std::future::get(),將會致使等待,直到承諾值(promised value)出現。若是 std::promise::set_exception()被調用, std::future::get()則會拋出異常。若是 std::promise銷燬了,而你調用 std::future::get(),你將會產生 broken_promise 異常。
例子:
std::promise<int> sex;
void foo()
{
// 在下面的調用以後,future::get()將會返回該值
sex.set_value(1); // After this call, future::get() will return this value.
// 調用以後,future::get()將會拋出這個異常
sex.set_exception(std::make_exception_ptr(std::runtime_error("TEST")));
}
int main()
{
future<int> makesex = sex.get_future();
std::thread t(foo);
// do stuff
try
{
makesex.get();
hurray();
}
catch(...)
{
// She dumped us :(
}
}
6.bind綁定成員函數和非成員函數
#include < functional>
(1)綁定非成員函數
int Func(int x, int y);
auto bf1 = std::bind(Func, 10, std::placeholders::_1);
bf1(20); ///< same as Func(10, 20)
(2)綁定成員函數(bind成員函數的第一個入參必須是對象自己)
class A
{
public:
int Func(int x, int y);
};
A a;
auto bf2 = std::bind(&A::Func, a, std::placeholders::_1, std::placeholders::_2);
bf2(10, 20); ///< same as a.Func(10, 20)
std::function< int(int)> bf3 = std::bind(&A::Func, a, std::placeholders::_1, 100);
bf3(10); ///< same as a.Func(10, 100)
C程序存儲分紅4段:
a.代碼段:存儲每個函數包括main函數的二進制代碼
b.數據段:分3種
b.a只讀數據段:存儲只讀全局變量和只讀局部變量,以及全部常量
b.b.已初始化讀寫數據段:存儲已經初始化的全局變量和局部靜態變量
b.c.未初始化讀寫數據段:運行時纔會產生該段,存儲未通過初始化的數據,該段不是目標文件的一部分
c.棧:存儲程序運行時的函數內部使用的變量、函數的參數以及返回值
d.堆:存儲程序運行時malloc分配的空間