【C++】类和对象(六个默认成员函数)

news2024/11/16 19:33:42

文章目录

    • 类的六个默认成员函数
      • **构造函数**
        • **构造函数的目的**
        • **构造函数的特性**
      • 析构函数
        • 析构函数概念
        • 析构函数处理的顺序
        • 析构函数清理细节
      • 拷贝构造函数
        • 拷贝构造函数典型调用场景
      • 赋值运算符重载
        • 运算符重载
        • 赋值运算重载
        • 前置++和后置++ 重载
      • const成员函数
        • 再提权限的问题:
      • 取地址及const取地址操作符重载
      • 日期类的实现(日期计算):
        • 获得某年某月的天数
        • 比较两个日期
        • 重点实现日期的计算
          • 日期加天数
          • 日期减日期(重点)
        • 流插入和流提取运算符重载

类的六个默认成员函数

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。(对于空类,并不是什么都没有,编译器会自动默认生成以下六个默认成员函数)

接下来,这六个默认成员函数,将通过日期Data类来介绍

请添加图片描述

请添加图片描述

构造函数

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

构造函数的目的

默认构造构造是为了解决创建对象,忘记对其对象进行初始化操作,同时麻烦调用Init函数进行初始化

构造函数的特性

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

特征如下:

  • 函数名与类名相同
  • 无返回值
  • 对象实例化时编译器自动调用对应的构造函数
  • 构造函数支持函数重载
class Date
{
public:
	//1.无参构造函数
	Date()
	{
		_year = 2024;
		_day = 6;
	}
	//2.带参构造函数
	Date(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;//调用无参构造函数
	Date d2(2024, 3, 6);//调用有参构造函数
	d1.Print();
	d2.Print();
	Date d3();//未调用原型函数(是否有意用变量定义?)
	return 0;
}

注意:

对于显式方式生成,同样需要遵守上述特征或者规则,关于Date d3(void)报错,因为编译器很难区分.这里是对象实例化调用无参构造函数还是函数声明为了避免混淆这两种情况要求对象实例化调用无参构造函数,不允许添加括号

同时对于无参构造和有参构造,无参构成采用函数内部设置好的数值,而有参构造则采用外部实参数值。对于这两个性质,可以使用缺省参数将这两个归并在一起,形成一个全缺省的构造函数。不建议全缺省构造函数和无参构造函数同时出现,语法上允许这种行为,但是调用使用过程,会存在歧义,编译器无法区分(有多种初始化方式,条件允许,实现一个全缺省最好用,比较灵活控制参数)

特征:如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义构造函数,编译器将不再生成

如果Date类中构造函数注释后,代码可以通过编译,编译器自动生成了一个无参的默认构造函数。但是定义有参构造函数(没有无参),对象实例化调用需求调用无参的。因为有显式构造,编译器不会默认自动生成无参构造,只存在有参构造,对此没有合适的默认构造函数可用,会报错error C2512: “Date”: 没有合适的默认构造函数可用

class Date
{
public:
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	Date(int year, int mont3, int day)//有参构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;//调用无参的,存在有参构造,编译器不会默认生成,但是这个有参构造,d1调不动。
	d1.Print();
	return 0;
}

特性:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。无参构造函数、全缺省构造函数、我们没有写编译器默认生成的构成函数,都可以认为是默认构造函数(对于这三个默认构造函数,使用时只调用其中一个)

问题:从编译后结果来看,无论是显式的默认构造函数还是编译器默认生成无参构造,对于内置类型的初始化都是随机值,从结果上来说,确实完成了每个对象的初始化,但是没有多大意义,对此编译器生成的默认构造函数并没有用的?

class Time
{
public:
	Time()
	{
		cout <<"Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{

private:
	//内置类型
	int _year;
	int _month;
	int _day;
	//自定义类型
	Time _t;
};

int main()
{
	Date d1;
}

C/C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型(int/char/double ),自定义类型就是自己通过关键字定义的类型(struct /class/union)。

结论:

  • 对内置类型不做处理
  • 对自定义类型的成员,会去调用他们的默认构造(无参构造函数、全缺省构造函数、我们没有写编译器默认生成的构成函数)

对此,为什么不对内置类型做处理了呢?

*这里语言设计过程中遗留下来问题,在C++11中对于内置类型是否处理有了争执,当然内置类型不处理也可能有它的原因,对此C++11还是保持对内置类型不处理的态度,但是打了补丁,即是:内置类型成员变量在类中声明事可以给缺省值

请添加图片描述

问题:有了构造函数,我们就什么事都不用做了吗?

答:无论是自己写的还是编译器提供的,一般建议是保证每个类都有提供一个默认构造函数,如果类中还含有自定义类型成员,可以使用特性,自定义类型会调用他们的默认构造,从而完成初始化。

但是无论是内置类型或者是自定义类型,数据都是需要我们自己处理,只不过是间接和直接而已(套娃:所谓的自定义类型不过是包含内置类型,其中可能还有自定义类型,但是自定义类型最后一定是内置类型,是内置类型都需要人去设置处理)。但是还有很有价值的,比如在MyQueue里面定义 stack s1stack s2,这里会调用默认构造,完成对象s1s2的初始化。

析构函数

析构函数概念

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

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

