日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C++多態特性之派生與虛函數與模板詳細介紹_C 語言

作者:后端碼匠 ? 更新時間: 2022-11-04 編程語言

繼承與派生

C ++ 是面向對象編程,那么只要面向對象,都會有多態、繼承的特性。C++是如何實現繼承的呢?

繼承(Inheritance)可以理解為一個類從另一個類獲取成員變量和成員函數的過程。例如類 B 繼承于類 A,那么 B 就擁有 A 的成員變量和成員函數。

在C++中,派生(Derive) 和繼承是一個概念,只是站的角度不同。繼承是兒子接收父親的產業,派生是父親把產業傳承給兒子。

被繼承的類稱為父類或基類,繼承的類稱為子類或派生類。“子類”和“父類”通常放在一起稱呼,“基類”和“派生類”通常放在一起稱呼。

在C++中繼承稱為派生類,基類孵化除了派生類,使用:來表示子類繼承父類,C++中支持多繼承,使用逗號分隔

class Parent {
public:
    int name;
protected:
    int code;
private:
    int num;
};
class Parent1 {
};
// C++中,:表示繼承,可以多繼承逗號分隔
// public/protected/private繼承,對于基類起到一些保護機制 默認是private繼承
class Child : public Parent, Parent1 {
    void test() {
        // 派生類可以訪問到public屬性和protected屬性
        this->name;
        this->code;
    }
};

C++中派生類中添加了public 派生、protected派生、private派生,默認是private派生

class 派生類名:[繼承方式] 基類名{ 派生類新增加的成員 };

class Parent {
public:
    int name;
protected:
    int code;
private:
    int num;
};
class Parent1 {
};
// private私有繼承
class Child1 : private Parent {
    void test() {
        this->name;
        this->code;
    }
};
// protected繼承
class Child2 : protected Parent {
    void test() {
        this->name;
        this->code;
    }
};

public 派生、protected派生、private派生對于,創建的對象調用父類的屬性和方法起到了限制和保護的作用

    Child child;
    child.name; // public繼承。調用者可以訪問到父類公有屬性,私有屬性訪問不到的
    Child1 child1;
//    child1.name; // private繼承.調用者訪問不到父類公有屬性和私有屬性
    Child2 child2;
//    child2.name; // protected繼承,調用者訪問不到父類公有屬性和私有屬性

虛函數

重點!!! C++的繼承和java中的繼承存在的不同點: 基類成員函數和派生類成員函數不構成重載

基類成員和派生類成員的名字一樣時會造成遮蔽,這句話對于成員變量很好理解,對于成員函數要引起注意,不管函數的參數如何,只要名字一樣就會造成遮蔽。換句話說,基類成員函數和派生類成員函數不會構成重載,如果派生類有同名函數,那么就會遮蔽基類中的所有同名函數,不管它們的參數是否一樣。

父類代碼如下

#include <cstring>
#include <iostream>
using namespace std;
class Person {
protected:
    char *str;
public:
    Person(char *str) {
        if (str != NULL) {
            this->str = new char[strlen(str) + 1];
            strcpy(this->str, str);
        } else {
            this->str = NULL;
        }
        cout << "parent" << endl;
    }
    Person(const Person &p) {
        cout << "copy parent" << endl;
    }
    void printC() {
        cout << "parent printC" << endl;
    }
    ~Person() {
//        if (str != NULL) {
//            delete[] str; // 如果調用了這個方法只會調用一次析構函數
//        }
//        cout << "parent destroy" << endl;
    }
};

子類繼承父類,并且調用父類的構造函數, 通過:來調用父類的構造函數

// 子類
class CTest : public Person {
public:
    // 調用父類的構造方法
    CTest(char *str) : Person(str) {
        cout << "child" << endl;
    }
    void printC() {
        cout << "child printC" << endl;
    }
    ~CTest() {
        cout << "child destroy " << endl;
    }
};

在C++中和Java的不同在于如下代碼:只要是父類的指針都是調用的父類的方法,哪怕子類對象直接賦值給父類,也會調用父類的方法,而不會調用子類的方法。

int main() {
    Person person = CTest("jake");
    person.printC(); // parent printC
    cout << "-----------" << endl;
    Person *p = NULL;
    CTest c1("123");
    p = &c1;
    c1.printC(); // child printC
    p->printC(); // parent printC 為什么會調用的是parent的方法呢?
    return 0;
}

