【三】【C++】类与对象(二)

news2024/9/23 17:17:37

类的六个默认成员函数

在C++中,有六个默认成员函数,它们是编译器在需要的情况下自动生成的成员函数,如果你不显式地定义它们,编译器会自动提供默认实现。这些默认成员函数包括:

  1. 默认构造函数 (Default Constructor): 如果你没有为类显式定义任何构造函数,编译器将生成一个无参的默认构造函数。用于创建对象而不需要提供任何参数。例如:ClassName obj;

  2. 析构函数 (Destructor): 如果你没有为类定义析构函数,编译器将生成一个默认的析构函数。用于在对象被销毁时释放资源、进行清理工作等操作。例如:当对象超出作用域、被删除或者动态分配的对象被 delete 时。

  3. 拷贝构造函数 (Copy Constructor):(具有缺陷---浅拷贝 如果你没有为类定义拷贝构造函数,编译器将生成一个默认的拷贝构造函数。用于创建一个对象作为另一个对象的副本,通常在赋值、传递参数等情况下调用。例如:ClassName obj2 = obj1;

  4. 赋值运算符重载 (Copy Assignment Operator):(具有缺陷---浅拷贝 如果你没有为类定义赋值运算符重载函数,编译器将生成一个默认的赋值运算符重载函数。用于将一个对象的值复制给另一个对象,通常在赋值操作中调用。例如:obj2 = obj1;

  5. 移动构造函数 (Move Constructor)

  6. 移动赋值运算符 (Move Assignment Operator)

主要常见的是前四个,本篇文章只介绍前四个默认成员函数,最后两个有兴趣的小伙伴可以自己去查一下别的资料。

初始化操作

 
/*1.正常使用Data类初始化*/
#include <iostream>
using namespace std;
class Data {
    public:
        void Init(int year, int month, int day) {
            _year = year;
            _month = month;
            _day = day;
        }
        void ShowInfo() {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
 };
int main() {
    Data d1;
    d1.Init(2024, 1, 29);
    d1.ShowInfo();

    Data d2;
    d2.Init(2024, 1, 28);
    d2.ShowInfo();

    return 0;
 }

每当我们定义一个自定义类型,创造对象的时候,一定需要对其进行初始化操作。既然对于所有的对象都需要进行初始化操作,那么每次显示调用Init函数就显得非常冗余。如果每当我们创建一个对象,自动对其进行初始化,就省去显示调用Init函数的步骤。

构造函数

C++引入了一个新的概念,构造函数。构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象的整个生命周期内只调用一次。

 
/*2.使用构造函数进行初始化*/
#include <iostream>
using namespace std;
class Data {
    public:
        Data(int year, int month, int day) {
            _year = year;
            _month = month;
            _day = day;
        }
        void ShowInfo() {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
 };
int main() {
    Data d1(2024,1,29);
    d1.ShowInfo();

    Data d2(2024,1,28);
    d2.ShowInfo();

    return 0;
 }

这样我们每创建一个对象,编译器就会自动调用对应的构造函数对其进行初始化操作。也就是说构造函数是为了方便我们对实例化对象进行初始化而创造出来的。

构造函数的特性

构造函数是一种特殊的成员函数。它的特性是:

  1. 函数名与类名相同。

  2. 无返回值。

  3. 对象实例化时编译器自动调用对应的构造函数。

  4. 构造函数可以重载。

 
/*3.初步介绍构造函数*/
#include <iostream>
using namespace std;
class Data {
public:
    Data(){

    }
    Data(int year,int month,int day){
        _year=year;
        _month=month;
        _day=day;
    }
    void ShowInfo() {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
 };
void TestDate(){
    Data d1;
    Data d2(2024,1,28);
 }
int main() {
    TestDate();
    return 0;
 }

探究构造函数的规则---创建对象自动调用构造函数

 
/*4.探究构造函数的规则---创建对象自动调用构造函数*/
#include <iostream>
using namespace std;
class Data {
public:
    Data(){
        cout<<"Data()"<<endl;
    }
    Data(int year,int month,int day){
        cout<<"Data(int year,int month,int day)"<<endl;
        _year=year;
        _month=month;
        _day=day;
    }
    void ShowInfo() {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
 };
void TestDate(){
    Data d1;
    Data d2(2024,1,28);
    d2.ShowInfo();
 }
int main() {
    TestDate();
    return 0;
 }

当我们创建d1对象时,后面不加括号表示调用无参的构造函数。后面带括号,括号里带参数,表示调用有参的构造函数。创建对象的时候系统会自动调用对应的构造函数对其进行初始化操作。

探究构造函数的规则---编译器默认生成无参构造函数

 
/*5.探究构造函数的规则---编辑器默认有无参构造函数*/
#include <iostream>
using namespace std;
class Data {
public:
    void ShowInfo() {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
 };
void TestDate(){
    Data d1;
    d1.ShowInfo();
 }
int main() {
    TestDate();
    return 0;
 }

当我们没有显示编写构造函数的时候,创建完对象后我们发现对象内年月日的初始值是0。

如果类中没有显示定义构造函数,编译器会自动生成一个无参的默认构造函数,但是一旦我们显示定义了构造函数,编译器就不会自动生成无参构造函数。

探究构造函数的规则---自己创建有参的构造函数时,编辑器不会自动创建无参构造函数

 
/*6.探究构造函数的规则---自己创建有参的构造函数时,编辑器不会自动创建无参构造函数*/
#include <iostream>
using namespace std;
class Data {
public:
//    Data(){
 //        cout<<"Data()"<<endl;
 //    }
    Data(int year,int month,int day){
        cout<<"Data(int year,int month,int day)"<<endl;
        _year=year;
        _month=month;
        _day=day;
    }
    void ShowInfo() {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
 };
void TestDate(){
    Data d1;
    Data d2(2024,1,28);
    d2.ShowInfo();
 }
int main() {
    TestDate();
    return 0;
 }

我们显示定义了有参的构造函数,此时编译器不会自动生成无参的构造函数。当我们创建对象,后面不加括号的时候,系统会自动调用无参的构造函数,但是类类型中没有无参的构造函数,编译器没有生成,所以就报错了。解决的办法就是自己再写一个无参的构造函数。

探究构造函数的规则---默认构造函数具体情况---无参,全缺省

 
/*7.探究构造函数的规则---默认构造函数具体情况---无参,全缺省*/
#include <iostream>
using namespace std;
class Data {
public:
    Data(){
        cout<<"Data()"<<endl;
        _year=1999;
        _month=1;
        _day=1;
    }
    Data(int year=2024,int month=2,int day=1){
        cout<<"Data(int year,int month,int day)"<<endl;
        _year=year;
        _month=month;
        _day=day;
    }
    void ShowInfo() {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
 };
void TestDate(){
    Data d1;
    d1.ShowInfo();
 }
int main() {
    TestDate();
    return 0;
 }

当我们没有显示的定义构造函数时,编译器会默认帮我们定义一个无参的构造函数。全缺省的构造函数等价于无参的构造函数。因此不可以同时存在,当我们创建对象后面不加括号时,系统调用无参的构造函数,此时编译器不知道调用无参的构造函数还是全缺省的构造函数。

问题产生的本质是缺省参数导致重载模棱两可,可以理解为缺省参数不存在,此时含有缺省参数的函数与另一个函数不构成重载,即使编译器没有报错。

 
/*7.缺省参数形成冲突*/
#include <iostream>
using namespace std;
class Data {
    public:

        Data(int year, int month = 2, int day = 1) {
            cout << "Data(int year,int month,int day)" << endl;
            _year = year;
            _month = month;
            _day = day;
        }
        Data(int year) {
            _year = year;
            _month = 1;
            _day = 1;
        }
        void ShowInfo() {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
 };
void TestDate() {
    Data d1(2024);
    d1.ShowInfo();
 }
int main() {
    TestDate();
    return 0;
 }

当我们创建对象传入2024一个参数的时候,编译器不知道调用有缺省参数的函数还是没有缺省参数的函数,缺省参数导致重载模棱两可,可以理解为缺省参数不存在,即Data(int year, int month = 2, int day = 1) 等价于Data(int year) 与下面的 Data(int year)不构成重载,即使编译器不会报错。

清理操作

 
/*8.正常使用栈,进行清理工作*/
#include <iostream>
using namespace std;
typedef int DataType;
class Stack {
    public:
        Stack(int capacity = 4) {
            _array = (DataType*)malloc(sizeof(DataType) * capacity);
            _capacity = capacity;
            _size = 0;
        }
        void CheckCapacity(){
            if(_capacity==_size){
                int newcapacity=_capacity*2;
                _array=(DataType*)realloc(_array,sizeof(DataType)*newcapacity);
                _capacity=newcapacity;
            }
        }
        void push(DataType data) {
            CheckCapacity();
            _array[_size++] = data;
        }
        void pop() {
            _size--;
        }
        void Destroyed() {
            free(_array);
            _array = NULL;
            _capacity = 0;
            _size = 0;
        }
        bool empty() {
            return _size == 0;
        }
        DataType top() {
            return _array[_size - 1];
        }
    private:
        DataType* _array;
        int _capacity;
        int _size;
 };
void TestStack() {
    Stack st;
    st.push(1);
    st.push(2);
    st.push(3);
    st.push(4);
    st.push(5);
    while (!st.empty()) {
        cout << st.top() << endl;
        st.pop();
    }
    st.Destroyed();

 }
int main() {
    TestStack();
    return 0;
 }

当我们使用创建对象时,使用完之后都需要显示调用Destroyed销毁函数进行清理工作,防止内存泄漏。既然对于所有的对象都需要进行清理操作,那么每次显示调用Destroyed函数就显得非常冗余。如果每当我们创建一个对象,对象出了作用域之后自动对其进行销毁,就省去显示调用Destroyed函数的步骤。

析构函数

 
/*9.使用析构函数,进行清理工作*/
#include <iostream>
using namespace std;
typedef int DataType;
class Stack {
    public:
        Stack(int capacity = 4) {
            _array = (DataType*)malloc(sizeof(DataType) * capacity);
            _capacity = capacity;
            _size = 0;
        }
        void CheckCapacity() {
            if (_capacity == _size) {
                int newcapacity = _capacity * 2;
                _array = (DataType*)realloc(_array, sizeof(DataType) * newcapacity);
                _capacity = newcapacity;
            }
        }
        void push(DataType data) {
            CheckCapacity();
            _array[_size++] = data;
        }
        void pop() {
            _size--;
        }
        ~Stack() {
            free(_array);
            _array = NULL;
            _capacity = 0;
            _size = 0;
        }
        bool empty() {
            return _size == 0;
        }
        DataType top() {
            return _array[_size - 1];
        }
    private:
        DataType* _array;
        int _capacity;
        int _size;
 };
void TestStack() {
    Stack st;
    st.push(1);
    st.push(2);
    st.push(3);
    st.push(4);
    st.push(5);
    while (!st.empty()) {
        cout << st.top() << endl;
        st.pop();
    }

 }
int main() {
    TestStack();
    return 0;
 }

C++引入了一个新的概念,析构函数。析构函数是一个特殊的成员函数,名字与类名相同,名字前面添加‘~’,类类型对象出作用域时由编译器自动调用。

析构函数的特性

析构函数是特殊的成员函数,其特征如下:

  1. 析构函数名是在类名前加上字符 ~。

  2. 无参数无返回值类型。

  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。

  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

 
/*10.初步介绍析构函数*/
#include <iostream>
using namespace std;
class Date {
    public:
        Date(int year, int month, int day) {
            cout << "Date(int year, int month, int day)" << endl;
            _year = year;
            _month = month;
            _day = day;
        }
        void Show() {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
        ~Date() {
            cout << "~Date()" << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
 };

void Test() {
    Date d1(2024, 1, 29);
    d1.Show();

 }
int main() {
    Test();
    return 0;
 }

拷贝构造

 
/*11.正常用一个已有的变量拷贝构造一个新变量---内置类型*/
#include <iostream>
using namespace std;

int main() {
    int a=10;
    int b=a;
    cout<<b<<endl;
    return 0;
 }

我们用已经存在的a变量去构造新的b变量,很明显如果a、b都是内置类型,我们很容易理解。但是如果我们希望用一个已经存在的自定义类型去构造新的自定义类型对象,应该如何操作?

 
/*12.正常用一个已有的变量拷贝构造一个新变量---自定义类型*/
#include <iostream>
using namespace std;
class Date {
    public:
        Date() {
        }
        Date(int year, int month, int day) {
            _year = year;
            _month = month;
            _day = day;
        }
        void Show() {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
        ~Date() {
            cout << "~Date()" << endl;
        }
        void assignment(const Date& d) {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
    private:
        int _year;
        int _month;
        int _day;
 };

void Test() {
    Date d1(2024, 1, 29);
    //d2=d1?
    Date d2;
    d2.assignment(d1);

    d2.Show();

 }
int main() {
    Test();
    return 0;
 }

我们可以在类里面定义一个函数,函数的参数是自定义类型的引用,作用是进行赋值,这样我们创建一个新对象后,调用这个对象的赋值函数,就可以实现我们想要的效果。我们可以发现对于每一个自定义类型来说,我们都希望可以这样实现"自定义类型 d2=d1",每个自定义类型都在类里面定义一个函数,外部调用会显得特别冗余,且外部调用形式是 Date d2; d2.assignment(d1); 先创建对象再进行赋值,而我们的期望是创建对象和赋值一体化,这两者似乎有所区别。

拷贝构造函数

 
/*12.使用拷贝构造函数拷贝构造一个新变量---自定义类型*/
#include <iostream>
using namespace std;
class Date {
    public:
        Date() {
        }
        Date(int year, int month, int day) {
            _year = year;
            _month = month;
            _day = day;
        }
        void Show() {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
        ~Date() {
            cout << "~Date()" << endl;
        }
        Date(const Date& d) {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
    private:
        int _year;
        int _month;
        int _day;
 };

void Test() {
    Date d1(2024, 1, 29);
    //d2=d1?
    Date d2(d1);
    d2.Show();
 }
int main() {
    Test();
    return 0;
 }

C++引入了一个新的概念,拷贝构造函数,只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

拷贝构造函数的特性

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。

  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

探究拷贝构造函数的规则---编译器默认生成拷贝构造函数

 
/*13.默认生成的拷贝构造函数*/
#include <iostream>
using namespace std;
class Date {
    public:
        Date() {
        }
        Date(int year, int month, int day) {
            _year = year;
            _month = month;
            _day = day;
        }
        void Show() {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
        ~Date() {
            cout << "~Date()" << endl;
        }

    private:
        int _year;
        int _month;
        int _day;
 };

void Test() {
    Date d1(2024, 1, 29);
    //d2=d1?
    Date d2(d1);
    d2.Show();
 }
int main() {
    Test();
    return 0;
 }

即使我们没有显示编写拷贝构造函数,我们依旧可以用Date d2(d1);拷贝构造d2对象。因为编译器会自动生成对应的拷贝构造函数。

探究拷贝构造函数的规则---编译器默认生成拷贝构造函数的缺陷(浅拷贝)

 
/*14.默认生成的拷贝构造函数---本质*/
#include <iostream>
using namespace std;
class f1 {
    public:
        f1(int x) {
            p = (int*)malloc(sizeof(int));
            *p = x;
        }
        void Show() {
            cout << *p << endl;
            cout << p << endl;
        }
        ~f1() {
            if (p) {
                free(p);
            }
        }
    private:
        int* p;
 };

void Test() {
    f1 x1(1);
    f1 x2(x1);
    x1.Show();
    x2.Show();

 }
int main() {
    Test();
    return 0;
 }

运行上面的代码我们会发现程序崩掉了。如果在28行打一个断点,进入调试模式,我们发现一直运行到28行处程序都没有发生问题。此时我们得到,

我们发现x1的地址和x2的地址是一样的。很容易知道接下来出Test函数后,x1和x2对象都需要调用析构函数,此时同一个地址会释放两次空间,程序就崩了。

编译器默认生成的拷贝构造函数按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。可以理解为浅拷贝是将自定义类型类内成员变量进行简单的等号赋值。即x2.p=x1.p;。此时指针就会指向同一段空间。而我们希望的是指针会新开辟一个空间,只是指针的值是相同的,空间不是相同的。

 
/*15.默认生成的拷贝构造函数---本质*/
#include <iostream>
using namespace std;
class f1 {
    public:
        f1(int x) {
            p = (int*)malloc(sizeof(int));
            *p = x;
        }
        void Show() {
            cout << p << endl;
            cout << *p << endl;
        }
        ~f1() {
            if (p) {
                free(p);
            }
        }
        f1(const f1& x) {
            p = (int*)malloc(sizeof(int));
            *p = *x.p;
        }
    private:
        int* p;
 };

void Test() {
    f1 x1(1);
    f1 x2(x1);
    x1.Show();
    x2.Show();
 }
int main() {
    Test();
    return 0;
 }

此时我们显示定义拷贝构造函数,新开辟空间,只是让指针上的值相等。这样就可以达到我们想要的效果,指针指向的空间不同,而指针上的值是相等的。

探究拷贝构造函数的规则---拷贝构造函数的使用场景(编译器优化、返回对象生命周期延长)

拷贝构造函数是C++中的一个特殊函数,用于创建一个新对象并将其初始化为另一个对象的副本。它通常在以下情况下被调用:

  1. 对象的初始化:当你创建一个新对象并将其初始化为另一个对象的副本时,拷贝构造函数被调用。

  2. 函数参数传递:当你将一个对象作为参数传递给一个函数时,拷贝构造函数可以被用来创建传递给函数的副本。

  3. 函数返回值:当函数返回一个对象的副本时,拷贝构造函数被用来创建返回的副本。(编译器优化可能省略)

 
/*16.拷贝构造函数---的使用场景*/
#include <iostream>
using namespace std;
class Date {
    public:
        Date(int year, int month, int day) {
            cout << "Date(int year,int month,int day):" << this << endl;
        }
        Date(const Date&d) {
            cout << "Date(const Date&d);" << this << endl;
        }
        ~Date() {
            cout << "~Date():" << this << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
 };
Date Test(Date d) {
    Date temp(d);

    return temp;
 }
int main() {
    Date d1(2024, 1, 29);
    Date  d2 =Test(d1);
    return 0;
 }

Date d1(2024, 1, 29);我们调用构造函数创建对象d1。Test(d1)此时调用Test函数,将d1传给形成d这一过程,调用拷贝构造函数,用已经存在的d1创建并初始化d对象。(函数参数传递:当你将一个对象作为参数传递给一个函数时,拷贝构造函数可以被用来创建传递给函数的副本。)在Test函数中,显示调用拷贝构造函数,用已经存在的d创建并初始化temp对象。(对象的初始化:当你创建一个新对象并将其初始化为另一个对象的副本时,拷贝构造函数被调用。在Test函数作用域结束,调用对象d的析构函数。在主函数作用域结束,调用temp的析构函数接着调用d2的析构函数。

你可能会产生疑问,在Test函数调用结束,temp作为函数返回值,此时应该还会调用拷贝构造函数。(函数返回值:当函数返回一个对象的副本时,拷贝构造函数被用来创建返回的副本。)为何运行代码并没有显示拷贝构造函数被调用。

实际上temp作为返回值,此时还会调用拷贝构造函数,但是经过编译器优化过,这一步的拷贝构造函数被省略掉了。尽管你在 Test 函数中创建了一个 temp 对象并返回它,但在实际的编译过程中,编译器会进行优化,避免调用拷贝构造函数,直接将 temp 对象的值传递给 d2。这是一种性能优化,可以减少不必要的对象拷贝操作,提高程序效率。

我对代码进行调试,注意看我的光标的位置,我的光标处于主函数中,但是析构函数只调用了一次,显然这个析构函数是对Test中d对象进行析构,而temp和d2对象并没有发生析构。显然temp在Test函数中作为返回值,此时temp对象的生命周期得到延伸,与d2同步,但最后编译器会依照前后顺序先调用temp的析构函数再调用d2的析构函数。虽然拷贝构造函数被省略了,但是 temp 对象的生命周期和 d2 对象的初始化完全一致,因此 temp 对象的析构函数会在 d2 对象的析构函数之前被调用,从而保证资源的正确释放。

运算符重载

运算符重载(Operator Overloading)是C++的一个重要特性,允许你为用户自定义的数据类型定义和重新定义运算符的行为。通过运算符重载,你可以使用C++内置运算符来执行自定义数据类型的操作,使代码更具可读性和表现力。

内置类型判断相等很简单,例如a,b变量都是int类型,判断相等只需要a==b?即可。但是如果我们想要定义自定义类型进行判断是否相等。我们希望也可以像“a==b”这样使用,对于ab都属于自定义类型,此时编译器显然不能像内置类型那样操作。于是我们引入运算符重载的概念,使得自定义类型也可以像内置类型那样进行操作。

 
/*17.运算符重载*/
#include <iostream>
using namespace std;
class Date {
public:
Date(int year,int month,int day){
    _year=year;
    _month=month;
    _day=day;
 }
bool operator==(const Date& d2){
    return _year==d2._year
    && _month==d2._month
    && _day==d2._day;
 }
private:
    int _year;
    int _month;
    int _day;
 };


int main() {
    Date d1(2024,1,29);
    Date d2(d1);
    cout<<(d1==d2)<<endl;
    return 0;
 }

当我们使用d1==d2这段代码的时候,实际上会转化为d1.operator==(d2) 此时调用d1对象的operator==函数,并传入d2对象作为参数。此时d1的地址会作为this指针,operator==函数会隐藏一个this指针,this指针指向的地址是d1的地址,_year等价于this->_year,_month等价于this->_month,_day等价于this->_day。

赋值运算符重载

如果我们想要以这种形式“a=b”对一个自定义类型进行赋值,那么我们就需要对“=”进行运算符重载。

赋值运算符重载格式:

参数类型:const T&,传递引用可以提高传参效率

返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值

返回*this :要符合连续赋值的含义

 
/*18.赋值运算符重载*/
#include <iostream>
using namespace std;
class Date {
    public:
        Date(int year, int month, int day) {
            _year = year;
            _month = month;
            _day = day;
        }
        Date& operator=(const Date&d) {
            if (this != &d) {
                _year = d._year;
                _month = d._month;
                _day = d._day;
            }
            return *this;
        }
        void Show() {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
 };


int main() {
    Date d1(2024, 1, 29);
    Date d2 = d1;
    d2.Show();
    return 0;
 }

实际上当我们没有显示定义赋值运算符重载的时候,编译器会生成一个默认的赋值运算符重载,以值的方式逐字节拷贝,即浅拷贝。我们知道浅拷贝当成员变量是指针的时候,p1=p2此时指针指向的地址会相同,而我们希望的是指针指向的地址不同,指针指向地址上的值相同,因为往往涉及到指针问题我们都需要显示定义赋值运算符重载,将浅拷贝变成深拷贝。

默认赋值运算符重载的缺陷

 
/*19.默认赋值运算符重载的缺陷*/
#include <iostream>
using namespace std;
class f1 {
    public:
        f1(int x) {
            p = (int*)malloc(sizeof(int));
            *p = x;
        }
        void Show() {
            cout << *p << endl;
            cout << p << endl;
        }
        ~f1() {
            if (p) {
                free(p);
            }
        }

    private:
        int* p;
 };

void Test() {
    f1 x1(1);
    f1 x2 = x1;
    x1.Show();
    x2.Show();
 }
int main() {
    Test();
    return 0;
 }

x1,x2对象中p成员指向的地址是相同的,当x1,x2对象出作用域时,会对同一块地址调用两次析构函数,此时程序就会崩掉。此时就需要显示定义赋值运算符重载,将浅拷贝变成深拷贝。

前置++和后置++的重载

 
/*20.前置++和后置++的重载(代码逻辑有缺陷)*/
#include <iostream>
using namespace std;
class Date {
    public:
        Date() {}
        Date(int year, int month, int day) {
            _year = year;
            _month = month;
            _day = day;
        }
        Date& operator++() {
            _day += 1;
            return *this;
        }
        Date operator++(int) {
            Date temp(*this);
            _day++;
            return temp;
        }
        void Show() {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
    private:
        int _year;
        int _month;
        int _day;
 };
void Test() {
    Date d;
    Date d1(2024, 1, 29);
    d = d1++;
    d.Show();
    d1.Show();
    d = ++d1;
    d.Show();
    d1.Show();
 }
int main() {
    Test();
    return 0;
 }

 
Date operator++(int) {
            Date temp(*this);
            _day++;
            return temp;
        }

这是后置++,为了区分前置++和后置++,规定后置++参数中写一个int,用来表示后置++。

前置++先对day进行+1操作,然后把处理过的自定义类型返回,此时返回引用,用引用可以提高效率,因为不用引用还需要进行一次拷贝构造操作,用引用就不需要进行拷贝构造操作。

后缀++返回的值是++前的值,因此我们先拷贝构造一个副本,对本体的day进行++操作,返回副本的值,注意此时不能够返回引用,因为副本出了作用域就消失了,所以只能不传引用。

d1++会被转化为d1.operator++(int),从而调用后置++的函数。++d1会被转化为d1.operator++(),从而调用前置++的函数。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

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

相关文章

springboot外出务工人员信息管理系统源码和论文

网络的广泛应用给生活带来了十分的便利。所以把疫情防控期间某村外出务工人员信息管理与现在网络相结合&#xff0c;利用java技术建设疫情防控期间某村外出务工人员信息管理系统&#xff0c;实现疫情防控期间某村外出务工人员信息的信息化。则对于进一步提高疫情防控期间某村外…

Aleo项目详细介绍-一个兼顾隐私和可编程性的隐私公链

Aleo上线在即&#xff0c;整理一篇项目的详细介绍&#xff0c;喜欢的收藏。有计划做aleo节点的可交流。 一、项目简介 Aleo 最初是在 2016 年构思的&#xff0c;旨在研究可编程零知识。公司由 Howard Wu、Michael Beller、Collin Chin 和 Raymond Chu 于 2019 年正式成立。 …

AST反混淆实战-jsjiamiv7最高配置

js加密混淆网站 https://www.jsjiami.com/一、混淆demo生成 01 打开目标网址 https://www.jsjiami.com/ 02 按照顺序加密混淆二、混淆前后demo 混淆前的源码 (function(w, d) { w.update "2023年01月17日05:34:29更新"; d.info "本站历时1年半研发的新版本V7…

【C语言/数据结构】排序(归并排序|计数排序|排序算法复杂度)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;《数据结构》https://blog.csdn.net/qinjh_/category_12536791.html?spm1001.2014.3001.5482 ​​​​ 目录 归并排序 代码实现&#xf…

gradle简单入门

安装 需要有Java环境 下载地址&#xff1a;https://gradle.org/releases/ 8.5版本仅有二进制文件&#xff1a;https://gradle.org/next-steps/?version8.5&formatbin 8.5版本包含文档和源码及二进制文件&#xff1a;https://gradle.org/next-steps/?version8.5&f…

C语言——如何进行文件操作

大家好&#xff0c;我是残念&#xff0c;希望在你看完之后&#xff0c;能对你有所帮助&#xff0c;有什么不足请指正&#xff01;共同学习交流 本文由&#xff1a;残念ing原创CSDN首发&#xff0c;如需要转载请通知 个人主页&#xff1a;残念ing-CSDN博客&#xff0c;欢迎各位→…

C语言探索:水仙花数的奥秘与计算

摘要&#xff1a; 水仙花数&#xff0c;一种特殊的三位数&#xff0c;其各位数字的立方和等于该数本身。本文将详细介绍水仙花数的定义、性质&#xff0c;以及如何使用C语言来寻找100至999范围内的水仙花数。 目录 一、水仙花数的定义与性质 二、用C语言寻找100至999范围内的…

AJAX的原理(重点)

◆ XMLHttpRequest 什么是XMLHttpRequest&#xff1f; 定义&#xff1a; 关系&#xff1a;axios 内部采用 XMLHttpRequest 与服务器交互 注意&#xff1a;直白点说就是axios内部就是封装了XMLHttpRequest这个对象来实现发送异步请求的 使用 XMLHttpRequest 步骤&#xff1a…

聊聊用户故事地图

这是鼎叔的第八十五篇原创文章。行业大牛和刚毕业的小白&#xff0c;都可以进来聊聊。 欢迎关注本专栏和微信公众号《敏捷测试转型》&#xff0c;星标收藏&#xff0c;大量原创思考文章陆续推出。本人新书《无测试组织-测试团队的敏捷转型》已出版&#xff08;机械工业出版社&…

npm安装下载修改镜像源

问题描述一 npm install 时&#xff0c;报错&#xff1a;npm ERR! network request to https://registry.npmjs.org/postcss-pxtorem failed, reason: connect ETIMEDOU&#xff0c;这是因为默认npm安装会请求国外的镜像源&#xff0c;导致下载缓慢容易断开请求下载失败的 np…

第九节HarmonyOS 常用基础组件18-checkBox

1、描述 提供多选框组件&#xff0c;通常用于某选项的打开或关闭。 2、接口 Checkbox(options:{name?: string, group?: string}) 3、参数 参数名 参数类型 必填 描述 name string 否 多选框名称 group string 否 多选框群组名称。&#xff08;未配合使用Chec…

Coppeliasim倒立摆demo

首先需要将使用Python远程控制的文件导入到文件夹&#xff0c;核心是深蓝色的三个文件。 本版本为4.70&#xff0c;其文件所在位置如下图所示&#xff0c;需要注意的是&#xff0c;目前不支持Ubuntu22的远程api&#xff1a; 双击Sphere这一行的灰色文件&#xff0c;可以看到远程…

【C++版】排序算法详解

目录 直接插入排序 希尔排序 选择排序 冒泡排序 堆排序 快速排序 hoare法 挖坑法 前后指针法 非递归版本 快速排序中的优化 归并排序 递归版本 非递归版本 计数排序 总结 直接插入排序 直接插入排序的思想是&#xff1a;把待排序的记录按其关键码值的大小逐个插入…

ICMP——网际控制报文协议

目录 1.1 网际控制报文协议 ICMP 1.2 ICMP 报文的格式 1.2.1 ICMP 报文的种类 ICMP 差错报告报文 ICMP 询问报文 1.3 ICMP 的应用 1.4 ICMP抓包 1.4.1 ICMP请求包&#xff08;request&#xff09; 1.4.2 ICMP应答包&#xff08;reply&#xff09; 1.1 网际控制报文协议…

解决maven 在IDEA 下载依赖包速度慢的问题

1.idea界面双击shift键 2.打开setting.xml文件 复制粘贴 <?xml version"1.0" encoding"UTF-8"?> <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:sc…

Spring AOP实现

Spring AOP实现 AOP概述什么是AOP什么是Spring AOP Spring AOP快速入门引入依赖实现计时器 Spring AOP详解Spring AOP核心概念切点(Pointcut)连接点(Join Point)通知(Advice)切面(Aspect) 通知类型注意事项 PointCut多个切面切面优先级 Order切点表达式execution表达式annotati…

【开源】基于JAVA的就医保险管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 科室档案模块2.2 医生档案模块2.3 预约挂号模块2.4 我的挂号模块 三、系统展示四、核心代码4.1 用户查询全部医生4.2 新增医生4.3 查询科室4.4 新增号源4.5 预约号源 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVue…

双链表的基本知识以及增删查改的实现

满怀热忱&#xff0c;前往梦的彼岸 前言 之前我们对单链表进行了非常细致的剖析&#xff0c;现在我们所面临的则是与之相对应的双链表&#xff0c;我会先告诉诸位它的基本知识&#xff0c;再接着把它的增删查改讲一下&#xff0c;ok&#xff0c;正文开始。 一.链表的种类 我…

07.领域驱动设计:掌握整洁架构、六边形架构以及3种常见微服务架构模型的对比和分析

目录 1、概述 2、整洁架构 3、六边形架构 4、三种微服务架构模型的对比和分析 5、从三种架构模型看中台和微服务设计 5.1 中台建设要聚焦领域模型 5.2 微服务要有合理的架构分层 5.2.1 项目级微服务 5.2.2 企业级中台微服务 5.3 应用和资源的解耦与适配 6、总结 1、概…

三步万能公式解决软件各种打不开异常

程序员都知道,辛苦做的软件发给客户打不开那是一个大写的尴尬,尴尬归尴尬还是要想办法解决问题. 第一步清理环境. 目标机台有环境和没有运行环境的,统统把vs环境卸载了,让目标机台缺少环境.第二步打包环境 源代码添加打包工程,setup,重新编译.![添加setup ](https://img-blo…