【C/C++ Pimpl模式】隐藏实现细节的高效方式 (Pimpl Idiom: An Efficient Way to Hide Implementation Details)

简介: 【C/C++ Pimpl模式】隐藏实现细节的高效方式 (Pimpl Idiom: An Efficient Way to Hide Implementation Details)

1. 简介 (Introduction)

在我们探讨高级编程技巧或深入挖掘复杂的数据结构时,一个核心的主题始终贯穿其中:隐藏细节。这种隐藏的力量,就像沉默的水滴可以穿石,它使我们的代码更加优雅,更具可维护性。正如水滴无声地塑造了其路径,Pimpl模式在C++中优雅地隐藏了实现细节。

1.1 Pimpl模式的定义 (Definition of the Pimpl Idiom)

Pimpl是“Pointer to implementation”的缩写,它是一种常用的C++设计模式。这种模式的核心思想是将类的实现细节从其接口中分离出来。想象一下,我们生活的世界中充满了这种分离:我们不需要知道电视是如何工作的,只需要知道如何使用遥控器。同样地,Pimpl模式允许我们仅展示类的功能,而隐藏其内部工作方式。

// header file
class MyClass {
public:
    void someFunction();
private:
    class Impl;
    Impl* pimpl;
};
// source file
class MyClass::Impl {
    void someFunction() {
        // Actual implementation
    }
};

如此,类的用户只看到接口,而不必担心实现细节,使得类的使用更加简单和直观。

1.2 Pimpl模式的目的和优势 (Purpose and benefits of using Pimpl)

使用Pimpl模式的主要目的是实现数据和函数的封装,以及提高编译时效率。当我们修改类的私有成员或实现时,不需要重新编译使用该类的所有代码,因为它们只依赖于类的接口,而不是实现。这可以大大减少项目的编译时间,特别是在大型项目中。

此外,Pimpl模式还提供了一个清晰、简洁的接口,使得代码更容易阅读和维护。它允许我们在不破坏现有API的情况下,更改或优化实现。

正如Bjarne Stroustrup在《The C++ Programming Language》中所说:“我们应该使接口尽可能简单,隐藏其复杂性。”


2. 为什么需要Pimpl模式? (Why the Pimpl Idiom?)

每一个设计模式的背后,都是一种试图解决的核心问题或挑战。在我们深入了解Pimpl模式之前,首先要理解为什么我们会选择使用它。这就好像为什么我们要选择某种交通工具:是因为距离、速度,还是为了欣赏沿途的风景?

2.1 避免头文件依赖 (Avoiding header file dependencies)

在C++中,头文件依赖是一个普遍的问题。当一个头文件改变时,所有包含这个头文件的源文件都需要重新编译。这可能导致编译时间显著增加,特别是在大型项目中。

考虑一个类,它包含了许多私有数据成员和复杂的实现。如果我们把所有这些都放在头文件中,那么任何小小的修改都会触发大量的重新编译。

Pimpl模式通过将实现细节移到源文件中,避免了这个问题,从而使得头文件变得轻量级和稳定。

不使用Pimpl 使用Pimpl
头文件中包含大量实现细节 头文件只包含必要的接口
修改头文件导致大量重新编译 修改实现不会触发大量重新编译
编译时间可能会很长 编译时间得到优化

2.2 缩短编译时间 (Reducing compile times)

正如之前所提到的,减少头文件的依赖可以显著缩短编译时间。想象一下,当我们走在一条长长的道路上,每次绕过一个障碍都会增加我们的旅程时间。而在Pimpl的世界中,这条路变得更加直接和高效。

当头文件只包含必要的接口,而不是整个实现,那么只有当接口真正发生变化时,使用该接口的代码才需要重新编译。这可以大大加快开发和测试的速度,特别是在迭代开发过程中。

2.3 实现API和ABI的稳定性 (Achieving API and ABI stability)

除了编译时间的优势,Pimpl模式还有一个重要的好处:它允许我们保持API和ABI的稳定性。API是应用程序编程接口,是软件组件之间交互的契约;而ABI是应用程序二进制接口,它涉及到如何在二进制层面上调用和链接代码。