parent
child
copy parent
child destroy 
parent printC
-----------
parent
child
child printC
parent printC
child destroy 

哪怕通過指針傳遞和引用傳遞,只要使用的父類都會調用父類的方法

// 通過指針傳遞只會調用父類的方法,不會調用子類的方法
void howToPaint(Person *p) {
    p->printC();
}
// 通過引用類型,只會調用父類的方法,不會調用子類的方法
void howToPaint1(Person &p) {
    p.printC();
}
cout << "---------" << endl;
howToPaint(p); // parent printC
howToPaint(&c1); // parent printC
cout << "-------" << endl;
Person p1("123");
// 都是父類的方法
howToPaint1(p1); // parent printC
howToPaint1(c1); // parent printC
cout << "--------" << endl;
CTest c2("123");
Person p2 = c2; // 會不會調用父類的拷貝函數呢? copy parent 會進行調用

---------
parent printC
parent printC
-------
parent
parent printC
parent printC
--------
parent
child
copy parent
child destroy 
child destroy 

這是為什么呢?

C++ 中會按照函數表的順序進行調用,很顯然父類的函數是在子類函數的前面的

那么如何調用到子類的方法呢?C ++提供了虛函數的方式,虛函數也是實現多態的關鍵。

虛函數與純虛函數,純虛函數在java 中 abstract == 純虛函數

實際開發中,一旦我們自己定義了析構函數,就是希望在對象銷毀時用它來進行清理工作,比如釋放內存、關閉文件等,如果這個類又是一個基類,那么我們就必須將該析構函數聲明為虛函數,否則就有內存泄露的風險。也就是說,大部分情況下都應該將基類的析構函數聲明為虛函數。

包含純虛函數的類稱為抽象類(Abstract Class)。之所以說它抽象,是因為它無法實例化,也就是無法創建對象。原因很明顯,純虛函數沒有函數體,不是完整的函數,無法調用,也無法為其分配內存空間。

抽象類通常是作為基類,讓派生類去實現純虛函數。派生類必須實現純虛函數才能被實例化。

  • 一個純虛函數就可以使類成為抽象基類,但是抽象基類中除了包含純虛函數外,還可以包含其它的成員函數(虛函數或普通函數)和成員變量。
  • 只有類中的虛函數才能被聲明為純虛函數,普通成員函數和頂層函數均不能聲明為純虛函數。
  • 基類的析構函數必須聲明為虛函數。
#include <iostream>
using namespace std;
class Person {
public:
    // 增加了一個虛函數表的指針
    // 虛函數 子類可以覆寫的函數
    virtual void look() {
        cout << "virtual look" << endl;
    }
    // 純虛函數 必須要讓子類實現的
    virtual void speak() {};
    // 基類的析構函數必須聲明為虛函數
    virtual ~Person() {
        cout << "~Person" << endl;
    }
};
class Child : public Person {
public:
    // 子類實現純虛函數
    void speak() override {
        cout << "child speak" << endl;
    }
    // 訪問父類的方法
    void look() override {
        cout << "child look" << endl;
        Person::look();
    }
    ~Child() {
        cout << "~Child" << endl;
    }
};
int main() {
    Person *person = new Child(); // 必須通過指針的方式,不同通過棧的方式去派生抽象
    person->speak(); // child speak
    person->look(); // child look
    Person p;
    cout << sizeof(p) << endl; // 8 這就表明了虛函數是有一個虛函數表,增加一個指針*vtable,指向了虛函數表
    // 下面代碼來證明
    typedef void (*func)(void);
    func fun = NULL;
    cout << (int *) &p << endl; // 指向函數的首地址 0x16ee1efa8
    cout << (int *) *(int *) &p << endl; // 函數的地址 0xfe40a0
    fun = (func) *((int *) *(int *) &p);
    fun(); // virtual look
    return 0;
}
/**
 * child speak
 * child look
 * virtual look
 * ~Child
 * ~Person
 */

child speak
child look
virtual look
8
0x16ee1efa8
0xfe40a0

模板

模板和java的泛型類似。 模板類不支持聲明(.h)和實現(.cpp)分開寫,「不能將模板的聲明和定義分散到多個文件中」的根本原因是:模板的實例化是由編譯器完成的,而不是由鏈接器完成的,這可能會導致在鏈接期間找不到對應的實例。

函數模板

