Modern C++(七)類

7、類

7.1、類聲明

前置聲明:聲明一個將稍后在此作用域定義的類類型。直到定義出現前,此類名具有不完整類型。當代碼僅僅需要用到類的指針或引用時,就可以采用前置聲明,無需包含完整的類定義。

前置聲明有以下幾個作用:

  • 降低編譯時的依賴性,加快編譯速度
  • 避免出現循環引用
// 前置聲明
class MyClass;void func(MyClass* obj); // 可以使用指針
void func2(const MyClass& obj); // 可以使用引用
MyClass* createObj(); // 返回指針或引用是可行的

使用前置聲明也會有一些限制,除了不能訪問類成員、不能創建類對象、不能使用類的大小信息外,還不能繼承自該類。

7.2、局部類

局部類(Local Class)是指定義在函數內部的類,其作用域僅限于該函數。它們無法被函數外部訪問。局部類可以訪問函數的靜態變量、外部全局變量和枚舉。局部類的非靜態成員函數不能訪問外部函數的非靜態局部變量。

int globalVar = 100;void func() {static int staticVar = 200;int localVar = 300; // 局部變量(非靜態)class Local {public:void print() {std::cout << globalVar << std::endl; // 合法:訪問全局變量std::cout << staticVar << std::endl; // 合法:訪問靜態變量// std::cout << localVar << std::endl; // 錯誤:無法訪問非靜態局部變量}};Local().print();
}

局部類一般用于封裝臨時性算法,實現函數內部的策略模式。

7.3、聯合體聲明

聯合體(Union)是C++中一種特殊的類類型,它允許在相同的內存位置存儲不同類型的數據。與結構體(struct)和類(class)相比,聯合體的主要特點是所有成員共享同一塊內存,因此在同一時刻只能存儲一個成員的值。

聯合體有以下特性:

  • 默認成員訪問是public。
  • 修改一個成員會覆蓋其他成員的值。
  • 聯合體的對齊方式通常由其最大成員的對齊要求決定
  • 聯合體的大小至少要能容納其最大的數據成員,并且可能因對齊需求而增加額外空間。
union Example {char c;       // 1字節int i;        // 4字節(假設int為4字節)double d;     // 8字節(假設double為8字節)
}; // 8字節

匿名聯合體是沒有名稱的聯合體,其成員直接成為包含它的作用域的成員:

struct Employee {enum class Type { MANAGER, ENGINEER };Type type;union {char* department;  // 當type為MANAGER時使用int engineerId;    // 當type為ENGINEER時使用}; // 匿名聯合體// 訪問方式:直接通過Employee對象訪問department或engineerId
};// 使用示例
Employee e;
e.type = Employee::Type::MANAGER;
e.department = "HR";

7.4、this指針

this指針在特殊場景中的行為:

  • 在析構函數中使用this:析構函數執行時,對象的成員變量仍在內存中,但已進入銷毀流程,析構函數執行完畢后,對象占用的內存會被回收。析構函數中不要訪問已釋放的成員變量,僅執行必要的資源釋放操作,避免復雜邏輯,應避免調用其他成員函數。
class Resource {
public:~Resource() {// 合法:析構函數執行時對象尚未完全銷毀std::cout << "Destroying resource at " << this << std::endl;// 但不能調用非析構的成員函數,可能訪問已釋放內存}
};
  • delete this的危險用法
class Dangerous {
public:void selfDestruct() {std::cout << "Before delete: " << this << std::endl;delete this; // 調用后當前對象被銷毀// this->data = 42;  // 災難!訪問已釋放的內存// std::cout << data; // 同樣危險// 應該立即返回}
private:int data;
};Dangerous* obj = new Dangerous;
obj->selfDestruct();
// 從這里開始,obj已經成為懸空指針
// 任何對obj的使用都是未定義行為

這種模式有時用于引用計數對象的自我銷毀

