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 (地址) 类型,在指定内存上构造,需手动调用析构 |