当前位置: 首页 > news >正文

【C++之类和对象篇002】

C++学习笔记---005

  • C++知识类和对象篇
    • 1、类的6个默认成员函数
    • 2、构造函数
      • 2.1、构造函数的特性
      • 2.2、内置类型和自定义类型
      • 2.3、什么是默认构造函数?
    • 3、析构函数
      • 3.1、什么是析构函数?
      • 3.2、析构函数的特性
      • 3.3、析构函数的释放顺序
    • 4、拷贝构造函数
      • 4.1、什么是拷贝构造函数?
      • 4.2、拷贝构造函数的特性
      • 4.3、拷贝构造函数典型调用场景
      • 4.4、理解传值传参和传引用传参
      • 4.5、const正确的适用
      • 4.6、拷贝构造的默认处理
      • 4.7、拷贝构造浅拷贝的无脑拷贝问题
    • 5、操作符重载,关键字operator
      • 5.1、关键字operator
      • 5.2、私有限定符情况
      • 5.3、了解‘.*’操作符
    • 6、赋值运算符重载
      • 6.1、 赋值运算符重载
      • 6.2、 拷贝构造函数与赋值运算符重载的区别
    • 7、类的6个默认成员函数总结

C++知识类和对象篇

前言:
前面篇章学习了C++对于C语言的语法优化,接下来继续学习,C++的类和对象中类的6个默认成员函数的知识。
/知识点汇总/

1、类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

class A{ };

1.初始化和清理
初始化:构造函数主要完成初始化工作;
清理:析构函数主要完成释放/清理工作。
2.拷贝和复制
拷贝:拷贝函数使用同类对象初始化创建对象;
赋值重载:主要把一个对象赋值给另一个对象。
3.取地址和重载(相对于前4个做个了解)
主要是普通对象和const对象取地址,这两个很少会自己实现(由编译器实现)。

2、构造函数

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

2.1、构造函数的特性

特性:
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

  1. 函数名与类名相同。
  2. 无返回值。(不是void,直接不用写)
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
#include <iostream>
using namespace std;class Date
{
public://构造函数,不写构造函数,编译器默认对于内置类型不执行操作,为随机值Date(){_year = 1;_month = 1;_day = 1;}//构造函数支持函数重载,支持缺省参数Date(int year, int month, int day){_year = year;_month = month;_day = day;}//构造函数,支持缺省参数,语法上可以与无参的构造函数重载,但是编译会有歧义,所以可以重载但有歧义。//编译报错显示:对重载函数调用不明确。//Date(int year = 1, int month = 2, int day = 3)//{//	_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.Print();//构造函数,特殊的函数,对象后面跟的参数,但是参数不能为空,因为需要区分无参函数。Date d2(2024,1,27);d2.Print();return 0;
}

2.2、内置类型和自定义类型

默认成员函数之一,构造函数,我们不写编译器就会生成一个,它并不会对内置类型执行什么操作(由编译器不同也有不同),会对自定义类型初始化0操作。
C++分为内置类型/基本类型,即语言自身定义的类型;int/char/doubl…还有自定义类型,即struct/class/enum…
默认生成的构造函数,对于内置类型不做处理,自定义类型会去调用他的默认构造。

#include <iostream>
using namespace std;
class A
{
public://构造函数,对自定义类型的默认处理A(){cout << "print_A()" << endl;_a = 0;cout << _a << endl;_a = 2;}
private:int _a;
};class Date
{
public://构造函数,不写构造函数随机值Date(){_year = 1;_month = 1;_day = 1;}void Print(){cout << _year << " - " << _month << " - " << _day << endl;}
private://这里是声明,不是初始化,给的是缺省值int _year = 1;int _month = 1;int _day;A _a;
};
int main()
{Date d1;d1.Print();return 0;
}

所以具体还是需要,分析一个类型成员是否需要初始化需求;
需要就写构造函数,不需要就交给编译器执行默认动作。
结论:一般情况下,都是自己写更适用。如果没写构造函数,编译器才会生成;否则,一旦写了编译器就不会写了。

#include <iostream>
using namespace std;
class stack
{
public:stack(){cout << "Stack_Init()..." << endl;}
};
class MyQueue
{
private:stack str1;stack str2;
};
int main()
{//构造函数适用于自定义类型MyQueue q;return 0;
}

2.3、什么是默认构造函数?

