網站首頁 編程語言 正文
繼承與派生
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
相關推薦
- 2022-09-19 用正則表達式匹配字符串中漢字及中文標點符號_正則表達式
- 2022-07-12 從GitHub(git)上指定分支clone代碼
- 2022-09-13 Golang優雅保持main函數不退出的辦法_Golang
- 2023-01-30 React合成事件及Test?Utilities在Facebook內部進行測試_React
- 2022-09-29 Kotlin協程Dispatchers原理示例詳解_Android
- 2022-12-21 OpenHarmony實現屏幕亮度動態調節方法詳解_Android
- 2023-03-04 c++元編程模板函數重載匹配規則示例詳解_C 語言
- 2022-09-29 C語言開發實現通訊錄管理系統_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支