【C++】类和对象(中期)

 

引言:在C++的类中如果我们在这个类中什么函数都不写,那这个类就被成为空类,但是编译器会在这个空类中生成有个默认成员函数。接下来就让我们介绍一下六个成员函数。

1.六大默认函数功能 

(这个图借鉴的是三分苦兄弟的) 

 先要理解好构造函数才能更好进一步理解六大函数。

2.构造函数

2.1构造函数出现背景:

在1中,说明了构造函数是完成初始化工作而不是来构造一个新函数(我理解的是编辑器在这个空类中新构造的函数) 。下面我们一起来看下刚由高中考上大学的李明同学写的代码。(因为李明同学英语作文较好,可能编程基础较差。)

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

class Date  //创建一个时间类
{
public:
	void Init(int year, int month, int day)  //初始化函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	//d1.Init(2022, 9, 27);
	
	d1.Print();
	return 0;
}

 未初始化的那个打印的是随机值,那如果李明同学忘记初始化了,作为高级编程语言,C++能不能人性化一点默认帮助这个一直参加高考的同学初始化一下呢。

构造函数出场了,构造函数本名初始化函数,奈何不够霸气改现名。构造函数名字 与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。李明同学写的上面这个代码课没有构造函数。

2.2构造函数的基本特性:

  • 1.函数名与类名一致。
  • 2.无返回值(void类型也不行)。
  • 3.对象实例化时编译器自动调用对应的构造函数。
  • 4. 构造函数可以重载.
  • 5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
  • 6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。(无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。)

就比如这个代码,一个无参函数,一个全缺省函数他就运行不了(此图是针对第六条特性 )

2.3思考与讨论:

李明同学又说编译器能生成构造函数而且还是免费的,为啥还要自己写?

1.兄弟们,自己写的东西自己想写啥写啥,想让程序打印什么结果都可以,那编辑器自动生成的是固定的,没有什么技术含量。

2.C++中把类型有分为内置类型(int,char等)和自定义类型(结构体,类),编辑器只能对自定义类型进行初始化,不能初始化内置类型。(内置类型这么多谁看不迷糊。)

由于这个漏洞,C++编写委员会变打了一个补丁——内置类型的成员变量在类中声明时可以给一个默认值。

2.4无参构造函数和全缺省构造函数区别

李明用无参构造函数实现一个代码,为啥打印结果还是随机值呢?

 兄弟们,刚才我们说编辑器不能对内置类型进行初始化,所以无参构造函数不能用在有内置类型的成员时用无参构造函数。当有内置成员类型和需要显示传参初始化时用全缺省构造函数。

 全缺省构造函数最能误解了,全缺省是全缺省值构造函数,也就是不需要传入数值就可以了,而不是没有一个参数。

默认构造函数:

  • 默认构造函数是指在没有显式定义构造函数的情况下,编译器会自动生成的构造函数。默认构造函数有以下两种
  1. 无参构造函数:如果一个类没有定义任何构造函数,那么编译器会自动生成一个无参构造函数。这个构造函数没有参数,也没有函数体,它只是对类的成员变量进行默认初始化。

  2. 合成默认构造函数:如果一个类定义了其他构造函数,但没有定义无参构造函数,那么编译器会自动生成一个合成默认构造函数。这个构造函数也没有参数,也没有函数体,它只是对类的成员变量进行默认初始化。

  • 需要注意的是,如果一个类定义了其他构造函数,但没有定义无参构造函数,并且我们需要使用无参构造函数来创建对象,那么就会出现编译错误。这时候我们需要显式地定义一个无参构造函数。
  • 另外,需要注意的是,如果一个类定义了有参构造函数,但没有定义无参构造函数,并且我们使用默认构造函数来创建对象,那么编译器会报错,因为默认构造函数已经被我们的有参构造函数覆盖了。此时我们需要显式地定义一个无参构造函数。

 显示写的无参构造函数算不算默认构造函数?

显式写的无参构造函数不算默认构造函数,因为默认构造函数是编译器自动生成的构造函数,而显式写的无参构造函数是程序员自己定义的构造函数。

