【C++grammar】析构、友元、拷贝构造函数、深浅拷贝

目录

  • 1、Destructor(析构函数)
    • 在堆和栈(函数作用域与内嵌作用域)上分别创建Employee对象,观察析构函数的行为
  • 2、Friend(友元)
    • 1、为何需要友元
    • 2、友元函数和友元类
    • 3、关于友元的一些问题
  • 3、Copy Constructor(拷贝构造函数)
    • 拷贝构造
    • 隐式声明的拷贝构造函数
    • 在堆和栈上分别拷贝创建Employee对象
  • 4、深拷贝与浅拷贝
    • 1、Customizing Copy Constructor(定制拷贝构造函数)
    • 2、待解决的疑问

1、Destructor(析构函数)

析构函数与构造函数正好相反。
注意,重载函数以函数参数的个数以及顺序来区分,析构函数没有参数也就不可重载了。
在这里插入图片描述

在堆和栈(函数作用域与内嵌作用域)上分别创建Employee对象,观察析构函数的行为

#include<iostream>
#include<string>
using namespace std;class Date {
private:int year = 2019, month = 1, day = 1;
public:int getYear() { return year; }int getMonth() { return month; }int getDay() { return day; }void setYear(int y) { year = y; }void setMonth(int m) { month = m; }void setDay(int d) { day = d; }Date() = default;Date(int y, int m, int d) :year(y), month(m), day(d) {std::cout << "Date" << toString() << std::endl;}std::string toString() {return (std::to_string(year) + '-' + std::to_string(month) + '-' + std::to_string(day));}
};enum class Gender {male,female,
};class Employee {
private:std::string name;Gender gender;Date* birthday;
public://静态成员,用于计算雇员对象的数量static int numberOfObjects;void setName(std::string name) { this->name = name; }void setGender(Gender gender) { this->gender = gender; }void setBirthday(Date birthday) { *(this->birthday) = birthday; }std::string getName() { return name; }Gender getGender() { return gender; }Date getBirthday() { return *birthday; }std::string toString(){return (name +( (gender == Gender::male ? std::string(" male ") : std::string(" female ") )+ birthday->toString()));}//带参构造函数Employee(std::string name,Gender gender,Date birthday):name{name},gender{gender}{//自增运算,完成每构造一次对象就数目+1numberOfObjects++;//注意,构造函数new出来的对象在析构函数要delete//在堆上构造了一个新的Date对象,然后存在数据成员里面,这样就将new出来的新的data地址传递到了当前对象的birthday变量this->birthday = new Date(birthday);std::cout << "Now there are : " << numberOfObjects << " employees" << std::endl;}//默认构造函数Employee():Employee("Alan",Gender::male,Date(2000,4,1)){}//析构函数~Employee(){//当析构掉一个对象时,成员个数-1numberOfObjects--;//将在堆上面构造的变量释放掉,由于这里没有浅拷贝函数,不需要特别注意delete birthday;birthday = nullptr;std::cout << "析构掉一个->Now there are : " << numberOfObjects << " employees" << std::endl;}
};
int Employee::numberOfObjects = 0;
//在堆和栈(函数作用域与内嵌作用域)上分别创建Employee对象,观察析构函数的行为
int main()
{Employee e1;std::cout << e1.toString() << std::endl;Employee* e2 = new Employee{"John",Gender::male,Date(1990,3,2) };std::cout << e2->toString() << std::endl;//e3是在内嵌作用域内定义的对象,出了这个作用域就被析构了。{Employee e3{ "Alice",Gender::female,{1989,2,14} };std::cout << e3.toString() << std::endl;}std::cout << "Now there are : " << Employee::numberOfObjects << " employees" << std::endl;return 0;
}

e3是在内嵌作用域内定义的对象,出了这个作用域就被析构了。
在这里插入图片描述

2、Friend(友元)

1、为何需要友元

1、私有成员无法从类外访问
2、但有时又需要授权某些可信的函数和类访问这些私有成员

2、友元函数和友元类

1、用friend关键字声明友元函数或者友元类
2、友元的缺点:打破了封装性
3、可以在类外面定义,但是必须在类里面声明。

