C++ Primer Plus 第12章:类和动态内存分配

12.1 动态内存与类

12.1.1 为什么类需要动态内存?

当类的成员需要在运行时确定大小时,就需要动态内存分配。

复制代码
静态成员(编译时确定大小):
┌──────────────────────┐
│ char name[20];       │  ← 固定20字节,可能浪费或不够用
└──────────────────────┘

动态成员(运行时确定大小):
┌──────────────────────┐
│ char* name;          │  ← 指针(固定大小)
└──────────────────────┘
         │
         ▼
┌──────────────────────┐
│ new char[实际长度+1] │  ← 堆上按需分配
└──────────────────────┘

12.1.2 动态内存类的问题引入

cpp 复制代码
// problem_demo.cpp -- 不正确的动态内存类(演示问题)
#include <iostream>
#include <cstring>

class BadString
{
private:
    char* str;
    int   len;

public:
    BadString(const char* s = "")
    {
        len = strlen(s);
        str = new char[len + 1];
        strcpy(str, s);
        std::cout << "构造:\"" << str << "\"" << std::endl;
    }

    ~BadString()
    {
        std::cout << "析构:\"" << str << "\"" << std::endl;
        delete[] str;
    }

    void show() const { std::cout << str << std::endl; }
};

void demo()
{
    BadString s1("Hello");
    BadString s2 = s1;   // ⚠️ 默认拷贝构造:浅拷贝!
    // s1.str 和 s2.str 指向同一块内存!
    // 函数结束时,s2先析构释放内存,s1再析构时重复释放 → 崩溃!
}

int main()
{
    // demo();   // 会崩溃!注释掉以避免运行时错误
    std::cout << "默认拷贝构造函数的浅拷贝问题演示" << std::endl;
    return 0;
}

问题根源:

复制代码
浅拷贝(默认行为):
s1: str ──→ "Hello"
s2: str ──→ "Hello"  ← 指向同一块内存!

深拷贝(正确做法):
s1: str ──→ "Hello"(内存块1)
s2: str ──→ "Hello"(内存块2,独立副本)

12.2 特殊成员函数

C++ 会自动生成以下特殊成员函数(如果用户未定义):

特殊函数 自动生成条件 默认行为
默认构造函数 未定义任何构造函数 什么都不做
拷贝构造函数 未定义 浅拷贝(逐成员复制)
赋值运算符 未定义 浅拷贝(逐成员赋值)
析构函数 未定义 什么都不做
移动构造函数 C++11,特定条件 移动资源
移动赋值运算符 C++11,特定条件 移动资源

⚠️ 含有指针成员的类 :默认的浅拷贝会导致多个对象共享同一块内存,析构时重复释放,引发崩溃。必须自定义拷贝构造函数和赋值运算符。


12.3 正确实现动态内存类

12.3.1 完整的 StringBad → String 类

cpp 复制代码
// string_class.h -- 正确的动态字符串类头文件
#ifndef STRING_CLASS_H_
#define STRING_CLASS_H_

#include <iostream>

class String
{
private:
    char* str;          // 指向动态分配的字符串
    int   len;          // 字符串长度
    static int numStrings;  // 静态成员:记录对象数量

public:
    // ===== 构造函数 =====
    String(const char* s = "");     // 普通构造
    String(const String& s);        // 拷贝构造(深拷贝)
    ~String();                      // 析构函数

    // ===== 运算符重载 =====
    String& operator=(const String& s);    // 赋值运算符
    String& operator=(const char* s);      // 从C字符串赋值
    char&   operator[](int i);             // 下标(可写)
    const char& operator[](int i) const;   // 下标(只读)

    // ===== 友元函数 =====
    friend bool operator<(const String& s1, const String& s2);
    friend bool operator>(const String& s1, const String& s2);
    friend bool operator==(const String& s1, const String& s2);
    friend std::ostream& operator<<(std::ostream& os, const String& s);
    friend std::istream& operator>>(std::istream& is, String& s);

    // ===== 访问器 =====
    int  length() const { return len; }
    static int howMany() { return numStrings; }
};

#endif
cpp 复制代码
// string_class.cpp -- String类的实现
#include <iostream>
#include <cstring>
#include "string_class.h"

