拷贝,赋值,销毁
在类的基本概念一文中,有讲过这三种操作
如果我们定义一个空类
class Empty
{
};
如果我们自己没有声明拷贝构造,拷贝赋值,析构函数,编译器在处理之后会自动添加此三函数的默认版本
(当然如果没有声明任何构造函数,也会声明一个default构造函数)
以上编译器生成的操作都是inline和public的
上面的空类就如同:
class Empty
{
Empty(){}
Empty(const &Empty){}
Empty& operator=(Empty&){}
~Empty(){}
};
且只有被调用时,这些函数才被创建
//调用方法:
Empty p1;//default构造函数
Empty p2(p1);//copy构造函数
p2 = p1;//拷贝赋值操作符
拷贝构造函数
带有默认参数的也可以是拷贝构造函数
class X
{
public:
X(const x&,int i = 1);
};
int main()
{
X b(a,0);
X c = b;
}
一般合成的拷贝构造函数会将给定对象的每个非static成员一次拷贝到创建的对象中
成员的类型决定拷贝的方式:
(1)类类型:使用其拷贝构造函数
(2)内置类型直接拷贝
(3)不能拷贝一个数组,但是可以逐个元素的拷贝数组中的成员(如果数组元素是类类型,则还会用到其拷贝构造函数)
调用拷贝构造函数
Circle c1(5,0);
//c++03:
Circle c2(c2);
//虽然使用=运算符,但是因为是定义时,所以也是调用拷贝构造函数
Circle c3 = c1;
//c++11:
Circle c4{c1};
Circle c5 = c1;
注意注意,=只有定义对象时,才是拷贝构造
//拷贝构造函数使用和定义实例
#pragma once
#include<iostream>
using namespace std;
class Square
{
private:
double side{1.0};
static int numberOfObjects;//创建对象的个数
public:
Square():Square(1.0){}//代理构造(委托构造)
Square(double side)
{
this->side = side;
numberOfObjects++;
}
double getSide(){ return side; }
void setSide(double side){ this->side = side; }
double getArea()
{
return side*side;
}
static int getObjectNum()
{
return numberOfObjects;
}
//拷贝构造函数,此处将参数声明为const的,防止在函数体中实参被意外修改
Square(const Square&rhs)
{
this->side = rhs.side;
numberOfObjects++;
cout<<"Squre(const Squre&)is invoked"<<endl;
}
~Square()
{
numberOfObjects--;
}
};
#include"Square.h"
#include<iostream>
using namespace std;
int Square::numberOfObjects = 0;//静态成员初始化
int main()
{
//一次输出每次的对象个数
Square s1(10.0);
cout<<Square::getObjectNum()<<endl;
Square s2{s1};
cout<<Square::getObjectNum()<<endl;
//将s1传递给拷贝构造函数,创建一个匿名对象,放在堆区,并将匿名对象的地址存在指针中
Square *s3 = new Square{s1};
cout<<Square::getObjectNum()<<endl;
system("pause");
return 0;
}
拷贝构造函数也可以使用委托构造方式初始化成员
拷贝构造函数的参数数量可以是1-n个
浅拷贝 (shallow copy)
类的数据域是一个指针,只拷贝指针的地址,而非指向的内容
发生的两种情况:
(1)使用隐式的构造函数
(2)使用=,为已有对象赋值的默认赋值运算符
e3使用默认拷贝构造函数创建,所以是浅拷贝,将e1的date类型指针拷贝给e3的date,这样这对象种的date指针成员就会指向同一个Date对象(如上图右侧所示),如果此时修改e3,date指向的内容,那么e1的内容也会改变
浅拷贝会导致对象的互相之间的干扰
深拷贝(deep copy)行为像值的类
要拷贝指针指向的内容
发生情况:重载拷贝赋值运算符,或编写拷贝构造函数去拷贝指针指向的内容
Employee{
Employee(const Employee&e) = default;//浅拷贝
Employee(const Employee&e)//深拷贝,不是拷贝指针,而是拷贝指针指向的值
{
birthday = new Date(*e.birthday);
}
};
//这样调用,就是深拷贝
Employee e3{e1};
深浅拷贝的问题是由类中的指针类型引起的
拷贝赋值运算符
如果一个运算符是成员函数,其左侧运算对象就会被绑定到隐式的this参数
拷贝赋值运算符返回一个指向左侧运算对象的引用
因为引用返回左值,其他类型返回右值
合成的版本将右侧对象的每个非static成员赋予左侧运算对象的相应成员
Sales_data& Sales_data&::operator=(const Sales_data&ths)
{
bookNo = rhs.bookNo;
units_sold = rhs.units_sold;
revenue = rhs.revenue;
return *this;
}
拒绝合成拷贝赋值运算符的情况
1.C++不允许引用更改指向的对象
2.且更改const成员也不合法,所以如果成员为引用或const的,则编译器拒绝合成,我们只能自定义此操作符
3.如果本类的基类将=运算符置为private,派生类的合成拷贝运算符应该可以处理基类部分,但是如果无权调用基类=运算符,则拒绝合成
重载=,改变其默认工作方式
(需要深拷贝时)
如果想要使用a = b = c形式,就要返回引用类型
默认使用合成的版本时的浅拷贝
Employee e1{"Jack",Date{1999,5,3},Gender::male};
Employee e2{"Anna",Date{2000,11,8},Gender::female};
e2 = e1;
class Emplotee{
public:
Employee& operator=(const Employee&e)//重载的是Employee类型的赋值运算符
{
name = e.name;
this->gender = e.gender;
*this->birthday = *e.birthday;//拷贝的是所指的对象,而不是指针
}
};
析构函数
析构函数负责释放对象使用的资源,销毁对象的非static数据成员
析构函数不接收参数,所以不能重载,一个类只有一个
析构函数首先执行函数体,然后按初始化的逆序销毁成员
成员销毁时,依赖自身的类型,如果是类类型,则使用其析构函数,如果是内置类型,则什么也不需要做
析构函数在以下情况自动调用:
![image-20211023154643151](C:\Users\god\AppData\Roaming\Typora\typora-user-images\image-
.png)
指向一个对象的引用或指针离开作用域时,析构函数不会执行
合成的析构函数
如果为空,则成员都会被自动销毁(注意:不是函数体销毁成员,而是在函数体之后隐式的析构阶段中销毁)
如果不为空,则可能是对于某些类,合成析构函数用于阻止该类型的对象被销毁
//如果有以下声明,则不允许编译器合成析构函数
~C() = delete;
13.9
什么时候会生成合成析构函数:
在一个类未定义自己的析构函数时,编译器会定义一个合成析构函数
#pragma once
#include<iostream>
#include<string>
#include"Date.h"
enum class Gender
{
male ,
famale ,
};
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 } {
numberOfObjects++;
this->birthday = new Date(birthday);//在堆上创建一个Date对象,调用了Date的合成版本的拷贝构造函数
std::cout<<"Employee create"<<" now have"<<numberOfObjects<<" objects"<<std::endl;
}
Employee() : Employee("Alan" , Gender::male , Date(2000 , 4 , 1)) {}
~Employee()
{
numberOfObjects--;
//归还内存
delete birthday;
birthday = nullptr;
std::cout<<"Employee delete"<<" now have"<<numberOfObjects<<" objects"<<std::endl;
}
};
#pragma once
#include<iostream>
#include<string>
class Date
{
private:
int year = 2019,month = 10,day = 5;//给初始值,因为默认构造函数没有将成员初始化
public:
int getYear(){ return year; }
int getMonth(){ return month; }
int getDay(){ return day; }
void SetYear(int year){ this->year = year; }
void SetMonth(int month){ this->month = month; }
void SetDay(int day){ this->day = day; }
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));
}
};
#include<iostream>
#include"Date.h"
#include"Employee.h"
using namespace std;
int Employee::numberOfObjects = 0;
int main()
{
Employee e1;
std::cout<<e1.toString()<<endl;
Employee* e2 = new Employee("John",Gender::male,Date(1990,3,2));
std::cout<<e2->toString()<<endl;
//内嵌作用域
{
Employee e3{"Alice",Gender::famale,{1989,2,14}};
std::cout<<e3.toString()<<endl;//由于e3在内嵌作用域中定义,所以出了这个作用域,e3就被销毁
}
//由于这里有输入的阻塞,所以程序未结束,所以e1,e2还没有销毁
cin.get();
return 0;
}
= default
显式的要求编译器生成合成版本的函数(也只能对具有合成版本的函数使用)
使用=default,合成的函数将会隐式的声明为内联的
如果不想内联,则只在类外定义时使用 = default
class Sales_data
{
Sales_data() = default;
Sales_data(const Sales_data&) = default;
~Sales_data() = default;
Sales_data& operator=(const Sales_data&);
};
//如果不想声明为内联的:
Sales_data& Sales_data::operator=(const Sales_data&) = default;