上篇學習了C語言,這篇繼續學習C++,這篇是創建在C基礎上的java
真的是碼字如蝸牛,寫了很久才寫這麼多,做爲筆記吧,防止之後忘記ios
首先打開你的文本編輯器,輸入一下內容c++
#include <iostream>
using namespace std;
int main() {
cout << "Hello, world!!!!!" << endl;
return 0;
}
複製代碼
而後把文件保存爲test.cpp
,最後編譯執行bash
L-96FCG8WP-1504:untitled renxiaohui$ g++ test.cpp
L-96FCG8WP-1504:untitled renxiaohui$ a.out
Hello, world!!!!!
複製代碼
#include <iostream>
using namespace std;
// main() 是程序開始執行的地方
int main() {
cout << "Hello World"; // 輸出 Hello World
return 0;
}
複製代碼
using namespace std;
命名空間,是C++新加的概念int main()
程序的入口cout << "Hello World"
在屏幕上輸出Hello worldreturn 0;
終止main函數爲何要寫using namespace std
這句話呢?tcp
這是C++引入的一個新的機制,主要是爲了解決多個模塊間命名出衝突的問題,就像現實人的名字重名一個道理,C++把名字相同的放到不一樣的空間中,來防止命名的衝突編輯器
例如標準的C++庫提供的對象都放在std這個標準的命名空間中,好比cin,cout,endl,因此咱們看到C++程序中,都會有using namespace std
這句話函數
好比這個程序學習
#include<iostream>
using namespace std;
int main() {
cout<<"Nice to meet you!"<<endl;
return 0;
}
複製代碼
這裏面用到了cout和endl則必須提早告知用std命名空間,除此以外還有倆種寫法ui
#include<iostream>
int main() {
std::cout<<"Nice to meet you!"<<std::endl;
return 0;
}
複製代碼
#include<iostream>
using std::cout;
using std::endl;
int main() {
cout<<"Nice to meet you!"<<endl;
return 0;
}
複製代碼
C++中的輸入輸出除了兼容C的寫法及使用printf
和scanf
實現外,C++還有本身的一套輸入輸出的寫法,C++的輸入流和輸出流分別使用cin
和cout
來表示,使用以前須要引入標準庫iostream,即 #include <iostream>
this
cout輸出流須要搭配<< 輸出操做符來使用,例如輸出語句
cout << "hello world";
複製代碼
會在屏幕上顯示hello world
本質是將hello world 插入cout 對象,並以cout對象爲返回值返回,所以能夠在後方接多個<<
cout << "hello world" << "kk";
複製代碼
cout
常常和endl
配合使用,起到換行符的做用
cout << "hello" << endl<<"world"<<endl;
複製代碼
輸出
hello
world
複製代碼
接收一個數據前,都要先定義一個與之類型相一致的變量,而後利用cin 和>> 從用戶的鍵盤接受輸入
#include <iostream>
using namespace std;
int main() {
int a;
cout << "請輸入你的數字" << endl;
cin >> a;
cout << a << endl;
return 0;
}
複製代碼
輸出
請輸入你的數字
12
12
複製代碼
cin也能夠接收多個變量
int a,b;
cin>>a>>b;
複製代碼
類型 | 關鍵字 |
---|---|
布爾型 | bool |
字符型 | char |
整形 | int |
浮點型 | float |
雙浮點型 | double |
無類型 | void |
寬字符型 | wchar_t |
其實wchar_t是這樣來的
typedef short int wchar_t;
複製代碼
因此一個wchar_t和short int 空間同樣
一些基本數據類型能夠用一個或多個修飾符修飾
下表顯示了各類變量類型所佔內存和能儲存的最大值和最小值
類型 | 位 | 範圍 |
---|---|---|
char | 1個字節 | -128到127或0到255 |
unsigned char | 1個字節 | 0到255 |
signed char | 1個字節 | -128到127 |
int | 四個字節 | -2147483648 到 2147483647 |
unsigned int | 四個字節 | 0 到 4294967295 |
signed int | 四個字節 | -2147483648 到 2147483647 |
short int | 倆個字節 | -32768 到 32767 |
unsigned short int | 倆個字節 | 0 到 65,535 |
signed short int | 倆個字節 | -32768 到 32767 |
long int | 8個字節 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
signed long int | 8個字節 | -9,223,372,036,854,775,808到9,223,372,036,854,775,807 |
unsigned long int | 8個字節 | 0 到 18,446,744,073,709,551,615 |
float | 四個字節 | 精度型佔4個字節(32位)內存空間,+/- 3.4e +/- 38 (~7 個數字) |
double | 8個字節 | 雙精度型佔8 個字節(64位)內存空間,+/- 1.7e +/- 308 (~15 個數字) |
long double | 16個字節 | 長雙精度型16個字節(128位)內存空間,可提供18-19位有效數字。 |
wchar_t | 2或4個字節 | 一個寬字符 |
在C++中容許在自定義定的函數的形參列表中,給一個形參的默認值,這樣若是調用的時候有參數,那麼按傳遞的參數來講,若是沒有則按照默認的參數
#include <iostream>
using namespace std;
int add(int a =4,int b =5){
return a+b;
}
int main(int argc, const char * argv[]) {
int a= add();
int b = add(7,8);
cout<<a<<"/"<<b<<endl;
return 0;
}
複製代碼
輸出
9/15
複製代碼
C++和java同樣都有函數的重載,函數重載能夠處理多種類型,同一個方法名,但依然須要分開定義,若是可讓他代碼更精簡一點,模板化就更好了
爲此C++提供了函數模板這一機制,能夠大大提升函數的通用性
函數模板能夠建立一個通用的函數,能夠支持多種形參
用關鍵字template
來定義,形式以下
template<class 類型名1,class 類型名2…> 返回值 函數名(形參表列) 模板參數表 {
函數體
}
複製代碼
上面template<class 類型名1,class 類型名2…>
是一個聲明語句,template
是定義模板函數的關鍵字,尖括號裏能夠有多個類型,前面都要用class(或typename來定義),而後後面跟定義的函數模板,中間不可用加其餘語句
#include <iostream>
using namespace std;
template<class T1,class T2> T1 add(T1 x,T2 y){
cout<<sizeof(T1)<<"/"<<sizeof(T2)<<endl;
return x+y;
}
int main(int argc, const char * argv[]) {
cout<<add(1,2)<<endl;
cout<<add(0.22, 0.33)<<endl;
cout<<add('A',2)<<endl;
return 0;
}
複製代碼
輸出
4/4
3
8/8
0.55
1/4
C
複製代碼
一個函數被另外一個函數調用的時候纔會有生命,纔會爲其準備相應的空間,在調用完畢以後纔會清理釋放結束
能夠看到每一次的函數調用都會帶來一些空間上的花銷
定義一個函數的做用,也是爲了提升代碼的重用性,能夠在須要的時候調用,提升開發效率,那麼一個代碼自己就很少,又被頻繁調用的函數,這樣作是否合算呢?
C++已經考慮到了這個問題,爲咱們提供了內聯機制,即仍然是使用自定義函數,可是編譯的時候,把函數代碼插入到函數調用的地方, 就像普通的程序執行代碼同樣,來解決這個問題
用法很是簡單,只須要在函數定義前加上inline
關鍵字聲明就能夠了
#include <iostream>
using namespace std;
inline int max1(int x,int y){
return x>y?x:y;
}
int main(int argc, const char * argv[]) {
cout<<max1(12, 2)<<endl;
cout<<max1(2, 13)<<endl;
return 0;
}
複製代碼
內聯函數的調用要出如今調用以前,纔可讓編譯器瞭解上下文進行代碼替換
類的含義就很少贅述了,直接看如何使用
class Status{
public:
int a;
char name[100];
int socre;
int print(){
cout<<a<<"/"<<name<<"/"<<socre<<endl;
return 0;
}
};
複製代碼
其實跟java類的定義差很少,其中public
是訪問權限控制符,C++還有其餘的權限控制符private,protected
private: 表示私有,他所修飾的成員,只能在類的內部訪問,外界不能訪問
protected: 除了類內部能夠訪問,他的子類也能夠訪問
public: 內部外部均可以訪問
類的最後有一個分號,不要忘記了
class Sts{
public:
int a;
char name[100];
int socre;
int print();
};
int Sts::print(){
cout<<a<<"/"<<name<<"/"<<socre<<endl;
return 0;
}
複製代碼
這種形式成員函數,僅僅在類內聲明函數原型,再類外定義函數體,在類外定義函數體的須要類名加上::域限定符
Status st;
st.a=1;
st.socre=100;
strcpy(st.name, "wang");
st.print();
複製代碼
Sts *p;
Sts s;
s.a=100;
s.socre=200;
strcpy(s.name, "xin");
p=&s;
p->print();
複製代碼
指針能夠經過->來訪問成員變量和方法
傳遞參數的時候建議使用指針來傳遞,由於傳遞的是地址,不會進行對象間的副本賦值,從而提升效率,減小內存開銷
引用C++中的新的類型,對象的引用是一個對象的別名,本質上也是把對象的地址賦值給了這個引用類型,倆者指向同一塊內存空間
定義
Student A;
Student &Aq=A;
複製代碼
定義一個Student的對象,而後用&符號定義一個該類型的引用類型,並把A對象賦值給Aq初始化
須要注意的是:
使用
Student A;
Student &Aq=A;
Aq.print();
複製代碼
其使用跟對象是同樣的方式
咱們能夠看到,引用其實也是地址,傳參不會耗太多內存,有指針的優點,同時使用起來和對象自己使用起來同樣,再作函數實參是,直接傳入便可,不用加地址符,看起來更加的直觀方便,這就是引用類型的優勢
#include <iostream>
#include <string>
using namespace std;
class Status{
public:
int a;
char name[100];
int socre;
int print(){
cout<<a<<"/"<<name<<"/"<<socre<<endl;
return 0;
}
};
class Sts{
public:
int a;
char name[100];
int socre;
int print();
};
int Sts::print(){
cout<<a<<"/"<<name<<"/"<<socre<<endl;
return 0;
}
int main(int argc, const char * argv[]) {
Status st;
st.a=1;
st.socre=100;
strcpy(st.name, "wang");
st.print();
Sts *p;
Sts s;
s.a=100;
s.socre=200;
strcpy(s.name, "xin");
p=&s;
p->print();
Sts &Aq=s;
Aq.print();
return 0;
}
複製代碼
輸出
1/wang/100
100/xin/200
100/xin/200
複製代碼
C++的構造函數和java的構造函數功能大體相同,當咱們定義一個類對象時,系統會自動調用它,進行專門的初始化,若是咱們沒有定義構造函數,則系統會自動生成一個無參構造函數,若是咱們本身定義了,則系統再也不自動生成構造函數,會區配用戶自定義的構造函數,
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
int a ;
char name[100];
int socre;
Student (int num , char *str,int sor);
void print();
};
Student::Student(int num, char *str,int sor){
a=num;
strcpy(name, str);
socre=sor;
cout<<"構造函數"<<endl;
}
void Student::print(){
cout<<a<<"/"<<name<<"/"<<socre<<endl;
}
int main(int argc, const char * argv[]) {
Student stu(100,"heihei",200);
stu.print();
return 0;
}
複製代碼
輸出
構造函數
100/heihei/200
複製代碼
對象建立時會調用構造函數,對象銷燬時會調用析構函數,他也和類同名,也沒有返回值,名字前有一個波浪線~,來區分構造函數,它主要作對象釋放後的清理工做
若是用戶不定義,則系統會自動生成一個,若是用戶定義,則會在對象銷燬時自動調用
構造函數能夠重載,可是析構函數不能夠重載,但他能夠是虛函數,一個類只能有一個析構函數
#include <iostream>
#include <cstring>
using namespace std;
class Status{
public:
int a ;
char name[100];
int sorce;
Status(int a ,char *str,int sor);
~Status();
};
Status::Status(int a,char *str,int sor){
this->a=a;
strcpy(name, str);
sorce=sor;
cout<<this->a<<name<<sorce<<endl;
}
Status::~Status(){
cout<<"析構函數"<<endl;
}
int main(int argc, const char * argv[]) {
Status sta(1,"aaa",200);
Status stw(10,"aaaq",2001);
return 0;
}
複製代碼
輸出
1aaa200
10aaaq2001
析構函數
析構函數
複製代碼
在C++中,與類同名,且形參是本類對象的引用類型函數,叫作拷貝構造函數,與構造函數同樣,若是咱們不主動定義,那麼系統會自動生成一個,進行倆個對象成員之間的賦值,用來初始化一個對象
#include <iostream>
#define AA 3.999
using namespace std;
class Status{
public:
int a ;
int b ;
Status(int a,int b);
Status(Status &A);
};
Status::Status(int a,int b){
this->a=a;
this->b=b;
cout<<a<<"/"<<b<<endl;
}
Status::Status(Status &A){
this->a=A.a;
this->b=A.b;
cout<<"拷貝k函數"<<a<<"/"<<b<<endl;
}
int main(int argc, const char * argv[]) {
Status st(1,2);
Status sts(st);
return 0;
}
複製代碼
輸出
1/2
拷貝k函數1/2
複製代碼
第一次定義的對象是用帶參的構造函數,第二次定義的對象,使用第一次的對象來進行初始化因此調用拷貝構造函數
上面講到的拷貝函數和系統的操做方式同樣,那麼爲何咱們要重寫一遍呢?咱們看下個例子
#include <iostream>
#include <cstring>
using namespace std;
class Status{
public:
int a;
int b;
char *str;
Status(int a,int b,char *str);
~Status();
void print();
};
Status::Status(int a,int b,char *str){
this->a=a;
this->b=b;
this->str=new char[strlen(str)+1];
strcpy(this->str, str);
}
Status::~Status(){
delete []str;
}
void Status::print(){
cout<<a<<"/"<<b<<"/"<<str<<endl;
}
int main(int argc, const char * argv[]) {
char *pp="2we";
Status st(1,2,pp);
st.print();
Status ss(st);
ss.print();
return 0;
}
複製代碼
這段代碼執行起來會有問題,緣由是,默認拷貝函數,只是this.str->str
簡單的賦值,倆個對象指向了同一個地址,並無爲新的對象開闢新的空間,這已經違背了咱們的初衷,倆個對象銷燬時都會調用析構函數,釋放這段代碼的內存空間,因爲對象調用倆次,delete了倆次,就會報錯
淺拷貝和深拷貝(47128,0x1000ac5c0) malloc: *** error for object 0x100595040: pointer being freed was not allocated
複製代碼
因此當類中有指針類型,依靠默認的拷貝方發,已經不能知足咱們的需求,定義一個特定的拷貝構造函數,不只進行數據的拷貝,還要爲成員分配空間,這就叫作深拷貝,也就是深拷貝構造函數
#include <iostream>
#include <cstring>
using namespace std;
class Status{
public:
int a;
int b;
char *str;
Status(int a,int b,char *str);
~Status();
Status(Status &A);
void print();
};
Status::Status(int a,int b,char *str){
this->a=a;
this->b=b;
this->str=new char[strlen(str)+1];
strcpy(this->str, str);
}
Status::~Status(){
delete []str;
}
Status::Status(Status &A){
this->a=A.a;
this->b=A.b;
this->str=new char[strlen(A.str)+1];
strcpy(this->str, A.str);
}
void Status::print(){
cout<<a<<"/"<<b<<"/"<<str<<endl;
}
int main(int argc, const char * argv[]) {
char *pp="2we";
Status st(1,2,pp);
st.print();
Status ss(st);
ss.print();
return 0;
}
複製代碼
咱們知道類內的私有變量,只能在類內訪問,類外不能訪問,假如咱們須要再類外訪問私有變量,那咱們該怎麼辦呢?
咱們能夠利用友元函數,把外部的函數聲明爲友元類型,賦予它能夠在類外訪問類的私有變量
友元函數既能夠是全局函數,也能夠是其餘類的成員函數,不只如此,友元還能夠是一個類,叫作友元類
怎麼使用? 對於友元函數只須要在類中對函數進行聲明,並在以前加上friend
關鍵字,這個函數就有了獨特的權限
須要注意的是,友元函數並不屬於類,不能夠用this指針,同時也不能夠被繼承
#include <iostream>
using namespace std;
class Status{
private:
int a;
int b;
public:
Status(int a,int b){
this->a=a;
this->b=b;
};
friend void print(Status st);
};
void print(Status st){
cout<<st.a<<"/"<<st.b<<endl;
}
int main(int argc, const char * argv[]) {
Status st(1,2);
print(st);
return 0;
}
複製代碼
輸出
1/2
複製代碼
友元類和友元函數同樣,以下
#include <iostream>
using namespace std;
class Status{
private:
int a;
int b;
public:
Status(int a,int b){
this->a=a;
this->b=b;
};
friend class A;
};
class A{
public:
void print(Status &st){
cout<<st.a<<"/"<<st.b<<endl;
}
};
int main(int argc, const char * argv[]) {
Status st(1,2);
A a;
a.print(st);
return 0;
}
複製代碼
輸出
1/2
複製代碼
被關鍵字const
修飾的變量,是不可修改和改變的,他除了能夠修飾變量還能夠修飾對象爲常對象,修飾類的成員和成員函數,分別叫作類的常數據成員和常成員函數
使用格式
數據類型 const 數據成員名;
或 const 數據類型 數據成員名;
被const修飾的變量必須初始化而且不能夠被修改
而初始化的方式是在構造函數中初始化,C++11支持後支持直接初始化
另一種狀況若是是static變量,初始化須要在類外初始化
複製代碼
例子以下
#include <iostream>
using namespace std;
class A{
public:
//能夠直接初始化
const int a = 0;
const int b;
const int c;
static int d;
//靜態常量能夠在類內初始化
static const int e = 4;
//在構造方法初始化
A(int b,int c):b(b),c(c){
}
void print(){
cout<<a<<"/"<<b<<"/"<<c<<"/"<<d<<"/"<<e<<"/"<<endl;
}
};
//靜態變量再類外初始化
int A::d =3;
int main(int argc, const char * argv[]) {
A a(1,2);
a.print();
return 0;
}
複製代碼
輸出
0/1/2/3/4/
複製代碼
C++中能夠把一個對象聲明成const
類型,成爲常對象,這樣聲明後,這個對象整個聲明週期都不能夠被更改,因此定義的時候須要在構造函數初始化;
定義的格式以下
類型 const 對象名;
或 const 類型 對象名;
須要注意的是常對象只能訪問,常成員函數,不能訪問很是成員函數
複製代碼
實例
#include <iostream>
using namespace std;
class A{
public:
const int a;
int b;
A(int a):a(a){
b=2;
}
void print(){
}
void print1() const{
cout<<a<<"/"<<b<<endl;
}
};
int main(int argc, const char * argv[]) {
const A a(1);
const A a1(2);
//編譯失敗,常對象不能被賦值
// a=a1;
//此處編譯失敗,常對象不能調用很是成員函數
// a.print();
a.print1();
return 0;
}
複製代碼
一個類中的成員函數被const
修飾後,就變成了常成員函數
定義以下
返回類型 函數名(形參表列)const;
複製代碼
須要注意的是
const
#include <iostream>
using namespace std;
class A{
public:
const int a;
int b;
A(int a):a(a){
b=2;
}
void print(){
}
void print1() const{
//此處編譯錯誤,常成員函數不能夠改變很是成員變量
// b=3;
//此處編譯錯誤,常成員函數不能夠調用很是成員變量
// print();
cout<<a<<"/"<<b<<endl;
}
};
int main(int argc, const char * argv[]) {
const A a(1);
const A a1(2);
//編譯失敗,常對象不能被賦值
// a=a1;
//此處編譯失敗,常對象不能調用很是成員函數
// a.print();
a.print1();
return 0;
}
複製代碼
C++繼承和java繼承概念相同,咱們看下如何實現繼承
#include <iostream>
using namespace std;
class A{
public:
int a;
void setA(int a){
this->a=a;
}
void showA(){
cout<<a<<endl;
}
};
class B:public A{
public:
int b;
void setB(int b){
this->b=b;
}
void showB(){
cout<<b<<endl;
}
};
int main(int argc, const char * argv[]) {
B b;
b.setA(1);
b.setB(2);
b.showA();
b.showB();
return 0;
}
複製代碼
輸出
1
2
複製代碼
一共有三種繼承方式,分別是,公有繼承,私有繼承,保護繼承,下面分別介紹下
能夠看出若是爲私有繼承,則私有成員在派生類中都不可使用,沒有什麼做用,這種狀況使用較少
公有繼承 | 保護繼承 | 私有繼承 | ||||
---|---|---|---|---|---|---|
訪問位置 | 類內 | 類外 | 類內 | 類外 | 類內 | 類外 |
公有成員 | 能夠 | 能夠 | 能夠 | 不能夠 | 能夠 | 不能夠 |
保護成員 | 能夠 | 不能夠 | 能夠 | 不能夠 | 能夠 | 不能夠 |
私有成員 | 不能夠 | 不能夠 | 不能夠 | 不能夠 | 不能夠 | 不能夠 |
咱們在建立一個派生類時,系統會首先建立一個基類,派生類會吸取全部基類的成員,可是不會吸取構造函數和析構函數,那麼早調用派生類的構造函數以前,會先調用基類的構造函數,當基類的構造函數是帶參數的,那麼派生類就要明確指出父類的構造函數而且指定參數
#include <iostream>
using namespace std;
class A{
public:
int a;
A(){
cout<<"父類的無參構造函數"<<endl;
}
A(int a){
this->a=a;
cout<<"父類的有參構造函數"<<endl;
}
};
class B:public A{
public:
B(){
cout<<"子類的無參構造"<<endl;
}
//指定父類帶參構造和參數
B(int a):A(a){
cout<<"子類的有參構造函數"<<endl;
}
};
int main(int argc, const char * argv[]) {
B b;
B b1(1);
return 0;
}
複製代碼
輸出
父類的無參構造函數
子類的無參構造
父類的有參構造函數
子類的有參構造函數
複製代碼
基類的析構函數也不可能以被繼承,其調用順序爲,子類析構函數->基類析構函數,和構造的函數的調用順序相反
#include <iostream>
using namespace std;
class A{
public:
~A(){
cout<<"父類的析構函數"<<endl;
}
};
class B:public A{
public:
~B(){
cout<<"子類的析構函數"<<endl;
}
};
int main(int argc, const char * argv[]) {
B b;
return 0;
}
複製代碼
輸出
子類的析構函數
父類的析構函數
複製代碼
咱們看下多繼承的狀況
#include <iostream>
using namespace std;
class A{
public:
int b;
};
class B:public A{
};
class C:public A{
};
class D :public B,public C{
};
int main(int argc, const char * argv[]) {
D d;
//在這裏編譯報錯,D繼承了倆份b變量
// d.b=8;
return 0;
}
複製代碼
解決這種狀況須要虛基類
所謂的虛基類就是在繼承的public以前加上virtual關鍵字,只要加上這個關鍵字派生類就維護一份基類對象,避免屢次拷貝,出現歧義
使用
#include <iostream>
using namespace std;
class A{
public:
int b;
};
class B:virtual public A{
};
class C:virtual public A{
};
class D :public B,public C{
};
int main(int argc, const char * argv[]) {
D d;
d.b=8;
return 0;
}
複製代碼
指一樣的方法被不一樣的對象執行時會有不一樣的效果
多態的實現又分爲倆種,編譯時多態和運行時多態,前者是編譯時就肯定了操做過程,後者是程序運行之中才肯定了操做過程,這種操做過程就是聯編,也稱爲綁定
聯編在編譯連接時確認的,叫作靜態聯編,以前遇到的函數重載和函數模板就屬於這一類
在運行時才肯定執行那段代碼的,就作動態聯編
靜態聯編在編譯時就肯定說以效率比較高,動態聯編雖然慢些但比較靈活,各有優勢
靜態聯編的例子
#include <iostream>
using namespace std;
class A{
public:
void print(){
cout<<"A"<<endl;
}
};
class B:public A{
public:
void print(){
cout<<"B"<<endl;
}
};
int main(int argc, const char * argv[]) {
A a;
a.print();
B b;
b.print();
A *p;
p=&b;
p->print();
A &pp=b;
pp.print();
return 0;
}
複製代碼
輸出
A
B
A
A
複製代碼
這個是靜態聯編,在編譯時已經肯定了*p和&pp的類型爲A,因此輸出爲A
很明顯這不是咱們指望的結果,若是想要達到咱們的要求,不管指針和引用是什麼類型,都要以實際指向的對象靈活決定,那麼咱們就要改變默認的靜態聯編,採用動態聯編
什麼是虛函數
在函數前面加上virtual
關鍵字,就是虛函數
virtual 函數返回值 函數名(形參)
{
函數體
}
複製代碼
有什麼做用
虛函數的出現容許函數,在調用時與函數體的聯繫在運行時創建,就是所謂的動態聯編,那麼在虛函數的派生類在運行的時候,就能夠根據動態聯編實現執行一個方法,卻又不一樣的效果,這就是多態,能夠解決上節遇到的問題
咱們只須要把基類中的函數變成虛函數就能夠了,以下
#include <iostream>
using namespace std;
class A{
public:
virtual void print(){
cout<<"A"<<endl;
};
};
class B : public A{
public:
void print(){
cout <<"B"<<endl;
}
};
int main(int argc, const char * argv[]) {
A a;
a.print();
B b;
b.print();
A *p;
p=&b;
p->print();
A &pp=b;
pp.print();
return 0;
}
複製代碼
輸出
A
B
B
B
複製代碼
此次的輸出就符合咱們的指望,這就是多態
須要注意的是
在C++中,不能把構造函數定義爲虛構函數,由於在實例化一個對象時纔會調用構造函數,切虛函數的實現本質上是經過一個虛擬函數指針表來調用的,尚未對象,沒有內存空間固然沒法調用
但析構函數能夠是虛函數,且大多數時候都聲明爲虛構函數,這樣就能夠用基類的指針,指向派生類對象在釋放時,能夠根據實際指向的對象類型,動態聯編子類的析構函數,來實現正確的內存釋放
#include <iostream>
using namespace std;
class A{
public:
char *str;
A(){
this->str=new char[100];
cout<<"A的構造函數"<<endl;
}
~A(){
delete []str;
cout<<"A的析構函數"<<endl;
}
};
class B : public A{
public:
char *str;
B(){
this->str=new char[100];
cout<<"B的構造函數"<<endl;
}
~B(){
delete [] str;
cout<<"B的析構函數"<<endl;
}
};
int main(int argc, const char * argv[]) {
A *p;
p=new B();
delete p;
return 0;
}
複製代碼
輸出
A的構造函數
B的構造函數
A的析構函數
複製代碼
咱們能夠看到基類中的析構函數沒有加virtual
,且基類和派生類中都有動態的內存開闢,當時只執行了基類的析構函數,不能正確的釋放內存
#include <iostream>
using namespace std;
class A{
public:
char *str;
A(){
this->str=new char[100];
cout<<"A的構造函數"<<endl;
}
virtual ~ A(){
delete []str;
cout<<"A的析構函數"<<endl;
}
};
class B : public A{
public:
char *str;
B(){
this->str=new char[100];
cout<<"B的構造函數"<<endl;
}
~B(){
delete [] str;
cout<<"B的析構函數"<<endl;
}
};
int main(int argc, const char * argv[]) {
A *p;
p=new B();
delete p;
return 0;
}
複製代碼
輸出
A的構造函數
B的構造函數
B的析構函數
A的析構函數
複製代碼
這樣就能夠正確的釋放內存
純虛函數就是沒有方法體的函數
virtual 返回值 函數名(形參)=0;
複製代碼
前面和虛函數同樣,後面加一個=0,表示沒有函數體,包括純虛函數的類就是抽象類,一個抽象類至少有一個純虛函數
抽象類的特色以下