25 C++ - 动态对象创建

简介: 25 C++ - 动态对象创建

当我们创建数组的时候,总是需要提前预定数组的长度,然后编译器分配预定长度的数组空间,在使用数组的时,会有这样的问题,数组也许空间太大了,浪费空间,也许空间不足,所以对于数组来讲,如果能根据需要来分配空间大小再好不过。

所以动态的意思意味着不确定性。

为了解决这个普遍的编程问题,在运行中可以创建和销毁对象是最基本的要求。当然c早就提供了动态内存分配(dynamic memory allocation),函数malloc和free可以在运行时从堆中分配存储单元。

然而这些函数在c++中不能很好的运行,因为它不能帮我们完成对象的初始化工作。

1. 对象创建

当创建一个c++对象时会发生两件事:

  1. 为对象分配内存
  2. 调用构造函数来初始化那块内存

第一步我们能保证实现,需要我们确保第二步一定能发生。c++强迫我们这么做是因为使用未初始化的对象是程序出错的一个重要原因。

2. C动态分配内存方法

为了在运行时动态分配内存,c在他的标准库中提供了一些函数,malloc以及它的变种calloc和realloc,释放内存的free,这些函数是有效的、但是原始的,需要程序员理解和小心使用。为了使用c的动态内存分配函数在堆上创建一个类的实例,我们必须这样做:

class Person{
public:
  Person(){
    mAge = 20;
    pName = (char*)malloc(strlen("john")+1);
    strcpy(pName, "john");
  }
  void Init(){
    mAge = 20;
    pName = (char*)malloc(strlen("john")+1);
    strcpy(pName, "john");
  }
  void Clean(){
    if (pName != NULL){
      free(pName);
    }
  }
public:
  int mAge;
  char* pName;
};
int main(){
  //分配内存
  Person* person = (Person*)malloc(sizeof(Person));
  if(person == NULL){
    return 0;
  }
  //调用初始化函数
  person->Init();
  //清理对象
  person->Clean();
  //释放person对象
  free(person);
  return EXIT_SUCCESS;
}

问题:

1)程序员必须确定对象的长度。

2)malloc返回一个void*指针,c++不允许将void*赋值给其他任何指针,必须强转。

3)malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功。

4)用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造函数是由编译器调用),用户有可能忘记调用初始化函数。

c的动态内存分配函数太复杂,容易令人混淆,是不可接受的,c++中我们推荐使用运算符new 和 delete.

3. new operator

C++中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为new的运算符里。当用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化。

Person* person = new Person;

相当于:

Person* person = (Person*)malloc(sizeof(Person));
  if(person == NULL){
    return 0;
  }
person->Init();

new操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显式确定调用是否成功。

现在我们发现在堆里创建对象的过程变得简单了,只需要一个简单的表达式,它带有内置的长度计算、类型转换和安全检查。这样在堆创建一个对象和在栈里创建对象一样简单。

4. delete operator

new表达式的反面是delete表达式。delete表达式先调用析构函数,然后释放内存。正如new表达式返回一个指向对象的指针一样,delete需要一个对象的地址。

delete只适用于由new创建的对象。

如果使用一个由malloc或者calloc或者realloc创建的对象使用delete,这个行为是未定义的。因为大多数new和delete的实现机制都使用了malloc和free,所以很可能没有调用析构函数就释放了内存。

如果正在删除的对象的指针是NULL,将不发生任何事,因此建议在删除指针后,立即把指针赋值为NULL,以免对它删除两次,对一些对象删除两次可能会产生某些问题。

class Person{
public:
  Person(){
    cout << "无参构造函数!" << endl;
    pName = (char*)malloc(strlen("undefined") + 1);
    strcpy(pName, "undefined");
    mAge = 0;
  }
  Person(char* name, int age){
    cout << "有参构造函数!" << endl;
    pName = (char*)malloc(strlen(name) + 1);
    strcpy(pName, name);
    mAge = age;
  }
  void ShowPerson(){
    cout << "Name:" << pName << " Age:" << mAge << endl;
  }
  ~Person(){
    cout << "析构函数!" << endl;
    if (pName != NULL){
      delete pName;
      pName = NULL;
    }
  }
public:
  char* pName;
  int mAge;
};
void test(){
  Person* person1 = new Person;
  Person* person2 = new Person("John",33);
  person1->ShowPerson();
  person2->ShowPerson();
  delete person1;
  delete person2;
}

