C++——手撕智能指針、單例模式、線程池、String

智能指針

今天我們來學習一下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 析構時釋放資源 #2std::cout << "\n=== 測試3: reset() 與 release() ===" << std::endl;{unique_ptr<TestResource> p(new TestResource(3));// 測試 reset()p.reset(new TestResource(4));  // 釋放舊資源 #3,接管新資源 #4p->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/2SharedPtr<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"; // nostd::cout << "p2 use_count: " << p2.use_count() << "\n"; // 1SharedPtr<TestObject> p3;p3 = std::move(p2); // 移動賦值std::cout << "p2 valid? " << (p2 ? "yes" : "no") << "\n"; // nop3->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;}
};

先寫到這,后續再更新詳細介紹一下這段代碼的邏輯。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88009.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88009.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88009.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Mybatis----留言板

基礎項目&#xff1a;留言板 截止到目前為止&#xff0c;我們已經學習了 Spring&#xff08;只學習了DI&#xff09;、Spring MVC、SpringBoot、Mybatis 這些知識了&#xff0c;已經滿足了做簡單項目的基本要求了&#xff0c;所以接下來我們就從0到1實現表白墻項目。 需求分析…

Web-API-day3 DOM事件進階

一、 事件流 1.事件冒泡 const fa document.querySelector(.father)const son document.querySelector(.son)document.addEventListener(click, function () {alert(我是爺爺)})fa.addEventListener(click, function () {alert(我是爸爸)})son.addEventListener(click, fun…

小波增強型KAN網絡 + SHAP可解釋性分析(Pytorch實現)

效果一覽一、傳統KAN網絡的痛點與突破 1. 傳統KAN的局限性 傳統Kolmogorov-Arnold網絡&#xff08;KAN&#xff09;雖在理論上有可靠的多變量函數逼近能力&#xff0c;但存在顯著瓶頸&#xff1a; 計算效率低&#xff1a;訓練速度慢于MLP&#xff0c;資源消耗大&#xff0c;尤其…

tomcat部署多個端口以及制定路徑部署-vue3

vue3項目tomcat部署記錄 使用hash路由 字符串拼接的圖片地址可以使用import.meta.env.BASE_URL 默認8080 如果部署地址為8080/xc 則設置 vite.config.js中設置base為’/xc/’ outDir設置為xc 打包產物直接拖到webapps目錄下 如果另開一個端口 如8081 設置根目錄訪問 conf/ser…

LeetCode三數之和-js題解

給你一個整數數組 nums &#xff0c;判斷是否存在三元組 [nums[i], nums[j], nums[k]] 滿足 i ! j、i ! k且 j ! k &#xff0c;同時還滿足 nums[i] nums[j] nums[k] 0 。請你返回所有和為 0 且不重復的三元組。 注意&#xff1a;答案中不可以包含重復的三元組。 示例 1&…

Flink SQLServer CDC 環境配置與驗證

一、SQL Server 數據庫核心配置 1. 啟用 CDC 功能&#xff08;Change Data Capture&#xff09; SQL Server CDC 依賴數據庫級別的 CDC 功能及表級別的捕獲配置&#xff0c;需按以下步驟啟用&#xff1a; 啟用數據庫 CDC -- 以管理員身份連接數據庫 USE master; GO-- 檢查數…

軟考(軟件設計師)存儲管理—設備管理,磁盤調度

I/O軟件的核心目標是管理硬件差異、提供統一接口、實現高效可靠的數據傳輸。 核心目標&#xff1a; 設備無關性&#xff1a; 應用程序無需關心具體硬件細節。錯誤處理&#xff1a; 處理硬件錯誤和傳輸異常。同步/異步傳輸&#xff1a; 支持阻塞&#xff08;等待完成&#xff09…

[C語言] C語言數學函數庫概覽

C語言數學函數庫概覽 文章目錄 C語言數學函數庫概覽一、概述二、基本數學函數詳解1. 平方根函數 sqrt(x)2. 冪函數 pow(x, y)3. 絕對值函數 fabs(x)4. 向上取整函數 ceil(x)5. 向下取整函數 floor(x) 三、三角函數與雙曲函數詳解1. 正弦函數 double sin(double x)2. 余弦函數 d…

【簡單三步】Stable diffusion Webai本地部署無法加載模型并報openai/clip-vit-large-patch14錯誤的解決方法

問題描述 Stable diffusion Webai本地部署成功后&#xff0c;手動加載本地模型checkpoint時&#xff0c;始終無法加載進去&#xff0c;確定模型存放位置無誤&#xff08;位于models\Stable-diffusion&#xff09;查看cmd窗口時&#xff0c;發現一個報錯提示&#xff1a;Can’t …

Java 命令行參數詳解:系統屬性、JVM 選項與應用配置

Java 命令行參數詳解&#xff1a;系統屬性、JVM 選項與應用配置 在 Java 應用啟動命令中&#xff0c;如&#xff1a; java -jar -Dserver.port8088 xdr-demo-1.0-SNAPSHOT-assembly.jar &-Dserver.port8088是一個 系統屬性&#xff08;System Property&#xff09; 設置。…

【論文筆記】World Models for Autonomous Driving: An Initial Survey

原文鏈接&#xff1a;https://ieeexplore.ieee.org/abstract/document/10522953 1. 世界模型的發展 A. 世界模型的結構基礎 世界模型包含4個關鍵組件&#xff0c;以模擬人類連貫的思考和決策過程。 a&#xff09;感知模塊使用如變分自編碼器&#xff08;VAE&#xff09;、掩…

Spring Cloud Config(微服務配置中心詳解)

關鍵詞&#xff1a;Spring Cloud Config、配置中心、遠程倉庫、動態刷新、加密解密 ? 摘要 在微服務架構中&#xff0c;隨著服務數量的增加&#xff0c;統一管理各服務的配置信息變得尤為重要。傳統的本地配置文件方式難以滿足多環境、多實例、集中化的需求。 Spring Cloud …

【Note】《深入理解Linux內核》 第二十章:深入理解 Linux 程序執行機制

《深入理解Linux內核》 第二十章&#xff1a;深入理解 Linux 程序執行機制&#xff08;Program Execution&#xff09;關鍵詞&#xff1a;exec 系列系統調用、可執行文件格式&#xff08;ELF&#xff09;、用戶地址空間、內存映射、動態鏈接、棧初始化、入口點、共享庫、內核態…

服務器如何配置防火墻規則以阻止惡意流量和DDoS攻擊?

防火墻是保護服務器免受惡意流量和 DDoS 攻擊的第一道防線。通過合理配置防火墻規則&#xff0c;可以有效阻止惡意訪問、限制不必要的流量&#xff0c;并減少攻擊對服務器的影響。以下是配置防火墻規則的全面指南&#xff0c;包括基礎規則設置、防御 DDoS 攻擊的高級策略和最佳…

持續性投入是成就自我價值的關鍵一環

概述 時間&#xff0c;的唯一公平之處就是給你我的長度是相同的&#xff0c;這也是它唯一公平&#xff0c;也是不公平的地方。 所謂的公平&#xff0c;就是不患寡而患不均中所說的平均。 所謂的不公平就是&#xff0c;相同時間內我們彼此對應的標價不同&#xff0c;延伸到后…

使用allegro在BoardGeometry的Silkscreen_Top層畫出圖案

目錄 1. 圖形及圖形放置顯示2. 繪制 1. 圖形及圖形放置顯示 繪制完成圖案&#xff1a; 導出后圖案&#xff1a; 2. 繪制 圖層選中&#xff1b; 畫圓型&#xff1b; 半徑3.5mm&#xff0c;原點生成&#xff1b; 在圖案中挖空&#xff1b; 用指令走線&#xff1a; …

Kotlin 協程:Channel 與 Flow 深度對比及 Channel 使用指南

前言 在 Kotlin 協程的異步編程世界里&#xff0c;Channel 和 Flow 是處理數據流的重要工具&#xff0c;它們有著不同的設計理念與適用場景。本文將對比二者功能與應用場景&#xff0c;詳細講解 Channel 的使用步驟及注意事項 。 一、Channel 與 Flow 的特性對比 Channel 是協程…

MYsql主從復制部署

MySQL 主從復制是將主數據庫的變更自動同步到從數據庫的過程&#xff0c;常用語讀寫分離、高可用性和數據備份。 1.環境準備 確保主從服務器已安裝相同版本的 MySQL&#xff0c;并能通過網絡互相訪問。 # 檢查 MySQL 版本 mysql -V 2.配置主服務器 &#xff08;1&#xff0…

安燈呼叫看板如何實現汽車生產異常秒級響應

在汽車零部件工廠的靜置車間&#xff0c;傳統生產管理依賴人工巡檢與紙質記錄&#xff0c;存在效率低、信息滯后、異常響應慢等問題。某汽車廠曾因物料靜置時間未及時監控&#xff0c;導致批次混料&#xff0c;損失超10萬元。而安燈呼叫看板系統的引入&#xff0c;通過實時狀態…

構造函數注入在spring boot 中怎么使用詳解

我們來詳細講解一下在 Spring Boot 中如何使用構造函數注入&#xff0c;并通過一個完整的、可運行的例子來演示。 構造函數注入是 Spring 官方最推薦的依賴注入方式&#xff0c;因為它能保證對象的不可變性和依賴的完整性。 核心理念 在 Spring Boot 中使用構造函數注入非常簡單…