以項目的方式學QT開發(一)——超詳細講解(120000多字詳細講解,涵蓋qt大量知識)逐步更新!

以項目的方式學QT開發

以項目的方式學QT開發

P1 QT介紹

1.1 QT簡介

1.2 QT安裝

1.2.1 Windows QT安裝

1.2.2 QT Creator 使用基本介紹

P2 C++基礎

2.1 命名空間

2.1.1 命名空間作用

2.1.2 自定義命名空間

2.2 C語言快速入門

2.2.1 輸入輸出

2.2.2 基本變量類型

2.2.3 流程控制

2.2.4 函數

2.2.5 內聯函數

2.2.6 Lambda 表達式

2.2.7 數組

2.2.8 練習

2.2.9 指針

2.2.9 字符串string類型

2.3

2.3.1 類的初探

2.3.2 結構體引入類

2.3.2.1 回憶結構體

2.3.2.2 新建C++工程來使用結構體

2.3.2.3 真正的成員函數

2.3.4 QT中經常出現的用法

2.4 權限初識

2.4.1 基本介紹

2.4.2 目前能概況的結論

2.4.3 提問和回答

2.5 引用

2.6.1 和指針的區別

2.6.2 把引用作為參數

2.6.3 把引用作為返回值

2.6 重載

2.5.1 函數重載

2.5.2 運算符重載

2.7 構造函數

2.7.1 什么是構造函數

2.7.2 帶參數構造函數

2.7.3 拷貝構造函數

2.7.3.1 基本概念及發生條件

2.7.3.2 淺拷貝

2.7.3.3 深拷貝

2.7.3.4 規則三則

2.7.3.5 避免不必要的拷貝

2.7.3.6 拷貝構造函數的隱式調用

2.7.3.7 禁用拷貝構造函數2.7.3.8 拷貝構造函數總結

2.7.4 使用初始化列表

2.7.5 this關鍵字

2.7.6 new關鍵字

2.8 析構函數

2.8.1 什么是析構函數

2.9 靜態成員

2.9.1 靜態成員的定義

2.9.2 靜態成員變量的作用

2.10 繼承

2.10.1 繼承基本概念

2.10.2 權限對繼承的影響

2.10.3 基類構造函數

2.10.4 虛函數

virtual 關鍵字

override 關鍵字

2.10.5 多重繼承

2.10.6 虛繼承

菱形繼承問題示例

使用虛繼承解決菱形繼承問題

2.11 多態

2.11.1 如何實現多態

2.11.2 抽象類

2.11.3 純虛函數-接口

2.12 友元

2.12.1 什么是友元

2.12.2 友元函數

2.12.3 友元類

2.12.4 友元成員函數

2.13 模板

2.13.1 類模板

2.13.2 函數模板

2.13.3 模板特化

2.14 標準模板庫STL

2.14.1 容器

2.14.2 vector

2.14.3 list

2.14.4 set

2.14.5 map

2.15 異常

2.15.1 異常基本

2.15.2 自定義異常

P3 記事本項目

3.1 項目概述

3.1.1 功能介紹

3.1.2 界面預覽

3.2.3 工程概述

3.2 UI設計師工具

3.2.1 按鍵 QPushButton

3.2.2 水平布局 QHBoxLayout

3.2.3 文本編輯器 TextEdit

3.2.4 垂直布局 QVBoxLayout

3.2.5 主窗體元素設計

3.3 按鍵響應-初識信號與槽

3.3.1 信號與槽基本介紹

3.3.2 按鍵QPushButton設置信號與槽3.3.3 自定義信號與槽

3.3 文件操作類 QFile

3.3.3 QTextStream

3.4 文件選擇對話框 QFileDialog

3.4.1 QFileDialog開發流程

3.4.2 QFileDialog 打開開發案例

3.4.3 QFileDialog 保存開發案例

3.6 實現文件打開功能

3.6.1 開發流程

3.6.2 代碼實現

3.6.3 打開功能優化

3.6.4 QComboBox

3.6.5 記事本支持字符編碼

3.6.6 添加行列顯示

3.6.7 添加文件打開提示

3.6.8 設置當前行高亮

3.6.8.1 QList

3.8.2 ExtraSelection 簡介

3.7 文件保存功能優化

3.7.1 開發流程

3.8 關閉優化

3.8.1 消息對話框 QMessageBox

3.7.3 代碼實現

3.9 實現快捷鍵功能

3.9.1 快捷鍵開發基礎

3.9.2 上官記事本添加快捷鍵

3.10 實現字體放大縮小功能

3.10.1 滾動調節字體大小的流程

3.10.2 本節筆記失誤

3.10.3 檢測Ctrl鍵被按下

3.10.4 記事本添加字體放大縮小

3.10.5 事件

事件處理過程

重寫事件案例

事件方式實現字體放大縮小

事件過濾器

3.10.6 鼠標滾輪和字體大小

3.12 記事本項目總結

P4 串口調試助手項目

4.1 項目概述

4.2 串口通信核心代碼開發

P5 網絡調試助手

5.1 TCP網絡調試助手

5.1.1 項目概述

5.1.2 開發流程

5.1.3 QTtcp服務器的關鍵流程

5.1.4 QTtcp客戶端的關鍵流程

5.1.2 TCP協議

5.1.4 Socket

5.2 UI設計

5.3 網絡通信核心代碼

5.3.1 創建TCP服務端的核心代碼

5.3.2 創建TCP客戶端的核心代碼

5.4 TCP服務端項目開發

5.5 TCP客戶端項目開發

5.6 項目總結P6 自定義控件

6.1 QPaintEvent繪圖事件

6.2 QPainter畫家

6.2.1 概述

6.2.2 漸變色

6.2.2.1 線性漸變

6.2.2.2 徑向漸變

6.2.2.3 圓錐形漸變

6.3 坐標轉移

6.4 畫雷達案例

6.5儀表表盤

6.5.1 初步完成

6.5.2 稍微美化

6.5.3 優化數字顯示后代碼整理

6.5.4 畫一個指針

6.5.5 內環

6.5.6 完結

6.6 汽車表盤參考樣式

P7 天氣預報項目

7.1項目概述

7.2 stylesheet樣式

7.3 窗體無狀態欄-關閉

7.4 窗口跟隨移動

7.5 天氣預報數據接口

7.6 軟件開發網絡通信架構

7.6.1 BS架構/CS架構

7.6.2 HTTP基本概念

7.7 QTHTTP編程

7.8 JSON數據

7.8.1 概述

7.8.2 QT生成JSON數據

7.8.3 QT解析JSON數據

P8 Ubuntu搭建QT開發環境

8.1 安裝Ubutnu22

8.1.1 下載和安裝Vmware

8.1.2 下載和安裝Ubuntu22

8.1.3 常用功能配置

8.2 安裝Ubuntu環境下的QT

8.2.1下載安裝UbuntuQT

8.2.2 Ubuntu中文支持

P9 加餐課

P1 QT介紹

1.1 QT簡介

Qt 是一個跨平臺的應用程序和用戶界面框架,用于開發圖形用戶界面(GUI)應用程序以及命令行工

具。它最初由挪威的?Trolltech (奇趣科技)公司開發,現在由?Qt Company 維護,2020128日發

QT6Qt 使用?C++ 語言編寫,支持多種編程語言通過綁定進行使用。

對于許多開發者和小型企業來說,Qt 的開源版提供了一個強大且靈活的開發框架,而對于需要額外支持

和專有功能的大型企業或具有特定需求的項目,商業版則提供了所需的服務和資源。Qt 商業版

商業版提供專有許可,需要購買許可證來使用。這適用于希望在不共享源代碼的情況下開發商業軟

件的公司和開發人員

QT免費開源版

開源版根據?GNU Lesser General Public License (LGPL) ?GNU General Public License (GPL)

布。這意味著用戶可以免費使用?Qt,但必須遵守特定的開源許可條款

版本

發布年

關鍵特性

Qt

1.x

1996

初始發布,專注于?X11 平臺

Qt

2.x

1999

引入了對?Microsoft Windows 的支持

Qt

3.x

2001

添加了許多新功能,包括網絡和?XML 支持

Qt

4.x

2005

重大改進,增強了跨平臺支持和圖形視圖框架

Qt

5.x

2012

專注于現代硬件的性能,引入了?QML ?Qt Quick 用于開發流暢的動

畫和觸摸界面

Qt

6.x

2020

進一步增強了性能和功能,針對未來的軟件開發趨勢進行了優化,包括

?3D 圖形的支持

QT主要歷史版本

學習者學習QT5QT6都是可以的,無論選擇哪個版本,Qt的基本概念和理念在各個版本之間是相通

的,因此你可以相對輕松地轉換到其他版本。本次我們基于QT5學習

成熟和穩定性

Qt 5已經存在了一段時間,經過了多個版本的迭代和改進。它在很多項目中被廣泛使用,證明了其

成熟性和穩定性。這對于在大型項目或生產環境中使用Qt的開發者來說是一個優勢。

豐富的文檔和社區支持

Qt 5有大量的文檔和社區支持。你可以輕松找到各種教程、示例和解決方案,這對于初學者來說是

非常寶貴的

廣泛的應用領域

Qt 5有大量的文檔和社區支持。你可以輕松找到各種教程、示例和解決方案,這對于初學者來說是

非常寶貴的。

1.2 QT安裝

1.2.1 Windows QT安裝

下載windowsQT安裝包

本教程使用的QT版本是:https://download.qt.io/archive/qt/5.12/5.12.9/?本教程的安裝包放在百度網

盤供大家獲取。?

QT安裝

如果沒有梯子,大家登錄QT官網可能會失敗,這里可以不需要QT賬號,直接離線安裝,所以要斷開網

絡。

選擇windows底下的編譯工具,QT源代碼,QT的繪圖模塊及QT的虛擬鍵盤

安裝完成后打開1.2.2 QT Creator 使用基本介紹

課程錄屏展示-創建并運行第一個QT項目

課程錄屏展示-創建并運行第一個C++項目

課程錄屏展示-QT Creator的界面介紹

功能

快捷鍵

中文說明

撤銷

CTRL + Z

撤銷最近的操作

重做

CTRL + Y

重做最近的撤銷操作

復制

CTRL + C

復制選中內容

粘貼

CTRL + V

粘貼內容

復制行向下

CTRL + ALT + DOWN

將當前行復制到下一行

復制行向上

CTRL + ALT + UP

將當前行復制到上一行

運行

CTRL + R

運行當前項目

返回編輯模式

ESCAPE

返回到編輯狀態

切換當前文件

CTRL + TAB

在打開的文件間切換

切換聲明和定義

F2

在代碼的聲明與定義間切換

切換頭文件和源文件

F4

在頭文件和源文件間切換

開始調試

F5

啟動調試

停止調試

SHIFT + F5

停止當前的調試

構建當前項目

CTRL + B

構建當前打開的項目

QT Creator常用的快捷鍵介紹功能

快捷鍵

中文說明

構建所有項目

CTRL + SHIFT + B

構建所有項目

新建文件或項目

CTRL + N

創建新文件或項目

打開文件或項目

CTRL + O

打開現有文件或項目

保存當前文件

CTRL + S

保存當前編輯的文件

保存所有文件

CTRL + SHIFT + S

保存所有打開的文件

關閉當前文件

CTRL + W

關閉當前文件

關閉所有文件

CTRL + SHIFT + W

關閉所有打開的文件

退出QT Creator

CTRL + Q

退出QT Creator

位置后退

ALT+Left

光標位置回退

P2 C++基礎

CC++之間的關系是緊密且復雜的。C++最初是作為C語言的一個擴展開發的,目的是在不放棄C的強大

功能和效率的同時,增加對象導向編程、泛型編程和其他一些特性。下面是CC++之間主要的關系和區

別:

1. 兼容性C++在很大程度上是與C兼容的。這意味著許多C程序可以在C++編譯器中編譯并運行,盡

管可能需要一些小的修改。

