C++多態(tài)特性之派生與虛函數(shù)與模板詳細介紹
繼承與派生
C ++ 是面向對象編程,那么只要面向對象,都會有多態(tài)、繼承的特性。C++是如何實現(xiàn)繼承的呢?
繼承(Inheritance)可以理解為一個類從另一個類獲取成員變量和成員函數(shù)的過程。例如類 B 繼承于類 A,那么 B 就擁有 A 的成員變量和成員函數(shù)。
在C++中,派生(Derive) 和繼承是一個概念,只是站的角度不同。繼承是兒子接收父親的產(chǎn)業(yè),派生是父親把產(chǎn)業(yè)傳承給兒子。
被繼承的類稱為父類或基類,繼承的類稱為子類或派生類。“子類”和“父類”通常放在一起稱呼,“基類”和“派生類”通常放在一起稱呼。
在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派生對于,創(chuàng)建的對象調用父類的屬性和方法起到了限制和保護的作用
Child child; child.name; // public繼承。調用者可以訪問到父類公有屬性,私有屬性訪問不到的 Child1 child1; // child1.name; // private繼承.調用者訪問不到父類公有屬性和私有屬性 Child2 child2; // child2.name; // protected繼承,調用者訪問不到父類公有屬性和私有屬性
虛函數(shù)
重點?。。?C++的繼承和java中的繼承存在的不同點: 基類成員函數(shù)和派生類成員函數(shù)不構成重載
基類成員和派生類成員的名字一樣時會造成遮蔽,這句話對于成員變量很好理解,對于成員函數(shù)要引起注意,不管函數(shù)的參數(shù)如何,只要名字一樣就會造成遮蔽。換句話說,基類成員函數(shù)和派生類成員函數(shù)不會構成重載,如果派生類有同名函數(shù),那么就會遮蔽基類中的所有同名函數(shù),不管它們的參數(shù)是否一樣。?
父類代碼如下
#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; // 如果調用了這個方法只會調用一次析構函數(shù) // } // cout << "parent destroy" << endl; } };
子類繼承父類,并且調用父類的構造函數(shù), 通過:來調用父類的構造函數(shù)
// 子類 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; // 會不會調用父類的拷貝函數(shù)呢? copy parent 會進行調用
---------
parent printC
parent printC
-------
parent
parent printC
parent printC
--------
parent
child
copy parent
child destroy
child destroy
這是為什么呢?
C++ 中會按照函數(shù)表的順序進行調用,很顯然父類的函數(shù)是在子類函數(shù)的前面的
那么如何調用到子類的方法呢?C ++提供了虛函數(shù)的方式,虛函數(shù)也是實現(xiàn)多態(tài)的關鍵。?
虛函數(shù)與純虛函數(shù),純虛函數(shù)在java 中 abstract == 純虛函數(shù)
實際開發(fā)中,一旦我們自己定義了析構函數(shù),就是希望在對象銷毀時用它來進行清理工作,比如釋放內(nèi)存、關閉文件等,如果這個類又是一個基類,那么我們就必須將該析構函數(shù)聲明為虛函數(shù),否則就有內(nèi)存泄露的風險。也就是說,大部分情況下都應該將基類的析構函數(shù)聲明為虛函數(shù)。
包含純虛函數(shù)的類稱為抽象類(Abstract Class)。之所以說它抽象,是因為它無法實例化,也就是無法創(chuàng)建對象。原因很明顯,純虛函數(shù)沒有函數(shù)體,不是完整的函數(shù),無法調用,也無法為其分配內(nèi)存空間。
抽象類通常是作為基類,讓派生類去實現(xiàn)純虛函數(shù)。派生類必須實現(xiàn)純虛函數(shù)才能被實例化。
- 一個純虛函數(shù)就可以使類成為抽象基類,但是抽象基類中除了包含純虛函數(shù)外,還可以包含其它的成員函數(shù)(虛函數(shù)或普通函數(shù))和成員變量。
- 只有類中的虛函數(shù)才能被聲明為純虛函數(shù),普通成員函數(shù)和頂層函數(shù)均不能聲明為純虛函數(shù)。
- 基類的析構函數(shù)必須聲明為虛函數(shù)。
#include <iostream> using namespace std; class Person { public: // 增加了一個虛函數(shù)表的指針 // 虛函數(shù) 子類可以覆寫的函數(shù) virtual void look() { cout << "virtual look" << endl; } // 純虛函數(shù) 必須要讓子類實現(xiàn)的 virtual void speak() {}; // 基類的析構函數(shù)必須聲明為虛函數(shù) virtual ~Person() { cout << "~Person" << endl; } }; class Child : public Person { public: // 子類實現(xiàn)純虛函數(shù) 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 這就表明了虛函數(shù)是有一個虛函數(shù)表,增加一個指針*vtable,指向了虛函數(shù)表 // 下面代碼來證明 typedef void (*func)(void); func fun = NULL; cout << (int *) &p << endl; // 指向函數(shù)的首地址 0x16ee1efa8 cout << (int *) *(int *) &p << endl; // 函數(shù)的地址 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)和實現(xiàn)(.cpp)分開寫,「不能將模板的聲明和定義分散到多個文件中」的根本原因是:模板的實例化是由編譯器完成的,而不是由鏈接器完成的,這可能會導致在鏈接期間找不到對應的實例。
函數(shù)模板
#include <iostream> #include <string> #include <cstring> using namespace std; /** * 函數(shù)模板和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; } // 普通函數(shù)優(yōu)先級比泛型函數(shù)高,只有類型重合的狀態(tài)下 void swapT(int &a, int &b) { cout << "swap : int a int b" << endl; int temp = a; a = b; b = temp; } int main() { // 函數(shù)模板 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; // 這里不可以返回,因為方法執(zhí)行完畢后會銷毀掉 return a; // 返回值是引用 } }; /** * 和java不同的部分,比java更加靈活 */ class Pp { public: void show() { cout << "Pp show" << endl; } }; template<typename T> class ObjTemp { private: T obj; public: void showPp() { // 自動檢查 但是會出現(xiàn)不可預期的錯誤 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
實現(xiàn)一個模板類ArrayList類似Java的列表實現(xiàn):
注意在之前學習的.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; // 在堆區(qū)申請數(shù)組 this->arr = new T[this->size]; // 在堆中開辟的一塊空間 存儲的是一個int[size] 數(shù)組,arr指向數(shù)組的首地址 } // 拷貝函數(shù) ArrayList(const ArrayList &arrayList) { this->size = arrayList.size; this->realSize = arrayList.realSize; this->arr = new T[arrayList.size]; // 將數(shù)組的值賦值到arr中 for (int i = 0; i < this->size; ++i) { this->arr[i] = arrayList.arr[i]; // arrayList.arr[i]他也是指針 this->arr[i] 是指針 } } // 析構函數(shù) ~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++; // 數(shù)據(jù)量大小+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; } // 如何移除呢?循環(huán)往前移動 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 定義為常函數(shù) int getLength() const { // realSize = realSize - 1; 這樣會報錯 不能修改函數(shù)內(nèi)部的所有變量 c = 11; // mutable 修飾的變量可以在常函數(shù)中修改 return realSize; } bool isEmpty() const { return realSize == 0; } void resize() { int netLength = size * 2; T *p = new T[netLength]; // 拷貝數(shù)據(jù) for (int i = 0; i < size; ++i) { *(p + i) = this->arr[i]; } // 釋放之前的數(shù)組 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{}; // 真實的數(shù)組長度 T *arr; // 這里不能使用數(shù)組,因為數(shù)組名是arr指針常量,不能對arr重新賦值, 指針是指針變量,而數(shù)組名只是一個指針常量 mutable int c = 10; // 可以在常函數(shù)中修改的變量 需要使用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是一個對象,內(nèi)部封裝了和C一樣的字符串的表現(xiàn)形式 string s1(); string s2("123"); string s3 = "wew"; // string字符串是聲明在堆區(qū)的 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 = "后端碼匠"; // 存儲在堆區(qū) 方法執(zhí)行完畢 執(zhí)行析構函數(shù) 從堆區(qū)移除 // 一般不會這樣使用 const char *s_c = s.c_str(); // 將C++ string轉換為支持C的字符串,返回常量指針 指針指向了常量,不能通過指針來修改常量 printf("%s\n", s_c); // 一般開發(fā)會使用strcpy拷貝,防止被銷毀掉等問題 在FFmpeg是使用的C,所以在使用C++開發(fā)時必須要對C的轉換 char ss[20]; strcpy(ss, s.c_str()); // 拷貝到一個新的變量中 return 0; }
kkkk 2345
123wewew
3we
后端碼匠
到此這篇關于C++多態(tài)特性之派生與虛函數(shù)與模板詳細介紹的文章就介紹到這了,更多相關C++派生 虛函數(shù) 模板內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++中使用哈希表(unordered_map)的一些常用操作方法
C++標準庫中使用的unordered_map底層實現(xiàn)是哈希表,下面這篇文章主要給大家介紹了關于C++中使用哈希表(unordered_map)的一些常用操作方法,需要的朋友可以參考下2022-03-03