对象与对象数组

news/2024/12/19 23:35:05/

对象与对象数组

实验介绍

本章节主要介绍对象数组和对象成员。在实际的开发中,对象数组和对象成员是经常使用的,所以首先需要学习对象数组与对象成员的各种使用方法。

提示:为了方便课程讲解,示例代码使用类内定义的方式实现,如果自己动手做实验的时候希望能够使用分文件类外定义的方式来编写代码。

知识点
  • 对象数组
    • 实例化对象数组
    • 堆上操作对象数组
  • 对象成员
    • 构造和析构顺序
    • 初始化对象成员

对象数组

假设定义了一个学生类,现在要实例化一个班的学生,如果逐个对学生进行实例化操作那肯定是非常麻烦的,这时使用对象数组就能很方便的完成编写。假设有一个点类,如果实例化一个矩形也可以使用对象数组的方式。

点类 - 示例代码 1

定义一个点类,在本小节以后的示例代码中都是用该类,在以下的示例代码中尽量使用之前学到过的知识点。

为了方便查看运行结果,分别在构造函数、拷贝构造函数和析构函数中打印函数的名称。

#include <iostream>
using namespace std;class Point
{
public:// 使用带参数默认构造函数,并使用初始化列表初始化 x,yPoint(double x = 0, double y = 0) : x(x), y(y) {//cout << "Point(double x = 0, double y = 0)" << endl;cout << "Point(double x = " << x << ", double y = " << y << ")" << endl;}// 拷贝构造函数Point(const Point & p) {//cout << "Point(const Point &p)" << endl;// 打印点的值cout << "Point(const Point &p:(" << p.x << ", " << p.y << ")" << endl;this->x = p.x;this->y = p.y;}// 析构函数,由于没有申请内存,析构函数中不需要做什么~Point() {//cout << "~Point()" << endl;cout << "~Point():(" << x << ", " << y << ")" << endl;}// x, y 绑定的成成员函数void setPoint(const Point &p) {this->x = p.x;this->y = p.y;}void setPoint(double x, double y) {this->x = x;this->y = y;}void setX(double x) { this->x = x; }void setY(double y) { this->y = y; }double getX() { return x; }double getY() { return y; }
private:double x;double y;
};

栈上实例化

示例代码 2

为了效果演示,示例代码将对象数组定义在一个函数中,可以在函数执行完之后调用对象数组的析构函数。

// 栈上实例化
void stackInstantiation()
{// 实例化对象数组Point point[3];// 对象数组操作cout << "p[0]: (" << point[0].getX() << ", " << point[0].getY() << ")" << endl;cout << "p[1]: (" << point[1].getX() << ", " << point[1].getY() << ")" << endl;cout << "p[2]: (" << point[2].getX() << ", " << point[2].getY() << ")" << endl;point[0].setPoint(3, 4);cout << "p[0]: (" << point[0].getX() << ", " << point[0].getY() << ")" << endl;
}int main()
{stackInstantiation();return 0;
}

运行结果:

栈上实例化对象数组小结:

  • 实例化对象数组时,每一个对象的构造函数都会被执行。
  • 系统自动销毁栈上对象数组,并且销毁对象数组时,每一个对象析构函数都会被执行。
  • 访问对象数组时使用 [ i ] 的方式访问相应位置的对象。
  • 建议将类数据成员都初始化,可以使用默认值初始化。
  • void setPoint(const Point &p); // 如果是自定义类作为参数时,建议使用引用的方式传入参数,如果该参数在函数中无需修改且没有输出,建议加上 const
示例代码 3
void stackInstantiation()
{Point point[3];Point *p = point;cout << "p: (" << p->getX() << ", " << p->getY() << ")" << endl;p++;cout << "p: (" << p->getX() << ", " << p->getY() << ")" << endl;p++;cout << "p: (" << p->getX() << ", " << p->getY() << ")" << endl;point[2].setPoint(3, 4);cout << "p: (" << p->getX() << ", " << p->getY() << ")" << endl;
}int main()
{stackInstantiation();return 0;
}

运行结果:试验中声明的是对象数组,但是数组其本身也是可以当做指针使用。

堆上实例化

在堆上操作对象数据会比在栈上操作对象数组复杂,但却比栈上操作更加的灵活,如果数据量比较大建议在堆上操作。