下面的例子中,Kid类和print函数都可以直接访问Date类中的私有成员

class Date {
private:int year{ 2019 } , month{ 1 };int day{ 1 };
public:friend class Kid;friend void print(const Date& d);
};
void print(const Date& d) {cout << d.year << "/" << d.month << "/" << d.day << endl;
}
class Kid {
private:Date birthday;
public:Kid() { cout << "I was born in " << birthday.year << endl; }
};
int main() {print(Date());Kid k;cin.get();
}

3、关于友元的一些问题

1、两个类可以互为友元类吗?如果你能举出例子就更好了
2、其它的面向对象编程语言中,有friend这种东西或者类似的东西吗?
3、一个类可以有友元,友元能够访问这个类中的私有/保护成员;那么,一个函数是否可以有友元,通过友元访问这个函数中的局部变量?

1、可以。
我们可以把Screen类声明为Window类的友元类,同时把Window类也声明为Screen类的友元类。这样两个类的成员函数就可以相互访问对方的私有和保护成员了。
2、没有
3、不可以

3、Copy Constructor(拷贝构造函数)

拷贝构造

拷贝构造:用一个对象初始化另一个同类对象
拷贝构造函数可以简写为 copy ctor,或者 cp ctor。
如何声明拷贝构造函数(copy ctor)

Circle (Circle&);
Circle (const Circle&);
Circle c1( 5.0 ); 
Circle c2( c1 );    //c++03
Circle c3 = c1;     //c++03
Circle c4 = { c1 }; //c++11
Circle c5{ c1 };    //c++11

带有额外的默认参数的拷贝构造函数

class X {  //来自C++11标准: 12.8节
// ...
public:X(const X&, int = 1);
};
X b(a, 0); // calls X(const X&, int);
X c = b;   // calls X(const X&, int);

两个对象obj1和obj2已经定义。然后这种形式的语句:

obj1 = obj2;

不是调用拷贝构造函数,而是对象赋值。

反之,如下语句:

AClass aObject = bObject; // bObject也是AClass类型的对象

虽然有“等号(=)”,但由于是在定义对象的时候“赋值”,此时的“等号(=)”被解释为初始化,需要调用拷贝构造函数。

隐式声明的拷贝构造函数

一般情况下,如果程序员不编写拷贝构造函数,那么编译器会自动生成一个。自动生成的拷贝构造函数叫做“隐式声明/定义的拷贝构造函数”。
一般情况下,隐式声明的copy ctor简单地将作为参数的对象中的每个数据域复制到新对象中。

在堆和栈上分别拷贝创建Employee对象

#include<iostream>
#include<string>
using namespace std;class Date {
private:int year = 2019, month = 1, day = 1;
public:int getYear() { return year; }int getMonth() { return month; }int getDay() { return day; }void setYear(int y) { year = y; }void setMonth(int m) { month = m; }void setDay(int d) { day = d; }Date() = default;Date(int y, int m, int d) :year(y), month(m), day(d) {std::cout << "Date" << toString() << std::endl;}std::string toString() {return (std::to_string(year) + '-' + std::to_string(month) + '-' + std::to_string(day));}
};enum class Gender {male,female,
};class Employee {
private:std::string name;Gender gender;Date* birthday;
public://静态成员,用于计算雇员对象的数量static int numberOfObjects;void setName(std::string name) { this->name = name; }void setGender(Gender gender) { this->gender = gender; }void setBirthday(Date birthday) { *(this->birthday) = birthday; }std::string getName() { return name; }Gender getGender() { return gender; }Date getBirthday() { return *birthday; }std::string toString(){return (name + ((gender == Gender::male ? std::string(" male ") : std::string(" female ")) + birthday->toString()));}//带参构造函数Employee(std::string name, Gender gender, Date birthday) :name{ name }, gender{ gender }{//自增运算,完成每构造一次对象就数目+1numberOfObjects++;//注意,构造函数new出来的对象在析构函数要delete//在堆上构造了一个新的Date对象,然后存在数据成员里面,这样就将new出来的新的data地址传递到了当前对象的birthday变量this->birthday = new Date(birthday);std::cout << "Now there are : " << numberOfObjects << " employees" << std::endl;}//默认构造函数Employee() :Employee("Alan", Gender::male, Date(2000, 4, 1)) {}//拷贝构造函数Employee(const Employee& e1) {this->birthday = e1.birthday;this->name = e1.name;this->gender = e1.gender;//个数也需要+1numberOfObjects++;std::cout << "Employee(const Employee&) is invoked" << std::endl;}//析构函数~Employee(){//当析构掉一个对象时,成员个数-1numberOfObjects--;//注意如果析构的是浅拷贝函数且被拷贝对象已经被delete了,则不需要delete这个数据//delete birthday;//birthday = nullptr;std::cout << "析构掉一个->Now there are : " << numberOfObjects << " employees" << std::endl;}
};
int Employee::numberOfObjects = 0;
//在堆和栈上分别拷贝创建Employee对象
int main()
{//默认构造Employee e1;std::cout << e1.toString() << std::endl;//拷贝构造Employee e2 = {e1};std::cout << e2.toString() << std::endl;//在堆上构造Employee* e3 = new Employee{ "John",Gender::male,Date(1990,3,2) };std::cout << e3->toString() << std::endl;std::cout << std::endl;std::cout << "Now there are : " << Employee::numberOfObjects << " employees" << std::endl;return 0;
}