// 静态成员在类外初始化
int String::numStrings = 0;

// ===== 构造函数 =====
String::String(const char* s)
{
    len = strlen(s);
    str = new char[len + 1];
    strcpy(str, s);
    numStrings++;
    std::cout << "[构造] \"" << str
              << "\" 当前对象数:" << numStrings << std::endl;
}

// 拷贝构造函数(深拷贝)
String::String(const String& s)
{
    len = s.len;
    str = new char[len + 1];   // 分配新内存
    strcpy(str, s.str);        // 复制内容
    numStrings++;
    std::cout << "[拷贝构造] \"" << str
              << "\" 当前对象数:" << numStrings << std::endl;
}

// 析构函数
String::~String()
{
    std::cout << "[析构] \"" << str
              << "\" 当前对象数:" << numStrings - 1 << std::endl;
    --numStrings;
    delete[] str;   // 释放动态内存
}

// ===== 赋值运算符 =====
String& String::operator=(const String& s)
{
    std::cout << "[赋值] \"" << s.str << "\"" << std::endl;
    if (this == &s)          // 防止自赋值
        return *this;
    delete[] str;            // 释放旧内存
    len = s.len;
    str = new char[len + 1]; // 分配新内存
    strcpy(str, s.str);      // 深拷贝
    return *this;
}

// 从C字符串赋值
String& String::operator=(const char* s)
{
    delete[] str;
    len = strlen(s);
    str = new char[len + 1];
    strcpy(str, s);
    return *this;
}

// 下标运算符(可写)
char& String::operator[](int i)
{
    return str[i];
}

// 下标运算符(只读)
const char& String::operator[](int i) const
{
    return str[i];
}

// ===== 友元函数 =====
bool operator<(const String& s1, const String& s2)
{
    return strcmp(s1.str, s2.str) < 0;
}

bool operator>(const String& s1, const String& s2)
{
    return s2 < s1;
}

bool operator==(const String& s1, const String& s2)
{
    return strcmp(s1.str, s2.str) == 0;
}

std::ostream& operator<<(std::ostream& os, const String& s)
{
    os << s.str;
    return os;
}

std::istream& operator>>(std::istream& is, String& s)
{
    char temp[80];
    is >> temp;
    s = temp;   // 调用赋值运算符
    return is;
}

12.3.2 使用 String 类

cpp 复制代码
// use_string.cpp -- 使用String类
#include <iostream>
#include "string_class.h"

int main()
{
    using namespace std;

    cout << "===== 创建对象 =====" << endl;
    String s1("Hello");
    String s2("World");
    String s3 = s1;   // 拷贝构造

    cout << "\n===== 输出 =====" << endl;
    cout << "s1 = " << s1 << endl;
    cout << "s2 = " << s2 << endl;
    cout << "s3 = " << s3 << endl;

    cout << "\n===== 赋值 =====" << endl;
    s3 = s2;   // 赋值运算符
    cout << "s3 = s2 后:" << s3 << endl;

    cout << "\n===== 比较 =====" << endl;
    cout << boolalpha;
    cout << "s1 < s2 : " << (s1 < s2) << endl;
    cout << "s1 == s3: " << (s1 == s3) << endl;

    cout << "\n===== 下标访问 =====" << endl;
    cout << "s1[0] = " << s1[0] << endl;
    s1[0] = 'h';   // 修改字符
    cout << "修改后 s1 = " << s1 << endl;

    cout << "\n===== 对象数量 =====" << endl;
    cout << "当前String对象数:" << String::howMany() << endl;

    cout << "\n===== 程序结束 =====" << endl;
    return 0;
}

12.4 静态类成员

12.4.1 静态数据成员

cpp 复制代码
// static_member.cpp -- 静态成员详解
#include <iostream>
#include <string>

class Employee
{
private:
    std::string name;
    int         id;
    double      salary;

    // 静态数据成员:所有对象共享,不属于某个对象
    static int    totalEmployees;   // 员工总数
    static double totalSalary;      // 工资总额
    static int    nextId;           // 下一个ID

public:
    Employee(const std::string& n, double s)
        : name(n), salary(s)
    {
        id = nextId++;
        totalEmployees++;
        totalSalary += salary;
        std::cout << "雇用:" << name << "(ID:" << id << ")" << std::endl;
    }

