当前位置: 首页 > news >正文

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

相关文章:

  • Flink Checkpoint 与实时任务高可用保障机制实战
  • SpeedyAutoLoot
  • Linux中的shell脚本练习
  • MCP之二_服务器与客户端实现
  • Python实例题:Pvthon实现键值数据库
  • 【计网】认识跨域,及其在go中通过注册CORS中间件解决跨域方案,go-zero、gin
  • 对VTK中的Volume Data体数据进行二维图像处理
  • 电子电器架构 ---电气/电子架构将在塑造未来出行方面发挥啥作用?
  • React速通笔记
  • OpenGL----OpenGL纹理与纹理缓存区
  • 系统架构-架构评估
  • Golang|使用函数作为参数和使用接口的联系
  • 如何使用 Redis 缓存验证码
  • 【MCP Node.js SDK 全栈进阶指南】高级篇(1):MCP多服务器协作架构
  • 在视图中交互 闪退问题
  • 有哪些和PPT自动生成有关的MCP项目?
  • 多实例情况下,实例名较长dbca失败
  • TimDbg
  • Shell脚本-随机数实战案例
  • 排序--数据结构初阶(4)(C/C++)
  • 10台核电新机组获核准,上海核电厂商独揽超500亿元订单
  • 扎克伯格怕“错过风口”?Meta AI数字伴侣被允许与未成年人讨论不当话题
  • 央视曝光假进口保健品:警惕!保税仓发货不等于真进口
  • 第152次中老缅泰湄公河联合巡逻执法行动圆满结束
  • 应勇:以法治力量服务黄河流域生态保护和高质量发展
  • 湖南娄底市长曾超群,已任娄底市委书记