  • 析构函数名是在类名前加上字符~
  • 无参数无返回值类型,导致了析构函数不支持重载函数
  • 一个类只能有一个析构函数。若未显式定义,系统会在自动生成默认的析构函数。注意:析构函数不能重载
  • 对象生命周期结束时,C++编译系统自动调用析构函数

首先,需要搞清楚,构造函数完成的是初始化,而析构函数完成的是清除资源(free),防止出现内存泄漏。这里的资源一般是指动态开辟的资源,同时如果没有析构函数处理,单纯只对对象进行开辟和销毁,没有考虑对象内部申请的动态空间,导致内存泄漏。(对象是存储在栈帧上,是由系统进行处理的,也称为自动变量)

请添加图片描述

通过个例子方便理解,对此我们需要先在创建函数中定义个类对象与显式析构函数,当函数结束后,更准确地说应该是当类对象出了作用域,生命周期销毁后,打印析构函数中数据,检查是否会自动调用析构函数

请添加图片描述

对此可以推断出,上述是正确的。这里涉及到了隐含this指针,对此调用析构函数中this指针存储对象的地址。

先谈下析构函数对内置类型和自定义类型的处理方式(同样以代码的方式展示)
请添加图片描述

结论:

  • 内置类型不处理
  • 自定义类型成员,调用对应的析构函数
析构函数处理的顺序

在上面打印地址中,不知道你们有没有注意到,打印顺序的问题,这里跟栈的特性是有关系的。栈的特点是先进后出,对此牢记两点。【先定义的,先构造】【后定义的,先析构】,这里两点都符合栈的特殊结构

class Date
{
public:
	Date(int year=1)
	{
		_year = year;
	}
	~Date()
	{
		cout << "~Date()->" <<_year<< endl;
	}
private:
	int _year;
	int _month;
	int _day;

};

Date d5(5);//全局对象
static Date d6(6);//全局对象

void func()
{
	Date d3(3);//局部变量
	static Date d4(4);//局部的静态
}
int main()
{
	Date d1(1);//局部变量
	Date d2(2);//局部变量
	func();
	return 0;
}

请添加图片描述

结论:局部对象(后定义先析构)->局部的静态->全局对象(后定义先析构)

析构函数清理细节
class Time
{
public:
    ~Time()
    {
    cout << "~Time()" << endl;
    }
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型
    Time _t;
};
int main()
{
    Date d;
    return 0;
}

对于默认析构函数内置类型不处理的方式,我们可以采用显式析构函数,里面的逻辑是自己设计的,可以对内置类型进行操作。但是这样子没有价值,内置类型不需要进行资源清除,同时将内置类型全部设置为0,同样没有完成清除的任务,对此在程序结束后,系统会自动回收内置类型的空间,不需要我们多此一举

d对象的销毁时,要将其内部包含的Time类的_t对象销毁,但是这里不是直接调用Time类的析构函数,因为实际要释放的是Date类对象,对此调用Date类对象对应的析构函数(编译器默认生成的析构函数),目的是在其内部调用Time。(没有直接调用Time类析构函数,通过Date类中析构函数间接调用)

注意:

  • 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收
  • 创建哪个类的对象,则调用该类的析构函数,销毁那个类的对象,则调用该类的析构函数
  • 如果类中没有申请资源(一般指堆上资源),析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date;有资源申请时,一定要写,否则会造成内存泄漏,比如Stack类(不是每一个类都需要析构)

拷贝构造函数

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

class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
    _year = year;
    _month = month;
    _day = day;
}
// Date(const Date& d) // 正确写法
Date(const Date d) // 错误写法:编译报错,会引发无穷递归
{
    _year = d._year;
    _month = d._month;
    _day = d._day;
}
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1;
    Date d2(d1);
    return 0;
}

拷贝构造函数的特性:

  • 拷贝构造函数是构造函数的一个重载形式,同类型对象进行初始化
  • 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用(编译器可能会强制检查)

首先,先解决几个问题!

  • 拷贝构造函数为什么只有一个参数?
  • 为什么传值会引发无穷递归调用?可以写一个返回条件控制吗?不可以是指针接收吗?
  • 为什么拷贝构造参数部分前面需要加const修饰?

第一个问题:

答:这里只需要传个需要拷贝对象参数就行,因为存在隐含this指针,将调用对象的地址传进来,编译器会自动处理

第二个问题:

答:关于这点,通过函数栈帧章节,得知如果是传值去调用拷贝构造函数,需要先形参开辟一块空间去拷贝一份实参数据。对此传值需要类对象的拷贝,就需要调用拷贝构造函数,要调用拷贝构造函数就需要先传值,这里就是套娃一样。
请添加图片描述

如写一个返回条件,这里压根就进不了拷贝构造函数,返回条件都用不上

指针可以,但是指针不适合这里。使用引用,就是给实参取别名,跟指向对象占用一块内存空间,对此就不需要拷贝一份数据,去调用拷贝函数

第三个问题:

保证被拷贝对象不会被修改,可以及时地报错检查是否位置放反了。同时如果拷贝构造传的是const修饰的变量,如果拷贝构造函数参数部分没用const修饰,就会造成权限放大(常引用章节有所涉及)

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

