作者:李春港
出處:https://www.cnblogs.com/lcgbk/p/14643010.html
- 前言
- 一、C++常用后綴
- 二、頭檔案
- 1、C++輸入輸出
- 2、在C++中使用C的庫函式
- 三、 指標與動態記憶體分配
- 1、C
- 2、C++
- 四、命名空間
- 1、作用
- 2、定義
- 3、使用空間成員
- 4、命名空間嵌套
- 5、匿名空間
- 六、參考
- 1、參考特點:
- 2、參考的應用:
- 七、函式多載
- 八、函式預設引數(默認引數)
- 九、類與物件
- 1、類
- 2、物件
- 十、構造和解構式
- 1、建構式
- <1>建構式定義及多載
- <2>建構式引數串列初始化
- <3>拷貝建構式
- 2、解構式
- 1、建構式
- 十一、類的記憶體空間
- 十二、類繼承
- 1、繼承方式
- 2、繼承
- (1)繼承構造方法和析構方法的定義和呼叫
- (2)繼承后成員函式呼叫
- 3、多繼承
- 4、虛擬繼承
- 十三、虛函式、虛表
- 十四、純虛函式(抽象函式)、抽象類
- 十五、多型、虛析構
- (1)多型
- (2)虛析構
- 十六、友元
- 十七、友元類
- 十八、運算子多載
- 1、雙目運算子多載
- (1)+ -多載
- (2)輸出、輸入, 運算子多載
- 3、單目運算子多載
- (1)++A、A++多載
- (2)多載中括號[ ]
- 4、用運算子多載實作資料型別轉換
- (1)轉換建構式
- (2)用運算子多載實作資料型別轉換
- 1、雙目運算子多載
- 十九、模板函式
- 二十、模板類
- 1、模板類的定義
- 2、模板類友元多載輸出
- 3、模板類繼承
- 4、模板類中的靜態成員
- 二十一、強制型別轉換const_cast、static_cast、reinterpert_cast、dynamic_cast
- 1、const_cast把常量轉為變數
- 2、static_cast靜態轉化
- 3、reinterpret_cast強制型別轉換符
- 4、dynamic_cast類轉換
- 二十二、例外捕捉和處理
- (1)例外拋出和捕捉陳述句
- (2)例外的處理規則
- (3)實體
- (4)總結
- 二十三、STL標準模板庫
- 1、vector(順序表)
前言
這篇文章是對C++的知識點做了一些簡單的總結,基本包含了所有的C++基礎知識點,以下提到的知識點并非深入講解,只是大概講解了各個知識點的基本使用,如需要深入了解,可以針對某個知識點去深入學習,
一、C++常用后綴
cpp, .h, cc, cxx, hpp
二、頭檔案
1、C++輸入輸出
- 頭檔案#include
- 標準輸入(standard input)與預定義的 istream 物件 cin 對應
- 標準輸出(standard output) 與預定義的 ostream 物件 cout 對應
- 標準出錯(standard error)與預定義的的 ostream 物件 cerr 對應
例子:用c++寫一個簡單計算器
#include <iostream>
int main(void)
{
int a=0, b=0;
char c=0;
std::cout<<"please input type: a+b" <<std::endl;
std::cin>>a>>c>>b;
switch(c)
{
case '+':
std::cout<<a<<c<<b<<"="<<a+b<<std::endl; break;
case '-':
std::cout<<a<<c<<b<<"="<<a-b<<std::endl; break;
case '*':
std::cout<<a<<c<<b<<"="<<a*b<<std::endl; break;
case '/':
std::cout<<a<<c<<b<<"="<<a/b<<std::endl; break;
}
return 0;
}
2、在C++中使用C的庫函式
extern "C"
{
#include <stdlib.h>
#include <string.h>
}
三、 指標與動態記憶體分配
靜態記憶體分配(全域變數, 區域變數), 動態記憶體分配(在 c 中用 malloc 分配的堆空間 free 來釋放)c++中用 new 分配堆空間 delete 釋放,
1、C
char *name =(char*) malloc(100);
free(name);
2、C++
- 整形數: int *p = new int(10) ; 分配空間并且初始化為 10 釋放 delete p
- 整形陣列:int *arr = new int[10] ; 分配十個連續整數空間 釋放 delete []arr
- 字符型:char *p = new char('a'); 釋放 delete p;
- 字串:char *arr = new char[100];分配 100 個字符空間 釋放 delete []arr;
四、命名空間
為了確保程式中的全域物體的名字不會與某些庫中宣告的全域物體名沖突,引入了命名空間,
1、作用
- 避免名稱沖突;
- 模塊化應用程式;
- 匿名的命名空間可避免產生全域靜態變數,創建的 “匿名” 命名空間只能在創建它的檔案中訪問,
2、定義
除main函式外所有函式, 變數, 型別,
namespace 空間名{
函式,變數, 型別
}
例子:
namespace class01
{
std::string name="jack";
int age=19;
int number = 123;
}
3、使用空間成員
1、 直接通過空間名::成員名 --標準使用--提倡使用的方法
class01::name -- ::所屬符號
2、using指示符指引
using namespace calss01; // 把class01空間了里面的內容暴露在當前位置,當檔案有變數與命名空間的成員一樣時,則后期使用該成員或變數時,程式運行時會報錯;但能編譯通過
3、 using宣告
using class01::number;
// 當檔案也有number變數時,則編譯的時候就報錯,相當于定義了兩次該變數,
所屬符號::