当我们使用Pimpl模式,类的大小和布局在二进制层面上保持稳定,即使我们更改了实现细节。这意味着已编译的代码可以在不重新编译的情况下,与新版本的库链接和工作,只要接口保持不变。

正如Bjarne Stroustrup在《The C++ Programming Language》中所说:“稳定的API和ABI对于软件的长期成功至关重要。”

3. Pimpl模式的基本结构 (Basic Structure of the Pimpl Idiom)

深入挖掘Pimpl模式的结构就如同探索一座古老的建筑。从外部,我们只能看到其精致的外观,但一旦我们走进内部,就能发现隐藏在其中的各种秘密。

3.1 头文件和前向声明 (Header file and forward declaration)

首先,让我们了解如何在头文件中正确地使用Pimpl模式。使用前向声明可以避免包含不必要的头文件,从而加速编译速度。

class MyClassImpl;  // 前向声明 (Forward declaration)
class MyClass {
public:
    MyClass();
    ~MyClass();
    void someMethod();
private:
    MyClassImpl* pimpl;  // 指向实现的指针 (Pointer to the implementation)
};

在上面的代码中,我们只是简单地声明了 MyClassImpl。我们并没有提供任何关于它是如何工作的信息,也没有暴露任何实现细节。

3.2 源文件中的实际实现 (Actual implementation in the source file)

源文件是Pimpl模式中隐藏实现细节的地方。这里,我们可以自由地添加、修改或删除数据成员和方法,而不影响头文件。

class MyClass::MyClassImpl {
public:
    void someMethod() {
        // 实际的实现 (Actual implementation)
    }
};
MyClass::MyClass() : pimpl(new MyClassImpl) {}
MyClass::~MyClass() { delete pimpl; }
void MyClass::someMethod() {
    pimpl->someMethod();
}

正如我们可以在这个实现中看到,实际的逻辑和数据完全隐藏在 MyClassImpl 中。这为我们提供了一个清晰的隔离层,使得头文件和源文件之间的分离变得更加明确。

3.3 使用指针隐藏实现 (Using a pointer to hide the implementation)

在Pimpl模式中,使用指针是至关重要的。它作为一个桥梁,连接接口和实现。这种方式确保了接口的简洁性,同时提供了一种灵活的机制来改变或扩展实现。

优点 (Advantages) 描述 (Description)
封装 (Encapsulation) 通过隐藏实现细节,提供了清晰的接口。 (Provides a clear interface by hiding the implementation details.)
编译时间优化 (Compile-time optimization) 修改实现不需要重新编译使用该类的代码。 (Changes to the implementation don’t require recompiling code that uses the class.)
灵活性 (Flexibility) 允许在不破坏API的情况下更改实现。 (Allows for changes to the implementation without breaking the API.)

在软件设计中,隐藏细节并不仅仅是为了保密。它更多地是为了减少复杂性,使得系统更加模块化和可维护。正如某位智者曾说:“真正的力量不在于你知道多少,而在于你隐藏了多少。”

4. Pimpl模式的性能考虑 (Performance Considerations with Pimpl)

在编程中,任何决策都是权衡的结果。当我们选择使用Pimpl模式时,除了获得设计上的优点外,还需要考虑其对性能的影响。尽管通常这种影响很小,但在某些场景中,这种开销可能变得关键。

4.1 内联和Pimpl (Inlining and Pimpl)

内联是C++中的一种技术,可以通过直接将函数体插入到调用位置来避免函数调用的开销。但是,当我们使用Pimpl模式时,这种优化可能会受到限制,因为函数的实际实现是隐藏的。

例如,考虑以下情况:

// MyClass.h
class MyClass {
public:
    void doSomething();
private:
    class Impl;
    Impl* pimpl;
};
// MyClass.cpp
void MyClass::doSomething() {
    pimpl->actualMethod();
}
class MyClass::Impl {
    void actualMethod() {
        // Implementation
    }
};

在这种情况下,doSomething方法可能不会被编译器内联,因为它需要间接地通过pimpl指针调用actualMethod

4.2 间接访问的开销 (The overhead of indirection)

每当我们通过指针或引用访问数据或函数时,都会引入一次间接访问。虽然现代硬件和编译器通常可以很好地处理这种开销,但在性能关键的代码路径中,这种额外的跳转可能会产生可察觉的影响。