class Time
{
public:
    Time()
    {
        _hour = 1;
        _minute = 1;
        _second = 1;
    }
Time(const Time& t)
{
    _hour = t._hour;
    _minute = t._minute;
    _second = t._second;
    cout << "Time::Time(const Time&)" << endl;
}
private:
    int _hour;
    int _minute;
    int _second;
};
class Date
{
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型
    Time _t;
};
int main()
{
    Date d1;
    // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
    // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
    Date d2(d1);
return 0;
}

这里也是需要注意的一点,拷贝构造是构造函数的一种特殊形式,如果存在拷贝构造,编译器不会自动生成默认构造函数,对此可以(函数名=default;)强制编译器生成。(大多出现在类中类)

在编译器生成的默认拷贝构造函数的结论:

  • 内置类型按照字节方式直接拷贝
  • 自定义类型是调用其他拷贝构造函数完成拷贝

对此,编译器默认生成的默认拷贝构造,本身可以实现内置类型按照字节方式直接拷贝,那么自己是否还需要实现显式拷贝构造吗?当然是在不同场景下,有不同场景的处理办法。

接下来如果继续使用浅拷贝程序就会崩溃掉,就需要使用深拷贝解决。因为这里两个对象调用析构函数,对同一块空间进行free,重复free会报错

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()
{
    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;
}

请添加图片描述

请添加图片描述

对于以上问题,这里可以对array使用深拷贝处理,开辟一块等大的空间,更换指向对此解决两个对象指向同一块空间的问题,在生命周期结束时,会自动调用对应析构函数释放资源(数据拷贝到新空间,将指向转为指向新空间)

Stack(const Stack& st)
	{
		_array = (DataType*)malloc(st._capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(_array, st._array, st._size * sizeof(DataType));//要记得把原来的数据拷贝过去
		_size = st._size;
		_capacity = st._capacity;
	}

重点:
类中没有涉及资源申请,拷贝构造是否写都是可以
类中一旦涉及资源申请,拷贝构造一定要写,否则就是浅拷贝

拷贝构造函数典型调用场景
  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象
class Date
{
public:
    Date(int year, int minute, int day)
    {
        cout << "Date(int,int,int):" << 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(2022,1,13);
    Test(d1);
    return 0;
}

请添加图片描述

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

赋值运算符重载

运算符重载

如果我们需要比较两个日期,那么就需要实现一个函数,但是不好的地方就是对函数名取名字(很依赖写代码人的素养)

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

  • operator运算符重载(针对自定义类型)对于运算符的行为重新定义控制,跟函数重载不是同一个东西
  • 函数名名字:关键字operator + 需要重载的运算符符号
  • 函数原型:返回值 operator操作符(参数列表)

使用该函数注意点:

  • 不能通过连接其他符号来创建新的操作符:比如operator@(需要是C/C++语法中存在)
  • 重载操作符必须有一个类类型参数(不能去重载运算符改变内置类型的行为)
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少一个,因为成员函数的第一个参数为隐藏的this指针
  • .* :: sizeof ?: .注意以上运算符不能重载。这个经常在笔试选择题中出现(注意第一个不是是可以重载的)
  • 并不是运算符都是需要重载的,需要看是否有存在的意义,参数部分需要对应顺序

还是通过样例来理解吧!

class Date
{
public:
	Date(int year = 2024, int month = 3, int day = 9)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//private:
	int _year;
	int _month;
	int _day;
};
bool operator==(const Date& x, const Date& y)
{
	return x._year == y._year
		&& x._month == y._month
		&& x._day == y._day;
}

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;
		}
	}
	return false;
};

请添加图片描述

祖师爷设置运算符重载的长期目标是自定义类型也可也使用运算符,同时这里编译器可以调用这两个对象,是原因存在this指针。但是可以更简单就是下面的写法(效果是等价的,同时注意优先级的问题)

问题:这里为什么可以访问类内成员?因为注释掉了private才能访问到,如果类外面不能随便访问成员,有什么办法可以解决呢?

答:有两种方式

  • 在类中提供Get函数 int Getname(){return _name};
  • 在类里面定义该函数,就可以使用类内成员

这里采用第二种方式

在类里面定义该函数,这样子该函数有隐含this指针,只需要传一个参数就行。就可以这样子写d1.operator<(d2)==operator(const this*d1,d2)同时在类内部定义就可以使用private去保护成员变量,完成了封装

请添加图片描述

注意:关于这两种写法都是可以的,编译器知道会调用这个函数。并且第一种写法不会转为第二种写法再调用,而是直接调用对应的函数,中间步骤省略

赋值运算重载

赋值运算符重载格式:

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

问题:类中实现显式的赋值运算符重载是这样的,那么如果注释该函数是否可以赋值成功呢?

void operator=(const Date& d)
{
		_year=d._year;
        _month=d._month;
        _day=d._day;
}

请添加图片描述

从结果上,编译器默认生成的赋值运算符重载可以完成对内置类型的处理,这种处理方式跟拷贝构造很是类似,那么现在是考验大家的时候到了。