#include <iostream>
#include <string>
#include <cstring>
using namespace std;
/**
 * 函數模板和java中的泛型類似
 */
// 方法泛型 這里只能聲明在方法上
template<typename T, typename R=int>
// R的默認類型是int
// typename == class 兩個等價的
void swap2(T t, R r) {
}
template<typename T>
void swapT(T &a, T &b) {
    cout << "swap: T a T b" << endl;
    T temp = a;
    a = b;
    b = temp;
}
// 普通函數優先級比泛型函數高,只有類型重合的狀態下
void swapT(int &a, int &b) {
    cout << "swap : int a int b" << endl;
    int temp = a;
    a = b;
    b = temp;
}
int main() {
    // 函數模板
    int a = 10;
    int b = 20;
    char c = 'a';
    swapT<int>(a, b); // 顯示調度
    swapT(a, b); // 自動推導
//    swap(a,c); // 報錯 無法推導出具體的類型
//    swap2(); // 報錯 無法推導出具體的類型
    char *a1 = "abc";
    char *a2 = "123";
    cout << a1 << a2 << endl;
    swapT(a1, a2);
    cout << a1 << a2 << endl;
    return 0;
}

swap: T a T b
swap : int a int b
abc123
swap: T a T b
123abc

類模板

#include <iostream>
#include <cstring>
using namespace std;
// 模板修飾在類上
template<typename T, typename R>
class Person {
public:
    T a;
    R b;
    Person(T t) {
    }
    T &getA() {
        T t1;
//        return t1; // 這里不可以返回,因為方法執行完畢后會銷毀掉
        return a; // 返回值是引用
    }
};
/**
 * 和java不同的部分,比java更加靈活
 */
class Pp {
public:
    void show() {
        cout << "Pp show" << endl;
    }
};
template<typename T>
class ObjTemp {
private:
    T obj;
public:
    void showPp() {
        // 自動檢查 但是會出現不可預期的錯誤
        obj.show(); // 假設模板是Pp,可以調用Pp的變量和方法,在java中需要<T extend Pp> T才能調用方法
    }
};
template<typename T, typename R>
class CTest {
public:
    T m_name;
    R m_age;
    CTest(T name, R age) {
        this->m_name = name;
        this->m_age = age;
    }
    void show() {
        cout << "show T:" << m_name << " R:" << m_age << endl;
    }
};
template<typename T, typename R>
void doWork(CTest<T, R> &cTest) {
    cTest.show();
}
template<typename T>
void doWork2(T &t) {
    t.show(); // 在java中必須是<T extend xxxx>
}
// 繼承模板問題和java是一樣的
template<typename T>
class Base {
public:
    T t;
};
// 確定的類型或者模板
template<typename T, typename R>
class Son : Base<R> {
public:
    T t1;
};
int main() {
    CTest<string, int> test("后端碼匠", 28); // show T:后端碼匠 R:28
    doWork(test);
    doWork2<CTest<string, int>>(test); // 顯示調用
    doWork2(test); // 自動推導
    ObjTemp<Pp> temp;
    temp.showPp(); // Pp show 可以調用傳遞過來的模板的方法
    // 自動類型推導,在類模板上不可以使用,無法推導出具體的類型
    Person<int, string> p(100);
    cout << p.getA() << endl;
    return 0;
}

show T:后端碼匠 R:28
show T:后端碼匠 R:28
show T:后端碼匠 R:28
Pp show
0

實現一個模板類ArrayList類似Java的列表實現:

注意在之前學習的.h和.cpp分開的方式,不支持模板,一般模板的部分都會合并到.h文件中。