属性 / 方法 直接访问 间接访问 (Pimpl)
数据访问 直接从内存地址读取 通过指针跳转到实际地址,然后读取
函数调用 直接跳转到函数地址 通过指针查找函数地址,然后跳转

4.3 内存分配和释放 (Memory allocation and deallocation)

Pimpl模式通常需要动态内存分配,因为我们通常使用指针来存储实现的实例。这意味着,每次创建或销毁类的实例时,都需要分配或释放内存。动态内存操作相对较慢,可能会导致内存碎片。

考虑以下示例:

// MyClass.h
class MyClass {
public:
    MyClass();
    ~MyClass();
    void someFunction();
private:
    class Impl;
    Impl* pimpl;
};
// MyClass.cpp
MyClass::MyClass() : pimpl(new Impl) {}
MyClass::~MyClass() { delete pimpl; }

每次创建MyClass的实例时,都会分配Impl的内存。与栈上的分配相比,这可能会带来额外的开销。同样,析构函数必须释放这些资源。

4.4 总结与建议 (Summary and Recommendations)

Pimpl模式为我们提供了许多设计上的优势,但也带来了一些性能开销。在大多数情况下,这些开销是可以接受的,但在性能关键的应用中,我们应该特别关注。

  • 选择适当的内存分配策略:考虑使用自定义内存分配器或对象池,以减少动态内存操作的开销。
  • 减少间接访问:尽量减少通过Pimpl指针进行的函数调用和数据访问。
  • 内联和编译器优化:确保编译器能够优化您的代码。使用适当的编译器标志和优化级别。

正如Bjarne Stroustrup在《The C++ Programming Language》中所说:“优化是一个双刃剑,它既可以提高性能,也可以增加复杂性。应该在确实需要时才考虑优化。”

5. Pimpl模式的优缺点 (Pros and Cons of the Pimpl Idiom)

深入探究任何技术或设计模式,都会发现它们都有其独特的优势和潜在的弱点。Pimpl模式也不例外。让我们从多个维度探讨这一模式的优缺点,从而为我们的决策提供一个坚实的基础。

5.1 优点:灵活性、封装性、编译时间优化 (Pros: Flexibility, Encapsulation, Compile-time optimization)

5.1.1 灵活性 (Flexibility)

Pimpl模式提供了更大的实现灵活性。由于实际的实现细节被隐藏,开发者可以自由地修改和优化内部实现,而不影响公共接口。这意味着,无论是为了修复错误、提高性能,还是为了引入新的功能,都可以在不破坏现有代码的情况下进行。

例如,考虑一个库的新版本,它可能需要改进数据结构或算法。如果使用Pimpl模式,这些改进可以在不更改公共API的情况下完成。

5.1.2 封装性 (Encapsulation)

封装是面向对象设计的核心原则之一。Pimpl模式增强了这一原则,确保类的内部表示和实现细节被完全隐藏。这不仅使得接口更简洁,而且降低了误用或错误使用类的可能性。

正如《Effective C++》中所指出的:“封装不是隐藏数据,而是隐藏数据的表示。”

5.1.3 编译时间优化 (Compile-time optimization)

当修改类的实现时,使用Pimpl模式可以避免不必要的重新编译。只有实现文件需要重新编译,而使用该类的其他代码可以保持不变。在大型项目中,这可以显著地减少编译时间,从而提高开发效率。

例如,想象一个大型的软件项目,其中包含了数百个文件。在这样的项目中,任何微小的修改都可能触发大量的重新编译,尤其是当核心头文件被更改时。使用Pimpl模式可以有效地隔离这些更改,确保只有真正受影响的文件需要重新编译。

5.2 缺点:可能的额外内存开销、代码复杂性增加 (Cons: Possible extra memory overhead, Increased code complexity)

5.2.1 可能的额外内存开销 (Possible extra memory overhead)

由于Pimpl模式使用指针来引用实际的实现,这可能会导致额外的内存开销。每个对象都需要为其实现分配内存,并维护一个指向该实现的指针。虽然这种开销在大多数情况下都是微不足道的,但在某些高性能或资源受限的应用中,这可能成为一个考虑因素。

