类和对象(六)--友元、内部类与再次理解类和对象

xml 复制代码
hello,这里是AuroraWanderll。
兴趣方向:C++,算法,Linux系统,游戏客户端开发
欢迎关注,我将更新更多相关内容!

我的个人主页

这是类和对象系列的第六篇文章,上篇指引:类和对象五:初始化列表和static

类和对象(六)--友元、内部类与再次理解类和对象

简易目录

  • 友元详解
  • 内部类详解
  • 再次理解类和对象

3. 友元

友元确实突破了封装性,提供了便利但增加了耦合度(破坏了软件工程高内聚低耦合的核心思想),应该谨慎使用(部分条件下必须用)。

3.1 友元函数

问题背景:重载 operator<< 的困境

问题: 如果将 operator<< 重载为成员函数,调用方式会变得很奇怪。

复制代码
class Date {
public:
    Date(int year, int month, int day)
        : _year(year), _month(month), _day(day)
    {}

    // 尝试作为成员函数重载 <<
    // 调用方式:d1 << cout  而不是正常的 cout << d1
    ostream& operator<<(ostream& _cout) {
        _cout << _year << "-" << _month << "-" << _day;
        return _cout;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main() {
    Date d(2024, 5, 20);
    d << cout;  // Error错误: 奇怪的调用方式,不符合习惯
    // 我们希望的是:cout << d;
}

原因: 成员函数的第一个参数是隐藏的 this 指针,所以对象必须在 << 的左侧。

cout也需要是第一个参数,所以二者产生冲突,非常尴尬,难以解决

因此,我们引出了友元

解决方案:使用友元函数
复制代码
class Date {
    // 声明友元函数 - 可以访问私有成员,一般习惯在最前面声明,但是其实无论放在哪个访问限定符号下都没影响
    friend ostream& operator<<(ostream& _cout, const Date& d);
    friend istream& operator>>(istream& _cin, Date& d);
    
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day)
    {}

private:
    int _year;
    int _month;
    int _day;
};

// 全局函数定义 - 注意:不再是成员函数!
ostream& operator<<(ostream& _cout, const Date& d) {
    _cout << d._year << "-" << d._month << "-" << d._day;  // 可以访问私有成员
    return _cout;
}

istream& operator>>(istream& _cin, Date& d) {
    _cin >> d._year >> d._month >> d._day;  // 可以访问私有成员
    return _cin;
}

int main() {
    Date d;
    cin >> d;        // 正确success: 正常调用:operator>>(cin, d)
    cout << d << endl; // 正确success: 正常调用:operator<<(cout, d)
    return 0;
}
友元函数的特点
  1. 不是成员函数:是定义在类外部的普通函数,注意:不再是作为成员函数重载,而是直接定义在类外面

  2. 可以访问私有成员 :在类内部声明为 friend

  3. 不受访问限定符限制 :可以在 publicprivateprotected 任何区域声明

  4. 不能用 const 修饰 :因为没有 this 指针

  5. 一个函数可以是多个类的友元

    class A; // 前向声明

    class B {
    private:
    int _b_data = 10;
    friend void PrintBoth(const A& a, const B& b); // 声明友元
    };

    class A {
    private:
    int _a_data = 20;
    friend void PrintBoth(const A& a, const B& b); // 声明友元
    };

    // 一个函数是两个类的友元
    void PrintBoth(const A& a, const B& b) {
    cout << "A data: " << a._a_data << endl; // 访问A的私有成员
    cout << "B data: " << b._b_data << endl; // 访问B的私有成员
    }

    int main() {
    A a;
    B b;
    PrintBoth(a, b); // 输出:A data: 20, B data: 10
    }

3.2 友元类

概念: 一个类可以是另一个类的友元,友元类的所有成员函数都可以访问另一个类的私有成员。

基本用法
复制代码
class Time {
    // 声明Date类为Time类的友元类
    friend class Date;
    
public:
    Time(int hour = 0, int minute = 0, int second = 0)
        : _hour(hour), _minute(minute), _second(second)
    {}

private:
    int _hour;
    int _minute;
    int _second;
};

class Date {
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day)
    {}