示例代码 4
int main()
{// 堆上实例化对象数组Point *point = new Point[3];cout << "p[0]: (" << point[0].getX() << ", " << point[0].getY() << ")" << endl;cout << "p[1]: (" << point[1].getX() << ", " << point[1].getY() << ")" << endl;cout << "p[2]: (" << point[2].getX() << ", " << point[2].getY() << ")" << endl;point[0].setPoint(3, 4);cout << "p[0]: (" << point[0].getX() << ", " << point[0].getY() << ")" << endl;// 释放内存delete [] point;point = nullptr;return 0;
}

运行结果:按照示例代码 3 中的访问方式与栈上访问方式是一样的,跟栈上访问的结果也是一样的。但是别急,后面还有堆上特有的操作。

示例代码 5

在堆上操作对象数据会比在栈上操作对象数组复杂,但却比栈上操作更加的灵活,如果数据量会比较大建议在堆上操作。

// 堆上实例化
int main()
{// 实例化对象Point *p = new Point[3];Point *point = p;cout << "point: (" << point->getX() << ", " << point->getY() << ")" << endl;p++;cout << "point: (" << point->getX() << ", " << point->getY() << ")" << endl;p++;cout << "point: (" << point->getX() << ", " << point->getY() << ")" << endl;point->setPoint(3, 4);cout << "point: (" << point->getX() << ", " << point->getY() << ")" << endl;cout << "p: (" << p->getX() << ", " << p->getY() << ")" << endl;// 释放内存delete [] point;point = nullptr;return 0;
}

运行结果:发现使用指针的方式一样可以访问对象数组,但是使用时也要注意几个问题。

  • 使用 -> 的方式来访问类成员函数,并且不需要使用下标。
  • Point *point = p; 可以发现我又重新声明一个指针,因为一个指针只能指向一个对象,通过指针 ++ 或者 -- 运算符的方式来访问对象数组中对象。

示例代码 6

强调堆上申请空间与释放空间的问题,请注意一下代码与之前的异同之处,在销毁对象数组时使用的是 delete point; 而在之前的示例代码中使用的是 delete [] point; 来销毁对象数组的。

// 堆上实例化
int main()
{// 实例化对象Point *point = new Point[3];cout << "point: (" << point->getX() << ", " << point->getY() << ")" << endl;point++;cout << "point: (" << point->getX() << ", " << point->getY() << ")" << endl;point++;cout << "point: (" << point->getX() << ", " << point->getY() << ")" << endl;point->setPoint(3, 4);cout << "point: (" << point->getX() << ", " << point->getY() << ")" << endl;// 指针使用完成后需要将指针指到起始地址point -= 2;// 释放内存delete point;point = nullptr;return 0;
}

运行结果:

  • linux 环境直接运行报错,但在 Windows 环境下可以正确运行,这就造成了内存泄漏。

对象成员

对象成员即对象中包含其他的对象。

这里示例代码将继续使用示例代码 1 中的点类。

示例代码 7

首先看一下当对象 A 有对象 B 时调用构造函数与析构函数的顺序。

class Line
{
public:Line(const Point & pA, const Point &pB) : pointA(pA), pointB(pB) {cout << "Line(const Point & pA, const Point &pB)" << endl;}Line(double aX, double aY, double bX, double bY) : pointA(aX, aY), pointB(bX, bY) {cout << "Line(double aX, double aY, double bX, double bY)" << endl;}~Line() {cout << "~Line()" << endl;}
private:Point pointA;Point pointB;
};int main()
{// 实例化Line *line = new Line(1, 2, 3, 5);// 释放内存delete line;line = nullptr;return 0;
}

运行结果:可以看到先调用 pointA 的构造函数,再调用 pointB 的构造函数,最后调用 Line 的构造函数;而析构函数时正好反过来的。这也是为什么当对象成员没有默认构造函数时必须要使用初始化列表的原因,因为对象成员先于对象初始化。

示例代码 8

如果将对象成员类型作为参数输入时看看其调用构造函数以及析构函数的顺序。

int main()
{// 实例化Line *line = new Line(Point(1, 2), Point(3, 5));// 释放内存delete line;line = nullptr;return 0;
}

