C++ 語言常見重要結論。

c++對於c語言的加強

namespace 命名空間

沒啥可以講的,避免了類名字,方法,成員變量的衝突。

實用性增強

變量可以邊定義邊用。

變量檢測增強

int a;
int a=8;會報錯了,不像c 不會報錯。

struct 類型增強

c++的struct 類似於class ,唯一的不同是class定義的函數默認是private 的,但是struct 定義的函數是public 的。而且,c不認爲這是一種新類型,仍然需要struct AAA,進行定義,但是c++則不需要,直接使用AAA 進行定義對象。即認爲是一種新的類型。

C++中所有變量和函數都必須有類型

如下代碼在c語言是OK的,但是在c++就不行咯。

f(i) {
printf("i = %d\n", i); }
g() {
return 5; }

新增bool 類型,返回值爲true 和false

三目運算符增強

返回的是引用,而不是值,所以可以對其進行賦值。

const 增強。

可以分清輸入與輸出的參數。帶const往往是輸入。在c語言中,const 是冒牌貨。
c++的const由編譯器處理,提供類型檢查與作用於檢查。
#define由預處理器處理,單純的文本替換。
c++的const 可能分配空間,也可能不分配空間。
當const 常量是全局的,並且需要在其他文件使用。會分配空間。
當使用&操作符。取const 地址,會分配空間。
const int &a=10; const 修飾引用,也會分配空間。

引用。

Type& name = var;
引用的規則。
1.引用沒有定義,是一種關係聲明,聲明和他原有的某一個變量關係,故不分配 內存,與被引用變量有着同樣的指向地址。
2.聲明的時候,必須得初始化,一經聲明,不能改變。
引用的本質

#include <iostream>
int main() {
int a = 10;
int &b = a; // 注意: 單獨定義的引⽤用時,必須初始化。
b = 11;
printf("a:%d\n", a); 
printf("b:%d\n", b); 
printf("&a:%p\n", &a); 
printf("&b:%p\n", &b);
return 0; }

引用在c++中的內部實現是一個常指針,即一個指向不能改變的指針。
Type& name <===> Type* const name
c++在編譯過程中。使用常指針作爲內部實現。因此,引用所佔用的空間與指針大小相同。
從使用的角度,引用會讓人誤會其只是一個別名,沒有自己的存儲空間。這個是c++爲了實用性,而做出的細節隱藏。

void func(int &a)
{
a = 5; }
void func(int *const a)
{
*a = 5; }

間接賦值的3各必要條件
1定義兩個變量 (一個實參一個形參)
2建立關聯 實參取地址傳給形參
3*p形參去間接的修改實參的值.
引用的實現上。只不過是把,間接賦值成立的三個條件的後面2個合二爲一了。當實參傳給形參引用的時候,只不過是c++編譯器幫我們程序員手工。取了一個實參的地址,傳給了一個常引用。(常量指針)

引用作爲函數的返回值(引用當左值)
當函數返回值爲引用時候。若返回棧變量,不能成爲其他引用的初始值。(不能作爲左值引用。)
當函數返回值爲引用時,如果返回靜態變量或全局變量,可以成爲其他引用的初始值。可以作爲右值,也可以成爲左值。

什麼是引用當左值?

int& fun(){
    int a=10;
    return a;
}
fun()=10;
//如下的只是值拷貝。
int x=fun();

指針引用

const 引用

const對象的引用必須是const的,將普通引用綁定到const 對象是不合法的。如下。

const int a=1;
int &b=a;
//常引用在初始化其實變成了2個步驟
int temp = val;
const int &ref = temp;
這也是爲什麼
int &a=1;//是錯誤的
const int &a=1;//卻是正確的的原因。

結論:
1.const int &a;相當於const int * const a;
2.普通引用相當於int * const e;
3.當使用const對應用初始化的時候,c++會爲常量值分配空間,並將引用名作爲這段空間的別名。
4.使用字面量對const引用初始化後,將生成一個只讀的變量。

inline 函數

可以想是宏函數的拓展,內聯的特點。
1.沒有函數調用的開銷。(壓棧,跳轉,返回)
2.內聯函數由編譯器處理,直接將編譯後的代碼插入調用的地方。宏片段由預處理器處理,進行簡單的文本替換,沒有任何的編譯過程。
同時存在着一定的限制。

  • 不能存在任何形式的循環語句
  • 不能存在過多的條件判斷語句
  • 函數體不能過於龐大
  • 不能對函數進行取址操作
  • 函數內聯聲明必須在調用語句之前

