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

news2025/2/5 5:02:14

文章目录

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

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

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

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

#ifndef RECTANGLE_H
#define RECTANGLE_H

class 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: 25

    return 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.coloradmin.cn/o/1328297.html

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

相关文章

图像处理—小波变换

小波变换 一维小波变换 因为存在 L 2 ( R ) V j 0 ⊕ W j 0 ⊕ W j 0 1 ⊕ ⋯ L^{2}(\boldsymbol{R})V_{j_{0}}\oplus W_{j_{0}}\oplus W_{j_{0}1}\oplus\cdots L2(R)Vj0​​⊕Wj0​​⊕Wj0​1​⊕⋯&#xff0c;所以存在 f ( x ) f(x) f(x)可以在子空间 V j 0 V_{j_0} Vj0…

2024年Etsy开店最全攻略,Etsy如何避免被封店铺?

Etsy是一个跨境电商平台&#xff0c;吸引了全球手工艺品制作者加入&#xff0c;商品独特且价格较高&#xff0c;个人卖家的利润空间也很大。因此&#xff0c;对于跨境卖家来说&#xff0c;在Etsy开店非常有吸引力。今天东哥整理了一份2024年最新Etsy开店流程&#xff0c;对此感…

鸿蒙ArkTS语言介绍与TS基础语法

1、ArkTS介绍 ArkTS是HarmonyOS主力应用开发语言&#xff0c;它在TS基础上&#xff0c;匹配ArkUI框架&#xff0c;扩展了声明式UI、状态管理等响应的能力&#xff0c;让开发者以更简洁、更自然的方式开发跨端应用。 JS 是一种属于网络的高级脚本语言&#xff0c;已经被广泛用…

02-基于GEC6818开发板的画正方形、画圆的操作——使用mmap映射提高效率

02-基于GEC6818开发板的画正方形、画圆的操作——使用mmap映射提高效率 本文主要是在01-基于粤嵌GEC6818实现屏幕的显示固定颜色进行自动切换-点击前往的基础上进行了进一步的更改&#xff0c;之前那个在切换时会有一定的花屏&#xff0c;是因为其效率低的原因&#xff0c;本文…

猜数字游戏 C语言xdoj490

问题描述 猜数字游戏是令游戏机随机产生一个 100 以内的正整数&#xff0c;用户输入一个数对其进行猜测&#xff0c;需要你编写程序自动对其与随机产生的被猜数进行比较&#xff0c;并提示大了&#xff08;“Too big”&#xff09;&#xff0c;还是小了&#xff08;“Too smal…

GBASE南大通用数据库提供的高可用负载均衡功能

GBASE南大通用GBase 8a ODBC 提供的高可用负载均衡功能是指&#xff0c;GBase 8a ODBC 会将客户 端请求的数据库集群连接平均分摊到集群所有可用的节点上。 GBASE南大通用数据库负载均衡的使用方法 GBASE南大通用GBase 8a ODBC 提供两种方式来使用高可用负载均衡。一种是配置数…

Ubuntu 20.4镜像国内地址下载较快

Ubuntu20.04版本比较稳定&#xff0c;部署OJ大都用这个版本。 推荐阿里云镜像点&#xff0c;点进去根据你的电脑版本下载iso后缀那个 ubuntu-releases-20.04安装包下载_开源镜像站-阿里云 下载速度较快 其他版本 http://mirrors.aliyun.com/ubuntu-releases/ 如果使用云服务…

图像畸变校正解决方案,无畸变的高质量视觉体验

摄像头已经成为我们生活中不可或缺的一部分。然而&#xff0c;由于摄像头的物理特性和环境因素&#xff0c;采集到的图像往往存在径向和切向畸变&#xff0c;导致画面扭曲&#xff0c;影响视觉效果。为了解决这个问题&#xff0c;美摄科技推出了一款先进的图像畸变校正解决方案…

Java网络编程---UDP