运行结果:对象成员类型作为参数传入时,传入的参数时会临时创建两个对象,初始化完成后临时对象自动销毁。

示例代码 9
int main()
{Line *p = new Line(1, 2, 3, 4);cout << "sizeof (p) = " << sizeof (p) << endl;cout << "sizeof (Line) = " << sizeof (Line) << endl;delete p;p = nullptr;return 0;
}

运行结果:p 指针占 8 字节,Line 类中有两个 Point 类数据成员,Point 类有两个 double 类型数据成员,所以 Line 一共占 32 个字节。

实验总结
  • 使用对象数组时会调用每个对象的构造函数和析构函数。
  • newdeletenew []delete [] 一定要配套使用。
  • 不要越界,不管是栈还是堆,访问数组时都不要越界。
  • 对象数组指针变量本身就是一个指针。
  • 堆上实例化的数组,要注意指针使用方法。
  • 如果是做项目,要考虑使用在堆上实例化申请内存,栈空间比堆空间小很多。
  • 当对象 A 中有常量时必须使用初始化列表。
  • 当对象 A 有其他的对象 B 并且对象 B 没有默认构造参数时需要使用初始化列表。
  • 除了以上两种情况,可以不使用初始化列表,但是推荐使用初始化列表。
  • 对象数据成员和对象成员先于对象初始化。
  • 在实例化对象时需要清楚初始化数据成员的顺序。

http://www.ppmy.cn/news/1265894.html

相关文章

spring 的概述和入门

​ 我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; …

mysql库名规范

mysql库名的一些规范和建议&#xff1a; 库名以小写字母、数字、下划线组成&#xff0c;不要以数字开头。建议不要超过32个字符&#xff0c;但尽量用简短的名称。因为很多地方用到库名&#xff0c;如果库名太长&#xff0c;容易出错。库名选择有意义的名称&#xff0c;尽量与应…

个人博客搭建保姆级教程-Nginx篇

官方文档 nginx documentation 说明 nginx是我们本次教程使用的http服务器。它能承受很高的并发&#xff0c;并且安装简单&#xff0c;占用内存少。 在服务器篇我们提到了nginx的安装&#xff0c;在发布篇我们简述了该怎么放置我们创建的博客html文档。 在本篇&#xff0c…

Linux中的几个重要指令

关於 Process 处理的指令 1. ps ps 是用来显示目前你的 process 或系统 processes 的状况。 以下列出比较常用的参数: 其选项说明如下: -a 列出包括其他 users 的 process 状况。 -u 显示 user - oriented 的 process 状况 。 -x 显示包括没有 terminal 控制的 process 状…

每日一道算法题 1

借鉴文章&#xff1a;Java-敏感字段加密 - 哔哩哔哩 题目描述 给定一个由多个命令字组成的命令字符串&#xff1b; 1、字符串长度小于等于127字节&#xff0c;只包含大小写字母&#xff0c;数字&#xff0c;下划线和偶数个双引号 2、命令字之间以一个或多个下划线_进行分割…

QT使用SQLite 超详细(增删改查、包括对大量数据快速存储和更新)

QTSQLite 在QT中使用sqlite数据库&#xff0c;有多种使用方法&#xff0c;在这里我只提供几种简单&#xff0c;代码简短的方法&#xff0c;包括一些特殊字符处理。在这里也给大家说明一下&#xff0c;如果你每次要存储的数据量很大&#xff0c;建议使用事务&#xff08;代码中…

一文详解Java单元测试Junit

文章目录 概述、Junit框架快速入门单元测试概述main方法测试的问题junit单元测试框架优点&#xff1a;使用步骤&#xff1a; 使用案例包结构 Junit框架的常见注解测试 概述、Junit框架快速入门 单元测试概述 就是针对最小的功能单元&#xff08;方法&#xff09;&#xff0c;…

【mysql】下一行减去上一行数据、自增序列场景应用

背景 想获取if_yc为1连续账期数据 思路 获取所有if_yc为1的账期数据下一行减去上一行账期&#xff0c;如果为1则为连续&#xff0c;不等于1就为断档获取不等于1的最小账期&#xff0c;就是离当前账期最近连续账期 代码 以下为mysql语法&#xff1a; select acct_month f…