默认构造函数有两种情况,一种是在类中没有定义任何构造函数时,编译器会自动生成一个无参构造函数另一种是在类中定义了其他构造函数,但没有定义无参构造函数时,编译器会自动生成一个合成默认构造函数。

何时需要显示写构造函数 

如果一个类没有显式地定义构造函数,编译器会自动生成一个默认构造函数,用于对象的初始化。但是,在以下情况下,我们需要自己写构造函数:

  • 对象的初始化需要进行额外的操作:如果我们需要在对象的构造过程中进行一些额外的操作,例如为对象的成员变量赋初值、打开文件或网络连接等操作,那么我们需要自己写构造函数来完成这些操作。

  • 对象的成员变量需要初始化为非默认值:如果对象的成员变量需要初始化为非默认值,那么我们需要自己写构造函数来完成这个任务。例如,如果对象的成员变量是指针类型,那么我们需要在构造函数中为它们分配内存空间。

  • 类的继承关系需要初始化:如果类之间存在继承关系,那么在创建派生类对象时,需要先初始化基类对象。这时候需要自己写构造函数来完成这个任务。

  • 禁止使用默认构造函数:如果我们不希望对象使用默认构造函数进行初始化,那么我们需要自己写构造函数来强制要求对象的初始化必须使用我们定义的构造函数。

需要注意的是,如果一个类没有显式地定义构造函数,并且不需要进行上述操作,那么我们可以直接使用默认构造函数来初始化对象,而不需要自己写构造函数。

3.析构函数

析构函数的作用是主要完成一些清理作用,就比如在C中,我们用完栈,队列会开辟一些空间来存储数据,但是我们要手写函数来主动释放这些空间,但有时李明会忘了,所以就有了自动帮我们清理空间的析构函数。

析构函数就是在对象用完销毁时调用它,完成一些类的清理工作。

3.1析构函数的特性

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
  5. 与构造函数类似,默认生成的析构函数对内置类型不做处理,自定义类型成员会去调用他的析构函数。
  6. 析构函数并不是一定要手写的,如果在我们写的类中没有开辟空间,出了函数栈桢被销毁后其所占的空间就被强行收回了,这个我们用系统默认的析构函数就行了,如果我们用malloc等在类中申请了空间那么我们就要手写析构函数进行资源清理。

  7. 析构函数对对象进行清理时与对象的存储区域有关。相同存储区域下先构造后析构,全局变量优先构造,最后析构,局部静态变量运行时初始化,等栈中的对象析构完成后才会去静态区进行析构。

接下来让李明同学写一个析构函数具体分析一下 :

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

class Date  //创建一个时间类
{
public:
	
	void Init(int year, int month, int day)  //初始化函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	~Date()
	{
		cout <<"~Date()"  << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	//d1.Init(2022, 9, 27);
	//d1.Print();
	return 0;
}

这就是打印结果。结果说明了系统会自动调用析构函数。

刚开始由于我们没给他初始化,李明新建的实例化对象会用全缺省函数进行初始化,然后出了它的作用域后就会调用析构函数来进行资源清理,所以会出现以上结果。

资源清理并不是每一类代码都有资格的,像一些出了函数栈桢就销毁的就不用,真正的是那些需要malloc,realloc开辟出空间的一些。 

就比如malloc new这样在堆上动态开辟的空间需要自己手动写析构函数去释放掉,像日期类这样的,当创建变量时,栈区已经给他开辟好空间了,使用完销毁了,根本不需要我们手动写,甚至都不需要默认析构函数。

malloc是C语言中的内存分配函数,它只负责分配内存空间,不会调用对象的构造函数和析构函数。因此,如果我们使用malloc分配内存空间来存储对象,那么对象不会被自动初始化和销毁,需要我们手动进行初始化和销毁。

但是,在以下情况下,程序员需要自己编写析构函数:

  • 类中有指针成员变量:如果一个类中有指针成员变量,那么在对象被销毁时,必须手动释放这些指针所指向的内存,否则会导致内存泄漏。在这种情况下,程序员需要自己编写析构函数,释放这些指针所指向的内存。

  • 类中有文件或资源句柄:如果一个类中有文件或资源句柄,那么在对象被销毁时,必须手动关闭这些文件或资源句柄,否则会导致资源泄漏。在这种情况下,程序员需要自己编写析构函数,关闭这些文件或资源句柄。

