Android 音視頻入門:C++語言基礎

概述

上篇學習了C語言,這篇繼續學習C++,這篇是創建在C基礎上的java

真的是碼字如蝸牛,寫了很久才寫這麼多,做爲筆記吧,防止之後忘記ios

先寫一個Hello world

首先打開你的文本編輯器,輸入一下內容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!!!!!
複製代碼

C++的程序結構

#include <iostream>
using namespace std;
 
// main() 是程序開始執行的地方
 
int main() {
   cout << "Hello World"; // 輸出 Hello World
   return 0;
}
複製代碼
  • 第一行 添加頭文件,和C的做用同樣
  • 第二行using namespace std;命名空間,是C++新加的概念
  • int main()程序的入口
  • cout << "Hello World"在屏幕上輸出Hello world
  • return 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;
}
複製代碼

第三種,用using和域限定符一塊兒制定用那些名字

#include<iostream>
using std::cout;
using std::endl;
int main() {
    cout<<"Nice to meet you!"<<endl;
    return 0;
}
複製代碼

C++中的輸入輸出

C++中的輸入輸出除了兼容C的寫法及使用printfscanf實現外,C++還有本身的一套輸入輸出的寫法,C++的輸入流和輸出流分別使用cincout來表示,使用以前須要引入標準庫iostream,即 #include <iostream>this

cout 輸出流的使用

cout輸出流須要搭配<< 輸出操做符來使用,例如輸出語句

cout << "hello world";
複製代碼

會在屏幕上顯示hello world

本質是將hello world 插入cout 對象,並以cout對象爲返回值返回,所以能夠在後方接多個<<

cout << "hello world" << "kk";
複製代碼

cout 常常和endl配合使用,起到換行符的做用

cout << "hello" << endl<<"world"<<endl;
複製代碼

輸出

hello
world
複製代碼

cin 輸入流的使用

接收一個數據前,都要先定義一個與之類型相一致的變量,而後利用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 空間同樣

一些基本數據類型能夠用一個或多個修飾符修飾

  • signed
  • unsigned
  • short
  • long

下表顯示了各類變量類型所佔內存和能儲存的最大值和最小值

類型 範圍
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;
}
複製代碼

這種形式成員函數,僅僅在類內聲明函數原型,再類外定義函數體,在類外定義函數體的須要類名加上::域限定符

對象的創建和使用

1 對象的建立

Status st;
    st.a=1;
    st.socre=100;
    strcpy(st.name, "wang");

    st.print();
複製代碼

2 對象指針

Sts *p;
    Sts s;
    
    s.a=100;
    s.socre=200;
    strcpy(s.name, "xin");
    
    p=&s;

    p->print();
複製代碼

指針能夠經過->來訪問成員變量和方法

傳遞參數的時候建議使用指針來傳遞,由於傳遞的是地址,不會進行對象間的副本賦值,從而提升效率,減小內存開銷

3 對象引用

引用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++的構造函數

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
複製代碼

C++中的析構函數

對象建立時會調用構造函數,對象銷燬時會調用析構函數,他也和類同名,也沒有返回值,名字前有一個波浪線~,來區分構造函數,它主要作對象釋放後的清理工做

若是用戶不定義,則系統會自動生成一個,若是用戶定義,則會在對象銷燬時自動調用

構造函數能夠重載,可是析構函數不能夠重載,但他能夠是虛函數,一個類只能有一個析構函數

#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++中的拷貝構造函數

在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
複製代碼

C++中常數據使用及其初始化

被關鍵字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,表示沒有函數體,包括純虛函數的類就是抽象類,一個抽象類至少有一個純虛函數

抽象類的特色以下

  • 抽象類沒法實例出一個對象,只能做爲基類讓派生類去實現抽虛函數,而後再實例化使用
  • 抽象的派生類依然不能夠實現基類中的純虛函數,繼續做爲抽象類被派生
  • 抽象類由於抽象沒法具化,因此不能做爲參數類型返回值,強轉類型
  • 但抽象類能夠定義一個指針,引用,指向派生類來實現多態性

參考: www.dotcpp.com/course/82

相關文章
相關標籤/搜索