C++面向对象编程完全指南

简介

C++作为一门支持面向对象编程(OOP)的语言,其核心思想是数据抽象和封装。本文从类的基础概念出发,逐步深入构造函数、继承、多态、模板、异常处理等高级特性,最后覆盖STL容器和智能指针,为开发者提供一份完整的C++面向对象编程参考。

一、类与对象基础

1.1 类的基本思想

类的基本思想是数据抽象封装

  • 数据抽象:依赖接口和实现分离的编程技术。类的接口包括用户能执行的操作,实现则包括数据成员和函数定义
  • 封装:实现了类的接口和实现的分离,隐藏了实现细节
class Box {
public:
    // 接口:用户可以调用的方法
    double getVolume(void);
    void setLength(double len);
    void setBreadth(double bre);
    void setHeight(double hei);

private:
    // 实现:隐藏的数据成员
    double length;
    double breadth;
    double height;
};

1.2 访问控制

修饰符 类内部 派生类 外部
public 可访问 可访问 可访问
protected 可访问 可访问 不可访问
private 可访问 不可访问 不可访问

默认访问修饰符class 默认为 privatestruct 默认为 public

二、构造函数详解

2.1 默认构造函数

class SmartPtr {
public:
    SmartPtr() : count(0), ptr(nullptr) {}
private:
    int count;
    char *ptr;
};

如果未声明任何构造函数,编译器会隐式定义一个默认构造函数。

2.2 初始化构造函数

SmartPtr(int cnt, int *p) : count(cnt), ptr(new int(*p)) {}

使用初始化列表来初始化字段,特别适用于初始化 const 类型变量和引用类型。

注意:如果初始化构造函数只有一个参数,可能存在隐式转换的风险:

class SmartPtr {
public:
    SmartPtr(int cnt) : count(cnt), ptr(nullptr) {}
};

SmartPtr obj = 18;  // 隐式转换,编译通过

2.3 拷贝构造函数

class SmartPtr {
public:
    SmartPtr(const SmartPtr &obj) : count(obj.count), ptr(new int(*obj.ptr)) {}
};

调用时机

  • 用一个对象初始化另一个对象时
  • 函数参数按值传递时
  • 函数返回对象时

重要特性

  • 形参必须是 const Class& 类型(否则会导致无限递归)
  • 拷贝构造函数和默认构造函数在一个对象生命周期内只调用其中一个
  • 如果类中有堆内存,必须自定义拷贝构造函数(深拷贝)

2.4 移动构造函数(C++11)

class SmartPtr {
public:
    SmartPtr(SmartPtr &&obj) noexcept {
        count = obj.count;
        ptr = obj.ptr;        // 直接转移资源所有权
        obj.ptr = nullptr;    // 源对象置空
    }
};

移动构造函数通过"窃取"资源而非拷贝,大幅提升性能,特别适用于包含大量堆内存的类。

2.5 赋值运算符重载

class SmartPtr {
public:
    SmartPtr& operator=(const SmartPtr &obj) {
        if (this == &obj) return *this;  // 防止自赋值
        delete ptr;
        count = obj.count;
        ptr = new int(*obj.ptr);
        return *this;  // 返回引用以支持链式赋值 a = b = c
    }
};

2.6 类的默认八种函数

编译器会默认生成以下函数(如果不显式定义):

  1. 默认构造函数
  2. 默认拷贝构造函数
  3. 默认析构函数
  4. 默认重载赋值运算符
  5. 默认重载取址运算符
  6. 默认重载取址运算符(const 版本)
  7. 默认移动构造函数(C++11)
  8. 默认重载移动赋值操作符(C++11)

注意:编译器默认生成的拷贝行为都是浅拷贝。如果类中包含堆内存,需要自行实现深拷贝。

2.7 explicit 关键字

explicit 阻止构造函数的隐式转换:

class Test1 {
public:
    Test1(int n) { num = n; }  // 普通构造函数
private:
    int num;
};

class Test2 {
public:
    explicit Test2(int n) { num = n; }  // 显式构造函数
private:
    int num;
};

Test1 t1 = 12;    // 合法:隐式转换
Test2 t2 = 12;    // 编译错误:不允许隐式转换
Test2 t2(12);     // 合法:显式调用