    // Date类的成员函数可以访问Time类的私有成员
    void SetTime(int hour, int minute, int second) {
        _t._hour = hour;      // 直接访问Time的私有成员
        _t._minute = minute;  // 直接访问Time的私有成员  
        _t._second = second;  // 直接访问Time的私有成员
    }

    void Display() {
        cout << _year << "/" << _month << "/" << _day << " ";
        cout << _t._hour << ":" << _t._minute << ":" << _t._second << endl;
    }

private:
    int _year;
    int _month;
    int _day;
    Time _t;  // Time类对象作为Date的成员
};

int main() {
    Date d(2024, 5, 20);
    d.SetTime(10, 30, 45);
    d.Display();  // 输出:2024/5/20 10:30:45
}
友元类的重要特性
1. 单向性(没有交换性)
复制代码
class Time {
    friend class Date;  // Date是Time的友元
    
private:
    int _hour;
};

class Date {
    // Time不是Date的友元,所以Time不能访问Date的私有成员
    
private:
    int _year;
};

void Test() {
    Time t;
    Date d;
    
    // Date可以访问Time的私有成员
    // d._year = 2024;  // 在Time类中不能这样写,因为Time不是Date的友元
}
2. 不能传递
复制代码
class A {
    friend class B;  // B是A的友元
private:
    int _a_data;
};

class B {
    friend class C;  // C是B的友元  
private:
    int _b_data;
};

class C {
public:
    void Test(A& a, B& b) {
        // b._b_data = 10;  //  正确:C是B的友元,所以C可以访问B的私有变量
        // a._a_data = 20;  //  错误!C不是A的友元(友元关系不能传递)
    }
};
3. 不能继承
复制代码
class Base {
    friend class FriendClass;
private:
    int _base_data;
};

class Derived : public Base {
private:
    int _derived_data;
};

class FriendClass {
public:
    void Test(Base& b, Derived& d) {
        b._base_data = 10;    //  正确:FriendClass是Base的友元
        // d._derived_data = 20; //  错误!友元关系不能继承到派生类
        // d._base_data = 30;    //  正确:但只能访问基类部分(通过基类引用)
    }
};

实际应用场景

1. 紧密协作的类
复制代码
// 树节点和树类 - 紧密协作
class TreeNode {
    friend class Tree;  // 树类需要频繁操作节点
    
private:
    int _data;
    TreeNode* _left;
    TreeNode* _right;
    TreeNode* _parent;
};

class Tree {
public:
    void Insert(int value) {
        // 可以直接操作TreeNode的所有私有成员
        // 简化了树的实现
    }
    
    void Remove(int value) {
        // 同样可以直接访问节点私有成员
    }
private:
    TreeNode* _root;
};
2. 工厂模式
复制代码
class Product {
    friend class Factory;  // 只有工厂能创建Product
    
private:
    Product() {}  // 构造函数私有化
    int _id;
    string _name;
};

class Factory {
public:
    static Product* CreateProduct(int id, const string& name) {
        Product* p = new Product();
        p->_id = id;      // 访问私有成员
        p->_name = name;  // 访问私有成员
        return p;
    }
};

使用友元的注意事项

  1. 破坏封装:友元可以直接访问私有成员,破坏了面向对象的封装原则
  2. 增加耦合:友元类之间紧密耦合,修改一个类可能影响另一个类
  3. 谨慎使用:只在真正必要时使用,比如运算符重载(cout,cin等等)、紧密协作的类
  4. 文档说明:使用友元时应该添加注释说明原因

总结

  • 友元函数:主要用于运算符重载,让全局函数能访问类的私有成员
  • 友元类:让一个类能访问另一个类的私有成员,用于紧密协作的类
  • 特性:单向性、不能传递、不能继承
  • 原则:谨慎使用,避免过度破坏封装性

4. 内部类

概念

内部类是指定义在另一个类内部的类。内部类是一个独立的类,它不属于外部类。

重要特性:

  • 内部类是外部类的友元类
  • 外部类不是内部类的友元
  • 不能通过外部类对象直接访问内部类成员

