【带头学C++】----- 九、类和对象 ---- 9.8 动态对象创建

目录

9.8 动态对象创建

9.8.1 动态创建对象基础概念

9.8.2 C语言创建动态对象的

9.8.3 new创建动态对象

9.8.4 delete释放动态对象

9.8.5 动态对象数组


9.8 动态对象创建

9.8.1 动态创建对象基础概念

       在创建数组时,我们通常需要预先指定数组的长度,然后编译器会为数组分配相应大小的空间。然而,在使用数组时会出现一些问题。一方面,可能会造成空间的浪费,因为数组的空间可能会过大。另一方面,可能会出现空间不足的情况。因此,对于数组来说,

     如果能够根据需要动态地分配空间大小就更好了......思考ing

        动态的意思表示了空间分配的不确定性。为了解决这个普遍的编程问题,最基本的要求之一就是可以在运行时创建和销毁对象。虽然 C++ 提供了动态内存分配的函数(如 malloc 和 free),可以在运行时从堆中分配存储单元,但这些函数在 C++ 中并不能很好地完成对象的初始化工作。

//从堆区栈区可以自己申请到空间

int n = 101;

int *p = new int;

对象可以这样吗?

接着往下看......

9.8.2 C语言创建动态对象的

首先不管是C语言还是C++,他们都是为了对象存储而申请内存空间的。但是方法不一样

1、为对象分配内存,申请空间。
2、调用构造函数来初始化那块内存,第一步我们能保证实现,需要我们确保第二步一定能发生。C++强迫我们这么做是因为使用末初始化的对象是程序出错的一个重要原因。C语言中动态分配内存方法为了在运行时动态分配内存,C在他的标准库中提供了一些函数,malloc以及calloc和realloc,释放内存的free,这些函数是有效的、但是原始的,需要程序员理解和小心使用。为了使用c的动态内存分配函数在堆上创建一个类的实例,我们需要按照下面这样做:

出现的问题:

1) 程序员必须确定对象的长度。
2) malloc返回一个void指针,c++不允许将void赋值给其他任何指针,必须强转。
3) malloc可能申请内存失败,所以必须判断返回值是否为null来确保内存分配成功。
4)用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造函数是由编译器调用),用户有可能忘记调用初始化函数。

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

9.8.3 new创建动态对象

        C++中解决动态内存分配的方案是把创建一个对象所需要的操作都结合在一个称为new的运算符里。可以使用C++中的new运算符来创建动态对象。new运算符会自动为对象分配内存并调用对象的构造函数进行初始化

使用new创建动态对象的语法如下:

Class* objectPtr = new Class(arguments);

其中,Class是要创建的对象的类名,arguments是传递给构造函数的参数。

例如,创建一个Person类的动态对象可以这样写:

Person* personPtr = new Person("John", 25);

        这将在堆上动态分配内存,创建一个Person对象,并调用Person类的构造函数进行初始化。返回的personPtr是一个指向动态对象的指针。
        New操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显式确定调用是否成功。现在我们发现在堆里创建对象的讨程变得简单了,只需要一个简单的表达式,它带有内置的长度计算、类型转换和安全检查。这样在堆创建一个对象和在栈里创建对象一样简单。

9.8.4 delete释放动态对象

        delete是new关键字对应的释放内存的关键字,new和delete一般成对出现,new申请的堆区内存,需要手动释放,只需要程序员使用delete 加上需要释放的对象的名称就可以实现手动回收内存。使用delete以后,会调用构造函数对应的析构函数,然后进行内存空间的释放。

s        记得在不需要使用动态对象时,使用delete来释放动态分配的内存,以防止内存泄漏。

delete personPtr;

        以上是使用newdelete手动进行内存管理的传统方法。然而,现代C++提供了更安全和方便的智能指针(如std::shared_ptrstd::unique_ptr)来管理动态对象的生命周期,推荐使用它们来避免手动释放内存的繁琐和潜在错误。

代码:

class Person2{
public:
    Person2(){
       cout << "无参构造函数!"<<endl;
       pName = new char[strlen("undefined")+1];
       strcpy_s(pName,strlen("undefined")+1,"undefined");
       mAge = 0;
    }
    Person2(const char* name,int age){
       cout << "有参构造函数!"<<endl;
       pName = new char[strlen(name)+1];
       strcpy_s(pName,strlen(name)+1,name);
       mAge = age;
    }
    void showPerson(){
        cout <<"Name:"<< pName << " Age:" << mAge << endl;
    }
    ~Person2(){
        cout <<"析构函数!"<<endl;
        if (pName != nullptr){
            delete [] pName;  //值得注意一个地方,释放const char *类型,需要带[]
            pName = nullptr;
        }
    }
public:
    int mAge;
    char *pName;
};
void test03(){
    Person2* person1 = new Person2;
    Person2* person2 = new Person2("ohn",33);
    person1->showPerson();
    person2->showPerson();
    delete person1;
    delete person2;
}