5.2.2 代码复杂性增加 (Increased code complexity)

尽管Pimpl模式提供了许多优点,但它也使代码结构变得更复杂。开发者需要管理两个相关的类,而不是一个,并确保它们之间的交互正确。此外,这种分离可能使新来的开发者难以理解代码的工作方式,因为实现被隐藏在另一个源文件中。

正如Scott Meyers在《Effective C++》中所说:“每一种技术都有其权衡之处。关键是要知道你在权衡什么。”

5.3 深入思考:隐藏之下的真实价值 (Deep Thoughts: The True Value Beneath the Hiding)

在我们的日常生活中,隐藏和封装无处不在。我们并不总是需要知道事物的工作原理,我们只需要知道如何与之互动。当我们开车时,我们不需要知道每一个内部零件的工作原理,我们只需要知道如何驾驶。同样,当我们与他人交往时,我们也不总是展现自己的所有一面,而是选择性地展现。这种选择性的展现并不意味着我们在隐藏真实的自己,而是为了与他人更好地相处。

同样地,Pimpl模式中的隐藏并不是为了欺骗或误导,而是为了提供一个清晰、简洁和稳定的接口。这使得代码更易于维护和扩展,同时也为开发者提供了更大的自由度来更改和优化实现,而不必担心影响到使用该接口的其他部分。

然而,正如生活中的隐藏有时会导致误解和困惑,代码中的隐藏也可能导致开发者对其工作原理的误解。因此,使用Pimpl模式时,开发者应确保提供充分的文档和注释,以帮助其他开发者理解其工作原理和使用方法。

正如一个古老的智慧所说:“真正的力量不在于你拥有什么,而在于你选择展现什么。”这句话不仅适用于人生,也同样适用于代码设计。通过明智地选择何时展现和何时隐藏,我们可以创造出强大、灵活且持久的软件设计。


6. Pimpl模式的变体和替代方案 (Variations and Alternatives to Pimpl)

在深入研究Pimpl模式的变体和替代方案之前,我们首先要认识到,正如人类思维总是在寻找解决问题的多种方式一样,编程世界也提供了多种设计和实现策略。每种策略都有其独特的优势,取决于特定的应用场景。

6.1 使用智能指针 (Using Smart Pointers)

在传统的Pimpl模式中,我们通常使用原始指针来持有实现的实例。但在现代C++中,使用智能指针,特别是std::unique_ptr,可以使内存管理更加自动化和安全。

class MyClassImpl;
class MyClass {
public:
    MyClass();
    ~MyClass(); // 注意:如果使用unique_ptr, 这个析构函数仍然需要在.cpp文件中定义,但可以为空。
    void someMethod();
private:
    std::unique_ptr<MyClassImpl> impl;
};

使用智能指针不仅避免了手动管理内存,还提供了资源所有权的明确语义,这是C++ RAII原则的核心。正如Bjarne Stroustrup在《The C++ Programming Language》中所说:“我们不应该通过手动方式管理资源,而应该依赖于资源管理类”。

6.2 其他设计模式与Pimpl的比较 (Comparison with Other Design Patterns)

6.2.1 策略模式 (Strategy Pattern)

策略模式是一种行为设计模式,允许在运行时选择对象的算法或策略。与Pimpl模式相似,策略模式也依赖于接口(通常是一个抽象基类)来定义特定的行为或操作。然后,可以有多个具体的策略类,它们都继承自这个接口并提供不同的实现。尽管策略模式的主要目标是定义一系列可互换的算法,但它与Pimpl模式在某种程度上有相似之处,即都试图提供一种方式来分离接口和实现。

例如,考虑一个排序算法的策略:

class SortStrategy {
public:
    virtual void sort(std::vector<int>& data) = 0;
};
class QuickSort : public SortStrategy {
public:
    void sort(std::vector<int>& data) override {
        // 实现快速排序
    }
};
class BubbleSort : public SortStrategy {
public:
    void sort(std::vector<int>& data) override {
        // 实现冒泡排序
    }
};

6.2.2 适配器模式 (Adapter Pattern)