    ~Employee()
    {
        totalEmployees--;
        totalSalary -= salary;
        std::cout << "离职:" << name << std::endl;
    }

    void show() const
    {
        std::cout << "ID:" << id
                  << " 姓名:" << name
                  << " 薪资:" << salary << std::endl;
    }

    // 静态成员函数:只能访问静态成员
    static void showStats()
    {
        std::cout << "员工总数:" << totalEmployees << std::endl;
        std::cout << "工资总额:" << totalSalary << std::endl;
        if (totalEmployees > 0)
            std::cout << "平均工资:" << totalSalary / totalEmployees << std::endl;
    }

    // 修改薪资
    void setSalary(double s)
    {
        totalSalary -= salary;
        salary = s;
        totalSalary += salary;
    }
};

// 静态成员在类外初始化(必须!)
int    Employee::totalEmployees = 0;
double Employee::totalSalary    = 0.0;
int    Employee::nextId         = 1001;

int main()
{
    using namespace std;

    cout << "===== 初始状态 =====" << endl;
    Employee::showStats();

    cout << "\n===== 雇用员工 =====" << endl;
    Employee e1("张三", 8000);
    Employee e2("李四", 12000);
    Employee e3("王五", 9500);

    cout << "\n===== 员工信息 =====" << endl;
    e1.show(); e2.show(); e3.show();

    cout << "\n===== 统计信息 =====" << endl;
    Employee::showStats();   // 通过类名访问静态函数

    cout << "\n===== 调薪后 =====" << endl;
    e1.setSalary(10000);
    Employee::showStats();

    cout << "\n===== 员工离职 =====" << endl;
    // e2 在这里离开作用域(模拟)
    {
        Employee temp("临时工", 5000);
        temp.show();
    }   // temp析构
    Employee::showStats();

    return 0;
}

12.5 移动语义(C++11)

12.5.1 移动构造函数

cpp 复制代码
// move_semantics.cpp -- 移动语义示例
#include <iostream>
#include <cstring>

class Buffer
{
private:
    char* data;
    int   size;

public:
    // 普通构造
    Buffer(int sz, char fill = 'A')
        : size(sz)
    {
        data = new char[size + 1];
        memset(data, fill, size);
        data[size] = '\0';
        std::cout << "[构造] size=" << size << std::endl;
    }

    // 拷贝构造(深拷贝)
    Buffer(const Buffer& other)
        : size(other.size)
    {
        data = new char[size + 1];
        strcpy(data, other.data);
        std::cout << "[拷贝构造] size=" << size << std::endl;
    }

    // 移动构造(C++11):转移资源所有权,不复制
    Buffer(Buffer&& other) noexcept
        : data(other.data), size(other.size)
    {
        other.data = nullptr;   // 原对象放弃所有权
        other.size = 0;
        std::cout << "[移动构造] size=" << size << std::endl;
    }

    // 赋值运算符(深拷贝)
    Buffer& operator=(const Buffer& other)
    {
        if (this == &other) return *this;
        delete[] data;
        size = other.size;
        data = new char[size + 1];
        strcpy(data, other.data);
        std::cout << "[拷贝赋值] size=" << size << std::endl;
        return *this;
    }

    // 移动赋值运算符(C++11)
    Buffer& operator=(Buffer&& other) noexcept
    {
        if (this == &other) return *this;
        delete[] data;          // 释放自己的资源
        data = other.data;      // 接管对方的资源
        size = other.size;
        other.data = nullptr;   // 对方放弃所有权
        other.size = 0;
        std::cout << "[移动赋值] size=" << size << std::endl;
        return *this;
    }

    ~Buffer()
    {
        std::cout << "[析构] size=" << size << std::endl;
        delete[] data;
    }

    void show() const
    {
        if (data)
            std::cout << "Buffer[" << size << "]: " << data << std::endl;
        else
            std::cout << "Buffer[空]" << std::endl;
    }
};

// 返回临时对象(触发移动语义)
Buffer makeBuffer(int size)
{
    return Buffer(size, 'X');   // 返回时触发移动构造
}