最佳实践:除非有明确的理由允许隐式转换,否则单参数构造函数都应声明为 explicit

2.8 default 与 delete(C++11)

class A {
public:
    A() = default;            // 使用编译器默认实现
    A(const A&) = delete;     // 禁止拷贝
    A& operator=(const A&) = delete;  // 禁止赋值
};

三、静态成员

3.1 静态数据成员

class CBook {
public:
    static int m_static;    // 静态数据成员
    int m_data;             // 非静态数据成员
    static CBook cb;        // 允许:静态成员可以是当前类类型
    CBook cb1;              // 不允许:非静态成员不能是当前类类型
    CBook *ptr;             // 允许
    static CBook *ptr1;     // 允许
};

// 必须在类外初始化
int CBook::m_static = 0;

特性

  • 所有对象共享同一份静态成员
  • 可以通过类名直接访问:CBook::m_static
  • 可以作为成员函数的默认参数

3.2 静态成员函数

class MyClass {
public:
    static int func(int a, int b);  // 静态成员函数
    int func1(int a, int b);
};

限制

  • 没有 this 指针
  • 只能访问静态数据成员和静态成员函数
  • 不能声明为 const
  • 在外部定义时不能加 static 关键字

通过传递对象指针可以访问非静态成员

class T {
private:
    int member;
public:
    static int func(T obj) {
        return obj.member;   // 可以:通过对象访问
    }
};

四、const 成员函数

class CBook {
public:
    int print() const {
        // 只能读取成员变量,不能修改
        // 但可以修改 static 成员和 mutable 成员
        std::cout << "const function";
    }
private:
    int data;
    mutable int counter;  // mutable 成员即使在 const 函数中也可修改
};

规则

  • const 修饰的成员函数不能修改任何非静态成员变量
  • const 函数只能调用其他 const 函数
  • const 对象只能调用 const 成员函数
  • const 对象也可以调用 const 成员函数

五、友元

5.1 友元函数

class CBook {
    friend int function();  // 友元函数声明
private:
    int data;
};

int function() {
    CBook book;
    return book.data;  // 可以访问私有成员
}

5.2 友元类

class CBook {
    friend class CCook;  // 友元类声明
private:
    int data;
};

class CCook {
    void func() {
        CBook book;
        book.data;  // 可以访问 CBook 的私有成员
    }
};

5.3 友元成员函数

class CBook {
    friend int CCook::func();  // 只有 CCook::func 能访问
private:
    int data;
};

注意:友元关系会暴露实现细节,降低封装性,应谨慎使用。

六、命名空间(namespace)

6.1 基本用法

namespace MyName {
    int var1;
    int var2;
    int func();  // 声明
}

// 在源文件中定义
int MyName::func() { return 0; }

6.2 嵌套命名空间

namespace Outer {
    namespace Inner {
        int value;
    }
}

using namespace Outer::Inner;  // 只引入 Inner

6.3 未命名的命名空间

namespace {
    int count;  // 等价于 static int count;
}

6.4 跨文件的命名空间

// A.cpp
namespace NS {
    int var;
    void func2(int, int);
}

// B.cpp(使用A中的命名空间内容)
namespace NS {
    extern int var;
    extern void func2(int, int);
}

七、继承

7.1 三种继承方式

基类成员        public继承     protected继承    private继承
─────────────────────────────────────────────────
public         public        protected       private
protected      protected     protected       private
private        不可见        不可见           不可见

7.2 构造与析构顺序

class Box {};
class Box1 : public Box {
public:
    Box1() : Box() {  // 先构造父类,再构造子类
        // 析构顺序相反:先析构子类,再析构父类
    }
};

7.3 同名成员处理

当子类和父类存在同名成员函数时,子类的函数会覆盖父类的所有函数重载。

child.Parent::func();  // 显式调用父类成员

7.4 虚继承

解决菱形继承问题:

class CAnimal {};
class CBird : virtual public CAnimal {};   // 虚继承
class CFish : virtual public CAnimal {};   // 虚继承
class CWaterBird : public CBird, public CFish {};

// 虚继承保证 CWaterBird 中只有一份 CAnimal 的副本

7.5 抽象类与纯虚函数

