动态创建对象
1. 什么是动态创建对象?
在学习之前的知识点时,我们知道有静态存储期和自动存储期。 静态存储期 的对象在程序的整个生命周期内都存在,全局变量和static修饰的局部变量都属于这一类。自动存储期的对象 ,这些对象在函数或代码块的执行期间存在,函数结束时自动销毁。局部变量是典型的自动存储期对象,它们在函数内部定义,函数结束时自动释放。
那么有没有人为命令的可以存储或者删除的对象呢?那就是动态创建对象,它允许你在程序运行时根据需要动态分配和释放内存。在处理不确定数量的对象、延迟初始化或者管理大型资源时非常有用。
2. 如何动态创建对象呢?
- 动态创建对象是通过new运算符来分配内存,而delete运算符用于释放内存。
cpp
#include <iostream>
using namespace std;
int main() {
// 动态创建一个整数
int* x = new int; // 分配一个整数的内存
// 给动态分配的整数赋值
*x = 42;
// 输出动态分配的整数
cout << "Value: " << *x << endl;
// 释放内存
delete x; // 释放内存
return 0;
}
在内存中的动态变化是这样的:
-
动态创建对象有以下初始化的方法:
-
动态创建数组
如果需要动态创建一个数组,可以使用new[]和delete[]。
cpp
#include <iostream>
using namespace std;
int main() {
int size = 5;
int* arr = new int[size]; // 动态分配一个整数数组
// 给数组赋值
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
// 输出数组内容
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
// 释放内存
delete[] arr; // 释放数组内存
return 0;
}
3.动态对象的存储位置--堆
- 堆(Heap)
堆是一个动态分配的内存区域,主要用于存储程序运行时需要灵活管理的资源。堆的大小通常是动态的,可以根据需要分配和释放。 - 堆的特点
手动管理:堆上的内存分配和释放需要程序员手动管理(使用new和delete)。
灵活大小:堆的大小可以动态扩展,适合存储大对象或动态数量的对象。
生命周期可控:堆上的对象生命周期由程序员控制,可以跨越函数调用。
访问速度较慢:堆的内存分配和释放速度比栈慢,因为需要额外的内存管理操作。
与之作为对比的栈存储区域,可以做一个简单的了解。
- 栈(stack)
栈是一个后进先出(LIFO,Last In, First Out)的内存区域,主要用于存储函数调用的上下文信息和局部变量。栈的大小通常是固定的,由操作系统在程序启动时分配。
- 栈的特点
自动管理:栈上的内存分配和释放是自动的,由编译器管理。
快速访问:栈的内存分配和释放速度非常快,因为它是连续的内存区域,且操作简单。
有限大小:栈的大小通常是有限的(例如几MB),不能动态扩展。
局部性:栈上的变量通常是函数内部的局部变量,生命周期与函数的调用相关。
4. 动态创建对象失败
如果由于内存空间不足,创建对象失败,通常会像下面这样处理:
抛出异常 :默认情况下,C++中的new操作符会在内存分配失败时抛出std::bad_alloc异常。这使得程序在遇到内存分配失败时能够及时发现并处理错误。C++通过try、throw和catch三个关键字实现异常处理。try块用于包裹可能会抛出异常的代码。当try块中的代码抛出异常时,程序会跳转到相应的catch块处理异常。throw语句用于抛出一个异常。异常可以是任何类型,通常使用标准异常类或自定义异常类。catch块用于捕获和处理异常。可以有多个catch块来处理不同类型的异常。
例如:
cpp
// 循环动态创建数组对象(异常处理)
#include <new>
#include <iostream>
using namespace std;
int main() {
cout << "循环创建元素个数为30000的double型数组。\n";
while (true) {
try {
double* a = new double[30000]; // 创建数组
} catch (bad_alloc) {
cout << "数组创建失败,程序中断。\n";
return 1;
}
}
}
返回空指针:在某些情况下,程序员可能不希望因为内存分配失败而中断程序的执行。这时可以使用std::nothrow来防止抛出异常,而是返回一个空指针。如果在创建对象时指定"(nothrow)",则也可以在不引起异常的情况下返回空指针,示例如下:
cpp
// 循环动态创建数组对象(抑制异常发生)
#include <cstdlib>
#include <iostream>
using namespace std;
int main() {
cout << "循环创建元素个数为30000的double型数组。\n";
while (true) {
double* a = new(nothrow) double[30000]; // 创建(抑制异常发生)
if (a == NULL) {
cout << "数组创建失败,程序中断。\n";
return 1;
}
}
}
5. 使用void指针动态创建对象
void指针是一种特殊的指针,它可以指向任何数据类型。void指针的类型是void*。由于void指针不能直接访问它所指向的数据,因此需要进行类型转换。
cpp
#include <iostream>
using namespace std;
int main() {
int num = 42;
void* ptr = # // ptr指向num的地址
// 通过类型转换访问数据
cout << "Value: " << *static_cast<int*>(ptr) << endl;
return 0;
}
在动态内存分配中,需要使用malloc和calloc等函数返回void*,再进行类型转换。malloc函数用于分配一块未初始化的内存。见下面例子。
cpp
#include <iostream>
#include <cstdlib> // 用于malloc和free
using namespace std;
int main() {
// 动态分配一个整数
int* ptrInt = static_cast<int*>(malloc(sizeof(int)));
if (ptrInt != nullptr) {
*ptrInt = 42;
cout << "Integer value: " << *ptrInt << endl;
free(ptrInt); // 释放内存
}
// 动态分配一个字符数组
char* ptrChar = static_cast<char*>(malloc(10 * sizeof(char)));
if (ptrChar != nullptr) {
strcpy(ptrChar, "Hello");
cout << "String: " << ptrChar << endl;
free(ptrChar); // 释放内存
}
return 0;
}
cpp
#include <iostream>
#include <cstdlib> // 用于calloc和free
using namespace std;
int main() {
// 动态分配一个整数数组
int* ptrIntArray = static_cast<int*>(calloc(5, sizeof(int)));
if (ptrIntArray != nullptr) {
for (int i = 0; i < 5; i++) {
ptrIntArray[i] = i * 10;
}
cout << "Integer array values: ";
for (int i = 0; i < 5; i++) {
cout << ptrIntArray[i] << " ";
}
cout << endl;
free(ptrIntArray); // 释放内存
}
// 动态分配一个字符数组
char* ptrCharArray = static_cast<char*>(calloc(10, sizeof(char)));
if (ptrCharArray != nullptr) {
strcpy(ptrCharArray, "Hello");
cout << "String: " << ptrCharArray << endl;
free(ptrCharArray); // 释放内存
}
return 0;
}