C++學習:六個月從基礎到就業——內存管理:new/delete操作符

C++學習:六個月從基礎到就業——內存管理:new/delete操作符

本文是我C++學習之旅系列的第十七篇技術文章,也是第二階段"C++進階特性"的第二篇,主要介紹C++中動態內存管理的核心操作符——new和delete。查看完整系列目錄了解更多內容。

引言

在上一篇文章中,我們深入探討了堆和棧的概念以及它們在內存管理中的作用。本文將聚焦于C++中用于動態內存分配和釋放的基本工具——newdelete操作符。與許多高級語言不同,C++允許程序員直接控制內存的分配和釋放,這提供了極大的靈活性,但同時也帶來了更多的責任和潛在的陷阱。

動態內存管理是現代軟件開發中不可或缺的部分,尤其是當處理大型數據結構、運行時大小未知的數據或需要在程序執行過程中持久存在的對象時。理解newdelete的工作原理及正確使用方法,對于編寫高效、穩定的C++程序至關重要。

new和delete基礎

new操作符概述

new操作符用于動態分配內存,它執行三個主要步驟:

  1. 分配足夠大的未初始化內存來存儲指定類型的對象
  2. 調用構造函數初始化對象(如果是類類型)
  3. 返回指向新創建對象的指針

基本語法:

Type* ptr = new Type;           // 分配單個對象
Type* arr = new Type[size];     // 分配對象數組

delete操作符概述

delete操作符用于釋放動態分配的內存,它執行兩個主要步驟:

  1. 調用對象的析構函數(如果是類類型)
  2. 釋放內存

基本語法:

delete ptr;      // 釋放單個對象的內存
delete[] arr;    // 釋放數組的內存

簡單示例

下面是一個使用newdelete的基本示例:

#include <iostream>class Simple {
public:Simple() {std::cout << "Simple constructor called" << std::endl;}~Simple() {std::cout << "Simple destructor called" << std::endl;}void sayHello() {std::cout << "Hello from Simple object!" << std::endl;}
};int main() {// 分配單個對象Simple* obj = new Simple;obj->sayHello();delete obj;  // 釋放單個對象std::cout << "-------------------" << std::endl;// 分配對象數組Simple* objArray = new Simple[3];objArray[0].sayHello();objArray[1].sayHello();objArray[2].sayHello();delete[] objArray;  // 釋放數組return 0;
}

輸出結果:

Simple constructor called
Hello from Simple object!
Simple destructor called
-------------------
Simple constructor called
Simple constructor called
Simple constructor called
Hello from Simple object!
Hello from Simple object!
Hello from Simple object!
Simple destructor called
Simple destructor called
Simple destructor called

new操作符詳解

分配過程詳解

當我們使用new操作符時,它實際執行以下操作:

  1. 調用底層的內存分配函數(通常是operator new)分配足夠的原始內存
  2. 將原始內存轉換為適當的類型指針
  3. 使用適當的構造函數初始化對象(對于非POD類型)

new的變體

帶初始化的new
int* p1 = new int;        // 未初始化值
int* p2 = new int();      // 初始化為0
int* p3 = new int(42);    // 初始化為42// C++11后,可以使用統一初始化語法
int* p4 = new int{42};    // 初始化為42
帶位置的new (placement new)

Placement new允許在預先分配的內存位置構造對象,而不分配新內存:

#include <iostream>
#include <new>  // 為placement new包含此頭文件class Complex {
private:double real;double imag;public:Complex(double r, double i) : real(r), imag(i) {std::cout << "Constructor called." << std::endl;}~Complex() {std::cout << "Destructor called." << std::endl;}void print() const {std::cout << real << " + " << imag << "i" << std::endl;}
};int main() {// 分配一塊足夠大的內存,但不構造對象char memory[sizeof(Complex)];// 在預先分配的內存上構造對象Complex* obj = new(memory) Complex(3.0, 4.0);obj->print();// 顯式調用析構函數(不要使用delete,因為內存不是通過new分配的)obj->~Complex();return 0;
}

Placement new主要用于:

  • 優化內存分配(避免多次分配/釋放)
  • 內存池實現
  • 對象的精確放置(如硬件通信緩沖區)
nothrow new

默認情況下,當new無法分配內存時會拋出std::bad_alloc異常。但我們也可以使用nothrow形式,失敗時返回nullptr而不是拋出異常:

#include <iostream>
#include <new>int main() {// 嘗試分配巨大的內存(可能失敗)int* hugeArray = new(std::nothrow) int[1000000000000];if (hugeArray == nullptr) {std::cout << "Memory allocation failed." << std::endl;} else {std::cout << "Memory allocation succeeded." << std::endl;delete[] hugeArray;}return 0;
}

分配數組

分配數組時,new會記錄數組的大小,以便delete[]能正確釋放全部內存:

int* array = new int[10];  // 分配10個整數的數組// 使用數組...delete[] array;  // 釋放整個數組

注意:當分配數組時必須使用delete[]而非delete來釋放內存,否則可能導致未定義行為。

二維數組的分配

有幾種方式可以分配二維數組:

// 方法1:使用連續內存(推薦)
int rows = 3, cols = 4;
int* matrix = new int[rows * cols];
matrix[row * cols + col] = value;  // 訪問元素// 方法2:數組的數組(指針數組)
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {matrix[i] = new int[cols];
}// 使用...// 釋放方法2的內存
for (int i = 0; i < rows; i++) {delete[] matrix[i];
}
delete[] matrix;
使用std::vector替代動態數組

通常,使用std::vector比原始動態數組更安全:

#include <vector>// 一維vector
std::vector<int> vec(10);  // 大小為10的向量// 二維vector
std::vector<std::vector<int>> matrix(rows, std::vector<int>(cols));
matrix[row][col] = value;  // 訪問元素

std::vector會自動管理內存分配和釋放,減少了內存泄漏和錯誤的風險。

delete操作符詳解

釋放過程詳解

當我們使用delete操作符時,它執行以下操作:

  1. 如果指針非空,調用對象的析構函數(對于非POD類型)
  2. 調用底層的內存釋放函數(通常是operator delete)來釋放內存

delete與析構函數

delete的關鍵功能之一是調用對象的析構函數。對于類對象,析構函數負責清理資源(如關閉文件、釋放其他動態分配的內存等)。

class ResourceManager {
private:int* data;public:ResourceManager() {data = new int[1000];std::cout << "Resource acquired" << std::endl;}~ResourceManager() {delete[] data;std::cout << "Resource released" << std::endl;}
};int main() {ResourceManager* rm = new ResourceManager();delete rm;  // 調用析構函數,確保data被正確釋放return 0;
}

如果不調用delete,析構函數不會被執行,導致data指向的內存泄漏。

delete[]與數組

delete[]操作符用于釋放通過new[]分配的數組。它確保數組中的每個對象都正確調用其析構函數。

class MyClass {
public:MyClass() {std::cout << "Constructor called" << std::endl;}~MyClass() {std::cout << "Destructor called" << std::endl;}
};int main() {MyClass* array = new MyClass[3];// 輸出: Constructor called (3次)delete[] array;  // 對數組中的每個對象調用析構函數// 輸出: Destructor called (3次)return 0;
}

使用錯誤的刪除形式可能導致嚴重問題:

  • 使用delete釋放通過new[]分配的內存:可能只調用第一個對象的析構函數,導致其他對象的資源泄漏
  • 使用delete[]釋放通過new分配的單個對象:未定義行為,可能導致崩潰

new和delete的內部機制

operator new與operator delete函數

newdelete操作符實際上調用了底層的函數operator newoperator delete來執行內存分配和釋放:

void* operator new(std::size_t size);
void operator delete(void* ptr) noexcept;void* operator new[](std::size_t size);
void operator delete[](void* ptr) noexcept;

這些函數可以被重載,以實現自定義內存管理策略:

#include <iostream>
#include <cstdlib>// 重載全局operator new
void* operator new(std::size_t size) {std::cout << "Custom global operator new: " << size << " bytes" << std::endl;void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc();return ptr;
}// 重載全局operator delete
void operator delete(void* ptr) noexcept {std::cout << "Custom global operator delete" << std::endl;std::free(ptr);
}class MyClass {
public:MyClass() {std::cout << "MyClass constructor" << std::endl;}~MyClass() {std::cout << "MyClass destructor" << std::endl;}// 類特定的operator new重載void* operator new(std::size_t size) {std::cout << "MyClass::operator new: " << size << " bytes" << std::endl;void* ptr = std::malloc(size);if (!ptr) throw std::bad_alloc();return ptr;}// 類特定的operator delete重載void operator delete(void* ptr) noexcept {std::cout << "MyClass::operator delete" << std::endl;std::free(ptr);}
};int main() {// 使用全局operator newint* pi = new int;delete pi;std::cout << "-------------------" << std::endl;// 使用類特定的operator newMyClass* obj = new MyClass;delete obj;return 0;
}

