C++类和对象(中)
类的默认成员函数
默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数。一个类,我们不写的情况下编译器会默认生成6个默认成员函数,C++11以后还会增加两个默认成员函数,移动构造和移动赋值。默认成员函数 很重要,也比较复杂,我们要从两个方面去学习:
第⼀:我们不写时,编译器默认⽣成的函数⾏为是什么,是否满⾜我们的需求?
第二:编译器默认⽣成的函数不满⾜我们的需求,我们需要⾃⼰实现,那么如何⾃⼰实现?
构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象(我们常使⽤的局部对象是栈帧创建时,空间就开好了),⽽是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Date类中写的Init函数的功能,构造函数⾃动调⽤的特点就完美的替代的了Init。
构造函数的特点:
- 函数名与类名相同。
- ⽆返回值。(返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)
- 对象实例化时系统会⾃动调⽤对应的构造函数。
- 构造函数可以重载。
- 如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦用户显式定义编译器将不再⽣成。
- 我们不写,编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是否初始化是不确定的,看编译器。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量没有默认构造函数,那么就会报错。
- ⽆参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函数。但是这三个函数有且只有⼀个存在,不能同时存在。⽆参构造函数和全缺省构造函数虽然构成函数重载,但是调⽤时会存在歧义。要注意很多人会认为默认构造函数是编译器默认⽣成那个叫默认构造,实际上⽆参构造函数、全缺省构造函数也是默认构造,总结⼀下就是不传实参就可以调⽤的构造就叫默认构造。
说明:C++把类型分成内置类型(基本类型)和⾃定义类型。内置类型就是语⾔提供的原⽣数据类型, 如:int / char / double / 指针等,⾃定义类型就是我们使⽤class / struct等关键字⾃⼰定义的类型。
#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 = 1, int day = 1)//{// _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(2023, 2, 2);//调用带参的构造函数d2.Print();//注意:如果通过⽆参构造函数创建对象时,对象后⾯不⽤跟括号,//否则编译器⽆法区分这⾥是函数声明还是实例化对象。//warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)//Date d3(); //-> Date func(); 这样就看着像函数声明了 return 0;
}
#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{
public://全缺省构造函数Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc fail");return;}_capacity = n;_top = 0;//打印证明确实调用了构造函数cout << "Stack(int n = 4)" << endl;}// ...
private:STDataType* _a;int _capacity;int _top;
};// 两个Stack实现队列
class MyQueue
{
public://编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化private:Stack pushst;Stack popst;
};
int main()
{MyQueue mq;return 0;
}
析构函数
析构函数与构造函数功能相反,析构函数不是完成对对象本⾝的销毁,⽐如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会⾃动调⽤析构函数,完成对象中资源的清理释放⼯作。析构函数的功能类⽐我们之前Stack实现的Destroy功能,⽽像Date没有Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的。
析构函数的特点:
- 析构函数名是在类名前加上字符~。
- ⽆参数⽆返回值。(这⾥跟构造类似,也不需要加void,但是多了一个无参)
- ⼀个类只能有⼀个析构函数。若未显式定义,系统会⾃动⽣成默认的析构函数。
- 对象⽣命周期结束时,系统会⾃动调⽤析构函数。
- 跟构造函数类似,我们不写编译器⾃动⽣成的析构函数对内置类型成员不做处理,自定义类型成员会调⽤他的析构函数。
- 注意:我们显式写析构函数,对于⾃定义类型成员也会调⽤他的析构,也就是说⾃定义类型成员⽆论什么情况都会⾃动调⽤析构函数。
- 如果类中没有申请资源时,析构函数可以不写,直接使⽤编译器⽣成的默认析构函数,如Date; 如果默认⽣成的析构就可以⽤,也就不需要显式写析构,如MyQueue;但是有资源申请时,⼀定要⾃⼰写析构,否则会造成资源泄漏,如Stack。
- ⼀个局部域的多个对象,C++规定后定义的对象先析构。
#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{
public://全缺省构造函数Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc fail");return;}_capacity = n;_top = 0;//打印证明确实调用了构造函数cout << "Stack(int n = 4)" << endl;}//析构函数~Stack(){free(_a);_a = nullptr;_capacity = _top = 0;//打印证明确实调用了析构函数cout << "~Stack()" << endl;}
private:STDataType* _a;int _capacity;int _top;
};// 两个Stack实现队列
class MyQueue
{
public://编译器默认⽣成MyQueue的析构函数调⽤了Stack的析构,释放的Stack内部的资源//显式写析构,也会⾃动调⽤Stack的析构//6. 我们显式写析构函数,对于⾃定义类型成员也会调⽤他的析构,也就是说⾃定义类//型成员⽆论什么情况都会⾃动调⽤析构函数。//~MyQueue()//{}
private:Stack pushst;Stack popst;
};
int main()
{MyQueue mq;return 0;
}
拷贝构造函数
如果⼀个构造函数的第⼀个参数是⾃⾝类类型的引⽤,且任何额外的参数都有默认值,则此构造函数也叫做拷⻉构造函数,也就是说拷⻉构造是⼀个特殊的构造函数。
拷贝构造的特点:
- 拷⻉构造函数是构造函数的⼀个重载。
- 拷⻉构造函数的第⼀个参数必须是类类型对象的引⽤,使⽤传值⽅式编译器直接报错,因为语法逻辑上会引发⽆穷递归调⽤。拷⻉构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引⽤,后⾯的参数必须有缺省值。
- C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥⾃定义类型传值传参和传值返回都会调⽤拷⻉构造完成。
- 若未显式定义拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉ / 浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造。
- 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的拷⻉构造就可以完成需要的拷⻉,所以不需要我们显式实现拷⻉构造。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的拷⻉构造完成的值拷⻉ / 浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器⾃动⽣成的拷⻉构造会调⽤Stack的拷⻉构造,也不需要我们显式实现MyQueue的拷⻉构造。这⾥还有⼀个⼩技巧,如果⼀个类显式实现了析构并释放资源,那么他就需要显式写拷⻉构造,否则就不需要。
- 传值返回会产⽣⼀个临时对象调⽤拷⻉构造,传引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉,但是⼀定要确保返回对象在当前函数结束后还在,才能⽤引⽤返回。
#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造//d2(d1)Date(const Date& d){_year = d._year;_month = d._month;_day = d._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;
};//形参是实参的一份临时拷贝,自定义类型拷贝要调用拷贝构造
//所以拷贝构造第一个参数如果不引用的话就会一直调用无限递归
void Func1(Date d)
{cout << &d << endl;d.Print();
}//Date Func2()
Date& Func2()
{Date tmp(2021, 2, 4);tmp.Print();return tmp;
}int main()
{Date d1(2024, 4, 25); //构造Date d2(d1); //拷贝构造 Date d2 = d1; //相当于Date d2(d1);//C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥传值传参要调⽤拷⻉构造//所以这⾥的d2传值传参给d1要调⽤拷⻉构造完成拷⻉,传引⽤传参可以较少这⾥的拷⻉//Func1(d1);//d1.Print();//d2.Print();//这⾥可以完成拷⻉,但是不是拷⻉构造,只是⼀个普通的构造//Date d3(&d1);//d1.Print();//d3.Print();// Func2返回了⼀个局部对象tmp的引⽤作为返回值// Func2函数结束,tmp对象就销毁了,相当于了⼀个野引⽤Date ret = Func2();ret.Print();return 0;
}
#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{
public://默认构造Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc fail");return;}_capacity = n;_top = 0;}//拷贝构造//st2(st1)Stack(const Stack& s){//需要对_a指向资源创建同样⼤的资源再拷⻉值_a = (STDataType*)malloc(sizeof(STDataType) * s._capacity);if (nullptr == _a){perror("malloc fail");return;}memcpy(_a, s._a, sizeof(STDataType) * s._top);_capacity = s._capacity;_top = s._top;}void Push(int x){//扩容//..._a[_top++] = x;}//析构~Stack(){free(_a);_a = nullptr;_capacity = _top = 0;}
private:STDataType* _a;int _capacity;int _top;
};// 两个Stack实现队列
class MyQueue
{
public:private:Stack pushst;Stack popst;
};int main()
{Stack st1;st1.Push(1);st1.Push(2);st1.Push(3);st1.Push(4);//Stack不显式实现拷⻉构造,⽤⾃动⽣成的拷⻉构造完成浅拷⻉//会导致st1和st2⾥⾯的_a指针指向同⼀块资源,析构时会析构两次,程序崩溃Stack st2 = st1;//Stack st2(st1);MyQueue mq1;//MyQueue⾃动⽣成的拷⻉构造,会⾃动调⽤Stack拷⻉构造完成pushst / popst//的拷⻉,只要Stack拷⻉构造⾃⼰实现了深拷⻉,他就没问题MyQueue mq2 = mq1;return 0;
}
赋值运算符重载
运算符重载
- 当运算符被⽤于类类型的对象时,C++语⾔允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使⽤运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错。
- 运算符重载是具有特殊名字的函数,他的名字是由operator和后⾯要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。
- 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。
- 如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数⽐运算对象少⼀个。
- 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
- 不能通过连接语法中没有的符号来创建新的操作符:⽐如operator@。
- .* :: sizeof ?: . 注意以上5个运算符不能重载。
- 重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,
如:operator+(int x, int y)- ⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义,⽐如Date类重载operator - 就有意义,但是重载operator * 就没有意义。
- 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。 C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。
- 重载 << 和 >> 时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位 置,第⼀个形参位置是左侧运算对象,调⽤时就变成了对象 << cout,不符合使⽤习惯和可读性。 重载为全局函数把ostream / istream放到第⼀个形参位置就可以了,第⼆个形参位置为类类型对象。
#include <iostream>
using namespace std;//error C2803: “operator +”必须至少有一个类类型的形参
//int operator+(int x, int y)
//{
// return x + y;
//}class A
{
public:void func(){cout << "void func()" << endl;}
};void func1()
{cout << "void func1()" << endl;
}int main()
{void(*pf1)() = func1; //函数指针pf1();//利用函数指针调用函数//C++规定成员函数要加&才能取到成员指针void(A::*pf)() = &A::func;//成员函数指针A tmp;//对象调用成员函数指针时,使用.*运算符(tmp.*pf)();return 0;
}
#include <iostream>
using namespace std;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;int _month;int _day;
};//重载为全局的运算符重载函数,⾯临对象访问私有成员变量的问题
//有⼏种⽅法可以解决:
//1、成员放公有
//2、Date提供getxxx函数
//3、友元函数
//4、重载为成员函数
bool operator==(const Date& x1, const Date& x2)
{return x1._year == x2._year&& x1._month == x2._month&& x1._day == x2._day;
}int main()
{Date d1(2024, 4, 3);Date d2 = d1;//bool ret = d1 == d2; //编译器会转换成operator==(d1, d2);bool ret = operator==(d1, d2);//运算符重载函数可以显式调⽤cout << ret << endl;return 0;
}
Date.h
#pragma once
#include <iostream>
#include <cassert>
using namespace std;class Date
{
public://友元函数声明friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);bool CheckDate();//日期是否非法检查void Print();Date(int year = 1, int month = 1, int day = 1);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;//直接定义类⾥⾯,他默认是inline,频繁调⽤//得到每月的天数int GetDay(int year, int month){assert(year > 0 && month > 0 && month < 13);static int Month[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if (month == 2 && ((year % 100 != 0 && year % 4 == 0) || year % 400 == 0))return 29;elsereturn Month[month];}//日期 += 天数Date& operator+=(int day);//日期 + 天数Date operator+(int day);//日期 -= 天数Date& operator-=(int day);//日期 - 天数Date operator-(int day);Date& operator++();//前置Date operator++(int);//后置Date& operator--();Date operator--(int);//日期 - 日期int operator-(const Date& d);private:int _year;int _month;int _day;
};ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
Date.cpp
#include "Date.h"void Date::Print()
{cout << _year << "/" << _month << "/" << _day << endl;
}bool Date::CheckDate()
{if (_month < 1 || _month > 12 || _year < 1|| _day < 1 || _day > GetDay(_year, _month))return false;elsereturn true;
}Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;//if (!this->CheckDate())if (!CheckDate())cout << "日期非法->" << *this << endl;
}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);
}bool Date::operator>(const Date& d)
{if (_year != d._year)return _year > d._year;else if (_month != d._month)return _month > d._month;elsereturn _day > d._day;
}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);
}Date& Date::operator+=(int day)
{if (day < 0)return *this -= -day;_day += day;while (_day > GetDay(_year, _month)){_day -= GetDay(_year, _month);_month++;if (_month > 12){_year++;_month = 1;}}return *this;
}Date Date::operator+(int day)
{Date tmp = *this;tmp += day;return tmp;//Date tmp = *this;//tmp._day += day;//while (tmp._day > GetDay(tmp._year, tmp._month))//{// tmp._day -= GetDay(tmp._year, tmp._month);// tmp._month++;// if (tmp._month > 12)// {// tmp._year++;// tmp._month = 1;// }//}//return tmp;
}Date& Date::operator-=(int day)
{if (day < 0)return *this += -day;_day -= day;while (_day <= 0){if (_month == 1){_month = 13;_year--;}_day += GetDay(_year, _month - 1);_month--;}return *this;
}Date Date::operator-(int day)
{Date tmp = *this;tmp -= day;return tmp;//Date tmp = *this;//tmp._day -= day;//while (tmp._day <= 0)//{// if (tmp._month == 1)// {// tmp._month = 13;// tmp._year--;// }// tmp._day += GetDay(tmp._year, tmp._month - 1);// tmp._month--;//}//return tmp;
}Date& Date::operator++()
{*this += 1;return *this;
}Date Date::operator++(int)
{Date tmp = *this;*this += 1;return tmp;
}Date& Date::operator--()
{*this -= 1;return *this;
}Date Date::operator--(int)
{Date tmp = *this;*this -= 1;return tmp;
}int Date::operator-(const Date& d)
{Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int cnt = 0;while (min != max){++min;++cnt;}return cnt * flag;
}ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}istream& operator>>(istream& in, Date& d)
{while (1){cout << "请依次输入年月日:";in >> d._year >> d._month >> d._day;if (!d.CheckDate())cout << "非法日期,请重新输入!" << endl;elsebreak;}return in;
}
test.cpp
#include "Date.h"
int main()
{//重载等于 ==//Date d1(2023, 3, 4);//Date d2(2023, 5, 6);//cout << (d1 == d2) << endl;//重载日期加等天数//Date d1(2025, 10, 20);//Date d2 = d1 += 100;//d1.Print();//d2.Print();//重载日期加天数//Date d1(2025, 10, 20);//Date d2 = d1 + 100;//d1.Print();//d2.Print();//重载大于 >//Date d1(2025, 10, 20);//Date d2(2025, 10, 21);//cout << (d1 > d2) << endl;//重载日期减天数//Date d1(2025, 1, 20);//Date d2 = d1 - 100;//d1.Print();//d2.Print();//重载日期减等天数//Date d1(2025, 4, 26);//Date d2 = d1 -= -1000;//d1.Print();//d2.Print();//重载前置后置加加 ++//Date d1(2025, 4, 26);//Date d2 = ++d1;//d1.Print();//d2.Print();//cout << endl;//Date d3(2025, 4, 26);//Date d4 = d3++;//d3.Print();//d4.Print();//重载前置后置减减 --//Date d1(2025, 4, 26);//Date d2 = --d1;//d1.Print();//d2.Print();//cout << endl;//Date d3(2025, 4, 26);//Date d4 = d3--;//d3.Print();//d4.Print();//重载日期 - 日期//Date d1(1979, 6, 3);//Date d2(2006, 7, 3);//cout << d2 - d1 << endl;//重载<< //将其放在类中重载为成员函数的情况//Date d1(2025, 4, 26);//Date d2(2025, 10, 21);//d1 << cout;//d1.operator<<(cout);//重载 << 和 >>//利用友元函数Date d1(2025, 4, 26);Date d2(2025, 10, 21);cout << d1 << d2 << endl;operator<<(cout, d1);operator<<(cout, d2);cin >> d1 >> d2;cout << d1 << d2 << endl;return 0;
}
赋值运算符重载
赋值运算符重载是⼀个默认成员函数,⽤于完成两个已经存在的对象直接的拷⻉赋值,这⾥要注意跟拷⻉构造区分,拷⻉构造⽤于⼀个对象拷⻉初始化给另⼀个要创建的对象。
赋值运算符重载的特点:
- 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const 当前类类型引⽤,否则会传值传参会有拷⻉。
- 有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了⽀持连续赋值场景。
- 没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认拷⻉构造函数类似,对内置类型成员变量会完成值拷⻉ / 浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的赋值重载函数。
4.像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器⾃动⽣成的赋值运算符重载就可以完成需要的拷⻉,所以不需要我们显式实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器⾃动⽣成的赋值运算符重载完成的值拷⻉ / 浅拷⻉不符合我们的需求,所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。像MyQueue这样的类型内部主要是⾃定义类型Stack成员,编译器⾃动⽣成的赋值运算符重载会调⽤Stack的赋值运算符重载,也不需要我们显式实现MyQueue的赋值运算符重载。这⾥还有⼀个⼩技巧,如果⼀个类显式实现了析构并释放资源,那么他就需要显式写赋值运算符重载,否则就不需要。
Date.h的Date类中
Date& operator=(const Date& d);
Date.cpp中
Date& Date::operator=(const Date& d)
{if (*this != d) //如果是d1 = d1就是自己给自己赋值的情况,直接跳过该逻辑{_year = d._year;_month = d._month;_day = d._day;}return *this;
}
test.cpp中
int main()
{Date d1(2025, 4, 26);//⼀个对象拷⻉初始化给另⼀个要创建的对象Date d2 = d1; //拷贝构造Date d3(2024, 1, 22);//两个已经存在的对象直接的拷⻉赋值d1 = d3; //赋值运算符重载d1 = d1;//自己给自己赋值d1 = d2 = d3;//连续赋值return 0;
}
取地址运算符重载
const成员函数
- 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后⾯。
- const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。 const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this变为const Date* const this
Date.h
//只要不改变调用对象的函数都建议加const//void Print(const Date* const this) const;void Print() const;
Date.cpp
void Date::Print() const
{cout << _year << "/" << _month << "/" << _day << endl;
}
总结:只要不改变调用对象的函数都建议加const,比如日期类中的日期加天数、日期减日期、比较大小的运算符重载等等都不改变调用对象,下面日期类的全部代码里能加const的基本都加了。
取地址运算符重载
取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动⽣成的就可以够我们⽤了,不需要去显式实现。除⾮⼀些很特殊的场景,⽐如我们不想让别⼈取到当前类对象的地址,就可以⾃⼰实现⼀份,胡乱返回⼀个地址。
Date.h
//取地址运算符重载Date* operator&(){//return this; //默认是这样写的//return nullptr; //让对方取到空地址return (Date*)0x0012ff40; //让对方取到我们随便编的地址}const Date* operator&() const{//return this;//return nullptr;return (const Date*)0x135222ff;}
test.cpp
int main()
{Date d1;const Date d2;cout << &d1 << endl;cout << &d2 << endl;return 0;
}
日期类全部代码
Date.h
#pragma once
#include <iostream>
#include <cassert>
using namespace std;class Date
{
public://友元函数声明friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);bool CheckDate();//只要不改变调用对象的函数都建议加const//void Print(const Date* const this) const;void Print() const;Date(int year = 1, int month = 1, int day = 1);//Date& operator=(const Date& d);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;//直接定义类⾥⾯,他默认是inline,频繁调⽤int GetDay(int year, int month){assert(year > 0 && month > 0 && month < 13);static int Month[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if (month == 2 && ((year % 100 != 0 && year % 4 == 0) || year % 400 == 0))return 29;elsereturn Month[month];}Date& operator+=(int day);Date operator+(int day) const;Date& operator-=(int day);Date operator-(int day) const;Date& operator++();//前置Date operator++(int);//后置Date& operator--();Date operator--(int);int operator-(const Date& d) const;//取地址运算符重载Date* operator&(){//return this;//return nullptr;return (Date*)0x0012ff40;}const Date* operator&() const{//return this;//return nullptr;return (const Date*)0x135222ff;}private:int _year;int _month;int _day;
};ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
Date.cpp
#include "Date.h"void Date::Print() const
{cout << _year << "/" << _month << "/" << _day << endl;
}bool Date::CheckDate()
{if (_month < 1 || _month > 12 || _year < 1|| _day < 1 || _day > GetDay(_year, _month))return false;elsereturn true;
}Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;//if (!this->CheckDate())if (!CheckDate())cout << "日期非法->" << *this << endl;
}//Date& Date::operator=(const Date& d)
//{
// if (*this != d) //如果是 d1 = d1 的情况,直接跳过该逻辑
// {
// _year = d._year;
// _month = d._month;
// _day = d._day;
// }
//
// return *this;
//}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 _year > d._year;else if (_month != d._month)return _month > d._month;elsereturn _day > d._day;
}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)
{if (day < 0)return *this -= -day;_day += day;while (_day > GetDay(_year, _month)){_day -= GetDay(_year, _month);_month++;if (_month > 12){_year++;_month = 1;}}return *this;
}Date Date::operator+(int day) const
{Date tmp = *this;tmp += day;return tmp;//Date tmp = *this;//tmp._day += day;//while (tmp._day > GetDay(tmp._year, tmp._month))//{// tmp._day -= GetDay(tmp._year, tmp._month);// tmp._month++;// if (tmp._month > 12)// {// tmp._year++;// tmp._month = 1;// }//}//return tmp;
}Date& Date::operator-=(int day)
{if (day < 0)return *this += -day;_day -= day;while (_day <= 0){if (_month == 1){_month = 13;_year--;}_day += GetDay(_year, _month - 1);_month--;}return *this;
}Date Date::operator-(int day) const
{Date tmp = *this;tmp -= day;return tmp;//Date tmp = *this;//tmp._day -= day;//while (tmp._day <= 0)//{// if (tmp._month == 1)// {// tmp._month = 13;// tmp._year--;// }// tmp._day += GetDay(tmp._year, tmp._month - 1);// tmp._month--;//}//return tmp;
}Date& Date::operator++()
{*this += 1;return *this;
}Date Date::operator++(int)
{Date tmp = *this;*this += 1;return tmp;
}Date& Date::operator--()
{*this -= 1;return *this;
}Date Date::operator--(int)
{Date tmp = *this;*this -= 1;return tmp;
}int Date::operator-(const Date& d) const
{Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int cnt = 0;while (min != max){++min;++cnt;}return cnt * flag;
}ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}istream& operator>>(istream& in, Date& d)
{while (1){cout << "请依次输入年月日:";in >> d._year >> d._month >> d._day;if (!d.CheckDate())cout << "非法日期,请重新输入!" << endl;elsebreak;}return in;
}
相关文章:
C++类和对象(中)
类的默认成员函数 默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数。一个类,我们不写的情况下编译器会默认生成6个默认成员函数,C11以后还会增加两个默认成员函数,移动构造和移动赋值。默认成员函数 很重要&#x…...
(Go Gin)Gin学习笔记(五)会话控制与参数验证:Cookie使用、Sessions使用、结构体验证参数、自定义验证参数
1. Cookie介绍 HTTP是无状态协议,服务器不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否由同一个客户端发出Cookie就是解决HTTP协议无状态的方案之一,中文是小甜饼的意思Cookie实际上就是服务器保存在浏览器上的一段信息。浏览…...
Windows 10 环境二进制方式安装 MySQL 8.0.41
文章目录 初始化数据库配置文件注册成服务启停服务链接服务器登录之后重置密码卸载 初始化数据库 D:\MySQL\MySQL8.0.41\mysql-8.0.41-winx64\mysql-8.0.41-winx64\bin\mysqld -I --console --basedirD:\MySQL\MySQL8.0.41\mysql-8.0.41-winx64\mysql-8.0.41-winx64 --datadi…...
Day.js一个2k轻量级的时间日期处理库
dayjs介绍 dayjs是一个极简快速2kB的JavaScript库,可以为浏览器处理解析、验证、操作和显示日期和时间,它的设计目标是提供一个简单、快速且功能强大的日期处理工具,同时保持极小的体积(仅 2KB 左右)。 Day.js 的 API…...
SQL实战:05之间隔连续数问题求解
概述 最近刷题时遇到一些比较有意思的题目,之前多次遇到一些求解连续数的问题,这次遇到了他们的变种,连续数可以间隔指定的数也视为是一个完整的“连续”。针对连续数的这类问题我们之前讲的可以利用等差数列的思想来解决,然而现…...
Windows下Dify安装及使用
Dify安装及使用 Dify 是开源的 LLM 应用开发平台。提供从 Agent 构建到 AI workflow 编排、RAG 检索、模型管理等能力,轻松构建和运营生成式 AI 原生应用。比 LangChain 更易用。 前置条件 windows下安装了docker环境-Windows11安装Docker-CSDN博客 下载 Git下载…...
回归分析丨基于R语言复杂数据回归与混合效应模型【多水平/分层/嵌套】技术与代码
回归分析是科学研究特别是生态学领域科学研究和数据分析十分重要的统计工具,可以回答众多科学问题,如环境因素对物种、种群、群落及生态系统或气候变化的影响;物种属性和系统发育对物种分布(多度)的影响等。纵观涉及数…...
EasyRTC嵌入式音视频实时通话SDK技术,打造低延迟、高安全的远程技术支持
一、背景 在当今数字化时代,远程技术支持已成为解决各类技术问题的关键手段。随着企业业务的拓展和技术的日益复杂,快速、高效地解决远程设备与系统的技术难题变得至关重要。EasyRTC作为一款高性能的实时通信解决方案,为远程技术支持提供了创…...
webrtc ICE 打洞总结
要搞清webrtc ICE连接是否能成功 , 主要是搞懂NAT NAT 类型 简单来说 一 是本地的ip和端口 决定外部的 ip和端口(和目的Ip和端口无关) , (这种情况又分为 , 无限制,仅限制 ip , 限制ip和port , 也就是…...
AI开发者的Docker实践:汉化(中文),更换镜像源,Dockerfile,部署Python项目
AI开发者的Docker实践:汉化(中文),更换镜像源,Dockerfile,部署Python项目 Dcoker官网1、核心概念镜像 (Image)容器 (Container)仓库 (Repository)DockerfileDocker Compose 2、Docker 的核心组件Docker 引擎…...
4.30阅读
一. 原文阅读 Passage 7(推荐阅读时间:6 - 7分钟) In department stores and closets all over the world, they are waiting. Their outward appearance seems rather appealing because they come in a variety of styles, textures, and …...
区块链:跨链协的技术突破与产业重构
引言:区块链的“孤岛困境”与跨链的使命 区块链技术自诞生以来,凭借去中心化、透明性和安全性重塑了金融、供应链、身份认证等领域。然而,不同区块链平台间的互操作性缺失,如同“数据与价值的孤岛”,严重限制…...
Github 热点项目 Qwen3 通义千问全面发布 新一代智能语言模型系统
阿里云Qwen3模型真是黑科技!两大模式超贴心——深度思考能解高数题,快速应答秒回日常梗。支持百种语言互译,跨国客服用它沟通零障碍!打工人福音是内置API工具,查天气做报表张口就来。字) 1Qwen3 今日星标 …...
有状态服务与无状态服务:差异、特点及应用场景全解
有状态服务和无状态服务是在分布式系统和网络编程中常提到的概念,下面为你详细介绍: 一、无状态服务 无状态服务指的是该服务的单次请求处理不依赖之前的请求信息,每个请求都是独立的。服务端不会存储客户端的上下文信息,每次请…...
【网络入侵检测】基于源码分析Suricata的引擎日志配置解析
【作者主页】只道当时是寻常 【专栏介绍】Suricata入侵检测。专注网络、主机安全,欢迎关注与评论。 1. 概要 👋 Suricata 的引擎日志记录系统主要记录该引擎在启动、运行以及关闭期间应用程序的相关信息,如错误信息和其他诊断信息,…...
Attention层的FLOPs计算
前置知识 设矩阵 A 的维度为 mn,矩阵 B 的维度为 np,则它们相乘后得到矩阵 C 的维度为 mp。其中,C 中每个元素的计算需要进行 n 次乘法和 n−1 次加法。也就是说,总的浮点运算次数(FLOPs)约为 m p (2n) …...
支付APP如何做好网络安全防护
支付APP的网络安全防护需要从技术、管理、用户行为等多层面综合施策,以下为核心措施: 一、技术防御:构建安全底层 数据加密 传输加密:使用最新协议(如TLS 1.3)对交易数据加密&…...
Missashe考研日记-day31
Missashe考研日记-day31 0 写在前面 芜湖,五一前最后一天学习圆满结束,又到了最喜欢的放假环节,回来再努力了。 1 专业课408 学习时间:2h学习内容: OK啊,今天把文件系统前两节的内容全部学完了…...
二叉树的路径总和问题(递归遍历,回溯算法)
112. 路径总和 - 力扣(LeetCode) class Solution { private: bool traversal(TreeNode*cur,int count){if(!cur->left&&!cur->right&&count0){return true;}if(!cur->left&&!cur->right){return false;}if(cur-…...
Java学习计划与资源推荐(入门到进阶、高阶、实战)
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息文章目录 Java学习计划与资源推荐**一、筑基阶段(2-3个月)****二、进阶开发阶段(2个月)****三、高级突破阶段(2-3个月)****四、项目实战与竞…...
动态规划 -- 子数组问题
本篇文章中主要讲解动态规划系列中的几个经典的子数组问题。 1 最大子数组和 53. 最大子数组和 - 力扣(LeetCode) 解析题目: 子数组是一个数组中的连续部分,也就是说,如果一个数组以 nums[i]结尾,那么有两…...
Sehll编程的函数于数组
目录 一、函数 1.1、定义函数 1.2、查看、删除函数 1.3、函数的返回值 1.4、函数的参数传递 1.5、函数的作用范围 1.6、函数递归 二、数组 2.1、声明数组 2.2、数组格式定义 2.3、数组调用 2.4、删除数组 一、函数 shell编程中,函数用于封装一段可以重…...
flutter 专题 六十四 在原生项目中集成Flutter
概述 使用Flutter从零开始开发App是一件轻松惬意的事情,但对于一些成熟的产品来说,完全摒弃原有App的历史沉淀,全面转向Flutter是不现实的。因此使用Flutter去统一Android、iOS技术栈,把它作为已有原生App的扩展能力,…...
AI生成Flutter UI代码实践(一)
之前的杂谈中有提到目前的一些主流AI编程工具,比如Cursor,Copilot,Trea等。因为我是Android 开发,日常使用Android Studio,所以日常使用最多的还是Copilot,毕竟Github月月送我会员,白嫖还是挺香…...
spring boot中@Validated
在 Spring Boot 中,Validated 是用于触发参数校验的注解,通常与 JSR-303/JSR-380(Bean Validation)提供的校验注解一起使用。以下是常见的校验注解及其用法: 1. 基本校验注解 这些注解可以直接用于字段…...
VBA代码解决方案第二十四讲:EXCEL中,如何删除重复数据行
《VBA代码解决方案》(版权10028096)这套教程是我最早推出的教程,目前已经是第三版修订了。这套教程定位于入门后的提高,在学习这套教程过程中,侧重点是要理解及掌握我的“积木编程”思想。要灵活运用教程中的实例像搭积木一样把自己喜欢的代码…...
SpringBoot+EasyExcel+Mybatis+H2实现导入
文章目录 SpringBootEasyExcelMybatisH2实现导入1.准备工作1.1 依赖管理1.2 配置信息properties1.3 H2数据库1.4 Spring Boot 基础概念1.5 Mybatis核心概念 1.6 EasyExcel核心概念 2.生成Excel数据工具类-随机字符串编写生成Excel的java文件 3.导入功能并且存入数据库3.1 返回结…...
算法四 习题 1.3
数组实现栈 #include <iostream> #include <vector> #include <stdexcept> using namespace std;class MyStack { private:vector<int> data; // 用于存储栈元素的数组public:// 构造函数MyStack() {}// 入栈操作void push(int val) {data.push_back…...
el-tabs与table样式冲突导致高度失效问题解决(vue2+elementui)
背景 正常的el-table能根据父容器自动计算剩余高度,并会在列表中判断自适应去放出滚动条。而el-tabs本身就是自适应el-tab-pane内容的高度来进行自适应调节,这样就会导致el-table计算不了当前剩余的高度,所以当el-tabs里面包含el-table时&am…...
Access开发:轻松一键将 Access 全库表格导出为 Excel
hi,大家好呀! 在日常工作中,Access 常常是我们忠实的数据管家,默默守护着项目信息、客户列表或是库存记录。它结构清晰,录入便捷,对于许多中小型应用场景来说,无疑是个得力助手。然而ÿ…...
合并多个Excel文件到一个文件,并保留格式
合并多个Excel文件到一个文件,并保留格式 需求介绍第一步:创建目标文件第二步:创建任务列表第三步:合并文件第四步:处理合并后的文件之调用程序打开并保存一次之前生成的Excel文件第五步:处理合并后的文件之…...
使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第十讲)
这一期我们讲解demo中登录、ok按键的回调函数以及界面的美化,以下是上期界面的图片如图所示: 首先点击界面在右侧的工具栏中调配颜色渐变色,具体设置如下图所示: 然后是关于界面内框也就是容器的美化,具体如下图所示…...
论文笔记(八十二)Transformers without Normalization
Transformers without Normalization 文章概括Abstract1 引言2 背景:归一化层3 归一化层做什么?4 动态 Tanh (Dynamic Tanh (DyT))5 实验6 分析6.1 DyT \text{DyT} DyT 的效率6.2 tanh \text{tanh} tanh 和 α α α 的消融实验…...
Mysql之数据库基础
🌟 各位看官好,我是maomi_9526! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习Mysql的相关知识。 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更…...
shell(5)
位置参数变量 1.介绍 当我们执行一个shell脚本时,如果希望获取到命令行的参数信息,就可以使用到位置参数变量. 比如:./myshell.sh100 200,这就是一个执行shell的命令行,可以在myshell脚本中获取到参数信息. 2.基本语法 $n(功能描述:n为数字,$0代表命令…...
VARIAN安捷伦真空泵维修清洁保养操作SOP换油操作流程内部转子图文并茂内部培训手侧
VARIAN安捷伦真空泵维修清洁保养操作SOP换油操作流程内部转子图文并茂内部培训手侧...
动画震动效果
项目场景: 提示:这里简述项目相关背景: 在有的相关目中特别是在C端一般都要求做的炫酷一些,这就需要一些简易的动画效果,这里就弄了一个简易的震动的效果如下视频所示 让图标一大一小的震动视频 分析: 提…...
DB-GPT V0.7.1 版本更新:支持多模态模型、支持 Qwen3 系列,GLM4 系列模型 、支持Oracle数据库等
V0.7.1版本主要新增、增强了以下核心特性 🍀DB-GPT支持多模态模型。 🍀DB-GPT支持 Qwen3 系列,GLM4 系列模型。 🍀 MCP支持 SSE 权限认证和 SSL/TLS 安全通信。 🍀 支持Oracle数据库。 🍀 支持 Infini…...
C++23 std::invoke_r:调用可调用 (Callable) 对象 (P2136R3)
文章目录 引言背景知识回顾可调用对象C17的std::invoke std::invoke_r的诞生提案背景std::invoke_r的定义参数和返回值异常说明 std::invoke_r的使用场景指定返回类型丢弃返回值 std::invoke_r与std::invoke的对比功能差异使用场景差异 结论 引言 在C的发展历程中,…...
pymysql
参数(会导致SQL注入) import pymysql# 创建数据库连接 conn pymysql.connect(user "root",password "root",host "127.0.0.1",port 3306,database "test" )# 创建游标对象 cur conn.cursor(cursorpymysql.…...
基于Spring Boot + Vue 项目中引入deepseek方法
准备工作 在开始调用 DeepSeek API 之前,你需要完成以下准备工作: 1.访问 DeepSeek 官网,注册一个账号。 2.获取 API 密钥:登录 DeepSeek 平台,进入 API 管理 页面。创建一个新的 API 密钥(API Key&#x…...
Spring Boot集成Kafka并使用多个死信队列的完整示例
以下是Spring Boot集成Kafka并使用多个死信队列的完整示例,包含代码和配置说明。 1. 添加依赖 (pom.xml) <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId&…...
全面了解CSS语法 ! ! !
CSS(层叠样式表)是网页设计的灵魂之一,它赋予了网页活力与美感。无论是为一个简单的个人博客增添色彩,还是为复杂的企业网站设计布局,CSS都是不可或缺的工具。那么,CSS语法到底是什么样的呢?它背…...
Springboot使用ThreadLocal提供线程局部变量,传递登录用户名
文章目录 概述使用创建ThreadLocalUtil工具类在登录拦截器中使用ThreadLocal存储登录用户名在/userInfo接口中获取登录用户名 注意事项参考视频 概述 使用 创建ThreadLocalUtil工具类 utils/ThreadLocalUtil.java package org.example.utils;/*** ThreadLocal 工具类*/ Supp…...
排序算法——选择排序
一、介绍 「排序算法sortingalgorithm」用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用,因为有序数据通常能够被更有效地查找、分析和处理。 如图所示,排序算法中的数据类型可以是整数、浮点数、字符或字符串等。排序的判断规则可根据需求…...
AlphaFold蛋白质结构数据库介绍
AlphaFold Protein Structure Database (AlphaFold DB) 是 DeepMind + EMBL-EBI 合作开发的公开蛋白质结构预测数据库,是利用 AlphaFold2/AlphaFold3 AI模型 预测的全基因组级蛋白质三维结构库。 网址: https://alphafold.ebi.ac.uk 项目内容主办单位DeepMind + EMBL-EBI上线…...
Roboflow标注数据集
使用Roboflow进行标注 关键点标注目标检测标注图像分类标注分割标注 Roboflow是一款易于使用的在线 图像标注。 关键点标注 每个图像的标注包括: 1、边界框坐标(每个物品应该有一个边界框,用*[x1,y1,x2,y2]*格式即左上角和右下角点描述&…...
大厂经验:第三方包Paramunittest参数化 VS Unittest内置参数化文本管理器subtest
大厂经验:第三方包Paramunittest参数化 VS Unittest内置参数化文本管理器subtest 代码解析 Paramunittest 核心逻辑 paramunittest.parametrized((Testerr, test, Invalid Login or Password., test_login_admin is passed),(Sam, test, Invalid Login or Passwo…...
[特殊字符]适合五四青年节的SVG模版[特殊字符]
宝藏模版 往期推荐(点击阅读): 趣味效果|高大上|可爱风|年终总结I|年终总结II|循环特效|情人节I|情人节II|情人节IIII|妇女节I&…...
当插入排序遇上“凌波微步“——希尔排序的奇幻漂流
文章目录 一、排序江湖的隐藏高手二、分而治之的魔法1. 核心思想拆解2. 动态演示(脑补版) 三、C语言实现大揭秘代码要点解析: 四、性能分析与实战技巧1. 时间复杂度迷思2. 实测性能对比 五、为什么说它永不过时?六、进阶思考题 一…...