【C++】——类和对象(构造函数,析构函数,拷贝构造函数,赋值运算符重载)

                                                  创作不易,多多支持! 

前言

相信你对这几个知识点有点混淆,相信看完以后,你会对此有一个清晰的认识。

一 类的6个默认成员函数

如果我们写一个类,但是类里面什么都没有,我们称之为空类。

其实这个类也不完全为空,因为编译器会类中自动生成这6个成员函数。

所以这几个成员函数也叫作默认成员函数,我们不去实现,编译器会生成。

接下来我们一个一个说明

二  初始化和清理

2.1  构造函数

1 .我们知道构造函数是执行初始化的操作,要是我们像以前一样写一个初始化函数去初始化也是可以的,下面用一个日期类去演示

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:void Init(int year, int month, int day)//初始化函数{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;//类的实例化d1.Init(2024, 7, 5);调用函数初始化d1.Print();Date d2;d2.Init(2024, 7, 6);d2.Print();return 0;
}

从代码中我们可以看出 如果我们要初始化我们就需要每次都调用这个初始化函数,这就会显得非常的麻烦,那有没有更加便捷的方法呢?

构造函数是一种特殊的成员函数,函数名与类名相同不需要返回值,在类实例化时自动调用,每个类只调用一次,我们可以用这个自动调用的特性去让我们的初始化变的非常方便

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:Date()//无参构造函数{_year = 2024;_month = 7;_day = 5;}Date(int year, int month, int day)//有参{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;//调用无参Date d2(2024, 4, 24);调用有参d1.Print();d2.Print();return 0;
}

 在上面代码中我们并没有去调用里面的构造函数,我们可以看看输出的结果是什么

这里就体现了自动调用的特性。其中我们可以看出无参调用的时候有的人会这样写

Date d1();

这其实就错误了,因为编译器不知道你是调用函数还是类的实例化,所以不能这样写 

2. 对于构造函数因为是编译器默认生成的,所以即使我们不写,那么编译器也会自动生成一个,但是如果我们写了,那么编译器就不会生成了 

为了更好理解,下面给出相应代码

#define _CRT_SECURE_NO_WARNINGS 1
#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.Print();return 0;
}

这串代码是不会报错的,因为编译器会自动生成一个默认的构造函数,这样在类实例化的时候会调用这个默认的构造函数

但是如果我们这样写

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Print();return 0;
}

 可以看出这样就不行了,因为我们写了一个带参的构造函数,但是我们要初始化无参的就没了,因为我们写了构造函数,那么编译器就不会再生成默认的构造函数

但是不要想着编译器会给你那么方便,他虽然可以帮你自动调用,你没写也可以自动生成,但是它自动生成的默认构造函数是不会给你初始化的,这一点要尤其注意

我们可以看第一段代码, 是可以运行通过的,但是运行的结果却是

 可以看出结果不是我们想的那样,它并没有完成初始化,所以我们可以得出默认的构造函数对于内置类型是不处理的,内置类型就是(int/double/char...)之类的类型。

3.  那这个默认的构造函数没用吗?并不是,对于自定义类型,这个默认构造函数会去调用这个自定义类型的默认构造函数

可能这段话看起来非常绕,那下面我们看一点代码就清楚了

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Time
{
public:Time()//Time类的默认构造{cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private://内置类型int _year;int _month;int _day;//自定义类型Time _t;
};
int main()
{Date d1;return 0;
}

 

之所以有这样一个结果就是在Date中有一个默认构造函数,这个是由编译器自动生成的,这个函数对于内置类型是不处理的,但是对于自定义类型,会去调用它的默认构造函数,所以就会打印出这个 Time()

4.  其实这让我们也非常难以接受,所以在后面c++又对它进行了补丁,可以在成员变量声明的时候给缺省值

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:void Print(){cout << _year <<'-'<< _month <<'-' << _day << endl;}
private://内置类型int _year=2024;int _month=7;int _day=21;//自定义类型Time _t;
};
int main()
{Date d1;d1.Print();return 0;
}

