C++初阶学习————类和对象(下)

类的6个默认成员函数

  • 构造函数
    • 总结
  • 析构函数
    • 总结
  • 拷贝构造
    • 总结
  • 赋值运算符重载
    • 1. 运算符重载
    • 2 赋值运算符重载
    • 总结
  • const修饰类的成员函数
  • 取地址操作符重载
    • 练习
    • 1.日期类的实现
    • 2.编译器对拷贝构造的优化

例如之前在学习栈用顺序表是现实会经常出现两个错误,一个是忘记初始化,另一个是我忘记释放内存造成内存泄漏;在C++中弥补了这一点,学完类的默认成员函数就可以解决大多数情况下这个问题

构造函数

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

class Data
{
public:
	void SetDate(int year, int month, int day)
 	{
 		_year = year;
 		_month = month;
		_day = day;
 	}	
 
 	void Display()
 	{
 		cout <<_year<< "-" <<_month << "-"<< _day <<endl;
 	}
 	
private:
	int _day;
	int _month;
	int _year;
};

int main()
{
 	Date d1;
 	d1.SetDate(2018,5,1);
 	d1.Display();
 
 return 0; 
}

上面代码中,可以用 SetDate 初始化,但是如果每次创建对象都需要调用该函数就会有些麻烦,所以在C++中就有了构造函数来替换这个工作

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

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。

代码示例如下

class Data
{
public:
	构造函数的形式1
	Data()
	{
		
	}
	
	构造函数的形式2
 	Data()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	
	构造函数的形式3
	Data(int year,int month,int day)
	{
		_day = day;
		_month = month;
		_year = year;
	}
	
	构造函数的形式4
	Data(int year = 1,int month = 1,int day = 1)
	{
		_day = day;
		_month = month;
		_year = year;
	}
	
 	void Display()
 	{
 		cout <<_year<< "-" <<_month << "-"<< _day <<endl;
 	}
 	
private:
	int _day;
	int _month;
	int _year;
};

注意:上面三种之许创建一种,这里只是示范不同的情况该如何调用
调用如下:

int main()
{
 	Date d1;			型式1,	实例化时自动调用,但是没有对成员进行改变
 	Date d2;			型式2					对成员赋默认值
 	Date d3(1,1,1);		型式3					对成员传参赋值
 	Date d4(1,1,1);		型式4	全缺省函数构造	传参时,参数对成员赋值
 	Date d4;			型式4	全缺省函数构造	不传参时,形参数默认值对成员赋值
 	return 0; 
}

在这里插入图片描述
在这里插入图片描述
C++初阶学习————类和对象(下)_第1张图片

上面代码提供了4个形式,但最建议用全缺省的形式

  1. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,是隐藏的,一旦用户显式定义编译器将不再生成
  2. 在C++中把类型分为两类:1.int double等内置类型也是基本类型,2.自定义类型如struct / class,所以默认生成的构造函数会有两种初始化处理:
    (1)当我们不创建构造函数时,编译器默认生成的构造函数对内置类型不做初始化处理,可以理解为默认生成一个上述代码型式1那样的空操作做,但是是隐藏的
    (2)对于自定义类型,会去调用他的无参(不用传参数就可以调用的)默认构造函数初始化,没有默认构造函数就会报错

代码验证如下:

class A
{
public :
	A()
	{
		cout << "A()" << endl;
		_a = 0;
	}
private:
	int _a;
};

class Data
{
public:
	Data(int year = 1,int month = 1,int day = 1)
	{
		//this->_day = day
		_day = day;
		_month = month;
		_year = year;
	}
	
 	void Display()
 	{
 		cout <<_year<< "-" <<_month << "-"<< _day <<endl;
 	}
 	
private:
	int _day;
	int _month;
	int _year;
	A temp;
};

int main()
{
	Data d1;
	return 0;
}

