- C++
C++ 动态内存分配初始化学习笔记教程
- 2025-8-10 15:07:01 @
C++ 动态内存分配初始化学习笔记教程
1. 动态内存分配初始化的基本概念
在C++中,使用new
操作符进行动态内存分配时,不仅可以申请内存空间,还可以对其进行初始化。动态内存初始化是指在分配内存的同时为其赋予初始值,这一过程能确保内存被正确初始化,避免使用未初始化的内存导致的不可预期行为(如垃圾值、程序崩溃等)。
动态内存初始化的核心价值在于:
- 保证内存使用的安全性,避免未初始化内存带来的隐患。
- 简化代码逻辑,将内存分配与初始化合并为一步操作。
- 支持多种初始化方式,适配不同场景的需求(如基础类型、自定义类型、数组等)。
2. 单个对象的动态内存初始化
对于单个基础类型或自定义类型对象,动态内存初始化主要有以下几种方式:
2.1 基础类型的初始化
(1)值初始化(C++98及以上)
使用圆括号()
指定初始值,适用于所有基础类型(int
、double
、char
等)。
#include <iostream>
int main() {
// 初始化int类型动态内存
int* intPtr = new int(10);
std::cout << "int值: " << *intPtr << std::endl; // 输出:10
// 初始化double类型动态内存
double* doublePtr = new double(3.14159);
std::cout << "double值: " << *doublePtr << std::endl; // 输出:3.14159
// 初始化char类型动态内存
char* charPtr = new char('A');
std::cout << "char值: " << *charPtr << std::endl; // 输出:A
// 释放内存
delete intPtr;
delete doublePtr;
delete charPtr;
return 0;
}
(2)列表初始化(C++11及以上)
使用花括号{}
指定初始值,是C++11引入的更通用的初始化方式,支持基础类型和复杂类型。
#include <iostream>
int main() {
// 列表初始化int
int* intPtr = new int{20};
std::cout << "int值: " << *intPtr << std::endl; // 输出:20
// 列表初始化double
double* doublePtr = new double{9.8};
std::cout << "double值: " << *doublePtr << std::endl; // 输出:9.8
// 释放内存
delete intPtr;
delete doublePtr;
return 0;
}
(3)零初始化
如果初始化时不指定具体值(仅使用()
或{}
),基础类型会被初始化为0(或等价的零值,如false
、'\0'
等)。
#include <iostream>
int main() {
// 零初始化int(值为0)
int* intPtr = new int();
std::cout << "int零值: " << *intPtr << std::endl; // 输出:0
// 零初始化bool(值为false)
bool* boolPtr = new bool{};
std::cout << "bool零值: " << std::boolalpha << *boolPtr << std::endl; // 输出:false
// 零初始化char(值为'\0')
char* charPtr = new char();
std::cout << "char零值(ASCII码): " << static_cast<int>(*charPtr) << std::endl; // 输出:0
// 释放内存
delete intPtr;
delete boolPtr;
delete charPtr;
return 0;
}
2.2 自定义类型(结构体/类)的初始化
对于自定义类型(如结构体或类),动态内存初始化会自动调用其构造函数,因此可以通过new
操作符传递构造函数所需的参数。
示例:结构体的动态初始化
#include <iostream>
#include <string>
struct Student {
std::string name;
int age;
// 构造函数
Student(std::string n, int a) : name(n), age(a) {}
void display() {
std::cout << "姓名: " << name << ", 年龄: " << age << std::endl;
}
};
int main() {
// 使用圆括号传递构造函数参数
Student* stu1 = new Student("张三", 18);
stu1->display(); // 输出:姓名: 张三, 年龄: 18
// 使用花括号传递构造函数参数(C++11及以上)
Student* stu2 = new Student{"李四", 19};
stu2->display(); // 输出:姓名: 李四, 年龄: 19
// 释放内存(会自动调用析构函数)
delete stu1;
delete stu2;
return 0;
}
示例:带默认构造函数的类初始化
如果类定义了默认构造函数(无参构造函数),动态初始化时可以不传递参数,直接调用默认构造函数。
#include <iostream>
#include <string>
class Book {
private:
std::string title;
std::string author;
public:
// 默认构造函数
Book() : title("未知书名"), author("未知作者") {}
// 带参数的构造函数
Book(std::string t, std::string a) : title(t), author(a) {}
void show() {
std::cout << "书名: " << title << ", 作者: " << author << std::endl;
}
};
int main() {
// 调用默认构造函数(不传递参数)
Book* book1 = new Book();
book1->show(); // 输出:书名: 未知书名, 作者: 未知作者
// 调用带参数的构造函数
Book* book2 = new Book{"C++编程", "张三"};
book2->show(); // 输出:书名: C++编程, 作者: 张三
delete book1;
delete book2;
return 0;
}
3. 动态数组的初始化
动态数组(使用new[]
分配的数组)的初始化方式与单个对象有所不同,主要支持列表初始化和零初始化,且需注意数组元素的类型是否支持相应的初始化方式。
3.1 基础类型数组的初始化
(1)列表初始化(C++11及以上)
使用花括号{}
为数组元素指定初始值,元素数量可以少于数组大小(剩余元素会被零初始化)。
#include <iostream>
int main() {
// 初始化int数组(指定所有元素)
int* arr1 = new int[3]{1, 2, 3};
std::cout << "arr1: ";
for (int i = 0; i < 3; i++) {
std::cout << arr1[i] << " "; // 输出:1 2 3
}
std::cout << std::endl;
// 初始化double数组(元素数量少于数组大小,剩余元素零初始化)
double* arr2 = new double[5]{3.14, 2.71};
std::cout << "arr2: ";
for (int i = 0; i < 5; i++) {
std::cout << arr2[i] << " "; // 输出:3.14 2.71 0 0 0
}
std::cout << std::endl;
// 释放数组内存
delete[] arr1;
delete[] arr2;
return 0;
}
(2)零初始化
如果仅使用()
或{}
而不指定具体值,数组的所有元素都会被零初始化。
#include <iostream>
int main() {
// 使用()零初始化int数组
int* arr3 = new int[4]();
std::cout << "arr3: ";
for (int i = 0; i < 4; i++) {
std::cout << arr3[i] << " "; // 输出:0 0 0 0
}
std::cout << std::endl;
// 使用{}零初始化char数组
char* arr4 = new char[3]{};
std::cout << "arr4(ASCII码): ";
for (int i = 0; i < 3; i++) {
std::cout << static_cast<int>(arr4[i]) << " "; // 输出:0 0 0
}
std::cout << std::endl;
delete[] arr3;
delete[] arr4;
return 0;
}
3.2 自定义类型数组的初始化
自定义类型数组的初始化会为每个元素调用相应的构造函数,因此需要确保类/结构体有匹配的构造函数。
示例:带构造函数的结构体数组初始化
#include <iostream>
#include <string>
struct Point {
int x;
int y;
// 带参数的构造函数
Point(int a, int b) : x(a), y(b) {}
// 默认构造函数(用于零初始化或未指定参数的元素)
Point() : x(0), y(0) {}
};
int main() {
// 初始化Point数组(指定部分元素,剩余元素调用默认构造函数)
Point* points = new Point[4]{Point(1, 2), Point(3, 4)};
std::cout << "Points数组: " << std::endl;
for (int i = 0; i < 4; i++) {
std::cout << "(" << points[i].x << ", " << points[i].y << ")" << std::endl;
}
// 输出:
// (1, 2)
// (3, 4)
// (0, 0) // 未指定,调用默认构造
// (0, 0) // 未指定,调用默认构造
delete[] points;
return 0;
}
注意:
- 如果自定义类型没有默认构造函数,初始化数组时必须为所有元素指定参数,否则会编译报错。
- 例如,若
Point
结构体没有默认构造函数,new Point[4]{Point(1,2)}
会报错(因为后3个元素无法初始化)。
4. 动态内存初始化的常见问题与注意事项
4.1 初始化方式的兼容性
- 圆括号初始化(
new T(val)
)在C++98中就已支持,但不支持数组的列表初始化。 - 花括号初始化(
new T{val}
)是C++11引入的统一初始化语法,支持单个对象和数组,且能避免“最令人头疼的解析”(如new int()
与new int
的歧义)。 - 建议在C++11及以上版本中优先使用花括号初始化,提高代码一致性和安全性。
4.2 避免过度初始化
- 对于大型数组,若后续会立即为所有元素赋值,零初始化可能造成不必要的性能开销(虽然影响通常很小)。此时可以不初始化:
int* arr = new int[1000];
(元素值为未定义,需后续手动赋值)。
4.3 初始化与智能指针结合
使用智能指针(如std::unique_ptr
、std::shared_ptr
)管理动态内存时,同样支持初始化操作:
#include <iostream>
#include <memory> // 智能指针头文件
int main() {
// unique_ptr初始化单个int
std::unique_ptr<int> uPtr1 = std::make_unique<int>(100);
std::cout << *uPtr1 << std::endl; // 输出:100
// unique_ptr初始化数组(C++14及以上支持make_unique数组版本)
auto uPtr2 = std::make_unique<int[]>(3);
uPtr2[0] = 1;
uPtr2[1] = 2;
uPtr2[2] = 3;
// shared_ptr初始化自定义类型
auto sPtr = std::make_shared<Point>(5, 6); // Point是前面定义的结构体
std::cout << "(" << sPtr->x << ", " << sPtr->y << ")" << std::endl; // 输出:(5, 6)
return 0;
}
- 注意:
std::make_unique
和std::make_shared
会自动进行初始化,是更安全的动态内存分配方式(避免内存泄漏和异常安全问题)。
4.4 初始化失败的情况
- 若动态内存分配失败(如内存不足),
new
会抛出std::bad_alloc
异常(默认行为),此时初始化不会执行。 - 可以使用
new (std::nothrow)
避免抛出异常,此时分配失败返回nullptr
,需手动检查:
#include <iostream>
#include <new> // 包含nothrow
int main() {
// 不抛出异常的分配方式
int* ptr = new (std::nothrow) int[1000000000000]; // 尝试分配过大内存
if (ptr == nullptr) {
std::cout << "内存分配失败!" << std::endl;
} else {
// 分配成功,进行初始化
*ptr = 10;
delete[] ptr;
}
return 0;
}
5. 动态内存初始化的实际应用示例
示例1:动态字符串初始化
#include <iostream>
#include <string>
int main() {
// 初始化动态字符串
std::string* str1 = new std::string("Hello, 动态内存!");
std::cout << *str1 << std::endl; // 输出:Hello, 动态内存!
// 初始化空字符串(调用默认构造函数)
std::string* str2 = new std::string();
*str2 = "通过赋值修改内容";
std::cout << *str2 << std::endl; // 输出:通过赋值修改内容
delete str1;
delete str2;
return 0;
}
示例2:动态二维数组初始化
#include <iostream>
int main() {
int rows = 3;
int cols = 4;
// 动态分配二维数组(数组的数组)
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {
// 初始化每行的元素(零初始化)
matrix[i] = new int[cols]{};
// 为每行赋值
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j;
}
}
// 打印二维数组
std::cout << "二维数组: " << std::endl;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
std::cout << matrix[i][j] << "\t";
}
std::cout << std::endl;
}
// 释放二维数组内存(先释放每行,再释放行指针数组)
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
}
delete[] matrix;
return 0;
}
输出结果:
二维数组:
0 1 2 3
4 5 6 7
8 9 10 11
总结
动态内存分配初始化是C++中确保内存安全使用的重要环节,本教程主要涵盖:
- 单个对象初始化:支持值初始化(
()
)、列表初始化({}
)和零初始化,适用于基础类型和自定义类型(通过构造函数)。 - 动态数组初始化:C++11及以上支持列表初始化,未指定的元素会被零初始化;自定义类型数组需匹配相应的构造函数。
- 注意事项:优先使用花括号初始化提高兼容性,结合智能指针增强安全性,避免初始化方式导致的歧义或错误。
- 实际应用:动态字符串、二维数组等场景的初始化示例。
掌握动态内存初始化的各种方式,能帮助你写出更安全、更简洁的C++代码,减少因未初始化内存导致的程序错误。在实际开发中,建议结合智能指针(如std::make_unique
)进行动态内存管理,进一步提升代码的可靠性。
1 条评论
-
admin SU @ 2025-8-10 15:08:35
使用new操作符进行动态内存分配和初始化的示例代码 cpp 运行
#include <iostream> #include <string> // 自定义结构体示例 struct Person { std::string name; int age; // 构造函数 Person(std::string n, int a) : name(n), age(a) {} // 默认构造函数 Person() : name("未知"), age(0) {} }; int main() { // 1. 基础类型的动态分配与初始化 // 1.1 单个int初始化(值初始化) int* intPtr = new int(100); std::cout << "int值: " << *intPtr << std::endl; // 输出:100 // 1.2 单个double初始化(列表初始化,C++11+) double* doublePtr = new double{3.14159}; std::cout << "double值: " << *doublePtr << std::endl; // 输出:3.14159 // 1.3 零初始化(不指定值,自动为0) bool* boolPtr = new bool(); std::cout << "bool零值: " << std::boolalpha << *boolPtr << std::endl; // 输出:false // 2. 自定义类型的动态分配与初始化 // 2.1 调用带参构造函数 Person* p1 = new Person("张三", 25); std::cout << "Person1: " << p1->name << ", " << p1->age << std::endl; // 输出:张三, 25 // 2.2 调用默认构造函数 Person* p2 = new Person(); std::cout << "Person2: " << p2->name << ", " << p2->age << std::endl; // 输出:未知, 0 // 3. 动态数组的分配与初始化 // 3.1 基础类型数组(列表初始化,C++11+) int* intArr = new int[3]{1, 2, 3}; std::cout << "int数组: "; for (int i = 0; i < 3; ++i) { std::cout << intArr[i] << " "; // 输出:1 2 3 } std::cout << std::endl; // 3.2 自定义类型数组(调用构造函数) Person* personArr = new Person[2]{Person("李四", 30), Person("王五", 28)}; std::cout << "Person数组: " << std::endl; for (int i = 0; i < 2; ++i) { std::cout << personArr[i].name << ", " << personArr[i].age << std::endl; // 输出:李四, 30 王五, 28 } // 释放动态内存 delete intPtr; delete doublePtr; delete boolPtr; delete p1; delete p2; delete[] intArr; delete[] personArr; return 0; }
- 1