C++——类和对象(2):构造函数、析构函数、拷贝构造函数

2. 类的6个默认成员函数

        我们将什么成员都没有的类称为空类,但是空类中并不是什么都没有。任何类中都会存在6个默认成员函数,这6个默认成员函数如果用户没有实现,则会由编译器默认生成。

        6个默认成员函数包括:负责初始化工作的构造函数;负责清理工作的析构函数;在用同类对象对创建对象进行初始化时用到的拷贝构造;用于对象之间赋值的赋值操作符重载;还有两个很少自己实现的取地址操作符重载const修饰的取地址操作符重载

2.1 构造函数

        构造函数是一个特殊的成员函数,帮助我们对新创建的对象进行初始化。

class Date
{
private:int _year;int _month;int _day;
public:Date(int year = 2000, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//void Init(int year = 2000, int month = 1, int day = 1)//{//	_year = year;//	_month = month;//	_day = day;//}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
};
int main()
{Date d1;Date d2(2024, 2, 26);//d1.Init(2024, 2, 26);d1.Print();d2.Print();
}

构造函数说明

        对于构造函数,我们需要对其进行一些说明与强调:

①构造函数属于默认成员函数,当发现我们没有写时编译器会默认生成一个,当我们写了编译器就不再会生成

②我们写构造函数时需要注意:构造函数的函数名应于类名相同;构造函数没有返回值返回类型的void不写;构造函数可以重载

class Date
{
private:int _year;int _month;int _day;
public:Date(){}Date(int year, int month, int day){_year = year;_month = month;_day = day;}
};
int main()
{Date d1();  //error 调用无参构造函数时,如果跟上()就成了函数声明Date d1;Date d2(2024, 2, 26);
}

③构造函数会在创建对象的时候自动调用,且在该对象的生命周期内仅调用这一次。

class Date
{
private:int _year;int _month;int _day;
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
};
int main()
{Date d1(2024, 2, 26);d1.Print();	//2024-2-26
}

④构造函数的任务不是开辟空间创造对象,而是对创建的对象进行初始化。

⑤编译器默认生成的构造函数,对内置类型(int、char等)不会做处理对自定义类型(类等)会调用该自定义类型的构造函数

class Time
{
private:int _hour;int _minute;int _second;
public:Time(){cout << "Time()" << endl;}
};
class Date
{
private:int _year;int _month;int _day;Time _t;
public:void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
};
int main()
{Date d1;d1.Print();//output//Time()//随机值
}

⑥C++11规定:内置类型可以在类中给默认值。

class Date
{
private://在类中给默认值int _year = 2000;int _month = 1;int _day = 1;
public:void Print(){cout << _year << '-' << _month << '-' << _day << endl;}
};
int main()
{Date d1;d1.Print();	//2000-1-1
}

2.2 析构函数

        构造函数是在对象创建后默认调用,用于初始化的。析构函数则是在对象的生命周期结束时调用自动析构函数,用来销毁对象,完成对象中资源的清理工作。

析构函数说明

①析构函数也属于默认成员函数,所以当我们没有写时编译器会默认生成一个,当我们写了编译器就不再会生成

②注意析构函数形式:析构函数的函数名为 ~类名 ;析构函数没有返回值和返回类型;析构函数不可以重载

class Stack
{
private:int* _arr;int _capacity;int _top;
public:Stack(int capacity = 4){_arr = (int*)malloc(sizeof(int) * capacity);if (_arr == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;}void Push(int x){//CheckCapacity();_arr[_top++] = x;}~Stack(){free(_arr);_arr = nullptr;_capacity = 0;_top = 0;}
};

③析构函数会在对象生命周期结束的时候自动调用

④编译器默认生成的析构函数,对内置类型不做资源清理,系统会自动将其内存回收;对自定义类型会调用该自定义类型的析构函数

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;//output://~Time()
}

 ⑤如果类中成员变量没有申请资源时,析构函数可以不需要写,使用默认即可。但是如果存在资源申请(如malloc),需要自己写出对应的析构函数来正确释放空间。

⑥在此处我们总结一下对于局部、全局、静态对象调用构造和析构的顺序问题:

class Date
{
public:Date(int year = 1){_year = year;cout << "Date()->" << _year << endl;}~Date(){cout << "~Date()->" << _year << endl;}
private:int _year;
};void func()
{Date d3(3);static Date d4(4);
}Date d5(5);
static Date d7(7);
Date d6(6);
static Date d8(8);int main()
{Date d1(1);Date d2(2);func();return 0;
}