  • 类需要进行资源管理:如果一个类需要进行资源管理,例如自己实现了动态内存分配的操作,那么就需要自己编写析构函数,释放这些动态分配的内存。

总之,当类中需要进行特殊的资源管理时,程序员需要自己编写析构函数。否则,可以不需要自己写析构函数,使用编译器自动生成的默认析构函数即可。

注意:

在C++中,使用malloc函数开辟空间时,它所分配的内存并不是通过new运算符分配的,因此在对象被销毁时,编译器并不会自动调用析构函数去释放这些内存。

默认析构函数只能释放由new运算符分配的内存,而不能释放通过malloc函数分配的内存,因为它们使用的是不同的内存管理机制。如果使用delete运算符释放malloc函数分配的内存,则会导致未定义行为。

因此,在使用malloc函数分配内存时,需要手动编写析构函数来释放这些内存。在析构函数中,需要使用free函数来释放malloc分配的内存,例如:

class MyClass {
public:
    MyClass() {
        // 使用malloc函数分配内存
        ptr = (int*)malloc(sizeof(int) * 10);
    }
    ~MyClass() {
        // 在析构函数中释放malloc分配的内存
        free(ptr);
    }
private:
    int* ptr;
};

在上面的示例代码中,使用malloc函数在构造函数中分配了一块内存,并在析构函数中使用free函数释放了这块内存。这样可以确保在对象被销毁时,这块内存也能被正确地释放。

总之,使用malloc函数分配内存时,需要手动编写析构函数来释放这些内存,在析构函数中需要使用free函数来释放malloc分配的内存。

自定义类型和内置类型 

在C++中,自定义类型是指由程序员定义的类型,它可以包含内置类型、其他自定义类型和自定义类型的组合。自定义类型可以由结构体、类、枚举等构成。

内置类型是指C++语言本身提供的基本数据类型,包括整型、浮点型、字符型、布尔型等。内置类型是C++语言的基础,可以直接使用,无需定义。

C++的内置类型包括以下几种:

  1. 布尔类型(bool):表示真或假。

  2. 字符类型(char):表示单个字符。

  3. 整型类型(int、short、long、long long):表示整数。

  4. 浮点类型(float、double、long double):表示实数。

  5. 空类型(void):表示无类型。

  6. 指针类型:表示一个内存地址。

总之,自定义类型是由程序员定义的类型,它可以包含内置类型、其他自定义类型和自定义类型的组合。内置类型是C++语言本身提供的基本数据类型,包括布尔类型、字符类型、整型类型、浮点类型、空类型和指针类型。

写不写析构的两种情况

1. 不写

当遇到像时间类这样,由系统自动分配和释放的,不需要程序员手动管理的类时不需要写析构函数,哪怕是默认的都用不到,但是他可以调用。

2.显示写/默认调用

  • 调用默认的析构函数:当类中需要对自定义类型进行资源的释放时,就不需要自己手写析构函数,用默认的析构函数就行了。
  • 显示写析构函数:当用到new在堆上开辟空间,以及类中有内置类型(在类中private定义的一些成员变量)需要释放时就必须显示写析构函数。

4.拷贝构造函数

4.1什么拷贝构造函数

先分析名字,拷贝·构造函数(d2),就是对构造函数(d1)进行拷贝,也就是说我们用d1去初始化d2,那d2就和d1一模一样了。

4.2拷贝构造函数特性

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

2.拷贝构造函数的参数只有一个且必须使用引用传参而不能用传值传参(不多要形参了),一般还要用const修饰。接下来让李明同学写个代码

class Date
{
public:
	Date(const Date& d) //d是实例d1的别名就好理解了
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	return 0;
}

那个d可以看成是类d1的别名这样是不是更好理解了。

哥几个都知道传引用传参吧,那我就不在多介绍d了。接下来说一下为啥不用传值传参。

因为传值传参其实形参是实参的拷贝,是单向的,你要用他就要拷贝实参进入第二层然后进入下一层······。而传引用是双向的不需要拷贝,是通过地址改变的所以不会陷入死循环中。

一般拷贝构造函数用const进行修饰,因为d是d1的别名,我们改变d时也会改变d1,但是d是拷贝的,我们不希望改变d1,所以一般加上const来固定。

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

class Date
{
public:
	//构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造函数
		/*
		Date(const Date& d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		*/
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022, 10, 14);
	Date d2(d1);
	//Func(d1);
	d1.Print();
	d2.Print();
}

