类和对象(二)

目录

一、类的六个默认成员函数

二、构造函数

2.1 概念

2.2 特性

三、析构函数

3.1 概念

3.2 特性

四、拷贝构造函数

4.1 概念

4.2 特性

五、赋值运算符重载

5.1 运算符重载

5.2 赋值运算符重载特性

六、const修饰成员函数

七、取地址及const取地址操作符重载


一、类的六个默认成员函数

若一个类中什么成员都没有,其被称为空类。但是空类中并不是什么都没有,即使什么都不写,类中也会自动生成6个默认成员函数(不止6个,C++11新增两个)

class Date {}; //空类

注意:这里的"默认"和"缺省"意思相近。即不写这6个函数,编译器自动生成;若是写了,编译器则不生成

二、构造函数

2.1 概念

  • 名字与类名相同,创建对象时由编译器自动调用,保证每个数据成员都有合适的初始值,在对象的生命周期内只调用一次

如:以下日期类中的成员函数Date就是构造函数。用该日期类创建对象时,编译器会自动调用该构造函数对新创建的变量进行初始化

class Date
{
public:Date(int year = 0, int month = 1, int day = 1)// 构造函数{_year = year;_month = month;_day = day;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private:int _year;int _month;int _day;
};

2.2 特性

  1. 构造函数的函数名与类名相同
  2. 构造函数无返回值(构造函数无返回值是真正意义上的无返回值,并不是返回值为void)
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数支持重载
  5. 无参的构造函数、全缺省的构造函数以及编译器自动生成的构造函数都被称为默认构造函数,并且默认构造函数只能有一个(无需传参就可以调用的构造函数就是默认构造函数)
  6. 若类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,若用户显示定义了,则编译器就不再生成

既然在不写的情况下,编译器会自动生成一个构造函数,那就没有必要写构造函数了吗?这个想法是错误

#include <iostream>
using namespace std;
class Date
{
public:void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1; // 编译器将调用自动生成的默认构造函数对d1进行初始化d1.Print();return 0;
}

代码输出结果如下: 

为什么d1对象调用了编译器自动生成的构造函数后,d1对象的_year/_month/_day依旧是随机值呢?

编译器自动生成的构造函数机制

  • 编译器自动生成的构造函数对内置类型不做处理
  • 对于自定义类型,编译器会再去调用其默认构造函数

编译器自动生成的构造函数可能达不到预期的效果,所以大多数情况下都需程序员自行编写构造函数

三、析构函数

3.1 概念

  • 与构造函数功能相反,析构函数负责完成对象的销毁,对象在销毁时自动调用析构函数,完成类的一些资源清理工作

当一个类对象销毁时,其中的成员变量也会随着该对象的销毁而销毁,如:用日期类创建了一个对象d1,当d1被销毁时,对象d1中的成员变量_year/_month/_day也会被自动销毁。但是这并不意味着析构函数没有什么意义。某些情况下,对象会在堆区开辟空间使用,当该对象被销毁时,其动态开辟的堆空间并不会随之被销毁,需对其进行空间释放

3.2 特性

  • 一、析构函数的函数名是在类名前加上字符"~"
class Date
{
public:Date()// 构造函数{}~Date()// 析构函数{}
private:int _year;int _month;int _day;
};
  • 二、析构函数无参数,无返回值(真正意义上的无返回值,并不是返回值为void)
  • 三、对象生命周期结束时,C++编译器会自动调用析构函数
  • 四、一个类有且只有一个析构函数。若未显示定义系统会自动生成默认的析构函数

编译器自动生成的析构函数机制

  1. 编译器自动生成的析构函数对内置类型不做处理
  2. 对于自定义类型,编译器会再去调用它们自己的默认析构函数
  • 五、先构造的后析构,后构造的先析构

因为对象是定义在函数中的,函数调用会建立栈帧,栈帧中的对象构造和析构也要符合先进后出的原则

四、拷贝构造函数

4.1 概念

  • 只有单个形参,该形参是对本类类型对象的引用(常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 0, int month = 1, int day = 1)// 构造函数{_year = year;_month = month;_day = day;}Date(const Date& d)// 拷贝构造函数{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2021, 5, 31);Date d2(d1); // 用已存在的对象d1创建对象d2return 0;
}

4.2 特性

  • 一、拷贝构造函数是构造函数的一个重载形式(拷贝构造函数的函数名也与类名相同)
  • 二、拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用