注意这里虽然是在声明的时候给了值,但是这里并不代表是定义,也就是没有给空间,给空间还是要在实例化的时候给空间

5.  这里还有一个点要分清楚,默认成员函数只包括(全缺省构造函数,无参构造函数,我们没写编译器默认生成的构造函数)对于不是这些类型的都不能算是默认构造函数

所以如果我们写一个无参的构造函数,再写一个全缺省的构造函数,那么这个编译器就会报错,因为再类实例化的时候,它不知道调用哪一个,这一点也是要注意的

2.2 析构函数

析构函数也是特殊的成员函数 ,他和构造函数相反,他是负责清理的,但是两者的特性是差不多的 对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

 析构函数特性

1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载
4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数

和构造函数一样,对于内置类型不处理,对于内置类型去调用它的析构函数

如果是对于这么日期类,里面只包含内置类型,那么也可以不写析构函数,因为出了作用域,内置类型的变量会随着栈的销毁而销毁 ,但是如果涉及到申请资源那么就需要用到析构函数了

#include<iostream>
using namespace std;class Time
{
public:~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

这里和上面的构造函数一样,就不做太多的说明了。

三 拷贝复制

3.1 拷贝构造函数

 拷贝构造函数只有单个形参,该形参是对本类类型对象的引用一般用const修饰。

拷贝构造函数特性:

1.拷贝构造函数是构造函数的一种重载

2.拷贝构造函数的参数只有一个,而且这个形参必须是该类的引用,如果用传值会导致无穷递归

#include<iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const Date& d)   // 正确写法Date(const Date d)   // 错误写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);return 0;
}

 在解释无穷递归的原因之前,我们得先来了解一下内置类型的拷贝和自定义类型的拷贝的区别

内置类型的拷贝就在底层就是一个字节一个字节的拷贝,也就是浅拷贝,那如果自定义类型也是浅拷贝的话,有可能会发生意想不到的后果

比如如果说自定义类型里面有申请空间开辟的数组,那么就会发生两次释放空间的问题

那如果避免这个问题了,这里我们就需要深拷贝,深拷贝就是再开辟一片空间,把他们分开,这样就不会导致重复释放了,所以这里我们就需要用拷贝构造函数去实现这一功能

所以我们对于自定义类型不管里面是不是有申请空间的变量,我们都去调用它的拷贝构造函数

 那么回到无穷递归这个问题,如果我们要拷贝自定义类型,那么编译器会去调用拷贝构造函数,那么就会传参,那么我们传参又会去调用新的拷贝构造,调用新的拷贝构造又会传参,那么就陷入无穷递归了

所以我们需要用到引用,但是这个引用我们不能去改变这个值,所以用const

所以拷贝构造函数的形式就是

Date(const Date& d)  {_year = d._year;_month = d._month;_day = d._day;}

 其他的形式都不是拷贝构造

拷贝构造和构造函数也有点相似的地方,比如我们没有写拷贝构造函数的时候,编译器会去调用默认生成的拷贝构造,不过这个拷贝构造不会完成深拷贝,只是简单的值拷贝,也就是浅拷贝

3.2  赋值运算符重载

赋值运算符重载是具有特殊函数名的函数

函数名为:operator后面接需要重载的运算符符号(+,-,*,[])

函数原型:返回值类型 operator操作符(参数列表)

但是要注意有几个符号是不能重载的

.*   ::   sizeof   ?:   .

 还需要注意的是

1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this

 1.运算符重载

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:bool operator==(const Date& d2)//运算符重载,这里只有一个参数//其实还有一个隐含的参数this//如果把该函数放在外面就没有this,但是就确保不了封装性了{return _year == d2._year&& _month == d2._month&& _day == d2._day;}Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};void Test()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);cout << (d1 == d2) << endl;
}
int main()
{Test();return 0;
}