int main()
{
	Date d1(2024, 1, 23);
    Date d2=(2024, 2, 28);
	Date d3 = d1;
    return 0;
}

问题:这里Date d3 = d1;是拷贝构造还是赋值运算符重载?

答案:属于拷贝构造

重要结论(d3不是已经存在的对象):

拷贝构造:同类型一个存在的对象进行初始化要创建对象

赋值运算符重载:已经存在的对象,一个拷贝赋值给另一个

同时关于连续赋值的问题

int main()
{
	Date d1(2024, 1, 23);
	Date d2(2024, 2, 28);
	Date d3;

	int i; int j = 10;
	i = j = 20;
	
	d3 = d2 = d1;
	return 0;
}

这里内置类型连续赋值是从右到左,这里是先处理j=20这个表达式,之后返回一个临时变量存储返回值,再跟跟i赋值。

那么自定义类型连续赋值,先处理d2=d1;这里就会调用赋值运算符重载。如果是连续赋值的话,这里返回值需要改为Date,并且返回对象*this

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

但是这个写法不好,因为返回值值先存储到寄存器中传值不会返回对象本身回返回他的拷贝如果是同类的话,就需要调用拷贝构造,太浪费

那么我们可以使用引用(Date&)做返回值,直接返回d2本身,不需要拷贝。对此这样说明了有些地方,引用可以,但是指针不行的地方

当然可能会存在一种情况,自己给自己赋值(一般人都不这么干,主要是白干),对此一般会加给判断语句

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

特性:赋值运算符只能重载成类的成员函数不能重载成全局函数

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
        }
        int _year;
        int _month;
        int _day;
    };
Date& operator=(Date& left, const Date& right)
{
    if (&left != &right)
    {
        left._year = right._year;
        left._month = right._month;
        left._day = right._day;
    }
    return left;
}
//编译失败:
//error C2801: “operator =”必须是非静态成员

赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了需要给两个参数。同时赋值运算符重载跟拷贝构造类似,如果不显式实现,编译器会生成一个默认的赋值运算符重载。此时用户再类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突,故而赋值运算符只能是类的成员函数
请添加图片描述

特性:用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

请添加图片描述

在编译器生成默认赋值运算符重载的结论(跟拷贝构造类似):

  • 内置类型成员变量是直接赋值的
  • 自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值

既然编译器生成的默认赋值运算符重载已经可以完成字节序的值拷贝,还需要自己实现吗?这个问题跟拷贝构造函数那里的问题是一样的(对于像日期类这样的类没有必要)

接下来如果继续使用浅拷贝程序就会崩溃掉,就需要使用深拷贝解决。因为这里两个对象调用同一个函数,对同一块空间进行free,重复free会报错

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
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()
{
    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;
    s2 = s1;
    return 0;
} 

请添加图片描述

总结:

如果类中未涉及到资源管理,赋值运算符是否实现都是可以的

如果类中涉及到资源管理,赋值运算符则必须实现

前置++和后置++ 重载
//++d1
Date operator++();
//d1++
Date operator++();

前置++和后置++ 都这样子写,编译器是无法区分的,对此需要特殊处理(解决语法逻辑不自洽,自相矛盾)–>++operator可以,但是C++给了其他的解决方法

前置++和后置++都是属于一元运算符,为了让前置++与后置++形成能正确重载。C++规定:后置++重载时,多增加一个int类型的参数(用来完成重载,没有实际意义),但是调用函数时该参数不用传参,编译器会自动传递。

//前置++
Date& operator++()
{
    _day += 1;
    return *this;
}
//后置++
Date operator++(int)
{
    Date temp(*this);
    _day += 1;
    return temp;
}

const成员函数

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

请添加图片描述

对此先提出几个问题:

  • cosnt对象可以调用非const成员函数吗?
  • 非const对象可以调用const成员函数吗?
  • const成员函数内可以调用其他的非const成员函数吗?
  • 非const成员函数内可以调用其他的const成员函数吗?

接下来,带着我们的问题进行。

int main()
{
	Date d1(2024, 1, 31);
	d1.Print();

	const Date d2(2024, 3, 31);
	d2.Print();

	return 0;
}

请添加图片描述

这里原因很显然是d2的权限被放大了(权限可以缩小,但是不能放大)

请添加图片描述

对于我们需要修饰this指向的内容,但是规则是this中形参和实参的位置不允许写,那么怎么修改呢?祖师爷给了一个办法,在函数定义地方加个const。void fname() const,至于为什么不是 const void fname()还是那一句,我们是语法的学习者。这样处理完了之后,对于const对象和非const对象都可以调用该函数。
请添加图片描述

问题:全部函数加上const不就行了吗?干嘛这么麻烦。

总结:

  • 成员变量,如果是一个对成员变量只进行读访问的函数->建议加const,这样const对象和非const对象都可以使用(比如print函数,只需要读权限)
  • 成员变量,如果是一个对成员变量只进行读写访问的函数->不能加const,否则不能修改成员变量(需要读写权限)

问题:那么流插入和流提取能不能加上const修饰呢?

这里就需要搞清楚const修饰谁,这里流插入和流提取运算符重载函数,是在全局实现的,不是在类中实现,没有隐含的this指针,而const成员函数是修饰this指针的