輸出:

Custom global operator new: 4 bytes
Custom global operator delete
-------------------
MyClass::operator new: 1 bytes
MyClass constructor
MyClass destructor
MyClass::operator delete

內存對齊與內存布局

new操作符確保分配的內存滿足對象的對齊要求。不同類型有不同的對齊要求:

std::cout << "alignof(char): " << alignof(char) << std::endl;
std::cout << "alignof(int): " << alignof(int) << std::endl;
std::cout << "alignof(double): " << alignof(double) << std::endl;

一個復雜對象的內存布局可能受到填充(padding)和對齊(alignment)的影響:

struct Padded {char c;    // 1字節// 可能有填充int i;     // 4字節// 可能有填充double d;  // 8字節
};std::cout << "sizeof(Padded): " << sizeof(Padded) << std::endl;
// 可能大于13(1+4+8)字節,由于內存對齊

常見的new/delete問題與陷阱

內存泄漏

內存泄漏發生在動態分配的內存未被釋放時:

void memoryLeak() {int* ptr = new int[100];// 無匹配的delete[],內存泄漏
}

懸掛指針(野指針)

懸掛指針指向已釋放的內存:

int* createAndDestroy() {int* ptr = new int(42);delete ptr;  // 內存已釋放return ptr;  // 返回懸掛指針
}int main() {int* dangling = createAndDestroy();*dangling = 10;  // 未定義行為:寫入已釋放的內存return 0;
}

重復釋放

重復釋放同一內存會導致未定義行為:

int* ptr = new int;
delete ptr;   // 第一次釋放,正確
delete ptr;   // 第二次釋放,未定義行為

使用錯誤的釋放形式

不匹配的分配和釋放形式會導致未定義行為:

int* single = new int;
delete[] single;  // 錯誤:應使用deleteint* array = new int[10];
delete array;     // 錯誤:應使用delete[]

自定義析構函數前提下的釋放問題

如果基類沒有虛析構函數,通過基類指針刪除派生類對象會導致未定義行為:

class Base {
public:~Base() {  // 非虛析構函數std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Base* ptr = new Derived;delete ptr;  // 只調用Base析構函數,不調用Derived析構函數return 0;
}

std::nothrow的限制

std::nothrow只影響new的內存分配部分,構造函數仍然可能拋出異常:

class ThrowingCtor {
public:ThrowingCtor() {throw std::runtime_error("Constructor failed");}
};int main() {try {// nothrow只對內存分配部分起作用,構造函數異常仍會傳播ThrowingCtor* obj = new(std::nothrow) ThrowingCtor;} catch (const std::exception& e) {std::cout << "Caught exception: " << e.what() << std::endl;}return 0;
}

替代方案與現代C++實踐

智能指針

現代C++提供了智能指針,自動管理動態內存的生命周期:

#include <memory>// 獨占所有權
std::unique_ptr<int> p1(new int(42));
// 或更好的方式
std::unique_ptr<int> p2 = std::make_unique<int>(42);  // C++14// 共享所有權
std::shared_ptr<int> p3 = std::make_shared<int>(42);
std::shared_ptr<int> p4 = p3;  // 現在p3和p4共享同一個對象

智能指針會在超出作用域時自動釋放其管理的內存,無需顯式調用delete

容器

標準庫容器如std::vectorstd::list等自動管理內存:

std::vector<int> vec;
for (int i = 0; i < 1000; ++i) {vec.push_back(i);  // 自動擴容,無需手動內存管理
}

RAII(資源獲取即初始化)原則

使用RAII原則設計類,在構造函數中獲取資源,在析構函數中釋放資源:

class ResourceWrapper {
private:Resource* resource;public:ResourceWrapper(const std::string& resourceName) {resource = acquireResource(resourceName);}~ResourceWrapper() {releaseResource(resource);}// 禁止復制ResourceWrapper(const ResourceWrapper&) = delete;ResourceWrapper& operator=(const ResourceWrapper&) = delete;// 允許移動ResourceWrapper(ResourceWrapper&& other) noexcept : resource(other.resource) {other.resource = nullptr;}ResourceWrapper& operator=(ResourceWrapper&& other) noexcept {if (this != &other) {releaseResource(resource);resource = other.resource;other.resource = nullptr;}return *this;}// 使用資源的方法...
};

內存池和分配器

對于性能關鍵的應用,可以使用自定義內存池來優化頻繁的小內存分配:

#include <cstddef>
#include <new>class SimpleMemoryPool {
private:struct Block {Block* next;};Block* freeList;static const size_t BLOCK_SIZE = 1024;public:SimpleMemoryPool() : freeList(nullptr) {}~SimpleMemoryPool() {Block* block = freeList;while (block) {Block* next = block->next;std::free(block);block = next;}}void* allocate(size_t size) {if (size > BLOCK_SIZE - sizeof(Block*)) {return std::malloc(size);}if (!freeList) {// 分配一批新塊char* memory = reinterpret_cast<char*>(std::malloc(BLOCK_SIZE * 10));if (!memory) return nullptr;// 將新內存塊鏈接到自由列表for (int i = 0; i < 10; ++i) {Block* block = reinterpret_cast<Block*>(memory + i * BLOCK_SIZE);block->next = freeList;freeList = block;}}// 使用自由列表的第一個塊Block* block = freeList;freeList = block->next;return block;}void deallocate(void* ptr, size_t size) {if (size > BLOCK_SIZE - sizeof(Block*)) {std::free(ptr);return;}// 將塊添加回自由列表Block* block = reinterpret_cast<Block*>(ptr);block->next = freeList;freeList = block;}
};// 為特定類型實現自定義new和delete
class MyObject {
private:static SimpleMemoryPool pool;public:void* operator new(size_t size) {return pool.allocate(size);}void operator delete(void* ptr, size_t size) {pool.deallocate(ptr, size);}// 類的其他成員...
};SimpleMemoryPool MyObject::pool;

性能考量

new/delete的開銷

動態內存分配涉及多種開銷:

  1. 調用操作系統分配內存:通常需要系統調用,這是昂貴的操作
  2. 查找合適的內存塊:內存管理器需要查找足夠大的空閑塊
  3. 記錄分配信息:存儲元數據(如分配大小)
  4. 對齊處理:確保內存對齊
  5. 構造和析構:調用構造函數和析構函數

對于頻繁的小內存分配和釋放,這些開銷可能會顯著影響性能。

性能優化策略

可以采用以下策略優化動態內存管理:

  1. 減少分配次數:預分配、重用對象
  2. 批量分配:一次分配多個對象
  3. 使用內存池:為特定大小的對象預分配內存
  4. 避免碎片化:使用適當的分配策略
  5. 考慮替代方案:使用棧內存(當對象較小且生命周期有限時)

棧與堆的性能對比

棧分配通常比堆分配快得多:

#include <iostream>
#include <chrono>
#include <vector>const int ITERATIONS = 1000000;void stackAllocation() {for (int i = 0; i < ITERATIONS; ++i) {int array[10];  // 棧分配array[0] = i;}
}void heapAllocation() {for (int i = 0; i < ITERATIONS; ++i) {int* array = new int[10];  // 堆分配array[0] = i;delete[] array;}
}void vectorAllocation() {for (int i = 0; i < ITERATIONS; ++i) {std::vector<int> vec(10);  // 使用std::vectorvec[0] = i;}
}template<typename Func>
double measureTime(Func func) {auto start = std::chrono::high_resolution_clock::now();func();auto end = std::chrono::high_resolution_clock::now();std::chrono::duration<double, std::milli> duration = end - start;return duration.count();
}int main() {std::cout << "Stack allocation: " << measureTime(stackAllocation) << " ms" << std::endl;std::cout << "Heap allocation: " << measureTime(heapAllocation) << " ms" << std::endl;std::cout << "Vector allocation: " << measureTime(vectorAllocation) << " ms" << std::endl;return 0;
}

運行此代碼會明顯看出棧分配比堆分配快得多,而std::vector通常介于兩者之間。

實際應用案例

實現簡單的字符串類

下面是使用動態內存管理實現簡單字符串類的示例:

#include <iostream>
#include <cstring>
#include <algorithm>class SimpleString {
private:char* data;size_t length;// 確保有足夠的空間void ensureCapacity(size_t newLength) {if (newLength > length) {// 分配新內存(添加一些額外空間以減少重新分配次數)size_t newCapacity = std::max(newLength, length * 2);char* newData = new char[newCapacity + 1];  // +1 用于空終止符// 復制現有數據if (data) {std::strcpy(newData, data);delete[] data;}data = newData;length = newCapacity;}}public:// 默認構造函數SimpleString() : data(nullptr), length(0) {data = new char[1];data[0] = '\0';}// 從C風格字符串構造SimpleString(const char* str) : data(nullptr), length(0) {if (!str) {data = new char[1];data[0] = '\0';} else {length = std::strlen(str);data = new char[length + 1];std::strcpy(data, str);}}// 復制構造函數SimpleString(const SimpleString& other) : data(nullptr), length(0) {length = other.length;data = new char[length + 1];std::strcpy(data, other.data);}// 移動構造函數SimpleString(SimpleString&& other) noexcept : data(other.data), length(other.length) {other.data = nullptr;other.length = 0;}// 析構函數~SimpleString() {delete[] data;}// 復制賦值SimpleString& operator=(const SimpleString& other) {if (this != &other) {delete[] data;length = other.length;data = new char[length + 1];std::strcpy(data, other.data);}return *this;}// 移動賦值SimpleString& operator=(SimpleString&& other) noexcept {if (this != &other) {delete[] data;data = other.data;length = other.length;other.data = nullptr;other.length = 0;}return *this;}// 連接操作符SimpleString operator+(const SimpleString& other) const {SimpleString result;result.ensureCapacity(length + other.length);std::strcpy(result.data, data);std::strcat(result.data, other.data);return result;}// 獲取C風格字符串const char* c_str() const {return data;}// 獲取長度size_t size() const {return std::strlen(data);}// 訪問元素char& operator[](size_t index) {return data[index];}const char& operator[](size_t index) const {return data[index];}// 設置新值void assign(const char* str) {if (!str) return;size_t newLength = std::strlen(str);ensureCapacity(newLength);std::strcpy(data, str);}// 追加字符串void append(const char* str) {if (!str) return;size_t currentLength = std::strlen(data);size_t appendLength = std::strlen(str);ensureCapacity(currentLength + appendLength);std::strcat(data, str);}
};int main() {// 測試構造函數SimpleString s1("Hello");SimpleString s2(" World");// 測試連接SimpleString s3 = s1 + s2;std::cout << s3.c_str() << std::endl;  // 輸出:Hello World// 測試復制和賦值SimpleString s4 = s1;std::cout << s4.c_str() << std::endl;  // 輸出:Hellos4 = s2;std::cout << s4.c_str() << std::endl;  // 輸出:World// 測試修改s4[0] = 'w';  // 改為小寫std::cout << s4.c_str() << std::endl;  // 輸出:world// 測試appends4.append("!");std::cout << s4.c_str() << std::endl;  // 輸出:world!return 0;
}

這個例子展示了如何使用newdelete在堆上管理字符串數據,包括深拷貝、移動語義和內存重分配邏輯。

自定義對象池

對于需要頻繁創建和銷毀的小對象,可以實現對象池來提高性能:

#include <iostream>
#include <vector>
#include <memory>template<typename T, size_t BlockSize = 100>
class ObjectPool {
private:// 表示內存塊的結構struct Block {union {T value;Block* next;};// 禁用構造和析構函數以允許在union中使用TBlock() : next(nullptr) {}~Block() {}};Block* freeList;std::vector<std::unique_ptr<Block[]>> blocks;public:ObjectPool() : freeList(nullptr) {}~ObjectPool() {// blocks的vector會自動清理分配的內存}// 分配對象template<typename... Args>T* allocate(Args&&... args) {if (freeList == nullptr) {// 分配新的內存塊auto newBlock = std::make_unique<Block[]>(BlockSize);// 初始化自由列表for (size_t i = 0; i < BlockSize - 1; ++i) {newBlock[i].next = &newBlock[i + 1];}newBlock[BlockSize - 1].next = nullptr;freeList = &newBlock[0];blocks.push_back(std::move(newBlock));}// 使用自由列表的第一個塊Block* block = freeList;freeList = block->next;// 使用placement new構造對象return new (&block->value) T(std::forward<Args>(args)...);}// 釋放對象void deallocate(T* object) {if (!object) return;// 調用析構函數object->~T();// 將塊添加回自由列表Block* block = reinterpret_cast<Block*>(object);block->next = freeList;freeList = block;}
};// 測試用的類
class TestObject {
private:int id;public:TestObject(int i) : id(i) {std::cout << "TestObject " << id << " constructed." << std::endl;}~TestObject() {std::cout << "TestObject " << id << " destructed." << std::endl;}int getId() const { return id; }
};int main() {// 創建對象池ObjectPool<TestObject, 5> pool;// 分配一些對象std::vector<TestObject*> objects;for (int i = 0; i < 10; ++i) {objects.push_back(pool.allocate(i));}// 使用對象for (auto obj : objects) {std::cout << "Object ID: " << obj->getId() << std::endl;}// 釋放一些對象for (int i = 0; i < 5; ++i) {pool.deallocate(objects[i]);objects[i] = nullptr;}// 分配更多對象(會重用已釋放的內存)for (int i = 0; i < 5; ++i) {objects[i] = pool.allocate(i + 100);}// 清理所有對象for (auto obj : objects) {if (obj) {pool.deallocate(obj);}}return 0;
}

對象池可以顯著減少內存分配和釋放的開銷,特別是對于頻繁創建和銷毀的小對象。

最佳實踐

總結一些使用newdelete的最佳實踐:

  1. 避免裸指針:盡可能使用智能指針(std::unique_ptrstd::shared_ptr

    // 不推薦
    Resource* res = new Resource();
    // 使用后...
    delete res;// 推薦
    std::unique_ptr<Resource> res = std::make_unique<Resource>();
    // 自動管理生命周期
    
  2. 優先使用標準庫容器:它們已經在內部處理好內存管理

    // 不推薦
    int* array = new int[size];
    // 使用后...
    delete[] array;// 推薦
    std::vector<int> array(size);
    
  3. 確保匹配newdeletenew[]delete[]

    // 正確
    int* single = new int;
    delete single;int* array = new int[10];
    delete[] array;
    
  4. 遵循RAII原則:在構造函數中獲取資源,在析構函數中釋放資源

  5. 檢查內存分配失敗:使用異常處理或std::nothrow

    // 使用異常
    try {int* array = new int[1000000000];
    } catch (const std::bad_alloc& e) {std::cerr << "Memory allocation failed: " << e.what() << std::endl;
    }// 使用nothrow
    int* array = new(std::nothrow) int[1000000000];
    if (!array) {std::cerr << "Memory allocation failed" << std::endl;
    }
    
  6. 基類使用虛析構函數:確保通過基類指針正確刪除派生類對象

    class Base {
    public:virtual ~Base() = default;
    };
    
  7. 考慮移動語義:當涉及大量數據轉移時

    class MovableResource {
    public:// 移動構造函數MovableResource(MovableResource&& other) noexcept: data(other.data) {other.data = nullptr;}// 移動賦值運算符MovableResource& operator=(MovableResource&& other) noexcept {if (this != &other) {delete[] data;data = other.data;other.data = nullptr;}return *this;}private:int* data;
    };
    
  8. 使用工具檢測內存問題:Valgrind、AddressSanitizer等

總結

newdelete操作符是C++中動態內存管理的基本工具,它們允許程序員在運行時分配和釋放內存。理解它們的工作原理、正確用法和潛在問題對于編寫健壯的C++程序至關重要。

然而,在現代C++中,我們通常應該傾向于使用更高級的抽象(如智能指針、容器和RAII類)來自動管理內存,這有助于減少常見錯誤如內存泄漏、懸空指針和重復釋放。

關鍵記憶點:

  • new分配內存并調用構造函數,delete調用析構函數并釋放內存
  • 數組使用new[]分配,delete[]釋放
  • 裸指針管理的內存需要顯式釋放,否則會導致內存泄漏
  • 現代C++提供更安全的替代方案:智能指針、容器和RAII

在下一篇文章中,我們將討論內存泄漏的檢測與避免,這是C++內存管理中的一個重要主題。


這是我C++學習之旅系列的第十七篇技術文章。查看完整系列目錄了解更多內容。

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

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

相關文章

15~30K,3年以上golang開發經驗

繼續分享最新的面經&#xff0c;前面發的兩篇大家也可以看看&#xff1a; 「坐標上海&#xff0c;20K的面試強度」「北京七貓&#xff0c;薪資25~35K&#xff0c;瞧瞧面試強度」 今天分享的是golang開發崗面經&#xff0c;要求是3年以上golang開發經驗&#xff0c;薪資為15~3…

Python爬蟲實戰:獲取優志愿專業數據

一、引言 在信息爆炸的當下,數據成為推動各領域發展的關鍵因素。優志愿網站匯聚了豐富的專業數據,對于教育研究、職業規劃等領域具有重要價值。然而,為保護自身數據和資源,許多網站設置了各類反爬機制。因此,如何高效、穩定地從優志愿網站獲取計算機專業數據成為一個具有…

ArcPy工具箱制作(下)

在上一篇博客中&#xff0c;我們已經初步了解了如何制作ArcPy工具箱&#xff0c;包括工具箱的基本概念、準備工作、腳本編寫以及將腳本轉換為工具箱的步驟。今天&#xff0c;我們將繼續深入探討ArcPy工具箱的制作&#xff0c;重點介紹一些進階技巧和優化方法. 一、優化工具箱的…

不一樣的flag 1(迷宮題)

題目 做法 下載壓縮包&#xff0c;解壓&#xff0c;把解壓后的文件拖進Exeinfo PE進行分析 32位&#xff0c;無殼 扔進IDA&#xff08;32位&#xff09;&#xff0c;找到main&#xff0c;F5反編譯 沒啥關鍵詞&#xff0c;ShiftF12也找不到什么有用的點 從上往下分析吧 puts(…

工程化實踐:Flutter項目結構與規范

工程化實踐&#xff1a;Flutter項目結構與規范 在Flutter項目開發中&#xff0c;良好的工程化實踐對于提高開發效率、保證代碼質量和團隊協作至關重要。本文將從項目結構、代碼規范、CI/CD流程搭建以及包管理等方面&#xff0c;詳細介紹Flutter項目的工程化最佳實踐。 項目結…

[Java · 初窺門徑] Java 語言初識

&#x1f31f; 想系統化學習 Java 編程&#xff1f;看看這個&#xff1a;[編程基礎] Java 學習手冊 0x01&#xff1a;Java 編程語言簡介 Java 是一種高級計算機編程語言&#xff0c;它是由 Sun Microsystems 公司&#xff08;已被 Oracle 公司收購&#xff09;于 1995 年 5 …

1187. 【動態規劃】競賽總分

題目描述 學生在我們USACO的競賽中的得分越多我們越高興。我們試著設計我們的競賽以便人們能盡可能的多得分。 現在要進行一次競賽&#xff0c;總時間T固定&#xff0c;有若干類型可選擇的題目&#xff0c;每種類型題目可選入的數量不限&#xff0c;每種類型題目有一個si(解答…

使用KeilAssistant代替keil的UI界面

目錄 一、keil Assistant的優勢和缺點 二、使用方法 &#xff08;1&#xff09;配置keil的路徑 &#xff08;2&#xff09;導入并使用工程 &#xff08;3&#xff09;默認使用keil自帶的ARM編譯器而非GUN工具鏈 一、keil Assistant的優勢和缺點 在日常學…

【React】通過 fetch 發起請求,設置 proxy 處理跨域

fetch 基本使用跨域處理 fetch 基本使用 在node使用原生ajax發請求&#xff1a;XMLHttpRequest()1.獲取xhr對象 2.注冊回調函數 3.設置參數&#xff0c;請求頭 4.發起連接原生ajax沒有帶異步處理 promise&#xff1b;原生ajax封裝一下&#xff0c;以便重復調用jQuery&#…

Redis(二) - Redis命令詳解

文章目錄 前言一、啟動Redis并進入客戶端1. 啟動Redis2. 進入Redis客戶端3. 使用IDEA連接Redis 二、查看命令幫助信息1. 查看所有命令2. 查看指定命令幫助 三、鍵操作命令1. set命令2. mset命令3. keys命令4. get命令5. mget命令6. dump命令7. exists命令8. type命令9. rename命…

【Qt】初識Qt(二)

目錄 一、顯示hello world1.1 圖形化界面1.2 寫代碼 二、對象樹三、使用輸入框顯示hello world四、使用按鈕顯示hello world 一、顯示hello world 有兩種方式實現hello world&#xff1a; 通過圖形化界面&#xff0c;在界面上創建出一個控件&#xff0c;顯示hello world通過寫…

空調制冷量和功率有什么關系?

空調的制冷量和功率是衡量空調性能的兩個核心參數,二者既有區別又緊密相關,以下是具體解析: 1. 基本定義 制冷量(Cooling Capacity)指空調在單位時間內從室內環境中移除的熱量,單位為 瓦特(W) 或 千卡/小時(kcal/h)。它直接反映空調的制冷能力,數值越大,制冷效果越…

【prometheus+Grafana篇】Prometheus與Grafana:深入了解監控架構與數據可視化分析平臺

&#x1f4ab;《博主主頁》&#xff1a;奈斯DB-CSDN博客 &#x1f525;《擅長領域》&#xff1a;擅長阿里云AnalyticDB for MySQL(分布式數據倉庫)、Oracle、MySQL、Linux、prometheus監控&#xff1b;并對SQLserver、NoSQL(MongoDB)有了解 &#x1f496;如果覺得文章對你有所幫…

基于n8n的AI應用工作流原理與技術解析

基于n8n的AI應用工作流原理與技術解析 在AI技術深度融入企業數字化轉型的今天&#xff0c;開源工作流自動化工具n8n憑借其靈活的架構和強大的集成能力&#xff0c;成為構建智能自動化流程的核心引擎。本文將從技術原理、AI融合機制、典型應用場景三個維度&#xff0c;解析n8n在…

經濟指標學習(二)

系列文章目錄 文章目錄 系列文章目錄1、市凈率**一、定義與計算****二、核心意義****三、應用場景****四、局限性****五、分類與衍生指標****總結** 2、市銷率**一、定義與計算****二、核心意義****三、優缺點分析****四、適用場景****五、與其他指標的對比****六、實際應用案例…

大語言模型減少幻覺的常見方案

什么是大語言模型的幻覺 大語言模型的幻覺&#xff08;Hallucination&#xff09;是指模型在生成文本時&#xff0c;輸出與輸入無關、不符合事實、邏輯錯誤或完全虛構的內容。這種現象主要源于模型基于概率生成文本的本質&#xff0c;其目標是生成語法合理、上下文連貫的文本&…

CSS 美化頁面(四)

一、浮動float屬性 ?屬性值??描述??適用場景?left元素向左浮動&#xff0c;騰出右側空間供其他元素使用&#xff0c;其他內容會圍繞在其右側?。橫向排列元素&#xff08;如導航菜單&#xff09;、圖文混排布局?。right元素向右浮動&#xff0c;騰出左側空間供其他元素使…

如何將 .txt 文件轉換成 .md 文件

一、因為有些軟件上傳文件的時候需要 .md 文件&#xff0c;首先在文件所在的目錄中&#xff0c;點擊“查看”&#xff0c;然后勾選上“文件擴展名”&#xff0c;這個時候該目錄下的所有文件都會顯示其文件類型了。 二、這時直接對目標的 .txt 文件進行重命名&#xff0c;把后綴…

C++ 迭代器失效詳解:如何避免 vector 操作中的陷阱

目錄 1. 什么是迭代器失效&#xff1f; 2. 哪些操作會導致迭代器失效&#xff1f; 2.1 vector 的插入操作&#xff08;push_back, insert&#xff09; 示例&#xff1a;push_back 導致迭代器失效 如何避免&#xff1f; 2.2 vector 的刪除操作&#xff08;erase, pop_back&…

(EtherCAT 轉 EtherNet/IP)EtherCAT/Ethernet/IP/Profinet/ModbusTCP協議互轉工業串口網關

型號 協議轉換通信網關 EtherCAT 轉 EtherNet/IP MS-GW12 概述 MS-GW12 是 EtherCAT 和 EtherNet/IP 協議轉換網關&#xff0c;為用戶提供兩種不同通訊協議的 PLC 進行數據交互的解決方案&#xff0c;可以輕松容易將 EtherNet/IP 網絡接入 EtherCAT 網絡中&#xff0c;方便…