         我们创建了一批对象,再令main函数正常结束,观察输出,可以总结出构造函数和析构函数调用的顺序:

构造函数:按照代码执行顺序进行创建。①顺序执行全局(包括全局静态)对象的创建;②进入main函数,顺序执行;③遇到函数,进入后顺序执行。

析构函数:按照声明周期结束时间进行调用。局部对象(后定义先析构)->全局和静态对象(后定义先析构)。

2.3 拷贝构造函数

        拷贝构造函数是一种特殊的构造函数,也是在初始化的时候发挥作用。拷贝构造函数只有一个由const修饰的本类类型对象的引用作为参数,在用已存在的类类型对象创建新对象时自动调用。

拷贝构造函数说明

①拷贝构造函数实际上是构造函数的一个重载形式。但是既然能成为默认成员函数,就意味着当我们没有写时编译器会默认生成一个,当我们写了编译器就不再会生成

class Date
{
private:int _year;int _month;int _day;
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << ' ' << _month << ' ' << _day << endl;}
};
int main()
{Date d1(2024, 2, 27);d1.Print();Date d2(d1);d2.Print();
}

②注意拷贝构造函数形式:拷贝构造函数是构造函数的重载形式,所以除了参数和构造函数一样。拷贝构造函数的参数只有一个,是const修饰的本类类型对象的引用。但是我们了解了this指针的存在,所以要注意分清调用的形式,实参应该是已经拷贝的“原件”。

class Date
{
private:int _year;int _month;int _day;
public:
//构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}
//拷贝构造函数Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << ' ' << _month << ' ' << _day << endl;}
};
int main()
{Date d1(2024,2,27);d1.Print();Date d2(d1);d2.Print();
}

③对于参数的说明:拷贝构造的一个参数必须是类类型对象的引用,如果不传引用而传值则会导致无穷递归调用引发报错。

void func(Date d)
{}
int main()
{Date d1(2024,2,27);func(d1);
}

        对于以上代码,在func函数使用了值传递,实际上就是将d1的值传给参d。类似于C语言中的结构体传值调用,C++的自定义类型值传递时都需要调用拷贝构造,在执行到这里的时候编译器将d1的值拷贝到d中,所以这时就需要调用Date类的拷贝构造函数。

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

        那么如果在实现拷贝构造函数的时候传值,那么在调用拷贝构造函数的时候,参数为了拿到值会再去调用拷贝构造,这次的拷贝构造仍然是值传递,为了获取参数的值会去调用下一次拷贝构造,因而产生无穷递归。

④编译器默认生成的拷贝构造函数,对内置类型是对内存存储中字节完成拷贝,这种拷贝称为浅拷贝,或值拷贝。对于自定义类型则是调用它的拷贝构造函数

class Time
{
public:Time(){_hour = 1;}Time(const Time& t){_hour = t._hour;cout << "Time::Time(const Time&)" << endl;}
private:int _hour;
};
class Date
{
private:// 基本类型(内置类型)int _year = 2000;// 自定义类型Time _t;
};
int main()
{Date d1;Date d2(d1);return 0;//oupput://Time::Time(const Time&)
}

⑤深拷贝和浅拷贝:浅拷贝仅仅是对字节进行逐一拷贝,这种拷贝方式在面对正常情况并无问题,但是一旦遇到申请资源的成员,浅拷贝便会产生问题。

class Stack
{
private:int* _arr;int _capacity;int _top;
public:Stack(int capacity = 4){_arr = (int*)malloc(sizeof(int) * capacity);if (_arr == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;}
};
int main()
{Stack st1;Stack st2(st1);return 0;
}

        对于上述栈这个类,如果仅仅是浅拷贝的方式,那么st2的_arr成员的值就会和st1的_arr值相同。但是我们知道这个_arr成员指向的是对上开辟的空间,如果我们的两个栈st1和st2的_arr成员相同,那就意味着是二者共用一块空间,这就出现了问题。