當函數體執行的開銷遠遠大與壓棧,跳轉,返回的開銷,內聯將失去意義

函數重載

C++利用 name mangling(傾軋)技術,來改名函數名,區分參數不同的同 名函數。
實現原理:用 v c i f l d 表示 void char int float long double 及其引 用。
每一個方法都有一個特殊的Symbol 符號。

拷貝構造函數調用的場景

場景1

Test t1(10);
Test t2=t1;

場景2

Test t1(10);
Test t2(t1);//使用對象t1初始化對象t2

1.函數返回值是一個元素(複雜類型),返回的是一個新的匿名對象(所以會調用匿名對象的copy構造函數)

2.有關匿名對象的去和留
如果匿名對象去初始化另外一個同類型的對象,匿名對象被扶正
如果用匿名對象去賦值給另外一個同類型的對象,匿名對象被析構

new delete

在這裏插入圖片描述
注意,new 與delete 返回的都是指針類型。是一塊內存地址。不能夠用普通對象直接接收,與malloc delete 有着同樣的功能,唯一的不同是,malloc free在初始化類對象的時候,是不會自動的幫我們調用構造函數,但是new 與delete 是會自動的幫我們調用。

靜態成員的定義

static 數據類型 成員變量; //在類的內部
//初始化
數據類型 類名::靜態數據成員 = 初值; //在類的外部
//調⽤用 類名::靜態數據成員 類對象.靜態數據成員

編譯器對屬性和方法的處理機制。

普通成員變量,存儲於對象中,與struct變量有着相同的內存佈局和字節對齊方式。
靜態成員變量,存儲於全局數據區
成員函數,存儲於代碼區

很多對象共用一塊代碼,代碼如何區分具體對象是那?
如下的圖應該算是很經典的一張圖了。
在這裏插入圖片描述

c++的普通成員函數都隱含一個包含當前對象的this指針
靜態成員函數不包含指向具體對象的指針。普通成員函數含有一個指向具體對象的指針。

友元

友元聲明以關鍵字friend開始,只能在類定義中出現,因爲不是授權類的成員,所以他不受類聲明的public private 和protected的影響
利弊:可以直接訪問類中的私有成員,破壞了類的封裝性和隱蔽性。但是提高了編碼的靈活性。
注意事項
友元關係無法繼承
友元關係無法交換
友元關係不具有傳遞性

運算符重載

在這裏插入圖片描述

在這裏插入圖片描述

執行重載的時候,記得對參數加const

雙目運算符重載

//使⽤用: L#R operator#(L,R); 
//全局函數 L.operator#(R); //成員函數
Complex& operator+=(const Complex &c) {
	this->_x += c._x; this->_y += c._y;
    return * this;
}

重載規則請務必記住

//重載+號 (全局)
friend const Complex operator+(const Complex &c1,const Complex &c2);
//重載+號(成員)
const Complex operator+(const Complex &another);
//a+=b;對於a應該返回的還是原來的a 所以不能爲const 
Complex& operator+=(const Complex &c)
//a-=b;同樣的是返回可以修改的內容。
friend Complex& operator-=(Complex &c1, const Complex & c2);
//前置++ 注意返回的是引用
friend Complex & operator++(Complex& c);
friend const Complex operator++(Complex &c,int);
friend ostream & operator<<(ostream &os, const Complex & c);
friend istream & operator>>(istream &is, Complex &c);

//賦值運算符重載 返回的是引用,不能用const 修飾,其目的是連等式
A& operator=(const A& another)
//數組符號的重載
類型 類 :: operator[] ( 類型 ) ;

結論:
1,一個操作符的左右操作數不一定是相同類型的對象,這就涉及到將該操作符函 數定義爲誰的友元,誰的成員問題。
2,一個操作符函數,被聲明爲哪個類的成員,取決於該函數的調用對象(通常是左 操作數)。
3,一個操作符函數,被聲明爲哪個類的友員,取決於該函數的參數對象(通常是右 操作數)。

重載&& 和||會丟失短路特性。

無參構造函數調用不能帶括號!

類的繼承方法

class 派⽣生類名:[繼承⽅方式] 基類名 { 派⽣生類成員聲明;
};

在這裏插入圖片描述

private成員在子類中依然存在,但是卻無法訪問到。不論何種方式繼承 基類,派生類都不能直接使用基類的私有成員 。

