目錄
嵌套類
嵌套類的定義
嵌套類結構的訪問權限
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;
}
嵌套類結構的訪問權限
外部類對內部類的成員進行訪問
內部類對外部類的成員進行訪問
內部類相當于是定義在外部類中的外部類的友元類
類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++編程技巧,其基本思想是將一個外部可見類的實現細節(一般是通過私有的非虛成員)放在一個單獨的實現類中,在可見類中通過一個私有指針來間接訪問該類型。
好處:
-
實現信息隱藏;
-
只要頭文件中的接口不變,實現文件可以隨意修改,修改完畢只需要將新生成的庫文件交給第三方即可;
-
可以實現庫的平滑升級。
下面為測試代碼,可自行測試
//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