要调用拷贝构造函数就需先传参,若传参使用传值传参,那么在传参过程中又需进行对象的拷贝构造,如此循环往复,最终引发无穷递归调用

  • 三、若未显示定义拷贝构造函数,系统将生成默认的拷贝构造函数
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 0, int month = 1, int day = 1)// 构造函数{_year = year;_month = month;_day = day;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2021, 5, 30);Date d2(d1); // 用已存在的对象d1创建对象d2d1.Print();d2.Print();return 0;
}

编译器自动生成的拷贝构造函数机制:

  1. 编译器自动生成的拷贝构造函数对内置类型会完成浅拷贝(值拷贝)
  2. 对于自定义类型,编译器会再去调用它们的拷贝构造函数

代码中,并没有显示定义拷贝构造函数,但编译器自动生成的拷贝构造函数最终还是完成了对象的拷贝构造

  • 四、编译器自动生成的拷贝构造函数不能实现深拷贝

深浅拷贝问题

编译器自动生成的拷贝构造函数会对内置类型完成浅拷贝。对于以下这句代码,浅拷贝实际上就是将d1的内容完完全全的复制了一份拷贝给d2,所以说浅拷贝也被称为值拷贝。

Date d2(d1);// 用已存在的对象d1创建对象d2

但某些场景下浅拷贝并不能达到预期的效果。如,如下栈(Stack)这样的类,编译器自动生成的拷贝构造函数就不能满足需求了

class Stack
{
public:Stack(int capacity = 4){_ps = (int*)malloc(sizeof(int)* capacity);_size = 0;_capacity = capacity;}void Print(){cout << _ps << endl;// 打印栈空间地址}
private:int* _ps;int _size;int _capacity;
};

类中没有显示定义拷贝构造函数,那么用已存在的对象来创建另一个对象时,将调用编译器自动生成的拷贝构造函数

int main()
{Stack s1;s1.Print();// 打印s1栈空间的地址Stack s2(s1);// 用已存在的对象s1创建对象s2s2.Print();// 打印s2栈空间的地址return 0;
}

结果打印s1栈和s2栈空间的地址相同,意味着,创建完s2栈后,对s1栈做的任何操作都会直接影响到s2栈

这种情况下,还会出现对同一块空间释放多次的问题。所以像Date这样的类,需要的就是浅拷贝,那么编译器自动生成的拷贝构造函数就够用了,不需显示定义。像Stack这样的类,浅拷贝会导致析构两次、程序崩溃等问题,需显示定义对应的拷贝构造函数

五、赋值运算符重载

5.1 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,其目的是让自定义类型可以像内置类型一样可以直接使用运算符进行操作

d1 == d2;// 可读性高(书写简单)
IsSame(d1, d2);// 可读性差(书写麻烦)
返回值 operator运算符 (参数列表)//函数原型

注意:

  1. 不能通过连接其他符号来创建新的操作符,如:operator@
  2. 重载操作符必须有一个类类型或枚举类型的操作数
  3. 用于内置类型的操作符,重载后其含义不能改变
  4. 作为类成员的重载函数时,函数有一个默认的形参this,限定为第一个形参
  5. sizeof 、:: 、.* 、?: 、. 这5个运算符不能重载

示例:前置++重载、后置++重载

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载。C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递

//前置++
Date& Date::operator++() {//...return *this;
}
//后置++
Date Date::operator++(int) {//...return tmp;
}

示例:重载运算符==

可以将该运算符重载函数作为类的一个成员函数,此时该函数的第一个形参默认为this指针

class Date
{
public:Date(int year = 0, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}void Print() {cout << _year << "年" << _month << "月" << _day << "日" << endl;}bool operator==(const Date& d)// 运算符重载函数{return _year == d._year && _month == d._month&&_day == d._day;}
private:int _year;int _month;int _day;
};

也可将该运算符重载函数放在类外面,但此时外部无法访问类中的成员变量,这时可以将类中的成员变量设置为共有(public),这样外部就可以访问该类的成员变量了(也可以用友元函数或提供接口解决该问题)。并且在类外没有this指针,所以此时函数的形参必须显示的设置两个

class Date
{
public:Date(int year = 0, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}int _year;int _month;int _day;
};
bool operator==(const Date& d1, const Date& d2)// 运算符重载函数
{return d1._year == d2._year && d1._month == d2._month&&d1._day == d2._day;
}

5.2 赋值运算符重载特性

class Date
{
public:Date(int year = 0, int month = 1, int day = 1)// 构造函数{_year = year;_month = month;_day = day;}Date& operator=(const Date& d)// 赋值运算符重载函数{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}void Print()// 打印函数{cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private:int _year;int _month;int _day;
};
  • 一、参数类型设置为引用,并用const进行修饰

赋值运算符重载函数的第一个形参默认是this指针,第二个形参是赋值运算符的右操作数。由于是自定义类型传参,若是使用传值传参,会额外调用一次拷贝构造函数,所以函数的第二个参数最好使用引用传参。其次,第二个参数,即赋值运算符的右操作数,在函数体内不会对其进行修改,所以最好加上const进行修饰