  • 多態場景下的this指針,在虛函數中,this指針始終指向實際對象(而非靜態類型)
class Base {
public:virtual void printAddress() {std::cout << "Base: " << this << std::endl;}
};class Derived : public Base {
public:void printAddress() override {Base::printAddress(); // 輸出基類視角的this(與派生類相同)std::cout << "Derived: " << this << std::endl;}
};// 輸出:Base和Derived的this指針地址相同,指向同一對象

7.5、static成員

在類定義中,關鍵詞static聲明不綁定到類實例的成員

static成員變量是具有靜態存儲期的獨立變量

class X { static int n; }; // 聲明(用 'static')
int X::n = 1;              // 定義(不用 'static')

static成員函數不依賴于對象實例(沒有this指針),只能訪問static成員變量,可以直接用類名調用。

7.6、嵌套類

嵌套類(Nested Class)是指在一個類(稱為外圍類/外部類)內部定義的另一個類。嵌套類的作用域受外圍類限制,但可以訪問外圍類的成員(包括私有成員),嵌套類被視為外部類的 “朋友”。。

嵌套類可以訪問外部類的所有成員(包括私有、受保護和公共成員)以及成員函數,不過訪問非靜態成員時,必須借助外部類的實例來實現。對于外部類的靜態成員,嵌套類可以直接訪問,無需外部類的實例。

外部類無法直接訪問嵌套類的私有和受保護成員,如需訪問要將將外部類聲明為友元類。

外部類和嵌套類之間成員的相互使用無需理會聲明順序:

class enclose
{
public:void printNest() {nested1 ne;ne.num = 10;}private:class nested1 {public:void print() {enclose en;en.num = 10;}private:friend class enclose;int num;};int num;
};

嵌套類可以前置聲明并在之后定義,在外圍類的體內或體外均可:

class enclose
{class nested1;    // 前置聲明class nested2;    // 前置聲明class nested1 {}; // 嵌套類的定義
};class enclose::nested2 { }; // 嵌套類的定義

嵌套類不影響外圍類的大小。

7.7、派生類

在基類子句中列出的類是直接基類,直接基類的基類被稱為間接基類。同一個類不能多次被指定為直接基類,但是可以既是直接基類又是間接基類。

基類子對象的構造函數被派生類的構造函數所調用:可以在成員初始化器列表中向這些構造函數提供實參。

繼承時,如果省略訪問說明符,那么它對以類關鍵詞struct聲明的類默認為public,對以類關鍵詞class聲明的類為private。

虛基類:虛繼承是為了解決菱形繼承問題而引入的。當使用虛繼承時,無論通過多少條路徑繼承同一個虛基類,最終派生對象中只會包含該虛基類的一個實例。

struct B { int n; };
class X : public virtual B {};
class Y : virtual public B {};
class Z : public B {};// 每個 AA 類型對象擁有一個 X,一個 Y,一個 Z 和兩個 B:
// 一個是 Z 的基類,另一個由 X 與 Y 所共享
struct AA : X, Y, Z
{void f(){X::n = 1; // 修改虛 B 子對象的成員Y::n = 2; // 修改同一虛 B 子對象的成員Z::n = 3; // 修改非虛 B 子對象的成員std::cout << X::n << Y::n << Z::n << '\n'; // 打印 223}
};

上述例子雖然能運行,但是n的定義是不明確的!!

私有繼承時,子類不對外表現出is-a的關系,但是可以在子類內部使用is-a,在類外部可以通過指針強轉進行虛函數調用。

7.8、using 聲明

在命名空間和塊作用域中:using聲明將另一命名空間的成員引入到當前命名空間或塊作用域中。

在類定義中:using聲明可以將別處定義的名字引入到此using聲明所在的聲明區中,例如將基類的受保護成員暴露為派生類的公開成員。這有幾個優點:

7.8.1、實現接口擴展與兼容性

當你設計一個類的繼承體系時,可能基類出于封裝和安全性的考慮,將某些成員設置為受保護的。但在派生類的使用場景中,這些成員可能需要被外部更方便地訪問,以滿足特定的接口需求。通過 using 聲明將基類的受保護成員提升為派生類的公開成員,可以在不破壞基類原有封裝的前提下,為派生類提供更廣泛的接口。

// 基類
class Base {
protected:void protectedFunction() {std::cout << "Base::protectedFunction() called" << std::endl;}
};// 派生類
class Derived : public Base {
public:using Base::protectedFunction;  // 將基類的受保護成員暴露為公開成員
};int main() {Derived d;d.protectedFunction();  // 可以直接調用,無需通過其他接口return 0;
}

引入有作用域枚舉項:除了另一命名空間的成員和基類的成員,using聲明也能將枚舉的枚舉項引入命名空間、塊和類作用域。

7.8.2、繼承構造函數

當在派生類中寫下using Base::Base; 時,編譯器會:

  • 隱式生成派生類的構造函數:這些構造函數與基類的構造函數具有相同的參數列表。
  • 將基類構造函數納入重載決議:當創建派生類對象時,編譯器會考慮基類的構造函數。
class Base {
public:Base(int a) { cout << "Base int " << endl; }Base(int a, double b) { cout << "Base int double " << endl; }Base(int a, double b, char c) { cout << "Base int double char " << endl; }
};class Derived : public Base {
public:Derived(int a, double b) : Base(a, b){cout << "Derived int double " << endl;}
private:using Base::Base;  // 讓Base的構造函數在Derived中可見
};int main() {Derived a(1);Derived b(1, 1.1);Derived c(1, 2, 'c');return 0;
}

當創建派生類對象時,編譯器會:

  • 優先考慮派生類自身定義的構造函數。
  • 如果沒有匹配的構造函數,則考慮通過 using Base::Base; 繼承的基類構造函數。
  • 繼承的構造函數只會初始化基類部分,派生類的新增成員需通過默認初始化(如果有默認構造函數)或保持未初始化狀態。

訪問控制規則:

  • 基類構造函數的訪問權限不變:如果基類的某個構造函數是 protected,繼承后在派生類中仍然是 protected。
  • 派生類無法繼承基類的私有構造函數:因為私有成員對派生類不可見。

構造函數繼承的限制:

  • 無法初始化派生類成員
  • 無法繼承默認 / 拷貝 / 移動構造函數
  • 沖突的構造函數會被刪除:如果派生類已經定義了與基類構造函數參數列表相同的構造函數,繼承的版本會被刪除。

7.9、空基類優化

為保證同一類型的不同對象地址始終有別,要求任何對象或成員子對象的大小至少為1,即使該類型是空的類類型(即沒有非靜態數據成員的類或結構體),否則兩個對象的大小為0,它們的內存地址可能相同,導致指針無法區分它們。

C++標準允許編譯器將空基類子對象的大小優化為0字節,即使普通對象必須至少1字節

class Empty {};  // 空基類class Derived : public Empty {int x;  // 只有一個數據成員
};// 通常 sizeof(int) 為 4 字節
// 由于 EBCO,Empty 基類子對象的大小被優化為 0
// 因此 sizeof(Derived) == 4,而非 5!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/87723.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/87723.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/87723.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

4-6WPS JS宏自定義函數變長參數函數(實例:自定義多功能數據統計函數)學習筆記

一、自定義函數:自定義多功能數據統計函數。示例1&#xff1a;function jia1(x,...arr){//自定義變長函數&#xff0c;X第一參數&#xff0c;...arr為變長參數可放入無數個參數&#xff0c;就像是數組return xWorksheetFunction.Sum(arr)//返回&#xff0c;X第一參數WorksheetF…

HDMI延長器 vs 分配器 vs KVM切換器 vs 矩陣:技術區別與應用場景

在音視頻和計算機信號傳輸領域&#xff0c;延長器、分配器、切換器和矩陣是四種常見設備&#xff0c;它們的功能和應用場景有顯著區別。以下是它們的核心差異對比&#xff1a; 1. 延長器&#xff08;Extender&#xff09; 功能&#xff1a; ? 將信號&#xff08;如HDMI、Displ…

從0到1解鎖Element-Plus組件二次封裝El-Dialog動態調用

技術難題初登場 家人們&#xff0c;最近在開發一個超復雜的后臺管理系統項目&#xff0c;里面有各種數據展示、表單提交、權限控制等功能&#xff0c;在這個過程中&#xff0c;我頻繁地使用到了element-plus組件庫中的el-dialog組件 。它就像一個小彈窗&#xff0c;可以用來顯示…

數據結構實驗習題

codeblock F2是出控制臺 1.1 /* by 1705 WYY */ #include <stdio.h> #include <stdlib.h> #define TRUE 1 #define FALSE 0 #define YES 1 #define NO 0 #define OK 1 #define ERROR 0 #define SUCCESS 1 #define UNSUCCESS 0 #define OVERFLOW -2 #define UNDERF…

PyTorch 2.7深度技術解析:新一代深度學習框架的革命性演進

引言:站在AI基礎設施變革的歷史節點 在2025年這個充滿變革的年份,PyTorch團隊于4月23日正式發布了2.7.0版本,隨后在6月4日推出了2.7.1補丁版本,標志著這個深度學習領域最具影響力的框架再次迎來了重大突破。這不僅僅是一次常規的版本更新,而是一次面向未來計算架構和AI應…

LTspice仿真10——電容

電路1中電容下標m5&#xff0c;表示5個該電阻并聯電路2中ic1.5v&#xff0c;表示電容初始自帶電量&#xff0c;電壓為1.5v

C#事件驅動編程:標準事件模式完全指南

事件驅動是GUI編程的核心邏輯。當程序被按鈕點擊、按鍵或定時器中斷時&#xff0c;如何規范處理事件&#xff1f;.NET框架通過EventHandler委托給出了標準答案。 &#x1f50d; 一、EventHandler委托&#xff1a;事件處理的基石 public delegate void EventHandler(object se…

全面的 Spring Boot 整合 RabbitMQ 的 `application.yml` 配置示例

spring:rabbitmq:# 基礎連接配置 host: localhost # RabbitMQ 服務器地址port: 5672 # 默認端口username: guest # 默認用戶名password: guest # 默認密碼virtual-host: / # 虛擬主機&#xff08;默認/&…

Win32 API實現串口輔助類

近期需要使用C++進行串口通訊,將Win32 API串口接口進行了下封裝,可實現同步通訊,異步回調通訊 1、SerialportMy.h #pragma once #include <Windows.h> #include <thread> #include <atomic> #include <functional> #include <queue> #inclu…

Python-執行系統命令-subprocess

1 需求 2 接口 3 示例 4 參考資料 Python subprocess 模塊 | 菜鳥教程

Web攻防-XMLXXE上傳解析文件預覽接口服務白盒審計應用功能SRC報告

知識點&#xff1a; 1、WEB攻防-XML&XXE-黑盒功能點挖掘 2、WEB攻防-XML&XXE-白盒函數點挖掘 3、WEB攻防-XML&XXE-SRC報告 一、演示案例-WEB攻防-XML&XXE-黑盒功能點挖掘 1、不安全的圖像讀取-SVG <?xml version"1.0" standalone"yes&qu…

瀏覽器工作原理37 [#] 瀏覽上下文組:如何計算Chrome中渲染進程的個數?

一、前言 在默認情況下&#xff0c;如果打開一個標簽頁&#xff0c;那么瀏覽器會默認為其創建一個渲染進程。 如果從一個標簽頁中打開了另一個新標簽頁&#xff0c;當新標簽頁和當前標簽頁屬于同一站點&#xff08;相同協議、相同根域名&#xff09;的話&#xff0c;那么新標…

位置編碼和RoPE

前言 關于位置編碼和RoPE 應用廣泛&#xff0c;是很多大模型使用的一種位置編碼方式&#xff0c;包括且不限于LLaMA、baichuan、ChatGLM等等 第一部分 transformer原始論文中的標準位置編碼 RNN的結構包含了序列的時序信息&#xff0c;而Transformer卻完全把時序信息給丟掉了…

手動使用 Docker 啟動 MinIO 分布式集群(推薦生產環境)

在生產環境中&#xff0c;MinIO 集群通常部署在多個物理機或虛擬機上&#xff0c;每個節點運行一個 MinIO 容器&#xff0c;并通過 Docker 暴露 API 和 Console 端口。 1. 準備工作 假設有 4 臺服務器&#xff08;也可以是同一臺服務器的不同端口模擬&#xff0c;但不推薦生產…

如何在IntelliJ IDEA中設置數據庫連接全局共享

在現代軟件開發中&#xff0c;數據庫連接管理是開發過程中不可或缺的一部分。為了提高開發效率&#xff0c;減少配置錯誤&#xff0c;并方便管理&#xff0c;IntelliJ IDEA 提供了一個非常有用的功能&#xff1a;數據庫連接全局共享。通過這個功能&#xff0c;你可以在多個項目…

【Python】文件應用: 查找讀取的文件內容

查找讀取的文件內容 from pathlib import Pathpath Path(pi_million_digits.txt) contents path.read_text()lines contents.splitlines() pi_string for line in lines:pi_string line.lstrip()birthday input("Enter your birthday, in the form mmddyy: "…

交互式剖腹產手術模擬系統開發方案

以下是為您設計的《交互式剖腹產手術模擬系統》開發方案框架,包含技術實現路徑與詳細內容結構建議。由于篇幅限制,這里呈現核心框架與關鍵模塊說明: 交互式剖腹產手術模擬系統開發方案 一、項目背景與意義 1.1 傳統醫學教學痛點分析 尸體標本成本高昂(約$2000/例)活體訓…

AWS WebRTC: 判斷viewer端拉流是否穩定的算法

在使用sdk-c viewer端進行拉流的過程中&#xff0c;viewer端拉取的是視頻幀和音頻幀&#xff0c;不會在播放器中播放&#xff0c;所以要根據收到的流來判斷拉流過程是否穩定流暢。 我這邊采用的算法是&#xff1a;依據相鄰幀之間的時間間隔是否落在期望值的 20% 范圍內。 音頻…

【Python】文件讀取:逐行讀取應用實例——從一個JSONL文件中逐行讀取文件

從一個JSONL文件中逐行讀取文件&#xff0c;并將這些問題保存到一個新的JSONL文件中 import json import argparse import os # 導入os模塊用于檢查文件是否存在def read_questions_from_jsonl(file_path, limit):"""從JSONL文件中讀取指定數量的question部分…

百寶箱生成智能體

點擊新建應用 工作流如下&#xff1a; 點擊發布 點擊Web服務&#xff0c;上架