C++ 嵌套類 (詳解 一站式講解)

目錄

嵌套類

嵌套類的定義

嵌套類結構的訪問權限

pimpl模式(了解)


嵌套類

嵌套類的定義

首先介紹兩個概念:

  • 類作用域(Class Scope)

類作用域是指在類定義內部的范圍。在這個作用域內定義的成員(包括變量、函數、類型別名等)可以被該類的所有成員函數訪問。類作用域開始于類定義的左花括號,結束于類定義的右花括號。在類作用域內,成員可以相互訪問,無論它們在類定義中的聲明順序如何。

  • 類名作用域(Class Name Scope)

類名作用域指的是可以通過類名訪問的作用域。這主要用于訪問類的靜態成員嵌套類型。類名必須用于訪問靜態成員或嵌套類型,除非在類的成員函數內部,因為它們不依賴于類的任何特定對象。以靜態成員為例:

class MyClass
{
public:void func(){_b = 100;//類的成員函數內訪問_b}static int _a;int _b;
};
//靜態成員要定義在類外,因為靜態成員是一個類所共有的,如果聲明在類中,每創建一個類對象就會生成一個靜態數據成員
int MyClass::_a = 0;void test0(){//這里靜態數據成員為公有所以可以在類外通過類名直接訪問MyClass::_a = 200;//類外部訪問_a
}

在函數和其他類定義的外部定義的類稱為全局類,絕大多數的 C++ 類都是全局類。我們在前面定義的所有類都在全局作用域中,全局類具有全局作用域。

與之對應的,一個類A還可以定義在另一類B的定義中,這就是嵌套類結構。A類被稱為B類的內部類,B類被稱為A類的外部類

以Point類和Line類為例

class Line
{
public:class Point{public:Point(int x,int y): _ix(x), _iy(y){}private:int _ix;int _iy;};
public:Line(int x1, int y1, int x2, int y2): _pt1(x1,y1), _pt2(x2,y2){}
private: Point _pt1;Point _pt2;
};

Point類是定義在Line類中的內部類,無法直接創建Point對象,需要在Line類名作用域中才能創建,因為point類在line類中,只能先找到line類在訪問point類

Point pt(1,2);//error
Line::Point pt2(3,4);//ok

Point類是Line類的內部類,并不代表Point類的數據成員會占據Line類對象的內存空間,在存儲關系上并不是嵌套的結構

只有當Line類有Point類類型的對象成員時,Line類對象的內存布局中才會包含Point類對象(成員子對象)。

(1)如果Line類中沒有Point類的對象成員,sizeof(Line) = 8;

(2)如果Line類中有兩個Point類的對象成員,sizeof(Line) = 24;

思考,如果想要使用輸出流運算符輸出Line對象,應該怎么實現?(重要)

最直觀的實現方式是定義一個運算符重載函數,但在函數體中需要讓輸出流運算符處理Point類型對象,所以還需要為Point類準備一個輸出流運算符重載函數。

—— 如果Point定義在Line的私有區域

那么還需要將operator<<函數聲明為Line的友元函數

下面為測試代碼,可自行測試

#include <iostream>
using namespace std;
class Line
{
public:class Point {public:Point(int x, int y): _ix(x), _iy(y){}friend ostream& operator<<(ostream& os, const Line::Point& rhs);private:int _ix;int _iy;};
public:Line(int x1, int y1, int x2, int y2): _pt1(x1, y1), _pt2(x2, y2){}~Line() {//cout << "~Line()" << endl;}friend ostream& operator<<(ostream& os, const Line& rhs);friend ostream& operator<<(ostream& os, const Point& rhs);private:Point _pt1;Point _pt2;
};//這里會訪問line的私有成員point所以在line類中聲明為友元,因為又要訪問point對象的數據成員,
//所以又要將這個函數聲明為內部類的友元
ostream& operator<<(ostream& os, const Line::Point& rhs)
{os << "(" << rhs._ix << "," << rhs._iy << ")";return os;
}//因為是輸出運算符重載,不會對操作數進行修改,傾向于聲明為有友元類
ostream& operator<<(ostream & os, const Line & rhs)
{os << rhs._pt1 << "------->" << rhs._pt2;return os;
}
void test()
{Line ll(1, 2, 3, 4);cout << ll << endl;
}
int main()
{test();return 0;
}

嵌套類結構的訪問權限

外部類對內部類的成員進行訪問

內部類對外部類的成員進行訪問

image-20240304153639911

內部類相當于是定義在外部類中的外部類的友元類

類A定義在類B中,那么類A訪問類B的成員時,就相當于默認的是類B的友元類。

下面為測試代碼,可以自行測試

#include <iostream>
using namespace std;
class Line
{
public:class Point {public:Point(int x, int y): _ix(x), _iy(y){}friend ostream& operator<<(ostream& os, const Line::Point& rhs);void print(){cout << "print" << endl;}friend class Line;void getline(const Line& rhs){//在內部類中不需要友元聲明,可以直接通過line對象直接訪問成員rhs._pt1;rhs._pt2;//通過類名作用域直接訪問line的私有靜態成員Line::_pt3;//直接用成員名訪問line的私有靜態數據成員_pt3;}private:int _ix;int _iy;static int _iz;};
public:Line(int x1, int y1, int x2, int y2): _pt1(x1, y1), _pt2(x2, y2){}~Line() {//cout << "~Line()" << endl;}friend ostream& operator<<(ostream& os, const Line& rhs);friend ostream& operator<<(ostream& os, const Point& rhs);void getpoint(){//在外部類中通過內部類訪問內部類的公有成員_pt1.print();//ok//外部類屬于內部類的類定義之外,這里是在內部類的私有數據成員//在外部類中通過內部類對象訪問內部類的私有成員//_pt1._ix;//需要聲明友元Point::_iz;//因為為私有,所以需要友元聲明//_ix;不可能實現//_iy;同上}
private:Point _pt1;Point _pt2;static double _pt3;
};
//要在外部類的外面對內部類的靜態成員進行定義
int Line::Point::_iz = 10;
double Line::_pt3 = 100;
//這里會訪問line的私有成員point所以在line類中聲明為友元,因為又要訪問point對象的數據成員,
//所以又要將這個函數聲明為內部類的友元
ostream& operator<<(ostream& os, const Line::Point& rhs)
{os << "(" << rhs._ix << "," << rhs._iy << ")";return os;
}//因為是輸出運算符重載,不會對操作數進行修改,傾向于聲明為有友元類
ostream& operator<<(ostream & os, const Line & rhs)
{os << rhs._pt1 << "------->" << rhs._pt2;return os;
}
void test()
{Line ll(1, 2, 3, 4);cout << ll << endl;
}
int main()
{test();return 0;
}

pimpl模式(了解)

實際項目的需求:希望Line的實現全部隱藏,在源文件中實現,再將其打包成庫文件,交給第三方使用。

(1)頭文件只給出接口:

//Line.hpp
class Line{
public:Line(int x1, int y1, int x2, int y2);~Line();void printLine() const;//打印Line對象的信息
private:class LineImpl;//類的前向聲明LineImpl * _pimpl;
};

(2)在實現文件中進行具體實現,使用嵌套類的結構(LineImpl是Line的內部類,Point是LineImpl的內部類),Line類對外公布的接口都是使用LineImpl進行具體實現的

在測試文件中創建Line對象(最外層),使用Line對外提供的接口,但是不知道具體的實現

//LineImpl.cc
class Line::LineImpl
{class Point{public:Point(int x,int y): _ix(x), _iy(y){}//...private:int _ix;int _iy;};//...
};//Line.cc
void test0(){Line line(10,20,30,40);line.printLine();
}

(3)打包庫文件,將庫文件和頭文件交給第三方

sudo apt install build-essential
g++ -c LineImpl.cc
ar rcs libLine.a LineImpl.o生成libLine.a庫文件
編譯:g++ Line.cc(測試文件) -L(加上庫文件地址) -lLine(就是庫文件名中的lib縮寫為l,不帶后綴)
此時的編譯指令為 g++ Line.cc -L. -lLine

內存結構

pimpl模式是一種減少代碼依賴和編譯時間的C++編程技巧,其基本思想是將一個外部可見類的實現細節(一般是通過私有的非虛成員)放在一個單獨的實現類中,在可見類中通過一個私有指針來間接訪問該類型。

好處:

  1. 實現信息隱藏;

  2. 只要頭文件中的接口不變,實現文件可以隨意修改,修改完畢只需要將新生成的庫文件交給第三方即可;

  3. 可以實現庫的平滑升級。

下面為測試代碼,可自行測試

//LineImpl.cc#include <iostream>
#include "Line.hpp"
using namespace std;
// 這是一個實現文件,不用包含測試,只需要實現頭文件中的函數等內容
//先對成員類型進行實現
class Line::LineImpl
{
public:class Point{public:Point(int x,int y): _ix(x), _iy(y){}~Point(){cout << "~Point()" << endl;}void print(){cout << "("<<_ix << "," << _iy << ")";}private:int _ix;int _iy;};
public:LineImpl(int x1, int y1, int x2, int y2): _pt1(x1,y1), _pt2(x2,y2){cout << "LineImpl(int *4)" << endl;}~LineImpl(){cout << "~LineImpl()" << endl;}void printline(){_pt1.print();cout << "-----> ";_pt2.print();cout << endl;}
private: Point _pt1;Point _pt2;
};Line::Line(int x1, int y1, int x2, int y2):_pimpl(new LineImpl(x1,y1, x2,y2))
{cout << "Line(int * 4)" << endl;
}
Line :: ~Line()
{cout << "~Line()" << endl;if(_pimpl){delete _pimpl;_pimpl = nullptr;}
}
void Line::printLine() const{_pimpl->printline();
}// test.cc#include "Line.hpp"
#include <iostream>
using std::cout;
using std::endl;void test()
{Line ll(1,2,3,4);ll.printLine();
}int main()
{test();return 0;
}//Line.hpp#ifndef _Line_HPP_
#define _Line_HPP_class Line {
public:Line(int x1, int y1, int x2, int y2);~Line();//提供給客戶使用的功能void printLine() const;//打印Line對象的信息
private:class LineImpl;//類的前向聲明LineImpl* _pimpl;
};#endif

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

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

相關文章

tcp 和http 網絡知識

1. 請簡述TCP和HTTP的定義與基本概念 TCP&#xff1a;即傳輸控制協議&#xff08;Transmission Control Protocol&#xff09;&#xff0c;是一種面向連接的、可靠的、基于字節流的傳輸層通信協議。它為互聯網中的數據通信提供穩定的傳輸機制&#xff0c;在不可靠的IP層之上&a…

MySQL安裝的多個組件中無用組件卸載

在決定卸載MySQL的哪些組件前&#xff0c;需根據你的實際使用場景判斷。以下是各組件的主要功能及卸載建議&#xff1a; 1. 核心組件卸載建議 組件名稱作用是否可卸載MySQL Server數據庫服務核心&#xff0c;存儲數據、處理SQL請求的核心程序。不可卸載 &#xff08;卸載會導致…

CosyVoice 技術全景解析:下一代語音生成模型的革命性突破

目錄 一、CosyVoice 模型概述 1. 背景與定位 二、技術架構與創新 1. 核心架構設計 2. 關鍵技術亮點 三、行業地位與競品對比 1. 市場定位分析 2. 競爭優勢 四、部署方案與硬件成本 1. 硬件需求 2. 優化技巧 五、優勢與挑戰 1. 核心優勢 2. 主要挑戰 六、開源生態…

rabbitmq-集群部署

場景&#xff1a;單個pod&#xff0c;部署在主節點&#xff0c;基礎版沒有插件&#xff0c;進階版多了一個插件 基礎版本&#xff1a; --- apiVersion: v1 kind: PersistentVolume metadata:name: rabbitmq-pv spec:capacity:storage: 5GiaccessModes:- ReadWriteOncestorage…

[密碼學實戰]商用密碼產品密鑰體系架構:從服務器密碼機到動態口令系統

[密碼學實戰]商用密碼產品密鑰體系架構:從服務器密碼機到動態口令系統 關鍵詞:商用密碼、密鑰體系、服務器密碼機、金融數據密碼機、動態口令、智能密碼鑰匙 摘要:本文深度解讀商用密碼產品的核心密鑰體系架構,涵蓋服務器密碼機、金融數據密碼機、VPN產品、動態口令系統及…

【unity游戲開發入門到精通——UGUI】UI事件監聽接口

注意&#xff1a;考慮到UGUI的內容比較多&#xff0c;我將UGUI的內容分開&#xff0c;并全部整合放在【unity游戲開發——UGUI】專欄里&#xff0c;感興趣的小伙伴可以前往逐一查看學習。 文章目錄 前言1、什么是UGUI事件接口&#xff1f;2、想要監聽事件步驟 一、事件接口1、U…

Spark知識總結

寬窄依賴&#xff1a;父RDD的分區只對應下面子RDD的一個分區&#xff0c;為窄依賴。其余為寬依賴 維度??窄依賴??寬依賴?數據傳輸無shuffle&#xff0c;本地處理14需shuffle&#xff0c;跨節點傳輸14并行度高&#xff08;允許流水線并行&#xff09;57低&#xff08;需等…

銘記之日(3)——4.28

銘記之日(3)——4.28 25.4.28&#xff0c;絕對是繼20.12.19與24.6.26之后&#xff0c;又一個被釘在恥辱柱上的日子。 4.28本質上為12.19的嚴重惡劣版。 道德敗壞、惡劣的大騙子終于在今日穿幫落馬。 斯文面孔下&#xff0c;竟藏匿了如此罪惡幽暗混沌的內心。 24.10.20&…

第16節:傳統分類模型-支持向量機(SVM)在圖像分類中的應用

一、引言 支持向量機(Support Vector Machine, SVM)作為一種經典的機器學習算法&#xff0c;自20世紀90年代由Vapnik等人提出以來&#xff0c;在模式識別和分類任務中表現出卓越的性能。 在深度學習興起之前&#xff0c;SVM長期占據著圖像分類領域的主導地位&#xff0c;即使…

《系統分析師-第三階段—總結(六)》

背景 采用三遍讀書法進行閱讀&#xff0c;此階段是第三遍。 過程 本篇總結第11章第12章的內容 第11章 第12章 總結 軟件架構設計是宏觀&#xff0c;基本架構確定之后&#xff0c;開始了系統化設計&#xff0c; 系統設計中對應的基本部分的知識較多&#xff0c;基礎知識是第…

new的使用

上次堆區的介紹中&#xff0c;我們提到了一個關鍵字new&#xff0c;那今天我們就詳細講講它 今天我們主要將兩個內容 1.new的基本語法 2.用new創建數組 1.new的基本語法 new,可以在堆區中創建空間&#xff0c;來存放數據&#xff0c;就比如像下面這樣 int* p new int(29);//n…

使用python實現自動化拉取壓縮包并處理流程

使用python實現自動化拉取壓縮包并處理流程 實現成果展示使用說明 實現成果展示 使用說明 執行./run.sh 腳本中的內容主要功能是&#xff1a; 1、從遠程服務器上下拉制定時間更新的數據 2、將數據中的zip拷貝到指定文件夾內 3、解壓后刪除所有除了lcm之外的文件 4、新建一個ou…

香橙派打包qt文件報錯“xcb 插件無法加載”與“QObject::moveToThread”線程錯誤的解決方案

PyQt 報錯總結&#xff1a;打包文件過程&#xff0c;“xcb 插件無法加載”與“QObject::moveToThread”線程錯誤的解決方案全解析 在使用 PyQt5 搭建圖形界面時&#xff0c;打包文件的過程中出現的問題&#xff0c;真難繃&#xff0c;搞了半天。 Qt 平臺插件 xcb 無法加載QOb…

Missashe考研日記-day29

Missashe考研日記-day29 1 專業課408 學習時間&#xff1a;3h學習內容&#xff1a; 今天先是把虛擬存儲剩余的課聽完了&#xff0c;然后就是做課后選擇題&#xff0c;57道&#xff0c;已經接受了OS課后題尤其多的事實了。解決并且理解完習題之后就開始預習文件管理的內容&…

【Linux】第十二章 安裝和更新軟件包

目錄 1. 什么是RPM&#xff1f; 2. dnf是什么&#xff0c;它和rpm有什么聯系和區別&#xff1f; 3. RHEL 中如何做才能啟用對第三方存儲庫的支持&#xff1f; 4. 怎么理解RHEL9中的應用流(Application Streams)和模塊(Modules)&#xff1f; 5. RHEL9 有兩個必要的軟件存儲…

新時代下的存儲過程開發實踐與優化

隨著現代應用系統的復雜度不斷增加&#xff0c;數據庫作為核心的數據存儲和處理引擎&#xff0c;其性能和可靠性顯得尤為重要。存儲過程&#xff08;Stored Procedure&#xff09;作為一種封裝在數據庫中的應用邏輯&#xff0c;使得開發者能夠在數據庫層面實現數據操作、數據校…

從梯度消失到百層網絡:ResNet 是如何改變深度學習成為經典的?

自AlexNet贏得2012年ImageNet競賽以來&#xff0c;每個新的獲勝架構通常都會增加更多層數以降低錯誤率。一段時間內&#xff0c;增加層數確實有效&#xff0c;但隨著網絡深度的增加&#xff0c;深度學習中一個常見的問題——梯度消失或梯度爆炸開始出現。 梯度消失問題會導致梯…

JVM——引入

什么是JVM&#xff1f;它與JDK、JRE的關系&#xff1f; JVM、JRE 和 JDK 是 Java 平臺的三個核心組件&#xff0c;各自承擔著不同的職責&#xff0c;它們之間的關系密不可分。理解它們的區別和聯系有助于更好地開發、部署和運行 Java 應用程序。對于 Java 開發者來說&#xff…

PyCharm 2023升級2024 版本

windows下把老版本卸載之后&#xff0c;需要把環境變量&#xff0c;注冊表信息刪除。 并且把C:\Users\用戶\AppData 文件夾下的 Local\JetBrains和Roaming\JetBrains 都刪除&#xff0c;再重新安裝 原舊項目升級的方式&#xff1a; 1.2023虛擬機的文件夾是venv 改為.venv…

從外賣大戰看O2O新趨勢:上門私廚平臺系統架構設計解析

京東高調進軍外賣市場&#xff0c;美團全力防守&#xff0c;兩大巨頭的競爭讓整個行業風起云涌。但在這場外賣大戰之外&#xff0c;一個更具潛力的細分市場正在悄然興起——上門私廚服務。 與標準化外賣不同&#xff0c;上門私廚提供的是個性化定制服務。廚師帶著新鮮食材上門現…