int main()
{
    using namespace std;

    cout << "===== 拷贝构造 =====" << endl;
    Buffer b1(5, 'A');
    Buffer b2 = b1;   // 拷贝构造
    b1.show();
    b2.show();

    cout << "\n===== 移动构造 =====" << endl;
    Buffer b3 = makeBuffer(8);   // 移动构造(从临时对象)
    b3.show();

    cout << "\n===== 显式移动 =====" << endl;
    Buffer b4(3, 'B');
    Buffer b5 = std::move(b4);   // 显式移动
    cout << "b4(移动后):"; b4.show();
    cout << "b5(接收后):"; b5.show();

    cout << "\n===== 程序结束 =====" << endl;
    return 0;
}

💡 移动语义的意义

  • 拷贝:分配新内存 + 复制数据,开销大
  • 移动:转移指针所有权,不复制数据,开销极小
  • 适用于临时对象(右值)的传递,显著提升性能

12.6 new 和 delete 的使用规则

cpp 复制代码
// new_delete_rules.cpp -- new/delete使用规则
#include <iostream>

class MyClass
{
public:
    int value;
    MyClass(int v = 0) : value(v)
    {
        std::cout << "构造 MyClass(" << value << ")" << std::endl;
    }
    ~MyClass()
    {
        std::cout << "析构 MyClass(" << value << ")" << std::endl;
    }
};

int main()
{
    using namespace std;

    // 规则1:new 对应 delete
    int* p1 = new int(42);
    cout << "*p1 = " << *p1 << endl;
    delete p1;
    p1 = nullptr;   // 释放后置nullptr

    // 规则2:new[] 对应 delete[]
    int* arr = new int[5]{1, 2, 3, 4, 5};
    for (int i = 0; i < 5; i++)
        cout << arr[i] << " ";
    cout << endl;
    delete[] arr;   // 必须用delete[]!
    arr = nullptr;

    // 规则3:对象的new/delete会调用构造/析构函数
    cout << "\n--- 对象的new/delete ---" << endl;
    MyClass* obj = new MyClass(100);
    cout << "obj->value = " << obj->value << endl;
    delete obj;   // 调用析构函数

    // 规则4:对象数组
    cout << "\n--- 对象数组 ---" << endl;
    MyClass* objs = new MyClass[3];   // 调用3次默认构造
    objs[0].value = 10;
    objs[1].value = 20;
    objs[2].value = 30;
    delete[] objs;   // 调用3次析构函数

    // 规则5:定位new(placement new)
    cout << "\n--- 定位new ---" << endl;
    char buffer[sizeof(MyClass)];
    MyClass* placed = new (buffer) MyClass(999);   // 在buffer上构造
    cout << "placed->value = " << placed->value << endl;
    placed->~MyClass();   // 必须手动调用析构函数!
    // 不需要delete,因为内存不是new分配的

    return 0;
}

12.7 智能指针简介(C++11)

cpp 复制代码
// smart_pointer.cpp -- 智能指针简介
#include <iostream>
#include <memory>   // 智能指针头文件
#include <string>

class Resource
{
public:
    std::string name;
    Resource(const std::string& n) : name(n)
    {
        std::cout << "[创建] " << name << std::endl;
    }
    ~Resource()
    {
        std::cout << "[销毁] " << name << std::endl;
    }
    void use() const
    {
        std::cout << "[使用] " << name << std::endl;
    }
};

int main()
{
    using namespace std;

    // ===== unique_ptr:独占所有权 =====
    cout << "--- unique_ptr ---" << endl;
    {
        unique_ptr<Resource> up1 = make_unique<Resource>("资源A");
        up1->use();
        // up1 离开作用域时自动释放,无需delete!

        // 转移所有权
        unique_ptr<Resource> up2 = move(up1);
        // up1 现在为空
        if (!up1)
            cout << "up1 已转移" << endl;
        up2->use();
    }   // up2 在这里自动析构
    cout << "unique_ptr 作用域结束" << endl;

    // ===== shared_ptr:共享所有权 =====
    cout << "\n--- shared_ptr ---" << endl;
    {
        shared_ptr<Resource> sp1 = make_shared<Resource>("资源B");
        cout << "引用计数:" << sp1.use_count() << endl;   // 1

        {
            shared_ptr<Resource> sp2 = sp1;   // 共享
            cout << "引用计数:" << sp1.use_count() << endl;   // 2
            sp2->use();
        }   // sp2 离开作用域,引用计数-1
        cout << "引用计数:" << sp1.use_count() << endl;   // 1
    }   // sp1 离开作用域,引用计数=0,自动释放
    cout << "shared_ptr 作用域结束" << endl;

    // ===== weak_ptr:弱引用(不增加引用计数)=====
    cout << "\n--- weak_ptr ---" << endl;
    shared_ptr<Resource> sp = make_shared<Resource>("资源C");
    weak_ptr<Resource>   wp = sp;   // 弱引用,不增加计数
    cout << "引用计数:" << sp.use_count() << endl;   // 1(wp不计入)

    if (auto locked = wp.lock())   // 尝试获取shared_ptr
        locked->use();

    return 0;
}