上面的代码就是运算符重载的一个实例,那赋值运算符重载其实道理也大差不差

 但是其中也有些许细节

参数类型 const T& ,传递引用可以提高传参效率
返回值类型 T& ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回 *this :要复合连续赋值的含义

 按照上面的格式,我们可以写出一个相应代码

2. 赋值运算符重载

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}Date(int year = 1900, 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;Date d2(2024, 7, 40);d1 = d2;d1.Print();d2.Print();return 0;
}

 3.赋值运算符重载只能定义成成员函数,不能定义成全局函数

因为运算符重载也是默认的成员函数所以,编译器会自己生成一个,那么我们自己如果在全局再写一个就会导致冲突,所以运算符重载必须写成成员函数,不能写成全局函数

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

4. 如果我们不写赋值运算符重载,那么编译器会自动生成一个,这个自动生成的对内置类型完成值拷贝,对于自定义类型会去调用对应类的赋值运算符重载

与拷贝构造函数类似,对于只有内置类型的类来说,写不写都可以,但是如果涉及到申请资源的变量那么就得自己写完成深拷贝的函数

5.  前置++与后置++重载

对于这两个直接看代码理解吧

class Date
{
public:Date& operator++()//前置++{_day += 1;return *this;}Date operator++(int)后置++{Date tmp = *this;_day+=1;return tmp;}Date(int year = 1900, 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形参去构成函数重载,这里只是构成重载没有任何作用

注意后置是先返回,后++,所以这得用一个临时对象保存,返回的时候不能用引用返回,因为返回的是临时对象,用引用返回会出现未定义行为

四  取地址重载

class Date
{ 
public :Date* operator&()//不加const{return this;}const Date* operator&()const//加const{return this ;}
private :int _year ; // 年int _month ; // 月int _day ; // 日
};

这里分为加const和不加const