三種繼承關係

保護繼承中,基類的公有成員和私有成員都以保護成員的身份出現在派生
類 中,而基類的私有成員不可訪問。

當類的繼承方式爲私有繼承時,基類中的公有成員和保護成員都以私有成
員身 份出現在派生類中,而基類的私有成員在派生類中不可訪問。

當類的繼承方式爲公有繼承時,基類的公有和保護成員的訪問屬性在派生
類中 不變,而基類私有成員不可訪問。

不論是什麼類型的繼承,在基類中不能使用的,只有私有成員,其餘就算因爲私有繼承而退化爲私有的,子類也是依然可以進行使用 的。

1.子類對象在創建時會先調用父類構造函數
2.父類構造函數執行結束後,執行子類構造函數
3.當父類構造函數有參數時,需要子類初始化列表中顯式調用
4.析構函數的調用順序與構造函數剛好相反。

先構造父類,再構造成員變量,最後再構造自己
先析構自己,再析構成員變量,最後析構父類

繼承總結

1、當子類成員變量與父類成員變量同名時
2、子類依然從父類繼承同名成員
3、在子類中通過作用域分辨符::進行同名成員區分(在派生類中使用基
類的同名成員,顯式地使用類名限定符)
4、同名成員存儲在內存中的不同位置

cout<<c.age<<endl;
cout<<c.Parent::age<<endl;
cout<<c.Child::age<<endl;

派生類中的static關鍵字

基類定義的靜態成員,將被所有派生類共享

根據靜態成員自身的訪問特性和派生類的繼承方式,在類層次體系中具 有不同的訪問性質 (遵守派生類的訪問控制)

派生類中訪問靜態成員,用以下形式顯式說明:
類名::成員、對象名 . 成員

靜態成員變量,無論使用父類名,還是子類名都可以直接調用得到。或者是實例,也可以調用。

cout<<c.count<<endl;
cout<<Parent::count;
Parent::count++;
cout<<Child::count;
Child::count++;
cout<<Object::count;
Object::count++;
cout<<Object::count;

多態

一般情況下,如果基類與子類有着同名的方法,即子類重寫了父類的方法。如果使用父類指針去承接子類對象,那麼調用被重寫的方法的時候,是執行父類的方法,而不是子類的方法。
在這裏插入圖片描述
如何實現,根據對象實際類型去打印?virtual 可以解決。通過使用virtual 修飾函數。可以視作是java中的abstract 抽象方法,不過這個virutal方法在是可以有方法體的。

virtual int fun()

多態成立的3個條件。
1.要有繼承
2.要有虛函數重寫
3.要有父類指針(父類引用)指向子類對象。

靜態聯編 動態聯編

靜態聯編,由於程序沒有運行,所以不可能知道父類指針指向的具體對象是父類對象還是子類對象,從程序安全的角度,編譯器假設父類指針只指向父類對象,因此編譯結果爲調用父類的成員函數。

多態是發生在動態聯編,是在程序執行的時候,判斷父類指針應該調用的方法。

虛析構函數

虛析構函數用於指引delete操作符正確析構動態對象。
在手動delete obj時候,如果是父類指針,沒有寫virutal 析構,那麼只會執行父類的析構函數代碼,而不會執行子類的析構函數。

重載 重寫 重定義

函數重載
必須在同一個類中進行,子類無法重載父類的函數,父類同名函數將被名稱覆蓋,重載是在編譯期間根據參數類型和個數決定函數調用

//以下都是重載
void a();
void a(int)
void a(char)
void a(int,char)

函數重寫

必鬚髮生在父類與子類之間 ,並且父類與子類必須有完全相同的原型
使用virtual聲明之後能夠產生多態(如果不使用virutal ,那就叫重定義)

virtual void fun();
-->
void fun();//多態如果沒有virutal 那就是重定義

虛函數表和Vptr指針

1.當類聲明虛函數時,編譯器會生成一個虛函數表
2.虛函數表是一個存儲類成員函數指針 的數據結構
3.虛函數表是 編譯器自動生成和維護的。
4.virtual 成員函數會被編譯進入虛函數表中。
5.存在虛函數的時候,每個對象中都有一個指向虛函數表的指(vptr指針。)

在這裏插入圖片描述

在這裏插入圖片描述

編譯器確定func 是不是虛函數
1)不是,編譯器可直接確定被調用的成員函數(靜態聯編,根據父類類型決定)