三种智能指针对比:

智能指针 所有权 引用计数 适用场景
unique_ptr 独占 单一所有者,性能最好
shared_ptr 共享 多个所有者共享资源
weak_ptr 不增加 打破循环引用,观察者模式

12.8 综合示例:完整的动态数组类

cpp 复制代码
// dynamic_array.cpp -- 综合示例:动态数组类
#include <iostream>
#include <stdexcept>
#include <algorithm>

template <typename T>
class DynamicArray
{
private:
    T*   data;
    int  size;      // 当前元素数量
    int  capacity;  // 当前容量

    // 扩容
    void resize(int newCapacity)
    {
        T* newData = new T[newCapacity];
        for (int i = 0; i < size; i++)
            newData[i] = std::move(data[i]);
        delete[] data;
        data     = newData;
        capacity = newCapacity;
        std::cout << "[扩容] 新容量:" << capacity << std::endl;
    }

public:
    // 构造函数
    explicit DynamicArray(int cap = 4)
        : size(0), capacity(cap)
    {
        data = new T[capacity];
    }

    // 拷贝构造(深拷贝)
    DynamicArray(const DynamicArray& other)
        : size(other.size), capacity(other.capacity)
    {
        data = new T[capacity];
        for (int i = 0; i < size; i++)
            data[i] = other.data[i];
    }

    // 移动构造
    DynamicArray(DynamicArray&& other) noexcept
        : data(other.data), size(other.size), capacity(other.capacity)
    {
        other.data     = nullptr;
        other.size     = 0;
        other.capacity = 0;
    }

    // 析构函数
    ~DynamicArray()
    {
        delete[] data;
    }

    // 赋值运算符
    DynamicArray& operator=(const DynamicArray& other)
    {
        if (this == &other) return *this;
        delete[] data;
        size     = other.size;
        capacity = other.capacity;
        data     = new T[capacity];
        for (int i = 0; i < size; i++)
            data[i] = other.data[i];
        return *this;
    }

    // 移动赋值
    DynamicArray& operator=(DynamicArray&& other) noexcept
    {
        if (this == &other) return *this;
        delete[] data;
        data           = other.data;
        size           = other.size;
        capacity       = other.capacity;
        other.data     = nullptr;
        other.size     = 0;
        other.capacity = 0;
        return *this;
    }

    // 添加元素
    void push_back(const T& val)
    {
        if (size == capacity)
            resize(capacity * 2);
        data[size++] = val;
    }

    // 删除末尾元素
    void pop_back()
    {
        if (size == 0)
            throw std::underflow_error("数组为空");
        --size;
    }

    // 下标访问
    T& operator[](int i)
    {
        if (i < 0 || i >= size)
            throw std::out_of_range("索引越界");
        return data[i];
    }

    const T& operator[](int i) const
    {
        if (i < 0 || i >= size)
            throw std::out_of_range("索引越界");
        return data[i];
    }

    // 查询
    int  getSize()     const { return size; }
    int  getCapacity() const { return capacity; }
    bool empty()       const { return size == 0; }

    // 输出
    friend std::ostream& operator<<(std::ostream& os,
                                    const DynamicArray& arr)
    {
        os << "[";
        for (int i = 0; i < arr.size; i++)
        {
            os << arr.data[i];
            if (i < arr.size - 1) os << ", ";
        }
        os << "]";
        return os;
    }
};