        对于这种申请了资源的情况,我们就需要深拷贝,即对对象下所管理的深层空间也进行拷贝,此时就需要自己实现拷贝构造函数了。

class Stack
{
private:int* _arr;int _capacity;int _top;
public:Stack(int capacity = 4){_arr = (int*)malloc(sizeof(int) * capacity);if (_arr == nullptr){perror("malloc fail");return;}_capacity = capacity;_top = 0;}Stack(const Stack& st){_arr = (int*)malloc(sizeof(int) * st._capacity);if (_arr == nullptr){perror("malloc fail");return;}memcpy(_arr, st._arr, sizeof(int) * st._capacity);_capacity = st._capacity;_top = st._top;}
};
int main()
{Stack st1;Stack st2(st1);return 0;
}

⑥拷贝构造函数自动调用的场景:a.用存在的对象初始化新对象;b.函数参数类型为类类型对象;c.函数的返回值类型为类类型对象。

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

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

相关文章

C语言-数据结构-顺序表

&#x1f308;个人主页: 会编辑的果子君 &#x1f4ab;个人格言:“成为自己未来的主人~” 目录 数据结构相关概念 顺序表 顺序表的概念和结构 线性表 顺序表分类 顺序表和数组的区别 顺序表分类 静态顺序表 动态顺序表 头插和尾插 尾插 数据结构相关概念 数据结构…

人工智能之Tensorflow程序结构

TensorFlow作为分布式机器学习平台&#xff0c;主要架构如下&#xff1a; 网络层&#xff1a;远程过程调用(gRPC)和远程直接数据存取(RDMA)作为网络层&#xff0c;主要负责传递神经网络算法参数。 设备层&#xff1a;CPU、GPU等设备&#xff0c;主要负责神经网络算法中具体的运…

半小时到秒级,京东零售定时任务优化怎么做的?

导言&#xff1a; 京东零售技术团队通过真实线上案例总结了针对海量数据批处理任务的一些通用优化方法&#xff0c;除了供大家借鉴参考之外&#xff0c;也更希望通过这篇文章呼吁大家在平时开发程序时能够更加注意程序的性能和所消耗的资源&#xff0c;避免在流量突增时给系统…

【Leetcode】938. 二叉搜索树的范围和

文章目录 题目思路代码结论 题目 题目链接 给定二叉搜索树的根结点 root&#xff0c;返回值位于范围 [low, high] 之间的所有结点的值的和。 示例 1&#xff1a; 输入&#xff1a;root [10,5,15,3,7,null,18], low 7, high 15 输出&#xff1a;32 示例 2&#xff1a; 输入…

HP笔记本电脑如何恢复出厂设置?这里提供几种方法

要恢复出厂设置Windows 11或10的HP笔记本电脑,你可以使用操作系统的标准方法。如果你运行的是早期版本,你可以使用HP提供的单独程序清除计算机并重新安装操作系统。 恢复出厂设置运行Windows 11的HP笔记本电脑​ 所有Windows 11计算机都有一个名为“重置此电脑”的功能,可…

kafka学习笔记三

第二篇 外部系统集成 Flume、Spark、Flink、SpringBoot 这些组件都可以作为kafka的生产者和消费者&#xff0c;在企业中非常常见。 Flume官网&#xff1a;Welcome to Apache Flume — Apache Flume Flink&#xff1a;Apache Flink_百度百科 Spark&#xff1a;Apache Spark…

图片转PDF

选择图片右键——打开方式 ——照片、画图、截图工具 其他的选择性尝试 点击打印 在刚刚保存的路径哪里即可得到刚刚保存的PDF版的图片

【学习总结】什么是弹性负载均衡? LB和ELB的区别

[Q&A] 什么是 LB (Load Balancer) 负载均衡器&#xff1a; 这是一个广泛的概念&#xff0c;泛指任何用于在网络流量进入时进行分配以实现服务器集群间负载均衡的设备或服务。传统的负载均衡器可以是硬件设备&#xff0c;也可以是软件解决方案&#xff0c;其基本目标是将客…

DHCP Snooping

DHCP Snooping是DHCP&#xff08;Dynamic Host Configuration Protocol&#xff09;的一种安全特性&#xff0c;用于保证DHCP客户端从合法的DHCP服务器获取IP地址&#xff0c;并记录DHCP客户端IP地址与MAC地址等参数的对应关系&#xff0c;防止网络上针对DHCP攻击。 1.1 DHCP …

Linux 内存管理概述(偏实战,略理论,附链接)

基础理论 1. 内存映射 可以参考&#xff1a; Linux内存映射 - 知乎 写的很详细&#xff0c;而且也有代码分析 2. 虚拟内存的空间分布 通过这张图你可以看到&#xff0c;用户空间内存&#xff0c;从低到高分别是五种不同的内存段。只读段&#xff0c;包括代码和常量等。数据段…

一款开源.NET WPF界面库介绍

一款开源.NET WPF界面库介绍 这是一个WPF版的Layui前端UI样式库&#xff0c;该控件库参考了Web版本的LayUI风格&#xff0c;利用该控件库可以完成现代化UI客户端程序&#xff0c;让你的客户端看起来更加简洁丰富又不失美感 如何使用 步骤一 : 添加LayUI.Wpf Nuget包; Inst…

SpringCloud微服务-Eureka注册中心

Eureka注册中心 文章目录 Eureka注册中心前言1、Eureka的作用2、搭建EurekaServer3、服务注册4、启动多个实例5、服务拉取 -实现负载均衡 前言 在服务调用时产生的问题&#xff1a; //2. 利用RestTemplate发起HTTP请求&#xff0c;查询user String url "http://localho…