  • 二、函数的返回值使用引用返回

若是只以d2 = d1这种方式使用赋值运算符,赋值运算符重载函数就没必要有返回值,因为在函数体内已经通过this指针对d2进行了修改。但是为了支持连续赋值,即d3 = d2 = d1,就需要为函数设置一个返回值了。而且返回值应该是赋值运算符的左操作数,即this指针指向的对象。和使用引用传参的道理一样,为了避免不必要的拷贝,最好还是使用引用返回。此时出了函数作用域this指针指向的对象并没有被销毁,所以可以使用引用返回

  • 三、赋值前检查是否是给自己赋值

若是出现d1 = d1,就不必进行赋值操作

  • 四、引用返回的是*this

赋值操作进行完毕时,应该返回赋值运算符的左操作数,而在函数体内只能通过this指针访问到左操作数,所以要返回左操作数就只能返回*this

  • 五、一个类如果没有显示定义赋值运算符重载,编译器也会自动生成一个,完成对象按字节序的值拷贝

赋值运算符重载编译器也可以自动生成,并且是支持连续赋值的。但是编译器自动生成的赋值运算符重载完成的是对象按字节序的值拷贝,如d2 = d1,编译器会将d1所占内存空间的值完完全全地拷贝到d2的内存空间中去,类似于memcpy。对于日期类,编译器自动生成的赋值运算符重载函数就可以满足需求。但当遇到一些特殊的类,还是得显示定义赋值运算符函数

注意 

Date d2(d1);//拷贝构造函数
Date d3 = d1;//拷贝构造函数
d1 = d3;//赋值运算符重载函数
  • 拷贝构造函数:用一个已经存在的对象去构造初始化另一个即将创建的对象
  • 赋值运算符重载函数:在两个对象都已经存在的情况下,将一个对象赋值给另一个对象

六、const修饰成员函数

const修饰类成员函数,实际修饰的是类成员函数隐含的this指针,表明在该成员函数中不能对this指针指向的对象进行修改

void Print()const {//cosnt修饰的打印函数cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
  1. const对象可以调用非const成员函数吗?
  2. 非const对象可以调用const成员函数吗?
  3. const成员函数内可以调用其他的非const成员函数吗?
  4. 非cosnt成员函数内可以调用其他的cosnt成员函数吗?

答案是:不可以、可以、不可以、可以

  1. 非const成员函数,即成员函数的this指针没有被const所修饰,传入一个被const修饰的对象,用没有被const修饰的this指针进行接收,属于权限的放大,函数调用失败
  2. const成员函数,即成员函数的this指针被const所修饰,传入一个没有被const修饰的对象,用被const修饰的this指针进行接收,属于权限的缩小,函数调用成功
  3. 在一个被const所修饰的成员函数中调用其他没有被const所修饰的成员函数,即:将一个被const修饰的this指针的值赋值给一个没有被const修饰的this指针,属于权限的放大,函数调用失败
  4. 在一个没有被const所修饰的成员函数中调用其他被const所修饰的成员函数,即:将一个没有被const修饰的this指针的值赋值给一个被const修饰的this指针,属于权限的缩小,函数调用成功

七、取地址及const取地址操作符重载

取地址操作符重载和const取地址操作符重载,这两个默认成员函数一般不用显示定义,使用编译器自动生成的即可

class Date
{
public:Date* operator&() { return this; }//取地址操作符重载const Date* operator&()const { return this; }//const取地址操作符重载
private:int _year;int _month;int _day;
};

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

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

相关文章

设计模式——备忘录模式

备忘录模式 定义 在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态&#xff0c;之后就可以将该对象恢复到原先保存的状态。 备忘录模式&#xff08;Memento Pattern&#xff09;是一种弥补缺陷的模式&#xff0c;能够在失败…

敏捷测试自动化

目录 前言&#xff1a; 敏捷宣言 敏捷软件开发VS传统方法 敏捷云服务 对什么进行自动化&#xff1f; 测试自动化的技巧 关于敏捷测试和自动化测试的关键信息 前言&#xff1a; 敏捷测试自动化是在敏捷开发环境中使用自动化工具和技术来支持测试活动的一种方法。它旨在提…

4G 网络跟 5G 的区别

4G网络和5G网络是两种不同的移动通信技术&#xff0c;它们在数据传输速度、延迟、连接密度和网络容量等方面存在一些区别。以下是它们之间的主要区别&#xff1a; 1. 速度&#xff1a;5G网络的速度比4G网络更快。5G网络具备更广的频段和更高的频率&#xff0c;能够提供更大的带…

【数据结构---排序】庖丁解牛式剖析常见的排序算法

排序算法 一、常见的排序算法二、常见排序算法的实现1. 直接插入排序2. 希尔排序3. 直接选择排序4. 堆排序5. 冒泡排序6. 快速排序6.1 递归实现快速排序思路一、hoare 版本思路二、挖坑法思路三、前后指针法 6.2 非递归实现快速排序 7. 归并排序7.1 归并排序的递归实现7.2 归并…

KuiperInfer深度学习推理框架环境配置-Ubuntu 22.04

KuiperInfer项目地址 Github项目地址 B站课程地址 安装Armadillo 官网&#xff1a;Armadillo官网 介绍&#xff1a;Armadillo C Library是一种C的线性代数库&#xff0c;包含一些矩阵和向量的运算&#xff0c;可以选用高效的LAPACK和BLAS进行加速。 矩阵相关计算的文档&…

发布项目管理生产的时候出现界面滚动不了

新的项目管理发布后出现页面鼠标上下滚动不了的问题&#xff0c;比较奇怪的是&#xff0c;本地开发没有问题&#xff0c;但发布生产后就出现问题了。 因为这次增加的组件比较多&#xff0c;相应的样式也比较多&#xff0c;所以那先分几个部分处理 1、先新加的几个组件是否有冲…

Unity VR 开发教程 OpenXR+XR Interaction Toolkit(九)根据不同物体匹配对应的抓取手势

文章目录 &#x1f4d5;教程说明&#x1f4d5;前置准备&#x1f4d5;HandData 脚本存储手部数据&#x1f4d5;制作预设手势&#x1f4d5;手势匹配脚本 GrabHandPose⭐完整代码⭐需要保存的数据⭐得知什么时候开始抓取和取消抓取⭐将手势数据赋予手部模型⭐平滑变化手势⭐开始抓…

Spark编程-共享变量(广播变量和累加器)

共享变量是什么 Spark中的两个重要抽象一个是RDD&#xff0c;另一个就是共享变量。 在默认情况下&#xff0c;当Spark在集群的多个不同节点的多个任务上并行运行一个函数时&#xff0c;它会把函数中涉及到的每个变量&#xff0c;在每个任务上都生成一个副本。 但是&…

单轴机器人的结构与特点

单轴机器人是由马达驱动的移动平台&#xff0c;由滚珠螺杆和 U型线性滑轨导引构成&#xff0c;其滑座同时为滚珠螺杆的驱动螺帽及线性滑轨的导引滑块&#xff0c;可用半导体、光电、交通运输业、环保节能产业、精密工具机、机械产业、智慧自动化、生技医疗上。 相对于传统的模组…

Python Web框架 Flask 安装、使用

Python Web框架 Flask 安装 安装 Flask 框架 首先需要安装 Flask 框架, 可以通过以下命令安装: [rootlocalhost web]# pip3 install Flask Collecting FlaskDownloading Flask-2.0.3-py3-none-any.whl (95 kB)|██████████████████████████████…

中国8K摄像机厂家加入国际广电设备制造商协会IABM

近日&#xff0c;BOSMA博冠正式成为国际广电设备制造商协会IABM会员&#xff0c;标志着中国8K摄像机厂家BOSMA博冠在广播电视超高清前端采集领域受到全球广电权威机构认可&#xff0c;进一步推动国产品牌在全球市场竞争中提升品牌知名度、加强行业影响力。 IABM创立于1976年&am…

Spring MVC 注解实现

注解描述 注解描述Controller用于标记在一个类上&#xff0c;使用它标记的类就是一个SpringMVC Controller 对象&#xff0c;分发处理器将会扫描使用了该注解的类的方法&#xff0c;并检测该方法是否使用了RequestMapping 注解。Controller 只是定义了一个控制器类&#xff0c…