int main()
{
    using namespace std;

    DynamicArray<int> arr;

    cout << "===== 添加元素 =====" << endl;
    for (int i = 1; i <= 10; i++)
        arr.push_back(i * 10);

    cout << "数组:" << arr << endl;
    cout << "大小:" << arr.getSize()
         << " 容量:" << arr.getCapacity() << endl;

    cout << "\n===== 访问元素 =====" << endl;
    cout << "arr[0] = " << arr[0] << endl;
    cout << "arr[9] = " << arr[9] << endl;
    arr[0] = 999;
    cout << "修改后:" << arr << endl;

    cout << "\n===== 拷贝 =====" << endl;
    DynamicArray<int> arr2 = arr;   // 深拷贝
    arr2[0] = 111;
    cout << "arr  = " << arr  << endl;   // 未受影响
    cout << "arr2 = " << arr2 << endl;

    cout << "\n===== 移动 =====" << endl;
    DynamicArray<int> arr3 = move(arr2);
    cout << "arr3 = " << arr3 << endl;
    cout << "arr2 大小:" << arr2.getSize() << endl;   // 0

    cout << "\n===== 字符串数组 =====" << endl;
    DynamicArray<string> strArr;
    strArr.push_back("Hello");
    strArr.push_back("World");
    strArr.push_back("C++");
    cout << "字符串数组:" << strArr << endl;

    return 0;
}

输出(部分):

复制代码
===== 添加元素 =====
[扩容] 新容量:8
[扩容] 新容量:16
数组:[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
大小:10 容量:16

===== 访问元素 =====
arr[0] = 10
arr[9] = 100
修改后:[999, 20, 30, 40, 50, 60, 70, 80, 90, 100]

===== 拷贝 =====
arr  = [999, 20, 30, 40, 50, 60, 70, 80, 90, 100]
arr2 = [111, 20, 30, 40, 50, 60, 70, 80, 90, 100]
...

📝 第12章知识点总结

知识点 核心要点
动态内存问题 默认浅拷贝导致多个对象共享内存,析构时重复释放崩溃
三/五法则 含指针成员时,必须自定义:析构函数、拷贝构造、赋值运算符(C++11加移动构造和移动赋值)
深拷贝 分配新内存 + 复制内容,两个对象完全独立
赋值运算符 防自赋值 → 释放旧内存 → 深拷贝 → 返回 *this
静态成员变量 所有对象共享,类外初始化,用 类名:: 访问
静态成员函数 只能访问静态成员,无 this 指针,用 类名:: 调用
移动构造函数 类名(类名&& other),转移指针所有权,原对象置 nullptr
移动赋值运算符 释放自身资源 → 接管对方资源 → 对方置空 → 返回 *this
std::move 将左值转为右值引用,触发移动语义
unique_ptr 独占所有权,离开作用域自动释放,不可复制只可移动
shared_ptr 共享所有权,引用计数为0时自动释放
weak_ptr 弱引用,不增加引用计数,用于打破循环引用
定位new new (地址) 类型,在指定内存上构造,需手动调用析构
相关推荐
星卯教育tony1 小时前
CIE中国电子学会2026年3月c++ Python scratch 机器人真题试卷含参考答案
c++·python·scratch·电子学会
阿里嘎多学长2 小时前
2026-05-30 GitHub 热点项目精选
开发语言·程序员·github·代码托管
wapicn992 小时前
API接口调试笔记:从注册到第一个数据返回,全流程详解
java·开发语言·python·lua
.千余2 小时前
【Linux】 TCP进阶详解:字节流、粘包问题、异常情况与UDP完整对比2
linux·运维·c语言·开发语言·经验分享·笔记·php
geovindu2 小时前
python: Bounded Parallelism Pattern
开发语言·python·设计模式·有界并行模式
大明者省2 小时前
Ubuntu Python 部署终极版教程
开发语言·python·ubuntu
光影少年2 小时前
Redux Toolkit 用法、解决原生Redux 冗余问题
开发语言·前端·javascript·react.js·中间件·前端框架·ecmascript
KANGBboy2 小时前
java知识二(数组)
java·开发语言·python
零陵上将军_xdr2 小时前
后端转全栈学习-Day3-JavaScript 基础-1
开发语言·javascript·学习