基本用法

复制代码
class Outer {
private:
    static int outer_static;
    int outer_data = 100;

public:
    // 内部类定义在public区域
    class InnerPublic {
    public:
        void AccessOuter(const Outer& outer) {
            cout << "访问外部类静态成员: " << outer_static << endl;  // 直接访问静态成员
            cout << "访问外部类实例成员: " << outer.outer_data << endl; // 通过对象访问实例成员
        }
    };

    // 内部类定义在private区域
    class InnerPrivate {
    public:
        void Show() {
            cout << "私有内部类" << endl;
        }
    };

    void UseInner() {
        InnerPrivate inner;  // 外部类可以创建内部类对象
        inner.Show();
    }
};

int Outer::outer_static = 50;  // 静态成员初始化

int main() {
    Outer outer;
    
    // 访问public内部类
    Outer::InnerPublic inner_pub;
    inner_pub.AccessOuter(outer);  // 可以访问外部类的私有成员
    
    // Outer::InnerPrivate inner_priv;  //  错误!私有内部类不能在外部访问
    outer.UseInner();  //  正确:通过外部类方法间接使用私有内部类
    
    return 0;
}

内部类的特性

1. 访问权限控制

内部类可以定义在外部类的任何访问区域:

复制代码
class Container {
public:
    // public内部类 - 外部可以访问
    class PublicInner {
    public:
        void PublicMethod() {
            cout << "Public Inner Class" << endl;
        }
    };

protected:
    // protected内部类 - 只有派生类可以访问
    class ProtectedInner {
    public:
        void ProtectedMethod() {
            cout << "Protected Inner Class" << endl;
        }
    };

private:
    // private内部类 - 只有外部类内部可以访问
    class PrivateInner {
    public:
        void PrivateMethod() {
            cout << "Private Inner Class" << endl;
        }
    };

public:
    void Test() {
        PublicInner pub;
        ProtectedInner prot;
        PrivateInner priv;
        
        pub.PublicMethod();
        prot.ProtectedMethod();
        priv.PrivateMethod();  // 外部类内部可以访问所有内部类
    }
};

int main() {
    Container c;
    Container::PublicInner pub;  //  正确
    pub.PublicMethod();
    
    // Container::ProtectedInner prot;  //  错误
    // Container::PrivateInner priv;    //  错误
    
    c.Test();
}
2. 直接访问外部类静态成员

内部类可以直接访问外部类的静态成员,不需要对象或类名;

并且可以通过创建对象的方式访问外部类普通成员

复制代码
class Outer {
private:
    static int static_var;
    int instance_var = 10;
    
public:
    class Inner {
    public:
        void AccessStatic() {
            cout << "直接访问静态成员: " << static_var << endl;  //  不需要Outer::static_var
            // cout << instance_var << endl;  //  错误!不能直接访问实例成员(重要)
        }
        
        void AccessInstance(const Outer& outer) {
            cout << "通过对象访问实例成员: " << outer.instance_var << endl;  // 正确,可以通过创建外部类对象访问外部类实例成员
        }
    };
    
    static void SetStatic(int val) {
        static_var = val;
    }
};

int Outer::static_var = 100;

int main() {
    Outer::Inner inner;
    inner.AccessStatic();  // 输出: 直接访问静态成员: 100
    
    Outer outer;
    inner.AccessInstance(outer);  // 输出: 通过对象访问实例成员: 10
    
    Outer::SetStatic(200);
    inner.AccessStatic();  // 输出: 直接访问静态成员: 200
}
3. 大小与外部类无关

sizeof(外部类) 只包含外部类的成员,不包含内部类:

准确的说:

核心要点:

  • sizeof(外部类) 只计算外部类自己的成员变量
  • sizeof(内部类) 只计算内部类自己的成员变量
  • 内部类的定义不会增加外部类的大小
  • 内部类和外部类是独立的类,只是作用域嵌套

cpp