C++初阶学习————类和对象(下)_第2张图片
C++初阶学习————类和对象(下)_第3张图片
C++初阶学习————类和对象(下)_第4张图片
将A的构造函数改为全缺省

	A(int a = 10)
	{
		cout << "A()" << endl;
		_a = a;
	}

C++初阶学习————类和对象(下)_第5张图片

  1. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

总结

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,是隐藏的,一旦用户显式定义编译器将不再生成
  6. 在C++中把类型分为两类:1.int double等内置类型也是基本类型,2.自定义类型如struct / class,所以默认生成的构造函数会有两种初始化处理:
    (1)当我们不创建构造函数时,编译器默认生成的构造函数对内置类型不做初始化处理,可以理解为默认生成一个上述代码型式1那样的空操作做,但是是隐藏的
    (2)对于自定义类型,会去调用他的无参(不用传参数就可以调用的)默认构造函数初始化,没有默认构造函数就会报错
  7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

任何一个类都有三种默认构造函数:1.全缺省,2.无参,3.编译器默认生成, 对于自定义类型,如果没有无参的构造函数就会报错

析构函数

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

析构函数的特性:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。
  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

代码示例如下

class Data
{
public:
	Data(int year = 1,int month = 1,int day = 1)
	{
		_day = day;
		_month = month;
		_year = year;
	}
	
	~Data()
	{
		cout << "~Data()" << endl;
	}
	
 	
private:
	int _day;
	int _month;
	int _year;
	A temp;
};

int main()
{
	Data d1;
	return 0;
}

C++初阶学习————类和对象(下)_第6张图片

由输出结果可知,析构函数也是自动调用的
析构函数一般用于资源的清理,例如动态申请的内存,结束之前就需要释放,就可以写到析构函数里

例如对栈的初始化和释放函数接口进行改写,代码如下:

class Stack
{
public:
	构造函数
	Stack(int capacity = 4)
	{
		_arr = (int*)malloc(sizeof(int)*capacity);
		if(_arr == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		_capacity = capacity;
		_top = 0;
	}
	
	析构函数
	~Stack()
	{
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};

5.和构造函数一样,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。
如果我们不写析构函数,他会自动生成并调用自己的析构函数
(1)对于内置类型,但是是空处理也可以说不做任何处理
(2)对于自定义类型会去调用他的析构函数(可能是默认的也可能是自己传创建的)
对于自定义类型,如果他的成员变量都是在栈帧里的,那么就可以使用默认生成的析构函数,若成员变量对动态申请内存,那就必须要自己创建析构函数

总结

  1. 析构函数名是在类名前加上字符 ~
  2. 无参数无返回值
  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
  5. 和构造函数一样,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。
    如果我们不写析构函数,他会自动生成并调用自己的析构函数
    (1)对于内置类型,但是是空处理也可以说不做任何处理
    (2)对于自定义类型会去调用他的析构函数(可能是默认的也可能是自己创建的)
    对于自定义类型,如果他的成员变量都是在栈帧里的,那么就可以使用默认生成的析构函数,若成员变量对动态申请内存,那就必须要自己创建析构函数

构造函数的顺序是按实例化顺序来的,而析构函数的顺序就是逆序的,最后构造的最先析构,且构造函数是全局在前局部在后,析构时是全局在后局部在前(可以看成栈)

拷贝构造

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

拷贝构造的特征:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

验证如下
传值时,形参是实参的临时拷贝,形参可以看成是个临时变量,是会占用同样大小的内存的
C++初阶学习————类和对象(下)_第7张图片

传引用就是对实参取别名,形参就是实参的别名,并且之前对引用的介绍是:在语法层,引用不会去新申请空间(不占用空间);所以引用只是实参临时多了个别名,不会占用空间,可直接操作到实参本身,就不存在传值传参的拷贝死循环了

所以正确的写法应该是:

class Data
{
public:
	Data(int year = 1,int month = 1,int day = 1)
	{
		_day = day;
		_month = month;
		_year = year;
		cout << "Data(int year = 1,int month = 1,int day = 1)" << endl;
	}
	
	拷贝构造
	建议加上const
	Data(const Data& d)  一定是传引用,传值会造成无限递归
	{
		//this->_year = d._year
		 _year = d._year; 
		_month = d._month;
		_day = d._day;
	}
 	
	~Data()
	{
		cout << "~Data()" << endl;
	}

private:
	int _day;
	int _month;
	int _year;
};

int main()
{
	Data d1;
	Data d2(d1);
	return 0;
}

在这里插入图片描述

  1. 若未显示定义,系统生成默认的拷贝构造函数,默认生成的拷贝构造会按字节序拷贝(1个字节1个字节拷贝)
    默认生成的拷贝构造:
    1.内置成员,会完成按字节序的拷贝,也叫浅拷贝(带有动态内存的深拷贝需要我们自己实现
    2.自定义类型成员,会调用他的拷贝构造

代码验证如下
当成员都是普通内置类型时(浅拷贝)

class Data
{
public:
	Data(int year = 1,int month = 1,int day = 1)
	{
		_day = day;
		_month = month;
		_year = year;
		cout << "Data(int year = 1,int month = 1,int day = 1)" << endl;
	} 	
	~Data()
	{
		cout << "~Data()" << endl;
	}
private:
	int _day;
	int _month;
	int _year;
};
int main()
{
	Data d1;
	Data d2(d1);
	return 0;
}

C++初阶学习————类和对象(下)_第8张图片

但是当成员申请内存资源时就会出错(例如动态内存,深拷贝)
这里用原先学习的栈做示例(这里只是作为演示,有些接口函数没有写如push、pop等)

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_arr = (int*)malloc(sizeof(int)*capacity);
		if(_arr == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		_capacity = capacity;
		_top = 0;
	}
	~Stack()
	{
		free(_arr);
		_arr = NULL;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};

int main()
{
	Stack s1;
	Stack s2(s1);
	return 0;
}

C++初阶学习————类和对象(下)_第9张图片
请注意看,s2拷贝连同动态内存一起拷贝了,s1_arr和 s2_arr的指针都指向这同一块地址,那么可能就会造成以下几个问题
1.s1改变动态内存的数值,s2里就会被改变,相反也是如此
2.例如扩容时是top等于capacity,假如s1或s2中有一个扩容说明他的capacity和top相等了 ,但另一个的capacity和top并没有同步但被扩容了,可能会发生错误
3.在程序结束时,他们的析构函数会对这个动态内存释放两次,就会出错

总结

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
  3. 若未显示定义,系统生成默认的拷贝构造函数,默认生成的拷贝构造会按字节序拷贝(1个字节1个字节拷贝)
    默认生成的拷贝构造:
    1.内置成员,会完成按字节序的拷贝,也叫浅拷贝(带有动态内存的深拷贝需要我们自己实现
    2.自定义类型成员,会调用他的拷贝构造

赋值运算符重载

这里还用上面定义的日期类来演示

class Data
{
public:
	Data(int year = 1,int month = 1,int day = 1)
	{
		_day = day;
		_month = month;
		_year = year;
		cout << "Data(int year = 1,int month = 1,int day = 1)" << endl;
	}
private:
	int _day;
	int _month;
	int _year;
};

int main()
{
	Data d1;
	Data d2(d1);
	return 0;
}

1. 运算符重载

在默认情况下,C++是不支持自定义类型对象使用运算符的,但C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似

函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

运算符重载代码示例如下

d1 < d2

bool operator>(const Data& d1,const Data& d2)
{
	if(d1._year > d2._year )
		return true;
	else if(d1._year == d2._year && d1._month > d2._month )
		return true;
	else if(d1._year == d2._year && d1._month == d2._month && d1._day > d2._day)
		return true;
	else
		false;
}

上面这种情况虽然是对的,但是比较麻烦,上面介绍类中存在一个this指针,那么就可以写成下面这种型式

//bool operator>(Data* const this,const Data& d)

bool operator>(const Data& d)
{
	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;
}

那么该如何使用呢?
开头介绍中,运算符重载是具有特殊函数名的函数,那么就可以把他放到类体中,当作成员函数一样

class Data
{
public:
	Data(int year = 1,int month = 1,int day = 1)
	{
		_day = day;
		_month = month;
		_year = year;
		cout << "Data(int year = 1,int month = 1,int day = 1)" << endl;
	}
	bool operator>(const Data& d)
	{
		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;
	}
private:
	int _day;
	int _month;
	int _year;
};

int main()
{
	Data d1;
	Data d2(d1);
	cout << (d1 < d2) << endl;
	return 0;
}
int main()
{
	Data d1(2,1,1);
	Data d2(1,1,1);
	
	if(d1 > d2)		//也可以写成这样d1.operator>(d2)
	{
		cout << "hello" << endl;
		cout << (d1 > d2) << endl;
	}
	return 0;
}

C++初阶学习————类和对象(下)_第10张图片
上面就是运算符重载的定义及调用型式了
注意:

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

2 赋值运算符重载

赋值运算符的定义型式和调用与运算符重载类似
赋值运算符主要有四点需要注意:

  1. 参数类型
  2. 返回值
  3. 检测是否自己给自己赋值
  4. 返回*this
  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝
Data d3;
d3 = d1;

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

以上就是基本的赋值运算符重载,但是在逻辑上还有些问题

int i,j,k;
i = j = k = 10;

10先给k赋值,k当右操作数给j赋值,j当右操作数给i赋值,所以当d3 = d2 = d1,d2应该变为右操作数 所以:

Data d3;
d3 = d2 = d1;
//d2 = d1
//d3 = d2

Data& operator=(const Data& d)
{
	if(this == &d)			防止自己给自己赋值
		return *this;
	d._year = _year;
	d._month = _month;
	d._day = _day;
	return *this;
}

在这里插入图片描述
先在operator=中给d2赋值,在返回d2的别名,d2的别名当做右值传参给d3赋值

形参传引用:
因为传值会生成临时拷贝,并且形参还会占用内存,而传引用就是对实参取别名,不会占用空间;对于基础类型差别不大,但是对于类(结构体)他们的大小不确定,传值既占用空间还浪费时间

返回值传引用:
假如是传返回值,那么会有两次拷贝构造,第一次是d1赋值给d2时的栈帧销毁, *this给临时变量时的拷贝构造,第二次是d2赋值给d3时的栈帧销毁,*this给临时变量拷贝构造。而引用返回,栈帧销毁 this还存在,返回的是this的别名,不会占用空间,不存在拷贝构造

所以相比传值,无论是传参还是传返回值,传引用都比传参效率高,传引用不会额外占用空间,也节省了拷贝时间,所以C++提倡是传引用的(返回值传引用前提是出了域返回值还存在)

赋值重载与拷贝构造的区别:

Data d1;
Data d2(d1);		拷贝构造是在即将实例化时使用的
Data d3 = d2;

Data d3;	
d3 = d2 = d1;		赋值重载是对已经存在的类使用的


总结

赋值运算符:

  1. 参数类型(传引用)
  2. 返回值(传引用)
  3. 检测是否自己给自己赋值(用this和传的地址进行判断)
  4. 返回*this (this代表左值的地址,返回的是左值的本身)
  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝
    (1)内置类型成员,按字节序拷贝(浅拷贝)
    (2)自定义类型成员,调用他的赋值重载函数

所以和拷贝构造类似,只有出现动态内存等才需要我们自己实现(深拷贝)

const修饰类的成员函数

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
请看代码验证:

class Data
{
public:
	Data(int year = 1,int month = 1,int day = 1)
	{
		_day = day;
		_month = month;
		_year = year;
		cout << "Data(int year = 1,int month = 1,int day = 1)" << endl;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _day;
	int _month;
	int _year;
};

int main()
{
	Data d1;
	d1.print();

	const Data d2;
	d2.print;
	return 0;
}

C++初阶学习————类和对象(下)_第11张图片
在类的实例化对象前面加上const,代表 const Data* const this
而调用成员函数的参数是:Data* const this,成员函数的权限是可读可写的(权限放大)
针对这种情况,C++的解决办法是

	void print()const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

在成员函数后面 加上const 等于 const Data const this*
加了const后,普通对象也可以调用(权限缩小),const修饰的对象也可以调用,所以对于不需要修改值的成员函数建议都加上const

取地址操作符重载

一般的运算符号,操作符都需要我们自己写,但是取地址一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容

int main()
{
	Data d1;
	cout << &d1 << endl;
	return 0;
}

不想让别人获取地址时
const Data* operator&() const
{
	return this;
}

练习

1.日期类的实现

写一个日期类,并实现判断日期大小、赋值、比较等

#ifndef DATA_H
#define DATA_H

#ifndef DATA_H
#define DATA_H

#include 
using namespace std;

class Data
{
public:
	Data& operator=(const Data& d);
	bool operator==(const Data& d);
	bool operator!=(const Data& d);

	bool operator>(const Data& d);
	bool operator>=(const Data& d);

	bool operator<(const Data& d);
	bool operator<=(const Data& d);

	Data& operator+=(int day);
	Data operator+(int day);
	Data& operator++();
	Data operator++(int);

	Data& operator-=(int day);
	Data operator-(int day);
	Data operator--(int);	//后置
	Data& operator--();		//前置

	对象 减 对象获取相差天数
	int operator-(const Data& d);
	Data(int year = 1,int month = 1,int day = 1);
	Data(const Data& d);
	打印
	void print();
	获取月份天数
	int GetMonthDay(int year,int month);
	获取星期
	void PrintWeekDay();
private:
	int _year;
	int _month;
	int _day;
};

#endif DATA_H
int Data::GetMonthDay(int year,int month)
{
	static int monthDayarr[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
	int day = monthDayarr [month];
	if( month == 2 && ((year % 4 == 0 && year % 100 != 0 ) || year % 400 == 0)) 或的括号不能省
		day++;
	return day;
}

Data::Data(int year,int month,int day)	缺省值定义和声明只能出现一次
{
	_year = year;
	_month = month;
	_day = day;
	if( !(year > 0 && (month > 0 && month <13) && (day > 0 && day < GetMonthDay(year,month) ) ))
	{
		cout << "日期非法" << endl;
	}
}
Data::Data(const Data& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

1. = 、== 、!=

Data& Data::operator=(const Data& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}
bool Data::operator==(const Data& d)
{
	if((_year == d._year) && (_month == d._month) && (_day == d._day))
		return true;
	else
		return false;
}
bool Data::operator!=(const Data& d)
{
	if(!(*this == d))
		return true;
	else
		return false;
}

2. >、 >=

bool Data::operator>(const Data& d)
{
	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 Data::operator>=(const Data& d)
{
	if( (*this > d) || (*this == d))
		return true;
	else
		return false;
}

3. < 、<=

bool Data::operator<(const Data& d)
{
	if(!(*this >= d))
		return true;
	else
		return false;
}
bool Data::operator<=(const Data& d)
{
	if(!( *this > d))
		return true;
	else
		return false;
}

5.+、+=、++前置、后置++

Data& Data::operator+=(int day)
{
	_day += day;
	while(_day > GetMonthDay(_year,_month))
	{
		_day -= GetMonthDay(_year,_month);
		_month++;
		if(_month == 13)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}
Data Data::operator+(int day)
{
	Data temp = *this;
	temp += day;
	return	temp;
}
Data& Data::operator++()
{
	*this += 1;
	return *this;
}
Data Data::operator++(int)
{
	Data temp = *this;
	*this += 1;
	return temp;
}

6.-、-=、前置–、–后置

Data& Data::operator-=(int day)
{
	if(day < 0)
	{
		*this += -day;
	}
	_day -= day;
	while(_day <= 0)
	{
		_month--;
		if(_month == 0)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year,_month);
	}
	return *this;
}
Data Data::operator-(int day)
{
	Data temp = *this;
	temp -= day;
	return temp;
}
Data& Data::operator--()
{
	*this -= 1;
	return *this;
}
Data Data::operator--(int)
{
	Data temp = *this;
	*this -= 1;
	return temp;
}

7.获取当前日期的星期

int Data::operator-(const Data& d)
{
	Data max = *this;
	Data min = d;
	int day = 0;
	if(*this < d)
	{
		max = d;
		min = *this;
	}
	
	while(max != min)
	{
		day++;
		++min;		尽量用前置
	}
	return day;
}

void Data::PrintWeekDay()
{
	static char* week[] = {"星期一","星期二","星期三","星期四","星期五","星期六","星期日"};
	Data temp(1900,1,1);
	int Getweek = *this - temp;
	cout << week[Getweek%7] << endl;
}

8.流插入和流提取
这里先介绍格式,但涉及到友元,请看下一篇


class Data
{
	 
public:
	friend ostream& operator<<(ostream& out,const Data& d);
	friend istream& operator>>(istream& in,Data& d);
..........
..........
..........
private:
	int _year;
	int _month;
	int _day;
};


ostream& operator<<(ostream& out,const Data& d)
{
	cout << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}


istream& operator>>(istream& in,Data& d)
{
	in >> d._year >> d._month >> d._day ;
	return in;
}
#ifndef DATA_H
#define DATA_H

#include 
using namespace std;

class Data
{
	 
public:
	friend ostream& operator<<(ostream& out,const Data& d);
	friend istream& operator>>(istream& in,Data& d);

	Data(int year = 1,int month = 1,int day = 1);
	Data(const Data& d);
	//void print();
	int GetMonthDay(int year,int month);

	Data& operator=(const Data& d);
	bool operator==(const Data& d);
	bool operator!=(const Data& d);

	bool operator>(const Data& d);
	bool operator>=(const Data& d);

	bool operator<(const Data& d);
	bool operator<=(const Data& d);

	Data& operator+=(int day);
	Data operator+(int day);
	Data& operator++();
	Data operator++(int i);

	Data& operator-=(int day);
	Data operator-(int day);
	Data operator--(int i);	//后置
	Data& operator--();		//前置

	int operator-(const Data& d);	
	void PrintWeekDay();
private:
	int _year;
	int _month;
	int _day;
};

#endif DATA_H

容易遇到的错误:
缺省值在声明和定义中都出现;
1.++ 、-- 的前置后置;
2.+=、-= 和 + 、 - 的混淆;
3.传参传值 和 反回值在调用结束后不销毁的情况下传值;
4.逻辑操作省括号;

刚开始学C++时,面对传参和返回值需要多使用什么(如指针、引用、值)
对于类的运算符重载中,后置++和 - - 要尽量少用,因为他会临时实例化一个对象,并在返回时返回的是这个临时对象的值,相对于前置操作,会先调用构造函数,返回时又会调用拷贝构造放到临时变量中

2.编译器对拷贝构造的优化

class A
{
public:
	A()
	{
		//cout << "A()" << endl;
		//count++;
	}
	A(A& a)
	{
		cout << "A(A& a)" << endl;
		count++;
	}
	static int get_count()
	{
		return count;
	}
private:
	int a;
	static int count;
};
int A::count = 0;
A f(A u)
{
	A b1(u);
	A b2 = b1;
	return b2;
}
int main()
{
	A a1;
	A a2 = f(f(a1));
	cout << A::get_count() <<endl;
	return 0;
}

上面这段代码在VS中,一共执行了7次拷贝构造
C++初阶学习————类和对象(下)_第12张图片

你可能感兴趣的:(c++,学习,开发语言)