9.8.5 动态对象数组

         在创建一个对象数组时,需要为数组中的每个对象调用构造函数进行初始化。这是因为对象数组需要为每个元素分配内存,并执行构造函数来初始化对象的状态。

        在栈上创建对象数组时,你可以使用聚合初始化来初始化数组元素的值。这样做不会调用默认构造函数,而是直接为数组元素设置指定的初始值。

        对于堆上创建的对象数组,如果对象没有提供默认构造函数,那么你必须提供一个可以将对象初始化的构造函数。否则,无法正确地初始化数组元素。

        需要注意的是,如果对象数组中的对象是具有非平凡析构函数的类类型对象,那么你需要手动释放内存,以避免内存泄漏。

拓展:无需记忆,了解一下

       聚合初始化是一种在创建对象时,使用大括号 {} 或等号 = 进行初始化的方式。它可用于初始化聚合类型的成员变量或数组元素。聚合类型包括数组、结构体和类(满足特定条件的类)。

#include <iostream>

struct Point {
    int x;
    int y;
};

int main() {
    // 聚合初始化结构体对象
    Point p1 = {2, 3};
    std::cout << "p1.x = " << p1.x << ", p1.y = " << p1.y << std::endl;

    // 聚合初始化数组元素
    Point points[] = {{1, 2}, {3, 4}, {5, 6}};
    std::cout << "points[0].x = " << points[0].x << ", points[0].y = " << points[0].y << std::endl;
    std::cout << "points[1].x = " << points[1].x << ", points[1].y = " << points[1].y << std::endl;
    std::cout << "points[2].x = " << points[2].x << ", points[2].y = " << points[2].y << std::endl;

    return 0;
}

        在上述示例中,我们定义了一个结构体 Point,具有两个整数成员变量 x 和 y。我们使用聚合初始化方式,分别在 p1 和 points 中初始化了结构体对象和数组元素。在输出中可以看到这些对象被正确地初始化了。

        通过使用聚合初始化,我们可以在创建对象时直接为其成员变量指定一个初始值,个数和顺序需要与对象的定义一致。这种初始化方式非常方便,尤其是在初始化较大的数组时可以提高代码的可读性和简洁性。

        当一个类的析构函数不是使用默认实现时,我们称其为非平凡析构函数。一个非平凡析构函数可能会执行一些特殊的清理操作,如释放资源、关闭文件或释放动态分配的内存。

示例:

#include <iostream>

class Resource {
public:
    Resource() {
        std::cout << "Resource acquired." << std::endl;
    }

    ~Resource() {
        // 假设这里有一个复杂的清理逻辑
        std::cout << "Resource released." << std::endl;
    }
};

int main() {
    Resource* resources = new Resource[5];
    delete[] resources;

    return 0;
}

        在上述示例中,我们定义了一个名为 Resource 的类,它具有一个非平凡的析构函数。在 main 函数中,我们使用 new 操作符在堆上动态分配了一个包含 5 个 Resource 对象的数组。当我们使用 delete[] 删除数组时,会自动调用每个 Resource 对象的析构函数进行清理操作。

        通过运行这段代码,你将看到每个 Resource 对象在释放时输出 "Resource released." 的消息。这证明了非平凡析构函数在析构对象时执行了一些特殊的操作。

接着回归正题,除了在栈上可以聚合初始化,必须提供一个默认的构造函数;

 代码:

class Person3{
public:
    Person3(){
       cout << "无参构造函数!"<<endl;
       pName = nullptr;
       mAge = 0;
    }
    Person3(const char* name,int age){
       cout << "有参构造函数!"<<endl;
       pName = new char[strlen(name)+1];
       strcpy_s(pName,strlen(name)+1,name);
       mAge = age;
    }
    ~Person3(){
        cout <<"析构函数!"<<endl;
        if (pName != nullptr){
            delete [] pName;
            pName = nullptr;
        }
    }
public:
    int mAge;
    char *pName;
};
void test04(){
    //栈聚合初始化,实际就是批量好几个对象的初始化
    Person3 person[] = {Person3("Jery",18),Person3("Tom",19)};
    cout << person[1].pName<<endl;
    //创建堆上对象数组必须提供构造函数
    Person3* worker = new Person3[10];
//此时会触发10个构造,对象数组。数组每个元素为Person3类型的对象
    delete [] worker;
//注意:new和delete成对出现,一个申请初始化构造,一个析构释放空间
}