4、命名空間嵌套
namespace AAA{
namespace BBB
{
int number=0;
}
}
使用:
- AAA::BBB::number;
- using namespace AAA::BBB; number;
- using AAA::BBB::number;
5、匿名空間
相當于全域變數直接使用(只能在本文中使用)
static,
定義:
namespace {
int data;
}
匿名空間與static的異同:
static 無法修飾自定義型別;static 產生當前 符號只在當前源檔案有效,是因為它修改了符號的Bind屬性,使之變為區域的;而匿名空間雖然可以產生相同效果,但是符號還是具有外部鏈接屬性,匿名命名空間內的變數與函式,只在當前源檔案內有效;不同源檔案的匿名命名空間,可以存在同名符合,static需要在每個變數加上
六、參考
參考:就是某一變數(目標)的一個別名,對參考的操作與對變數直接操作完全一樣,
參考的宣告方法:型別識別符號 &參考名=目標變數名;(別名)
int a = 10;
int &ra = a; (ra 就是 a 的參考 ,也稱 a 的別名)
1、參考特點:
- &在此不是求地址運算子,而是起標識作用,
- 型別識別符號是指目標變數的型別,
- 宣告參考時,必須同時對其進行初始化,
- 參考宣告完畢后,相當于目標變數有兩個名稱即該目標原名稱和參考名,且不能再把該參考名作為其他變數名的別名,
- 宣告一個參考,不是新定義了一個變數,它只表示該參考名是目標變數名的一個別名,它本身不是一種資料型別,因此參考本身不占存盤單元,系統也不給參考分配存盤單元,但參考本身是有大小的,一個指標的大小,在64位系統:sizeof(&ra) = 8,32位為4位元組,sizeof(ra)=sizeof(a)被參考物件的大小,故:對參考求地址,就是對目標變數求地址, &ra 與&a 相等,
- 不能建立陣列的參考,因為陣列是一個由若干個元素所組成的集合,所以無法建立一個
2、參考的應用:
1.參考作為引數
參考的一個重要作用就是作為函式的引數,以前的 C 語言中函式引數傳遞是值傳遞,如果有大塊資料作為引數傳遞的時候,采用的方案往往是指標,因為這樣可以避免將整塊資料全部壓堆疊,可以提高程式的效率,但是現在(C++中)又增加了一種同樣有效率的選擇
2.常參考
常參考宣告方式:const 型別識別符號 &參考名 = 目標變數名;
用這種方式宣告的參考,不能通過參考對目標變數的值進行修改,從而使參考的目標成為 const,達到了參考的安全性,
3.參考作為函式回傳值
要以參考回傳函式值,則函式定義時要按以下格式:
型別識別符號 &函式名 (形參串列及型別說明){ 函式體 }
特點:
- 以參考回傳函式值,定義函式時需要在函式名前加&
- 用參考回傳一個函式值的最大好處是,在記憶體中不產生被回傳值的副本,
- 不能回傳區域變數的參考
七、函式多載
函式多載只能在同一個類中
int open();
int open(const char* filename);
int open(const char *filename , int flag);
c++中編譯程式的是檢測函式,通過函式名和引數串列
如果在一個檔案中出現同名的函式但引數串列不同,那么這些函式屬于多載
函式多載的依據:
-
函式名相同,
-
引數串列不同,(個數, 型別) add(int, doube) add(double, int)
-
如果引數是指標, 或參考,那么const修飾也可以作為多載依據
-
函式的回傳不能作為函式多載的依據
-
引數是否為默認引數不能作為函式多載的依據
八、函式預設引數(默認引數)
int open(const char *filename, int flag=10)
int open(const char *filename="c++", int flag=10)
int open(const char *filename="c++", int flag) 錯誤
注意: 若給某一引數設定了默認值,那么在引數表中其后(也就是右邊)所有的引數都必須也設定默認值
九、類與物件
1、類
(1)定義:
class 類名{
類的特征(屬性) 成員變數
類的行為(功能) 成員方法, 函式
};
注意:當類里面的成員引數函式有默認值時,若需要在外部定義該函式時,不能寫默認值,默認值只能在類里面宣告的時候寫默認值,
例子:
class Tdate
{
public:
void Set(int m, int d, int y )
{
month = m ;
day = d ;
year = y ;
}
private:
int month;
int day;
int year;
};
struct和class在C++都能定義類,其區別:
- struct作為資料結構的實作體,它默認的資料訪問控制是public的,而class作為物件的實作體,它默認的成員變數訪問控制是private的,
- 默認的繼承訪問權限,struct是public的,class是private的,
(2)類成員權限控制
- public 公有 公有段的成員是提供給外部的介面,在外部可以訪問
- protected 保護 保護段成員在該類和它的派生類中可見,在類外不能訪問(也就是在外部創建物件時不能訪問)
- private 私有 私有段成員僅在類中可見(友元函式或者友元類可以訪問),在類外不能訪問(也就是在外部創建物件時不能訪問)
(3)類內/外訪問
- 類內訪問:在類的成員函式中訪問成員(沒有任何限制)
- 類外訪問: 在類的外部通過類的物件訪問類的成員
2、物件
定義與成員訪問:
class Tdate
{
public:
int num;
void set(int m, int d, int y )
{
month = m ;
day = d ;
year = y ;
}
private:
int month;
int day;
int year;
};
//定義物件
Tdate A;
Tdate *B = new Tdate( );
//物件成員訪問
A.set(1,1,1);
A.num = 2;
B->set(1,1,1);
B->num = 2;
十、構造和解構式
1、建構式
建構式是成員函式,函式名與類名相同,函式沒有回傳值, 函式不需要用戶呼叫,在創建物件的時候自動呼叫,
(1)如果創建一個類你沒有寫任何建構式,則系統會自動生成默認的無參建構式,函式為空,什么都不做,
(2)只要你寫了一個下面的某一種建構式,系統就不會再自動生成這樣一個默認的建構式,如果希望有一個這樣的無參建構式,則需要自己顯示地寫出來
(3)引數串列初始化:只有建構式才有引數串列初始化,若要在類內宣告,類外定義建構式,且使用引數串列初始化引數時,則在類內宣告的時候不允許引數串列初始化,只能類外定義的時候進行引數串列初始化
(4)函式默認引數:無論是成員函式還是建構式,若需要類內宣告和類外定義的時候,默認引數值在宣告或者定義的時候都可賦值,但宣告和定義的引數不能有默認值
<1>建構式定義及多載
class Complex
{
public:
Complex()//建構式定義
{
m_real = 0.0;
m_imag = 0.0;
}
Complex(int a,int b)//多載建構式定義
{
m_real = a;
m_imag = b;
}
private :
double m_real;
double m_imag;
};
//創建物件
Complex A;//這個時候不能有()
Complex A(1,1);
Complex *A = new Complex( );//可以有()也可以沒有
Complex *A = new Complex(1,1);
<2>建構式引數串列初始化
Student(string n="name", int num=1000)
:name(n),number(num){
//name = n;
//number = num;
}
注意:
* name、number:是本類里面的成員;
* n、num:是對成員賦的值或者變數;
* 不能在類里面宣告的時候用引數列初始化,宣告的時候可以加默認值;
對物件成員進行串列初始化:
class A
{
public:
A(int a,int b){}
}
class B:public A
{
A a;
public:
B(int c,int d ):a(c,d){}
}
使用原因及用處:
- 建構式是成員函式,必須創建物件后才能呼叫
- 引數串列初始化是在申請空間的同時就初始化
- 如果成員是const修飾的成員、參考成員、繼承時候呼叫父類建構式,這幾種情況就必須用引數串列初始化,
<3>拷貝建構式
(1)如果沒有自定義拷貝建構式,系統會默認生成一個拷貝建構式(淺拷貝建構式,不會拷貝堆空間)
Student Jack; //建構式
Student Rose = Jack; //拷貝建構式
Student Tom (Jack); //拷貝建構式
后面兩種拷貝建構式不會再次呼叫建構式
(2)深拷貝建構式
class Student
{
public:
Student(int age, const char *n){
this->age = age;
this->name = new char[32];
strcpy(this->name, n);
cout<<"Student()"<<endl;
}//this指標就是函式呼叫者
~Student(){
delete []this->name;
cout<<"~Student()"<<endl;
}
//深拷貝建構式
Student(Student& s)
{
cout<<"Student(Student&)"<<endl;
this->age = s.age;
this->name = new char[32];
strcpy(this->name, s.name);
//this->name = s.name;
}
private:
int age;
char *name;
};
2、解構式
- 函式名有類一樣在函式名前面添加~符號
- 解構式沒有回傳值, 也沒有引數
- 解構式在物件銷毀的時候自動呼叫(如果new一個物件,建構式會自動執行,只有在delete的時候才呼叫解構式)
例子:
class Complex
{
public:
Complex( )//建構式定義
{
cout << "complex" << endl;
}
~ Complex( )//解構式定義
{
cout << "~complex" << endl;
}
};
int main( )
{
Complex a;
Complex *p = new Complex();
delete p;
return 0;
}
結果:
complex
complex
~complex
~complex
十一、類的記憶體空間
類本身是一個資料型別,在沒有定義物件前是不占用記憶體空間的,定義物件的時候才會分配空間,
- 計算一個類的物件占用多少空間用sizeof(類名或物件)
- 類的物件大小是其資料成員(非靜態資料段),和虛函式表指標(一個類里最多只能有兩個指標,一個是虛函式的指標,一個是虛繼承的指標)大小和,普通方法(普通函式)不占用記憶體,但用virtual修飾的虛函式占用一個指標大小的記憶體,注:一個指標的大小、記憶體的對齊方式和編譯器有關;64位的話,大小為8;32位的話,大小為4,
- 如果一個類中沒有資料成員,也沒有虛表那么這個類的大小規定為 1 個位元組,
十二、類繼承
繼承:
- 新的類從已知的類中得到已有的特征的程序
- 新類叫派生類/子類
- 已知的類叫基類/父類
- 如果直接將派生類的物件賦值給基類物件,派生類自身的成員就會被丟棄,只保留基類繼承來的成員,
- 將基類指標指向派生類物件是安全的,因為派生類物件“是”它的基類的物件,但是要注意的是,這個指標只能用來呼叫基類的成員函式,
作用:
繼承可以減少重復的代碼,比如父類已經提供的方法,子類可以直接使用,不必再去實作,
類的繼承格式:
class 子類名 :繼承方式 父類
{
子類成員
};
例如:
class Base
{
public:
Base() {}
int b;
};
class Child: public Base
{
};
1、繼承方式
繼承方式: 公有繼承, 保護繼承, 私有繼承
- 公有繼承(public):繼承時保持基類中各成員屬性不變,并且基類中的private成員被隱藏,派生類的成員只能訪問基類中的public/protected成員,而不能訪問private成員;派生類的物件只能訪問基類中的public成員,
- 保護繼承(protected):繼承時基類中各成員屬性均變為protected,并且基類中的private成員被隱藏,派生類的成員只能訪問基類中的public/protected成員,而不能訪問private成員;派生類的物件不能訪問基類中的任何的成員,
- 私有繼承(private):繼承時基類中各成員屬性均變為private,并且基類中private成員被隱藏,派生類的成員也只能訪問基類中的public/protected成員,而不能訪問private成員;派生類的物件不能訪問基類中的任何的成員,
注意1: 私有繼承在下一次被繼承時,所有從父類繼承而來的都會別隱藏,而保護繼承在下次被繼承時根據繼承的屬性其中的資料可能被從新利用,所以私有繼承的保護性更加強,
注意2: 無論那種繼承子類的大小為子類+父類(所有成員都要加起來,包括私有成員)
2、繼承
(1)繼承構造方法和析構方法的定義和呼叫
因為有父類才有子類,所以呼叫順序如下:
建構式的呼叫順序父類建構式—物件成員建構式—子類建構式,
解構式則相反,
注意:
- 當派生類的建構式為B(){cout << "Afather\n";}時,創建一個派生類默認會呼叫沒有引數的父類建構式A(),
- 如果父類建構式帶無默認值引數,派生類建構式怎么寫?
如下:
例子一:
父類建構式
public:
Person(string name, string sex, int age):name(name),sex(sex),age(age) {
cout<<"Person()"<<endl;
}
子類建構式
public:
Student( ):Person("jack","man",19){
cout<<"Student()"<<endl;
} //==>Person();
例子二:
class Animal
{
public:
Animal(int w, const char *color, int age){
this->weight = w;
this->age = age;
strcpy(this->color, color);
}
protected:
int weight;
int age;
char color[32];
};
class Cat:public Animal
{
public:
Cat(int w, const char *color, int age, const char *type):Animal(w, color, age)
{
strcpy(this->type, type);
}
void show()
{
cout << "weight=" << weight << "\nage=" << age << "\ncolor=" << color <<endl;
}
protected:
char type[32];
};
(2)繼承后成員函式呼叫
- 父子類成員函式名相同時,不是多載,這時父類的此函式會別隱藏
- 子類呼叫成員函式時候會檢測子類是否存在,如果存在就呼叫自己的, 如果不存在就呼叫父類的(前提是父要有這個函式)
- 如果子類和父同時存在這個函式,一定要呼叫父類函式,可以用(父類名::函式名( ))呼叫,
例如:
#include <iostream>
using namespace std;
class A{
public:
A(){cout << "Afather\n";}
~A(){cout << "~Afather\n";}
void fun( ){
cout << "father fun\n";
}
};
class B:public A{
public:
B(){cout << "Bchildren\n";}
~B(){cout << "~~Bchildren\n";}
void fun(){
cout << "children fun\n";
}
};
int main( )
{
B x;
x.A::fun( );//呼叫父類的fun
return 0;
}
輸出結果:
Afather
Bchildren
father fun
~~Bchildren
~Afather
3、多繼承
(1)語法:
class <派生類名>:<繼承方式1> <基類名1>,<繼承方式2><基類名2>,…
{
<派生類類體>
};
(2)例子1:
class A{
public:
A(){cout<<"A()"<<endl;}
~A(){cout<<"~A()"<<endl;}
protected:
int dataA;
};
class B{
public:
B(){cout<<"B()"<<endl;}
~B(){cout<<"~B()"<<endl;}
protected:
int dataB;
};
class C:public A, public B
{
public:
C(){cout<<"C()"<<endl;}
~C(){cout<<"~C()"<<endl;}
protected:
int dataC;
};
注意:創建子類物件構造順序 A->B->C
如果改為:class C:public B, public A,創建子類物件構造順序 B->A->C
(3)例子2: 如果父類建構式帶引數
繼承關系class C:public A, public B
父類帶引數
A(int a):dataA(a){cout<<"A()"<<endl;}
B(int b):dataB(b){cout<<"B()"<<endl;}
C(int a, int b, int c):A(a), B(b),dataC(c){cout<<"C()"<<endl;}
(4)多個父類有同名的成員
多個父類有同名的成員, 在子類中訪問會出現歧義
- 顯示呼叫對應父類中的成員
c.A::info();
c.B::info(); - 在子類中添加父類的同名成員
這個時候系統會將父類的同名成員隱藏,呼叫子類的成員
4、虛擬繼承
語法:
class D :virtual public B{ //虛擬繼承
...
};