适配器模式的目的是允许两个不兼容的接口工作在一起。它通过提供一个中间层(或适配器)将一个接口转换为另一个期望的接口。从某种意义上说,Pimpl模式也是一个适配器,因为它为客户端代码提供了一个简单的接口,而在内部它可能与复杂的或不同的实现相适配。

例如,如果我们有一个旧的类库,该库使用了一组不再推荐使用的API,而我们希望为新的客户端代码提供一个更现代和简洁的接口,我们可以使用Pimpl来"适配"旧API,使其对客户端代码隐藏。

6.2.3 代理模式 (Proxy Pattern)

代理模式与Pimpl模式有相似之处,因为它们都在某种程度上隐藏了真正的对象或实现。代理控制对原始对象的访问,可能是为了插入某种行为(例如,延迟初始化、引用计数或访问控制),或为了控制资源使用。Pimpl模式,特别是使用智能指针的变体,与代理模式有一定的重叠,特别是在资源管理方面。

6.3 深度思考:隐藏与透明性之间的权衡 (Deep Dive: The Trade-off between Hiding and Transparency)

隐藏实现细节的需要与为读者提供透明性之间存在一个有趣的权衡。这很像我们在生活中遇到的许多情况,我们在选择是否展示或隐藏自己的真实情感时会经历类似的权衡。在编程中,这种权衡通常是为了减少复杂性、提高灵活性和保护数据。

隐藏实现可以带来许多好处,如前面所述,但它也可能导致缺乏透明性。例如,当开发人员试图优化或调试代码时,他们可能会希望了解实际的实现细节。此外,过度的隐藏可能会导致效率低下,因为每次访问隐藏的数据都需要额外的间接开销。

正如许多哲学家和思考者所指出的,生活中的大多数事情都是权衡。同样,在编程设计中,我们也需要在隐藏和透明性之间找到一个平衡点。

7. 实际示例 (Practical Examples)

在理论的海洋中游泳,有时会让我们迷失方向。正如Confucius曾说:“知之者不如好之者,好之者不如乐之者。”(“The one who knows it is not as good as the one who loves it, and the one who loves it is not as good as the one who enjoys practicing it.”)。所以,让我们通过一些实际的代码示例来更好地理解和享受Pimpl模式。

7.1 一个简单的Pimpl实例

首先,我们从一个简单的Pimpl模式实例开始,看看如何在实际项目中应用它。

// MyWidget.h
class MyWidgetImpl;
class MyWidget {
public:
    MyWidget();
    ~MyWidget();
    void draw();
private:
    MyWidgetImpl* pImpl;
};
// MyWidget.cpp
#include "MyWidget.h"
class MyWidgetImpl {
public:
    void draw() {
        // Actual drawing implementation
    }
};
MyWidget::MyWidget() : pImpl(new MyWidgetImpl) {}
MyWidget::~MyWidget() { delete pImpl; }
void MyWidget::draw() {
    pImpl->draw();
}

在上面的代码中,我们可以看到MyWidget类是如何将其实现隐藏在MyWidgetImpl类之后的。这为我们提供了一个非常简洁和干净的接口,同时隐藏了实现细节。

7.2 深入探索Pimpl的精妙之处

正如Bjarne Stroustrup在《The C++ Programming Language》中所说:“C++是一个深海,但它的深度使其成为一个有趣的冒险。” Pimpl模式提供了一种将接口和实现分开的方法,这使得代码更加模块化,并减少了编译时间。

我们从std库中挑选一个类来看看Pimpl是如何在现实中应用的。考虑std::unique_ptr,这是一个智能指针,它在<memory>头文件中定义。在GCC编译器中,我们可以在bits/unique_ptr.h文件中找到它的定义。但实际的实现细节被隐藏在其他文件中,这是Pimpl模式的一个实例。

7.2.1 思考:为什么要使用Pimpl?

人类的思维就像编程一样,我们总是在为自己设定一个抽象层。我们不可能知道所有事情的每一个细节,因此我们依赖于抽象。这是为什么我们喜欢将复杂的概念和任务分解成更小、更易管理的部分。Pimpl模式正是这种思维方式的体现,它帮助我们管理和组织代码,使其更加清晰。

7.3 结合其他设计模式