默认的构造函数,不完全等于编译器生成的构造函数,还包括无参的和全缺省的构造函数。
一般构造函数只需要写一个,否则存在编译歧义。

所以什么是默认构造函数?
答:1.无参构造函数 2.全缺省构造函数 3.没写构造而由编译器默认生成的构造函数

简述:不需要传参就可以调用的构造函数,都可以叫默认构造函数。

使用构造函数通常满足三个条件:
1.条件1,因为我们写了与类同名且无返回类型的构造函数,所以编译器就不会写了。
2.条件2,但是我们自己写的额构造函数不成立,因为构造函数有2中,除了编译器生成的以外就是无参和全缺省,所以报错:没有合适的默认构造函数可用。
3.条件3,3种构造函数又只能写一种(推荐写全缺省),否则编译会与其它2种构造函数存在歧义。

#include <iostream>
using namespace std;
class Date
{
public://构造函数的条件不成立,所以报错Date(int year, int month, int day){_year = year;_month = month;_day = day;}//无参的构造函数Date(){_year = 2024;_month = 1;_day = 27;}void Print(){cout << _year << " - " << _month << " - " << _day << endl;}
private://这里是声明,不是初始化,给的是缺省值//注意:是因为C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,//即:内置类型成员变量在类中声明时可以给默认值。不等于初始化int _year = 1;int _month = 1;int _day;
};
int main()
{//构造函数的条件不成立,所以报错//1.条件1,因为我们写了与类同名且无返回类型的构造函数,所以编译器就不会写了。//2.条件2,但是我们自己写的额构造函数不成立,因为构造函数有2中,除了编译器生成的以外就是无参和全缺省,所以报错:没有合适的默认构造函数可用。//3.条件3,3种构造函数又只能写一种(推荐写全缺省),否则编译会与其它2种构造函数存在歧义。Date d1;d1.Print();//构造函数支持函数重载,直接对象传参Date d2(2024, 2, 11);d2.Print();return 0;
}

3、析构函数

主要完成的清理和释放资源(常用于malloc等开辟的空间释放)

3.1、什么是析构函数?

概念
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

#include <iostream>
using namespace std;
class Date
{
public://无参的构造函数Date(){_year = 2024;_month = 1;_day = 27;}void Print(){cout << _year << " - " << _month << " - " << _day << endl;}//析构函数的格式,一般用户自定义类型,比如栈...~Date(){//验证编译器确实会在, 对象生命周期结束时默认调用执行cout << "~Date" << endl;}
private:int _year = 1;int _month = 1;int _day;
};
int main()
{Date d1;d1.Print();return 0;
}