 编译器默认生成的是不加const,所以一般这种重载我们不写交给编译器,但是如果有特殊的要求则需要自己手动写,比如想人获取指定的内容

const 成员函数 

用const修饰的成员函数称之为const成员函数,使用const修饰的成员函数不能修改类的成员变量,也不能调用非const成员函数

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

语法声明为:void fun() const;

class Date
{
public:void fun()const{_year = 6;//尝试对成员变量进行赋值}Date(int year = 1900, 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;
};

 

对于const对象调用const成员函数时,会调用const版本函数,而使用非const对象调用const成员函数时,会调用非const版本函数

这也就是一一对应

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Date
{
public:void fun()const{cout << "const" << endl;}void fun(){cout << "非const" << endl;}Date(int year = 1900, 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()
{const Date d1;Date d2;d2.fun();d1.fun();return 0;
}

可以看出结果也是一一对应的。

在这里还有一个点就是

🎈const成员函数不能调用非const成员函数,因为这是 权限放大

在const成员函数里它承诺了不能修改成员变量,如果去调用非const,非const又可以修改,这就违法了const成员变量的约定

🎈非const成员函数可以调用const成员函数,权限缩小是可以的

在非const可以修改也可以不修改,那在里面调用const成员函数,const成员函数规定不能修改,在非const里面并不矛盾,可以包容,所以是合理的

🎈const对象不可以调用非const成员函数

const对象里面的成员函数被隐式的看成了const成员函数,因此就和上面的道理是差不多的了

🎈非const对象可以调用const成员函数

 一句话就是,如果内部不涉及修改的,用const修饰,如果涉及修改的就不能加const

 

 相信看到这里,你会对构造函数,析构函数,拷贝构造函数,赋值运算符重载有一个更深的认识

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

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

相关文章

DC-DC电源芯片规格书上的各种参数详解

1.输出电压精确度 输出电压的精确度,也被称为设定点精度,它描述了输出电压的允许误差。该参数通常是在常温,满载和额定输入电压的条件下测得的,它是这样定义的: 输出电压之所以产生误差,是因为元器件本身存在误差,特别是输出端的分压电阻,它将输出电压降低后比PWM比较…

Mac装虚拟机好不好 Mac装虚拟机和装Windows系统一样吗 PD虚拟机

随着跨系统操作的不断发展&#xff0c;虚拟机技术在生产力领域扮演着越来越重要的角色。Mac作为一款主流的操作系统&#xff0c;也有着运行虚拟机的能力。接下来给大家介绍Mac装虚拟机好不好&#xff0c;Mac装虚拟机和装Windows系统一样吗的具体内容。 一、Mac装虚拟机好不好 …

UNIX环境高级编程->高级IO(概念篇)

UINX环境 常见的操作系统比如Linux 和 MacOS都是基于UNIX的。它们都继承了UNIX的许多特性和设计理念。因此&#xff0c;它们的高级I/O技术确实是建立在UNIX基础之上的。Linux和macOS都提供了丰富的高级I/O功能和API&#xff0c;利用了UNIX系统调用和原则。这些功能包括非阻塞I…

【Linux】自定义协议——实现网络序列化和反序列化

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;承接上文内容【Linux】应用层协议序列化和反序列化 目录 &#x1f449;&#x1f3fb;代码实现如下Calculate.hp…

C语言-联合体基本概念

联合体的外在形式跟结构体非常类似&#xff0c;但它们有一个本质的区别&#xff1a;结构体中的各个成员是各自独立的&#xff0c;而联合体中的各个成员却共用同一块内存&#xff0c;因此联合体也称为共用体。 联合体内部成员的这种特殊的“堆叠”效果&#xff0c;使得联合体有如…

Ollama完成本地模型的运行

Ollama完成本地模型的运行 llama 3 8b很多pc都可以run起来,可以用这个练练手 简介 Ollama 是一个开源的大型语言模型(LLM)服务工具,它允许用户在本地运行和使用各种大型语言模型。Ollama 提供了一个命令行界面,支持多种流行的模型,如 Llama 3、Qwen 1.5、Mixtral、Gemma…

MapMagic 2 Biomes and Functions

MapMagic 2(免费)世界生成器官方模块。支持基于遮罩混合几个图形,从而可以在无限地形上混合不同的生物群落。也随附函数节点,从而可以在子图中执行复杂的生成过程。将它们视作含有输入和输出连接器的生物群落。请注意,必须使用 MapMagic 2 的现有安装才能使用该模块。 下…

(一)JVM实战——jvm的组成部分详解

前言 本节内容是关于java虚拟机JVM组成部分的介绍&#xff0c;通过其组成架构图了解JVM的主要组成部分。 正文 ClassFile&#xff1a;字节码文件 - javac&#xff1a;javac前端编译器将源代码编译成符合jvm规范的.class文件&#xff0c;即字节码文件 - class文件的结构组成&a…

数据变更捕获 (CDC):PostgreSQL 与 ClickHouse - 第一部分

本文字数&#xff1a;13442&#xff1b;估计阅读时间&#xff1a;34 分钟 审校&#xff1a;庄晓东&#xff08;魏庄&#xff09; 本文在公众号【ClickHouseInc】首发 简介 在之前的文章中&#xff0c;我们已经讨论了OLTP数据库&#xff08;例如Postgres&#xff09;和OLAP数据…

【go零基础】go-zero从零基础学习到实战教程 - 1项目表设计

既然是0基础&#xff0c;现在来写下设计思路&#xff0c;因为go-zero是个微服务架构&#xff0c;所以&#xff0c;哪怕是0基础&#xff0c;也从两个服务模块开始写起。 我们的目标是&#xff1a;最小可用微服务架构最佳实践&#xff01; 好了&#xff0c;饼画完了。 第0部分写到…

504网关超时可能是哪些原因导致

当前随时互联网的发展普及&#xff0c;我们经常会使用到网站服务&#xff0c;许多网站为了提高打开速度&#xff0c;都会接入使用CDN。当我们在浏览网页或使用网络服务时&#xff0c;有时候可能有遇到网站打不开的情况&#xff0c;出现各式各样的错误代码&#xff0c;其中504网…

【注解和反射】通过反射动态创建对象、调用普通方法、操作属性

继上一篇博客【注解和反射】获取类运行时结构-CSDN博客 目录 八、通过反射动态创建对象 测试&#xff1a;通过反射动态创建对象 思考&#xff1a;难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器并将参数传递进去之后&#xff0c;才可以实…

三维图形程序员必学-CGAL几何算法

GCAL几何算法库,涵盖了很多数学几何算法,矩阵运算、平面拟合、曲线拟合、曲面重建、网格优化、网格剖分、面线相交、布尔运算等等各种图形学几何相关的算法。 文章最后放了一个CGAL求点集拟合平面,投影求线的例子代码。 CGAL是一个开源代码库,官网连接GitHub - CGAL/cgal…

网络安全之弱口令与命令爆破(上篇)(技术进阶)

目录 一&#xff0c;什么是弱口令&#xff1f; 二&#xff0c;为什么会产生弱口令呢&#xff1f; 三&#xff0c;字典的生成 四&#xff0c;使用Burpsuite工具弱口令爆破 总结 一&#xff0c;什么是弱口令&#xff1f; 弱口令就是容易被人们所能猜到的密码呗&#xff0c;…

Linux动态追踪——eBPF

目录 摘要 1 什么是 eBPF 2 eBPF 支持的功能 3 BCC 4 编写脚本 5 总结 6 附 摘要 ftrace 和 perf 与 ebpf 同为 linux 内核提供的动态追踪工具&#xff0c;其中 ftrace 侧重于事件跟踪和内核行为的实时分析&#xff0c;perf 更侧重于性能分析和事件统计&#xff0c;与…

vim+xxd 编辑16进制

1. vim -b mib 2. 在vim 中执行 %!xxd, 这样就可以输入16进制&#xff1a; 3. 输入完成后&#xff0c;在vim中 执行 %!xxd -r 切换至原模式&#xff1b; 4. 保存退出即可 5. 重新打开mib文件&#xff1a;vim -b mib 6. 在vim 中执行 %!xxd, 查看是否符合预期&#xff1a;…

如何理解GDP、国民总收入(GNI)的区别和联系

国内生产总值和国民总收入是衡量一个国家&#xff08;地区&#xff09;经济状况和发展水平的两个重要总量指标。两者既有密切的联系&#xff0c;又有一定区别&#xff0c;用途都非常广泛。 一、GDP与GNI的基本概念 国内生产总值(Gross Domestic Product&#xff0c;GDP)&…

一个联合均值与方差模型的R包——dglm

目录 一、引言二、包的安装与载入三、模拟例子3.1 数据生成3.2 数据查看3.3 模型估计参数 一、引言 在 R 语言中&#xff0c;dglm 包是用于拟合双参数广义线性模型&#xff08;Double Generalized Linear Models&#xff0c;简称 DGLMs&#xff09;的一个工具。这类模型允许同…

模块三:二分——162.寻找峰值

文章目录 题目描述算法原理解法一&#xff1a;暴力查找解法二&#xff1a;二分查找 代码实现解法一&#xff1a;暴力查找解法二&#xff1a;CJava 题目描述 题目链接&#xff1a;162.寻找峰值 根据题意&#xff0c;需要使用O(log N)的时间复杂度来解决&#xff0c;得出本道题…

在美国站群服务器部署时如何保障从253个IP到1000个IP的无缝扩展?

在美国站群服务器部署时如何保障从253个IP到1000个IP的无缝扩展? 在当今企业的数字化转型中&#xff0c;服务器的部署和管理成为了保证业务连续性和拓展性的关键。尤其对于站群服务器来说&#xff0c;随着企业业务的增长和市场的扩展&#xff0c;需要从较小规模的253个IP地址…