注:代码的注释部分内容,也需要仔细阅读,不可大意。看到这里,点个赞赞吧,收藏、关注看下一章。谢谢各位大佬支持,加油!!!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/234063.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

三个臭皮匠(ctr,nerdctl,crictl)顶一个诸葛亮(docker)

文章目录 containerd简介 nerdctl简介安装精简 Minimal 安装完整Full 安装启动服务 命令参数容器运行容器列出容器详情容器日志容器进入容器停止容器删除镜像列表镜像拉取镜像标签镜像导出镜像导入镜像删除镜像构建配置tab键配置加速配置仓库http方式https方式 ctr简介命令参数…

java--Calendar

1.Calendar ①代表的是系统此刻时间对应的日历 ②通过它可以单独获取、修改时间中的年、月、日、时、分、秒等(月份是从0开始的)。 2.Calender日历类的常见方法 注意&#xff1a;calender是可变对象&#xff0c;一旦修改后其对象本身表示的时间将产生变化。

8. MySQL 触发器

目录 概述 定义 触发器特性&#xff1a; 基础操作 创建触发器 NEW和OLD 其他操作 查看触发器 删除触发器 注意事项 概述 定义 触发器&#xff0c;就是一种特殊的存储过程。触发器和存储过程一样是一个能够完成特定功能、存储在数据库服务器上的SQL片段&#xff0c;但是触…

Serverless单体架构的崛起

在过去的几十年里&#xff0c;我们见证了应用架构以快速的速度演变。当我还是一个年轻的程序员时&#xff0c;开始编写一个简单的代码库&#xff0c;我们可以称之为单体应用。 我记得为前端编写了一些HTML/CSS&#xff0c;后端用了一些Java。但后来&#xff0c;随着时代发展和…

系统设计之Nginx

一、Nginx是什么 Nginx ("engine x") 是一个开源的&#xff0c;支持高性能、高并发的 Web 服务和代理服务软件。它是由俄罗斯人 Igor Sysoev 开发的&#xff0c;最初被应用在俄罗斯的大型网站 www.rambler.ru 上。后来作者将源代码以类 BSD 许可的形式开源出来供全球…

2023年山东省职业院校技能大赛信息安全管理与评估第一阶段样题

2023年山东省职业院校技能大赛信息安全管理与评估样题 竞赛需要完成三个阶段的任务&#xff0c;分别完成三个模块&#xff0c;总分共计 1000 分。三个模块内容和分值分别是&#xff1a; \1. 第一阶段&#xff1a;模块一 网络平台搭建与设备安全防护&#xff08;240 分钟&…

Nginx负载均衡实战

&#x1f3b5;负载均衡组件 ngx_http_upstream_module https://nginx.org/en/docs/http/ngx_http_upstream_module.html upstream模块允许Nginx定义一组或多组节点服务器组&#xff0c;使用时可以通过多种方式去定义服务器组 样例&#xff1a; upstream backend {server back…

2023中国(海南)国际高尔夫旅游文化博览会 暨国际商界峰层·全球华人高尔夫精英巡回赛 全国颍商自贸港行盛大启幕

2023中国&#xff08;海南&#xff09;国际高尔夫旅游文化博览会&#xff08;以下简称“海高博”&#xff09;暨全国颍商走进海南自贸港于12月7-9日在海口观澜湖盛大开幕。该活动由中国国际贸易促进委员会海南省委员会、海南省旅游和文化广电体育厅主办&#xff0c;中国国际商会…

windows安装rabbitmq

注&#xff1a;安装 rabbitmq 之前需要先安装 erlang 语言包&#xff0c;否则安装过程会报错 erlang 语言包名称&#xff1a;otp_win64_25.3.2.3.exe。erlang 和 rabbitmq 有严格的版本对应。 部分版本对应&#xff1a; 安装 1、安装 erlang 并配置环境变量 1.1 双击 otp_…

在Deepin中安装x11vnc工具并结合内网穿透软件实现远程访问桌面