2) 是,編譯器根據對象的vptr指針,所指向虛函數表查詢到func 函數並且調用。注意,查找與調用是在運行的時候執行

由於虛函數是在運行的時候動態進行調用。效率低是一個問題。構造函數無法實現多態,父類構造的時候,vptr 指向父類的虛函數表,子類構造時候,指向子類。

base(){
	this->print()///調用的是父類的print方法
}
child(){
	this->print()//調用的是子類的print 方法
}

純虛函數

virtual 類型 函數名(參數表) = 0;類似於java 的抽象方法。主要特點是無方法體。子類繼承必須實現,否則子類也是抽象類’

1.含有純虛函數的類,稱爲抽象基類,不可實列化。即不能創建對象,存在 的意義 就是被繼承,提供族類的公共接口。
2,純虛函數只有聲明,沒有實現,被“初始化”爲 0。
3,如果一個類中聲明瞭純虛函數,而在派生類中沒有對該函數定義,則該虛函數在 派生類中仍然爲純虛函數,派生類仍然爲純虛基類。

class Triangle:public Shape{
protected:
    double w;
    double h;
public:
    Triangle(double w,double h):w(w),h(h){}
    virtual double getArea() {
        return w*h/2;
    };
};
class Rectangle:public Triangle{
public:
    Rectangle(double w,double h):Triangle(w,h){
    }
    double getArea(){
        //可以調用到父類方法,只不過需要加上作用範圍。
        return this->Triangle::getArea()*2;
    }
};

C 語言面向接口編程

c 語言通過函數指針實現面向接口編程

關於模板類使用友元重載<<的部分說明

如果單獨把代碼寫在h文件裏面,此時需要注意的細節會比較少,但是如果需要分文件,即使h cpp
如下只是重載了<<運算符。

friend ostream& operator<< <T>(ostream &o,Animal<T> t);

//此時,在h文件頭我們需要做如下聲明
template<class T>
class Animal;

template<class T>
ostream& operator<<(ostream &o,Animal<T> t);
//同時,我們需要在cpp文件裏面完善代碼。
template<class T>
ostream& operator<<(ostream &o,Animal<T> t){
    o<<t.t;
    return o;
}

//最後,在main裏面,別忘記了,要引入我們的cpp文件。

在這裏插入圖片描述
所以這也是爲什麼我們對於這些模板類的編寫,一般就用一個文件hpp,我們不把代碼分離開,因爲如果分開用戶在使用的時候,還需要手工引入hpp,多麻煩啊。所以乾脆寫在一個文件裏面算了。

一般性結論
friend ostream & operator<< <T> (ostream &os, Complex<T> &c); //在模板類中 如果有友元重載操作符<<或者>>需要 在 operator<< 和 參數列表之間 //加⼊入
//濫⽤用友元函數,本來可以當成員函數,卻要⽤用友元函數
//如果說是⾮非<< >> 在模板類中當友元函數
//在這個模板類 之前聲明這個函數
friend Complex<T> mySub <T>(Complex<T> &one, Complex<T> &another); //最終的結論, 模板類 不要輕易寫友元函數, 要寫的 就寫<< 和>> 。

傳說中的hpp在這裏插入圖片描述

類型轉換

C++風格的類型轉換提供了 4 種類型轉換操作符來應對不同場合的應用。
static_cast 靜態類型轉換。如 int 轉換成 char
reinterpreter_cast 重新解釋類型
dynamic_cast 命名上理解是動態類型轉換。如子類和父類之間的多態類型轉換。 const_cast, 字面上理解就是去 const 屬性。

由於二次編譯,模板類在.h在第一次編譯之後,並沒有最終確定類的具 體實現,只是編譯器的詞法校驗和分析。在第二次確定類的具體實現後,是 在.hpp文件生成的最後的具體類,所以main函數需要引入.hpp文件。
綜上:引入hpp文件一說也是曲線救國之計,所以實現模板方法建議在同 一個文件.h中完成

在這裏插入圖片描述

異常

捕捉萬能異常?


//設置未知異常處理回調函數
set_terminate(my_tm_h);

//typedef void (*terminate_handler)();
//編寫回調函數
void my_tm_h(){
	cout<<"error occure with clear reason!"<<endl;
}

在這裏插入圖片描述
在這裏插入圖片描述
統一的異常處理機制,將讓我們捕捉異常的時候,更加方便。能夠進行統一的控制操作。