再提权限的问题:
const int i=0;
int j=i;

int& r=i;//报错

const int*p1=&i1;
int *p2=p1//报错

总结;

否可以完成拷贝,需要对象的改变是否影响被拷贝对象。关于报错的地址,是权限放大(指针和引用赋值才存在权限放大)

对于上面的几个问题的答案:

  • cosnt对象可以调用非const成员函数吗?不可以,权限放大
  • 非const对象可以调用const成员函数吗?可以,权限缩小
  • const成员函数内可以调用其他的非const成员函数吗?不可以,权限放大
  • 非const成员函数内可以调用其他的const成员函数吗?可以,权限缩小

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

这两个默认成员函数一般不用重新定义,编译器会默认生成的

这两个运算符一般不需要重载,使用编译器生成的默认取地址重载即可,只有特殊情况,才需要重载,比如想让别人获得指定的内容

class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&() const
	{
		return this;
	}
private:
		int _year; // 年
		int _month; // 月
		int _day; // 日
};
int main()
{
	int a = 0;
	const int b = 10;
	cout << &a << endl;
	cout << &b << endl;
	return 0;
}

特殊情况:

class Date
{
public:
	Date* operator&()
	{
		return null;
	}
	const Date* operator&() const
	{
		return (const Date*)0xeeffee;
	}
private:
		int _year; // 年
		int _month; // 月
		int _day; // 日
};

对于返回地址具有选择性,可以指定返回空或者返回一个像模像样的地址(传了假地址都很难发现,写bug小妙招)

对此我们来实现天数的加法

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date& operator++()
	{
		_day += 1;
		return *this;
	}	
	Date operator++(int)
	{
		Date temp(*this);
		_day += 1;
		return temp;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d;
	Date d1(2022, 1, 13);
	d = d1++; // d: 2022,1,13 d1:2022,1,14
	d = ++d1; // d: 2022,1,15 d1:2022,1,15
	return 0;
}

结论:

前置++:返回+1之后的结果
注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率

后置++:
注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1,而temp是临时对象,出了作用域生命周期结束,因此只能以值的方式返回,不能返回引用

(这样说明了,虽然this指针不能在形参和实现显式写,但是在函数里面可以使用,因为是有需求的)

日期类的实现(日期计算):

这个真的是一个大问题,对此我们需要将他不断分解为小问题解决!!!这里同样涉及到很多知识点。

如果你想让这个函数是内敛,可以在类里面定义的函数,默认是内敛,内敛不支持声明和定义分离。

获得某年某月的天数

关于计算日期,最频繁使用的就是某年某月的天数,对此可以单独实现一个函数获得该天数,但是关于表示年月日的成员对象都在日期类中封装起来,类外部不能随便访问类成员,只能在类中实现GetMonthDay函数,在通过return将获得的天数返回

实现逻辑(涉及到很多历史发展背景,这里局限于现代的日期计算)

int Date::GetMonthDay(int year, int month) const
{
	assert(month > 0 && month < 13);//保证月数合法
	static int montharr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	
	if (month == 2 && (year % 4 == 0 && year & 100 != 0) || year & 400 == 0)
		return 29;
	else
		return montharr[month];
}

实现该接口:

  • 这里由于自转和公转问题,当是闰年时,二月的天数加一
  • 还有一些细节上的问题(但是CPU跑太快,没啥影响),static int montharr属于静态变量,只能定义一次,对此频繁调用时,不用多次定义。在判断语句中,可以将位置进行调正,这里跟&&短路知识点有关,如果前面是假,不同接下去判断,整个表达式都为假
比较两个日期

这里需要涉及到运算符重载,但是这里有个小技巧,只需要实现大于等于或者小于等于的函数就可以了。

下列是需要实现的函数声明:

	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;
	bool operator<(const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator>(const Date& d) const;
	bool operator>=(const Date& d) const;

按照上面的小技巧,这边主要具体实现小于等于,之后大于等于取反就行了

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

bool Date::operator!=(const Date& d) const
{
	return !(*this == d);//这里会调用的
}

bool Date::operator<(const Date& d) const
{
	if (_year < d._year)
		return true;
	else if (_year == d._year && _month < d._month)
		return true;
	else if (_year == d._year && _month == d._month && _day < d._day)
		return true;
	else
		return false;
}

bool Date::operator<=(const Date& d) const
{
	return (*this < d) || (*this == d);
}

bool Date::operator>(const Date& d) const
{
	return !(*this <= d);
}

bool Date::operator>=(const Date& d) const
{
	return !(*this < d);
}
重点实现日期的计算
日期加天数

需要使用持续进位原则

Date& Date::operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);//频繁调用,不用考虑其中细节问题
			++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}
	return *this;
}

Date Date::operator+(int day) const
{
	Date temp(*this);//拷贝构造
	temp += day;
	return temp;
}

实现该接口时:

  • 当日期加完天数后,通过日期的规则,需要按照进位原则,对年月日数据进行调正
  • 在实现operator+=(+),都可以间接实现operator+(+=)
  • 这里operator+=使用引用返回,提高了效率和避免传值返回中的拷贝过程
  • operator+这里不能使用引用返回,这里是创建了一个临时变量,调用完会销毁
  • 那么是先实现+=,再间接实现+呢?先实现+,再间接实现operator+=