在这里插入图片描述

4、深拷贝与浅拷贝

由于上面的拷贝函数,我们是将一个对象的所有数据成员否赋值给一个新的对象,所以会出现一个问题。
如果一个数据成员是指针类型(地址),那么我们新构造的对象的这个数据的地址也是这个。
对于非地址数据,则不会有这个问题。
我感觉,这也是拷贝函数的一个漏洞,一般来说我直观理解的拷贝就是深拷贝而非浅拷贝。
浅拷贝:数据域是一个指针,只拷指针的地址,而非指针指向的内容
在两种情况下会出现浅拷贝:

创建新对象时,调用类的隐式/默认构造函数
为已有对象赋值时,使用默认赋值运算符

深拷贝:拷贝指针指向的内容
解释:
前提条件:类A中有个指针p,指向一个外挂对象b(b是B类型的对象);如果类A里面没有指针成员p,那也就不要谈深浅拷贝了。
现在有一个类A的对象a1(a1的指针p指向外挂对象b1)。以拷贝构造的方式,创建a1的一个拷贝a2。

(1) 如果仅仅将a1.p的值(这个值是个地址)拷贝给 a2.p,这就是浅拷贝。浅拷贝之后,a1.p和a2.p都指向外挂对象 b1
(2) 如果创建一个外挂对象b2,将 a2.p指向b2;并且将b1的值拷贝给b2,这就是深拷贝

Employee e1{"Jack", Date(1999, 5, 3),  Gender::male};
Employee e2{"Anna", Date(2000, 11, 8), Gender:female};
Employee e3{ e1 };  //cp ctor,执行一对一成员拷贝

创建 e3 对象时,调用了Employee的拷贝构造函数。
上面的代码执行之后,e3.birthday指针指向了 e1.birthday所指向的那个Date对象,这样会导致修改e1,e2对象也会被修改。
在这里插入图片描述

1、Customizing Copy Constructor(定制拷贝构造函数)

如何深拷贝

(1) 自行编写拷贝构造函数,不使用编译器隐式生成的(默认)拷贝构造函数
(2) 重载赋值运算符,不使用编译器隐式生成的(默认)赋值运算符函数

此时我们根据被拷贝对象来生成一个新的对象,然后把这个对象赋给拷贝对象。

class Employee {
public:// Employee(const Employee &e) = default; //浅拷贝ctorEmployee(const Employee& e){    //深拷贝ctorbirthdate = new Date{ e.birthdate };} // ...
}
Employee e1{"Jack", Date(1999, 5, 3), Gender::male};
Employee e2{"Anna", Date(2000, 11, 8),, Gender:female};
Employee e3{ e1 };  //cp ctor 深拷贝

2、待解决的疑问

有关浅拷贝对象以及它的析构的一个问题
如果我们使用浅拷贝构造函数:

Employee(const Employee& e1) {

    this->birthday = e1.birthday;this->name = e1.name;this->gender = e1.gender;//个数也需要+1numberOfObjects++;std::cout << "Employee(const Employee&) is invoked" << std::endl;}

然后我们在主函数用到了这个浅拷贝构造函数,由于我们带参构造函数是在堆new了一个新的数据对象

//带参构造函数Employee(std::string name, Gender gender, Date birthday) :name{ name }, gender{ gender }{//自增运算,完成每构造一次对象就数目+1numberOfObjects++;//注意,构造函数new出来的对象在析构函数要delete//在堆上构造了一个新的Date对象,然后存在数据成员里面,这样就将new出来的新的data地址传递到了当前对象的birthday变量this->birthday = new Date(birthday);std::cout << "Now there are : " << numberOfObjects << " employees" << std::endl;}

所以在析构函数中我们会delete这个数据

    delete birthday;birthday = nullptr;

那么问题来了:

我们在delete拷贝构造出来的对象时,如果它指向对象已经被析构了,也就是说birthday 已经被delete了,这时候编译器就会报错,如何解决这个问题呢?
这个问题我已经在慕课上提问了,等老师回复再做更新。

解决回复:

一般来说,普通构造函数中有为类成员分配内存的操作,那么拷贝构造函数、重载的赋值运算符函数均需要执行深拷贝。

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

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

相关文章

用mstsc /console 强行“踢”掉其它在线的用户

由于公司有很多windows服务器&#xff0c;而且有很大一部分不在国内&#xff0c;所以经常需要使用远程桌面进行连接&#xff0c;这其中&#xff0c;就会经常遇到因为超出了最大连接数&#xff0c;而不能连接的事情&#xff0c;其中最头痛的就是&#xff0c;在连接国外的服务器时…

set vector_Java Vector set()方法与示例

set vector向量类set()方法 (Vector Class set() method) set() method is available in java.util package. set()方法在java.util包中可用。 set() method is used to replace the old element with the given element (ele) when it exists otherwise it sets the given ele…

Android PreferenceActivity 使用

我想大家对于android的系统配置界面应该不会陌生吧&#xff0c;即便陌生&#xff0c;那么下面的界面应该似曾相识吧&#xff0c;假若还是不认识&#xff0c;那么也没有关系&#xff0c;我们这一节主要就是介绍并讲解android 中系统配置界面的使用&#xff0c;相信大家看完本节后…

Pandas(数据分析处理库)---讲解

本内容来自《跟着迪哥学Python数据分析与机器学习实战》&#xff0c;该篇博客将其内容进行了整理&#xff0c;加上了自己的理解&#xff0c;所做小笔记。若有侵权&#xff0c;联系立删。 迪哥说以下的许多函数方法都不用死记硬背&#xff0c;多查API多看文档&#xff0c;确实&a…

hdu 1141

地址&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid1141 题意&#xff1a;atmel公司1960年发布4bits的处理器&#xff0c;每10年翻一番。给一个年份&#xff0c;问最近一次发布的处理器能运算的n!最大的n是多少。 mark&#xff1a;最大的处理器位数是2160年的4194304…

leetcode 78. 子集 思考分析

题目 给定一组不含重复元素的整数数组 nums&#xff0c;返回该数组所有可能的子集&#xff08;幂集&#xff09;。 说明&#xff1a;解集不能包含重复的子集。 思考分析 画出解空间树。 我们可以发现我们所需要的结果是解空间的所有结点。而我们之前组合问题和分割问题都是…

PHP checkdate()函数与示例

PHP checkdate()函数 (PHP checkdate() function) checkdate() function is used to check the valid Gregorian dates. It accepts the date and returns Boolean values (True/False) based on the date values. checkdate()函数用于检查有效的公历日期。 它接受日期&#xf…

设计模式读书笔记-----备忘录模式

个人比较喜欢玩单机游戏&#xff0c;什么仙剑、古剑、鬼泣、使命召唤、三国无双等等一系列的游戏我都玩过(现在期待凡人修仙传)&#xff0c;对于这些游戏除了剧情好、场面大、爽快之外&#xff0c;还可以随时存档&#xff0c;等到下次想玩了又可以从刚开始的位置玩起(貌似现在的…

【C++grammar】vector类和字符串字面量

C的vector类 用数组存放数据时&#xff0c;容量大小不可变&#xff0c;vector对象容量可自动增大。 vector的操作&#xff1a; 调用push_back函数时&#xff0c;vector对象的容量可能会增大。 观察下列操作对vector的影响&#xff1a; #include <vector> #include <…

除去数组中的空字符元素array_filter

<?php$str1_arrayarray(电影,,http://www,,1654,);$str1_arrayarray_filter($str1_array);print_r($str1_array); ?>显示结果&#xff1a;Array( [0] > 电影 [2] > http://www [4] > 1654) 转载于:https://www.cnblogs.com/skillCoding/archive/20…

date.after方法_Java Date after()方法与示例

date.after方法日期类after()方法 (Date Class after() method) after() method is available in java.util package. after()方法在java.util包中可用。 after() method is used to check whether this date is after the given date (d) or not. after()方法用于检查此日期是…

Matplotlib(数据可视化库)---讲解

本内容来自《跟着迪哥学Python数据分析与机器学习实战》&#xff0c;该篇博客将其内容进行了整理&#xff0c;加上了自己的理解&#xff0c;所做小笔记。若有侵权&#xff0c;联系立删。 迪哥说以下的许多函数方法都不用死记硬背&#xff0c;多查API多看文档&#xff0c;确实&a…

找min和max

看到的貌似是阿里的笔试题&#xff0c;题意是一组数&#xff0c;要找到min和max&#xff0c;同时要求时间复杂度&#xff08;比较次数&#xff09;小于2n&#xff08;2n的办法都想得到&#xff09;。 别人的思路&#xff1a;n个数的数组里看作每两个一组&#xff0c;若n是奇数&…

Shader Compiler 界面进展1

先从模仿Composer的界面开始. 目前的进展:不用不知道,虽然wxweidgets有很多界面工具如DialogBlocks(DB), 但仍然不好使. 我使用wxAui界面, DialogBlocks并不支持输出其xrc格式, 我猜是wx本身就没有解析wxAui的xrc格式.像wxAuiToolBar或其他wxToolBar, DB工具也不能独立输出xrc.…

leetcode 90. 子集 II 思考分析

与本题相关联的题目解析&#xff1a; leetcode 78. 子集 思考分析 leetcode 40. 组合总和 II思考分析 题目 给定一个可能包含重复元素的整数数组 nums&#xff0c;返回该数组所有可能的子集&#xff08;幂集&#xff09;。 说明&#xff1a;解集不能包含重复的子集。 思考 …

java bitset_Java BitSet and()方法与示例

java bitsetBitSet类和()方法 (BitSet Class and() method) and() method is available in java.util package. and()方法在java.util包中可用。 and() method is used to perform logical AND between two Bitset. This bit set is updated so that every bit holds the value…

Redis-主从复制

一、Redis的Replication&#xff1a; 这里首先需要说明的是&#xff0c;在Redis中配置Master-Slave模式真是太简单了。相信在阅读完这篇Blog之后你也可以轻松做到。这里我们还是先列出一些理论性的知识&#xff0c;后面给出实际操作的案例。 下面的列表清楚的解释了Redis…

.wav音乐文件转换为.fft.npy频谱格式文件

需要修改的地方 十个文件夹&#xff0c;每个文件夹下都有100首.au格式的音乐&#xff0c;这里举个例子&#xff0c;那其中5个类别进行转换 genre_list ["classical", "jazz", "country", "pop", "rock", "metal"…

WINDOWS编程笔记 2012.2.7

操作系统感知事件和传递事件是通过消息机制来实现的typedef struct tagMSG{ HWND hwnd; //窗口的句柄 UINT message; WPARAM wParam; //信息的附加参数 LPARAM lParam; DWORD time; //消息传递的时间 POINT pt; //消息投递的时候&#xff0c;光标的位置}…

php 邮件验证_PHP程序来验证电子邮件地址

php 邮件验证Suppose there is a form floating where every user has to fill his/her email ID. It might happen that due to typing error or any other problem user doesnt fill his/her mail ID correctly. Then at that point, the program should be such that it sho…