为啥没写拷贝构造也会自动完成拷贝构造函数呢?上面我们说了,拷贝构造函数能够对内置类型进行处理也就是说可以把d1的内置类型按照字节拷贝给d2。所以对于日期这些内置类型我们可以不写构造函数。但是栈类就必须要我们手写构造函数了。

在C++中,默认的拷贝构造函数是进行浅拷贝的,对于简单的自定义类型和内置类型,使用默认的拷贝构造函数进行浅拷贝是可以的。但是,如果自定义类型涉及到了指针类型的成员变量,就需要注意浅拷贝的问题。

何时需要显示写拷贝构造函数:

当一个类中含有指针变量或动态分配内存的成员变量时,通常需要手动编写拷贝构造函数。

如果不手动编写拷贝构造函数,系统会默认生成一个浅拷贝的拷贝构造函数。这样,在进行对象复制时,只是复制了指针或引用,而没有复制对象的实际内容,容易导致内存泄漏或者多个对象指向同一块内存的问题。

手动编写拷贝构造函数可以实现深拷贝即将指针所指向的内容也进行复制,而不是仅仅复制指针本身。这样,在对象复制时,新对象和原对象不会共享同一块内存,从而避免了出现上述问题。

4.3手写拷贝构造函数

4.31栈类拷贝构造

typedef int DateType;
class Stack
{
public:
	Stack(int newcapacity = 4)//构造函数
	{
		a = (DateType*)malloc(sizeof(DateType) * newcapacity);
		size = 0;
		capacity = newcapacity;
	}
	Stack(const Stack& st)
	{
		a = st.a;
		size = st.size;
		capacity = st.capacity;
	}
	~Stack()//析构函数
	{
		if (a)
		{
			free(a);
			size = capacity = 0;
			a = nullptr;
		}
	}
private:
	DateType* a = 0;
	int size = 0;
	int capacity = 0;
};
int main()
{
	Stack st1(10);
	Stack st2(st1);
	return 0;

}

 这个直接报错了。

 看监视发现这两者数组的指针都指向同一片空间了,为啥呢?因为拷贝时是按字节拷贝方式进行的,所以也会将st1的地址拷贝给st2,所以指向同一片空间。在函数调用完成后,对象销毁时会自动去调用析构函数释放空间,因此s1,s2中的a指向的同一块空间将会释放两次,此时编译器将会报错。

 4.32自定义类型拷贝

那如果我们写一个myqueue函数但是也不写拷贝构造函数会咋样?

class Stack
{
public:
	//构造函数
	Stack(int capacity = 10)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		assert(_a);
		_top = 0;
		_capacity = capacity;
	}
	//不写拷贝构造,编译器调用默认拷贝构造
		
		Stack(const Stack& st)
		{
			_a = st._a;
			_top = st._top;
			_capacity = st._capacity;
		}
		
		//析构函数
	
private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue
{
private:
	int _size = 0;
	Stack _S1;
	Stack _s2;
};

int main()
{
	MyQueue Q1;
	MyQueue Q2(Q1);
}

两个地址又是一样的,所以又发生了浅拷贝。 

总结一点:如果类对象需要开辟空间,则需要自己实现深拷贝来构建拷贝构造函数。拷贝构造函数的参数必须为类类型的引用。

深浅拷贝的一些区别:

浅拷贝只是简单地将一个对象的所有成员变量拷贝到另一个对象中,包括指针类型的成员变量。如果两个对象的指针类型的成员变量指向同一个内存地址,那么它们之间的关系就是浅拷贝。如果这样的两个对象被销毁,就会导致重复释放同一个内存地址,从而引起内存泄漏或者程序崩溃等问题。

