当前位置: 首页 > 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;需要在 训练…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用

一、方案背景​ 在现代生产与生活场景中&#xff0c;如工厂高危作业区、医院手术室、公共场景等&#xff0c;人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式&#xff0c;存在效率低、覆盖面不足、判断主观性强等问题&#xff0c;难以满足对人员打手机行为精…...

在 Spring Boot 中使用 JSP

jsp&#xff1f; 好多年没用了。重新整一下 还费了点时间&#xff0c;记录一下。 项目结构&#xff1a; pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

C++_哈希表

本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、基础概念 1. 哈希核心思想&#xff1a; 哈希函数的作用&#xff1a;通过此函数建立一个Key与存储位置之间的映射关系。理想目标&#xff1a;实现…...