5. 用于数组的new和delete

使用new和delete在堆上创建数组非常容易。

//创建字符数组
char* pStr = new char[100];
//创建整型数组
int* pArr1 = new int[100]; 
//创建整型数组并初始化
int* pArr2 = new int[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//释放数组内存
delete[] pStr;
delete[] pArr1;
delete[] pArr2;

当创建一个对象数组的时候,必须对数组中的每一个对象调用构造函数,除了在栈上可以聚合初始化,必须提供一个默认的构造函数。

class Person{
public:
  Person(){
    pName = (char*)malloc(strlen("undefined") + 1);
    strcpy(pName, "undefined");
    mAge = 0;
  }
  Person(char* name, int age){
    pName = (char*)malloc(sizeof(name));
    strcpy(pName, name);
    mAge = age;
  }
  ~Person(){
    if (pName != NULL){
      delete pName;
    }
  }
public:
  char* pName;
  int mAge;
};
void test(){
  //栈聚合初始化
  Person person[] = { Person("john", 20), Person("Smith", 22) };
  cout << person[1].pName << endl;
    //创建堆上对象数组必须提供构造函数
  Person* workers = new Person[20];
}

6. delete void*可能会出错

如果对一个void*指针执行delete操作,这将可能成为一个程序错误,除非指针指向的内容是非常简单的,因为它将不执行析构函数.以下代码未调用析构函数,导致可用内存减少。

class Person{
public:
  Person(char* name, int age){
    pName = (char*)malloc(sizeof(name));
    strcpy(pName,name);
    mAge = age;
  }
  ~Person(){
    if (pName != NULL){
      delete pName;
    }
  }
public:
  char* pName;
  int mAge;
};
void test(){
  void* person = new Person("john",20);
  delete person;
}

7. 使用new和delete采用相同形式

Person* person = new Person[10];
delete person;

以上代码有什么问题吗?(vs下直接中断、qt下析构函数调用一次)

使用了new也搭配使用了delete,问题在于Person有10个对象,那么其他9个对象可能没有调用析构函数,也就是说其他9个对象可能删除不完全,因为它们的析构函数没有被调用。

我们现在清楚使用new的时候发生了两件事: 一、分配内存;二、调用构造函数,那么调用delete的时候也有两件事:一、析构函数;二、释放内存。

那么刚才我们那段代码最大的问题在于:person指针指向的内存中到底有多少个对象,因为这个决定应该有多少个析构函数应该被调用。换句话说,person指针指向的是一个单一的对象还是一个数组对象,由于单一对象和数组对象的内存布局是不同的。更明确的说,数组所用的内存通常还包括“数组大小记录”,使得delete的时候知道应该调用几次析构函数。单一对象的话就没有这个记录。单一对象和数组对象的内存布局可理解为下图:

本图只是为了说明,编译器不一定如此实现,但是很多编译器是这样做的。

当我们使用一个delete的时候,我们必须让delete知道指针指向的内存空间中是否存在一个“数组大小记录”的办法就是我们告诉它。当我们使用delete[],那么delete就知道是一个对象数组,从而清楚应该调用几次析构函数。

结论:

如果在new表达式中使用[],必须在相应的delete表达式中也使用[].如果在new表达式中不使用[], 一定不要在相应的delete表达式中使用[].

目录
相关文章
|
2月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
1月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
1月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
2月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
1月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
6月前
|
编译器 C++
C++之类与对象(完结撒花篇)(上)
C++之类与对象(完结撒花篇)(上)
64 0
|
3月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
117 19
|
2月前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
|
2月前
|
存储 程序员 C语言
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
3月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
123 13

热门文章

最新文章

下一篇
oss创建bucket