在类的基本概念一文中,有讲过这三种操作
如果我们定义一个空类
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个
类的数据域是一个指针,只拷贝指针的地址,而非指向的内容
发生的两种情况:
(1)使用隐式的构造函数
(2)使用=,为已有对象赋值的默认赋值运算符
e3使用默认拷贝构造函数创建,所以是浅拷贝,将e1的date类型指针拷贝给e3的date,这样这对象种的date指针成员就会指向同一个Date对象(如上图右侧所示),如果此时修改e3,date指向的内容,那么e1的内容也会改变
浅拷贝会导致对象的互相之间的干扰
要拷贝指针指向的内容
发生情况:重载拷贝赋值运算符,或编写拷贝构造函数去拷贝指针指向的内容
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
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;