3.2、析构函数的特性

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

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值类型。
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
#include <iostream>
#include <assert.h>
using namespace std;typedef int DataType;class Date
{
public://无参的构造函数Date(){_year = 2024;_month = 1;_day = 27;}void Print(){cout << _year << " - " << _month << " - " << _day << endl;}//析构函数的格式,一般用户自定义类型,比如栈...~Date(){//验证编译器确实会在, 对象生命周期结束时默认调用执行cout << "~Date" << endl;}
private:int _year;int _month;int _day;
};class Stack
{
public:Stack(int n = 4){//放个打印,表示默认执行了的cout << "Stack" << endl;_array = (DataType*)malloc(sizeof(DataType) * n);if (_array == nullptr){perror("malloc fail");return;}_size = 0;_capacity = n;}void PushBack(DataType data){//扩容if (_size == _capacity){int newcapacity = _capacity == 0 ? 4 : _capacity * 2;DataType* tmp = (DataType*)realloc(_array, sizeof(DataType) * newcapacity);if (tmp == nullptr){perror("realloc fail");return;}_array = tmp;_capacity = newcapacity;}_array[_size] = data;_size++;}DataType GetTop(){return _array[_size-1];}void PopBack(){_size--;}//DataType& GetPopVal()//{//	return _array[_size-1];//}bool Empty(){return _size == 0;}~Stack(){//放个打印,表示默认执行了的cout << "~Stack" << endl;if (_array){free(_array);_array = nullptr;_size = 0;_capacity = 0;}}
private:DataType* _array;int _size;int _capacity;
};int main()
{Date d1;d1.Print();//Stack s1(4);Stack s1;s1.PushBack(1);s1.PushBack(2);s1.PushBack(3);while (!s1.Empty()){cout << s1.GetTop() << endl;s1.PopBack();}return 0;
}

小结

1.可以发现,先打印的 ~Stack ,再打印的 ~Date,显然先创建的d1对象,后创建的s1对象;
说明,析构满足栈的特性。先进后出/后进先出。即:先创建的后析构。
2.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。
3.编译器默认生成的析构与构造函数类似;内置类型不做处理,自定义类型则调用它的写的析构(适合Queue,stack)

3.3、析构函数的释放顺序

析构函数的释放顺序:
局部对象(后定义的先析构)–》局部静态–》全局对象(后定义的先析构)

补充:虽然编译器对于构造函数和析构函数对自定义函数处理,但自定义类型的底层还是内置类型的嵌套。

#include <iostream>
using namespace std;class Date
{
public://全缺省的构造函数Date(int year){_year = year;}//析构函数~Date(){cout << "~Date()" << _year <<  endl;}
private:int _year;int _month;int _day;
};
Date d6(6);
static Date d7(7);void Test()
{Date d4(4);static Date d5(5);
}
int main()
{Date d1(1);Date d2(2);static Date d3(3);Test();return 0;
}

4、拷贝构造函数

4.1、什么是拷贝构造函数?

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

4.2、拷贝构造函数的特性

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

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
    注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
  4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类这样的类是没必要的。所以是根据实际的应用场景决定
    注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

4.3、拷贝构造函数典型调用场景

1.使用已存在对象创建新对象
2.函数参数类型为类类型对象
3.函数返回值类型为类类型对象

#include <iostream>
using namespace std;class Date
{
public://默认的成员函数,都别忘了隐式的this指针参数//全省参数构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造函数的基本格式//const目的理解为控制权限/权限保护Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << " - " << _month << " - " << _day << endl;}
private:int _year = 1;int _month = 1;int _day = 1;
};int main()
{Date d1(2024, 1, 28);d1.Print();//拷贝构造Date d2(d1);d2.Print();return 0;
}

4.4、理解传值传参和传引用传参

C++规定,执行自定义类型都会调用拷贝构造,执行了拷贝构造之后再进入自定义类型执行。

#include <iostream>
using namespace std;class Date
{
public://默认的成员函数,都别忘了隐式的this指针参数//全省参数构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造函数的基本格式//const目的理解为控制权限/权限保护Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << " - " << _month << " - " << _day << endl;}
private:int _year = 1;int _month = 1;int _day = 1;
};
//传值传参
//由于C++规定的这个特性,就可能会导致造成“死递归”的循环去调用拷贝构造。
//所以通常会结合const修饰参数的权限/属性
void func1(Date d)
{}
//传引用传参,引用不会调用拷贝构造,它是别名直接就可以操作参数。
void func2(Date& d)
{}
int main()
{Date d1(2024, 1, 28);d1.Print();//拷贝构造Date d2(d1);d2.Print();//C++规定自定义类型都会先调用拷贝构造//调用拷贝构造之前会先传参,又因为是传值传参就会形成新的拷贝构造,依次类推的陷入死循环func1(d1);func2(d2);return 0;
}

4.5、const正确的适用

const正确的适用有利于保护数据/权限访问/参数检查

#include <iostream>
using namespace std;class Date
{
public://默认的成员函数,都别忘了隐式的this指针参数//全省参数构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造函数的基本格式//Date(Date& d)易错//const目的理解为控制权限/权限保护Date(const Date& d){//const保护参数是否写法的检查//d._year = this->_year;_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << " - " << _month << " - " << _day << endl;}
private:int _year = 1;int _month = 1;int _day = 1;
};int main()
{Date d1(2024, 1, 28);d1.Print();//拷贝构造Date d2(d1);d2.Print();Date d3(d2);d3.Print();return 0;
}

4.6、拷贝构造的默认处理

拷贝构造会对自定义类型进行默认处理
1.若没有显式定义的拷贝构造函数,那么编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按照内置类型成员按照内存存储的额字节依次完成拷贝,这种叫作浅拷贝。或叫值拷贝。
2.另外对于自定义的类型的拷贝函数称为深拷贝/引用拷贝。 拷贝构造函数的参数只能有一个并且必须是类类型对象的引用,因为传值是死递归直接报错。

#include <iostream>
using namespace std;class Time
{
public://强制编译器生成构造函数,因为后面写了拷贝构造函数,所以需要自己写,//但这里只是为了验证拷贝构造函数的深拷贝,所以构造函数就没什么意义,//就使用强制默认构造defaultTime() = default;//拷贝构造函数Time(const Time& t){//打印验证cout << "Time()" << endl;_hour = t._hour;_minute = t._minute;_second = t._second;}void Print(){cout << _hour << " - " << _minute << " - " << _second << endl;}
private:int _hour = 1;int _minute = 1;int _second = 1;
};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 = 1;int _month = 1;int _day = 1;//验证自定义类型Time _t;
};int main()
{Date d1(2024, 1, 28);d1.Print();//Time t1;//t1.Print();//拷贝构造Date d2(d1);d2.Print();Date d3(d2);d3.Print();return 0;
}

