C++的面向对象学习(4):对象的重要特性:构造函数与析构函数

文章目录

  • 前言:将定义的类放在不同文件夹供主文件调用的方法
  • 一、构造函数与析构函数
    • 1.什么是构造函数和析构函数?
    • 2.构造函数和析构函数的语法
    • 3.构造函数的具体分类和调用方法
      • ①总的来说,构造函数分类为:默认无参构造、有参构造、拷贝构造
      • ②举一个全面的例子
    • 4.构造函数的使用时机
      • ①用一个已经创建完毕的对象来初始化一个新对象
      • ②值传递的方式给函数参数传值
  • 二、构造函数的进阶知识
    • 1.构造函数的调用规则
    • 2.初始化列表:给类中的成员属性初始化的另一种方法
    • 3.一个类的对象作为另一个类的成员,如何对这两个类进行构造函数初始化?

前言:将定义的类放在不同文件夹供主文件调用的方法

人话:.h文件存放类的成员的声明,.c文件存放的是类的成员函数的定义,用::来说明函数属于这个类
通常情况下,头文件(.h 或 .hpp)用于存放类的成员的声明,包括类的数据成员和成员函数的声明。而源文件(.cpp 或 .c)用于存放类的成员函数的定义。

在类的成员函数的定义中,使用作用域解析运算符 :: 来指定函数属于哪个类。这样可以将函数的实现与类的声明分离开来,提高代码的可读性和可维护性。
例子:
(1)Rectangle.h文件

#ifndef RECTANGLE_H
#define RECTANGLE_Hclass Rectangle {
private:double length;double width;public:double getLength() const;double getWidth() const;double getArea() const;
};#endif

(2)Rectangle.cpp文件

#include "rectangle.h"double Rectangle::getLength() const {return length;
}double Rectangle::getWidth() const {return width;
}double Rectangle::getArea() const {return length * width;
}

一、构造函数与析构函数

1.什么是构造函数和析构函数?

构造函数(Constructor): 构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的数据成员
构造函数的名称与类名相同,没有返回类型(包括 void),可以有参数,也可以没有参数。构造函数在对象创建时自动调用,用于完成对象的初始化工作。如果没有显式定义构造函数,编译器会提供一个默认的无参构造函数。构造函数可以有多个重载版本,根据传递的参数类型和个数来决定调用哪个构造函数。

析构函数(Destructor): 析构函数是一种特殊的成员函数,用于在对象销毁时清理对象所占用的资源。析构函数的名称与类名相同,前面加上一个波浪号 ~,没有返回类型(包括 void),没有参数。析构函数在对象销毁时自动调用,用于完成对象的清理工作。如果没有显式定义析构函数,编译器会提供一个默认的析构函数。析构函数只能有一个,不能重载。

人话就是:构造函数在创建函数时为成员属性赋默认的初值,编译器自动调用完成。析构函数也是系统自动调用,用于在对象被销毁时执行清理工作。
比如这个类。

