C++表达式new与delete知识详解

时间:2022-06-20 05:54:59

C++中,new表达式用于动态创建对象,即在堆(*存储区)空间上为对象分配内存,而程序员也要小心的使用这些申请来的内存空间,当不再使用时应该调用delete表达式来释放该存储空间并且将指针置零。 本文学习了如何动态创建对象,动态创建的对象与一般对象的区别,动态创建的对象的初始化以及释放动态分配的内存等知识点。

C++中分配的内存大致有三类:静态存储区,栈内存和堆内存

其中,静态存储区是在程序编译阶段就已经分配好的,用于全局变量,static变量等;堆栈是比较常用的对象存储方式。

new和delete是C++中程序员申请和撤销堆内存的唯一方式(据我所知)。

1. 创建对象

无论是定义内置类型变量还是类类型变量,都必须指定其对应的数据类型和名字。

动态创建对象只需指定其数据类型,而不必为该对象命名。

也就是说,变量定义得到的对象可以通过额外定义的指针或其对象名本身来访问,而动态创建方式得到的对象只能通过返回的指针来访问,因为该对象并没有名字。

2. 初始化

a. 可以在变量定义或是动态创建的阶段初始化对象,例如:

?
1
2
int i(222); // definition, initialized
int *pi = new int(222) // dynamically, initialized

b. 在这还要说一说变量定义时的默认初始化问题:

内置类型的变量初始化与其定义位置有关:函数外的自动初始化为零,而函数内的没有初始化。对于类类型的变量,程序总是会调用默认构造函数来初始化,这个默认构造函数可以是系统自动生成的,也可以是程序员定义的。如果没有默认构造函数,那么该类型变量的定义也就不能采用默认初始化方式,必须提供显式的初始化式。

动态创建的对象默认初始化与函数内变量定义的初始化方式相同,例如:

?
1
2
string *ps = new string; // initialized to empty string
int *pi = new int; // pi points to an uninitialized int

c. 动态创建对象的值初始化(value-initialize)

这种值初始化不能用于变量定义,而只能用于动态创建方式。

?
1
2
string *ps2 = new string(); // initialized to empty string
int *pi = new int(); // pi points to an int value-initialized to 0

值初始化表明程序员想要做初始化,但并未提供特定的初值。实际上,对于提供了默认构造函数的类类型(如string),没有必要对其对象进行值初始化:无论程序是明确地不初始化还是要求进行值初始化,都会自动调用其默认构造函数初始化该对象。值初始化真正有用的是对于内置类型。另外需要注意的一点是,值初始化的 () 语法必须置于类型名后面,而不是变量名后面,否则得到的是一个函数声明,如下:

?
1
2
int myValue(); // not value-initialized int variable, but a function named myValue
int *myPtr = new int(); // correct! a value-initialized int object

对于没有默认构造函数的类类型,无论是变量定义还是动态创建对象都必须采用显式初始化。

3. 撤销对象

动态创建的对象用完后,程序员必须显式地将该对象占用的内存返还给*存储区。C++提供了delete表达式释放指针所指向的地址空间。

delete myPtr;
上述语句释放 myPtr 指向的 int 型对象所占用的内存。

C++没有明确定义如何释放指向不是用 new 分配的内存地址的指针。此外,编译器通常不能断定一个指针究竟指向什么类型的对象,因此如果调用delete 企图释放指向栈内存地址的指针时,编译器并不会报错,但请尽量不要依赖于该未定义的行为。

在C++中 delete 一个零值指针是合法且安全的,但实际上毫无意义。

悬垂指针(dangling pointer)

删除了指针所指向的对象后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象实际已经不存在了。悬垂指针往往导致程序错误,并且很难检测。因此,在调用delete 释放指针所指对象内存后应该立刻将指针置零。

4. Const 对象的动态分配和回收

const 对象,必须在定义阶段或动态创建阶段进行初始化,并且初始化之后其值不能再修改。

与其他 const 对象的地址一样,由于 new 返回的地址上存放的是 const 对象,因此该地址只能赋给指向 const 的指针。

?
1
2
3
// allocate and initialize a const object
const int *pci = new const int(222); // initialize to 222
const int *pci2 = new const int();  // initialize to 0

尽管程序员不能改变 const 对象的值,但可撤销对象本身。

delete pci; // ok: delete a const object
三种常见的程序错误都与动态内存分配相关:

1、删除动态分配内存失败,称为内存泄漏(memory leak)
2、读写已删除的对象
3、对同一个内存空间使用两次 delete 表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。第二个指针的 delete 操作往往会破坏*存储区。
个人实践部分:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <cstring>
using namespace std;
 
int main(void)
{
  string str = "hello str";
  // strNew points to dynamically allocated,
  // initialized to empty string
  string *strNew = new string;
  
  cout<<"str object address: "<<&str<<endl;
  cout<<"strNew pointer itself address: "<<&strNew<<endl;
  cout<<"strNew pointer to address: "<<strNew<<endl;
  
  // assignment
  *strNew = "hello strNew";
  cout<<"strNew pointer to address: "<<strNew<<endl;
  
  // free memory
  delete strNew;
  cout<<"strNew pointer to address: "<<strNew<<endl;
  strNew = NULL;
  
  // point to other object
  strNew = &str;
  cout<<"strNew pointer to address: "<<strNew<<endl;
  
  
  const int cvalue(10);
  // iptr points to a const int object
  const int *iptr = new const int(222);
  cout<<"iptr value: "<<*iptr<<endl;
  delete iptr;
  iptr = NULL;
  
  iptr = &cvalue;
  cout<<"iptr value: "<<*iptr<<endl;
  
  return 0;
}


一次运行的结果如下:

?
1
2
3
4
5
6
7
8
str object address: 0x28ff24
strNew pointer itself address: 0x28ff20
strNew pointer to address: 0x602f70
strNew pointer to address: 0x602f70
strNew pointer to address: 0x602f70
strNew pointer to address: 0x28ff24
iptr value: 222
iptr value: 10

程序中间将原来指向 new 创建的对象的指针重定向到一般的变量,可以看到指针存放地址的改变。另外需要注意,在释放 new 对象之前不要将指针重新指向某个其他对象,这样会导致原来动态创建的对象没有指针指向它,无法释放内存空间。
以上就是本文的全部内容,希望对大家的学习有所帮助。