复制代码
class Outer {
private:
    int data1 = 10;
    int data2 = 20;
    
public:
    class Inner {
    private:
        double inner_data1 = 1.1;
        double inner_data2 = 2.2;
        double inner_data3 = 3.3;
    public:
        void Show() {
            cout << "Inner class method" << endl;
        }
    };
    
    void UseInner() {
        Inner inner;
        inner.Show();
    }
};

int main() {
    cout << "sizeof(Outer): " << sizeof(Outer) << endl;      // 输出: 8 (两个int)
    cout << "sizeof(Outer::Inner): " << sizeof(Outer::Inner) << endl;  // 输出: 24 (三个double)
    
    Outer outer;
    Outer::Inner inner;
    
    outer.UseInner();  // 正常使用
}

实际应用场景

1. 迭代器模式

内部类最经典的用途是实现迭代器:

复制代码
class List {
private:
    struct Node {
        int data;
        Node* next;
        Node(int val) : data(val), next(nullptr) {}
    };
    
    Node* head = nullptr;

public:
    // 内部类:迭代器
    class Iterator {
    private:
        Node* current;
        
    public:
        Iterator(Node* node) : current(node) {}
        
        int& operator*() {
            return current->data;
        }
        
        Iterator& operator++() {
            current = current->next;
            return *this;
        }
        
        bool operator!=(const Iterator& other) {
            return current != other.current;
        }
    };
    
    void Add(int value) {
        Node* newNode = new Node(value);
        newNode->next = head;
        head = newNode;
    }
    
    Iterator begin() {
        return Iterator(head);
    }
    
    Iterator end() {
        return Iterator(nullptr);
    }
};

int main() {
    List list;
    list.Add(3);
    list.Add(2);
    list.Add(1);
    
    // 使用内部类迭代器
    for (List::Iterator it = list.begin(); it != list.end(); ++it) {
        cout << *it << " ";  // 输出: 1 2 3
    }
    cout << endl;
}
2. 实现细节隐藏

用私有内部类隐藏实现细节:

复制代码
class Database {
private:
    // 私有内部类,对外隐藏连接细节
    class Connection {
    private:
        string connection_string;
        bool connected = false;
        
    public:
        Connection(const string& conn_str) : connection_string(conn_str) {}
        
        bool Connect() {
            // 实际的连接逻辑
            connected = true;
            cout << "连接到: " << connection_string << endl;
            return true;
        }
        
        void Disconnect() {
            connected = false;
            cout << "断开连接" << endl;
        }
    };
    
    Connection* conn = nullptr;

public:
    Database(const string& conn_str) {
        conn = new Connection(conn_str);  // 外部类创建内部类对象
    }
    
    ~Database() {
        if (conn) {
            conn->Disconnect();
            delete conn;
        }
    }
    
    bool Open() {
        return conn->Connect();
    }
    
    // 外部无法直接创建Connection对象,实现了信息隐藏
};

int main() {
    Database db("server=localhost;user=admin");
    db.Open();
    
    // Database::Connection conn;  //  错误!Connection是私有内部类
}

5. 再次理解类和对象

从现实世界到计算机世界的转换过程

步骤1:现实世界抽象(人脑认知)

例子:洗衣机

  • 属性:品牌、容量、颜色、当前状态
  • 行为:洗衣、脱水、烘干、设置模式
步骤2:计算机语言描述(编写类)
复制代码
// 对洗衣机的抽象描述
class WashingMachine {
private:
    string brand;      // 品牌
    double capacity;   // 容量
    string color;      // 颜色  
    string state;      // 当前状态
    
public:
    WashingMachine(string b, double c, string clr) 
        : brand(b), capacity(c), color(clr), state("待机") {}
    
    // 行为方法
    void Wash() {
        state = "洗衣中";
        cout << brand << "洗衣机开始洗衣..." << endl;
    }
    
    void Rinse() {
        state = "脱水中";
        cout << "进行脱水操作" << endl;
    }
    
    void SetMode(string mode) {
        cout << "设置模式: " << mode << endl;
    }
    