Pimpl模式可以与其他设计模式(如工厂模式、单例模式等)相结合,以实现更加复杂和强大的功能。例如,我们可以使用工厂模式来创建和返回Pimpl类的实例。

8. 结论 (Conclusion)

在深入探讨了Pimpl模式的许多方面之后,我们可以对其进行一些总体的思考。

8.1 Pimpl模式的核心思想 (The Core Idea of Pimpl Idiom)

Pimpl模式是一个非常强大的设计模式,它可以帮助我们将实现与接口分离,这是软件工程中一个长久而又普遍的追求。正如Bjarne Stroustrup在《The C++ Programming Language》中所说:“我们应该尽量将接口与实现分离,以提高代码的灵活性和可维护性。”

在人的思维过程中,我们常常通过观察表面现象来理解事物的本质。这与Pimpl模式的哲学相似,即隐藏内部细节,只展示最重要的接口,使得外部用户能够更简单、直观地理解和使用。

8.2 Pimpl模式的现实应用 (Practical Applications of Pimpl)

Pimpl模式的应用遍及许多现代软件框架和库。例如,在Qt框架中,这种模式被广泛采用,以隔离库的公开API与内部实现,确保向前兼容性。

从深层次来看,人们对事物的认知往往是层次化的。从表面到深入,我们逐渐探索并理解其本质。Pimpl模式恰好与这种认知过程相匹配,它鼓励我们先了解接口,然后再深入到实现。

8.3 前景与展望 (Future Prospects)

随着软件开发的复杂性增加,隐藏实现细节并提供清晰的接口变得更加重要。Pimpl模式为此提供了一个有效的解决方案。然而,与所有工具和技术一样,最重要的是正确地使用它。正如古老的名言所说:“知其然,更要知其所以然。” (Understanding the ‘what’ is important, but it’s even more crucial to understand the ‘why’.)

在未来的软件开发实践中,我们期待看到更多关于Pimpl模式的创新应用,以及其他模式与Pimpl模式的结合,以满足不断变化的需求。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
3月前
|
消息中间件 负载均衡 监控
【ZMQ PUB模式指南】深入探究ZeroMQ的PUB-SUB模式:C++编程实践、底层原理与最佳实践
【ZMQ PUB模式指南】深入探究ZeroMQ的PUB-SUB模式:C++编程实践、底层原理与最佳实践
1142 1
|
3月前
|
设计模式 中间件 程序员
【C/C++ 奇异递归模板模式 】C++中CRTP模式(Curiously Recurring Template Pattern)的艺术和科学
【C/C++ 奇异递归模板模式 】C++中CRTP模式(Curiously Recurring Template Pattern)的艺术和科学
228 3
|
3月前
|
算法 编译器 程序员
深入理解C++编译模式:了解Debug和Release的区别
深入理解C++编译模式:了解Debug和Release的区别
515 3
|
3月前
|
消息中间件 存储 监控
【ZeroMQ的SUB视角】深入探讨订阅者模式、C++编程实践与底层机制
【ZeroMQ的SUB视角】深入探讨订阅者模式、C++编程实践与底层机制
516 1
|
3月前
|
设计模式 负载均衡 算法
C/C++发布-订阅者模式世界:揭秘高效编程的秘诀
C/C++发布-订阅者模式世界:揭秘高效编程的秘诀
184 1
|
3月前
|
算法 程序员 C语言
【C++ 迭代器实现细节 】深入探索C++迭代器:从实现到整合
【C++ 迭代器实现细节 】深入探索C++迭代器:从实现到整合
115 0
|
3月前
|
设计模式 算法 编译器
【C/C++ PIMPL模式 】 深入探索C++中的PIMPL模式
【C/C++ PIMPL模式 】 深入探索C++中的PIMPL模式
144 0
|
7天前
|
C++ 容器
C++中自定义结构体或类作为关联容器的键
C++中自定义结构体或类作为关联容器的键
13 0
|
7天前
|
存储 算法 搜索推荐
【C++】类的默认成员函数
【C++】类的默认成员函数
|
6天前
|
存储 安全 编译器
【C++】类和对象(下)
【C++】类和对象(下)
【C++】类和对象(下)