代码:
class date
{
public:
void init(int year, int month, int day)
{ //this指针不可以在形参和实参显式的写,但是可以在类里面显示的用
_year = year;//this->_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
void printpos()
{
cout << this << endl;
}
private://类只存变量;
int _year;//加_是为了好区分;
int _month;
int _day;
};
int main()
{
date d1;
d1.init(1, 2, 3);//d1.init(&d1,1, 2, 3);
cout << &d1 << endl;
d1.printpos();
return 0;
}
运行结果:
结果分析:
init函数实际上是void init(date* this,int year, int month, int day)编译器加上this指针数;this指针就是定义的类的地址;this指针不可以在实参和形参显式的写,但是可以显式调用;、
代码:
class a
{
public:
void print()
{
cout << "print()" << endl;
}
private:
int _a;
};
int main()
{
/*a* p = nullptr;
p->print();*///p对象成员函数的地址不存在对象里,而是存在代码区,所以直接那函数名字地址去代码区找,所以没有对p解引用;
a* p = nullptr;
(*p).print();//print依旧不在对象里,所以依旧不会对p解引用; //只有光写p->a不会崩;p->a=1会崩;
return 0;
}
运行结果:
代码分析:
p对象成员函数的地址不存在对象里,而是存在代码区,所以直接那函数名字地址去代码区找,所以没有对p解引用;是否在p解引用看访问的成员是否在p里面;
二、类的默认成员函数和成员;
1、构造函数;
定义:
构造函数是一个特殊的成员函数,名字与类名相同(date类构造函数 date(x,y)),创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
特点:
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
1. 函数名与类名相同。2. 无返回值。3. 对象实例化时编译器 自动调用 对应的构造函数。4. 构造函数可以重载。
date()
{
cout << "date()" << endl;
_year = 1;
_month = 1;
_day = 1;
}
//带参初始化;
date(int year, int month, int day)
{
cout << "date(int year, int month, int day)" << endl;
_year = year;
_month = month;
_day = day;
}
上面两个构造合并;
date(int year=1, int month=1, int day=1)//
{
cout << "date(int year=1, int month=1, int day=1)" << endl;
_year = year;
_month = month;
_day = day;
}
注意:如果通过无参构造函数创建对象时,对象后面不用跟括号(date d1),否则就成了函数声明(date d1());第一个和第三个语法上可以同时存在 ,但是使用的时候不可以;如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。默认构造的特点:
1、我们写它生成,我们写了它不生成;
2、内置类型:语言原生的类型(指针都是内置类型);自定义:类、结构;C++默认构造对内置类型不处理、处理自定义类型(C++支持声明给缺省值),会去调用这个成员的默认构造函数;什么是默认构造:无参的构造函数、全缺省的构造函数、我们不写编译器自动生成的构造函数都被称为默认构造函数(不传参就会调用),他们只能有一个;
2、析构函数;
概念:
通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
特性:
析构函数 是特殊的成员函数,其 特征 如下:1. 析构函数名是在类名前加上字符 ~ 。2. 无参数无返回值类型。3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载。4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数。(例如date类的析构函数是~date())。5、如果系统没有显示定义,会生成默认析构,对于内置类型不处理,自定义类型调用它的析构函数处理。
代码:
class x
{
public:
x(int a=1)
{
cout << "x()" << endl;
_a = a;
}
~x()
{
cout << "~x()" << endl;
}
private:
int _a;
};
int main()
{
x x1;
return 0;
}
运行结果:
结果分析:
构造和析构的最大特性是自动调用。
3、拷贝构造函数;
概念:
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。
特征:
拷贝构造函数也是特殊的成员函数,其 特征 如下:1. 拷贝构造函数 是构造函数的一个重载形式 。2. 拷贝构造函数的 参数只有一个 且 必须是类类型对象的引用 ,使用 传值方式编译器直接报错 , 因为会引发无穷递归调用。(如果是date(date d),那么d1(d2)会先d(d2),形成了新的拷贝构造,导致又要 d3(d2),无穷调用)。若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,会造成资源二次释放的问题。代码:
class stack//还可以定义成员函数; //类里面不受访问限定符的限制,访问限定符限制的是外面的人使用; { public://类一开始默认为私有的;struct一开始默认为公有的; stack(int n = 4) { cout << "stack()" << endl; cout << "stack(int n = 4)" << endl; if (n == 0) { a = (int*)malloc(sizeof(int) * 4); if (a == nullptr) { perror("malloc fail"); } size = 0; capa = 4; } else { a = (int*)malloc(sizeof(int) * n); if (a == nullptr) { perror("malloc fail"); } size = 0; capa = n; } } void push(int x) { if (size == capa) { size_t newcapa = capa == 0 ? 4 : 2 * capa; auto x1 = a; a = (int*)realloc(a, newcapa * (sizeof(int))); if (a == nullptr) { perror("realloc fail"); exit(-1); } capa = newcapa; } a[size++] = x; } void pop() { assert(size > 0); size--; } int top()//栈顶元素; { return a[size - 1]; } void print() { cout << size << endl << capa << endl; } stack(const stack& s)//解决了两次析构问题;(深拷贝); { //s不能改变;防止错误代码; cout << "stack(stack& s)" << endl; a= (int*)malloc(s.capa * (sizeof(int))); if (a == nullptr) { perror("realloc fail"); exit(-1); } memcpy(a, s.a,sizeof(int)*s.size); size = s.size; capa = s.capa; } ~stack() { cout << "~stack()" << endl; free(a); a=nullptr;//这行和下一行不写不算错,算是好习惯; size = capa = 0; } bool isempty() { //assert(size>0);// return size == 0; } private: int* a; int size ; int capa ; }; class date { public: date(int year=2024, int month=11, int day=11) { year_ = year; month_ = month; day_ = day; } void print() { cout << year_<<"/" << month_ << "/" << day_ << endl; } // date d2(d1); d是d1的别名,this是d2的指针; date(const date& d)//如果是date(date d),那么d1(d2)会先d(d2),形成了新的拷贝构造,导致又要 d3(d2),无穷调用; { //防止错误的改变,防止误伤; cout << "date(date& d)" << endl; year_ = d.year_; month_ = d.month_; day_ = d.day_; } ~date() { cout << "~date()" << endl; } private: int year_; int month_; int day_; }; void func1(date d) { d.print(); } void func2(stack& s)//s的作用域是main函数栈帧,但是问题是s变化s1也变化; { s.push(1); s.push(2); } int main() { date d1; func1(d1); stack s1; func2(s1);//会出现浅拷贝问题 stack s2(s1); //以下两种写法一样; date d2(d1); date d3 = d1; return 0; }
运行结果:
4、赋值运算符重载;
概念:
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字 operator 后面接需要重载的运算符符号 。函数原型: 返回值类型 operator 操作符 ( 参数列表 )注意:不能通过连接其他符号来创建新的操作符:比如 operator@重载操作符必须有一个类类型参数用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不 能改变其含义作为类成员函数重载时,其形参看起来比操作数数目少 1 ,因为成员函数的第一个参数为隐藏的 this.* :: sizeof ?: . 注意以上 5 个运算符不能重载。这个经常在笔试选择题中出现。
代码(以date类举例):
class date
{
public:
date(int year=2024, int month=11, int day=11)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year<<"/" << _month << "/" << _day << endl;
}
date(const date& d);
{
cout << "date(date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator<( const date& x2)//运算符重载可以写为全局函数,也可以写为类里面的成员函数;
{
if (_year < x2._year)
{
return true;
}
else if (_year == x2._year && _month < x2._month)
{
return true;
}
else if (_year == x2._year && _month == x2._month && _day < x2._day)
{
return true;
}
else
{
return false;
}
}
bool operator==(const date& x2)
{
return _year == x2._year && _month == x2._month && _day == x2._day;
}
bool operator<=(const date& x2)
{
return ((*this)<x2)|| ((*this) == x2);
}
bool operator>(const date& x2)
{
return !((*this) <= x2);
}
bool operator!=(const date& x2)
{
return !((*this) == x2);
}
~date()
{
cout << "~date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1(2023, 11, 11);
date d2(2024, 12, 12);
//为了对自定义类型进行比较,发明了运算符重载;
int i1 = 10;
int j1 = 100;
i1 < j1;//内置类型转为运算符指令;
cout << (d1< d2) << endl;//自定义类型转为运算符重载;
//cout << (operator<(d2, d1) )<< endl;//可以显示调用;
cout << (d1 < d2) << endl;//上面的自动转为下列公式;
cout << d1.operator<(d2) << endl;
cout << (d2 < d1) << endl;
cout << d2.operator<(d1) << endl;
cout << (d1 == d2)<<endl;
cout << (d1 != d2) << endl;
cout << (d1 <= d2) << endl;
cout << (d1 > d2) << endl;
return 0;
}
运行结果:
结果分析:
自定义类型原来不支持比较,写了运算符重载才支持;运算符重载不能改变操作符的操作数个数;写完运算符重载后可以显式调用也可以隐式调用。注意比较的时候操作数的左右顺序要和重载函数的顺序一致。
+=运算符和+运算符;
+=原来的数改变;+原来的数不改变;
代码:
int getmonthday(int year,int month)//得到每年每月的天数。
{
int monthday[] = { 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)))
{//先判断是不是2月;
monthday[2]+=1;
}
return monthday[month];
}
date& operator+=(int day)
{
_day += day;
while (_day > getmonthday(_year, _month))//多次循环,直到天数小于该月的天数;
{
_day -= getmonthday(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
//+=的时候返回的是*this,出了作用域还在,可以用引用返回;+返回的是d2,出了作用域就不在了,使用传值返回;
date operator+(int day)
{
date d2(*this);//不写这个是+=
//d2._day += day;
//while (d2._day > getmonthday(d2._year, d2._month))//多次循环,直到天数小于该月的天数;
//{
// d2._day -= getmonthday(d2._year,d2._month);
// d2._month++;
// if (d2._month > 12)
// {
// d2._year++;
// d2._month = 1;
// }
//}
//return d2;
d2 += day;
return d2;
}
代码分析:
+可以通过拷贝构造,然后复用+=;+=的时候返回的是*this,出了作用域还在,可以用引用返回;+返回的是d2,出了作用域就不在了,使用传值返回;
+=负数;
代码:
date& date::operator-=(int day)
{
if (day < 0)
{
return *this += (-day);
}
_day -= day;
while (_day < 1)
{
_month--;
if (_month < 1)
{
_year--;
_month = 12;
}
_day += getmonthday(_year, _month);
}
return *this;
}
date& date::operator+=(int day)
{
if (day < 0)
{
return *this -= (-day);
}
_day += day;
while (_day > getmonthday(_year, _month))
{
_day -= getmonthday(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
代码分析:
+=负值等于-=正值;
>>和<<运算符;
代码:
ostream& operator<<(ostream& out, 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<<d;打印的顺序d在后,所以不能将<<重载函数写在类里面;只能写在类外;由于cout<<d1<<d2,打印完d1之后还要打印d2,所以返回值是out。
前置++和后置++运算符;
代码:
date& date::operator++()
{
*this += 1;
return *this;
}
date date::date::operator++(int)//int 只是为了匹配;
{
date d(*this);
*this += 1;
return d;
}
代码分析:
后置++和前置++最后都是加1,但是返回值不同;
赋值运算符重载
赋值运算符重载格式
参数类型 : const T& ,传递引用可以提高传参效率返回值类型 : T& ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值检测是否自己给自己赋值返回 *this :要复合连续赋值的含义赋值重载如果没有显示定义;默认赋值重载函数:内置类型值拷贝,自定义类型调用赋值重载函数;
代码:
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}