    void DisplayStatus() {
        cout << brand << " - 状态: " << state << endl;
    }
};
步骤3:实例化对象(计算机认知)
复制代码
int main() {
    // 计算机现在"认识"洗衣机了
    WashingMachine myWasher("海尔", 8.5, "白色");  // 创建具体的洗衣机对象
    WashingMachine officeWasher("美的", 10.0, "灰色");  // 另一个洗衣机对象
    
    myWasher.DisplayStatus();  // 输出: 海尔 - 状态: 待机
    myWasher.Wash();           // 输出: 海尔洗衣机开始洗衣...
    myWasher.Rinse();          // 输出: 进行脱水操作
}
步骤4:模拟现实实体
复制代码
// 更完整的洗衣机模拟
class AdvancedWashingMachine {
private:
    string brand;
    double capacity;
    int waterLevel;     // 水位
    int temperature;    // 温度
    int speed;          // 转速
    bool isPoweredOn;
    
public:
    AdvancedWashingMachine(string b, double c) 
        : brand(b), capacity(c), waterLevel(0), temperature(0), 
          speed(0), isPoweredOn(false) {}
    
    void PowerOn() {
        isPoweredOn = true;
        cout << brand << "洗衣机已启动" << endl;
    }
    
    void SetWaterLevel(int level) {
        if (isPoweredOn) {
            waterLevel = level;
            cout << "水位设置为: " << level << endl;
        }
    }
    
    void SetTemperature(int temp) {
        if (isPoweredOn) {
            temperature = temp;
            cout << "温度设置为: " << temp << "°C" << endl;
        }
    }
    
    void StartWash() {
        if (isPoweredOn && waterLevel > 0) {
            cout << "开始洗衣程序..." << endl;
            cout << "容量: " << capacity << "kg, 温度: " << temperature 
                 << "°C, 水位: " << waterLevel << endl;
        }
    }
};

int main() {
    // 模拟现实中的洗衣机使用
    AdvancedWashingMachine washer("小天鹅", 7.5);
    
    washer.PowerOn();        // 开机
    washer.SetWaterLevel(3); // 设置水位
    washer.SetTemperature(40); // 设置温度
    washer.StartWash();      // 开始洗衣
}

类和对象的本质理解

类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性, 那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。

总结

内部类要点:

  • 内部类是外部类的友元,可以访问外部类所有成员
  • 内部类的访问权限受其在外部类中位置的控制
  • 内部类可以直接访问外部类的静态成员
  • 内部类不影响外部类的大小

类和对象要点:

  • :对现实实体的抽象描述,是自定义的数据类型
  • 对象:类的具体实例,占用实际内存空间
  • 过程:现实抽象 → 类描述(用计算机语言,让计算机认识到这个对象) → 实例化 → 借助对象模拟现实

通过类和对象,我们能够在计算机世界中建立与现实世界的映射关系,实现更加直观和易于维护的程序设计。

xml 复制代码
感谢你能够阅读到这里,如果本篇文章对你有帮助,欢迎点赞收藏支持,关注我,
我将更新更多有关C++,Linux系统·网络部分的知识。
相关推荐
leaves falling2 小时前
c语言-给定两个数,求这两个数的最大公约数
数据结构·算法
SamtecChina20232 小时前
Electronica现场演示 | 严苛环境下的56G互连
大数据·网络·人工智能·算法·计算机外设
想做后端的小C2 小时前
数据结构:线性表原地逆置
数据结构·考研
Tim_102 小时前
【C++入门】05、复合类型-数组
开发语言·c++·算法
jikiecui2 小时前
信奥崔老师:三目运算 (Ternary Operator)
数据结构·c++·算法
无限进步_2 小时前
【C语言&数据结构】另一棵树的子树:递归思维的双重奏
c语言·开发语言·数据结构·c++·算法·github·visual studio
t198751282 小时前
同伦(Homotopy)算法求解非线性方程组
算法
汉克老师2 小时前
GESP2025年9月认证C++一级真题与解析(判断题1-10)
c++·数据类型·累加器·循环结构·gesp一级·gesp1级
Elwin Wong2 小时前
从 Louvain 到 Leiden:保证社区连通性的社区检测算法研究解读
算法·社区检测·graphrag·louvain·leiden