class Abstract {
public:
    virtual int func(int args) = 0;  // 纯虚函数
};
// 抽象类不能实例化对象,但可以使用抽象类指针
// 派生类必须实现所有纯虚函数

八、多态与虚函数

8.1 虚函数机制

C++编译器通常是静态绑定的,但 virtual 关键字能让程序在运行时进行动态绑定

class Base {
public:
    virtual void show() { cout << "Base" << endl; }
};

class Derived : public Base {
public:
    void show() override { cout << "Derived" << endl; }
};

Base *obj = new Derived();
obj->show();  // 输出 "Derived",动态绑定

8.2 虚函数表

  • 编译器一旦发现类中有虚函数,就会为该类型生成虚函数表(vtable)
  • 每个实例中会添加一个指向虚函数表的指针(vptr)
  • 32位系统占4字节,64位系统占8字节

8.3 override 与 final(C++11)

class Base {
public:
    virtual void func() {}
};

class Derived : public Base {
public:
    void func() override;   // 强制重写基类虚函数
    void other() final;     // 禁止子类重写此函数
};

8.4 虚函数只能通过指针或引用生效

// 值传递会导致对象切片,多态失效
void badFunc(Base obj);      // 对象切片,丢失派生类信息

// 正确方式
void goodFunc(Base &obj);    // 引用
void goodFunc(Base *obj);    // 指针

九、模板编程

9.1 函数模板

template <class T>
T Max(T a, T b) {
    return a > b ? a : b;
}

// 调用
Max<int>(3, 5);
Max(3.0, 5.0);  // 自动推导

9.2 模板特化

对于特殊类型,通用模板可能不适用,可以进行特化:

// 通用模板
template <class T>
T Max(T a, T b) {
    return a > b ? a : b;
}

// 特化版本
template <>
const char* Max<const char*>(const char* a, const char* b) {
    return strcmp(a, b) > 0 ? a : b;
}

9.3 类模板

template <typename T1, typename T2 = int>
class Class1 {
public:
    T1 var;
    T2 func(T1 args);
};

// 类模板成员函数的外部定义
template <typename T1, typename T2>
T2 Class1<T1, T2>::func(T1 args) {
    // ...
}

// 实例化
Class1<int, double> obj;

9.4 类模板的静态成员

类模板中同一类型的实例拥有各自独立的静态成员副本:

template <class T>
class Class1 {
    static int m_Static;
};

Class1<int> c1;     // 拥有独立的 m_Static
Class1<char> c2;    // 拥有另一份独立的 m_Static

9.5 typename 的特殊用法

在模板编程中,typename 用于告诉编译器依赖类型名是一个类型:

template <typename T>
void func() {
    typename T::value_type val;  // 使用 T 内部的类型
}

十、智能指针

10.1 auto_ptr(已弃用)

C++98 的智能指针,C++11 已弃用,存在所有权转移导致空指针的问题。

10.2 unique_ptr

独占式智能指针,禁止拷贝赋值,但支持移动语义:

unique_ptr<T> ptr = make_unique<T>();
unique_ptr<T> ptr2 = ptr;            // 编译错误
unique_ptr<T> ptr3 = std::move(ptr); // 正确,ptr 变为空

10.3 shared_ptr

共享式智能指针,通过引用计数管理生命周期:

shared_ptr<T> ptr1 = make_shared<T>();
ptr1.use_count();  // 1
shared_ptr<T> ptr2 = ptr1;
ptr1.use_count();  // 2
ptr2.use_count();  // 2

循环引用问题

class A {
    shared_ptr<B> ptrb;  // 改为 weak_ptr<B> 解决
};
class B {
    shared_ptr<A> ptra;  // 改为 weak_ptr<A> 解决
};
// 两个对象互相持有 shared_ptr,引用计数永远不为0,导致内存泄漏

10.4 weak_ptr

弱引用指针,不增加引用计数,用于解决 shared_ptr 的循环引用问题:

shared_ptr<A> ptra = make_shared<A>();
weak_ptr<A> weaka = ptra;  // 不增加引用计数
ptra.use_count();           // 仍然为 1

if (auto locked = weaka.lock()) {
    // 成功获取 shared_ptr,对象仍然存活
}