请添加图片描述

这里是推荐先实现operator+=,再间接实现operator+

理由如下:

这里不能交叉着比,需要横向对比。这里实现operator+是等价的,但是实现operator+=,左边需要复用+operator,对比之下就多了一次拷贝构造Date tmp=*this;(这里为什么是拷贝构造,而不是赋值运算符重载,在赋值运算符重载有介绍)

这里就不详细介绍关于operator-和operator-=,跟operator+和operator+有异曲同工之处,具体在Date.cpp看看。

日期减日期(重点)

需要使用持续借位原则,如果天数为0,需要得到上月的天数

这里有两个办法(operator-,即使运算符重载也是函数重载)

第一个方法:不断++直到等于大的那个年份(方便,但效率有点低)

int Date::operator-(const Date& d)
{
	int flag = 1;
	Date max = *this;
	Date min = d;

	if (*this < d)
	{
		int flag = -1;//判断两个天数相差
		max = d;
		min = *this;
	}

	int n = 0;
	while (min != max)
	{
		++min;//这里会调用operato++()
		++n;//operator++()
	}

	return n * flag;
}

*第二个方法:先将两个年份修饰到1月1日,再计算两个年之间有多少个年,如果是平年+365,闰年+366

int Date::operator-(const Date& d) const
{
	//不知道哪个操作数大,先假设
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)//假设错了就认错
	{
		Date max = d;
		Date min = *this;
		int flag = -1;//用来标记
	}
	int count =0;
	//大的减到1月1日  count++
	while (!(max._day == 1 && max._month == 1))
	{
		--max;
		++count;
	}
	//小的减到1月1日  count--
	while (!(min._day == 1 && min._month == 1))
	{
		--min;
		--count;
	}
	//都减到1月1日了  算差多少年
	while (min._year != max._year)
	{
		if (is_leapyear(min._year))
			count += 366;
		else
			count += 365;
		++min._year;
	}
	return flag * count;
}

流插入和流提取运算符重载

cout和cin的本质是输入和输出流对象,对于<<和>>用于重载的运算符,从图可以得,cout属于ostream类,cin属于istream类,可以自动识别类型。

对于我们可以在日期类中,实现<<和>>重载打印日期和提取日期!

int main()
{
	Date d1(2024, 3, 10);
    //void operator<<(ostream& out);
    //cout<<d1;
    
	d1 << cout;//->d1.operator<<(cout)->operator<<(const Date *d1,cout);
	return 0;
}

如果使用运算符重载,隐含的this指针占用第一个参数的位置,Date必须是左操作数,d1<<cout是不符合我们的习惯的

对此我们可以在类外实现该运算符重载函数,就可以自己设计参数部分的位置。但是又引出了另一个问题:类外不能访问类中的私有成员,如果将私有权限放开,就缺乏安全性对此C++中有友元,接下来我们会涉及到,这里就使用下,虽然这个全局函数不在类中,但是可以访问私有成员函数

//友元,告诉该类这两个全局函数是我们的朋友,允许使用私有成员(在类中)
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "" << d._month << "" << d._day << "" << endl;
	return out;
}
 
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

如果我们需要连续打印cout<<d1<<d2的话,这里就不合适的,因为这里的结合性是从左往右cout<<d1会返回一个临时变量,那么这里运算符重载函数需要通过引用返回了。(C++存在私有的,printf不支持自定义打印,cout本质实现所用类型的打印)

注意:

  • 用引用做返回值,应对连续流插入和流提取
  • 流提取不是不能对Date进行const修饰,需要通过键盘读取数据存储在成员变量

最后需要注意一点,需要判断输入进去的数据是否有误!

请添加图片描述

源代码展示:

Date.h

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1);
	bool operator<(const Date& d);
	bool operator<=(const Date& d);
	bool operator>(const Date& d);
	bool operator>=(const Date& d);
	bool operator==(const Date& d);
	bool operator!=(const Date& d);

	// d1 + 100
	Date& operator+=(int day);
	Date operator+(int day);
	// d1 - 100
	Date operator-(int day);
	Date& operator-=(int day);

	// ++d1
	Date& operator++();
	// 特殊处理:解决语法逻辑不自洽,自相矛盾的问题
	// d1++
	// 为了跟前置++区分,强行增加一个int形参,够成重载区分
	Date operator++(int);

	Date operator--(int);
	Date& operator--();

	// d1 - d2
	int operator-(const Date& d);

	// 本质就是inline
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
		static int monthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

		// 365   自转  公转  365 5+h
		// 366
		if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{
			return 29;
		}

		return monthDays[month];
	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

Date.cpp

#include"Date.h"

Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}

bool Date::operator<(const Date& d)
{
	if (_year < d._year)
	{
		return true;
	}
	else if (_year == d._year)
	{
		if (_month < d._month)
		{
			return true;
		}
		else if (_month == d._month)
		{
			if (_day < d._day)
			{
				return true;
			}
		}
	}

	return false;
}
// d1 <= d2
bool Date::operator<=(const Date& d)
{
	return *this < d || *this == d;
}

