ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2021-11-03 13:32:35  阅读:159  来源: 互联网

标签:int C++ 运算符 Employee 析构 Date 拷贝 构造函数


拷贝,赋值,销毁

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

如果我们定义一个空类

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;

标签:int,C++,运算符,Employee,析构,Date,拷贝,构造函数
来源: https://www.cnblogs.com/ziggystardust-pop/p/15503159.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有