小结
若未显式定义,编译器会自动生成默认的拷贝构造,传值拷贝会调用拷贝构造函数,称为浅拷贝;
对自定义类型的成员则调用它自己的拷贝构造,称为深拷贝。
补充
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

4.7、拷贝构造浅拷贝的无脑拷贝问题

那么既然编译器不管内置类型还是自定义类型,都会帮我们执行拷贝工作,那么还需要我们自己写?
答:当然,编译器不可能完成一些复杂的逻辑和操作,所以遇到栈等开辟额外空间的操作时,需要我们自己注意完善。

#include <iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}//深拷贝处理//解决办法就是开辟另一块同样大小的空间进行相关的操作Stack(const Stack& s){DataType* tmp = (DataType*)malloc(s._capacity * sizeof(DataType));if (nullptr == tmp){perror("malloc申请空间失败");return;}//拷贝数据 -->sizememcpy(tmp, s._array, sizeof(DataType) * s._size);_array = tmp;_size = s._size;_capacity = s._capacity;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);//拷贝构造函数Stack s2(s1);return 0;
}

这段代码的核心问题在于,拷贝构造函数会连同数组的地址一同无脑的拷贝,所以在程旭执行结束执行free释放时,由于两个数组地址指向了同一块地址空间,那么释放掉其中一个指针,那么另外一个再去释放,就造成了同一块区域空间的重复释放的问题。

解决办法就是我们自己编写深拷贝,处理正确的数组指针,正确的释放。
小结:一般来说,浅拷贝不适用于malloc开辟空间这种情况。所以一般交给深拷贝处理。

5、操作符重载,关键字operator

5.1、关键字operator

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
操作符重载为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

注意
1.不能通过连接其他符号来创建新的操作符:比如operator@。
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变, 例如:内置的整型 + ,不能改变其含义作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
4.注意:‘.*’ , ‘::’ , ‘sizeof’ , ‘?:’ , ‘.’ 注意以上5个运算符不能重载。

#include <iostream>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
//private:int _year;int _month;int _day;
};//bool DateEqual(const Date& x, const Date& y)
//运算符重载:operator + 运算符
bool operator==(const Date& x, const Date& y)
{return x._year == y._year && x._month == y._month && x._day == y._day;
}
//日期不能整体比较
//2024 1 28
//2024 1 27
//2024 2 26//bool DateLess(const Date& x, const Date& y)
//运算符重载:operator + 运算符
bool operator<(const Date& x, const Date& y)
{if (x._year < y._year){return true;}else if (x._year == y._year){if (x._month < y._month){return true;}else if (x._month == y._month){return x._day < y._day;}}//写完true,则其余情况falsereturn false;
}int main()
{Date d1(2024, 1, 28);Date d2(2024, 2, 26);//对象不能直接比较,不能strcmp//error:d1 < d2//error:strcmp(d1,d2)//cout << DateEqual(d1, d2) << endl;//cout << DateLess(d1, d2) << endl;//为了可读性和通识性,引入关键词operator//对运算符的需要重新控制//operator+运算符 作函数名cout << operator==(d1, d2) << endl;cout << operator<(d1, d2) << endl;//等价写法:但是注意,传参参数的和函数的参数顺序绑定的,不可任意交换参数顺序cout << (d1 ==  d2) << endl;//cout << operator==(d1, d2) << endl;cout << (d1 < d2) << endl;//cout << operator<(d1, d2) << endl;return 0;
}

重载的运算符通常是具备一定意义的运算符。
注意以上的访问操作是基于,类的成员变量处于pubilc状态的。
那么如何解决私有状态的正确访问呢?