#include <iostream>
#include <cstring>
using namespace std;
#ifndef CPPDEMO_ARRAYLIST_H
#define CPPDEMO_ARRAYLIST_H
template<typename T>
class ArrayList {
public:
    int d = 11;
    ArrayList() {
        this->size = 16;
        this->realSize = 0;
        this->arr = new T[this->size];
    }
    // explicit 不能通過隱式調用
    explicit ArrayList(int capacity) {
        this->size = capacity;
        this->realSize = 0;
        // 在堆區申請數組
        this->arr = new T[this->size]; // 在堆中開辟的一塊空間 存儲的是一個int[size] 數組,arr指向數組的首地址
    }
    // 拷貝函數
    ArrayList(const ArrayList &arrayList) {
        this->size = arrayList.size;
        this->realSize = arrayList.realSize;
        this->arr = new T[arrayList.size];
        // 將數組的值賦值到arr中
        for (int i = 0; i < this->size; ++i) {
            this->arr[i] = arrayList.arr[i]; // arrayList.arr[i]他也是指針  this->arr[i] 是指針
        }
    }
    // 析構函數
    ~ArrayList() {
        if (this->arr != nullptr) {
            delete[] this->arr;
            this->arr = nullptr;
        }
    }
    void add(T val) {
        add(val, this->realSize);
    }
    void add(T val, int index) {
        if (index < 0 || index > size) {
            return;
        }
        // 判斷容量是否夠大 不夠進行擴容
        if (this->realSize >= size * 0.75) {
            resize();
        }
        this->arr[index] = val; // 等價于   *((this->arr)+index) = val
        this->realSize++; // 數據量大小+1
    }
    T get(int index) {
        if (index < 0 || index >= realSize) {
            return -1;
        }
        return this->arr[index];
    }
    T remove(int index) {
        if (index < 0 || index >= realSize) {
            return -1;
        }
        // 如何移除呢?循環往前移動
        int result = this->arr[index];
        for (int i = index; i < size - 1; ++i) {
            this->arr[i] = this->arr[i + 1];
        }
        this->realSize--;
        // 判斷縮減容量
        return result;
    }
    // const 定義為常函數
    int getLength() const {
        // realSize = realSize - 1; 這樣會報錯 不能修改函數內部的所有變量
        c = 11; // mutable 修飾的變量可以在常函數中修改
        return realSize;
    }
    bool isEmpty() const {
        return realSize == 0;
    }
    void resize() {
        int netLength = size * 2;
        T *p = new T[netLength];
        // 拷貝數據
        for (int i = 0; i < size; ++i) {
            *(p + i) = this->arr[i];
        }
        // 釋放之前的數組
        delete[] this->arr;
        // 重新賦值
        this->arr = p;
        this->size = netLength;
    }
    void toString() {
        cout << "[ ";
        for (int i = 0; i < realSize; ++i) {
            cout << arr[i] << ", ";
        }
        cout << " ] " << endl;
    }
private:
    int size{}; // 容器的大小
    int realSize{}; // 真實的數組長度
    T *arr; // 這里不能使用數組,因為數組名是arr指針常量,不能對arr重新賦值, 指針是指針變量,而數組名只是一個指針常量
    mutable int c = 10; // 可以在常函數中修改的變量 需要使用mutable進行修飾
};
int main() {
    ArrayList<int> arrayList;
    arrayList.add(1);
    arrayList.add(2);
    arrayList.add(3);
    arrayList.add(4);
    arrayList.add(5);
    arrayList.add(6);
    for (int i = 0; i < arrayList.getLength(); ++i) {
        cout << arrayList.get(i) << endl;
    }
    return 0;
}
#endif // CPPDEMO_ARRAYLIST_H

1
2
3
4
5
6

字符串

int main() {
    // 字符串 string 是C++獨有的string是一個對象,內部封裝了和C一樣的字符串的表現形式
    string s1();
    string s2("123");
    string s3 = "wew"; // string字符串是聲明在堆區的
    string s4(4, 'k'); // 4個K組成 kkkk
    string s5("123456", 1, 4); // 從1開始,輸出四個字符串:2345
    cout << s4 << " " << s5 << endl;
    s2.append(s3); // 追加123wew
    s2.append(s3, 1, 2); // ew
    cout << s2 << endl; // 123wewew
    string sub = s2.substr(2, 3); // 字符串裁剪
    cout << sub << endl; // 3we
    s4.swap(s5); // 字符串交換,只有引用和地址才會改變外部的值
    // c_str 支持C,轉換為char *
    string s = "后端碼匠"; // 存儲在堆區 方法執行完畢 執行析構函數 從堆區移除
    // 一般不會這樣使用
    const char *s_c = s.c_str(); // 將C++ string轉換為支持C的字符串,返回常量指針 指針指向了常量,不能通過指針來修改常量
    printf("%s\n", s_c);
    // 一般開發會使用strcpy拷貝,防止被銷毀掉等問題 在FFmpeg是使用的C,所以在使用C++開發時必須要對C的轉換
    char ss[20];
    strcpy(ss, s.c_str()); // 拷貝到一個新的變量中
    return 0;
}


原文鏈接:https://blog.csdn.net/weixin_43874301/article/details/126737650

欄目分類
最近更新