客户端 import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.util.Scanner;public class Client {public static void main(String[] args) throws Exception {//1.创建客户端对象DatagramSocket socket new Datagra…

node实现简单的数据爬虫

前言 我使用的是墨迹天气的页面&#xff0c;因为这个使用的链接简单 页面结构简单并且大都是文字形式 第一步 打开墨迹天气网址 随便点开一个页面 点击F12或者鼠标右键点击检查 查看页面的信息 分析页面内容 使用文字所在的class和标签来定位 编写代码 配置express环境 …

vscode中vue项目报错

当在vscode中写代码时&#xff0c;报错报错报错......... 已经头大&#xff0c;还没写就报错&#xff0c; 这是因为eslint对语法的要求太过严格导致的编译时&#xff0c;出现各种语法格式错误 我们打开vue.config.js&#xff0c;加上这句代码&#xff0c;就OK啦 lintOnSave:…

Python基本数据类型详解,新手小白入门必学

文章目录 1.注释2.输出3.变量4.命名规范5.变量的定义方式1.字符串类型2.数字类型3.List列表类型4.tuple 元组类型的定义5.Dict字典类型6.set集合类型7.数据类型转换8.自动类型转换9.强制类型转换Python技术资源分享1、Python所有方向的学习路线2、学习软件3、入门学习视频4、实…

关于“Python”的核心知识点整理大全35

目录 13.3.4 重构 create_fleet() game_functions.py 13.3.5 添加行 game_functions.py alien_invasion.py 13.4 让外星人群移动 13.4.1 向右移动外星人 settings.py alien.py alien_invasion.py game_functions.py 13.4.2 创建表示外星人移动方向的设置 13.4.3 检…

2023 英特尔On技术创新大会直播 | AI魅力的生活化

目录 前言正文 前言 依稀记得去年的直播大会&#xff0c;主要展现了其灵活、加速和半集成化的独特优势&#xff0c;广泛应用于人工智能、5G通信、边缘计算以及视觉图像处理等领域&#xff0c;不断提供领先的性能、能效和可编程性的创新。 如今又带来一些不一样的特色&#xf…

大一C语言作业题目1

目录 字符串和字符数组&#xff1f; %s found的变化&#xff1a; 7-1 学生成绩录入及查询 学生成绩表中&#xff0c;一名学生的信息包含如下信息&#xff1a; 学号(11位)、姓名、数学成绩、英语成绩、程序设计成绩、物理成绩。 本题要求编写程序&#xff0c;录入N条学生的…

IDEA 设置 SpringBoot logback 彩色日志(附配置文件)

1、背景说明 最开始使用 SpringBoot 时&#xff0c;控制台日志是带彩色的&#xff0c;让人眼前一亮&#x1f604; 后来彩色莫名丢失&#xff0c;由于影响不大&#xff0c;一直没有处理。 2、配置彩色 最近找到了解决方法&#xff08;其实是因为自定义 logback.xml&#xff0…

任天堂,steam游戏机通过type-c给VR投屏与PD快速充电的方案 三type-c口投屏转接器

游戏手柄这个概念&#xff0c;最早要追溯到二十年前玩FC游戏的时候&#xff0c;那时候超级玛丽成为了许多人童年里难忘的回忆&#xff0c;虽然长大了才知道超级玛丽是翻译错误&#xff0c;应该是任天堂的超级马里奥&#xff0c;不过这并不影响大家对他的喜爱。 当时FC家用机手柄…

电容内容介绍

0 Preface/Foreword 电容&#xff0c;Capacitance&#xff0c;i.e. 电容量&#xff0c;指在给定电位差下自由电荷的储存量&#xff0c;符号为C&#xff0c;单位为F&#xff08;法拉&#xff09;。 电容&#xff0c;指容纳电荷的能力。任何静电场都是由许多电容组成&#xff0…

Linux---进程状态

目录 一、系统进程状态介绍 1.运行状态 2.阻塞状态 3.挂起状态 二、Linux中的进程状态 1.R (running) 2.S (sleeping) 3.D&#xff08;disk sleep&#xff09; 4.T&#xff08;stopped&#xff09; 5.t&#xff08;tracing stop&#xff09; 6.X&#xff08;dead&am…

Spring AOP入门指南:轻松掌握面向切面编程的基础知识

面向切面编程 1&#xff0c;AOP简介1.1 什么是AOP?1.2 AOP作用1.3 AOP核心概念 2&#xff0c;AOP入门案例2.1 需求分析2.2 思路分析2.3 环境准备2.4 AOP实现步骤步骤1:添加依赖步骤2:定义接口与实现类步骤3:定义通知类和通知步骤4:定义切入点步骤5:制作切面步骤6:将通知类配给…