智能指针
今天我们来学习一下C++中的智能指针,如果有人不知道C++中的智能指针的概念的话:
C++智能指针是一种基于RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制的高级内存管理工具,用于自动化动态内存的分配与释放,从而避免内存泄漏、悬空指针等问题。其核心思想是将资源(如堆内存)的生命周期绑定到对象的生命周期上——对象构造时获取资源,析构时自动释放资源
目前主流的智能指针包含两种:独占式指针和共享式指针。
独占式指针
什么是独占式指针?
我们来看一个简化版的实现:
template <typename T, typename Deleter = std::default_delete<T>>
class unique_ptr {
private:
T* ptr = nullptr; // 管理的裸指针
Deleter deleter; // 删除器(默认为 std::default_delete)
public:
// 1. 构造与析构
explicit unique_ptr(T* p = nullptr) noexcept : ptr(p) {}
~unique_ptr() noexcept {
if (ptr) deleter(ptr); // 自动调用删除器
}
// 2. 禁用拷贝
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
// 3. 允许移动
unique_ptr(unique_ptr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr; // 转移后置空原指针
}
unique_ptr& operator=(unique_ptr&& other) noexcept {
if (this != &other) {
reset(); // 释放当前资源
ptr = other.ptr;
other.ptr = nullptr; // 置空原指针
}
return *this;
}
// 4. 关键接口
T* get() const noexcept { return ptr; }
T& operator*() const noexcept { return *ptr; }
T* operator->() const noexcept { return ptr; }
explicit operator bool() const noexcept { return ptr != nullptr; }
void reset(T* p = nullptr) noexcept {
if (ptr) deleter(ptr); // 释放旧资源
ptr = p; // 接管新资源
}
T* release() noexcept {
T* old_ptr = ptr;
ptr = nullptr;
return old_ptr; // 放弃所有权,返回裸指针
}
};
我们来一点一点介绍:
template <typename T, typename Deleter = std::default_delete<T>>
这是模板的定义,智能指针本质上是一个封装了指针的类模板。typename T表明泛型,同时定义一个默认类型为std::default_delete<T>的名为Deleter的模板参数来作为删除器。
然后我们定义好指针和删除器:
private:
T* ptr = nullptr; // 管理的裸指针
Deleter deleter; // 删除器(默认为 std::default_delete)
构造函数和析构函数:
// 1. 构造与析构
explicit unique_ptr(T* p = nullptr) noexcept : ptr(p) {}
~unique_ptr() noexcept {
if (ptr) deleter(ptr); // 自动调用删除器
}
这里的explicit和noexcept关键字的作用:禁止隐式类型转换,强制要求显式构造对象或类型转换,避免意外行为;声明函数不抛出异常,优化性能并提升可靠性;
当我们调用析构函数后,如果检测到独占式指针存在就删除掉这个指针。
// 2. 禁用拷贝
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
这是我们的拷贝构造函数和赋值运算符重载,我们都修改为delete。
一般的拷贝构造函数和赋值运算符重载的格式如下:
ClassName(const ClassName& other);
...
ClassName& operator=(const ClassName& other);
可以看到参数列表中的内容格式是固定的。
// 3. 允许移动
unique_ptr(unique_ptr&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr; // 转移后置空原指针
}
unique_ptr& operator=(unique_ptr&& other) noexcept {
if (this != &other) {
reset(); // 释放当前资源
ptr = other.ptr;
other.ptr = nullptr; // 置空原指针
}
return *this;
}
这是我们的移动语义的内容,如果参数是右值则将当前资源转移到另一个指针。
// 4. 关键接口
T* get() const noexcept { return ptr; }
T& operator*() const noexcept { return *ptr; }
T* operator->() const noexcept { return ptr; }
explicit operator bool() const noexcept { return ptr != nullptr; }
这是一系列的接口实现,包括get方法获取指针,重载*和->模拟指针操作,
void reset(T* p = nullptr) noexcept {
if (ptr) deleter(ptr); // 释放旧资源
ptr = p; // 接管新资源
}
T* release() noexcept {
T* old_ptr = ptr;
ptr = nullptr;
return old_ptr; // 放弃所有权,返回裸指针
}
这个是转移指针指向资源的所有权和释放内存的实现。
接下来我们加入一段测试代码:
// 自定义测试类,通过构造函数/析构函数打印验证生命周期
class TestResource {
public:
explicit TestResource(int id) : id(id) {
std::cout << "TestResource #" << id << " created\n";
}
~TestResource() {
std::cout << "TestResource #" << id << " destroyed\n";
}
void print() const {
std::cout << "Accessing resource #" << id << "\n";
}
private:
int id;
};
//
......
//
int main() {
std::cout << "\n=== 测试1: 基础功能与自动释放 ===" << std::endl;
{
unique_ptr<TestResource> p1(new TestResource(1)); // 创建资源 #1
// 验证访问功能
assert(p1 && "指针应为非空");
p1->print(); // 通过 -> 访问
(*p1).print(); // 通过 * 访问
std::cout << "离开作用域,应自动释放资源..." << std::endl;
} // p1 在此析构,资源 #1 应被自动释放
std::cout << "\n=== 测试2: 移动语义 ===" << std::endl;
{
unique_ptr<TestResource> p1(new TestResource(2));
unique_ptr<TestResource> p2 = std::move(p1); // 移动构造
assert(!p1 && "移动后原指针应为空");
assert(p2 && "新指针应接管资源");
std::cout << "资源所有权已转移至 p2" << std::endl;
unique_ptr<TestResource> p3;
p3 = std::move(p2); // 移动赋值
assert(!p2 && "移动赋值后源指针应为空");
assert(p3 && "目标指针应接管资源");
std::cout << "资源所有权再次转移至 p3" << std::endl;
} // p3 析构时释放资源 #2
std::cout << "\n=== 测试3: reset() 与 release() ===" << std::endl;
{
unique_ptr<TestResource> p(new TestResource(3));
// 测试 reset()
p.reset(new TestResource(4)); // 释放旧资源 #3,接管新资源 #4
p->print(); // 应访问资源 #4
// 测试 release()
TestResource* raw_ptr = p.release();
assert(!p && "release()后智能指针应为空");
std::cout << "已释放所有权,手动管理资源..." << std::endl;
delete raw_ptr; // 需手动释放
} // p 析构时不释放资源(已release)
std::cout << "\n=== 测试4: 禁止拷贝(编译时验证)===" << std::endl;
{
unique_ptr<TestResource> p1(new TestResource(5));
// unique_ptr<TestResource> p2 = p1; // 应产生编译错误:尝试拷贝构造
// unique_ptr<TestResource> p3;
// p3 = p1; // 应产生编译错误:尝试拷贝赋值
std::cout << "拷贝操作被正确禁止(未使用注释代码时正常编译)" << std::endl;
}
std::cout << "\n=== 测试5: 布尔转换验证 ===" << std::endl;
{
unique_ptr<TestResource> p1(new TestResource(6));
unique_ptr<TestResource> p2;
if (p1) {
std::cout << "p1 有效(布尔转换正确)" << std::endl;
}
if (!p2) {
std::cout << "p2 无效(布尔转换正确)" << std::endl;
}
}
std::cout << "\n所有测试通过!" << std::endl;
return 0;
}
整个测试的逻辑如下:
如果有人不知道assert的作用的话:assert
(断言)是一种在代码中嵌入检查点的调试机制,用于在运行时或编译时验证程序逻辑的假设条件是否成立。
结果输出如下:
共享式指针
然后是我们的共享式指针。
template <typename T>
class SharedPtr {
private:
T* ptr = nullptr; // 指向动态资源的指针
std::atomic<size_t>* ref_count = nullptr; // 原子引用计数器
// 释放资源并更新引用计数
void release() noexcept {
if (!ref_count) return;
// 原子减少计数,若归零则销毁资源
if (--(*ref_count) == 0) {
delete ptr; // 释放对象
delete ref_count; // 释放计数器
ptr = nullptr;
ref_count = nullptr;
}
}
public:
// === 构造函数 ===
SharedPtr() noexcept = default; // 默认构造(空指针)
// 从原始指针构造(独占资源)
explicit SharedPtr(T* raw_ptr)
: ptr(raw_ptr), ref_count(new std::atomic<size_t>(1)) {}
// 拷贝构造(共享所有权)
SharedPtr(const SharedPtr& other) noexcept
: ptr(other.ptr), ref_count(other.ref_count) {
if (ref_count) (*ref_count)++; // 引用计数增加
}
// 移动构造(转移所有权)
SharedPtr(SharedPtr&& other) noexcept
: ptr(other.ptr), ref_count(other.ref_count) {
other.ptr = nullptr;
other.ref_count = nullptr;
}
// === 析构函数 ===
~SharedPtr() { release(); }
// === 赋值运算符 ===
// 拷贝赋值
SharedPtr& operator=(const SharedPtr& other) noexcept {
if (this != &other) {
release(); // 释放当前资源
ptr = other.ptr; // 共享资源
ref_count = other.ref_count;
if (ref_count) (*ref_count)++;
}
return *this;
}
// 移动赋值
SharedPtr& operator=(SharedPtr&& other) noexcept {
if (this != &other) {
release(); // 释放当前资源
ptr = other.ptr; // 接管资源
ref_count = other.ref_count;
other.ptr = nullptr;
other.ref_count = nullptr;
}
return *this;
}
// === 访问操作 ===
T& operator*() const noexcept { return *ptr; }
T* operator->() const noexcept { return ptr; }
explicit operator bool() const noexcept { return ptr != nullptr; }
// === 工具函数 ===
size_t use_count() const noexcept {
return ref_count ? ref_count->load() : 0;
}
T* get() const noexcept { return ptr; }
// 重置指针(可接管新资源)
void reset(T* new_ptr = nullptr) noexcept {
release(); // 释放旧资源
if (new_ptr) {
ptr = new_ptr;
ref_count = new std::atomic<size_t>(1); // 新计数器
}
}
};
可以看到共享式指针就要复杂得多。
T* ptr = nullptr; // 指向动态资源的指针
std::atomic<size_t>* ref_count = nullptr; // 原子引用计数器
这里涉及到了原子操作:这行代码定义了一个指向原子引用计数器的指针。
// 释放资源并更新引用计数
void release() noexcept {
if (!ref_count) return;
// 原子减少计数,若归零则销毁资源
if (--(*ref_count) == 0) {
delete ptr; // 释放对象
delete ref_count; // 释放计数器
ptr = nullptr;
ref_count = nullptr;
}
}
负责检查计数并释放资源。
// === 构造函数 ===
SharedPtr() noexcept = default; // 默认构造(空指针)
// 从原始指针构造(独占资源)
explicit SharedPtr(T* raw_ptr)
: ptr(raw_ptr), ref_count(new std::atomic<size_t>(1)) {}
// 拷贝构造(共享所有权)
SharedPtr(const SharedPtr& other) noexcept
: ptr(other.ptr), ref_count(other.ref_count) {
if (ref_count) (*ref_count)++; // 引用计数增加
}
// 移动构造(转移所有权)
SharedPtr(SharedPtr&& other) noexcept
: ptr(other.ptr), ref_count(other.ref_count) {
other.ptr = nullptr;
other.ref_count = nullptr;
}
构造函数,可以看到实现了拷贝和移动的构造函数。
// === 析构函数 ===
~SharedPtr() { release(); }
析构函数——直接调用我们写好的释放内存的函数即可。
剩下的内容和独占式的大差不差,不再赘述。
加入以下测试代码后:
// 测试类
class TestObject {
public:
TestObject(int id) : id(id) {
std::cout << "TestObject[" << id << "] created\n";
}
~TestObject() {
std::cout << "TestObject[" << id << "] destroyed\n";
}
void log() const {
std::cout << "Accessing object " << id << "\n";
}
private:
int id;
};
// === 测试代码 ===
int main() {
// 测试1:基础构造与析构
std::cout << "=== Test 1: Basic Lifecycle ===\n";
{
SharedPtr<TestObject> p1(new TestObject(1));
std::cout << "p1 use_count: " << p1.use_count() << "\n"; // 1
} // 自动销毁
// 测试2:拷贝语义
std::cout << "\n=== Test 2: Copy Semantics ===\n";
{
SharedPtr<TestObject> p1(new TestObject(2));
auto p2 = p1; // 拷贝构造
p1->log(); // 访问对象
std::cout << "p1/p2 use_count: "
<< p1.use_count() << "/" << p2.use_count() << "\n"; // 2/2
SharedPtr<TestObject> p3;
p3 = p2; // 拷贝赋值
std::cout << "p1/p2/p3 use_count: "
<< p1.use_count() << "/" << p2.use_count()
<< "/" << p3.use_count() << "\n"; // 3/3/3
} // 所有指针离开作用域,对象销毁
// 测试3:移动语义
std::cout << "\n=== Test 3: Move Semantics ===\n";
{
SharedPtr<TestObject> p1(new TestObject(3));
auto p2 = std::move(p1); // 移动构造
std::cout << "p1 valid? " << (p1 ? "yes" : "no") << "\n"; // no
std::cout << "p2 use_count: " << p2.use_count() << "\n"; // 1
SharedPtr<TestObject> p3;
p3 = std::move(p2); // 移动赋值
std::cout << "p2 valid? " << (p2 ? "yes" : "no") << "\n"; // no
p3->log();
}
// 测试4:reset() 和线程安全
std::cout << "\n=== Test 4: reset() & Thread Safety ===\n";
{
SharedPtr<TestObject> p1(new TestObject(4));
p1.reset(new TestObject(5)); // 重置(先销毁4,再接管5)
std::cout << "p1 use_count: " << p1.use_count() << "\n"; // 1
}
std::cout << "\nAll tests passed!\n";
return 0;
}
单例模式
什么是单例模式?
单例模式(Singleton Pattern)是一种创建型设计模式,其核心目的是确保一个类在整个系统中仅有一个实例,并提供该实例的全局访问点,从而避免重复创建对象造成的资源浪费或状态不一致问题。
#include <iostream>
#include <mutex>
#include <atomic>
class Singleton {
public:
// 禁用拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 获取单例实例
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mutex);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
void log() { std::cout << "Singleton in use\n"; }
private:
// 私有构造函数
Singleton() { std::cout << "Singleton created\n"; }
~Singleton() = default;
// 静态成员
static std::atomic<Singleton*> instance;
static std::mutex mutex;
};
// 初始化静态成员
std::atomic<Singleton*> Singleton::instance(nullptr);
std::mutex Singleton::mutex;
手撕单例最核心的部分就是去彻底禁用拷贝和赋值(类似独占式指针)。
// 禁用拷贝和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
这是实时获取单例实例的方法:
// 获取单例实例
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mutex);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
这里有很多新的知识点啊:
instance是atomic<Singleton*>类的成员,这个类的含义是Singleton*类的原子变量。
然后instance分别调用了load,store两个函数:
std::lock_guard是一个自动锁管理的模板类,关于这句代码:
std::lock_guard<std::mutex> lock(mutex);
lock_guard规定了锁的管理方式,mutex是C++自带的互斥锁类,而lock是这个互斥锁类的名称,括号里的mutex则是一个已定义的 std::mutex
类型的具体对象,代表需要被管理的互斥锁。
整个流程就是基于双重检查锁定(DCLP) 的线程安全单例模式,其执行流程如下:首先,通过 instance.load(std::memory_order_acquire)
原子读取当前单例指针 tmp
,若 tmp
非空(表明实例已初始化),则直接返回实例以跳过锁开销;若 tmp
为空,则进入临界区,通过 std::lock_guard<std::mutex> lock(mutex)
锁定互斥量,确保同一时间仅一个线程执行初始化操作,并在加锁后再次调用 instance.load(std::memory_order_relaxed)
检查实例是否已被其他线程创建(避免重复初始化);若二次检查仍为空,则调用 new Singleton()
创建实例,并通过 instance.store(tmp, std::memory_order_release)
原子存储指针,其中 memory_order_release
保证对象构造完成后再更新指针,防止其他线程读到未初始化的内存;最终返回 tmp
,后续线程通过首次无锁检查即可直接获取实例。
线程池
什么是线程池?
线程池在程序启动时预先创建一定数量的线程(核心线程),并将它们置于空闲状态等待任务。当有任务提交时,线程池从池中分配一个空闲线程执行任务;任务完成后,线程不被销毁,而是返回池中等待新任务。这种机制通过维护“线程池+任务队列”实现线程的复用和任务的调度管理。
#include <iostream>
#include <vector>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
class ThreadPool {
public:
explicit ThreadPool(size_t thread_count = std::thread::hardware_concurrency())
: stop(false) {
for (size_t i = 0; i < thread_count; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{ // 临界区开始
std::unique_lock<std::mutex> lock(queue_mutex);
condition.wait(lock, [this] {
return stop || !tasks.empty();
});
// 终止条件:线程池停止且任务队列为空
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
} // 临界区结束(自动解锁)
task(); // 执行任务(在锁外执行以减少锁持有时间)
}
});
}
}
~ThreadPool() {
{ // 设置停止标志
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all(); // 唤醒所有线程
for (auto& worker : workers) {
if (worker.joinable()) worker.join(); // 等待线程结束
}
}
// 提交任务接口(支持任意可调用对象及参数)
template <typename F, typename... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {
using return_type = decltype(f(args...));
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{ // 临界区(添加任务)
std::unique_lock<std::mutex> lock(queue_mutex);
if (stop) throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task]() { (*task)(); }); // 封装为void()类型
}
condition.notify_one(); // 通知一个等待线程
return res;
}
private:
std::vector<std::thread> workers; // 工作线程集合
std::queue<std::function<void()>> tasks; // 任务队列
std::mutex queue_mutex; // 队列互斥锁
std::condition_variable condition; // 条件变量(任务通知)
bool stop; // 终止标志
};
我先介绍一下这个condition_variable和future库:
private:
std::vector<std::thread> workers; // 工作线程集合
std::queue<std::function<void()>> tasks; // 任务队列
std::mutex queue_mutex; // 队列互斥锁
std::condition_variable condition; // 条件变量(任务通知)
bool stop; // 终止标志
这一系列的变量的用途:
explicit ThreadPool(size_t thread_count = std::thread::hardware_concurrency())
: stop(false) {
for (size_t i = 0; i < thread_count; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{ // 临界区开始
std::unique_lock<std::mutex> lock(queue_mutex);
condition.wait(lock, [this] {
return stop || !tasks.empty();
});
// 终止条件:线程池停止且任务队列为空
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
} // 临界区结束(自动解锁)
task(); // 执行任务(在锁外执行以减少锁持有时间)
}
});
}
}
这是线程池的构造函数,其中std::thread::hardware_concurrency()
是 C++ 标准库中用于获取硬件支持的并发线程数的静态成员函数。workers.emplace_back([this]{ ... })
中[this]{ ... }是lambda表达式,后续的{...}是线程实际执行的逻辑。while(true)则是表明线程进入循环等待任务。
std::function<void()> task声明一个无参数、无返回值的可调用对象,用于存储从队列中取出的任务;{ std::unique_lock<std::mutex> lock(queue_mutex); ... }从临界区开始, std::unique_lock
锁定互斥锁 queue_mutex
,保护共享资源(任务队列 tasks
);condition.wait(lock, [this] { return stop || !tasks.empty(); })的作用就是让线程休眠,直到满足唤醒条件:线程池需终止或者任务队列非空;若线程池已停止且任务队列为空,则线程退出循环并销毁。
~ThreadPool() {
{ // 设置停止标志
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all(); // 唤醒所有线程
for (auto& worker : workers) {
if (worker.joinable()) worker.join(); // 等待线程结束
}
}
这是线程池的析构函数,我们进入临界区把停止标志设置为true之后并唤醒所有线程之后检查线程是否可合并(即是否在运行中),并通过 join()
等待其自然退出。
为什么要判断线程是否可以合并?这里牵扯到的是线程的一些内容:
这里我补充一下关于所谓的临界区和进入临界区的概念:
在并发编程中,“进入临界区”是指一个线程成功获取了同步锁(如互斥锁、临界区对象等),开始执行受保护的共享资源访问代码的过程。
// 提交任务接口(支持任意可调用对象及参数)
template <typename F, typename... Args>
auto enqueue(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {
using return_type = decltype(f(args...));
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{ // 临界区(添加任务)
std::unique_lock<std::mutex> lock(queue_mutex);
if (stop) throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task]() { (*task)(); }); // 封装为void()类型
}
condition.notify_one(); // 通知一个等待线程
return res;
}
这段代码实现了提交任务的接口enqueue,支持任意类型任务的提交,并返回 std::future
以便异步获取结果,同时确保线程安全的任务入队和线程唤醒。其中typename... Args
声明了一个名为 Args
的类型参数包,允许模板接受多个未知类型。接受任意可调用对象及其参数,通过std::bind
和完美转发将任务与参数打包成一个无参函数,再封装进std::packaged_task
以捕获返回值类型并创建关联的std::future
;任务函数被安全地放入线程池任务队列后(此过程需加锁保护并检查线程池是否已停止),随即通过条件变量唤醒一个等待的工作线程执行任务,最终将用于异步获取任务执行结果的std::future
对象返回给调用者。
我们加入以下测试代码:
// 测试函数
void printNumber(int num) {
std::cout << "Thread " << std::this_thread::get_id()
<< ": " << num << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟耗时
}
int main() {
ThreadPool pool(4); // 创建4个工作线程
// 提交20个任务
std::vector<std::future<void>> results;
for (int i = 0; i < 20; ++i) {
results.emplace_back(
pool.enqueue(printNumber, i)
);
}
// 等待所有任务完成
for (auto&& result : results)
result.wait();
std::cout << "所有任务执行完毕!" << std::endl;
return 0;
}
输出如下:
String
想要自己实现string类,要注意的内容包含这些方面:
内存管理
- 动态分配:字符串内容需要在堆上动态分配内存
- 深拷贝:拷贝构造/赋值时必须复制内容而非指针
- 内存释放:析构函数必须释放分配的内存
- 容量管理:实现类似vector的容量(capacity)概念以减少重新分配
基本功能实现
可以看到功能需求还是非常多的。
#include <cstring> // 用于字符串操作函数
class MyString {
private:
char* m_data; // 实际存储字符串数据的指针
size_t m_size; // 当前字符串长度(不含结尾'\0')
size_t m_cap; // 当前分配的容量(含结尾'\0')
// 辅助函数:确保有足够的容量
void ensure_capacity(size_t new_size) {
if (new_size < m_cap) return;
// 加倍策略扩容(避免频繁扩容)
size_t new_cap = (new_size > m_cap * 2) ? new_size + 1 : m_cap * 2;
// 分配新内存并复制内容
char* new_data = new char[new_cap];
if (m_data) {
std::strncpy(new_data, m_data, m_size);
delete[] m_data; // 释放旧内存
}
m_data = new_data;
m_cap = new_cap;
}
public:
// 默认构造函数:创建空字符串
MyString() : m_data(nullptr), m_size(0), m_cap(0) {
ensure_capacity(1); // 保证至少有1字节容量
m_data[0] = '\0';
}
// C字符串构造函数
MyString(const char* str) : m_data(nullptr), m_size(0), m_cap(0) {
if (str) {
m_size = std::strlen(str);
ensure_capacity(m_size);
std::strcpy(m_data, str);
} else {
ensure_capacity(1);
m_data[0] = '\0';
}
}
// 拷贝构造函数
MyString(const MyString& other)
: m_data(nullptr), m_size(0), m_cap(0) {
*this = other; // 复用赋值操作符
}
// 析构函数
~MyString() {
delete[] m_data;
}
// 拷贝赋值操作符
MyString& operator=(const MyString& other) {
if (this != &other) {
delete[] m_data;
m_size = other.m_size;
ensure_capacity(m_size);
std::strcpy(m_data, other.m_data);
}
return *this;
}
// 获取C风格字符串
const char* c_str() const {
return m_data ? m_data : "";
}
// 获取字符串长度
size_t size() const {
return m_size;
}
// 获取当前容量
size_t capacity() const {
return m_cap - 1; // 不计数结尾的'\0'
}
// 下标访问(支持const和非const)
char& operator[](size_t index) {
return m_data[index];
}
const char& operator[](size_t index) const {
return m_data[index];
}
// 字符串连接操作
MyString operator+(const MyString& other) const {
MyString result(*this);
result += other;
return result;
}
// 连接赋值操作
MyString& operator+=(const MyString& other) {
size_t new_size = m_size + other.m_size;
ensure_capacity(new_size);
std::strcat(m_data, other.m_data);
m_size = new_size;
return *this;
}
// 比较操作符
bool operator==(const MyString& other) const {
if (m_size != other.m_size) return false;
return std::strcmp(m_data, other.m_data) == 0;
}
};
先写到这,后续再更新详细介绍一下这段代码的逻辑。