因此,在涉及到指针类型的成员变量时,使用默认的拷贝构造函数进行浅拷贝是不够的,需要自定义拷贝构造函数来实现深拷贝。深拷贝会在拷贝对象时,将指针指向的内存复制一份,这样就避免了重复释放同一内存地址的问题。

下面是一个自定义类型中涉及指针类型的成员变量的例子:

class MyClass {
public:
    int* p;
    MyClass(const MyClass& other) {
        // 进行深拷贝
        p = new int;
        *p = *(other.p);
    }
    ~MyClass() {
        // 释放动态分配的内存
        delete p;
    }
};

在上面的示例代码中,我们自定义了拷贝构造函数,用于实现深拷贝。在拷贝构造函数中,我们对指针类型的成员变量进行了深拷贝,即动态分配了一块新的内存空

 5.赋值运算符重载:

5.1运算符重载

  • 对于内置类型,编译器可以用运算符直接进行运算
  • 对于自定义类型,编译器不知道如何计算才能得到你想要的结果,所以要自己手动完成一个重载函数,这个函数是关于运算符的,所以叫运算符重载

C++运算符重载函数的作用是在特定的运算符被使用时进行重载,从而实现对自定义类型的支持。运算符重载函数的名称为"operator"加上被重载的运算符,例如"operator+"表示重载加号运算符。运算符重载函数可以像普通的成员函数一样被调用,但其参数和返回值有一定的限制。

通过运算符重载函数,我们可以实现以下功能:

  1. 支持自定义类型的加、减、乘、除等运算符,使得自定义类型可以像基本类型一样进行运算。

  2. 支持自定义类型的比较运算符,例如等于、大于、小于等,使得自定义类型可以进行比较操作。

  3. 支持自定义类型的输入输出运算符,例如"<<""和">>",使得自定义类型可以通过输入输出流进行输入输出操作。

 C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值的类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

运算符重载主要是给自定义类型使用的。(内置类型直接用操作符计算)

C++中有一些内置类型的运算符,例如加法运算符、减法运算符、乘法运算符等等,这些运算符可以直接用于内置类型的变量上,例如整型、浮点型、字符型等等。但对于自定义类型,这些运算符是无法直接使用的,需要通过运算符重载来重新定义它们的含义。

例如,我们可以为自定义类型创建一个运算符重载函数,使其支持加法运算符。具体示例如下:

class MyNumber {
public:
    int value;
    MyNumber operator+(const MyNumber& other) const {
        MyNumber result;
        result.value = value + other.value;
        return result;
    }
};

在上面的示例代码中,我们为自定义类型MyNumber重载了加法运算符。重载函数的返回类型是MyNumber,表示加法运算的结果也是一个MyNumber类型的对象。在重载函数中,我们对成员变量进行了加法运算,并将结果保存在一个新的MyNumber对象中,最后将这个对象返回。

有了运算符重载,我们就可以像使用内置类型一样使用自定义类型了。例如,我们可以创建两个MyNumber类型的对象,然后使用加法运算符将它们相加:

MyNumber a, b, c;
a.value = 10;
b.value = 20;
c = a + b;

总之,运算符重载是一种特殊的函数重载,它允许程序员重新定义运算符的含义,使其能够用于自定义类型。因此,运算符重载主要是给自定义类型使用的。

5.11相应源码及实例

比如在日期类中实现d1==d2


bool operator==(const Date& d1, const Date& d2) //避免传值传参调用拷贝构造
{
	return d1._year == d2._year &&
		d1._month == d2._month &&
		d1._day == d2._day;
}

这是相等操作符,

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include<assert.h>
#include<stdlib.h>

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};
//全局相等运算符的重载,内置私有成员无法访问
bool operator ==(const Date& d1,const Date& d2)
	{
		return d1._year == d2._year
			&& d1._month == d2._month
			&& d1._day == d2._day;
	}
int main()
{
	Date d1(2022, 7, 25);
	Date d2(2022, 7, 26);
	cout << (d1 == d2) << endl;//直接使用相等判断即可
	return 0;
}

 就比如我们写的这个,

 为啥会报错?

因为成员变量是私有的,出了类我们访问不了,除非变成公有,那样没啥意义了,或者能不能写到类中呢?

 他报的是参数太多,啥意思,就是有三个参数了,虽然我们只写了两个但是成员函数中还有this指针呢,这不就是三个参数了吗。