class Circle {//数据属性
private:
//public:float Radius;//圆的半径//函数行为
public:void setRadius(float radius) {//利用类的成员函数接口来从外部实现对私有成员圆的半径的操作Radius = radius;}float getCircumference() {//提供给外部的接口函数return 2 * PI * Radius;}float getArea() {return PI * pow(Radius, 2);//提供给外部的接口函数}};

如果主程序中实例化一个对象:

 Circle cir1;

这时编译器就会自动调用构造函数为cir1的成员变量赋默认值了,保证其有内存空间。销毁时也是同理。

2.构造函数和析构函数的语法

构造函数和析构函数的语法如下:

构造函数的语法:

ClassName::ClassName(parameters) {// 构造函数的实现// 对象的初始化操作
}

其中,ClassName 是类的名称,与构造函数同名。parameters 是构造函数的参数列表,可以包含零个或多个参数。

①构造函数不写返回值,也不要void。
②函数名与类名相同。
③允许重载,所以允许有不同的参数
④实例化对象时自动调用,无需我们手动写代码调用,且只调用一次。

析构函数的语法:

ClassName::~ClassName() {// 析构函数的实现// 对象的清理操作
}

其中,ClassName 是类的名称,与析构函数同名,前面加上一个波浪号 ~。析构函数没有参数列表。
一个类是一定有构造函数和析构函数的,如果函数的{}里面我们用户什么都不写,那么编译器会提供一个空实现(即里面一行代码都没有)去自动调用。
举个例子:

class Person {//首先写构造函数/*①构造函数不写返回值,也不要void。②函数名与类名相同。③允许重载,所以允许有不同的参数④实例化对象时自动调用,无需我们手动写代码调用,且只调用一次。*/Person(){//空实现}//析构函数~Person() {//空实现}private:string name;int Age;string address;public:void setAge(int age) {Age = age;}int getAge() {return Age;}
};

3.构造函数的具体分类和调用方法

①总的来说,构造函数分类为:默认无参构造、有参构造、拷贝构造

(1)默认构造函数的调用:直接创建对象即可,不需要传递任何参数。

 Person(){//无参构造函数//空实现}Person p; // 调用默认构造函数

(2)带参数的构造函数的调用:在创建对象时,通过传递参数来调用对应的构造函数。例如:

 Person(string name,int age){//有参构造函数}Person p("John", 25); // 调用带参数的构造函数

(3)拷贝构造函数的调用:在创建对象时,使用已有对象作为参数来调用拷贝构造函数。注意,拷贝构造函数相当于把一个传入的对象的所有相关信息复制到另一个对象上。例如:

 Person(const Person &p){//拷贝构造函数age=p.age;name = p.name;}Person p1("John", 25);
Person p2(p1); // 调用拷贝构造函数

②举一个全面的例子

#include <iostream>
#include <string>class Person {
private:std::string name;int age;public:// 默认构造函数Person() {name = "";age = 0;}// 有参构造函数Person(const std::string& n, int a) {name = n;age = a;}// 拷贝构造函数Person(const Person& p) {name = p.name;age = p.age;}// 打印姓名和年龄void printInfo() {std::cout << "Name: " << name << ", Age: " << age << std::endl;}
};int main() {// 默认构造函数Person p1;p1.printInfo(); // 输出: Name: , Age: 0// 有参构造函数Person p2("John", 25);p2.printInfo(); // 输出: Name: John, Age: 25// 拷贝构造函数Person p3(p2);p3.printInfo(); // 输出: Name: John, Age: 25return 0;
}

4.构造函数的使用时机

①用一个已经创建完毕的对象来初始化一个新对象

void test1() {Person p1(15);//有参构造函数Person p2(p1);//拷贝构造函数
}

②值传递的方式给函数参数传值

void copy(Person p) {}void test2(Person p) {Person p;//会调用一次默认构造函数copy(p);//把实例化的p作为参数值传递给函数copy,那么相当于建立了一个p的副本对象,等效于调用了一次拷贝构造函数}

二、构造函数的进阶知识

1.构造函数的调用规则

默认情况下,即不管咱们在类里面写不写构造函数,系统都会自动添加三个函数:
1.默认无参构造函数
2.默认无参析构函数
3.默认拷贝构造函数

规则为:
①如果咱们手动定义了一个有参的构造函数,那么系统就不会自动生成无参构造函数了。如果直接写Person p;就会报错。

如果我们定义了有参构造函数,编译器会认为我们有意地禁止了无参构造函数的使用。如果我们需要同时拥有有参和无参构造函数,可以在手动定义有参构造函数的同时,再显式地定义一个无参构造函数。

②如果咱们手动定义了拷贝构造函数,那么系统就不会提供其他构造函数。

这是因为拷贝构造函数是用于创建对象的副本的,它可以接受同类型的对象作为参数。如果我们手动定义了拷贝构造函数,编译器会认为我们已经提供了一种方式来创建对象的副本,因此不再需要其他构造函数。但是需要注意的是,如果我们需要同时拥有拷贝构造函数和其他构造函数,可以在手动定义拷贝构造函数的同时,再显式地定义其他构造函数

人话就是,无参最低级,有参次之,拷贝最高。为了防止系统认为我们禁用了某些构造函数,所以:
写了有参,就要补上无参;写了拷贝,就要补上有参和无参

2.初始化列表:给类中的成员属性初始化的另一种方法

前面学的传统构造函数初始化成员属性的语法:

class Person {
private:int age;public:// 默认构造函数Person() {age = 0;cout << "默认构造函数调用" << endl;}// 有参构造函数Person( int a) {age = a;cout << "有参构造函数调用" << endl;}// 拷贝构造函数Person(const Person& p) {age = p.age;cout << "拷贝构造函数调用" << endl;}
};//主函数
Person p(20);//实例化对象

而初始化列表是一种在类的构造函数中给成员属性进行初始化的方法。它使用冒号(:)后跟成员属性的初始化列表,而不是在构造函数的函数体中进行赋值操作

使用初始化列表可以在对象创建时直接初始化成员属性,而不需要先创建默认构造函数再进行赋值操作。这样可以提高代码的效率和可读性,并且在某些情况下可以避免不必要的对象拷贝。

class MyClass {
private:int num;double value;
public:MyClass(int n, double v) : num(n), value(v) {// 构造函数的函数体}
};//主程序里面
MyClass class(10,20);

在上面的示例中,构造函数的初始化列表部分是 : num(n), value(v),其中 num 和 value 是类的成员属性,n 和 v 是构造函数的参数。通过初始化列表,我们直接将参数的值赋给成员属性,而不需要在构造函数的函数体中进行赋值操作。

注意两点:
①语法:与传统构造函数类似,但是函数体里面没有内容,而是在形参的括号后面用:冒号符,跟上成员属性以及要赋的值。

 Person(int a) :age(a) {//使用初始化列表的方法给成员属性赋初值}

②初始化列表与常规的构造函数相比,有什么优势吗?
初始化列表相比常规的构造函数,在以下几个方面具有优势:

效率:使用初始化列表可以直接初始化成员属性,而不需要先创建默认构造函数再进行赋值操作。这样可以避免不必要的对象拷贝和临时对象的创建,提高代码的执行效率。

可读性:初始化列表将成员属性的初始化放在构造函数的声明中,使得代码更加清晰和易读。通过一目了然的初始化列表,可以直观地看到成员属性是如何被初始化的,而不需要在构造函数的函数体中寻找赋值操作。

成员属性的顺序:初始化列表要求成员属性的初始化顺序与它们在类中声明的顺序一致。这样可以避免因为不正确的初始化顺序而导致未定义行为。

常量成员属性和引用成员属性:对于常量成员属性和引用成员属性,只能通过初始化列表来进行初始化,而不能在构造函数的函数体中进行赋值操作。

3.一个类的对象作为另一个类的成员,如何对这两个类进行构造函数初始化?

顾名思义,所以我直接上一段代码:

#include <iostream>
#include <string>
using namespace std;class Phone {
public:string phone_name;int value;public:Phone(string name, int val) {//phone的构造函数phone_name = name;value = val;}
};class Person {
private:int age;string name;//手机也是一个类Phone p1("huawei",4999);public:Person(int a, string sname,string pname,int pval) :age(a), name(sname),p1(pname,pval) {//初始化列表构造函数}};

这段代码有一个经典的错误。
在这里插入图片描述
在这里插入图片描述

为什么图一会报错,图二就不报错了呢?
原因是:在类的成员初始化阶段,类成员的初始化顺序是按照它们在类中声明的顺序进行的

在Person类中,我们定义了age和name两个成员变量,但是如果咱们不在主程序中实例化一个对象,系统就不会给这两个变量分配空间,也就是说目前这两个变量是没有被初始化的。
但是如果Phone p1(“huawei”,4999);也就是让第三个成员变量p1提前被初始化了的话,就违背了初始化顺序,就会报错。

所以,只能先声明这个嵌套类Phone的成员,然后用大类的构造函数或者初始化列表来有顺序的对成员属性进行初始化

Person(int a, string sname,string pname,int pval) :age(a), name(sname),p1(pname,pval) {//初始化列表构造函数cout << a << "岁的" << sname << "拿着" << pval <<"块钱的" << pname << endl;}

然后主程序:

int main() {Person p(18, "小明", "华为mate60", 5999);system("pause");return 0;
}

这里注意:类Phone的成员变量string phone_name;和int value;都是公开的。那如果我想让他们的权限是私有的,那应该怎么在另一个类中完成这个嵌套类的对象初始化呢?
其实是一样的,不存在这个问题。因为一个类的构造函数在初始化成员变量时,是不会区分其是否为私有或者公开权限的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【RocketMQ每日一问】rocketmq事务消息原理?

rocketmq事务消息原理&#xff1f; RocketMQ的事务消息主要由三部分组成&#xff1a;半消息&#xff08;Half Message&#xff09;、执行本地事务和事务补偿机制。下面详细介绍这三部分&#xff1a; 半消息&#xff08;Half Message&#xff09;用户向RocketMQ发送半消息&…

多臂老虎机算法步骤

内容导航 类别内容导航机器学习机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归机器学习算法—聚类机器学习算法—异常检测机器学习算法—时间序列数据可视化数据可视化—折线图数据可视化—箱线图数据可视化—柱状图数据可视化—饼图、环形图、雷达图统…

文件上传存储工具

x-file-storage x-file-storage: 一行代码将文件存储到 本地、FTP、SFTP、WebDAV、谷歌云、阿里云OSS、华为云OBS、七牛云Kodo、腾讯云COS、百度云 BOS、又拍云USS、MinIO、 AWS S3、金山云 KS3、美团云 MSS、京东云 OSS、天翼云 OOS、移动云 EOS、沃云 OSS、 网易数帆 NOS、U…

JavaScript中concat函数和“+”的区别

concat方法用于连接两个字符串&#xff0c;返回一个新字符串&#xff0c;不改变原字符串 var s1 abola; var s2 ABOLA;s1.concat(s2) // s1 // 该方法可以接受多个参数 &#xff0c;如果参数不是字符串&#xff0c;concat方法会将其先转为字符串&#xff0c;然后再连接 …

antdv中的slider组件会默认将min值传递给value

如果是使用响应式变量&#xff0c;会将min的值传递到v-model对应的变量里

最大化控制资源成本 - 华为OD统一考试

OD统一考试 题解: Java / Python / C++ 题目描述 公司创新实验室正在研究如何最小化资源成本,最大化资源利用率,请你设计算法帮他们解决一个任务分布问题:有taskNum项任务,每人任务有开始时间(startTime) ,结更时间(endTme) 并行度(paralelism) 三个属性,并行度是指这个…

vivado 主时钟分析

主时钟 主时钟是通过输入端口或千兆位进入设计的板时钟收发器输出引脚&#xff08;例如恢复的时钟&#xff09;。主时钟只能由create_clock命令定义。主时钟必须附加到网表对象。此网表对象表示中的点所有时钟边沿源自其并在时钟树上向下游传播的设计。换句话说&#xff0c;主…

Android Realm数据库使用

当我们的app有数据需要保存到本地缓存时&#xff0c;可以使用file&#xff0c;sharedpreferences&#xff0c;还有sqlite。 sharedpreferences其实使用xml的方式&#xff0c;以键值对形式存储基本数据类型的数据。对于有复杂筛选查询的操作&#xff0c;file和sharedpreference…

[Angular] 笔记 7:模块

Angular 中的模块(modules) 是代码在逻辑上的最大划分&#xff0c;它类似于C, C# 中的名字空间&#xff1a; module 可分为如下几种不同的类型&#xff1a; 使用模块的第一个原因是要对代码进行逻辑上的划分&#xff0c;第二个非常重要的原因是为了实现懒惰加载(lazy loading)&…

位运算:消失的两个数字

题目描述&#xff1a; 给定一个数组&#xff0c;包含从 1 到 N 所有的整数&#xff0c;但其中缺了两个数字。你能在 O(N) 时间内只用 O(1) 的空间找到它们吗&#xff1f; 以任意顺序返回这两个数字均可。 示例 1: 输入: [1] 输出: [2,3] 示例 2: 输入: [2,3] 输出: [1,4]…

面试每日三题

MySQL篇 MySQL为什么使用B树索引 B树每个节点可以包含关键字和对应的指针&#xff0c;即B树的每个节点都会存储数据&#xff0c;随机访问比较友好&#xff0c;B树的叶子节点之间是无指针相连接的 B树所有关键字都存储在叶子节点上&#xff0c;非叶子节点只存储索引列和指向子…

计算机网络 应用层上 | 域名解析系统DNS 文件传输协议FTP,NFS 万维网URL HTTP HTML

文章目录 1 域名系统DNS1.1 域名vsIP&#xff1f;1.2 域名结构1.3 域名到IP的解析过程域名服务器类型 2 文件传送协议2.1 FTP 文件传输协议2.2 NFS 协议2.3 简单文件传送协议 TFTP 3 万维网WWW3.1 统一资源定位符URL3.2 超文本传送协议HTTP3.2.1 HTTP工作流程3.2.2 HTTP报文结构…

真实进行软件测试面试中,自动化测试面试到底会问那些?

作者&#xff1a;川石信息 链接&#xff1a;https://www.zhihu.com/question/342170872/answer/813076226 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 自动化测试面试1&#xff1a; 1、使用什么测试框架做的上…

linux环境下从一个服务器复制文件到另一个服务器

在Linux中使用scp命令可以将文件或目录从一台服务器复制到另外一台服务器。 # 从源服务器复制文件到目标服务器 scp /path/to/source_file usernamedestination:/path/to/destination_directory # 从源服务器复制目录及其内容到目标服务器 scp -r /path/to/source_directory us…

基于LightGBM的肺癌分类模型:从预测到个体化治疗

一、引言 肺癌作为全球范围内主要死因之一&#xff0c;对人类健康产生了巨大威胁。准确的肺癌分类是制定有效治疗和预后评估的基础。传统的肺癌分类方法&#xff0c;如组织学类型和分期&#xff0c;虽然在临床实践中被广泛应用&#xff0c;但存在着诊断标准不一致、主观性强以及…

7.串口通信uart编写思路及自定义协议

前言&#xff1a; 串口是很重要的&#xff0c;有许多模块通信接口就是串口&#xff0c;例如gps模块&#xff0c;蓝牙模块&#xff0c;wifi模块还有一些精度比较高的陀螺仪模块等等&#xff0c;所以学会了串口之后&#xff0c;这些听起来很牛批的模块都能够用起来了。此外&#…

MySQL 8.0 InnoDB Tablespaces之File-per-table tablespaces(单独表空间)

文章目录 MySQL 8.0 InnoDB Tablespaces之File-per-table tablespaces&#xff08;单独表空间&#xff09;File-per-table tablespaces&#xff08;单独表空间&#xff09;相关变量&#xff1a;innodb_file_per_table使用TABLESPACE子句指定表空间变量innodb_file_per_table设置…

Git系统有哪些优势

在现在的这个软件开发领域&#xff0c;版本控制是一项非常重要的工作。Git作为比较流行的分布式版本控制系统&#xff0c;他有着独特的优势成为了很多开发者们的首选。那Git系统都有哪些优势呢&#xff0c;下面我以自己的理解简单的介绍一下。 分布式版本控制的优势 Git用的是…

JAVA那些事(三)方法

目录&#xff1a; 方法声明 方法调用 参数传递 递归 正文&#xff1a; 方法是完成特定功能的、相对独立的程序段。方法一旦定义&#xff0c;就可以在不同的程序段中多次调用 方法声明 格式; [修饰符] 返回值类型 方法名 [&#xff08;参数表&#xff09;] {声明部分语句…

标准地址门牌管理系统:提升地址管理效率与准确性的关键

在信息化社会的今天&#xff0c;地址管理的重要性日益凸显。无论是商业活动、物流配送&#xff0c;还是公共安全&#xff0c;都需要精确、高效的地址管理。然而&#xff0c;传统地址管理方式往往存在地址不规范、信息不全等问题&#xff0c;这无疑增加了管理难度和工作量。为此…