5.2、私有限定符情况

1.写“回调函数” Getyear()/ Getmonth()/ Getday()
2.直接在类里写其相关函数即可

私有限定符情况,解决方法1:

#include <iostream>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//解决方法1:int Getyear(){return _year;}int Getmonth(){return _month;}int Getday(){return _day;}
private:int _year;int _month;int _day;
};bool operator==(Date& x,  Date& y)
{return x.Getyear() == y.Getyear()&& x.Getmonth() == y.Getmonth()&& x.Getday() == y.Getday();
}bool operator<(Date& x, Date& y)
{if (x.Getyear() < y.Getyear()){return true;}else if (x.Getyear() == y.Getyear()){if (x.Getmonth() < y.Getmonth()){return true;}else if (x.Getmonth() == y.Getmonth()){return x.Getday() < y.Getday();}}return false;
}int main()
{Date d1(2024, 1, 28);Date d2(2024, 2, 26);cout << operator==(d1, d2) << endl;cout << operator<(d1, d2) << endl;//等价写法:但是注意,传参参数的和函数的参数顺序绑定的,不可任意交换参数顺序cout << (d1 == d2) << endl;//cout << operator==(d1, d2) << endl;cout << (d1 < d2) << endl;//cout << operator<(d1, d2) << endl;return 0;
}

私有限定符情况,解决方法2:

#include <iostream>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//解决方法2:直接放进类中,会报错,运算符函数的参数太多。//因为包括了隐含的成员函数this指针。//所以去掉多于参数,并且放进类之后,本身也就只需要被比较的参数了//bool operator==(const Date& x, const Date& y)bool operator==(const Date& y){return _year == y._year&& _month == y._month&& _day == y._day;}//bool operator<(const Date& x, const Date& y)bool operator<(const Date& y){if (_year < y._year){return true;}else if (_year == y._year){if (_month < y._month){return true;}else if (_month == y._month){return _day < y._day;}}//写完true,则其余情况falsereturn false;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 1, 28);Date d2(2024, 2, 26);//cout << operator==(d1, d2) << endl;//cout << operator<(d1, d2) << endl;cout << d1.operator==(d2) << endl;cout << d1.operator<(d2) << endl;cout << d1.operator==(d1) << endl;cout << d1.operator<(d1) << endl;cout << d2.operator==(d2) << endl;cout << d2.operator<(d2) << endl;cout << d2.operator==(d1) << endl;cout << d2.operator<(d1) << endl;//等价写法:但是注意,传参参数的和函数的参数顺序绑定的,不可任意交换参数顺序cout << (d1 == d2) << endl;//cout << operator==(d1, d2) << endl;cout << (d1 < d2) << endl;//cout << operator<(d1, d2) << endl;return 0;
}

5.3、了解‘.*’操作符

'.’ 等价于 '->'属于成员指针运算符(不支持重载)

#include <iostream>
using namespace std;class ob
{
public:void func(){cout << "void func()" << endl;}
};typedef void(ob::* pobfunc)();int main()
{pobfunc p = &ob::func;ob temp;//(*p)();//this(temp.*p)();return 0;
}

6、赋值运算符重载

6.1、 赋值运算符重载

1.参数类型:const T & ,传递引用可以提高传参效率
2.返回值类型:T & ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
3.检测是否自己给自己赋值
4.返回 * this :要复合连续赋值的含义

6.2、 拷贝构造函数与赋值运算符重载的区别

1.赋值运算符只能重载成类的成员函数不能重载成全局函数;
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。 此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
2.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?
答:当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
即与拷贝构造函数类似,涉及malloc动态开辟的空间操作就是自己写,编译器会把数组地址无脑拷贝