运行一下结果试一试。 

5.12特点 

  •  1.不能通过连接其他符号来创建新的操作符:比如operator@。
  • 2.重载操作符必须有一个类类型(对自定义类型成员才可运算符重载)或者枚举类型的操作数。
  • 3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 4.作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参。

5.2赋值运算符重载

5.21赋值运算符重载的特性 

  • (1)赋值运算符的重载也是类中默认会生成的一个函数,因此赋值运算符只能重载成类的成员函数,不能重载成全局函数。

        为什么不能写成全局呢?难道仅仅是因为私有成员无法访问到吗?并不是,原因在于如果类中没有显示提供赋值运算符重载,类会自动生成一个,当我们在全局实现时,与类中自动生成的赋值运算符重载冲突,无法调用,所以赋值运算符只能是类的成员函数。

  • (2)赋值运算符重载的格式:

参数类型:引用传参,const Date& d ,使用引用传参可以提高效率,使用const修饰可以避免书写错误对原有数据进行修改。

返回值类型:引用返回,Date& ,提高效率,有返回值可以支持连续赋值。

进行检测:检测是否自己给自己赋值

返回this*:符合连续赋值

前面我们学了拷贝构造函数,但是我不想用它,我们就能换一种思考方式:赋值运算符重载。

int main()
{
    Date d1(2022, 10, 4);
    Date d2(2022, 10,4);
    Date d3(d1);//拷贝构造 -----一个存在的对象去初始化另一个要创建的对象
    d2 = d1; //赋值重载---------- 两个已经存在的对象之间赋值
}

就是直接用d1给d2赋值。

Date operator=(const Date& d)
	{
	
			_year = d._year;
			_month = d._month;
			_day = d._day;
		return *this;
	}

 这样就写完了但这就完了吗?

我们使用的是传值返回所以再返回时会调用拷贝构造函数,如果出了作用于就不行了,所以一步到位直接传引用(参考上面的死循环拷贝构造)。还有就是自己赋值给自己这样就麻烦了。

	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
	   }
			
		return *this;
	}

 注意:operator赋值也是默认成员函数,我们不写赋值重载,编译器也会默认生成,不过编译器完成的依旧是值拷贝或浅拷贝。日期类这种简单的编译器默认的函数就能实现了但是像栈这些就必须要我们手动去写。

5.22拷贝构造和赋值区别 

int main()
{
	Date d1(2022, 10, 4);
	Date d2(2022, 10, 7);
	//理解一下到底哪个才算赋值
	Date d3 = d1; //等价于 Date d3(d1); 是拷贝构造
	d2 = d1; //两个已经存在的对象才是赋值
}

注意:拿一个对象去初始化另一个对象是拷贝构造(d3=d1)。

6.const成员

6.1const修饰成变量

  将const修饰的成员函数称为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明该成员函数中不能对类的任何成员进行修改。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	void Print()
	{
		cout << _year << "年" << _month<<"月" << _day <<"天" << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main()
{
	Date d1(2022, 10, 4);
	 const Date d2(2022, 10, 7);
	 d1.Print();
	 d2.Print();

}

为啥d1就行而加了const的d2为啥不行,这就涉及到访问权限的问题了。 

 

 

所以我们就在Date*前面再加一个·const(const Date* const this)就可以是权限平移了。

但是this指针是隐藏的,我们就不能直接在他前面加上const,那咋办?

     void Print()const
    {
        cout << _year << "/" << _month<<"/" << _day  << endl;
    }

既然前面不让用,那就用后面。

那成员函数中不修改成员变量的成员函数,都加上const,此时普通对象和const对象都可以调用。 

它的权限最小,随便调用。

7、取地址及const取地址操作符重载

class Date
{
public:
    //取地址&重载
	Date* operator&()
	{
		return this;
	}
    //const取地址&重载
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

 1.取地址操作符无非是对自己实现的类进行取地址操作,const修饰的对象取地址取出的应该是const 类型的地址。

2.我们不写,编译器也会默认生成一个重载函数,当我们不想让其他人看到隐私代码,那就要我们自己写取地址操作符重载了。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值