2. 面向對象編程(OOPC++引入了面向對象編程。它允許使用類和對象,而C是一個過程性語

言,不支持這些概念,或者說支持的不好,麻煩。

3. 模板C++支持模板,這是一種允許程序員編寫與數據類型無關的代碼的功能。C沒有這個功能。

4. 標準庫C++有一個更豐富的標準庫,包括STL(標準模板庫),這為數據結構和算法提供了廣泛的

支持。而C的標準庫相對較小。

5. 類型檢查C++C提供更嚴格的類型檢查。這意味著某些在C中可行但可能導致錯誤的代碼在

C++中可能無法編譯。

6. 異常處理C++支持異常處理,這是一種處理程序運行時錯誤的機制。C沒有內置的異常處理機制。

7. 命名空間C++引入了命名空間,這有助于防止名稱沖突。C沒有這個概念。

2.1 命名空間

2.1.1 命名空間作用

創建自己的命名空間是?C++ 中組織代碼的一種好方法,特別是在開發大型項目或庫時。命名空間可以幫

助你避免名稱沖突,并且清晰地組織代碼。

std ?C++ 標準庫的命名空間。它是一個定義在?C++ 標準庫中的所有類、函數和變量的命名空間。

我們新建一個QTCreatorC++工程,默認生成的代碼在?C++ 中,如果你想使用標準庫中的任何類、函數或對象,你通常有兩種選擇:

1. 使用 std:: 前綴:這是最常見的方式,它明確指定了你正在使用的是位于 std 命名空間中的元

素。

2. 使用 using namespace std; :這允許你在不顯式指定 std:: 的情況下使用 std 命名空間中的

所有元素。

std包含的內容

std 命名空間包含了許多類、函數和對象,例如:

輸入輸出庫(如 std::cout , std::cin , std::endl

容器類(如 std::vector , std::map , std::set

字符串類( std::string

異常類( std::exception 和相關子類)

算法(如 std::sort , std::find

實用工具(如 std::pair , std::tuple

其他許多功能

使用建議

對于小型代碼或示例代碼,使用 using namespace std; 通常是安全的。

對于大型項目或庫,建議顯式地使用 std:: 前綴,以避免潛在的名稱沖突,并提高代碼的可讀性

和可維護性。

std 命名空間是?C++ 編程的基礎部分,理解和正確使用它對于編寫健壯和高效的?C++ 代碼至關重要。

2.1.2 自定義命名空間

定義命名空間

假設我們要創建一個命名空間來包含與圓形相關的功能。我們可以命名這個命名空間為 Cir

#include <iostream>

using namespace std;

int main()

{

cout << "Hello World!" << endl;

return 0;

}

std::cout << "Hello, world!" << std::endl;

using namespace std;

cout << "Hello, world!" << endl;在這個頭文件中,我們定義了一個名為 Cir 的命名空間,其中包含了計算圓的面積和周長的函數,以及

圓周率常量 PI

使用命名空間

在另一個文件中,我們可以使用這個命名空間中定義的函數和常量:

main.cpp 中,我們首先包含了定義 Cir 命名空間的頭文件。然后,我們可以使用 Cir:: 前綴來訪

問該命名空間中的函數和常量。

通過使用自定義命名空間,你可以有效地組織你的代碼,并減少不同庫之間的名稱沖突。這在大型項目

和團隊協作中尤其重要。

#ifndef CIR_H

#define CIR_H

namespace Cir {

const double PI = 3.141592653;

double areaOfCircle(double radius){

return PI*radius*radius;

}

double lenthOfCircle(double radius){

return 2*PI*radius;

}

}

#endif // CIR_H

#include "cir.h"

#include <stdio.h>

int main()

{

double radius = 5;

printf("半徑為%f的圓,周長為%f,面積為%f\n",

radius,Cir::lenthOfCircle(radius), Cir::areaOfCircle(radius));

return 0;

}

#include "cir.h"

#include <stdio.h>

using namespace Cir;

int main()

{

double radius = 5;

printf("半徑為%f的圓,周長為%f,面積為%f\n",

radius,lenthOfCircle(radius), areaOfCircle(radius));

return 0;

}2.2 C語言快速入門

2.2.1 輸入輸出

C++ 中的輸入和輸出(I/O)主要是通過標準庫中的輸入輸出流來實現的。最常用的是?iostream 庫,它

提供了用于輸入和輸出的基本流類,包括 cin cout cerr clog

標準輸出流?( cout )

cout 代表標準輸出流,通常用于向屏幕輸出數據。

使用操作符 << (插入操作符)向 cout 發送數據。

例如, std::cout << "Hello, world!" << std::endl; 會在屏幕上打印?"Hello, world!" 并換

行。

標準輸入流?( cin )

cin 代表標準輸入流,用于從鍵盤接收數據。

使用操作符 >> (提取操作符)從 cin 提取數據。

例如, int x; std::cin >> x; 會從用戶那里讀取一個整數并存儲在變量 x 中。

標準錯誤流?( cerr ) 和標準日志流?( clog )

cerr 用于輸出錯誤消息。與 cout 不同, cerr 不是緩沖的,這意味著它會立即輸出。

clog 類似于 cerr ,但它是緩沖的。它通常用于記錄錯誤和日志信息。

示例代碼

下面是一個展示如何使用這些基本流的簡單示例:

2.2.2 基本變量類型

C++ 基本數據類型整理成表格。以下是一個表格,展示了不同的基本數據類型及其一般用途和大小范

圍:

C語言類似。

#include <iostream>

int main() {

// 使用 cout 輸出

std::cout << "Enter a number: ";

// 使用 cin 輸入

int num;

std::cin >> num;

// 輸出結果

std::cout << "You entered: " << num << std::endl;

std::clog << "Logging: user entered a number." << std::endl;

return 0;

}數據類型

描述

大小(通常情況

下)

用途

int

整型

至少?16

存儲整數

short int

短整型

至少?16

存儲較小的整數

long int

長整型

至少?32

存儲較大的整數

long long

int

更長的整型

至少?64

存儲非常大的整數

unsigned

int

無符號整型

int

存儲非負整數

float

單精度浮點類

32

存儲小數,精度約為?6-7 位小數

double

雙精度浮點類

64

存儲小數,精度約為?15-16 位小數

long double

擴展精度浮點

類型

80 位或更多

存儲小數,提供比 double 更高的精度

char

字符型

8

存儲單個字符或小整數

unsigned

char

無符號字符型

8

存儲較大的字符或作為字節使用

signed char

有符號字符型

8

明確作為帶符號的字符或小整數使用

bool

布爾型

通常為?8

存儲真值 true 或假值 false?C語言

C99以上支持

wchar_t

寬字符類型

通過為16位或

32

存儲中文或者unicode

寬字符的用法

#include <iostream>

#include <locale>

#include <wchar.h>

int main() {

// 設置本地化以支持寬字符

std::setlocale(LC_ALL, "");

// 使用 wchar_t 類型定義一個寬字符串

wchar_t wstr[] = L"你好,世界!";

// C++ 中打印寬字符串

std::wcout << wstr << std::endl;

return 0;

}?C++ 中, <climits> (或在?C 中是 <limits.h> )是一個標準頭文件,提供了關于整型限制的信

息。這個頭文件中定義了各種整型數據類型的屬性,如最大值、最小值等。使用這些信息可以幫助你了

解在特定編譯器和平臺上各種數據類型的大小和范圍。

如何使用 <climits>

要使用 <climits> 中定義的常量,你首先需要包含這個頭文件:

然后,你可以使用它提供的各種常量,例如:

INT_MAX int 類型的最大值。

INT_MIN int 類型的最小值。

UINT_MAX unsigned int 類型的最大值。

LONG_MAX long int 類型的最大值。

LONG_MIN long int 類型的最小值。

LLONG_MAX long long int 類型的最大值。

LLONG_MIN long long int 類型的最小值。

示例代碼

下面是一個簡單的示例,展示了如何使用 <climits> 中的值:

這個程序會輸出 int unsigned int long long int 類型的最大值和最小值。

注意事項

<climits> 提供的是編譯時確定的常量,這意味著這些值在編譯時就已經固定,根據編譯器和平臺

的不同而可能有所不同。

使用這些限制值可以幫助你編寫更可移植和安全的代碼,特別是在處理可能超出數據類型范圍的操

作時。

#include <climits>

#include <iostream>

#include <climits>

int main() {

std::cout << "The range of int is from " << INT_MIN << " to " << INT_MAX <<

std::endl;

std::cout << "The maximum value of unsigned int is " << UINT_MAX <<

std::endl;

std::cout << "The range of long long is from " << LLONG_MIN << " to " <<

LLONG_MAX << std::endl;

return 0;

}2.2.3 流程控制

?C++ 中,流程控制語句用于根據不同條件控制程序的執行流程。它們是編程中的基本構建塊,允許程

序根據條件執行不同的代碼段,重復執行某些操作,或者根據特定情況跳過某些代碼段。下面是?C++

最常見的流程控制語句:

條件語句

1. if 語句:基于條件的基本控制結構。如果條件為真,則執行代碼塊。

else 語句:與 if 語句配合使用,當 if 的條件為假時執行。

else if 語句:用于測試多個條件。

switch 語句:基于變量的值選擇執行不同代碼塊的方法。

循環語句

for 循環:當知道循環應該執行的次數時使用。

if (condition) {

// 條件為真時執行的代碼

}

if (condition) {

// 條件為真時執行的代碼

} else {

// 條件為假時執行的代碼

}

if (condition1) {

// 第一個條件為真時執行的代碼

} else if (condition2) {

// 第二個條件為真時執行的代碼

} else {

// 所有條件為假時執行的代碼

}

switch (expression) {

case value1:

// expression 等于 value1 時執行的代碼

break;

case value2:

// expression 等于 value2 時執行的代碼

break;

default:

// 沒有匹配的 case 時執行的代碼

}

for (initialization; condition; increment) {

// 循環體

}while 循環:當條件為真時,重復執行代碼塊。

do-while 循環:至少執行一次循環體,然后再檢查條件。

跳轉語句

1. break 語句:用于立即跳出最近的 switch 或循環( for while do-while )。

2. continue 語句:跳過循環的當前迭代,并繼續下一次迭代。

3. goto 語句:直接跳轉到程序中的另一個點。使用 goto 通常不推薦,因為它可以使代碼難以閱讀

和維護。

流程控制語句是編程中非常重要的部分,允許開發者編寫可以根據不同情況改變行為的靈活且強大的程

序。在使用這些語句時,應該確保邏輯清晰,以便代碼易于理解和維護。

2.2.4 函數

?C++ 中,函數是一段執行特定任務的代碼塊,它可以帶有參數,并且可能返回一個值。函數的使用使

得代碼更加模塊化和可重用,有助于降低代碼的復雜性,并提高可維護性。

函數的基本結構

C++ 函數的基本結構包括返回類型、函數名、參數列表和函數體:

示例

以下是一個?C++ 函數的簡單示例:

while (condition) {

// 循環體

}

do {

// 循環體

} while (condition);

返回類型 函數名(參數列表) {

// 函數體

// 返回語句(如果有返回值的話)

}

#include <iostream>

using namespace std;

// 函數聲明

int add(int x, int y);

int main() {

int result = add(5, 3);

cout << "Result: " << result << endl;

return 0;

}

// 函數定義

int add(int x, int y) {在這個示例中, add 函數接收兩個整數參數,并返回它們的和。

函數的組成部分

1. 返回類型:指定函數返回的數據類型。如果函數不返回任何值,則使用 void

2. 函數名:函數的標識符,用于調用函數。

3. 參數列表:括號內的變量列表,用于從函數的調用者那里接收值。如果函數不接收任何參數,則此

列表為空。

4. 函數體:大括號 {} 內的一系列語句,定義了函數的執行操作。

2.2.5 內聯函數

內聯函數(Inline Function)是C++中一種特殊的函數,其定義直接在每個調用點展開。這意味著編譯器

會嘗試將函數調用替換為函數本身的代碼,這樣可以減少函數調用的開銷,尤其是在小型函數中。

特點

1. 減少函數調用開銷:內聯函數通常用于優化小型、頻繁調用的函數,因為它避免了函數調用的常規

開銷(如參數傳遞、棧操作等)。

2. 編譯器決策:即使函數被聲明為內聯,編譯器也可能決定不進行內聯,特別是對于復雜或遞歸函

數。

3. 適用于小型函數:通常只有簡單的、執行時間短的函數適合做內聯。

4. 定義在每個使用點:內聯函數的定義(而非僅僅是聲明)必須對每個使用它的文件都可見,通常意

味著將內聯函數定義在頭文件中。

使用方法

通過在函數聲明前添加關鍵字 inline 來指示編譯器該函數適合內聯:

示例

在這個示例中,函數 add 被定義為內聯函數。當它被調用時,編譯器可能會將函數調用替換為函數體內

的代碼。

return x + y;

}

inline int max(int x, int y) {

return x > y ? x : y;

}

#include <iostream>

inline int add(int a, int b) {

return a + b;

}

int main() {

int result = add(5, 3); // 編譯器可能會將此替換為:int result = 5 + 3;

std::cout << "Result: " << result << std::endl;

return 0;

}注意事項

過度使用的風險:不應濫用內聯函數,因為這可能會增加最終程序的大小(代碼膨脹)。對于大型

函數或遞歸函數,內聯可能導致性能下降。

編譯器的決定:最終是否將函數內聯是由編譯器決定的,即使函數被標記為 inline

適用場景:最適合內聯的是小型函數和在性能要求高的代碼中頻繁調用的函數。

內聯函數是一種用于優化程序性能的工具,但需要合理使用,以確保代碼的可維護性和性能的平衡。

2.2.6 Lambda 表達式

Lambda 表達式是?C++11 引入的一種匿名函數的方式,它允許你在需要函數的地方內聯地定義函數,而

無需單獨命名函數

Lambda 表達式的基本語法如下:

Lambda 表達式由以下部分組成:

捕獲列表(Capture clause:用于捕獲外部變量,在?Lambda 表達式中可以訪問這些變量。捕

獲列表可以為空,也可以包含變量列表 [var1, var2, ...]

參數列表(Parameters:與普通函數的參數列表類似,可以為空或包含參數列表 (param1,

param2, ...)

返回類型(Return typeLambda 表達式可以自動推斷返回類型auto,也可以顯式指定返回類

-> return_type 。如果函數體只有一條返回語句,可以省略返回類型。

函數體(BodyLambda 表達式的函數體,包含需要執行的代碼。

Lambda 表達式最簡單的案例是在需要一個小型函數或臨時函數時直接使用它。以下是一個非常簡單的

例子,其中使用?Lambda 表達式來定義一個加法操作,并立即使用它來計算兩個數的和。

示例:使用?Lambda 表達式進行加法

[capture clause](parameters) -> return_type {

// 函數體

// 可以使用捕獲列表中的變量

return expression; // 可選的返回語句

}

#include <iostream>

int main() {

// 定義一個簡單的 Lambda 表達式進行加法

auto add = [](int a, int b) {

return a + b;

};

// 使用 Lambda 表達式計算兩個數的和

int sum = add(10, 20);

std::cout << "Sum is: " << sum << std::endl;

return 0;在這個例子中:

我們定義了一個名為 add ?Lambda 表達式,它接受兩個整數參數,并返回它們的和。

然后,我們使用這個?Lambda 表達式來計算兩個數字(10 ?20)的和,并將結果存儲在變量 sum

中。

最后,我們打印出這個和。

這個例子展示了?Lambda 表達式的基本用法:作為一種簡潔而快速的方式來定義小型函數。

我們可以寫一個例子,其中使用一個函數來找出兩個數中的較大數,這個函數將接受一個?lambda 函數

作為回調來比較這兩個數。Lambda 函數將直接在函數調用時定義,完全是匿名的。

先回憶以下回調函數

示例:使用匿名?Lambda 函數來返回兩個數中的較大數

}

#include <iostream>

// 函數,接受兩個整數和一個比較的 lambda 函數

bool myCompare(int a, int b){

return a > b;

}

int getMax(int a, int b, bool(*compare)(int, int)) {

if (compare(a, b)) {

return a;

} else {

return b;

}

}

int main() {

int x = 10;

int y = 20;

// 回調函數

int max = getMax(x, y, myCompare);

std::cout << "The larger number is: " << max << std::endl;

return 0;

}

#include <iostream>

// 函數,接受兩個整數和一個比較的 lambda 函數

int getMax(int a, int b, bool(*compare)(int, int)) {

if (compare(a, b)) {

return a;

} else {

return b;

}在這個例子中:

getMax 函數接受兩個整數 a b ,以及一個比較函數 compare 。這個比較函數是一個指向函數

的指針,它接受兩個整數并返回一個布爾值。

main 函數中,我們調用 getMax ,并直接在調用點定義了一個匿名的?lambda 函數。這個

lambda 函數接受兩個整數并返回一個表示第一個整數是否大于第二個整數的布爾值。

這個?lambda 函數在 getMax 中被用作比較兩個數的邏輯。根據?lambda 函數的返回值, getMax

返回較大的數。

這個例子展示了如何直接在函數調用中使用匿名?lambda 函數,使代碼更加簡潔和直接。這種方法在需

要臨時函數邏輯的場合非常有用,尤其是在比較、條件檢查或小型回調中。

?Lambda 表達式中,參數捕獲是指?Lambda 表達式從其定義的上下文中捕獲變量的能力。這使得

Lambda 可以使用并操作在其外部定義的變量。捕獲可以按值(拷貝)或按引用進行。

讓我們通過一個簡單的示例來展示帶參數捕獲的?Lambda 表達式。

示例:使用帶參數捕獲的?Lambda 表達式

}

int main() {

int x = 10;

int y = 20;

// 直接在函數調用中定義匿名 lambda 函數

int max = getMax(x, y, [](int a, int b) -> bool {

return a > b;

});

std::cout << "The larger number is: " << max << std::endl;

return 0;

}

#include <iostream>

int main() {

int x = 10;

int y = 20;

// 捕獲 x y 以便在 Lambda 內部使用

// 這里的捕獲列表 [x, y] 表示 x y 被按值捕獲

auto sum = [x, y]() {

// x++;

// y++; 按值捕獲,關注的是值本身,無法修改

return x + y;

};

std::cout << "Sum is: " << sum() << std::endl;

std::cout << "x is now: " << x << ", y is now: " << y << std::endl;

// 捕獲所有外部變量按值捕獲(拷貝)

int z = 30;

auto multiply = [=]() {特性

Lambda 函數

內聯函數

定義

一種匿名函數,通常用于定義在需要它們

的地方。

一種常規函數,通過 inline 關鍵字定

義。

用途

提供一種快捷方式來定義臨時的、小型的

函數。

用于優化小型函數,減少函數調用的開

銷。

語法

使用 [capture](params) { body }

的形式定義。

使用常規函數定義語法,但在前面加上

inline 關鍵字。

生命周期

在定義它們的作用域內有效。

在整個程序執行期間有效。

捕獲外部

變量

可以捕獲外部作用域中的變量(按值或按

引用)。

不能直接捕獲外部變量,只能通過參數

傳遞。

調用方式

作為函數對象,可直接調用。

像普通函數一樣調用。

在這個例子中:

第一個?Lambda 表達式 sum 按值捕獲了 x y (即它們的副本)。這意味著 sum 內的 x y

是在?Lambda 定義時的值的拷貝。

第二個?Lambda 表達式 multiply 使用 [=] 捕獲列表,這表示它按值捕獲所有外部變量。

第三個?Lambda 表達式 modifyAndSum 使用 [&] 捕獲列表,這表示它按引用捕獲所有外部變量。

因此,它可以修改 x y 的原始值。

這個示例展示了如何使用不同類型的捕獲列表(按值和按引用)來控制?Lambda 表達式對外部變量的訪

問和修改。按值捕獲是安全的,但不允許修改原始變量,而按引用捕獲允許修改原始變量,但需要注意

引用的有效性和生命周期問題。

以下是一個表格,概述了?Lambda 函數和內聯函數在?C++ 中的相似之處和區別:

// x++;

// y++; 按值捕獲,關注的是值本身,無法修改

return x * y * z;

};

count << x << "," << y << endl;

std::cout << "Product is: " << multiply() << std::endl;

std::cout << "x is now: " << x << ", y is now: " << y << std::endl;

// 捕獲所有外部變量按引用捕獲

auto modifyAndSum = [&]() {

x = 15; // 修改 x 的實際值

y = 25; // 修改 y 的實際值, 引用捕獲可以修改

return x + y;

};

std::cout << "Modified Sum is: " << modifyAndSum() << std::endl;

std::cout << "x is now: " << x << ", y is now: " << y << std::endl;

return 0;

}特性

Lambda 函數

內聯函數

優化

可以被編譯器自動內聯化,但這取決于編

譯器優化策略。

明確請求編譯器嘗試內聯,但實際內聯

化也取決于編譯器。

可見性

通常只在定義它們的局部作用域內可見。

可以在定義它的任何作用域內可見。

使用場景

適合于一次性使用的場景,如作為回調、

在算法中使用等。

適合于頻繁調用的小型函數。

請注意,雖然?Lambda 函數和內聯函數在某些方面有相似之處,如它們都可以被編譯器優化以減少調用

開銷,但它們在設計和用途上有明顯的不同。Lambda 函數的核心優勢在于它們的匿名性和對外部變量

的捕獲能力,而內聯函數則主要關注于提高小型函數的性能。

2.2.7 數組

?C++ 中,數組是一種存儲固定大小的相同類型元素的序列。數組的所有元素都存儲在連續的內存位置

上。這種數據結構非常適合于存儲具有固定數量和相同數據類型的元素集合。

聲明數組

聲明數組的基本語法如下:

例如,聲明一個類型為 int 的數組,包含?10 個元素:

初始化數組

在聲明數組時,您可以同時初始化數組:

如果您在初始化數組時沒有指定所有元素的值,未初始化的元素將被自動設置為該數據類型的默認值

(對于基本數據類型通常是?0):

訪問數組元素

您可以通過指定索引來訪問數組中的元素。數組索引是從?0 開始的,所以數組的第一個元素是 數組名

[0] ,第二個元素是 數組名[1] ,依此類推:

示例

以下是使用數組的簡單示例:

數據類型 數組名[數組大小];

int myArray[10];

int myArray[5] = {10, 20, 30, 40, 50};

int myArray[5] = {10, 20}; // 其余元素將被初始化為 0

int value = myArray[2]; // 訪問第三個元素注意事項

數組的大小必須在編譯時已知,且不能更改。

數組索引越界是常見的錯誤,可能會導致未定義的行為。

對于更復雜的用例,您可能需要使用?C++ 的標準模板庫(STL)中的容器,如 std::vector ,它提

供了可以動態改變大小的數組。

數組的元素存儲在連續的內存位置上,這使得訪問數組元素非常快。

2.2.8 練習

計算器支持加減乘除

#include <iostream>

using namespace std;

int main() {

int myArray[5] = {10, 20, 30, 40, 50};

// 輸出所有數組元素的值

for(int i = 0; i < 5; ++i) {

cout << "Element at index " << i << ": " << myArray[i] << endl;

}

return 0;

}

#include <iostream>

using namespace std;

int add(int a,int b)

{

return a+b;

}

int min(int a,int b)

{

return a-b;

}

int mul(int a,int b)

{

return a*b;

}

float divRet(int a,int b)

{

return (float)a/b;

}

int main() {

int a;

int b;char calWay;

while(1){

cout << "請輸入兩個數:"<< endl;

cin >> a;

cin >> b;

cout<<"請輸入運算符號:+ - * /" <<endl;

cin >> calWay;

switch(calWay){

case '+':

printf("兩數之和是%d\n",add(a,b));

break;

case '-':

printf("兩數之差是%d\n",min(a,b));

break;

case '*':

printf("兩數之積是%d\n",mul(a,b));

break;

case '/':

printf("兩數之余是%f\n",divRet(a,b));

break;

default:

printf("運算符輸入錯誤,請重新輸入\n");

}

}

}

#include <iostream>

using namespace std;

/*

int add(int a, int b)

{

return a+b;

}

int minu(int a, int b)

{

return a-b;

}

int mul(int a, int b)

{

return a*b;

}

double diliv(int a, int b)

{

return (double)a/b;

}

*/數組找最大值

int calculator(int a, int b, int (*p)(int a, int b))

{

cout << "開始計算" << endl;

// p(a,b);

}

int main()

{

int a = 0;

int b = 0;

char cal;

while(1){

cout << "請輸入兩個數:" << endl;

cin >> a;

cin >> b;

cout << "請輸入運算符+-*/" << endl;

cin >> cal;

// auto minu = [a,b]()->int{ return a - b;};

switch(cal){

case '+':

cout << calculator(a,b,[](int a, int b){return a + b;}) << endl;

break;

case '-':

cout << calculator(a,b,[](int a, int b){return a - b;}) << endl;

break;

case '*':

cout << calculator(a,b,[](int a, int b){return a * b;}) << endl;

break;

case '/':

cout << calculator(a,b,[](int a, int b){return a / b;}) << endl;

break;

}

}

// cout << "Hello World!" << endl;

return 0;

}

#include <iostream>

using namespace std;

void initArry(int *arry, int len)

{

for(int i=0; i< len; i++){

cout << "請輸入第" << i+1 << "個數" << endl;

cin >> arry[i];

}

}2.2.9 指針

C++完全兼容C語言指針,多出一個this指針,在面向對象中再講解。

void printArry(int *arry,int len)

{

for(int i=0; i< len; i++){

cout << arry[i] << endl;

}

}

int getMaxFromArray(int *arry, int len )

{

int maxTmp = arry[0];

for(int i=0; i< len; i++){

if(maxTmp < arry[i])

maxTmp = arry[i];

}

return maxTmp;

}

int main() {

int arry[5];

int len = sizeof(arry)/sizeof(arry[0]);

initArry(arry,len);

printArry(arry,len);

cout << "最大數是:" << getMaxFromArray(arry,len);

}

#include <iostream>

using namespace std;

void swap(int *pa, int *pb)

{

int tmp;

tmp = *pa;

*pa = *pb;

*pb = tmp;

}

int main() {

int a = 10;

int b = 20;

cout << a << endl;

cout << b << endl;

cout << "after chage:" << endl;

swap(&a,&b);

cout << a << endl;

cout << b << endl;2.2.9 字符串string類型

C語言中對字符串的表示通常用指針,新手會面臨內存泄漏或者段錯誤等眾多問題。

?C++ 中, string 類是標準庫的一部分,用于表示和操作字符串。它是對傳統的?C 風格字符串(以空

字符?'\0' 結尾的字符數組)的一個更安全、更方便的封裝。 string 類是在 <string> 頭文件中定義

的,并且位于 std 命名空間中。

string 類提供了許多有用的功能和特性,包括:

1. 動態大小:與?C 風格的字符串不同, string 對象可以動態改變大小,這意味著你可以在運行時添

加或移除字符,而不需要擔心分配和釋放內存。

2. 安全性:由于 string 管理其自己的內存,因此減少了內存泄漏和緩沖區溢出的風險。

3. 方便的成員函數string 類提供了各種操作字符串的方法,如 append() (添加)、 insert()

(插入)、 erase() (刪除)、 substr() (獲取子字符串)等。

4. 操作符重載string 類重載了多個操作符,使得字符串比較、連接和賦值更加直觀。例如,你可

以使用 + 操作符來連接兩個字符串,或者使用 == 操作符來比較兩個字符串是否相等。

5. 迭代器支持:像其他標準庫容器一樣, string 類也支持迭代器,使得你可以使用迭代器來遍歷字

符串中的字符。

6. ?C 風格字符串的兼容性string 類提供了與?C 風格字符串互操作的功能,例如,你可以使用

c_str() 方法來獲取一個與?C 風格字符串兼容的、以?null 結尾的字符數組。

下面是一個簡單的 string 類的使用示例:

在這個示例中,我們創建了一個 string 對象 str ,然后使用不同的方法對其進行操作。這展示了

string 類的靈活性和強大功能。

下面是一個表格,展示了?C++ std::string 類的一些常用成員函數及其功能和參數:

}

#include <iostream>

#include <string>

int main() {

std::string str = "Hello, world!";

std::cout << str << std::endl; // 輸出字符串

str += " I am a C++ string."; // 字符串連接

std::cout << str << std::endl;

std::string substr = str.substr(7, 5); // 獲取子字符串

std::cout << "Substring: " << substr << std::endl;

return 0;

}函數名

功能

參數

返回值類型

length() size()

返回字符串的長

size_t

empty()

檢查字符串是否

為空

bool

append(const string& str)

向字符串末尾添

加另一個字符串

要追加的字符串

string&

substr(size_t pos = 0, size_t

len = npos)

返回一個子字符

pos :子字符串

的起始位置

len :子字符串

的長度

string

find(const string& str, size_t

pos = 0)

查找子字符串出

現的位置

str :要查找的

字符串

pos :搜索起始

位置

size_t

compare(const string& str)

比較兩個字符串

要比較的字符串

int

erase(size_t pos = 0, size_t

len = npos)

刪除字符串中的

一部分

pos :起始位置

len :要刪除的

長度

string&

insert(size_t pos, const

string& str)

在指定位置插入

字符串

pos :插入位置

str :要插入的

字符串

string&

replace(size_t pos, size_t len,

const string& str)

替換字符串中的

一部分

pos :起始位置

len :要替換的

長度

str :替換的字

符串

string&

c_str()

返回?C 風格字符

串表示

const

char*

operator[] (size_t pos)

訪問指定位置的

字符

pos :字符位置

char&

這些函數是 std::string 類中常用的一部分,提供了強大且靈活的字符串操作能力。使用這些函數可

以方便地處理和修改字符串數據。2.3

2.3.1 類的初探

C++ 中的類(class)是一種編程結構,用于創建對象。這些對象可以擁有屬性(即數據成員)和行為

(即成員函數或方法)。類的概念是面向對象編程的核心之一,其主要目的是將數據和與數據相關的操

封裝在一起。例如,如果你有一個汽車類,它可能包含顏色、品牌、型號等屬性(數據成員),以及

啟動、停止、加速等行為(成員函數)。每當你基于這個類創建一個對象時,你就有了一個具體的汽

車,具有這些屬性和行為。

C++ 類的基本結構通常包含:

1. 數據成員(Attributes:定義類的屬性。這些是類內部的變量,用于存儲對象的狀態。

2. 成員函數(Methods:定義類的行為。這些是可以操作對象的數據成員的函數。

3. 構造函數和析構函數:特殊的成員函數。構造函數在創建對象時自動調用,用于初始化對象。析構

函數在對象銷毀時調用,用于執行清理操作。

4. 訪問修飾符:如 public , private , protected ,用于控制對類成員的訪問權限。例如, public

成員可以在類的外部訪問,而 private 成員只能在類內部訪問。

5. 繼承:允許一個類繼承另一個類的特性。這是代碼重用和多態性的關鍵。

通過這些特性,C++ 類提供了一種強大的方式來組織和處理數據,使得代碼更加模塊化、易于理解和維

護。

2.3.2 結構體引入類

2.3.2.1 回憶結構體

如果用C語言實現上面描述的汽車類,我們實現如下代碼

#include <stdio.h>

#include <stdlib.h>

struct Car{ //汽車

char *color; //顏色

char *brand; //品牌

char *type; //車型

int year; //年限

void (*printCarInfo)(char *color,char *brand,char *type, int year); //函數指

針,指向車介紹函數

void (*carRun)(char *type); //函數指針,指向車運行的函數

void (*carStop)(char *type); //函數指針,執行車停止的函數

};

void bwmThreePrintCarInfo(char *color,char *brand,char *type, int year)

{

printf("車的品牌是:%s, 型號是: %s, 顏色是:%s,上市年限是%d\n",

brand,type,color,year);

}

void A6PrintCarInfo(char *color,char *brand,char *type, int year)

{

printf("車的品牌是:%s,型號是: %s, 顏色是:%s, 上市年限是%d\n",

brand,type,color,year);2.3.2.2 新建C++工程來使用結構體

C++中,字符串用string來表示,發現有個string賦值給char 的警告,所以修改所有char?*

string類型

修改后,發現printf%s控制位,不能用于string的輸出,所有有string構建了即將要輸出的字符串

C++中,通過std::tostring()函數,將整型數轉化成字符串

printInfo中使用cout輸出汽車信息

發現在C++工程中,使用malloc在堆申請結構體空間有問題,所以直接在此引入類的概念,把struct

改成class

引入新問題,class的成員數據和成員函數在不指定權限的情況下,默認private權限,類的對象無法

進行直接訪問

添加public屬性

}

int main()

{

struct Car BWMthree;

BWMthree.color = "白色";

BWMthree.brand = "寶馬";

BWMthree.type = "3";

BWMthree.year = 2023;

BWMthree.printCarInfo = bwmThreePrintCarInfo;

BWMthree.printCarInfo(BWMthree.color,BWMthree.brand,BWMthree.type,BWMthree.year)

;

struct Car *AodiA6;

AodiA6 = (struct Car*)malloc(sizeof(struct Car));

AodiA6->color = "黑色";

AodiA6->brand = "奧迪";

AodiA6->type = "A6";

AodiA6->year = 2008;

AodiA6->printCarInfo = A6PrintCarInfo;

AodiA6->printCarInfo(AodiA6->color,AodiA6->brand,AodiA6->type,AodiA6->year);

return 0;

}

main.cpp:33:17: warning: ISO C++11 does not allow conversion from string literal

to 'char *'

main.cpp:33:9: error: 'color' is a private member of 'Car'

main.cpp:5:11: note: implicitly declared private here訪問權限

類內部

同一個類的對象

派生類(子類)

類外部

public

?? 可訪問

?? 可訪問

?? 可訪問

?? 可訪問

private

?? 可訪問

? 不可訪問

? 不可訪問

? 不可訪問

protected

?? 可訪問

? 不可訪問

?? 可訪問

? 不可訪問

main函數中的原本結構體變量改成了類的實例化,如果變量類型是指針,把原來的malloc改成

new一個對象

最后解決了所有問題

#include <stdio.h>

#include <stdlib.h>

#include <string>

#include <iostream>

using namespace std;

class Car{ //汽車

public:

string color; //顏色

string brand; //品牌

string type; //車型

int year; //年限

void (*printCarInfo)(string color,string brand,string type, int year); //函數

指針,指向車介紹函數

void (*carRun)(string type); //函數指針,指向車運行的函數

void (*carStop)(string type); //函數指針,執行車停止的函數

};

void bwmThreePrintCarInfo(string color,string brand,string type, int year)

{

string str = "車的品牌是:" + brand

+ ",型號是: " + type

+ ",顏色是:" + color

+ ",上市年限是:" + std::to_string(year);

cout << str << endl;

}

void A6PrintCarInfo(string color,string brand,string type, int year)

{

string str = "車的品牌是:" + brand

+ ",型號是: " + type

+ ",顏色是:" + color

+ ",上市年限是:" + std::to_string(year);

cout << str << endl;

}

int main()

{

Car BWMthree;

BWMthree.color = "白色";

BWMthree.brand = "寶馬";但是!我們還沒有真正體驗到面向對象的封裝特性,僅僅感受到權限上的區別

2.3.2.3 真正的成員函數

上一節的案例中, void (*printCarInfo)(string color,string brand,string type, int

year); 到底是變量函數函數呢?

是一個指針變量,是保存某個函數地址的變量,所以它不是成員函數,是成員數據

真正的成員函數遵守封裝特性,在函數體內部訪問成員數據的時候,不需要參數傳遞

?C++ 中,雙冒號 :: 稱為?"作用域解析運算符"Scope Resolution Operator)。它用于指定一

個成員(如函數或變量)屬于特定的類或命名空間。例如,在類的外部定義成員函數時, :: 用于

指明該函數屬于哪個類。

BWMthree.type = "3";

BWMthree.year = 2023;

BWMthree.printCarInfo = bwmThreePrintCarInfo;

BWMthree.printCarInfo(BWMthree.color,BWMthree.brand,BWMthree.type,BWMthree.year)

;

Car *AodiA6 = new Car();

// AodiA6 = (struct Car*)malloc(sizeof(struct Car));

AodiA6->color = "黑色";

AodiA6->brand = "奧迪";

AodiA6->type = "A6";

AodiA6->year = 2008;

AodiA6->printCarInfo = A6PrintCarInfo;

AodiA6->printCarInfo(AodiA6->color,AodiA6->brand,AodiA6->type,AodiA6->year);

return 0;

}

#include <stdio.h>

#include <stdlib.h>

#include <string>

#include <iostream>

using namespace std;

class Car{ //汽車

public:

//成員數據

string color; //顏色

string brand; //品牌

string type; //車型

int year; //年限

//其實也是成員數據,指針變量,指向函數的變量,并非真正的成員函數void (*printCarInfo)(string color,string brand,string type, int year); //函數

指針,指向車介紹函數

void (*carRun)(string type); //函數指針,指向車運行的函數

void (*carStop)(string type); //函數指針,執行車停止的函數

void realPrintCarInfo();//聲明成員函數

};

void Car::realPrintCarInfo() //在類的外部進行成員函數的實現

{

string str = "車的品牌是:" + brand

+ ",型號是: " + type

+ ",顏色是:" + color

+ ",上市年限是:" + std::to_string(year);

cout << str << endl;

}

void bwmThreePrintCarInfo(string color,string brand,string type, int year)

{

string str = "車的品牌是:" + brand

+ ",型號是: " + type

+ ",顏色是:" + color

+ ",上市年限是:" + std::to_string(year);

cout << str << endl;

}

void A6PrintCarInfo(string color,string brand,string type, int year)

{

string str = "車的品牌是:" + brand

+ ",型號是: " + type

+ ",顏色是:" + color

+ ",上市年限是:" + std::to_string(year);

cout << str << endl;

}

int main()

{

Car BWMthree;

BWMthree.color = "白色";

BWMthree.brand = "寶馬";

BWMthree.type = "3";

BWMthree.year = 2023;

BWMthree.printCarInfo = bwmThreePrintCarInfo;

BWMthree.printCarInfo(BWMthree.color,BWMthree.brand,BWMthree.type,BWMthree.year)

;

BWMthree.realPrintCarInfo();

Car *AodiA6 = new Car();

// AodiA6 = (struct Car*)malloc(sizeof(struct Car));

AodiA6->color = "黑色";

AodiA6->brand = "奧迪";

AodiA6->type = "A6";2.3.4 QT中經常出現的用法

?C++中,一個類包含另一個類的對象稱為組合(Composition)。這是一種常見的設計模式,用

于表示一個類是由另一個類的對象組成的。這種關系通常表示一種"擁有""has-a")的關系。

普通變量訪問成員變量或者成員函數,使用?“ . ” 運算符

指針變量訪問成員變量或者成員函數,使用“ -> ”運算符,像C語言的結構體用法

AodiA6->year = 2008;

AodiA6->printCarInfo = A6PrintCarInfo;

AodiA6->printCarInfo(AodiA6->color,AodiA6->brand,AodiA6->type,AodiA6->year);

AodiA6->realPrintCarInfo();

return 0;

}

#include <stdio.h>

#include <stdlib.h>

#include <string>

#include <iostream>

using namespace std;

class Wheel

{

public:

string brand;

int year;

void wheelPrintInfo();

};

void Wheel::wheelPrintInfo()

{

cout << "我的輪胎品牌是:" << brand << endl;

cout << "我的輪胎日期是:" << year << endl;

}

//C++中,一個類包含另一個類的對象稱為組合(Composition)。

class Car{ //汽車

public:

//成員數據

string color; //顏色

string brand; //品牌

string type; //車型

int year; //年限

Wheel wl;

Wheel *pwl;

//其實也是成員數據,指針變量,指向函數的變量,并非真正的成員函數void (*printCarInfo)(string color,string brand,string type, int year); //函數

指針,指向車介紹函數

void (*carRun)(string type); //函數指針,指向車運行的函數

void (*carStop)(string type); //函數指針,執行車停止的函數

void realPrintCarInfo();//聲明成員函數

};

void Car::realPrintCarInfo() //在類的外部進行成員函數的實現

{

string str = "車的品牌是:" + brand

+ ",型號是: " + type

+ ",顏色是:" + color

+ ",上市年限是:" + std::to_string(year);

cout << str << endl;

}

void bwmThreePrintCarInfo(string color,string brand,string type, int year)

{

string str = "車的品牌是:" + brand

+ ",型號是: " + type

+ ",顏色是:" + color

+ ",上市年限是:" + std::to_string(year);

cout << str << endl;

}

void A6PrintCarInfo(string color,string brand,string type, int year)

{

string str = "車的品牌是:" + brand

+ ",型號是: " + type

+ ",顏色是:" + color

+ ",上市年限是:" + std::to_string(year);

cout << str << endl;

}

int main()

{

Car BWMthree;

BWMthree.color = "白色";

BWMthree.brand = "寶馬";

BWMthree.type = "3";

BWMthree.year = 2023;

BWMthree.pwl = new Wheel();

BWMthree.pwl->brand = "米其林";

BWMthree.pwl->year = 2023;

//BWMthree.wl.brand = "米其林";

//BWMthree.wl.year = 2023;

BWMthree.printCarInfo = bwmThreePrintCarInfo;

BWMthree.printCarInfo(BWMthree.color,BWMthree.brand,BWMthree.type,BWMthree.year)

;

BWMthree.realPrintCarInfo();

//BWMthree.wl.wheelPrintInfo();訪問權限

類內部

同一個類的對象

派生類(子類)

類外部

public

?? 可訪問

?? 可訪問

?? 可訪問

?? 可訪問

private

?? 可訪問

? 不可訪問

? 不可訪問

? 不可訪問

protected

?? 可訪問

? 不可訪問

?? 可訪問

? 不可訪問

好處?/ 缺點

描述

好處

封裝性

通過隱藏類的內部實現(私有和受保護成員),提高了代碼的安全性和健壯

性。

接口與實現的

分離

公開接口(公開成員)與私有實現分離,有助于用戶僅關注于如何使用類而不

是如何實現。

易于維護

修改類的內部實現不會影響使用該類的代碼,從而降低了維護成本。

2.4 權限初識

2.4.1 基本介紹

C++中的訪問權限主要分為三種: public private protected 。這些權限決定了類成員(包括數

據成員和成員函數)的可訪問性。以下是一個總結表格,說明了在不同情況下這些權限如何應用:

使用權限(如 public private protected )在C++中是一種關鍵的封裝手段,它們旨在控制對類

成員的訪問。下面是一個表格,總結了使用權限的主要好處和潛在缺點:

Car *AodiA6 = new Car();

// AodiA6 = (struct Car*)malloc(sizeof(struct Car));

AodiA6->color = "黑色";

AodiA6->brand = "奧迪";

AodiA6->type = "A6";

AodiA6->year = 2008;

AodiA6->printCarInfo = A6PrintCarInfo;

AodiA6->pwl = new Wheel;

AodiA6->pwl->brand = "普利司通";

AodiA6->pwl->year = 2012;

//AodiA6->wl.brand = "馬牌";

//AodiA6->wl.year = 2023;

AodiA6->printCarInfo(AodiA6->color,AodiA6->brand,AodiA6->type,AodiA6->year);

AodiA6->realPrintCarInfo();

//AodiA6->wl.wheelPrintInfo();

AodiA6->pwl->wheelPrintInfo();

return 0;

}好處?/ 缺點

描述

控制讀寫訪問

通過設置訪問權限,可以精確控制類成員的讀寫訪問。

繼承的靈活性

protected 成員在派生類中是可訪問的,使得繼承更加靈活。

缺點

增加復雜性

過度使用或不當使用權限可能導致代碼結構復雜,難以理解。

測試難度

私有成員的測試比公共成員更困難,因為它們不能從類的外部訪問。

靈活性降低

過于嚴格的封裝可能限制了某些有效的用法,降低了靈活性。

可能導致緊耦

過多依賴 friend 類或函數可能導致類之間的耦合過緊。

2.4.2 目前能概況的結論

public 權限相當于我們學習C語言結構體一樣,不考慮訪問權限的存在,但是要注意,類中不寫權

限,默認是私有權限

protected 留到繼承講解的時候再提

private 私有權限,通過一下案例向各位表達一下作用的意思,但需要未來實戰中慢慢體會。

這個例子將闡述在類設計中使用 private 成員的必要性。我們將創建一個簡單的 BankAccount 類,展示

如何使用 private 來保護賬戶的余額,確保它只能通過指定的方法進行修改。

所以,我們可以腦部一個場景:

銀行的賬戶是一個模板,是一個類,有存款人信息和賬戶額度,而具體的存款人視為一個對象,

一個對象不能私自修改賬戶額度,需要通過一個操作流程,比如去ATM或者柜臺進行操作才能修改

到賬戶額度,

所以,存款人信息和賬戶額度設計成私有權限,通過公有的操作流程,也就是公有函數去操作私有

變量。

基于這個場景,我們編程實現代碼

#include <iostream>

#include <string>

using namespace std;

/*

銀行的賬戶是一個模板,是一個類,有存款人信息和賬戶額度,而具體的存款人視為一個對象,

一個對象不能私自修改賬戶額度,需要通過一個操作流程,比如去ATM或者柜臺進行操作才能修改到賬戶額度,

所以,存款人信息和賬戶額度設計成私有權限,通過公有的操作流程,也就是公有函數去操作私有變量。

基于這個場景,我們編程實現代碼

*/

class BankAccount{

private:

//有存款人信息和賬戶額度

string name;

string addr;int age;

double balance;

public:

string bankAddr;

//比如去ATM或者柜臺進行操作才能修改到賬戶額度

void registerMes(string newName, string newAddr,int newAge,double

newBalance);

void withdraw(double amount);

void deposit(double amount);

double getBalance();

void printUserInfo();

};

void BankAccount::printUserInfo()

{

string mesTem = "賬戶名:" + name + ",地址:" + addr +

",年齡:"+ std::to_string(age) + ",存款:" + std::to_string(balance);

cout << mesTem << endl;

}

void BankAccount::registerMes(string newName, string newAddr,int newAge,double

newBalance)

{

name = newName;

addr = newAddr;

age = newAge;

balance = newBalance;

}

// 存款方法

void BankAccount::deposit(double amount) {

if (amount > 0) {

balance += amount;

} else {

cerr << "Deposit amount must be positive." << endl;

}

}

// 取款方法

void BankAccount::withdraw(double amount) {

if (amount > balance) {

cerr << "Insufficient funds." << endl;

} else if (amount <= 0) {

cerr << "Withdrawal amount must be positive." << endl;

} else {

balance -= amount;

}

}

// 獲取當前余額的方法

double BankAccount::getBalance() {

return balance;

}

int main()

{

BankAccount user1;

user1.registerMes("老陳","深圳光明區",35,100);在這個示例中, balance 是一個 private 成員變量,它不能被類的外部直接訪問。這保證了賬戶余額只

能通過類提供的方法(如 deposit , withdraw , getBalance )來修改和查詢,從而防止了不合適的

修改,比如直接設置余額為負數或任意值。這樣的設計保證了類的封裝性和數據的完整性。

2.4.3 提問和回答

問:為什么新手學習C++感受不到訪問權限的必要性呢

答:新手學習C++時可能不會立即感受到訪問權限(如 public private protected )的必要性,

主要有以下幾個原因:

1. 簡單的例子和練習:初學者通常從簡單的例子和練習開始,這些例子可能不需要復雜的封裝或繼承

結構。在這種情況下,訪問權限的作用可能不太明顯。

2. 封裝的概念需要時間去理解:封裝是面向對象編程中的一個核心概念,但對于初學者來說,理解封

裝的價值需要一定的時間和實踐。在初期,更多的關注點可能放在基本語法和程序結構上。

3. 缺乏大型項目經驗:在小型項目或單文件程序中,訪問權限的重要性可能不如在大型、多人協作的

項目中那么顯著。在復雜的軟件開發中,適當的訪問控制對于代碼的維護性和可讀性至關重要。

4. 直接操作感覺更簡單:對于初學者來說,直接訪問和修改類的所有成員可能看起來更簡單直接。他

們可能還沒有遇到由于不恰當訪問控制導致的維護和調試問題。

5. 抽象和設計模式的理解:理解何時以及如何使用訪問權限通常涉及到對軟件設計模式和抽象的深入

理解。這些通常是隨著經驗積累和更深入的學習而逐漸掌握的。

隨著經驗的增長,學習者開始處理更復雜的項目,他們將開始意識到恰當的訪問控制的重要性,特別是

在保持代碼的可維護性、可讀性以及在團隊環境中的協作方面。因此,對于教育者和學習者來說,強調

并實踐這些概念是很重要的,以便在編程技能成熟時能夠有效地運用它們。

2.5 引用

引用變量是一個別名,也就是說,它是某個已存在變量的另一個名字。

一旦把引用初始化為某個變量,就可以使用該引用名稱或變量名稱來指向變量。

思維發散:

C語言中,一個數據對應一個內存,通過由一個變量名來訪問這個內存空間的數據,叫做直接訪問,相

對直接訪問,有個間接訪問的說法,叫做指針。

而引用相當于又給這個內存中的數據提供了一個新的變量名,

這個變量名功能比傳統變量名更特殊,是直達地址的,后續代碼驗證!

user1.printUserInfo();

user1.deposit(1000);

cout << user1.getBalance() << endl;

user1.withdraw(30);

cout << user1.getBalance() << endl;

return 0;

}2.6.1 和指針的區別

引用很容易與指針混淆,它們之間有三個主要的不同:

不存在空引用。引用必須連接到一塊合法的內存。

一旦引用被初始化為一個對象,就不能被指向到另一個對象。指針可以在任何時候指向到另一個對

象。

引用必須在創建時被初始化。指針可以在任何時間被初始化。

官方沒有明確說明,但是引用確實不是傳統意義上的獨立變量,它不能

試想變量名稱是變量附屬在內存位置中的標簽,您可以把引用當成是變量附屬在內存位置中的第二

個標簽。因此,您可以通過原始變量名稱或引用來訪問變量的內容。例如:

我們可以為?i 聲明引用變量,如下所示:

在這些聲明中,& 讀作引用。因此,第一個聲明可以讀作?"r 是一個初始化為?i 的整型引用",第二個

聲明可以讀作?"s 是一個初始化為?d ?double 型引用"。下面的實例使用了?int ?double 引用:

int i = 17; int* p = &i; *p = 20;

int& r = i;

double& s = d;

#include <iostream>

using namespace std;

int main ()

{

// 聲明簡單的變量

int i;

double d;

// 聲明引用變量

int& r = i;

double& s = d;

i = 5;

cout << "Value of i : " << i << endl;

cout << "Value of i reference : " << r << endl;

d = 11.7;

cout << "Value of d : " << d << endl;

cout << "Value of d reference : " << s << endl;

return 0;

}2.6.2 把引用作為參數

我們已經討論了如何使用指針來實現引用調用函數。下面的實例使用了引用來實現引用調用函數。

實例

運行結果:

2.6.3 把引用作為返回值

通過使用引用來替代指針,會使?C++ 程序更容易閱讀和維護。C++ 函數可以返回一個引用,方式與返回

一個指針類似。

#include <iostream>

using namespace std;

// 函數聲明

void swap(int& x, int& y);

int main ()

{

// 局部變量聲明

int a = 100;

int b = 200;

cout << "交換前,a 的值:" << a << endl;

cout << "交換前,b 的值:" << b << endl;

/* 調用函數來交換值 */

swap(a, b);

cout << "交換后,a 的值:" << a << endl;

cout << "交換后,b 的值:" << b << endl;

return 0;

}

// 函數定義

void swap(int& x, int& y)

{

int temp;

temp = x; /* 保存地址 x 的值 */

x = y; /* y 賦值給 x */

y = temp; /* x 賦值給 y */

return;

}

交換前,a 的值: 100

交換前,b 的值: 200

交換后,a 的值: 200

交換后,b 的值: 100當函數返回一個引用時,則返回一個指向返回值的隱式指針。這樣,函數就可以放在賦值語句的左邊。

例如,請看下面這個簡單的程序:

當上面的代碼被編譯和執行時,它會產生下列結果:

#include <iostream>

using namespace std;

double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};

double& setValues(int i) {

double& ref = vals[i];

return ref; // 返回第 i 個元素的引用,ref 是一個引用變量,ref 引用 vals[i]

}

// 要調用上面定義函數的主函數

int main ()

{

cout << "改變前的值" << endl;

for ( int i = 0; i < 5; i++ )

{

cout << "vals[" << i << "] = ";

cout << vals[i] << endl;

}

setValues(1) = 20.23; // 改變第 2 個元素

setValues(3) = 70.8; // 改變第 4 個元素

cout << "改變后的值" << endl;

for ( int i = 0; i < 5; i++ )

{

cout << "vals[" << i << "] = ";

cout << vals[i] << endl;

}

return 0;

}

改變前的值

vals[0] = 10.1

vals[1] = 12.6

vals[2] = 33.1

vals[3] = 24.1

vals[4] = 50

改變后的值

vals[0] = 10.1

vals[1] = 20.23

vals[2] = 33.1

vals[3] = 70.8

vals[4] = 50當返回一個引用時,要注意被引用的對象不能超出作用域。所以返回一個對局部變量的引用是不合法

的,但是,可以返回一個對靜態變量的引用。

2.6 重載

2.5.1 函數重載

在同一個作用域內,可以聲明幾個功能類似的同名函數,

這些同名函數的形式參數(指參數的個數、類型或者順序)必須不同。您不能僅通過返回類型的不同來

重載函數。

下面的實例中,同名函數 print() 被用于輸出不同的數據類型:

int& func() {

int q;

//! return q; // 在編譯時發生錯誤

static int x;

return x; // 安全,x 在函數作用域外依然是有效的

}

#include <iostream>

using namespace std;

class printData

{

public:

void print(int i) {

cout << "整數為: " << i << endl;

}

void print(double f) {

cout << "浮點數為: " << f << endl;

}

void print(char c[]) {

cout << "字符串為: " << c << endl;

}

};

int main(void)

{

printData pd;

// 輸出整數

pd.print(5);

// 輸出浮點數

pd.print(500.263);

// 輸出字符串

char c[] = "Hello C++";

pd.print(c);

return 0;

}2.5.2 運算符重載

C++中,運算符重載是一個允許程序員自定義各種運算符(如 + , - , == , != 等)在自定義類型(類或

結構體)上的行為的特性。這意味著你可以定義類似于內置類型的運算符行為,使你的自定義類型更加

直觀和易于使用。

基本原則

1. 不可以創建新的運算符:只能重載已經存在的運算符。

2. 至少有一個操作數是用戶定義的類型:不能重載兩個基本類型的運算符。

3. 不能更改運算符的優先級:重載的運算符保持其原有的優先級和結合性。

示例1:假設我們有一個Person 類,我們可以重載 == 運算符來實現兩個Person是否相等的判斷。

示例2:假設我們有一個簡單的 Point 類,我們可以重載 + 運算符來實現兩個點的加法。

#include <iostream>

using namespace std;

class Person

{

public:

string name;

int inNumberTail;

bool operator==(Person pTmp);

};

bool Person::operator==(Person pTmp){

return pTmp.name == name && pTmp.inNumberTail == inNumberTail;

}

int main()

{

//假設我們認定名字和身份證尾號6位一樣的兩個對象是同一個人!

Person p1;

p1.name = "張三";

p1.inNumberTail = 412508;

Person p2;

p2.name = "張三";

p2.inNumberTail = 412508;

bool ret = p1 == p2;

cout << ret << endl;

return 0;

}

class Point {在這個例子中, operator+ 被重載為一個成員函數,接受一個 Point 類型的常量引用作為參數,并返

回兩個點相加的結果。

這里的 const 表明這個 operator+ 函數不會修改調用它的 Point 對象。它只是讀取對象的 x y

成員,并返回一個新的 Point 對象。這種做法在設計類的時候是很有用的,因為它可以確保某些函數不

會意外地改變對象的狀態,同時也使得這個函數可以在常量對象上被調用。

注意事項

一致性:重載的運算符應與其原始意圖和常見用法保持一致。例如, + 運算符通常應該實現加法,

而不是其他意外的操作。

復雜性:過度使用運算符重載可能導致代碼難以理解和維護。確保它們的使用直觀且合理。

運算符重載是C++中提高代碼可讀性和表達力的強大工具,但需要謹慎使用,以保證代碼的清晰性和維護

性。

2.7 構造函數

2.7.1 什么是構造函數

類的構造函數是類的一種特殊的成員函數,它會在每次創建類的新對象時執行。

構造,那構造的是什么呢?

構造成員變量的初始化值,內存空間等

構造函數的名稱與類的名稱是完全相同的,并且不會返回任何類型,也不會返回?void。構造函數可用于

為某些成員變量設置初始值。

下面的實例有助于更好地理解構造函數的概念:

public:

int x, y;

// 重載 + 運算符

Point operator+(const Point& other) const {

return Point(x + other.x, y + other.y);

}

};

int main() {

Point p1;

p1.x = 1;

p1.y = 2;

Point p2;

p2.x = 2;

p2.y = 3;

Point p3 = p1 + p2; // 使用重載的 + 運算符

std::cout << "p3.x: " << p3.x << ", p3.y: " << p3.y << std::endl; // 輸出

p3.x: 4, p3.y: 6

return 0;

}2.7.2 帶參數構造函數

默認的構造函數沒有任何參數,但如果需要,構造函數也可以帶有參數。這樣在創建對象時就會給對象

賦初始值,如下面的例子所示:

#include <iostream>

#include <string>

using namespace std; // 使用std命名空間

class Car {

public:

string brand; // 不需要使用std::string

int year;

// 無參構造函數

Car() {

brand = "未知";

year = 0;

cout << "無參構造函數被調用" << endl; // 不需要使用std::coutstd::endl

}

void display() {

cout << "Brand: " << brand << ", Year: " << year << endl;

}

};

int main() {

Car myCar; // 創建Car對象

myCar.display(); // 顯示車輛信息

return 0;

}

#include <iostream>

#include <string>

using namespace std;

class Car {

public:

string brand;

int year;

// 帶參數的構造函數,使用常規的賦值方式

Car(string b, int y) {

brand = b;

year = y;

}

void display() {

cout << "Brand: " << brand << ", Year: " << year << endl;

}2.7.3 拷貝構造函數

2.7.3.1 基本概念及發生條件

拷貝構造函數是?C++ 中的一種特殊的構造函數,用于創建一個新對象作為現有對象的副本。它在以下幾

種情況下被調用:

1. 當一個新對象被創建為另一個同類型的現有對象的副本時

例如: MyClass obj1 = obj2; MyClass obj1(obj2); ,其中 obj2 是現有的對象。

2. 將對象作為參數傳遞給函數時(按值傳遞):

當對象作為參數傳遞給函數,并且參數不是引用時,會使用拷貝構造函數創建函數內部的對象

副本。

3. 從函數返回對象時(按值返回):

當函數返回對象,并且沒有使用引用或指針時,拷貝構造函數用于從函數返回值創建副本。

4. 初始化數組或容器中的元素時

例如,在創建一個包含對象的數組時,數組中的每個對象都是通過拷貝構造函數初始化的。

拷貝構造函數的典型聲明如下:

其中, other 是對同類型對象的引用,通常是常量引用。

示例代碼

};

int main() {

Car myCar("Toyota", 2020); // 使用帶參數的構造函數創建Car對象

myCar.display(); // 顯示車輛信息

return 0;

}

class MyClass {

public:

MyClass(const MyClass& other);

};

#include <iostream>

#include <string>

using namespace std;

class Car {

public:

string brand;

int year;

// 常規構造函數

Car(string b, int y) : brand(b), year(y) {}

// 拷貝構造函數2.7.3.2 淺拷貝

?C++ 中,深拷貝和淺拷貝是處理對象拷貝時的兩種不同方法,尤其是在對象包含指針或動態分配的內

存時。我將分別給出深拷貝和淺拷貝的例子。

淺拷貝?(Shallow Copy)

淺拷貝只復制對象的成員變量的值。如果成員變量是指針,則復制指針的值(即內存地址),而不是指

針所指向的實際數據。這會導致多個對象共享相同的內存地址。

Car(const Car& other) {

brand = other.brand;

year = other.year;

cout << "拷貝構造函數被調用" << endl;

}

void display() {

cout << "Brand: " << brand << ", Year: " << year << endl;

}

};

int main() {

Car car1("Toyota", 2020); // 使用常規構造函數

Car car2 = car1; // 使用拷貝構造函數

car1.display();

car2.display();

return 0;

}

#include <iostream>

using namespace std;

class Shallow {

public:

int* data;

Shallow(int d) {

//(d):這是初始化表達式。在這里,分配的 int 類型內存被初始化為 d 的值。如果 d 的值是

20,那么分配的內存將被初始化為 20

data = new int(d); // 動態分配內存

cout << "觀察數據:" << endl;

cout << d << endl;

cout << *data << endl;

cout << "觀察內存在構造函數中:" << endl;

cout << data << endl;

}

// 默認的拷貝構造函數是淺拷貝

~Shallow() {

delete data; // 釋放內存

}在這個例子中, obj2 是通過淺拷貝 obj1 創建的。這意味著 obj1.data obj2.data 指向相同的內

存地址。

obj1 obj2 被銷毀時,同一內存地址會被嘗試釋放兩次,導致潛在的運行時錯誤。

QT中我們不能直觀看見,在Linux中我們獲得如下運行結果:

2.7.3.3 深拷貝

深拷貝復制對象的成員變量的值以及指針所指向的實際數據。這意味著創建新的獨立副本,避免了共享

內存地址的問題。

};

int main() {

Shallow obj1(20);

Shallow obj2 = obj1; // 淺拷貝

cout << "觀察內存在main函數obj2data地址:" << endl;

cout << obj2.data << endl;

cout << "obj1 data: " << *obj1.data << ", obj2 data: " << *obj2.data << endl;

return 0;

}

#include <iostream>

using namespace std;

class Deep {

public:

int* data;

Deep(int d) {

data = new int(d); // 動態分配內存

cout << "觀察數據:" << endl;

cout << d << endl;

cout << *data << endl;

cout << "觀察內存在構造函數中:" << endl;在這個例子中, obj2 是通過深拷貝 obj1 創建的。這意味著 obj1.data obj2.data 指向不同的內

存地址。每個對象有自己的內存副本,因此不會相互影響,避免了潛在的運行時錯誤。

2.7.3.4 規則三則

?C++ 中,規則三則(Rule of Three)是一個面向對象編程原則,它涉及到類的拷貝控制。規則三則指

出,如果你需要顯式地定義或重載類的任何一個拷貝控制操作(拷貝構造函數、拷貝賦值運算符、析構

函數),那么你幾乎肯定需要顯式地定義或重載所有三個。這是因為這三個功能通常都是用于管理動態

分配的資源,比如在堆上分配的內存。

下面是一個遵循規則三則的簡單示例:

cout << data << endl;

}

// 顯式定義深拷貝的拷貝構造函數

Deep(const Deep& source) {

data = new int(*source.data); // 復制數據,而不是地址

cout << "深拷貝構造函數\n";

}

~Deep() {

delete data; // 釋放內存

}

};

int main() {

Deep obj1(20);

Deep obj2 = obj1; // 深拷貝

cout << "觀察內存在main函數obj2data地址:" << endl;

cout << obj2.data << endl;

cout << "obj1 data: " << *obj1.data << ", obj2 data: " << *obj2.data << endl;

return 0;

}#include <iostream>

#include <cstring>

class MyClass {

private:

char* buffer;

public:

// 構造函數

MyClass(const char* str) {

if (str) {

buffer = new char[strlen(str) + 1];

strcpy(buffer, str);

} else {

buffer = nullptr;

}

}

// 析構函數

~MyClass() {

delete[] buffer;

}

// 拷貝構造函數

MyClass(const MyClass& other) {

if (other.buffer) {

buffer = new char[strlen(other.buffer) + 1];

strcpy(buffer, other.buffer);

} else {

buffer = nullptr;

}

}

// 拷貝賦值運算符

MyClass& operator=(const MyClass& other) {

if (this != &other) {

delete[] buffer; // 首先刪除當前對象的資源

if (other.buffer) {

buffer = new char[strlen(other.buffer) + 1];

strcpy(buffer, other.buffer);

} else {

buffer = nullptr;

}

}

return *this;

}

};

int main() {

MyClass obj1("Hello");

MyClass obj2 = obj1; // 調用拷貝構造函數

MyClass obj3("World");

obj3 = obj1; // 調用拷貝賦值運算符

return 0;

}在這個例子中:

構造函數為成員變量 buffer 分配內存,并復制給定的字符串。

析構函數釋放 buffer 所占用的內存,以避免內存泄露。

拷貝構造函數創建一個新對象作為另一個現有對象的副本,并為其分配新的內存,以避免多個對象

共享同一內存。

拷貝賦值運算符更新對象時,首先釋放原有資源,然后根據新對象的狀態分配新資源。

這個類遵循規則三則,確保了動態分配資源的正確管理,避免了內存泄露和淺拷貝問題。

2.7.3.5 避免不必要的拷貝

避免不必要的拷貝是?C++ 程序設計中的一個重要原則,尤其是在處理大型對象或資源密集型對象時。使

用引用(包括常量引用)和移動語義(C++11 引入)是實現這一目標的兩種常見方法。下面是兩個示

例:

1. 使用引用傳遞對象

通過使用引用(尤其是常量引用)來傳遞對象,可以避免在函數調用時創建對象的副本。

在這個例子中, processLargeObject 函數接受一個對 LargeObject 類型的常量引用,避免了在函數

調用時復制整個 LargeObject

2. 使用移動語義

C++11 引入了移動語義,允許資源(如動態分配的內存)的所有權從一個對象轉移到另一個對象,這避

免了不必要的拷貝。

#include <iostream>

#include <vector>

using namespace std;

class LargeObject {

// 假設這是一個占用大量內存的大型對象

};

void processLargeObject(const LargeObject& obj) {

// 處理對象,但不修改它

cout << "Processing object..." << endl;

}

int main() {

LargeObject myLargeObject;

processLargeObject(myLargeObject); // 通過引用傳遞,避免拷貝

return 0;

}

#include <iostream>

#include <utility> // 對于 std::move

using namespace std;

class MovableObject {

public:

MovableObject() {在這個例子中, MovableObject 類有一個移動構造函數和一個移動賦值運算符,它們允許對象的資源

(如動態分配的內存)在賦值或返回時被移動而非復制。這減少了對資源的不必要拷貝,提高了效率。

通過這些方法,你可以在?C++ 程序中有效地減少不必要的對象拷貝,尤其是對于大型或資源密集型的對

象。

2.7.3.6 拷貝構造函數的隱式調用

?C++ 中,拷貝構造函數可能會在幾種不明顯的情況下被隱式調用。這種隱式調用通常發生在對象需要

被復制時,但代碼中并沒有明顯的賦值或構造函數調用。了解這些情況對于高效和正確地管理資源非常

重要。下面是一些典型的隱式拷貝構造函數調用的例子:

1. 作為函數參數傳遞(按值傳遞)

當對象作為函數參數按值傳遞時,會調用拷貝構造函數來創建參數的本地副本。

// 構造函數

}

MovableObject(const MovableObject& other) {

// 拷貝構造函數(可能很昂貴)

}

MovableObject(MovableObject&& other) noexcept {

// 移動構造函數(輕量級)

// 轉移資源的所有權

}

MovableObject& operator=(MovableObject&& other) noexcept {

// 移動賦值運算符

// 轉移資源的所有權

return *this;

}

};

MovableObject createObject() {

MovableObject obj;

return obj; // 返回時使用移動語義,而非拷貝

}

int main() {

MovableObject obj = createObject(); // 使用移動構造函數

return 0;

}

#include <iostream>

using namespace std;

class MyClass {

public:

MyClass() {}

MyClass(const MyClass &) {

cout << "拷貝構造函數被隱式調用" << endl;

}

};2. 從函數返回對象(按值返回)

當函數返回一個對象時,拷貝構造函數會被用于創建返回值的副本。

3. 初始化另一個對象

當用一個對象初始化另一個同類型的新對象時,會使用拷貝構造函數。

在所有這些情況下,如果類包含資源管理(例如,動態內存分配),那么正確地實現拷貝構造函數是非

常重要的,以確保資源的正確復制和管理,防止潛在的內存泄漏或其他問題。此外,隨著?C++11 的引

入,移動語義提供了對資源的高效管理方式,可以減少這些場景中的資源復制。

2.7.3.7 禁用拷貝構造函數

?C++ 中,禁用拷貝構造函數是一種常用的做法,尤其是在設計那些不應該被復制的類時。這可以通過

將拷貝構造函數聲明為 private 或使用?C++11 引入的 delete 關鍵字來實現。這樣做的目的是防止類

的對象被拷貝,從而避免可能導致的問題,如資源重復釋放、無意義的資源復制等。

使用 delete 關鍵字

?C++11 及更高版本中,可以使用 delete 關鍵字明確指定不允許拷貝構造:

void function(MyClass obj) {

// obj 的操作

}

int main() {

MyClass myObject;

function(myObject); // 調用 function 時,拷貝構造函數被隱式調用

return 0;

}

MyClass function() {

MyClass tempObject;

return tempObject; // 返回時,拷貝構造函數被隱式調用

}

int main() {

MyClass myObject = function(); // 接收返回值時可能還會有一次拷貝(或移動)

return 0;

}

int main() {

MyClass obj1;

MyClass obj2 = obj1; // 初始化時,拷貝構造函數被隱式調用

return 0;

}

class NonCopyable {

public:

NonCopyable() = default; // 使用默認構造函數

// 禁用拷貝構造函數

NonCopyable(const NonCopyable&) = delete;要點

描述

定義和作用

拷貝構造函數在創建對象作為另一個現有對象副本時調用,通常有一個對同

類型對象的常量引用參數。

語法

典型聲明為 ClassName(const ClassName &other)

深拷貝與淺拷貝

淺拷貝復制值,深拷貝創建資源的獨立副本。對于包含指針的類,深拷貝通

常必要。

規則三則?(Rule of

Three)

如果實現了拷貝構造函數、拷貝賦值運算符或析構函數中的任何一個,通常

應該實現所有三個。

這種方法清晰明了,它向編譯器和其他程序員直接表明該類的對象不能被拷貝。

使用 private 聲明(C++98/03

?C++11 之前,常見的做法是將拷貝構造函數和拷貝賦值運算符聲明為 private ,并且不提供實現:

在這個例子中,任何嘗試拷貝 NonCopyable 類型對象的操作都會導致編譯錯誤,因為拷貝構造函數和

拷貝賦值運算符是私有的,外部代碼無法訪問它們。

通過這些方法,你可以確保你的類的對象不會被意外地拷貝,從而避免可能出現的資源管理相關的錯

誤。

2.7.3.8 拷貝構造函數總結

C++ 中拷貝構造函數需要注意的七個要點的表格:

// 禁用拷貝賦值運算符

NonCopyable& operator=(const NonCopyable&) = delete;

};

int main() {

NonCopyable obj1;

// NonCopyable obj2 = obj1; // 編譯錯誤,拷貝構造函數被禁用

return 0;

}

class NonCopyable {

private:

// 將拷貝構造函數和拷貝賦值運算符設為私有

NonCopyable(const NonCopyable&);

NonCopyable& operator=(const NonCopyable&);

public:

NonCopyable() {}

};

int main() {

NonCopyable obj1;

// NonCopyable obj2 = obj1; // 編譯錯誤,因為無法訪問私有的拷貝構造函數

return 0;

}要點

描述

避免不必要的拷貝

對于大型對象,使用移動語義避免不必要的拷貝,并在傳遞對象時使用引用

或指針。

拷貝構造函數的隱

式調用

不僅在顯式復制時調用,也可能在將對象作為函數參數傳遞、從函數返回對

象時隱式調用。

禁用拷貝構造函數

對于某些類,可以通過將拷貝構造函數聲明為私有或使用 delete 關鍵字

禁用拷貝。

2.7.4 使用初始化列表

C++中,使用初始化列表來初始化類的字段是一種高效的初始化方式,尤其在構造函數中。初始化列表

直接在對象的構造過程中初始化成員變量,而不是先創建成員變量后再賦值。這對于提高性能尤其重

要,特別是在涉及到復雜對象或引用和常量成員的情況下。

初始化列表緊跟在構造函數參數列表后面,以冒號( : )開始,后跟一個或多個初始化表達式,每個表

達式通常用逗號分隔。下面是使用初始化列表初始化字段的例子:

在這個例子中, MyClass 有三個成員變量: a int 類型)、 b double 類型)和 c

std::string 類型)。當創建 MyClass 的一個實例時,我們通過構造函數傳遞三個參數,這些參數

被用于通過初始化列表直接初始化成員變量。初始化列表 : a(x), b(y), c(z) 的意思是用 x 初始化

a ,用 y 初始化 b ,用 z 初始化 c

初始化列表的優點包括:

1. 效率:對于非基本類型的對象,使用初始化列表比在構造函數體內賦值更高效,因為它避免了先默

認構造然后再賦值的額外開銷。

2. 必要性:對于引用類型和常量類型的成員變量,必須使用初始化列表,因為這些類型的成員變量在

構造函數體內不能被賦值。

3. 順序:成員變量的初始化順序是按照它們在類中聲明的順序,而不是初始化列表中的順序。

使用初始化列表是C++中推薦的初始化類成員變量的方式,因為它提供了更好的性能和靈活性。

class MyClass {

private:

int a;

double b;

std::string c;

public:

// 使用初始化列表來初始化字段

MyClass(int x, double y, const std::string& z) : a(x), b(y), c(z) {

// 構造函數體

}

};2.7.5 this關鍵字

?C++ 中, this 關鍵字是一個指向調用對象的指針。它在成員函數內部使用,用于引用調用該函數的

對象。使用 this 可以明確指出成員函數正在操作的是哪個對象的數據成員。下面是一個使用 Car 類來

展示 this 關鍵字用法的示例:

示例代碼

下面的代碼展示了如何使用 this 關鍵字:

#include <iostream>

#include <string>

using namespace std;

class Car {

private:

string brand;

int year;

public:

Car(string brand, int year) {

this->brand = brand;

this->year = year;

// cout << "構造函數中:" << endl;

// cout << this << endl;

}

void display() const {

cout << "Brand: " << this->brand << ", Year: " << this->year << endl;

// 也可以不使用 this->,直接寫 brand year

}

Car& setYear(int year) {

this->year = year; // 更新年份

return *this; // 返回調用對象的引用

}

};

int main()

{

Car car("寶馬",2024);

car.display();

// 鏈式調用

car.setYear(2023).display();

// cout << "main函數中:" << endl;

// cout << &car << endl;

// Car car2("寶馬",2024);

// cout << "main函數中:" << endl;

// cout << &car2 << endl;

return 0;

}在這個例子中, Car 類的構造函數使用 this 指針來區分成員變量和構造函數參數。同樣, setYear

成員函數使用 this 指針來返回調用該函數的對象的引用,這允許鏈式調用,如

myCar.setYear(2021).display(); 。在 main 函數中創建了 Car 類型的對象,并展示了如何使用這

些成員函數。

2.7.6 new關鍵字

C++中, new 關鍵字用于動態分配內存。它是C++中處理動態內存分配的主要工具之一,允許在程序運

行時根據需要分配內存。

基本用法

分配單個對象:使用 new 可以在堆上動態分配一個對象。例如, new int 會分配一個 int 類型的空

間,并返回一個指向該空間的指針。

分配對象數組new 也可以用來分配一個對象數組。例如, new int[10] 會分配一個包含10個整數的

數組。

初始化:可以在 new 表達式中使用初始化。對于單個對象,可以使用構造函數的參數:

delete 配對使用

使用 new 分配的內存必須顯式地通過 delete (對于單個對象)或 delete[] (對于數組)來釋放,以

避免內存泄露:

釋放單個對象:

釋放數組:

注意事項

異常安全:如果 new 分配內存失敗,它會拋出 std::bad_alloc 異常(除非使用了 nothrow

本)。

內存泄露:忘記釋放使用 new 分配的內存會導致內存泄露。

匹配使用 delete delete[] :為避免未定義行為,使用 new 分配的單個對象應該使用

delete 釋放,使用 new[] 分配的數組應該使用 delete[] 釋放。

示例代碼

int* ptr = new int; //C語言中,int *p = (int *)malloc(sizeof(int));

int* arr = new int[10]; //C語言中,int *arr = (int *)malloc(sizeof(int)*10);

MyClass* obj = new MyClass(arg1, arg2);

delete ptr; // 釋放 ptr 指向的對象

delete[] arr; // 釋放 arr 指向的數組

class MyClass {

public:

MyClass() {在這個例子中, new 被用來分配一個 MyClass 類型的對象和一個整數數組,然后使用 delete

delete[] 來釋放內存。每個 new 都對應一個 delete ,保證了動態分配的內存被適當管理。

2.8 析構函數

2.8.1 什么是析構函數

析構函數是C++中的一個特殊的成員函數,它在對象生命周期結束時被自動調用,用于執行對象銷毀前的

清理工作。析構函數特別重要,尤其是在涉及動態分配的資源(如內存、文件句柄、網絡連接等)的情

況下。

基本特性

1. 名稱:析構函數的名稱由波浪號( ~ )后跟類名構成,如 ~MyClass()

2. 無返回值和參數:析構函數不接受任何參數,也不返回任何值。

3. 自動調用:當對象的生命周期結束時(例如,一個局部對象的作用域結束,或者使用 delete 刪除

一個動態分配的對象),析構函數會被自動調用。

4. 不可重載:每個類只能有一個析構函數。

5. 繼承和多態:如果一個類是多態基類,其析構函數應該是虛的。

示例

假設我們有一個類 MyClass ,它包含了動態分配的內存或其他資源:

std::cout << "Object created" << std::endl;

}

};

int main() {

// 分配單個對象

MyClass* myObject = new MyClass();

// 分配對象數組

int* myArray = new int[5]{1, 2, 3, 4, 5};

// 使用對象和數組...

// 釋放內存

delete myObject;

delete[] myArray;

return 0;

}

#include <iostream>

using namespace std;

class MyClass{

private:

int* datas;

public:

MyClass(int size){

datas = new int[size];要點

描述

定義和作

析構函數在對象生命周期結束時自動調用,用于清理對象可能持有的資源。

語法

析構函數名稱由波浪線?(~) 后跟類名構成,例如?MyClass 的析構函數為

~MyClass()

資源管理

用于釋放對象在生命周期中分配的資源,如動態內存、文件句柄、網絡連接等。

自動調用

機制

當對象離開其作用域或通過 delete 刪除時,將自動調用其析構函數。

防止資源

泄露

正確實現析構函數對防止資源泄露至關重要,特別是在涉及動態資源分配的情況。

虛析構函

如果類作為基類設計,應有一個虛析構函數,以確保正確調用派生類的析構函數。

析構函數

與異常

析構函數不應拋出異常,如果可能拋出,應在函數內捕獲。

在這個示例中, MyClass 的構造函數分配了一塊內存,而析構函數釋放了這塊內存。當 obj 的生命周

期結束時(即離開了它的作用域), MyClass 的析構函數被自動調用,負責清理資源,防止內存泄露。

重要性

析構函數在管理資源方面非常重要。沒有正確實現析構函數,可能導致資源泄露或其他問題。在基于RAII

(資源獲取即初始化)原則的C++編程實踐中,確保資源在對象析構時被適當釋放是非常關鍵的。當使用

智能指針和其他自動資源管理技術時,可以減少顯式編寫析構函數的需要,但了解析構函數的工作原理

仍然很重要。

以下是關于?C++ 中析構函數需要了解的十個要點的表格:

標注粗體部分,是能快速上手的內容,方便后續QT的學習,而沒有粗體的部分,會在QT結束后,如果安

C++深入講解的課程的話,會安排到。

同理拷貝構造函數,考慮到學習策略安排,這里先陳列出來。

}

~MyClass(){

cout << "析構函數被調用" << endl;

delete[] datas;

}

};

int main()

{

MyClass m1(5);

MyClass *m2 = new MyClass(10);

delete m2;

return 0;

}要點

描述

刪除的析

構函數

可以通過將析構函數聲明為刪除( ~MyClass() = delete; )來禁止刪除某類對象。

與構造函

數的關系

每個類只能有一個析構函數,不可重載,與構造函數相比。

規則三

/五則

如果類需要自定義析構函數、拷貝構造函數或拷貝賦值運算符,可能也需要自定義另

外兩個(規則三則)。在?C++11 后還包括移動構造函數和移動賦值運算符(規則五

則)。

這個表格概括了在學習和使用?C++ 析構函數時應考慮的主要點

2.9 靜態成員

2.9.1 靜態成員的定義

靜態成員在C++類中是一個重要的概念,它包括靜態成員變量和靜態成員函數。靜態成員的特點和存在的

意義如下:

靜態成員變量

1. 定義:靜態成員變量是類的所有對象共享的變量。與普通成員變量相比,無論創建了多少個類的實

例,靜態成員變量只有一份拷貝。

2. 初始化:靜態成員變量需要在類外進行初始化,通常在類的實現文件中。

3. 訪問:靜態成員變量可以通過類名直接訪問,不需要創建類的對象。也可以通過類的對象訪問。

4. 用途:常用于存儲類級別的信息(例如,計數類的實例數量)或全局數據需要被類的所有實例共

享。

靜態成員函數

1. 定義:靜態成員函數是可以不依賴于類的實例而被調用的函數。它不能訪問類的非靜態成員變量和

非靜態成員函數。

2. 訪問:類似于靜態成員變量,靜態成員函數可以通過類名直接調用,也可以通過類的實例調用。

3. 用途:常用于實現與具體對象無關的功能,或訪問靜態成員變量。

示例代碼

class MyClass {

public:

static int staticValue; // 靜態成員變量

MyClass() {

// 每創建一個對象,靜態變量增加1

staticValue++;

}

static int getStaticValue() {

// 靜態成員函數存在的意義

共享數據:允許對象之間共享數據,而不需要每個對象都有一份拷貝。

節省內存:對于頻繁使用的類,使用靜態成員可以節省內存。

獨立于對象的功能:靜態成員函數提供了一種在不創建對象的情況下執行操作的方法,這對于實現

工具函數或管理類級別狀態很有用。

2.9.2 靜態成員變量的作用

靜態成員變量在C++中的一個典型應用是用于跟蹤類的實例數量。這個案例體現了靜態成員變量的特性:

它們在類的所有實例之間共享,因此適合于存儲所有實例共有的信息。

下面是一個示例,展示了如何使用靜態成員變量來計數一個類的實例數量:

return staticValue;

}

};

// 類外初始化靜態成員變量

int MyClass::staticValue = 0;

int main() {

MyClass obj1, obj2;

std::cout << MyClass::getStaticValue(); // 輸出2

}

#include <iostream>

using namespace std;

class Myclass{

private:

static int staticNumofInstance;

public:

Myclass(){

staticNumofInstance++;

}

~Myclass(){

staticNumofInstance--;

}

static int getNunofInstance(){

return staticNumofInstance;

}

};

int Myclass::staticNumofInstance = 0;

int main()

{

Myclass m1;在這個例子中:

Myclass 類有一個靜態成員變量 staticNumofInstance ,用來跟蹤該類的實例數量。

每當創建 Myclass 的新實例時,構造函數會增加 staticNumofInstance

每當一個 Myclass 實例被銷毀時,析構函數會減少 staticNumofInstance

通過靜態成員函數 getNunofInstance 可以隨時獲取當前的實例數量。

靜態成員變量 staticNumofInstance 在類外初始化為0

這個案例展示了靜態成員變量如何在類的所有實例之間共享,并為所有實例提供了一個共同的狀態(在

這個例子中是實例的數量)。這種技術在需要跟蹤對象數量或實現某種形式的資源管理時特別有用。

2.10 繼承

2.10.1 繼承基本概念

繼承是面向對象編程(OOP)中的一個核心概念,特別是在C++中。它允許一個類(稱為派生類或子

類)繼承另一個類(稱為基類或父類)的屬性和方法。繼承的主要目的是實現代碼重用,以及建立一種

類型之間的層次關系。

特點

1. 代碼重用:子類繼承了父類的屬性和方法,減少了代碼的重復編寫。

2. 擴展性:子類可以擴展父類的功能,添加新的屬性和方法,或者重寫(覆蓋)現有的方法。

3. 多態性:通過繼承和虛函數,C++支持多態,允許在運行時決定調用哪個函數。

基本用法

C++中,繼承可以是公有(public)、保護(protected)或私有(private)的,這決定了基類成員在

派生類中的訪問權限。

cout << Myclass::getNunofInstance() << endl;

Myclass m2;

cout << m2.getNunofInstance() << endl;

{

Myclass m3;

cout << Myclass::getNunofInstance() << endl;

Myclass m4;

cout << Myclass::getNunofInstance() << endl;

}

cout << Myclass::getNunofInstance() << endl;

Myclass *m5 = new Myclass;

cout << Myclass::getNunofInstance() << endl;

delete m5;

cout << Myclass::getNunofInstance() << endl;

return 0;

}

#include <iostream>

using namespace std;

//基類,父類在這個例子中, Vehicle 類公有地繼承自 Vehicle 類,這意味著所有 Vehicle 類的公有成員在

Vehicle 類中也是公有的。

讓我們用一個簡單而有趣的案例來說明繼承的概念:動物園中的動物。

想象我們正在創o在這個程序中,我們有一個基類 Animal ,它定義了所有動物共有的特性和行為。然

后,我們可以創建幾個派生類,如 Lion Elephant Bird ,這些類繼承自 Animal 類,并添加或

修改特定于它們自己的特性和行為。

基類:Animal

class Vehicle{ //交通工具,車,抽象的概念

public:

string type;

string contry;

string color;

double price;

int numOfWheel;

void run(){

cout << "車跑起來了" << endl;

}

void stop();

};

//派生類,子類

class Bickle : public Vehicle{

};

//派生類,子類

class Roadster : public Vehicle{ //跑車,也是抽象,比父類感覺上范圍縮小了點

public:

int stateOfTop;

void openTopped();

void pdrifting();

};

int main()

{

Roadster ftype;

ftype.type = "捷豹Ftype";

ftype.run();

Bickle bike;

bike.type = "死飛";

bike.run();

return 0;

}

#include <iostream>

#include <string>

class Animal {

protected:派生類:Lion

派生類:Elephant

派生類:Bird

std::string name;

int age;

public:

Animal(std::string n, int a) : name(n), age(a) {}

virtual void makeSound() {

std::cout << name << " makes a sound." << std::endl;

}

virtual void display() {

std::cout << "Animal: " << name << ", Age: " << age << std::endl;

}

};

class Lion : public Animal {

public:

Lion(std::string n, int a) : Animal(n, a) {}

void makeSound() override {

std::cout << name << " roars." << std::endl;

}

void display() override {

std::cout << "Lion: " << name << ", Age: " << age << std::endl;

}

};

class Elephant : public Animal {

public:

Elephant(std::string n, int a) : Animal(n, a) {}

void makeSound() override {

std::cout << name << " trumpets." << std::endl;

}

void display() override {

std::cout << "Elephant: " << name << ", Age: " << age << std::endl;

}

};基類成員類型

public 繼承

protected 繼承

private 繼承

public

public

protected

private

protected

protected

protected

private

使用這些類

在這個例子中:

Animal 是基類,定義了所有動物共有的屬性(如 name age )和方法(如 makeSound

display )。

Lion Elephant Bird 是派生類,它們繼承了 Animal 的特性,并根據自身的特性重寫了

makeSound display 方法。

main 函數中,創建了各種動物的實例,并展示了它們的行為。

這個例子展示了繼承如何使代碼更有組織、更易于管理,并且如何通過重寫基類方法來實現多態性。

2.10.2 權限對繼承的影響

C++中,訪問控制符對繼承的影響可以通過下表來清晰地展示。這個表格展示了不同類型的繼承

public protected private )如何影響基類的不同類型成員( public protected

private )在派生類中的訪問級別。

class Bird : public Animal {

public:

Bird(std::string n, int a) : Animal(n, a) {}

void makeSound() override {

std::cout << name << " sings." << std::endl;

}

void display() override {

std::cout << "Bird: " << name << ", Age: " << age << std::endl;

}

};

int main() {

Lion lion("Leo", 5);

Elephant elephant("Ella", 10);

Bird bird("Bella", 2);

lion.display();

lion.makeSound();

elephant.display();

elephant.makeSound();

bird.display();

bird.makeSound();

return 0;

}基類成員類型

public 繼承

protected 繼承

private 繼承

private

不可訪問

不可訪問

不可訪問

訪問權限

類內部

同一個類的對象

派生類(子類)

類外部

public

?? 可訪問

?? 可訪問

?? 可訪問

?? 可訪問

private

?? 可訪問

? 不可訪問

? 不可訪問

? 不可訪問

protected

?? 可訪問

? 不可訪問

?? 可訪問

? 不可訪問

解釋:

public 繼承:基類的 public 成員在派生類中仍然是 public 的, protected 成員仍然是

protected 的。基類的 private 成員在派生類中不可訪問。

protected 繼承:基類的 public protected 成員在派生類中都變成 protected 的。基類

private 成員在派生類中不可訪問。

private 繼承:基類的 public protected 成員在派生類中都變成 private 的。基類的

private 成員在派生類中不可訪問。

這個表格提供了一個快速參考,幫助理解在不同類型的繼承中基類成員的訪問級別是如何變化的。記

住,無論繼承類型如何,基類的 private 成員始終不可直接在派生類中訪問。

訪問權限回顧

課程上寫的驗證代碼

#include <iostream>

using namespace std;

//基類,父類

class Vehicle{ //交通工具,車,抽象的概念

public:

string type;

string contry;

string color;

double price;

int numOfWheel;

protected:

int protectedData;

private:

int privateData;

public:

void run(){

cout << "車跑起來了" << endl;

}

void stop();

};

//私有繼承測試

class TestClass : private Vehicle{

public:void tsetFunc(){

price = 10; //基類的公有數據被私有繼承后,在派生類中權限編程私有,只限在類內部使用

}

};

//公有繼承測試

class Truck : protected Vehicle{

public:

void testFunc(){

type = "數據測試"; //編程了公有權限

protectedData = 10; //保持公有權限

privateData = 10; //報錯了,基類的私有成員,不管哪種方式的繼承都是不可訪問的。

}

};

//公有繼承,基類的公有權限和保護權限不變,私有成員不能訪問

class Bickle : public Vehicle{

public:

void testFunc(){

protectedData = 10;

}

};

//派生類,子類

class Roadster : public Vehicle{ //跑車,也是抽象,比父類感覺上范圍縮小了點

public:

int stateOfTop;

void openTopped();

void pdrifting();

};

int main()

{

TestClass test;

test.price = 3.3; //報錯了,基類的公有成員被私有繼承后,降為私有權限

Truck t;

t.type = "測試"; //報錯了,基類的公有成員被保護繼承后,降為保護權限

t.protectedData = 10; //從報錯信息看出,保護繼承造成基類的保護成員還是保持保護權限

Roadster ftype;

ftype.type = "捷豹Ftype";

ftype.run();

Bickle bike;

bike.type = "死飛";

bike.run();

return 0;

}2.10.3 基類構造函數

C++中,派生類可以通過其構造函數的初始化列表來調用基類的構造函數。這是在構造派生類對象時初

始化基類部分的標準做法

當創建派生類的對象時,基類的構造函數總是在派生類的構造函數之前被調用。如果沒有明確指定,將

調用基類的默認構造函數。如果基類沒有默認構造函數,或者你需要調用一個特定的基類構造函數,

需要在派生類構造函數的初始化列表中明確指定

示例

假設我們有一個基類 Base 和一個派生自 Base 的類 Derived

在這個例子中:

Base 類有一個接受一個整數參數的構造函數。

Derived 類繼承自 Base ,它的構造函數接受一個整數和一個雙精度浮點數。在其初始化列表中,

它調用 Base 類的構造函數,并傳遞整數參數。

Derived 類的對象被創建時,首先調用 Base 類的構造函數,然后調用 Derived 類的構造函

數。

通過這種方式,派生類能夠確保其基類部分被正確初始化。在繼承層次結構中,這是非常重要的,特別

是當基類需要一些特定的初始化操作時。

class Base {

public:

int data;

Base(int x) {

std::cout << "Base constructor with x = " << x << std::endl;

}

};

class Derived : public Base {

public:

double ydata;

Derived(int x, double y) : Base(x) { // 調用 Base 類的構造函數

std::cout << "Derived constructor with y = " << y << std::endl;

}

};

int main() {

Derived obj(10, 3.14); // 首先調用 Base(10),然后調用 Derived 的構造函數

return 0;

}

#include <iostream>

using namespace std;

//基類,父類

class Vehicle{ //交通工具,車,抽象的概念

public:

string contry;

double price;2.10.4 虛函數

C++中, virtual override 關鍵字用于支持多態,尤其是在涉及類繼承和方法重寫的情況下。正

確地理解和使用這兩個關鍵字對于編寫可維護和易于理解的面向對象代碼至關重要。

virtual 關鍵字

1. 使用場景:在基類中聲明虛函數。

2. 目的:允許派生類重寫該函數,實現多態。

3. 行為:當通過基類的指針或引用調用一個虛函數時,調用的是對象實際類型的函數版本。

4. 示例

Vehicle(string contry, double price){

cout << "基類的構造函數被調用" << endl;

this->contry = contry;

this->price = price;

};

void run(){

cout << "車跑起來了" << endl;

}

void stop();

};

//派生類,子類

class Roadster : public Vehicle{ //跑車,也是抽象,比父類感覺上范圍縮小了點

public:

int stateOfTop;

Roadster(string contry, double price, int state) : Vehicle(contry, price){

cout << "派生類的構造函數被調用" << endl;

stateOfTop = state;

}

void openTopped();

void pdrifting();

};

int main()

{

Roadster FTYPE("法國",70,0);

return 0;

}override 關鍵字

1. 使用場景:在派生類中重寫虛函數。

2. 目的:明確指示函數意圖重寫基類的虛函數。

3. 行為:確保派生類的函數確實重寫了基類中的一個虛函數。如果沒有匹配的虛函數,編譯器會報

錯。

4. 示例

注意點

只在派生類中使用?overrideoverride 應僅用于派生類中重寫基類的虛函數。

虛析構函數:如果類中有虛函數,通常應該將析構函數也聲明為虛的。

默認情況下,成員函數不是虛的:在C++中,成員函數默認不是虛函數。只有顯式地使用 virtual

關鍵字才會成為虛函數。

繼承中的虛函數:一旦在基類中聲明為虛函數,該函數在所有派生類中自動成為虛函數,無論是否

使用 virtual 關鍵字。

正確使用 virtual override 關鍵字有助于清晰地表達程序員的意圖,并利用編譯器檢查來避免常

見的錯誤,如簽名不匹配導致的非預期的函數重寫。

2.10.5 多重繼承

C++中,多重繼承是一種允許一個類同時繼承多個基類的特性。這意味著派生類可以繼承多個基類的屬

性和方法。多重繼承增加了語言的靈活性,但同時也引入了額外的復雜性,特別是當多個基類具有相同

的成員時。

基本概念

在多重繼承中,派生類繼承了所有基類的特性。這包括成員變量和成員函數。如果不同的基類有相同名

稱的成員,則必須明確指出所引用的是哪個基類的成員。

示例

假設有兩個基類 ClassA ClassB ,以及一個同時從這兩個類繼承的派生類 Derived

class Base {

public:

virtual void func() {

std::cout << "Function in Base" << std::endl;

}

};

class Derived : public Base {

public:

void func() override {

std::cout << "Function in Derived" << std::endl;

}

};

class ClassA {

public:

void displayA() {在這個示例中, Derived 類同時繼承了 ClassA ClassB 。因此,它可以使用這兩個類中定義的方

法。

注意事項

菱形繼承問題:如果兩個基類繼承自同一個更高層的基類,這可能導致派生類中存在兩份基類的副

本,稱為菱形繼承(或鉆石繼承)問題。這可以通過虛繼承來解決。

復雜性:多重繼承可能會使類的結構變得復雜,尤其是當繼承層次較深或類中有多個基類時。

設計考慮:雖然多重繼承提供了很大的靈活性,但過度使用可能導致代碼難以理解和維護。在一些

情況下,使用組合或接口(純虛類)可能是更好的設計選擇。

多重繼承是C++的一個強大特性,但應謹慎使用。合理地應用多重繼承可以使代碼更加靈活和強大,但不

當的使用可能導致設計上的問題和維護困難。

2.10.6 虛繼承

虛繼承是C++中一種特殊的繼承方式,主要用來解決多重繼承中的菱形繼承問題。在菱形繼承結構中,一

個類繼承自兩個具有共同基類的類時,會導致共同基類的成員在派生類中存在兩份拷貝,這不僅會導致

資源浪費,還可能引起數據不一致的問題。虛繼承通過確保共同基類的單一實例存在于繼承層次中,來

解決這一問題。

菱形繼承問題示例

考慮以下的類結構:

std::cout << "Displaying ClassA" << std::endl;

}

};

class ClassB {

public:

void displayB() {

std::cout << "Displaying ClassB" << std::endl;

}

};

class Derived : public ClassA, public ClassB {

public:

void display() {

displayA(); // 調用 ClassA displayA

displayB(); // 調用 ClassB displayB

}

};

int main() {

Derived obj;

obj.displayA(); // 調用 ClassA displayA

obj.displayB(); // 調用 ClassB displayB

obj.display(); // 調用 Derived display

return 0;

}

class Base {

public:

int data;在這個例子中, FinalDerived 類通過 Derived1 Derived2 間接地繼承自 Base 類兩次。因此,

它包含了兩份 Base 的成員拷貝。

使用虛繼承解決菱形繼承問題

要解決這個問題,應使用虛繼承:

通過將 Derived1 Derived2 Base 的繼承聲明為虛繼承( virtual public Base ),

FinalDerived 類中只會有一份 Base 類的成員。無論通過 Derived1 還是 Derived2 的路徑,訪問

的都是同一個 Base 類的成員。

特點和注意事項

初始化虛基類:在使用虛繼承時,虛基類(如上例中的 Base 類)只能由最派生的類(如

FinalDerived )初始化。

內存布局:虛繼承可能會改變類的內存布局,通常會增加額外的開銷,比如虛基類指針。

設計考慮:虛繼承應謹慎使用,因為它增加了復雜性。在實際應用中,如果可以通過其他設計(如

組合或接口)避免菱形繼承,那通常是更好的選擇。

虛繼承是C++語言中處理復雜繼承關系的一種重要機制,但它也帶來了一定的復雜性和性能考慮。正確地

使用虛繼承可以幫助你建立清晰、有效的類層次結構。

C++ 繼承相關的學習內容整理成表格的形式:

};

class Derived1 : public Base {

// 繼承自 Base

};

class Derived2 : public Base {

// 繼承自 Base

};

class FinalDerived : public Derived1, public Derived2 {

// 繼承自 Derived1 Derived2

};

class Base {

public:

int data;

};

class Derived1 : virtual public Base {

// 虛繼承 Base

};

class Derived2 : virtual public Base {

// 虛繼承 Base

};

class FinalDerived : public Derived1, public Derived2 {

// 繼承自 Derived1 Derived2

};學習內容

描述

繼承的基礎

理解基類和派生類的概念,以及如何通過繼承擴展類功能。了解不同繼

承類型(公有、私有、保護)及其影響。

構造函數和析構函數在

繼承中的行為

學習派生類如何調用基類的構造函數和析構函數,以及它們的調用順

序。

訪問控制和繼承

理解公有、私有和保護繼承對成員訪問權限的影響。掌握繼承中的訪問

修飾符(public, protected, private)。

函數重寫和多態

學習多態和如何通過虛函數實現它,了解如何重寫基類方法,以及純虛

函數和抽象類的概念。

虛繼承和解決菱形問題

理解菱形繼承問題及其解決方式,學習如何使用虛繼承。

C++11 新特性中的繼承

相關內容

理解和應用 override final 關鍵字,了解移動語義在繼承中的應

用。

設計原則與最佳實踐

學習正確使用繼承的方法,區分何時使用繼承,何時使用組合,以及面

向對象設計原則的應用。

實際案例分析

通過分析和編寫實際代碼示例加深理解,研究設計模式中繼承的應用。

這個表格概述了學習?C++ 繼承的關鍵方面和內容,有助于系統地理解和應用繼承的概念。

2.11 多態

多態的基本概念(polymorphic)

想象一下,你有一個遙控器(這就像是一個基類的指針),這個遙控器可以控制不同的電子設備(這些

設備就像是派生類)。無論是電視、音響還是燈光,遙控器上的/按鈕(這個按鈕就像是一個虛函

數)都能控制它們,但具體的操作(打開電視、播放音樂、開燈)則取決于你指向的設備。

2.11.1 如何實現多態

1. 使用虛函數(Virtual Function

我們在基類中定義一個虛函數,這個函數可以在任何派生類中被重寫或者說定制

使用關鍵字 virtual 來聲明。

2. 創建派生類并重寫虛函數

在派生類中,我們提供該虛函數的具體實現。這就像是告訴遙控器,當你控制我的這個設備

時,這個按鈕應該這樣工作

3. 通過基類的引用或指針調用虛函數

當我們使用基類類型的指針或引用來調用虛函數時,實際調用的是對象的實際類型(派生類)

中的函數版本。

視頻課程中的手寫案例

#include <iostream>

using namespace std;class RemoteCon{

public:

virtual void openUtils(){

cout << "遙控器的開被按下" << endl;

}

};

class TvRemoteCon : public RemoteCon{

public:

void openUtils() override{

cout << "電視遙控器的開被按下" << endl;

}

void testFunc(){

}

};

class RoundspeakerCon : public RemoteCon{

public:

void openUtils() override{

cout << "音響遙控器的開被按下" << endl;

}

};

class LightCon : public RemoteCon{

public:

void openUtils() override{

cout << "燈光遙控器的開被按下" << endl;

}

};

void test(RemoteCon& r)//引用的方式

{

r.openUtils();

}

int main()

{

RemoteCon *remoteCon = new TvRemoteCon; //多態

remoteCon->openUtils();

RemoteCon *remoteCon2 = new TvRemoteCon; //多態

remoteCon2->openUtils();

RemoteCon *remoteCon3 = new LightCon; //多態

remoteCon3->openUtils();

TvRemoteCon tvRemote;

test(tvRemote);在這個例子中,不同的對象( TvRemoteCon TvRemoteCon )以它們自己的方式,盡管調用的是

相同的函數 openUtils 。這就是多態的魅力——相同的接口,不同的行為。

為什么使用多態

靈活性:允許我們編寫可以處理不確定類型的對象的代碼。

可擴展性:我們可以添加新的派生類而不必修改使用基類引用或指針的代碼。

接口與實現分離:我們可以設計一個穩定的接口,而將具體的實現留給派生類去處理。

2.11.2 抽象類

抽象類的基本概念

想象一下,你有一個交通工具的概念。這個概念告訴你所有交通工具都應該能做什么,比如移動

move),但它并不具體說明怎么移動。對于不同的交通工具,比如汽車和自行車,它們的移動方式是

不同的。在這個意義上,交通工具是一個抽象的概念,因為它本身并不能直接被使用。你需要一個具體

的交通工具,比如汽車自行車,它們根據交通工具的概念具體實現了移動的功能。

?C++ 中,抽象類就像是這樣的一個抽象概念。它定義了一組方法(比如移動),但這些方法可能沒有

具體的實現。這意味著,抽象類定義了派生類應該具有的功能,但不完全實現這些功能。

抽象類的特點

1. 包含至少一個純虛函數

抽象類至少有一個純虛函數。這是一種特殊的虛函數,在抽象類中沒有具體實現,而是留給派

生類去實現。

純虛函數的聲明方式是在函數聲明的末尾加上 = 0

2. 不能直接實例化

由于抽象類不完整,所以不能直接創建它的對象。就像你不能直接使用交通工具的概念去任

何地方,你需要一個具體的交通工具。

3. 用于提供基礎結構

抽象類的主要目的是為派生類提供一個共同的基礎結構,確保所有派生類都有一致的接口和行

為。

return 0;

}

#include <iostream>

using namespace std;

class Teacher{

public:

string name;

string shool;

string major;

virtual void goInClass() = 0;

virtual void startTeaching() = 0;2.11.3 純虛函數-接口

?C++ 中,雖然沒有像其他編程語言(比如?Java 中的接口Interface)一樣直接定義接口的關鍵字,但可

以通過抽象類和純虛函數的方式來實現接口的概念。

接口通常用于定義類應該實現的方法,但不提供具體實現。這樣的實現方式允許多個類共享相同的接

口,同時讓每個類根據需要去實現這些接口。

一個類作為接口可以通過以下步驟來實現:

virtual void afterTeaching() = 0;

};

class EnglishTeacher : public Teacher{

public:

void goInClass() override{

cout << "英語老師開始進入教室" << endl;

}

void startTeaching() override{

cout << "英語老師開始教學" << endl;

}

void afterTeaching() override{

};

};

class ProTeacher : public Teacher{

public:

void goInClass() override{

cout << "編程老師開始進入教室" << endl;

}

void startTeaching() override{

cout << "編程老師開始擼代碼了,拒絕讀PPT" << endl;

}

void afterTeaching() override{

cout << "編程老師下課后手把手教x學員寫代碼" << endl;

};

};

int main()

{

// Teacher t;//抽象類,不支持被實例化

EnglishTeacher e;

e.goInClass();

ProTeacher t;

t.startTeaching();

t.afterTeaching();

//抽象類,多態

Teacher *teacher = new ProTeacher;

teacher->startTeaching();

return 0;

}1. 定義抽象類:創建一個包含純虛函數的抽象類,這些函數構成了接口的一部分。這些函數在抽象類

中只有聲明而沒有具體的實現。

2. 派生類實現接口:派生類繼承抽象類,并實現其中的純虛函數,以具體實現接口定義的方法。

以下是一個簡單的示例來展示如何使用抽象類來模擬接口:

通過這種方式,您可以在?C++ 中模擬出類似接口的行為,允許多個類共享相同的接口并提供各自的實

現。

#include <iostream>

using namespace std;

class BasketBallMove{

public:

virtual void passTheBall() = 0;

};

class LiveMove{

public:

virtual void eat() = 0;

virtual void bite() = 0;

virtual void drink() = 0;

virtual void la() = 0;

};

class Human : public LiveMove,BasketBallMove{

public:

void eat() override{};

void bite() override{};

void drink() override{};

void la() override{};

void passTheBall() override{};

};

class Dog : public LiveMove{

public:

void eat() override{};

void bite() override{};

void drink() override{};

void la() override{};

};

int main()

{

Human h;

Dog g;

// LiveMove *l = new LiveMove;

return 0;

}2.12 友元

2.12.1 什么是友元

C++中,友元(friend)關鍵字用于給特定的外部函數或類訪問某類的私有(private)和保護

protected)成員的權限。友元關系不是相互的,也不是可繼承的。這意味著被聲明為友元的函數或類

可以訪問原始類的私有和保護成員,但原始類不能訪問友元的私有成員,除非它們也被聲明為友元。

友元主要有三種類型:

1. 友元函數:一個普通函數(不是類的成員函數)可以被聲明為一個類的友元函數。它可以訪問該類

的所有成員(公有、保護和私有)。

2. 友元類:一個類可以被聲明為另一個類的友元。這意味著友元類的所有成員函數都可以訪問原始類

的保護和私有成員。

3. 友元成員函數:一個類的成員函數可以被聲明為另一個類的友元。這意味著該成員函數可以訪問另

一個類的保護和私有成員。

為什么使用友元

友元提供了一種機制,允許某些外部函數或類直接訪問類的私有或保護成員。這在某些情況下非常有

用,例如:

實現運算符重載,如重載輸入輸出運算符( << >> )。

允許兩個不同類的對象之間進行密切的交互。

當類設計需要一種安全控制的方式來允許非成員函數或類訪問私有成員時。

2.12.2 友元函數

示例

下面是一個展示友元函數的例子:在這個例子中, showValue 函數被聲明為 MyClass 的友元函數,它

可以訪問 MyClass 的私有成員 value

class MyClass {

private:

int value;

public:

MyClass(int v) : value(v) {}

// 聲明友元函數

friend void showValue(MyClass& obj);

};

// 友元函數的定義

void showValue(MyClass& obj) {

std::cout << "Value of MyClass: " << obj.value << std::endl;

}

int main() {

MyClass obj(42);

showValue(obj); // 可以訪問 MyClass 的私有成員

return 0;

}友元函數實現運算符重載,如重載輸入輸出運算符( << >> )。

C++中,重載輸入輸出運算符( << >> )通常需要使用友元函數。這是因為輸入輸出運算符通常需

要訪問類的私有成員,同時又需要符合標準庫中流對象(如 std::ostream std::istream )的用

法。

下面是一個如何使用友元函數來重載 << >> 運算符的例子:

示例

假設我們有一個 Point 類,表示二維空間中的一個點,我們想要重載 << >> 運算符以輸出和輸入點

的坐標。

在這個例子中:

<< 運算符用于將 Point 對象的內容輸出到輸出流(如 std::cout )。

>> 運算符用于從輸入流(如 std::cin )中讀取值到 Point 對象。

這兩個運算符都被聲明為 Point 類的友元函數,以便它們可以訪問類的私有成員變量 x y

注意事項

雖然友元提供了強大的功能,但也應謹慎使用:

#include <iostream>

class Point {

private:

int x, y;

public:

Point() : x(0), y(0) {}

Point(int x, int y) : x(x), y(y) {}

// 重載 << 運算符(輸出)

friend std::ostream& operator<<(std::ostream& os, const Point& point) {

os << "(" << point.x << ", " << point.y << ")";

return os;

}

// 重載 >> 運算符(輸入)

friend std::istream& operator>>(std::istream& is, Point& point) {

is >> point.x >> point.y;

return is;

}

};

int main() {

Point p1(1, 2);

std::cout << "Point p1: " << p1 << std::endl; // 輸出點 p1

Point p2;

std::cout << "Enter coordinates for p2 (x y): ";

std::cin >> p2; // 輸入點 p2

std::cout << "Point p2: " << p2 << std::endl; // 輸出點 p2

return 0;

}過度使用友元可能會破壞封裝,使得代碼難以維護和理解。

保持友元的數量最小,僅在確實需要時使用,以維持良好的封裝性。

2.12.3 友元類

C++中,一個類可以被聲明為另一個類的友元類(friend class)。當一個類被聲明為另一個類的友元

類時,它可以訪問后者的所有成員,包括私有(private)和保護(protected)成員。

友元類是一種強大的特性,它允許在保持封裝性的同時,提供對類內部的深入訪問。然而,由于它允許

對另一個類的內部細節進行直接操作,因此應謹慎使用,以免破壞封裝性。

示例

假設我們有兩個類 ClassA ClassB 。我們可以使 ClassB 成為 ClassA 的友元類,這樣 ClassB

就可以訪問 ClassA 的所有成員,包括私有成員。

在這個示例中, ClassB 能夠訪問 ClassA 的私有成員 value ,因為它被聲明為 ClassA 的友元類。

注意事項

謹慎使用:友元類應該謹慎使用,因為它們可能會破壞對象的封裝性和隱藏性。

非相互性:如果 ClassA ClassB 的友元類,這并不意味著 ClassB 自動成為 ClassA 的友元

類。友元關系是單向的。

非繼承性:友元關系不會被繼承。如果類 C 繼承自類 B ,并且 B A 的友元類,那么 C 不自動

成為 A 的友元類,除非 A 明確聲明 C 為友元類。

友元類提供了一種機制,允許其他類訪問私有成員,但應在不破壞封裝性的前提下謹慎使用。

class ClassA {

private:

int value;

public:

ClassA(int v) : value(v) {}

// 聲明 ClassB 為友元類

friend class ClassB;

};

class ClassB {

public:

void showValue(ClassA& a) {

// 可以訪問 ClassA 的私有成員

std::cout << "Value of ClassA: " << a.value << std::endl;

}

};

int main() {

ClassA a(100);

ClassB b;

b.showValue(a); // 輸出 "Value of ClassA: 100"

return 0;

}2.12.4 友元成員函數

C++中,除了可以將整個類或單獨的函數聲明為友元外,還可以將特定類中的某個成員函數單獨聲明為

另一個類的友元。這樣做可以讓這個成員函數訪問另一個類的所有成員(包括私有和受保護的成員),

而無需將整個類聲明為友元,從而提供了更細粒度的訪問控制。

示例

假設我們有兩個類 ClassA ClassB ,我們希望 ClassB 的一個特定成員函數 showValue 能夠訪問

ClassA 的私有成員。我們可以將 ClassB showValue 函數聲明為 ClassA 的友元。

在這個示例中, ClassB::showValue 成為 ClassA 的友元函數。這意味著 showValue 可以訪問

ClassA 的私有成員 value 。注意,我們在 ClassA 前提供了 ClassB 的前向聲明。這是必須的,因為

在聲明 ClassA ClassB 還未完全定義。

注意事項

精確控制:與將整個類聲明為友元相比,將特定成員函數聲明為友元可以更精確地控制訪問權限。

破壞封裝:即使是單個成員函數,過度使用友元也可能破壞類的封裝性。應當謹慎使用。

前向聲明:在一個類中聲明另一個類的成員函數為友元時,可能需要前向聲明另一個類,特別是在

兩個類相互引用對方的成員函數時。

友元成員函數是C++中一種強大的特性,允許開發者在保持類封裝性的同時提供必要的訪問權限。然而,

應當謹慎使用,以保持代碼的清晰性和維護性。

#include <iostream>

class ClassB; // 前向聲明

class ClassA {

private:

int value;

public:

ClassA(int v) : value(v) {}

// 聲明 ClassB 的成員函數為友元

friend void ClassB::showValue(ClassA& a);

};

class ClassB {

public:

void showValue(ClassA& a) {

// 訪問 ClassA 的私有成員

std::cout << "Value of ClassA: " << a.value << std::endl;

}

};

int main() {

ClassA a(100);

ClassB b;

b.showValue(a); // 輸出 "Value of ClassA: 100"

return 0;

}2.13 模板

2.13.1 類模板

?C++ 中,模板(Template)是一種通用的編程工具,允許程序員編寫泛型代碼,使得類或函數能夠適

用于多種不同的數據類型而不需要重復編寫相似的代碼。C++ 提供了兩種主要類型的模板:類模板和函

數模板。

類模板(Class Templates):

類模板允許定義通用的類,其中某些類型可以作為參數。這樣的類可以處理不同類型的數據,而不需要

為每個數據類型編寫單獨的類。

2.13.2 函數模板

函數模板允許編寫通用的函數,可以處理多種不同類型的數據。

// 定義一個通用的類模板

template <typename T>

class MyTemplate {

private:

T data;

public:

MyTemplate(T d) : data(d) {}

T getData() {

return data;

}

};

int main() {

// 使用類模板創建對象

MyTemplate<int> intObject(5);

MyTemplate<std::string> stringObject("Hello");

// 調用類模板中的函數

std::cout << intObject.getData() << std::endl; // 輸出 5

std::cout << stringObject.getData() << std::endl; // 輸出 Hello

return 0;

}

// 定義一個通用的函數模板

template <typename T>

T add(T a, T b) {

return a + b;

}

int main() {

// 使用函數模板調用通用函數

int result1 = add(5, 10);

double result2 = add(3.5, 2.7);模板提供了一種在編寫代碼時更具通用性的方法,能夠處理不同類型的數據而無需為每種類型編寫特定

的函數或類。通過使用模板,可以提高代碼的重用性和可維護性。

2.13.3 模板特化

模板特化(Template Specialization)是?C++ 中模板的一個概念,它允許針對特定的數據類型或特定的

模板參數提供定制化的實現。模板特化允許您為模板提供一個特殊的實現,以覆蓋或擴展默認的模板行

為。

有兩種類型的模板特化:完全特化(Full Specialization)和部分特化(Partial Specialization)。

完全特化(Full Specialization):

完全特化是對模板中的所有模板參數都進行特化的情況。在完全特化中,模板參數被指定為特定的類

型,為特定的類型提供獨特的實現。

以下是一個示例,演示了對模板函數的完全特化:

在這個示例中, maximum 是一個模板函數,可以比較不同類型的數據,并返回較大的值。然后,為了特

化針對 const char* 類型,我們提供了一個特化版本的 maximum 函數,該函數使用 strcmp 函數來

比較字符串,并返回較大的字符串。

部分特化(Partial Specialization):

// 輸出函數模板調用結果

std::cout << "Result 1: " << result1 << std::endl; // 輸出 Result 1: 15

std::cout << "Result 2: " << result2 << std::endl; // 輸出 Result 2: 6.2

return 0;

}

#include <iostream>

// 定義一個通用的模板函數

template <typename T>

T maximum(T a, T b) {

return (a > b) ? a : b;

}

// 對模板函數進行完全特化,針對 char* 類型

template <>

const char* maximum<const char*>(const char* a, const char* b) {

return strcmp(a, b) > 0 ? a : b;

}

int main() {

int intMax = maximum(3, 5); // 調用模板函數

std::cout << "Maximum of integers: " << intMax << std::endl;

const char* charMax = maximum("apple", "orange"); // 調用特化的函數

std::cout << "Maximum of strings: " << charMax << std::endl;

return 0;

}容器類型

描述

特點

vector

動態數組,支持快速隨

機訪問和尾部插入/刪除

支持動態大小調整,適用于需要隨機訪問

和動態增刪元素的場景

list

雙向鏈表,支持快速插

/刪除操作

插入/刪除元素快速,但不支持隨機訪問元

部分特化是指對模板中的部分參數進行特化,允許更具體地特化某些模板參數。這通常在模板類中使

用。

以下是一個示例,展示了對模板類的部分特化:

在這個示例中, MyTemplate 是一個模板類,然后我們對 MyTemplate 進行部分特化,當第二個模板參

數是 int 類型時,提供了特殊的實現。因此,對于特定的類型組合,我們可以提供自定義的實現。

2.14 標準模板庫STL

2.14.1 容器

當談到?C++ 的標準模板庫(STL)容器時,通常有一些常見的容器類型。以下是一些常見的?STL 容器及

其特點的簡要總結:

#include <iostream>

// 定義一個通用的模板類

template <typename T, typename U>

class MyTemplate {

public:

void display() {

std::cout << "Generic Display" << std::endl;

}

};

// 對模板類進行部分特化,針對特定的類型組合

template <typename T>

class MyTemplate<T, int> {

public:

void display() {

std::cout << "Specialized Display for T and int" << std::endl;

}

};

int main() {

MyTemplate<float, double> obj1;

obj1.display(); // 輸出 "Generic Display"

MyTemplate<int, int> obj2;

obj2.display(); // 輸出 "Specialized Display for T and int"

return 0;

}容器類型

描述

特點

deque

雙端隊列,支持在兩端

快速插入/刪除操作

支持在頭尾快速插入/刪除元素,不同于

vector 在頭部操作時效率較低,但隨機訪

問效率比?list

stack

后進先出(LIFO)的堆

棧數據結構

基于?deque ?vector 實現,只允許在棧

頂進行插入和刪除操作

queue

先進先出(FIFO)的隊

列數據結構

基于?deque ?list 實現,只允許在隊尾進

行插入,在隊頭進行刪除操作

priority_queue

優先隊列,按照一定順

序維護元素

基于?vector 實現,默認情況下是最大堆,

可以通過自定義比較函數來實現不同的優

先級順序

set

有序不重復元素集合,

基于紅黑樹實現

自動排序,插入/查找元素的平均時間復雜

度為?O(log n),不允許重復元素

multiset

有序可重復元素集合,

基于紅黑樹實現

允許存儲重復元素,按序存儲,插入/查找

元素的平均時間復雜度為?O(log n)

map

鍵值對集合,基于紅黑

樹實現

存儲鍵值對,按鍵自動排序,不允許重復

鍵,插入/查找元素的平均時間復雜度為

O(log n)

multimap

鍵值對集合,允許重復

鍵,基于紅黑樹實現

允許存儲重復鍵值對,按鍵自動排序,插

/查找元素的平均時間復雜度為?O(log n)

unordered_set

無序不重復元素集合,

基于哈希表實現

不按順序存儲元素,插入/查找/刪除元素的

平均時間復雜度為?O(1),不允許重復元素

unordered_multiset

無序可重復元素集合,

基于哈希表實現

允許存儲重復元素,不按順序存儲,插入/

查找/刪除元素的平均時間復雜度為?O(1)

unordered_map

無序鍵值對集合,基于

哈希表實現

鍵值對無序存儲,插入/查找/刪除元素的平

均時間復雜度為?O(1),不允許重復鍵

unordered_multimap

無序鍵值對集合,允許

重復鍵,基于哈希表實

允許存儲重復鍵值對,鍵值對無序存儲,

插入/查找/刪除元素的平均時間復雜度為

O(1)

2.14.2 vector

C++的標準模板庫(STL)中, std::vector 是一種動態數組容器。它提供了動態大小的數組功能,

能夠在運行時根據需要自動調整大小,允許在其尾部高效地進行元素的插入和刪除操作。

1. 動態大小: std::vector 允許動態增加或減少其大小。它會自動處理內存分配和釋放,無需手動

管理內存。

2. 隨機訪問: 支持使用索引進行快速的隨機訪問,因為它底層基于連續的內存塊。

3. 尾部插入/刪除: 在數組的尾部插入或刪除元素的操作非常高效,時間復雜度為常數時間

Amortized Constant Time)。

4. 連續內存存儲: std::vector 中的元素在內存中是連續存儲的,這有助于提高訪問速度和緩存利

用率。5. 動態增長策略: 當向 std::vector 添加元素時,如果當前容量不足,它會動態地重新分配更大的

內存空間,并將現有元素復制到新的內存位置。這種動態增長策略確保了插入操作的高效性。

std::vector 的缺點是,在執行插入或刪除操作時,如果不是在容器的末尾進行,可能會導致較高的時

間復雜度,因為需要移動后續元素。

以下是一個使用?C++ STL 中的 vector 容器的簡單示例:

#include <iostream>

#include <vector>

int main() {

// 創建一個空的 vector 容器

std::vector<int> myVector;

// vector 容器尾部添加元素

myVector.push_back(3);

myVector.push_back(7);

myVector.push_back(12);

// 使用迭代器遍歷 vector 容器并輸出其中的元素

// C++ 中,auto 是一個關鍵字,用于聲明變量時的類型推斷。

// 它允許編譯器根據變量的初始化表達式推斷出變量的類型,從而簡化代碼書寫過程。

std::cout << "Vector elements: ";

for (auto it = myVector.begin(); it != myVector.end(); ++it) {

std::cout << *it << " ";

}

std::cout << std::endl;

// 獲取 vector 容器的大小和訪問特定位置的元素

std::cout << "Vector size: " << myVector.size() << std::endl;

std::cout << "Element at index 1: " << myVector[1] << std::endl;

// 修改特定位置的元素

myVector[2] = 20;

// 使用范圍-based for 循環遍歷 vector 并輸出元素

std::cout << "Modified Vector elements: ";

for (int num : myVector) {

std::cout << num << " ";

}

std::cout << std::endl;

// 清空 vector 容器

myVector.clear();

// 檢查 vector 是否為空

if (myVector.empty()) {

std::cout << "Vector is empty." << std::endl;

} else {

std::cout << "Vector is not empty." << std::endl;

}

return 0;

}API

描述

函數原型

參數說明

push_back()

vector

尾部添加一

個元素

void push_back(const T&

value);

value :要添加到尾部

的元素

pop_back()

刪除

vector

部的一個元

void pop_back();

無參數

size()

返回

vector

元素的數量

size_type size() const

noexcept;

無參數

capacity()

返回

vector

前可容納的

元素數量

size_type capacity()

const noexcept;

無參數

resize()

改變

vector

大小,可以

增加或減少

元素數量

void resize(size_type

count);

void resize(size_type

count, const T& value);

count :新的 vector

大小

value :若添加元素,

初始化值為 value

reserve()

修改

vector

容量,預留

足夠的存儲

空間

void reserve(size_type

new_cap);

new_cap :新的

vector 容量

clear()

清空

vector

的所有元素

void clear() noexcept;

無參數

empty()

檢查

vector

否為空

bool empty() const

noexcept;

無參數

at()

返回指定位

置的元素,

并進行邊界

檢查

reference at(size_type

pos);

const_reference

at(size_type pos) const;

pos :要訪問的位置。

如果超出范圍,會引發

std::out_of_range

異常

這個例子展示了如何創建 vector 容器、向其尾部添加元素、使用迭代器和索引訪問元素、修改元素

值、清空容器以及檢查容器是否為空。 vector 是一個動態數組,可以根據需要動態地增加或減少其大

小,適合需要隨機訪問元素且頻繁進行尾部插入/刪除操作的場景。

當涉及到 std::vector ?API 方法時,參數列表、返回值以及參數的說明可以幫助更清楚地了解每個

方法的使用和含義。以下是 std::vector 常用?API 列表,包括參數列表、返回值和參數說明:API

描述

函數原型

參數說明

operator[]

重載操作

符,用于訪

問指定位置

的元素

reference operator[]

(size_type pos);

const_reference

operator[](size_type

pos) const;

pos :要訪問的位置。

不進行邊界檢查,如果

超出范圍,行為未定義

front()

返回

vector

第一個元素

的引用

reference front();

const_reference front()

const;

無參數

back()

返回

vector

最后一個元

素的引用

reference back();

const_reference back()

const;

無參數

begin()

返回指向

vector

一個元素的

迭代器

iterator begin()

noexcept;

const_iterator begin()

const noexcept;

無參數

end()

返回指向

vector

尾(最后一

個元素的后

面)的迭代

iterator end()

noexcept;

const_iterator end()

const noexcept;

無參數

rbegin()

返回指向

vector

后一個元素

的逆向迭代

器(逆向開

始迭代)

reverse_iterator

rbegin() noexcept;

const_reverse_iterator

rbegin() const noexcept;

無參數

rend()

返回指向

vector

一個元素之

前的逆向迭

代器(逆向

結束迭代)

reverse_iterator rend()

noexcept;

const_reverse_iterator

rend() const noexcept;

無參數

insert()

在指定位置

插入一個或

多個元素

iterator

insert(const_iterator

pos, const T& value);

void

insert(const_iterator

pos, size_type count,

const T& value);

pos :插入位置

value :要插入的元素

count :要插入的元素

個數API

描述

函數原型

參數說明

erase()

刪除指定位

置或指定范

圍內的一個

或多個元素

iterator

erase(const_iterator

pos);

iterator

erase(const_iterator

first, const_iterator

last);

pos :要刪除的元素位

置或范圍的起始位置

first last :要刪除

的范圍

swap()

交換兩個

vector

器的內容

void swap(vector&

other);

other :要交換內容的

另一個 vector 容器

emplace()

在指定位置

就地構造一

個元素

iterator

emplace(const_iterator

pos, Args&&... args);

pos :就地構造的位置

args :構造元素所需的

參數

emplace_back()

vector

尾部就地構

造一個元素

void

emplace_back(Args&&...

args);

args :構造元素所需的

參數

這些詳細說明包括每個函數的參數、返回值以及對參數的解釋,有助于更清晰地理解 std::vector

常用?API 的使用方式和含義。

2.14.3 list

STL 中的 list 是雙向鏈表(doubly linked list)的實現,它是?C++ 標準模板庫中的一個容器,提供了

一種能夠高效進行插入、刪除操作的數據結構。與 vector 不同, list 不支持隨機訪問,但它允許在

任意位置快速插入和刪除元素。

以下是關于 std::list 的一些特點和說明:

雙向鏈表結構: std::list 使用雙向鏈表來組織其元素,每個節點都包含指向前一個節點和后一

個節點的指針,因此在任意位置進行插入和刪除操作的開銷較小。

不支持隨機訪問: vector 不同, list 不支持通過索引直接訪問元素,因為它不具備隨機訪問

能力。要訪問 list 中的元素,需要使用迭代器進行順序遍歷。

動態大小調整: list 具有動態大小調整的特性,可以動態增加或減少元素的數量。對于大量的插

入和刪除操作, list 往往比 vector 更高效。

迭代器操作: 使用迭代器可以對 list 中的元素進行訪問、插入和刪除。 list 提供了

begin() end() rbegin() rend() 等迭代器相關方法,支持正向和逆向迭代。

插入和刪除操作效率高: list 中,在任意位置進行插入和刪除操作的時間復雜度是?O(1),因為

只需要調整相鄰節點的指針,無需移動大量元素。

空間開銷: 相比于 vector list 需要額外的空間來存儲指向前一個和后一個節點的指針,可能

會導致更高的存儲開銷。

下面是一個簡單的示例,演示了如何使用?STL 中的 std::list 容器。在這個案例中,我們創建了一個

std::list 來存儲整數,并展示了一些基本的操作,如插入、刪除、迭代等。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/83132.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/83132.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/83132.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【前端】【css】【總復習】三萬字詳解CSS 知識體系

&#x1f308; CSS 知識體系目錄大綱 一、基礎知識入門 1. CSS 簡介與作用 CSS&#xff08;Cascading Style Sheets&#xff0c;層疊樣式表&#xff09;是一種用于給 HTML 頁面添加樣式的語言&#xff0c;作用是讓網頁更美觀、結構更清晰、布局更靈活。 核心作用&#xff1a;…

R利用spaa包計算植物/微生物的生態位寬度和重疊指數

一、生態位寬度 生態位寬度指數包括shannon生態位指數和levins生態位指數。下面是采用levins方法計算生態位寬度。method也可以選擇“shannon”。 二、生態位重疊指數 生態位重疊指數&#xff0c;包括levins生態位重疊指數、schoener生態位重疊指數、petrai…

【論信息系統項目的合同管理】

論信息系統項目的合同管理 論文要求寫作要點正文前言一、合同的簽訂管理二、合同履行管理三、合同變更管理四、合同檔案管理五、合同違約索賠管理結語 論文要求 項目合同管理通過對項目合同的全生命周期進行管理&#xff0c;來回避和減輕可識別的項目風險。 請以“論信息系統項…

最新網盤資源搜索系統,電視直播,Alist聚合播放

源碼描述&#xff1a; 本項目是基于Vue與Nuxt.js技術構建的網盤搜索項目&#xff0c;持續開源并維護更新。該項目旨在使每個人都能擁有屬于自己的網盤搜索網站。我們強烈建議用戶自行部署該項目。 更新日志&#xff1a; 新增TV播放功能新增Alist源聚合播放功能新增批量刪除功…

【Ubuntu】安裝BitComet種子下載器

環境 Ubuntu 24.04.2 下載依賴庫 環境比較新&#xff0c;此軟件需要依賴很多舊的庫&#xff0c;逐個安裝下載&#xff1a; 1.libicu70 http://nz.archive.ubuntu.com/ubuntu/pool/main/i/icu/libicu70_70.1-2_amd64.deb2.libjavascriptcoregtk-4.0-18 http://security.ubu…

修復“ImportError: DLL load failed while importing lib: 找不到指定的程序”筆記

#工作記錄 一、問題描述 在運行CosyVoice_For_Windows項目時&#xff0c;出現以下報錯&#xff1a; Traceback (most recent call last): File "D:\ProgramData\anaconda3\envs\CosyVoice\Lib\pydoc.py", line 457, in safeimport module __import__(path) …

ubuntu18 設置靜態ip

百度 編輯/etc/netplan/01-netcfg.yaml 系統沒有就自己編寫 network: version: 2 renderer: networkd ethernets: eth0: dhcp4: no addresses: [192.168.20.8/24] # 設置你的IP地址和子網掩碼 gateway4: 192.168.20.1 # 網關地址 namese…

幀差法識別

定義&#xff1a; 視頻通過閃過x幀畫面來實現&#xff0c;幀差法就是利用兩幀之間的差異找出。也就是移動目標識別 幀差法識別步驟&#xff1a; 1、灰度處理&#xff1a;將多通道變成雙通道壓縮圖像數據。 cvtColor(before_frame,before_gray,CV_RGB2GRAY);cvtColor(after_f…

基于OAuth2+SpringSecurity+Jwt實現身份認證和權限管理后端服務

1、簡介 本文講述了如何實現簡易的后端鑒權服務。所謂“鑒權”&#xff0c;就是“身份鑒定”“權限判斷”。涉及的技術有&#xff1a;OAuth2、SpringSecurity、Jwt、過濾器、攔截器。OAuth2用于授權&#xff0c;使用Jwt簽發Access Token和Refresh Token&#xff0c;并管理token…

<C++> MFC自動關閉對話框(MessageBoxTimeout)

MFC自動關閉對話框&#xff08;MessageBoxTimeout&#xff09; 記錄一下今天在界面開發中的解決方案。自動關閉對話框有兩種方案&#xff1a; 1.使用定時器實現延遲關閉&#xff08;DeepSeek方案&#xff09; 提示框顯示幾秒后自動關閉&#xff0c;可以使用 SetTimer KillT…

多語言支持的常見設計方案

在 Java 項目中實現**多語言&#xff08;國際化&#xff0c;i18n&#xff09;**功能&#xff0c;是很多企業級應用支持不同地區和語言用戶的基礎需求。以下是 Java 中實現多語言支持的常見設計方案&#xff1a; 一、常見多語言設計方案 1. 使用 ResourceBundle 讀取 propertie…

vuex基本介紹

Vuex是Vue.js應用程序中專門用于狀態管理的庫。以下是其基本介紹&#xff1a; 概念 Vuex采用集中式存儲管理應用的所有組件的狀態&#xff0c;并以相應的規則保證狀態以一種可預測的方式發生變化。 特點 - 集中化管理&#xff1a;將應用的狀態集中存儲在一個單一的狀態…

Android開發-在應用之間共享數據

在Android系統中&#xff0c;應用之間的隔離機制&#xff08;沙箱機制&#xff09;保障了系統的安全性與穩定性。然而&#xff0c;在實際開發中&#xff0c;我們經常需要實現跨應用的數據共享&#xff0c;例如&#xff1a; 從一個應用向另一個應用傳遞用戶信息&#xff1b;多個…

深度解析 JWT:從原理到實戰的全場景解決方案(附永久 Token 設計與集成系統實踐)

摘要 本文結合 JWT 官方標準&#xff08;RFC 7519&#xff09;與生產級實踐&#xff0c;全面解析 JSON Web Token 的核心機制、安全規范及 Java 生態最佳實現。涵蓋 JJWT 工具類優化、Auth0/Nimbus 替代方案對比、永久 Token 設計&#xff08;滿足集成系統長期調用需求&#x…

[特殊字符]Meilisearch:AI驅動的現代搜索引擎

前言 大家好&#xff0c;我是MAI麥造&#xff01; 上文介紹一了Manticore Search 這款輕量級的搜索引擎&#xff0c;這次又有了新的發現&#xff01;傳送門&#xff1a; Elasticsearch太重&#xff1f;它的超輕量的替代品找到了&#xff01; 這是一個讓我超級興奮的AI搜索引…

【Linux C/C++開發】輕量級關系型數據庫SQLite開發(包含性能測試代碼)

前言 之前的文件分享過基于內存的STL緩存、環形緩沖區&#xff0c;以及基于文件的隊列緩存mqueue、hash存儲、向量庫annoy存儲&#xff0c;這兩種屬于比較原始且高效的方式。 那么&#xff0c;有沒有高級且高效的方式呢。有的&#xff0c;從數據角度上看&#xff0c;&#xff0…

首個專業AI設計Agent發布-Lovart

Lovart是什么 Lovart 是為設計師打造的世界上首個專業設計 Agent。Lovart 能像專業設計師一樣思考和執行設計任務&#xff0c;提供高水平的設計方案。基于自然語言交互&#xff0c;用戶能快速調整布局、顏色和構圖。Lovart 支持從創意拆解到專業交付的全鏈路設計&#xff0c;單…

關于Python 實現接口安全防護:限流、熔斷降級與認證授權的深度實踐

作為一名IT從業者&#xff0c;就自己的職業經歷&#xff0c;我一直很注重系統安全的。從桌面時代就對此很感興趣&#xff0c;后來隨著技術的更新迭代&#xff0c;系統安全衍生出來了網絡安全。維度更大&#xff0c;范圍更廣。尤其在數字化浪潮席卷全球的今天&#xff0c;互聯網…

onGAU:簡化的生成式 AI UI界面,一個非常簡單的 AI 圖像生成器 UI 界面,使用 Dear PyGui 和 Diffusers 構建。

?一、軟件介紹 文末提供程序和源碼下載 onGAU&#xff1a;簡化的生成式 AI UI界面開源程序&#xff0c;一個非常簡單的 AI 圖像生成器 UI 界面&#xff0c;使用 Dear PyGui 和 Diffusers 構建。 二、Installation 安裝 文末下載后解壓縮 Run install.py with python to setup…

南方科技大學Science! 自由基不對稱催化新突破 | 樂研試劑

近日&#xff0c;南方科技大學劉心元教授團隊聯合浙江大學洪鑫教授團隊在自由基不對稱催化領域取得新進展。課題組開發了一系列大位阻陰離子 N,N,P-配體&#xff0c;用于銅催化未活化外消旋仲烷基碘與亞砜亞胺的不對稱胺化反應。該反應表現出廣泛的底物兼容性&#xff0c;涵蓋具…