#include <iostream>
using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator==(const Date& y){return _year == y._year&& _month == y._month&& _day == y._day;}bool operator<(const Date& y){if (_year < y._year){return true;}else if (_year == y._year){if (_month < y._month){return true;}else if (_month == y._month){return _day < y._day;}}return false;}//不写赋值重载,编译器会自动生成,但是只能是类的成员函数,//因为当用户在类外自己实现一个全局的赋值运算符重载时,就和编译器在类中生成的默认赋值运算符重载冲突了。//d1 = d2//void operator=(const Date& d)//满足,赋值运算符的连续赋值,添加Date返回值类型。//传值和传引用,返回又有一定的区别,传值只是拷贝,传引用才能改变变量Date& operator=(const Date& d){//添加,if判断使其不会浪费空间资源去执行赋值操作。if (this != &d){_year = d._year;_month = d._month;_day = d._day;return *this;}}void Print(){cout << _year << " - " << _month << " - " << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 1, 28);Date d2(2024, 2, 26);//拷贝构造Date d3(d1);//拷贝构造,同类型中一个已存在对象进行初始化要创建的对象//d1 = d2;//已经存在的对象,一个拷贝赋值给另一个存在对象d1 = d2;d1.Print();d2.Print();//连续赋值int i, j, k;i = j = k = 10;//优先级cout << ((i = j) = 10) << endl;cout << (i = j = 10) << endl;//那么赋值重载的运算符能实现连续赋值操作吗?//当然能,解决没有找到void类型的右操作数的运算符。//修改返回值为类类型并加引用。//不写赋值重载,编译器会自动生成d1 = d2 = d3;//偶尔会测试/失误的自己会给自己赋值//d1 = d1;//添加,if判断使其不会浪费空间资源去执行赋值操作。return 0;
}

小结:

1.用户没有显示实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝; 赋值重载与拷贝构造函数类似的,赋值重载内置类型可由编译器自动赋值,自定义类型调用它的赋值重载赋值。
2.不写赋值重载,编译器会自动生成,但是只能是类的成员函数,因为当用户在类外自己实现一个全局的赋值运算符重载时,就和编译器在类中生成的默认赋值运算符重载冲突了。

7、类的6个默认成员函数总结

1.构造函数和析构函数可归为一组,内置类型不做处理,自定义类型调用对应的构造函数和析构函数。
2.拷贝构造函数和赋值重载可归为一组,内置类型拷贝或赋值(编译器无法干一些复杂的逻辑操作,比如malloc开辟的空间处理等),自定义类型调用对应的拷贝构造函数和赋值重载

相关文章:

【C++之类和对象篇002】

C学习笔记---005 C知识类和对象篇1、类的6个默认成员函数2、构造函数2.1、构造函数的特性2.2、内置类型和自定义类型2.3、什么是默认构造函数&#xff1f; 3、析构函数3.1、什么是析构函数&#xff1f;3.2、析构函数的特性3.3、析构函数的释放顺序 4、拷贝构造函数4.1、什么是拷…...

k8s学习(RKE+k8s+rancher2.x)成长系列之简配版环境搭建(三)

3.19.切换RKE用户&#xff0c;并做免密登录&#xff08;三台机器相互免密&#xff09; su rke cd~ ssh-keygen[rkemaster.ssh]$ssh-copy-id rkeslaver2 [rkemaster.ssh]$ssh-copy-id rkeslaver1 [rkemaster.ssh]$ssh-copy-id rkemaster3.20.搭建RKE集群 为了方便理解&#…...