10.5 enable_shared_from_this

当需要在类的成员函数中将 this 作为回调参数时,使用 enable_shared_from_this 防止对象被意外释放:

class MyClass : public enable_shared_from_this<MyClass> {
public:
    void registerCallback() {
        // 使用 shared_from_this() 而非 this
        auto self = shared_from_this();
        register_handler([self]() { /* ... */ });
    }
};

十一、异常处理

11.1 基本语法

try {
    throw "This is an error";
}
catch (const char* msg) {
    cout << "捕获异常:" << msg << endl;
}
catch (...) {
    cout << "捕获所有异常" << endl;
}

11.2 异常的逐层传递

异常可以逐层向上抛出,直到被捕获。

11.3 析构函数中的异常

Effective C++ 原则:不要在析构函数中抛出异常。析构函数抛出异常且不处理,析构函数就不会被调用。

建议将资源分配放到 Initialize() 函数中,释放放到 Destroy() 函数中。

十二、RTTI(运行时类型识别)

12.1 概念

RTTI 允许在只有一个指向基类的指针或引用时,确定对象的实际类型。

12.2 C++ 四种强制类型转换

1. static_cast

基本数据类型之间的转换,以及有继承关系的类之间的转换:

static_cast<int>(7.987);  // 基本类型转换
B *b = new A();
A *a = static_cast<A*>(b);  // 继承关系的转换

2. dynamic_cast

运行时类型检查,要求基类有虚函数:

// 成功返回指针/引用,失败返回 NULL(指针)或抛出异常(引用)
Derived *d = dynamic_cast<Derived*>(basePtr);

3. const_cast

添加或移除 const 属性:

const int &ref = 10;
int &mod = const_cast<int&>(ref);

4. reinterpret_cast

底层重新解释,几乎可以做任何转换,不到万不得已不要使用。

十三、C++ 引用 vs 指针

特性 引用 指针
空值 不存在空引用 可以为 NULL
初始化 必须在创建时初始化 可在任何时候初始化
重新绑定 一旦初始化不能改变指向 可以随时指向另一个对象
本质 变量的别名 存储地址的变量

十四、malloc/free vs new/delete

特性 malloc/free new/delete
性质 库函数 运算符
类型转换 需要手动强转 自动返回正确类型
构造/析构 不调用 自动调用
失败处理 返回 NULL 抛出异常

十五、C++与C的混合编程

15.1 C++ 调用 C

// c_header.h
#ifdef __cplusplus
extern "C" {
#endif
    void C_fun();
#ifdef __cplusplus
}
#endif

15.2 C 调用 C++

// cpp_header.h
extern "C" int add(int x, int y);

// cpp_source.cpp
extern "C" int add(int x, int y) { return x + y; }

十六、单例模式

class CDanli {
public:
    static CDanli* getInstance() {
        if (ptr == nullptr) {
            ptr = new CDanli();
        }
        return ptr;
    }
private:
    CDanli() {}
    ~CDanli() {}
    static CDanli *ptr;
};

CDanli* CDanli::ptr = nullptr;

十七、using 与 typedef

// 两者功能相同
typedef double ware;
using ware = double;

typedef double* p;          // p 是 double* 类型
using p = double*;          // 同上

十八、mutable 关键字

mutableconst 相反,允许 const 成员函数修改被 mutable 修饰的成员变量:

class MyClass {
public:
    int func(int a) const {
        return counter++;  // 合法:counter 是 mutable
    }
private:
    mutable int counter;
};

总结

C++ 面向对象编程是一个庞大而精密的体系。从基本的类定义到高级的模板元编程,从简单的对象构造到复杂的虚函数机制,每一个知识点都在实际工程中扮演着重要角色。

核心要点回顾:

  • 深入理解构造函数体系(默认、拷贝、移动、赋值),避免浅拷贝陷阱
  • 善用 explicitoverridefinal 等关键字增强代码安全性
  • 理解虚函数表和动态绑定机制,正确实现多态
  • 优先使用智能指针管理资源,注意循环引用问题
  • 合理使用模板实现泛型编程,同时关注特化技术

原始笔记来源: frasight/C++笔记.cpp, hhjt/otherNotes.cpp

更多推荐