文章目录 1. 安装x11vnc2. 本地远程连接测试3. Deepin安装Cpolar4. 配置公网远程地址5. 公网远程连接Deepin桌面6. 固定连接公网地址7. 固定公网地址连接测试 x11vnc是一种在Linux系统中实现远程桌面控制的工具&#xff0c;它的原理是通过X Window系统的协议来实现远程桌面的展…

007:vue实现与iframe实现页面数据通信

首页先搭建一个html页面和vue页面&#xff0c;在vue页面中&#xff0c;嵌入我们需要的iframe页面 文章目录 1. 搭建 html 页面和 vue 页面2. 实现 iframe 向 vue 页面通信3. 在实现 vue 向 iframe 页面通信 1. 搭建 html 页面和 vue 页面 暂定为 iframeDemo.html 和 vueDemo.v…

Linux中的SNAT与DNAT实践

Linux中的SNAT与DNAT实践 1、SNAT的介绍1.1&#xff0c;SNAT概述1.2&#xff0c;SNAT源地址转换过程1.3&#xff0c;SNAT转换 2、DNAT的介绍2.1&#xff0c;DNAT概述2.2&#xff0c;DNAT转换前提条件2.3&#xff0c;DNAT的转换 3、防火墙规则的备份和还原4、tcpdump抓包工具的运…

App 设计工具中的启动任务和输入参数

目录 创建 startupFcn 回调 定义输入 App 参数 可以使用 App 设计工具创建一个特殊函数&#xff0c;该函数在 App 启动时、但在用户与 UI 进行交互之前执行。此函数称为 startupFcn 回调&#xff0c;它非常适用于设置默认值、初始化变量或执行影响 App 初始状态的命令。例如&…

redis中使用事务保护数据完整性

事务是指一个执行过程&#xff0c;要么全部执行成功&#xff0c;要么失败什么都不改变。不会存在一部分成功一部分失败的情况&#xff0c;也就是事务的ACID四大特性&#xff08;原子性、一致性、隔离性、持久性&#xff09;。但是redis中的事务并不是严格意义上的事务&#xff…

使用Pytorch实现VGGNet(含VGGNet特征整理)

知识点整理 VGGNet 的主要特点&#xff1a; 采用3x3的小卷积核将模型提升到11-19层进一步提升了模型的泛化能力模型结构相对简洁 VGGNet主要解决了以下几个问题&#xff1a; 首先在当时的卷积神经网络中网络结构越深网络表现的性能越好&#xff0c;但同时也会带来较大的复杂…

IP地址定位技术为网络安全建设提供全新方案

随着互联网的普及和数字化进程的加速&#xff0c;网络安全问题日益引人关注。网络攻击、数据泄露、欺诈行为等安全威胁层出不穷&#xff0c;对个人隐私、企业机密和社会稳定构成严重威胁。在这样的背景下&#xff0c;IP地址定位技术应运而生&#xff0c;为网络安全建设提供了一…

CPU设计——Triumphcore——MP_work版本

该版本用作系统寄存器的实现&#xff0c;M/S/U状态的实现与切换&#xff0c;以及load/store的虚实地址转换 设计指标 2023.12.8 2023.12.9 不实现mideleg和medeleg&#xff0c;因此一旦出现异常&#xff0c;直接切换至M态&#xff0c; 调试记录 到存储区中取PTE要额外至少…

HNU计算机视觉作业三

前言 选修的是蔡mj老师的计算机视觉&#xff0c;上课还是不错的&#xff0c;但是OpenCV可能需要自己学才能完整把作业写出来。由于没有认真学&#xff0c;这门课最后混了80多分&#xff0c;所以下面作业解题过程均为自己写的&#xff0c;并不是标准答案&#xff0c;仅供参考 …

leetcode:643. 子数组最大平均数 I(滑动窗口)

一、题目 链接&#xff1a;643. 子数组最大平均数 I - 力扣&#xff08;LeetCode&#xff09; 函数原型&#xff1a; double findMaxAverage(int* nums, int numsSize, int k) 二、思路 滑动窗口&#xff1a; 先计算数组前k个元素总和&#xff0c;作为第一个窗口&#xff0c;默…

软件设计之组合模式

组合模式&#xff1a;将对象组合成树形结构。 案例&#xff1a;公司管理。一个公司可以分总公司和分公司&#xff0c;无论是总公司还是分公司都有自己的部门&#xff0c;如人力资源管理部门、财务部门。分公司可以建立自己在不同地域的办事处。请使用组合模式打印出某个公司的…