關於標準輸入輸出流

標準輸入流對象cin

cin.get() //一次只能讀取一個字符
cin.get(一個參數) //讀一個字符
cin.get(三個參數) //可以讀字符串
cin.getline() //獲取緩衝區的一行
cin.ignore() //跳過緩衝區幾個字符
cin.peek() //查看緩衝區有沒有數據,阻塞
cin.putback() //塞進去緩衝區
ctrl-z 將產生一個EOF,mac 測試無效。

標準輸出流對象cout

控制符詳解

控制符,可以直接cout<<控制符,也就是追加

控制符 作用
endl 不做解釋
dec 設置數字基數爲10進制
hex 16進制
oct 8進制
setfill(’*’) 填充字符,一般與setw 配合使用,一次性用,endl作廢
setprecision(n) 設置浮點數字的輸出精度,多次有效
setw(12) 設置字段寬度,本次有效。僅僅數字不足才補足,一旦設置setprecision將優先填充到precision 的位數
setiosflags(ios::a|ios::b…) 可以設置標誌位
resetiosflags(ios::a|ios::b…) 重置io標誌

標誌位作用講解

標誌位 作用
ios::fixed 有效數字位數默認爲爲6位,可以通過setprecision來定製。
ios::scientific 設置浮點數以科學計數法表示
ios::left 左對齊,僅僅setw ,並且字符串長度超過有效 。
ios:right 右對齊
ios::skipws skipws是作用於流式輸入的,而非輸出。
cin默認是已經把skipws開啓了。 也就是說 a b c 讀入三個字符,不會吧空格讀進去。
ios::uppercase 只有16進制輸出才以大寫輸出
ios::lowercase 小寫輸出
ios::showpos 輸出正數給出“+”號
ios::internal 數值的符號位在域寬左對齊,數值右對齊,中間填充字符
ios:unitbuf 每次輸出後刷新所有的流
ios::stdio 每次輸出後清除stdout,stderr

流成員函數

如下方法屬於cout成員,使用cout.method進行調用即可。

函數 功能
precision(n) 設置精度
width(n) 設置字符寬度
fill© 設置填充字符
setf() 設置ios標誌
unsetf() 取消設置ios標誌

文件流

引入頭文件

#include <fstream>

文件的輸入輸出方式控制
在這裏插入圖片描述

STL

常見的容器有如下的幾種
vector list deque set map stack queue

string

如何寫一個標準的替換字符串函數

string str="jflkasfijfamsfkajsaf";
string find="j";
string rep="*";
int pos=0;
while((pos=str.find(find,pos))!=-1){
    str.replace(pos,find.length(),rep);
    pos+=rep.length();
}

vector

deque

與vector 不同在於,deque 可以push_front,即雙端操作。pop_front()可以取除第一個元素

stack

支持push 與pop ,通過top 獲取元素

queue

隊列,pop時候,只會把隊頭元素拋出。支持front 與end

list

不可隨機存儲讀寫,但是支持插入?支持push pop back front

總結一句話,能夠用下標獲取,其底層機構一定是數組,如果無法,但是可以通過不斷的++ ,那他一定是一條鏈路。
各個容器的使用正確時機。

簡單的總結

在這裏插入圖片描述
 deque的使用場景:比如排隊購票系統,對排隊者的存儲可以採用deque,支持頭 端的快速移除,尾端的快速添加。如果採用 vector,則頭端移除時,會移動大量的 數據,速度慢。

  • vector 與 deque 的比較:
  • 一:vector.at()比 deque.at()效率高,比如 vector.at(0)是固定的,deque 的開始位置卻
    是不固定的。
  • 二:如果有大量釋放操作的話,vector 花的時間更少,這跟二者的內部實現有關。
  • 三:deque 支持頭部的快速插入與快速移除,這是 deque 的優點。
  • list 的使用場景:比如公交車乘客的存儲,隨時可能有乘客下車,支持頻繁的不確實
    位置元素的移除插入。
  • set 的使用場景:比如對手機遊戲的個人得分記錄的存儲,存儲要求從高分到低分的
    順序排列。
  • map 的使用場景:比如按 ID 號存儲十萬個用戶,想要快速要通過 ID 查找對應的用
    戶。二叉樹的查找效率,這時就體現出來了。如果是 vector 容器,最壞的情況下可 能要遍歷完整個容器才能找到該用戶。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章