bool Date::operator>(const Date& d)
{
	return !(*this <= d);
}

bool Date::operator>=(const Date& d)
{
	return !(*this < d);
}

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

bool Date::operator!=(const Date& d)
{
	return !(*this == d);
}

// d1 += 10
Date& Date::operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}

	return *this;
}

Date Date::operator+(int day)
{
	//Date tmp(*this);
	Date tmp = *this; // 
	tmp += day;

	return tmp;
}

// d1 + 10
//Date Date::operator+(int day)
//{
//	//Date tmp(*this);
//	Date tmp = *this; // 
//
//	tmp._day += day;
//	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
//	{
//		tmp._day -= GetMonthDay(tmp._year, tmp._month);
//		++tmp._month;
//		if (tmp._month == 13)
//		{
//			++tmp._year;
//			tmp._month = 1;
//		}
//	}
//
//	return tmp;
//}
//
 d1 += 100
//Date& Date::operator+=(int day)
//{
//	*this = *this + day;
//
//	return *this;
//}

Date Date::operator-(int day)
{
	Date tmp = *this;
	tmp -= day;

	return tmp;
}

Date& Date::operator-=(int day)
{
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

// ++d ->d.operator++()
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

// d++ ->d.operator++(0)
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

// d1 - d2
int Date::operator-(const Date& d)
{
	int flag = 1;
	Date max = *this;
	Date min = d;
	if (*this < d)
	{
		int flag = -1;
		max = d;
		min = *this;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}
	return n * flag;
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"
#include"Date.h"

int main()
{
	Date d1(2024, 1, 29);
	Date d2 = d1 + 20;
	d2.Print();
	d1.Print();

	d2 -= 20;
	d2.Print();

	d1 += 30000;
	d1.Print();

	++d1;
	d1.operator++();
	d1.Print();

	d1++;
	d1.operator++(10);
	d1.Print();

	/*bool ret = false;
	if (ret)
	{
		d1.Print();
	}*/

	Date d4(2024, 1, 29);
	Date d5(2024, 8, 1);
	cout << d5 - d4 << endl;

	return 0;
}

谢谢大家的观看,这里是个人笔记,希望对你学习C++有帮助。

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

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

相关文章

HAproxy反向代理与负载均衡

目录 一、HAproxy介绍 1. 概述 2. 关于4/7层负载均衡 2.1 无负载均衡 2.1.1 图示 2.1.2 说明 2.2 四层负载均衡 2.2.1 图示 2.2.2 说明 2.3 七层负载 2.3.1 图示 2.3.2 说明 3. 特性 4. HAProxy负载均衡常见策略 5. 处理模式 二、HAproxy安装 1. yum安装 2. 第…

文献阅读笔记:全卷积神经网络

文献阅读笔记&#xff1a;全卷积神经网络 摘要Abstract1. 全卷积神经网络1.1 文献摘要1.2 全卷积神经网络1.2.1 网络结构1.2.0 从分类器到密集 FCN1.2.2 上采样 Upsampling1.2.3 跳级结构1.2.4 FCN训练 1.3 实验1.4 总结 2. 代码实现 摘要 本周学习了全卷积神经网络&#xff0…

模板不存在:./Application/Home/View/OnContact/Index.html 错误位置

模板不存在:./Application/Home/View/OnContact/Index.html 错误位置FILE: /home/huimingdedhpucixmaihndged5e/wwwroot/ThinkPHP123/Library/Think/View.class.php  LINE: 110 TRACE#0 /home/huimingdedhpucixmaihndged5e/wwwroot/ThinkPHP123/Library/Think/View.class.php(…

关于迁移数据库的一些问题

开发人员&#xff0c;并且需要将数据库迁移到另一个分库&#xff0c;尽量采取以下步骤&#xff1a; 备份源数据库&#xff1a; 在进行任何操作之前&#xff0c;首先要确保对源数据库进行备份&#xff0c;以防止意外数据丢失或损坏。 创建目标分库&#xff1a; 在目标数据库服务…

mockjs学习

1.前言 最近面试发现之前团队协同合作的项目没有mock数据难以向面试官直接展示&#xff0c;所以迟到得来速学一下mockjs。 参考视频&#xff1a;mockJs 妈妈再也不用担心我没有后端接口啦_哔哩哔哩_bilibili 一开始查阅了一些资料&#xff0c;先是看了下EasyMock&#xff0c…

windows安装ElasticSearch踩坑记

ElasticSearch是一个开源的分布式搜索和分析引擎。它提供实时分布式搜索功能&#xff0c;可以索引和搜索大量的结构化和非结构化数据。Elasticsearch以其速度、可伸缩性和处理复杂查询的能力而闻名。它常用于日志分析、全文搜索、文档搜索和数据分析等领域。使用ElasticSearch的…

AHU 算法分析 实验四 动态规划

实验四&#xff1a;动态规划 实验目的 • 理解动态规划的基本思想&#xff0c;理解动态规划算法的两个基本要素最 优子结构性质和子问题的重叠性质。 • 熟练掌握典型的动态规划问题。 • 掌握动态规划思想分析问题的一般方法&#xff0c;对较简单的问题能正确 分析&#x…

Day31:安全开发-JS应用WebPack打包器第三方库JQuery安装使用安全检测

目录 打包器-WebPack-使用&安全 第三方库-JQuery-使用&安全 思维导图 JS知识点&#xff1a; 功能&#xff1a;登录验证&#xff0c;文件操作&#xff0c;SQL操作&#xff0c;云应用接入&#xff0c;框架开发&#xff0c;打包器使用等 技术&#xff1a;原生开发&…

章六、集合(1)—— 概念、API、List 接口及实现类、集合迭代

零、 关闭IDEA调试时自动隐藏空元素 一、 集合的概念 存储一个班学员信息&#xff0c;假定一个班容纳20名学员 当我们需要保存一组一样&#xff08;类型相同&#xff09;的元素的时候&#xff0c;我们应该使用一个容器来存储&#xff0c;数组就是这样一个容器。 数组有什么缺…

简析内部审计数字化转型的方法和路径

简析内部审计数字化转型的方法和路径 内部审计是一种独立的、客观的确认和咨询活动&#xff0c;包括鉴证、识别和分析问题以及提供管理建议和解决方案。狭义的数字化转型是指将企业经营管理和业务操作的各种行为、状态和结果用数字的形式来记录和存储&#xff0c;据此再对数据…

基于纳什谈判理论的风–光–氢多主体能源系统合作运行方法(含matlab代码)

目录 主要内容 部分代码 结果一览 下载链接 主要内容 程序解决的是一个基于合作博弈的风光氢能源交易的问题&#xff0c;首先&#xff0c;考虑主体间的电能交易建立各主体的优化运行模型&#xff0c; 然后基于纳什谈判理论建立风–光–氢多主体合作运行模型&…

Springboot+vue的高校危化试剂仓储系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的高校危化试剂仓储系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#x…

(003)SlickEdit Unity的补全

文章目录 步骤XML知识点 附录 步骤 1.下载 unity 源码。 2.将自定义文件 MonoBehaviour.cs 放到解压后的项目里面&#xff1a; using System;namespace UnityEngine {public partial class MonoBehaviour{public virtual void Awake(){throw new NotImplementedException();…

Axure原型设计项目效果 全国职业院校技能大赛物联网应用开发赛项项目原型设计题目

目录 前言 一、2022年任务书3效果图 二、2022年任务书5效果图 三、2022年国赛正式赛卷 四、2023年国赛第一套样题 五、2023年国赛第二套样题 六、2023年国赛第三套样题 七、2023年国赛第四套样题 八、2023年国赛第七套样题 九、2023年国赛正式赛题&#xff08;第八套…

flink重温笔记(十四): flink 高级特性和新特性(3)——数据类型及 Avro 序列化

Flink学习笔记 前言&#xff1a;今天是学习 flink 的第 14 天啦&#xff01;学习了 flink 高级特性和新特性之数据类型及 avro 序列化&#xff0c;主要是解决大数据领域数据规范化写入和规范化读取的问题&#xff0c;avro 数据结构可以节约存储空间&#xff0c;本文中结合企业真…

RabbitMQ - 06 - Topic交换机

目录 控制台创建队列与交换机 编写消费者方法 编写生产者测试方法 结果 Topic交换机与Direct交换机基本一致 可参考 这篇帖子 http://t.csdnimg.cn/AuvoK topic交换机与Direct交换机的区别是 Topic交换机接收的消息RoutingKey必须是多个单词&#xff0c;以 . 分割 Topic交…

练习01-登录注册(简单)

一、用户登录/注册实现 综合前面学的知识来实现简单的注册登录功能 1.准备工作 注册登录页面 数据库&#xff0c;数据表 mybatis 坐标引入&#xff0c;MySQL驱动 配置 映射文件 用户实体类 Servlet代码 2.页面 不想手写的可以看博主IT黄大大【带源码】 【炫酷登录界…

贝叶斯优化CNN-GRU回归预测(matlab代码)

贝叶斯优化CNN-GRU回归预测matlab代码 贝叶斯优化方法则采用贝叶斯思想&#xff0c;通过不断探索各种参数组合的结果&#xff0c;根据已有信息计算期望值&#xff0c;并选择期望值最大的组合作为最佳策略&#xff0c;从而在尽可能少的实验次数下达到最优解。 数据为Excel股票…

PostgreSQL数据优化——死元组清理

最近遇到一个奇怪的问题&#xff0c;一个百万级的PostgreSQL表&#xff0c;只有3个索引。但是每次执行insert或update语句就要几百ms以上。经过查询发现是一个狠简单的问题&#xff0c;数据库表死元组太多了&#xff0c;需要手动清理。 在 PG 中&#xff0c;update/delete 语句…

每日五道java面试题之springMVC篇(二)

目录&#xff1a; 第一题. 请描述Spring MVC的工作流程&#xff1f;描述一下 DispatcherServlet 的工作流程&#xff1f;第二题. MVC是什么&#xff1f;MVC设计模式的好处有哪些?第三题. 注解原理是什么?第四题. Spring MVC常用的注解有哪些&#xff1f;第五题. SpingMvc中的…