基于SSM的疫情期间学生信息管理平台的设计与实现(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的疫情期间学生信息管理平台的设计与实现&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…...

LeetCode_20_简单_有效的括号

文章目录 1. 题目2. 思路及代码实现&#xff08;Python&#xff09;2.1 栈 1. 题目 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型…...

gRPC 备查

简介 HTTP/2 HTTP/2 的三个概念 架构 使用流程 gRPC 的接口类型 1.单一RPC 2.服务器流式RPC 3.客户端式流式RPC 4.双向流式RPC...

MySQL 基础知识(十)之 MySQL 架构

目录 1 MySQL 架构说明 2 连接层 3 核心业务层 3.1 查询缓存 3.2 解析器 3.3 优化器 3.4 执行器 4 存储引擎层 5 参考文档 1 MySQL 架构说明 下图是 MySQL 5.7 及其之前版本的逻辑架构示意图 MySQL 架构大致可分为以下三层&#xff1a; 连接层&#xff1a;负责跟客户…...

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--大模型、扩散模型

专属领域论文订阅 VX关注{晓理紫},每日更新论文,如感兴趣,请转发给有需要的同学,谢谢支持 如果你感觉对你有所帮助,请关注我,每日准时为你推送最新论文。 为了答谢各位网友的支持,从今日起免费为300名读者提供订阅主题论文服务,只需VX关注公号并回复{邮箱+论文主题}(如…...

Delphi v11 安卓权限申请

问题 Delphi 10.4 的安卓权限申请代码&#xff0c;在 Delphi 11 下面编译无法通过。 原因 原因是里面有几个变量类型的定义有所不同。 procedure TDmBLE.RequestPermissionsResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TAr…...

频谱仿真平台HTZ Communications为私有5G建设铺平道路

韩国的国家监管机构韩国通信委员会&#xff08;KCA&#xff09;计划在德思特频谱仿真平台HTZ Communications的支持下加快扩大无线电接入范围&#xff0c;提升全国电信服务的质量和效率。 韩国通信委员会&#xff08;KCA&#xff09;在韩国的监管环境中扮演着至关重要的角色&am…...

【高效开发工具系列】PyCharm使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

进程终止与进程等待

fork 函数 fork 函数是 Linux 中一个非常重要的函数&#xff0c;它的作用是从已存在的进程中创建一个新进程。这个新进程就是当前进程的子进程。 fork() 函数使用方法&#xff1a;它在头文件 #include <unistd.h> 中&#xff0c;函数原型为 pid_t fork(void); 用一个…...

MySQL 基础知识(六)之数据查询(二)

目录 6 数值型函数 7 字符串函数 8 流程控制函数 9 聚合函数 10 分组查询 (group by) 11 分组过滤 (having) 12 限定查询 (limit) 13 多表查询 13.1 连接条件关键词 (on、using) 13.2 连接算法 13.3 交叉连接 (cross join) 13.4 内连接 (inner join) 13.5 外连接 …...

蓝桥杯嵌入式STM32G431RBT6知识点(主观题部分)

目录 1 前置准备 1.1 Keil 1.1.1 编译器版本及微库 1.1.2 添加官方提供的LCD及I2C文件 1.2 CubeMX 1.2.1 时钟树 1.2.2 其他 1.2.3 明确CubeMX路径&#xff0c;放置芯片包 2 GPIO 2.1 实验1&#xff1a;LED1-LED8循环亮灭 ​编辑 2.2 实验2&#xff1a…...

ELAdmin 部署

后端部署 按需修改 application-prod.yml 例如验证码方式、登录状态到期时间等等。 修改完成后打好 Jar 包 执行完成后会生成最终可执行的 jar。JPA版本是 2.6&#xff0c;MyBatis 版本是 1.1。 启动命令 nohup java -jar eladmin-system-2.6.jar --spring.profiles.active…...

计算机功能简介:EC, NVMe, SCSI/ISCSI与块存储接口 RBD,NUMA

一 EC是指Embedded Controller 主要应用于移动计算机系统和嵌入式计算机系统中&#xff0c;为此类计算机提供系统管理功能。EC的主要功能是控制计算机主板上电时序、管理电池充电和放电&#xff0c;提供键盘矩阵接口、智能风扇接口、串口、GPIO、PS/2等常规IO功能&#xff0c;…...

linux上安装bluesky的步骤

1、设备上安装的操作系统如下&#xff1a; orangepiorangepi5b:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04.2 LTS Release: 22.04 Codename: jammy 2、在用户家目录下创建一个目录miniconda3目录&a…...

视频监控需求八问:视频智能分析/视频汇聚平台EasyCVR有何特性?

最近TSINGSEE青犀视频在与业内伙伴进行项目合作的过程中&#xff0c;针对安防监控可视化视频管理系统EasyCVR视频融合平台在电信运营商项目中的应用&#xff0c;进行了多方面的项目需求沟通。今天我们就该项目沟通为案例&#xff0c;来具体了解一下用户关心度较高的关于视频智能…...

django rest framework 学习笔记2

注意&#xff1a;该文章部分摘抄之百度&#xff0c;仅当做学习笔记供小白使用&#xff0c;若侵权请联系删除&#xff01; 显示关联表的数据&#xff0c;本示例会显示所有的关联的数据信息 from rest_framework import serializers from .models import Student class StudentM…...

第四篇【传奇开心果系列】Python文本和语音相互转换库技术点案例示例:pyttsx3自动化脚本经典案例

传奇开心果短博文系列 系列短博文目录Python文本和语音相互转换库技术点案例示例系列 短博文目录前言一、雏形示例代码二、扩展思路介绍三、批量处理文本示例代码四、自定义语音设置示例代码五、结合其他库和API示例代码六、语音交互系统示例代码七、多语言支持示例代码八、添加…...

model.train()和model.eval()两种模式的原理

1. model.train() 在使用 pytorch 构建神经网络的时候&#xff0c;训练过程中会在程序上方添加一句model.train()&#xff0c;作用是 启用 batch normalization 和 dropout 。 如果模型中有BN层&#xff08;Batch Normalization&#xff09;和 Dropout &#xff0c;需要在 训练…...

docker的底层原理六: 联合文件系统(UnionFS)

Docker的底层存储原理基于联合文件系统&#xff08;UnionFS&#xff09;。 联合文件系统&#xff08;UnionFS&#xff09;是一种特殊的文件系统&#xff0c;它允许独立地叠加多个目录层&#xff0c;呈现给用户的是这些目录层的联合视图。这种结构使得在Docker中&#xff0c;不…...

【动态规划专栏】专题一:斐波那契数列模型--------1.第N个泰波那契数

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…...

自养号测评低成本高效率推广,安全可控

测评的作用在于让用户更真实、清晰、快捷地了解产品以及产品的使用方法和体验。通过买家对产品的测评&#xff0c;也可以帮助厂商和卖家优化产品缺陷&#xff0c;提高用户的使用体验。这进而帮助他们获得更好的销量&#xff0c;并更深入地了解市场需求。因此&#xff0c;测评在…...

ubuntu22.04@laptop OpenCV Get Started: 015_deep_learning_with_opencv_dnn_module

ubuntu22.04laptop OpenCV Get Started: 015_deep_learning_with_opencv_dnn_module 1. 源由2. 应用Demo2.1 C应用Demo2.2 Python应用Demo 3. 使用 OpenCV DNN 模块进行图像分类3.1 导入模块并加载类名文本文件3.2 从磁盘加载预训练 DenseNet121 模型3.3 读取图像并准备为模型输…...

【elk查日志 elastic(kibana)】

文章目录 概要具体的使用方式一&#xff1a;查找接口调用历史二&#xff1a;查找自己的打印日志三&#xff1a;查找错误日志 概要 每次查日志&#xff0c;我都需要别人帮我&#xff0c;时间长了总觉得不好意思&#xff0c;所以这次下定决心好好的梳理一下&#xff0c;怎么查日…...

RapidMiner数据挖掘2 —— 初识RapidMiner

本节由一系列练习与问题组成&#xff0c;这些练习与问题有助于理解多个基本概念。它侧重于各种特定步骤&#xff0c;以进行直接的探索性数据分析。因此&#xff0c;其主要目标是测试一些检查初步数据特征的方法。大多数练习都是关于图表技术&#xff0c;通常用于数据挖掘。 为此…...

基于STM32的光照检测系统设计

基于STM32的光照检测系统设计 摘要: 随着物联网和智能家居的快速发展,光照检测系统在智能环境控制中扮演着越来越重要的角色。本文设计了一种基于STM32的光照检测系统,该系统能够实时检测环境光强度,并根据光强度调节照明设备,实现智能照明控制。本文首先介绍了系统的总体…...

车辆管理系统设计与实践

车辆管理系统是针对车辆信息、行驶记录、维护保养等进行全面管理的系统。本文将介绍车辆管理系统的设计原则、技术架构以及实践经验&#xff0c;帮助读者了解如何构建一个高效、稳定的车辆管理系统。 1. 系统设计原则 在设计车辆管理系统时&#xff0c;需要遵循以下设计原则&…...

板块一 Servlet编程:第四节 HttpServletResponse对象全解与重定向 来自【汤米尼克的JAVAEE全套教程专栏】

板块一 Servlet编程&#xff1a;第四节 HttpServletResponse对象全解与重定向 一、什么是HttpServletResponse二、响应数据的常用方法三、响应乱码问题字符流乱码字节流乱码 四、重定向&#xff1a;sendRedirect请求转发和重定向的区别 在上一节中&#xff0c;我们系统的学习了…...

漫谈:C/C++ char 和 unsigned char 的用途

C/C的字符默认是有符号的&#xff0c;这一点非常的不爽&#xff0c;因为很少有人用单字节表达有符号数&#xff0c;毕竟&#xff0c;ASCII码是无符号的&#xff0c;对字符的绝大多数处理都是基于无符号的。 这一点在其它编程语言上就好很多&#xff0c;基本上都提供了byte这种类…...