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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值