多繼承中多級繼承時候多個父類同時繼承同一個基類出現二義性問題--用虛擬繼承解決,
例如:
class A{
public:
void fun(){}
};
class B:virtual public A{ };
class C:virtual public A{ };
class D:public B,public C{ };
int main(void)
{
D x;
x.fun();//如果不是虛繼承,會出現二異性,因為在D類繼承了兩次A類
return 0;
}
十三、虛函式、虛表
定義: 在類的成員函式宣告前面添加virtual
virtual void show(){cout<<data<<endl;}
- 如果一個類中包含虛函式, 那么這個類的物件中會包含一個虛表指標vptr
- 虛表指標保存在物件空間的最前面
- 虛表中存盤的是類中的虛函式地址
- 物件呼叫類中虛函式,會查詢虛表指標再執行函式
- 一個類里最多只有兩個虛表指標(一個是虛函式的指標,一個是虛繼承的指標)
- 用virtual修飾的虛函式占用一個指標大小的記憶體,64位的話,大小為8;32位的話,大小為4,
- 同一個類的不同實體共用同一份虛函式表, 它們都通過一個所謂的虛函式表指標__vfptr(定義為void**型別)指向該虛函式表.

例子1:觀察輸出的最后結果是什么(一定要看)
#include <iostream>
using namespace std;
class Base
{
public:
Base(){}
virtual ~Base(){}
public:
virtual void show(int a=123){
cout<<"Base::show()"<<a<<endl;
}
};
class Child:public Base
{
public:
Child(){}
~Child(){}
virtual void show(int a=321){
cout<<"Child::show()"<<a<<endl;
}
virtual void info()
{
cout<<"Child::info()"<<endl;
}
};
int main()
{
Child c;
Base *p = &c;
p->show();
return 0;
}
結果:
Child::show()123
注意:
(1)當show函式不是虛繼承時,輸出結果為Base::show()123,因為父類的指標只能呼叫自己的成員,如果有虛繼承,則虛表里面父類的show函式的地址會被子類的show函式地址覆寫,被覆寫的前提是:兩個函式的名稱和引數型別、個數和回傳值型別一樣,
例子2:通過指標呼叫虛表中的虛函式(在ubuntu下運行,虛表地址通過qt除錯查看)
#include <iostream>
using namespace std;
class Base
{
public:
Base(){}
virtual ~Base(){}
protected:
virtual void show(int a= 0){
cout<<"Base::show()"<<endl;
}
};
class Child:public Base
{
public:
Child(){}
~Child(){}
virtual void show(){
cout<<"Child::show()"<<endl;
}
virtual void info()
{
cout<<"Child::info()"<<endl;
}
};
int main()
{
Child c;
typedef void (*Fun)();
c.show();
Fun f = (Fun)(((long*)(*((long*)(&c))))[2]);
f();
return 0;
}
結果:
Child::show()
Base::show()
十四、純虛函式(抽象函式)、抽象類
(1)純虛函式--虛函式不需要實作直接賦值為0,純虛函式有時稱為抽象函式,
定義:
virtual void run()=0;
(2)抽象類
- 如果一個類中包含純虛函式,那么這個就是抽象類,抽象類是不能創建物件,
- 抽象類可以派生出子類, 如果在子類中沒有把父類中的純虛函式全部實作,那么子類照樣是抽象類,
例子:執行緒獲取時間
#include <iostream>
#include <pthread.h>
#include <windows.h>
#include <time.h>
using namespace std;
class Thread{
public:
Thread(){}
~Thread(){}
void start();
virtual void run()=0;
protected:
pthread_t id;
};
void *handle(void *arg)
{
Thread* th = (Thread*)arg;
th->run();
}
void Thread::start()
{
int ret = pthread_create(&id, NULL, handle, (void*)this);
if(ret < 0)
{
cout<<"create fail"<<endl;
}
}
//派生一個執行緒子類--獲取系統時間
class TimeThread: public Thread{
public:
virtual void run()
{
while(1)
{
cout<<"TimeThread::run()"<<endl;
Sleep(1000);
time_t t;
time(&t);
cout<<pthread_self()<<"-----------"<<ctime(&t)<<endl;
}
}
};
int main()
{
TimeThread tth;
tth.start();
TimeThread tt;
tt.start();
while(1){}
return 0;
}
十五、多型、虛析構
(1)多型
<1>概念
C++中,多型性是指具有不同功能的函式可以用同一個函式名,這樣就可以用一個函式名呼叫不同內容的函式,
在面向物件方法中一般是這樣表述多型性的:向不同的物件發送同一訊息(呼叫函式),不同的物件在接收時會產生不同的行為(即方法,不同的實作,即執行不同的函式),可以說多型性是“一個介面,多種方法”,
多型性分為兩類:
(1)靜態多型性:在程式編譯時系統就能決定呼叫的是哪個函式,因此又稱為編譯時的多型性,通過函式的多載實作(運算子多載實際上也是函式多載);
(2)動態多型性:在程式運行程序中才動態地確定操作所針對的物件,又稱為運行時多型性,通過虛函式實作,
區別:函式多載是同一層次上的同名函式(首部不同,即引數個數或型別不同),虛函式是不同層次上的同名函式(首部相同),
<2>動態多型性和虛函式
父類參考(指標變數)指向子類物件時,呼叫的方法仍然是父類中的方法,如果將父類中的該方法定義為virtual,則呼叫的方法就是子類中的方法了,
說明:本來,父類指標是用來指向父類物件的,如果指向子類物件,則進行型別轉換,將子類物件的指標轉為父類的指標,所以父類指標指向的是子類物件中的父類部分,也就無法通過父類指標去呼叫子類物件中的成員函式,但是,虛函式可以突破這一限制!如果不使用虛函式,企圖通過父類指標呼叫子類的非虛函式是絕對不行的!
注意:父類中非虛函式被子類重寫后,父類指標呼叫的是父類的成員函式,子類指標呼叫的是子類中的成員函式,這并不是多型!因為沒有用到虛函式!
例如:

#include <iostream>
using namespace std;
class Person{
public:
Person(){}
~Person(){}
virtual void work(){cout<<"Person::work()"<<endl;}
protected:
int data;
};
class ChildPerson: public Person{
public:
ChildPerson(){}
~ChildPerson(){}
virtual void show(){cout<<"ChlidPerson::show()"<<endl;}
virtual void work(){cout<<"ChildPerson::work()"<<endl;}
virtual void info(){}
protected:
int number;
};
class A: public Person{
public:
A(){}
~A(){}
virtual void show(){cout<<"A::show()"<<endl;}
virtual void work(){cout<<"A::work()"<<endl;}
virtual void info(){}
protected:
int num;
};
int main()
{
ChildPerson cp;
A a;
Person* p = &a;
Person* pson = &cp;
pson->work(); //ChildPerson::work();
p->work();//A:work();
return 0;
}
(2)虛析構
多型的時候,用父類指標指向子類物件, 在delete 父類指標的時候默認只會呼叫父類解構式,子類解構式沒有執行(可能會導致子類的記憶體泄漏)--通過設定父類解構式為虛函式類解決,執行子類解構式后,自動執行父類解構式,
例如:
#include<iostream>
using namespace std;
class Base
{
public:
Base(){cout<<"create Base"<<endl;}
virtual ~Base(){cout<<"delete Base"<<endl;}
};
class Der : public Base
{
public:
Der(){cout<<"create Der"<<endl;}
~Der(){cout<<"Delete Der"<<endl;}
};
int main(int argc, char const* argv[])
{
Base *b = new Der;
delete b;
return 0;
}
十六、友元
友元:是c++里面一個特性,為了解決在函式中可以訪問類的私有,或保護成員,
友元函式是可以直接訪問類的私有成員的非成員函式,它是定義在類外的普通函式,它不屬于任何類,
- 友元優點: 可以在函式中直接訪問成員資料, 可以適當提高程式效率
- 友元缺點:在函式類的權限失效, 破壞了類的封裝性
friend關鍵宣告友元函式,或類,
第一種定義情況:類外定義:例如
class Data
{
public:
Data() {}
void setA(int a)
{
this->a = a;
}
protected:
int a;
private:
int b;
//在Data類中宣告函式fun為友元函式
friend void fun();
};
void fun()
{
Data data;
//data.a = 120;
data.setA(120);
data.b = 220;
}
友元宣告只放類內部宣告, 可以放在類內任意位置
第二種定義情況:類內定義例如:
class Data
{
public:
Data() {}
void setA(int a)
{
this->a = a;
}
protected:
int a;
private:
int b;
//在Data類中宣告函式fun為友元函式
friend void fun();
//宣告show為友元函式,show不是成員函式
friend void show()
{
Data data;
data.a = 130;
cout<<data.a<<" "<<data.b<<endl;
}
};
void show(); //在外面宣告函式
十七、友元類
在一個類中的成員函式可以訪問另外一個類中的所有成員比如在 A 類中的成員函式可以訪問 B 類中的所有成員,有兩種方法如下:
(1)在 B 類中設定 A 類為友元類,
(2)A::fun 要訪問B類中的所有成員, 把A::fun函式宣告為B類的友元函式,
(1)例如:在 B 類中設定 A 類為友元類,A類成員函式可以訪問B類的protected、private成員,B類不能訪問A類,如果要雙向訪問則要在兩個類中宣告對方為友元類,友元關系不能被繼承,
class B
{
public:
B(){}
friend class A;//在 B 類中宣告 A 類為友元類
private:
int bdata;
};
class A
{
public:
A(){}
void showB(B &b)
{
b.bdata = https://www.cnblogs.com/lcgbk/archive/2021/04/11/100;//在 A 類中成員函式使用 B 類的私有資料
}
private:
int adata;
};
(2)A::fun 要訪問B類中的所有成員, 把A::fun函式宣告為B類的友元函式
#include <iostream>
using namespace std;
//前向宣告----只能用于函式形參, 定義指標, 參考,不能使用類具體成員
class B;
class A{
public:
void fun(B& b);
};
class B{
public:
B(){}
protected:
int mb;
private:
int nb;
friend void A::fun(B& b);//在B類中宣告fun為友元函式
};
void A::fun(B& b){
cout<<b.mb<<b.nb<<endl;
}
int main()
{
cout << "Hello World!" << endl;
return 0;
}
十八、運算子多載
運算子多載 關鍵子函式operator
- 根據實際應用需求來多載運算子, 多載的時候必須保持不能改變運算子本來的特性
- 只能多載c++已有的運算子,不能自己新創建運算子
1、那些運算能多載
- 雙面運算子 (+,-,*,/, %)
- 關系運算子 (==, !=, <, >, <=, >=)
- 邏輯運算子 (||, &&, !)
- 單目運算子 (*, &, ++, --)
- 位運算子 (|, &, ~, ^, <<, >>)
- 賦值運算子 (=, +=, -=, .....)
- 空間申請運算子 (new , delete)
- 其他運算子 ((), ->, [])
2、那些運算子不能多載
- .(成員訪問運算子)
- .*(成員指標訪問運算子)
- ::(域運算子)
- sizeof(資料型別長度運算子)
- ?:(條件運算子, 三目運算子)
注意:

3、格式:
回傳型別說明符 operator 運算子符號(<引數表>)
{
函式體
}
4、多載方式:
1.多載方式---成員函式多載
Complex C = A+B; --》A.operator+(B);
規定:左值是函式呼叫者, 右值函式的引數
2.多載方式--友元多載(普通函式多載)(可以在類里面定義,也可以在類外定義類內宣告)
Complex C = A-B; -->operator-(A, B);
規定:左值為第一個引數, 右值為第二個引數
1、雙目運算子多載
(1)+ -多載
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(int r, int i):real(r), image(i) {}
//成員多載實作多載加法+
Complex operator+ (Complex &B)
{
cout<<"operator"<<endl;
Complex C(0,0);
C.real = this->real + B.real;
C.image = this->image + B.image;
return C;
}
//成員多載實作 物件加一個整型數
int operator+(const int &a)
{
this->real += a;
this->image += a;
return this->real;
}
private:
int real;
int image;
friend Complex operator- (Complex &A, Complex &B);
};
//友元多載實作多載減法
Complex operator- (Complex &A, Complex &B)
{
Complex C(0,0);
C.real = A.real - B.real;
C.image = A.image - B.image;
return C;
}
int main()
{
Complex A(2,2);
Complex B(1,1);
Complex C = A+B; //==>A.operator+(B)
int c = A+100; //==>A.operator+(100)
Complex D = A-B; //operator-(A, B)
return 0;
}
(2)輸出、輸入, 運算子多載
#include <iostream>
using namespace std;
class Point
{
public:
Point (int x=0, int y=0):x(x),y(y){}
void show()
{
cout<<"("<<x<<","<<y<<")"<<endl;
}
private:
int x, y;
//宣告友元函式
friend ostream& operator<<(ostream &out, Point& p);
friend istream& operator>>(istream &in, Point& p);
};
//多載輸出
ostream& operator<<(ostream &out, Point& p)
{
out<<"("<<p.x<<","<<p.y<<")"<<endl;
return out;
}
//輸入多載
istream& operator>>(istream &in, Point& p)
{
in>>p.x>>p.y;
return in;
}
int main()
{
Point p(10,20);
p.show();
cout<<p<<endl; //==> ostream& operator<<(cout, p)
Point A(0,0);
cin>>A;
cout<<A;
return 0;
}
3、單目運算子多載
(1)++A、A++多載
成員函式多載
#include <iostream>
using namespace std;
class Data
{
public:
Data(int d=0):data(d) {}
//多載A++
Data operator++(int)
{
Data old(*this);//保存原先的數捍
this->data += 1;//對原數進行自劍
return old;//回傳未加之前的數捍
}
//多載++A
Data& operator++()
{
this->data += 1;//對原數進行自劍
return *this;
}
private:
int data;
friend ostream &operator<<(ostream& out, Data &d);
};
ostream &operator<<(ostream& out, Data &d)
{
out<<d.data<<endl;
return out;
}
int main()
{
Data A;
Data d = A++; //==>A.operator++(int)
cout<<d<<A<<endl;
Data &c = ++A;
cout<<c<<A<<endl;
return 0;
}
友元函式多載
#include <iostream>
using namespace std;
class A
{
int data;
public:
A(int d = 0):data(d) {}
void show()
{
cout << this->data << endl;
}
//友元函式多載++A
friend A& operator++ (A &a);
//友元函式多載A++
friend A operator++ (A &b,int);
//友元函式多載<<
friend ostream& operator<< (ostream &out,A &a);
};
//友元函式多載++A
A& operator++ (A &a)
{
a.data += 1;
return a;
}
//友元函式多載A++
A operator++ (A &b,int)
{
A old(b);
b.data += 1;
return old;
}
//友元函式多載<<
ostream& operator<< (ostream &out,A &a)
{
out << a.data;
return out;
}
int main(int argc,char **argv)
{
A a(5);
A b = ++a;
cout << a << " " << b << endl;
A c(5);
A d = c++;
cout << c << " " << d << endl;
return 0;
}
(2)多載中括號[ ]
#include <iostream>
using namespace std;
class Array
{
public:
Array(int n):length(n) {
this->ptr = new int[this->length];
}
~Array(){
delete []this->ptr;
}
//拷貝建構式---深拷貝(類的成員有指標指向堆空間)
Array(Array& array)
{
this->length = array.length;
this->ptr = new int[this->length];
memcpy(this->ptr, array.ptr , this->length);
}
//多載[]
int& operator[](int i)
{
cout<<i<<endl;
return this->ptr[i];//回傳第i個物件
}
private:
int length;
int *ptr;
};
int main()
{
Array mArr(10);
mArr[0] = 100; //
return 0;
}
4、用運算子多載實作資料型別轉換
(1)轉換建構式
轉換建構式的作用:是將一個其他型別的資料轉換成一個類的物件? 當一個建構式只有一個引數,而且該引數又不是本類的const參考時,這種建構式稱為轉換建構式, 轉換建構式是對建構式的多載,
例如:
#include <iostream>
using namespace std;
class Complex
{
public:
Complex():real(0),imag(0){cout << "test1\n";}
Complex(double r, double i):real(r),imag(i){cout << "test2\n";}
// 定義轉換建構式
Complex(double r):real(r),imag(0){cout << "test3\n";}
/*
// 拷貝建構式
Complex(Complex &a){ cout << "test4\n"; }//當此函式存在時,Complex c = 1;Complex c2 = c1 + 3.1;編譯時都會報錯
*/
void Print(){
cout<<"real = " << real <<" image = "<<imag<<endl;
}
Complex operator+(Complex c){
Complex ret(this->real + c.real, this->imag + c.imag);
return ret;
}
private:
double real;
double imag;
};
int main()
{
Complex c;
c = 4; // 呼叫轉換建構式將1.2轉換為Complex型別,此時會呼叫轉換建構式
c.Print();
Complex c1(2.9, 4.2);
Complex c2 = c1 + 3.1; // 呼叫轉換建構式將3.1轉換為Complex型別
c2.Print();
return 0;
}
輸出結果:
test1
test3
real = 4 image = 0
test2
test3
test2
real = 6 image = 4.2
注意:
- 1、用轉換建構式可以將一個指定型別的資料轉換為類的物件?但是不能反過來將一個類的物件轉換為一個其他型別的資料(例如將一個Complex類物件轉換成double型別資料)?
- 2、如果不想讓轉換建構式生效,也就是拒絕其它型別通過轉換建構式轉換為本型別,可以在轉換建構式前面加上explicit,
(2)用運算子多載實作資料型別轉換
用轉換建構式可以將一個指定型別的資料轉換為類的物件?但是不能反過來將一個類的物件轉換為一個其他型別的資料(例如將一個Complex類物件轉換成double型別資料)?而型別轉換函式就是專門用來解決這個問題的!
型別轉換函式的作用是將一個類的物件轉換成另一型別的資料?
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age=0):age(age) {}
operator int() //通過運算子多載來實作資料型別轉換
{
cout<<"int"<<endl;
return age;
}
operator long() //通過運算子多載來實作資料型別轉換
{
cout<<"long"<<endl;
return num;
}
int getAge(){return age;}
private:
int age;
long num;
};
int main()
{
Person Jack(19);
int age = Jack;
int a = Jack.getAge();
long b = Jack;
return 0;
}
注意:
- 1、在函式名前面不能指定函式型別,函式沒有引數?
- 2、其回傳值的型別是由函式名中指定的型別名來確定的?
- 3、型別轉換函式只能作為成員函式,因為轉換的主體是本類的物件,不能作為友元函式或普通函式?
- 4、從函式形式可以看到,它與運算子多載函式相似,都是用關鍵字operator開頭,只是被多載的是型別名?double型別經過多載后,除了原有的含義外,還獲得新的含義(將一個Complex類物件轉換為double型別資料,并指定了轉換方法)?這樣,編譯系統不僅能識別原有的double型資料,而且還會把Complex類物件作為double型資料處理?
十九、模板函式
1、概念: 如果一個函式實作的功能類似,但是函式引數個數相同型別不同,這樣就可以把實在該功能的函式設計為模板函式,
2、格式:
template <typename T> //T為型別名
資料型別 函式名(引數串列){
函式體
}
3、注意:
- (1)在編譯時,根據變數生成實體,
- (2)template
T只對其下面的函式模板有效,如果要定義第二個模板函式時,則要再寫template , - (3)typename也可以用class,
- (4)T名字可以隨便取,
- (5)當引數不一樣時,可以這樣定義引數串列template <class T,class Tp>
- (6)引數串列可以帶默認型別,template <class T,class Tp = int>,
- (7)模板函式只有在使用(不是運行)的時候才會檢測錯誤,
例子1:
//設計一個模板函式實作兩個物件交換
template <typename T>
void mswap(T &a, T &b)
{
T c = a;
a = b;
b = c;
}
int main()
{
int a = 10;
int b = 20;
cout<<a<<" "<<b<<endl;
mswap(a, b);
cout<<a<<" "<<b<<endl;
return 0;
例子2:
//錯誤,因為不能夠確定回傳值的型別
template <class T, class Tp>
Tp fun(T &a)
{
return a;
}
//修改,但回傳值定死了
template <class T, class Tp = int>
Tp fun(T &a)
{
return a;
}
//呼叫函式時指定型別
template <class T, class Tp = int>
Tp fun(T &a)
{
return a;
}
int main(void)
{
int a = 2;
double ret = fun<int, double>(a)
}
4、模板函式與函式普通同時存在該如何呼叫
template <typename T>
void mswap(T &a, T &b)
{
cout<<"template"<<endl;
T c = a;
a = b;
b = c;
}
//普通函式
void mswap(int &a, int &b)
{
cout<<"std"<<endl;
int c = a;
a = b;
b = c;
}
呼叫(1)
int a = 10;
int b = 20;
cout<<a<<" "<<b<<endl;
mswap(a, b);//---普通函式
cout<<a<<" "<<b<<endl;
呼叫(2)
double a = 10;
double b = 20;
cout<<a<<" "<<b<<endl;
mswap(a, b);//---模板函式
cout<<a<<" "<<b<<endl;
如果模板函式和普通函式同時存在, 呼叫的時候會根據引數選擇最優函式
二十、模板類
1、模板類的定義
//設計一個模板類 -模板類的類名 A<T>
//template< class T , class Ty> //A<T, Ty>
template< class T > //A<T>
class A
{
public:
A() {}
protected:
T dataA;
};
int main()
{
A<int> a;//定義模板類物件
return 0;
}
注意:
- (1)如果是浮點型或者其他普通型別, 是指標或者是參考 template <double &N,class T=int>
class array{...}
定義物件: array<N,int> a ;這里的 N 必須是全域變數, - (2)引數串列可以帶默認型別,template <class T,class Tp = int> , 如果是默認型別, 與函式的默認引數類似, 必須是如果從那個一個開始默認, 那么后面的所有模板型別多必須有默認型別,
- (3)如果使用數值為整型( char, short, int, long) 時候,template <int N,class T=int> class array{...},這里的N只能是常量不能是變數,例如 array<10,int> a或者const int a = 5; array<a,int>,
2、模板類友元多載輸出
例如: 用模板類設計一個順序表(陣列)
#include <iostream>
using namespace std;
template< class T >
class MVector{
public:
MVector(){
this->size = 1024;
this->count = 0;
this->ptr = new T[this->size];
}
~MVector(){
delete []this->ptr;
}
//拷貝建構式
MVector(MVector& mv){
this->size = mv.size;
this->ptr = new T[this->size];
memcpy(this->ptr, mv.ptr, this->size*sizeof(T));
}
//添加資料
void append(const T &data){
this->ptr[this->count] = data;
this->count++;
}
//多載<<追加資料
void operator<<(int data)
{
this->ptr[this->count] = data;
this->count++;
}
#if 1//在類里面定義多載函式
//宣告友元多載輸出<<
friend ostream& operator<<(ostream& out, MVector &mv)
{
for(int i=0; i<mv.count; i++)
{
out<<mv.ptr[i]<<" ";
}
out<<endl;
return out;
}
#endif
//template<class Ty>
//friend ostream& operator<<(ostream& out, MVector<Ty> &mv);
protected:
int count;
int size;
T* ptr;
};
#if 0 //在類內宣告,在類外定義多載函式
//多載輸出<<運算子
template< class Ty >
ostream& operator<<(ostream& out, MVector<Ty> &mv)
{
for(int i=0; i<mv.count; i++)
{
out<<mv.ptr[i]<<" ";
}
out<<endl;
return out;
}
#endif
//模板函式在使用(不是運行)該函式的時候才會檢查語法
int main()
{
MVector<int> mvs;
mvs.append(100);
mvs<<200;
cout<<mvs;
return 0;
}
3、模板類繼承
如果在派生子類的時候父類類沒有確定class B: public A
例如:
#include <iostream>
using namespace std;
//設計一個模板類A<T>
template< class T >
class A
{
public:
A(T a) {}
protected:
T data;
};
//設計一個子類B 繼承模板類A<T> --B類也是模板類
template< class T >
class B: public A<T>
{
public:
B(T a):A<T>(a){}
protected:
int datab;
};
//設計一個子類C 繼承模板類A<int> --C類就是一個具體類
class C: public A<int>
{
public:
C():A<int>(10){}
};
int main()
{
A<char> a(10); //模板類創建物件
B<string> b("hello"); //模板類子類創建物件
C c;
return 0;
}
4、模板類中的靜態成員
編譯時根據模板生成的不同類的靜態成員是不同記憶體空間的;在同一個類中創建的物件的靜態成員是共用一個記憶體空間的,
如下:
#include <iostream>
using namespace std;
template<class T>
class Data
{
public:
Data() {}
void show(T msg)
{
data = https://www.cnblogs.com/lcgbk/archive/2021/04/11/msg;
cout<
T Data::data ;
int main()
{
//創建一個物件
Data mydata; //編譯的時候會生成一個 T為int的類
mydata.show(100);
Data::data ="hello"; //編譯的時候會生成一個T 為string的類
cout<<Data<string>::data<<endl;
Data<string> mystr;
cout<<mystr.data<<endl;
return 0;
}
二十一、強制型別轉換const_cast、static_cast、reinterpert_cast、dynamic_cast
注意:以上,如果轉換失敗的時候會回傳空
1、const_cast把常量轉為變數
#include <iostream>
using namespace std;
int main()
{
const int a = 10;
const int *p = &a;
int *ptr = (int*)(&a);//c語言轉換(在c語言可以這樣寫:int *ptr=&a,只是會警告,一樣可以操作,c++不允許)
*ptr = 1;
cout<<a<<endl;
cout<<*ptr<<endl;
int &ra = const_cast<int&>(a);
ra = 2;
cout<<a<<endl;
cout<<ra<<endl;
int *x = const_cast<int*>(p);
*x = 3;
cout<<a<<endl;
cout<<*x<<endl;
return 0;
}
輸出結果:
10
1
10
2
10
3
解釋:因為a是const修飾的,此時a的值會存在符號表中,也就是改變a地址所指向的值,也不會改變a的值,當呼叫a的時候,編譯器回到符號表中取值,而不是從a的地址取值,
(1)為何要去除const限定
原因(1)是,我們可能呼叫了一個引數不是const的函式,而我們要傳進去的實際引數確實const的,但是我們知道這個函式是不會對引數做修改的,于是我們就需要使用const_cast去除const限定,以便函式能夠接受這個實際引數,
例如:
#include <iostream>
using namespace std;
void Printer (int* val,string seperator = "\n")
{
cout << val<< seperator;
}
int main(void)
{
const int consatant = 20;
//Printer(consatant);//Error: invalid conversion from 'int' to 'int*'
Printer(const_cast<int *>(&consatant));
return 0;
}
原因(2):
還有一種我能想到的原因,是出現在const物件想呼叫自身的非const方法的時候,因為在類定義中,const也可以作為函式多載的一個標示符,
2、static_cast靜態轉化
static_cast < type-id > ( expression )該運算子把expression轉換為type-id型別,但沒有運行時型別檢查來保證轉換的安全性,它主要有如下幾種用法:
- ①用于類層次結構中基類(父類)和派生類(子類)之間指標或參考的轉換,不允許不相關的類進行轉換,
進行上行轉換(把派生類的指標或參考轉換成基類表示)是安全的;
進行下行轉換(把基類指標或參考轉換成派生類表示)時,由于沒有動態型別檢查,所以是不安全的, - ②用于基本資料型別之間的轉換,如把int轉換成char,把int轉換成enum,這種轉換的安全性也要開發人員來保證,
- ③把空指標轉換成目標型別的空指標,
- ④把任何型別的運算式轉換成void型別,
注意: static_cast不能轉換掉expression的const、volatile、或者__unaligned屬性
例如
#include <iostream>
using namespace std;
int main()
{
char a = 'a';
int b = (int)a;
double g = static_cast<int>(a);
//為什么不能轉換普通型別指標,卻能轉換物件指標和void指標(規定的)
void *pp;
double *pp1 = static_cast <double*>(pp);
int *xx;
void *xx1 = static_cast <void*>(xx);
//double *xx2 = static_cast <double*>(xx);//錯誤寫法
return 0;
}
3、reinterpret_cast強制型別轉換符
reinterpret_cast
type-id 必須是一個指標、參考、算術型別、函式指標或者成員指標,它可以把一個指標轉換成一個整數,也可以把一個整數轉換成一個指標(先把一個指標轉換成一個整數,再把該整數轉換成原型別的指標,還可以得到原先的指標值),
- reinterpret_cast可以轉換任意一個32bit整數,包括所有的指標和整數,可以把任何整數轉成指標,也可以把任何指標轉成整數,以及把指標轉化為任意型別的指標,但不能將非32bit的實體轉成指標,總之,只要是32bit的東東,怎么轉都行!
- 因為任何指標可以被轉換到void,而void可以被向后轉換到任何指標(對于static_cast<> 和 reinterpret_cast<>轉換都可以這樣做),如果沒有小心處理的話錯誤可能發生,
例如1:
#include <iostream>
using namespace std;
class A {
public:
int m_a;
};
class B {
public:
int m_b;
};
class C : public A, public B {};
int main()
{
int n= 1231651 ;
double *d;
cout << d << endl;
d=reinterpret_cast<double*> (&n);
//為什么d和n的地址一樣但為什么地址里面的值不一樣?
//是因為double型別資料存盤的方式不一樣,用*d訪問時,
//系統會以讀取double型別資料來讀取,
cout << d << " " << &n << endl;
cout << *d << " " << n << endl;
cout << "---------------------------\n";
//將一個32位的整數轉換成一個指標
char *n_p = reinterpret_cast<char*>(10);
//reinterpret_cast和static_cast的主要區別在于多繼承
C c;
printf("%p, %p, %p", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));//前兩個的輸出值是相同的,最后一個則會在原基礎上偏移4個位元組,這是因為static_cast計算了父子類指標轉換的偏移量,并將之轉換到正確的地址(c里面有m_a,m_b,轉換為B*指標后指到m_b處),而reinterpret_cast卻不會做這一層轉換,
return 0;
}
結果:

例如2:
//強制型別轉換//撰寫一個程式跳轉到地址0x12345678運行
typedef void(*Fun)(void);//定義一個函式指標資料型別
Fun fun = reinterpret_cast<Fun>( 0x12345678 );
fun();
4、dynamic_cast類轉換
dynamic_cast < type-id > ( expression )
說明: 該運算子把expression轉換成type-id型別的物件,Type-id必須是類的指標、類的參考或者void ;如果type-id是類指標型別,那么expression也必須是一個指標,如果type-id是一個參考,那么expression也必須是一個參考,
使用場景: dynamic_cast主要用于類層次間的上行轉換和下行轉換,還可以用于類之間的交叉轉換,在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;在進行下行轉換時,dynamic_cast具有型別檢查的功能,比static_cast更安全,
注意:
① dynamic_cast是動態轉換,只有在基類指標轉換為子類指標時才有意義,
② dynamic_cast<>需要類成為多型,即包括“虛”函式,并因此而不能成為void*,
③ static_cast和dynamic_cast可以執行指標到指標的轉換,或實體本身到實體本身的轉換,但不能在實體和指標之間轉換,static_cast只能提供編譯時的型別安全,而dynamic_cast可以提供運行時型別安全,
例如:
//dynamic---用于繼承程序中把父類指標轉換為子類指標
#include <iostream>
using namespace std;
class A
{
public:
A() {}
virtual ~A(){}
};
class B:public A
{
public:
B() {}
~B(){}
};
//呼叫
int main()
{
A *p = new B(); //用戶子類指標初始化父類指標
A *a = new A();//創建一個A類物件
//以下兩句必須在基類有虛析構的情況下才正確,否則編譯的時候報錯
B *bptr = dynamic_cast<B*>(a);//把新的父類指標賦值子類指標(nullptr)
B *bptr1 = dynamic_cast<B*>(p);//p是A型別指向B物件空間,把p轉回B類
if(bptr1 == nullptr)//為nullptr時轉換不成功
{
cout<<"fail"<<endl;
}
else {
cout<<"success"<<endl;
}
return 0;
}
二十二、例外捕捉和處理
在閱讀別人開發的專案中,也許你會經常看到了多處使用例外的代碼,也許你也很少遇見使用例外處理的代碼,那在什么時候該使用例外,又在什么時候不該使用例外呢?在學習完例外基本概念和語法之后,后面會有講解,
(1)例外拋出和捕捉陳述句
//1.拋出例外
throw 例外物件
//2.例外捕捉
try{
可能會發生例外的代碼
}catch(例外物件){
例外處理代碼
}
- throw子句:throw 子句用于拋出例外,被拋出的例外可以是C++的內置型別(例如: throw int(1);),也可以是自定義型別,
- try區段:這個區段中包含了可能發生例外的代碼,在發生了例外之后,需要通過throw拋出,
- catch子句:每個catch子句都代表著一種例外的處理,catch子句用于處理特定型別的例外,catch塊的引數推薦采用地址傳遞而不是值傳遞,不僅可以提高效率,還可以利用物件的多型性,
(2)例外的處理規則
- throw拋出的例外型別與catch抓取的例外型別要一致;
- throw拋出的例外型別可以是子類物件,catch可以是父類物件;
- catch塊的引數推薦采用地址傳遞而不是值傳遞,不僅可以提高效率,還可以利用物件的多型性,另外,派生類的例外捕獲要放到父類例外撲獲的前面,否則,派生類的例外無法被撲獲;
- 如果使用catch引數中,使用基類捕獲派生類物件,一定要使用傳遞參考的方式,例如catch (exception &e);
- 例外是通過拋出物件而引發的,該物件的型別決定了應該激活哪個處理代碼;
- 被選中的處理代碼是呼叫鏈中與該物件型別匹配且離拋出例外位置最近的那一個;
- 在try的陳述句塊內宣告的變數在外部是不可以訪問的,即使是在catch子句內也不可以訪問;
- 堆疊展開會沿著嵌套函式的呼叫鏈不斷查找,直到找到了已拋出的例外匹配的catch子句,如果拋出的例外一直沒有函式捕獲(catch),則會一直上傳到c++運行系統那里,導致整個程式的終止,
(3)實體
- 實體1:拋出自定義型別例外,
class Data
{
public:
Data() {}
};
void fun(int n)
{
if(n==0)
throw 0;//拋例外 int例外
if(n==1)
throw "error"; //拋字串例外
if(n==2)
{
Data data;
throw data;
}
if(n>3)
{
throw 1.0;
}
}
int main()
{
try {
fun(6);//當例外發生fun里面,fun以下代碼就不會再執行,調到catch處執行例外處理代碼,后繼續執行catch以外的代碼,當throw拋出例外后,沒有catch捕捉,則整個程式會退出,不會執行整個程式的以下代碼
cout<<"*************"<<endl;
}catch (int i) {
cout<<i<<endl;
}catch (const char *ptr)
{
cout<<ptr<<endl;
}catch(Data &d)
{
cout<<"data"<<endl;
}catch(...)//抓取 前面例外以外的所有其他例外
{
cout<<"all"<<endl;
}
return 0;
}
- 實體2:標準出錯類拋出和捕捉例外,
#include <iostream>
using namespace std;
int main()
{
try {
char* p = new char[0x7fffffff]; //拋出例外
}
catch (exception &e){
cout << e.what() << endl; //捕獲例外,然后程式結束
}
return 0;
}
輸出結果:
當使用new進行開空間時,申請記憶體失敗,系統就會拋出例外,不用用戶自定義例外型別,此時捕獲到例外時,就可告訴使用者是哪里的錯誤,便于修改,

- 實體3:繼承標準出錯類的派生類的例外拋出和捕捉,
#include <iostream>
#include <exception>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
class FileException :public exception
{
public:
FileException(string msg) {
this->exStr = msg;
}
virtual const char*what() const noexcept//宣告這個函式不能再拋例外
{
return this->exStr.c_str();
}
protected:
string exStr;
};
void fun()
{
int fd = ::open("./open.txt",O_RDWR);
if(fd<0)
{
FileException openFail("open fail"); //創建例外物件
throw openFail;//拋例外
}
}
int main( )
{
try {
fun();
} catch (exception &e) {//一般需要使用參考
cout<<e.what()<<endl;
}
cout<<"end"<<endl;
return 0;
}
當檔案不存在時,輸出結果:

如果在Linux上運行,上述代碼需要根據環境修改:
98標準寫法
~FileException()throw(){}//必須要
virtual const char*what() const throw()//宣告這個函式不能再拋例外
{
return this->exStr.c_str();
}
//編譯
g++ main.cpp
2011標準寫法
~FileException()noexcept{}//必須要
virtual const char*what() const noexcept//宣告這個函式不能再拋例外
{
return this->exStr.c_str();
}
//編譯
g++ main.cpp -std=c++11 指定用c++11標準編譯
(4)總結
1. 使用例外處理的優點:
- 傳統錯誤處理技術,檢查到一個錯誤,只會回傳退出碼或者終止程式等等,我們只知道有錯誤,但不能更清楚知道是哪種錯誤,使用例外,把錯誤和處理分開來,由庫函式拋出例外,由呼叫者捕獲這個例外,呼叫者就可以知道程式函式庫呼叫出現的錯誤是什么錯誤,并去處理,而是否終止程式就把握在呼叫者手里了,
2. 使用例外的缺點:
- 如果使用例外,光憑查看代碼是很難評估程式的控制流:函式回傳點可能在你意料之外,這就導致了代碼管理和除錯的困難,啟動例外使得生成的二進制檔案體積變大,延長了編譯時間,還可能會增加地址空間的壓力,
- C++沒有垃圾回識訓制,資源需要自己管理,有了例外非常容易導致記憶體泄漏、死鎖等例外安全問題, 這個需要使用RAII來處理資源的管理問題,學習成本較高,
- C++標準庫的例外體系定義得不好,導致大家各自定義各自的例外體系,非常的混亂,
3. 什么時候使用例外?
- 建議:除非已有的專案或底層庫中使用了例外,要不然盡量不要使用例外,雖然提供了方便,但是開銷也大,
4. 程式所有的例外都可以catch到嗎?
- 并非如此,只有發生例外,并且又拋出例外的情況才能被catch到,例如,陣列下標訪問越界的情況,系統是不會自身拋出例外的,所以我們無論怎么catch都是無效的;在這種情況,我們需要自定義拋出型別,判斷陣列下標是否越界,然后再根據自身需要throw自定義例外物件,這樣才可以catch到例外,并進行進一步處理,
二十三、STL標準模板庫
容器:
- vector---順序存盤---順序表 (訪問遍歷查詢)
- list ------鏈式存盤 ----鏈表 (適合資料長度不確定, 經常改變)
- map ----鍵值對存盤 (key:value) (適合資料成對存盤)
- set ------容器-----------------------(存盤資料是唯一的)
1、vector(順序表)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
//創建vector物件
//大小變化:1024 2048 4096(開始會以2倍數增加,后面慢慢以1/3、1/5等的形式增加)
vector<string> names;
//賦值,3個jack
names.assign(3,"Jack"); //Jack, Jack, Jack
//插入資料
//創建一個迭代器
vector<string>::iterator it = names.begin();
//insert之后迭代器it已經改變,回傳值為插入值的位置
it = names.insert(++it, "Rose"); //結果:Jack Rose Jack Jack
it = names.insert(++it,"Jim");//結果:Jack Rose Jim Jack Jack
it = names.insert(++it, "lcg"); //結果:Jack Rose Jim lcg Jack Jack
it = names.insert(names.end(), "Jack"); //結果:Jack Rose Jim lcg Jack Jack Jack
//查詢資料/洗掉資料---迭代器遍歷使用vector, list, map, set
//names.end()為順序表最后一個元素的下一個地址
for(it = names.begin(); it != names.end(); ++it)
{
if(*it == "Jack")
{
cout<<*it<<" ";
//擦除,回傳擦出元素的下一個位置
//例如a b c,洗掉b后,回傳迭代器指向c
it=names.erase(it);
--it;
}
}
cout<<endl;
//遍歷---順序表
for(int i=0; i<names.size(); i++)
{
cout<<names[i]<<" ";
}
cout<<endl;
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/274982.html
標籤:其他
