C/C++教程

复习 C++ 类(二)拷贝构造,赋值运算符,析构(1)

本文主要是介绍复习 C++ 类(二)拷贝构造,赋值运算符,析构(1),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

拷贝,赋值,销毁

在类的基本概念一文中,有讲过这三种操作

如果我们定义一个空类

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;
}

image

拷贝构造函数也可以使用委托构造方式初始化成员

拷贝构造函数的参数数量可以是1-n个

浅拷贝 (shallow copy)

类的数据域是一个指针,只拷贝指针的地址,而非指向的内容

发生的两种情况:
(1)使用隐式的构造函数

(2)使用=,为已有对象赋值的默认赋值运算符

image

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};

image

深浅拷贝的问题是由类中的指针类型引起的

拷贝赋值运算符

如果一个运算符是成员函数,其左侧运算对象就会被绑定到隐式的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;

image

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-image
.png)

指向一个对象的引用或指针离开作用域时,析构函数不会执行

合成的析构函数

如果为空,则成员都会被自动销毁(注意:不是函数体销毁成员,而是在函数体之后隐式的析构阶段中销毁)

如果不为空,则可能是对于某些类,合成析构函数用于阻止该类型的对象被销毁

//如果有以下声明,则不允许编译器合成析构函数
~C() = delete;

13.9

什么时候会生成合成析构函数:
在一个类未定义自己的析构函数时,编译器会定义一个合成析构函数

image

#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;
}

image

= 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;
这篇关于复习 C++ 类(二)拷贝构造,赋值运算符,析构(1)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!