类和对象(中)

目录

6个默认成员函数

构造函数

特性 

析构函数 

特性 

拷贝构造函数 

特性 

赋值重载 

运算符重载

const成员 

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

完整日期类实现


6个默认成员函数

默认成员函数,若我们不显示定义,编译器会自动产生默认成员函数。

构造函数

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

特性 

特性:

1.名字与类名相同

2.无返回值(可以带参数)

3.对象实例化时编译器自动调用对应的构造函数

4.构造函数可以重载 

class Date
{
public:
	// 1.无参构造函数
	Date()
	{}

	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		
	/* 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
	用户显式定义编译器将不再生成。*/
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
void TestDate()
{
	Date d1; // 调用无参构造函数
	Date d2(2015, 1, 1); // 调用带参的构造函数

	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	Date d3();//声明d3函数,不是调用构造函数!
}

如果我们不写构造函数,会发生什么呢? 

不写构造函数,编译器默认生成构造函数。

编译器默认生成的构造函数对于内置类型不进行处理,初始化

对于自定义类型,会调用它的默认构造函数,如果自定义类型没有默认构造函数,就报错

默认构造函数有三种:1.编译器默认生成 2.显示定义全缺省构造函数 3.显示定义无参构造函数。

注:默认构造函数只能存在一个!!!

写构造函数: 

class A
{public:
	A()//默认构造函数
	{
		_a = 0;
		cout << "A()" << endl;
	}
private:
	int _a;
};
class Date
{
public:
	// 1.无参构造函数
	Date()
	{
		_year = 0;
		_month = 0;
		_day = 0;
		cout << "Date()" << endl;
	}
	// 2.带参全缺省构造函数
	/*Date(int year=10, int month=10, int day=10)
	{
		_year = year;
		_month = month;
		_day = day;
	}*/
	//3.不显示定义,编译器默认生成
	//这3种默认构造函数只能存在一个!!!(否则编译器不知道调用哪个)
private:
	//内置类型
	//如果不写构造函数,编译器生成的默认构造函数不对内置类型处理
	int _year;
	int _month;
	int _day;
	//自定义类型
	A _aa;//调用自己的默认构造函数
};
void testdate()
{
	Date d1;
}
int main()
{
	testdate();
	return 0;
}

 

不写构造函数

class A
{public:
	A()//默认构造函数
	{
		_a = 0;
		cout << "A()" << endl;
	}
private:
	int _a;
};
class Date
{
public:
	// 1.无参构造函数
	/*Date()
	{
		_year = 0;
		_month = 0;
		_day = 0;
		cout << "Date()" << endl;
	}*/
	// 2.带参全缺省构造函数
	/*Date(int year=10, int month=10, int day=10)
	{
		_year = year;
		_month = month;
		_day = day;
	}*/
	//3.不显示定义,编译器默认生成
	//这3种默认构造函数只能存在一个!!!(否则编译器不知道调用哪个)
private:
	//内置类型
	//如果不写构造函数,编译器生成的默认构造函数不对内置类型处理
	int _year;
	int _month;
	int _day;
	//自定义类型
	A _aa;//调用自己的默认构造函数
};
void testdate()
{
	Date d1;
}

 

 通过调试窗口和输出,验证了上面的说法。

析构函数 

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

特性 

1. 析构函数名是在类名前加上字符 ~

2. 无参数无返回值类型

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。 

析构函数的调用规则与构造函数类似。

不写析构函数,编译器默认生成。

对于内置类型不进行处理

对于自定义类型,会调用它的析构函数

如果自定义类型也没有自己的析构函数,它的默认析构函数不会对其完成清理。

所以凡是涉及到动态内存的,一定要写析构函数!否则内存泄漏

编译器默认生成的析构函数对于自定义类型会完成处理吗?

答案是否定的,它不会完成对资源的释放。 

class stack
{
public:
	
	stack(int capacity=4)
	{
		if (_capacity == 0)
		{
			_a =(int*)malloc(sizeof(int) * capacity);
			_capacity = capacity;
			_top = 0;
		}
	}
	~stack()
	{
		if (_a)
		{
			free(_a);
			_a = nullptr;
			_top = _capacity = 0;
			cout << "~stack()" << endl;
		}
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
void teststack()
{
	stack s;
	//出了test函数后会调用析构函数
}
int main()
{
	teststack();
	return 0;
}

 

拷贝构造函数 

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

特性 

1. 拷贝构造函数是构造函数的一个重载形式

2. 拷贝构造函数的参数只有一个必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用

注:写了拷贝构造却不写构造函数,编译器不会生成默认构造函数!

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

 

如果不写拷贝构造,编译器会生成默认拷贝构造函数。

对于内置类型,会完成按字节拷贝(也就是浅拷贝)

对于自定义类型,会调用它的拷贝构造,若没写,则也完成浅拷贝。

如果进行浅拷贝,对于内置类型,没有问题,但对于涉及到动态空间的自定义类型,会造成大问题! 

// 这里会发现下面的程序会崩溃掉?
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);
	Stack s2(s1);//s1和s2指向同一块空间,free两次,所以崩溃
	return 0;

 

通过调试窗口我们发现,s1,s2指向同一块空间,所以当main函数结束时,将对同一块空间调用两次析构函数,造成一块空间的多次释放!所以会崩溃。 

所以只进行浅拷贝会出问题,要自己写拷贝构造。实现深拷贝。

赋值重载 

运算符重载

c++为了增强代码可读性,引入了运算符重载,运算符重载是具有特殊函数名的函数。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意:不能通过连接其他符号来创建新的操作符:比如operator@

重载操作符必须有一个类类型参数

用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义

作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this指针

.*   ::   sizeof   ?:   . 注意以上5个运算符不能重载。 

1. 赋值运算符重载格式
参数类型:const A & ,传递引用可以提高传参效率
返回值类型:A & ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回 * this :要复合连续赋值的含义 

2. 赋值运算符只能重载成类的成员函数不能重载成全局函数

原因:赋值运算符如果不显式实现编译器会生成一个默认的。此时用户再在类外自己实现
一个全局的赋值运算符重载,就和编译器在类中生成默认赋值运算符重载冲突了,故赋值
运算符重载只能是类的成员函数。

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

Date& Date::operator=(const Date& d)//返回值方便连续=
 {
	 if (*this != d)
	 {//自己给自己赋值,直接跳过!
		 _year = d._year;
		 _month = d._month;
		 _day = d._day;
	 }
	 return *this;
 }
void test()
{
	Date d1(2022, 12, 11);
	Date d2=d1;//与d2(d1)相同,调用拷贝构造!因为d2是刚创建的新对象
	Date d3; 
	d3 = d2 = d1;//赋值重载,d1,d2,d3均已存在
	d1.Print();
	d2.Print();
	d3.Print();
}

 

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必 须要实现。 

总结:

赋值重载与拷贝构造类似。

如果不写,编译器默认生成

对于内置类型,完成浅拷贝,

对于自定义类型,调用它自己的函数

如果自定义类型也未写,则也进行浅拷贝。 

const成员 

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

 

void Print()const;//形参为 const Date const*this
void Print();//形参为Date const*this

思考下面几个问题:

1. const对象可以调用非const成员函数吗?

2. 非const对象可以调用const成员函数吗?

3. const成员函数内可以调用其它的非const成员函数吗?

4. 非const成员函数内可以调用其它的const成员函数吗? 

以Date中Print为例子:

1.const对象不可以调用非const函数,会发生权限放大!

例:const对象传给非const的Print,相当于 const Date*this传给Date const*this 

这里发生了权限的放大!所以不能调用。

2. 非const对象可以调用const成员函数。发生权限缩小。

权限小可以接受权限大的!

3.const成员函数内可以调用其它的非const成员函数

4.非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 ; // 日
};

完整日期类实现

.h

#include<iostream>
#include<assert.h>
using namespace std;
class Date

{
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	void Print()const;
	// 获取某年某月的天数

	int GetMonthDay(int year, int month)const;

	// 全缺省的构造函数

	Date(int year = 1900, int month = 1, int day = 1);

	// 拷贝构造函数

  // d2(d1)

	 Date(const Date& d);

	// 赋值运算符重载

  // d2 = d3 -> d2.operator=(&d2, d3)

	Date& operator=(const Date& d);

	// 析构函数
	~Date();

	// 日期+=天数
	Date& operator+=(int day);

	// 日期+天数
	Date operator+(int day)const;

	// 日期-天数
	Date operator-(int day)const;

	// 日期-=天数
	Date& operator-=(int day);

	// 前置++
	Date& operator++();

	// 后置++
	Date operator++(int);

	// 后置--
	Date operator--(int);

	// 前置--
	Date& operator--();

	// >运算符重载

	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;

	// 日期-日期 返回天数

	int operator-(const Date& d)const;
	int Dateweekday()const;
	/*void operator<<(ostream& out);*/ //不能在局部定义

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

.cpp

#include"日期类.h"
void Date:: Print()const
{
	if (_year<1 ||
		_month < 1 ||
		_month>12 ||
		_day>GetMonthDay(_year, _month)
		|| _day < 1)
	{
		cout << "非法日期";
	}
	cout << _year << '/' << _month << '/' << _day << endl;
}
Date::Date(int year, int month, int day)//默认构造函数 初始化
//缺省参数 不能同时在声明和定义存在
//建议存放在声明中
{
		_year = year;
		_month = month;
		_day = day;
}
 Date::~Date()
{
	 _year = 0;
	 _month = 0;
	 _day = 0;
}
 Date::Date(const Date& d)
 {
	 _year = d._year;
	 _month = d._month;
	 _day = d._day;
	 //cout << "拷贝构造" << endl;
 }
 int Date:: GetMonthDay(int year, int month)const
 {
	 assert(year > 0 && month>0&&month<13);//禁止不合法
	 static int arr[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;
	 }
	 return arr[month];
 }
 Date& Date::operator=(const Date& d)//返回值方便连续=
 {
	 if (*this != d)
	 {//自己给自己赋值,直接跳过!
		 _year = d._year;
		 _month = d._month;
		 _day = d._day;
	 }
	 return *this;
 }
 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);
 }
 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 || *this == d;
 }
 bool Date:: operator <= (const Date& d)const
 {
	 return *this < d || *this == d;
 }
 bool Date:: operator != (const Date& d)const
 {
	 return !(*this == d);
 }
 
 Date&Date::operator+=(int day)
 {
	 if (day < 0)
	 {
		 day = (-1) * day;
	 }
	 _day += day;
	 while ( _day>GetMonthDay(_year,_month))
	 {
		 _day -= GetMonthDay(_year, _month);
		 _month++;
		 if (_month == 13)
		 {
			 _month = 1;
			 _year++;
		 }
	 }
	 return *this;
 }
 Date Date::operator+(int day)const
 {
	 Date ret = *this;
	 ret += day;
	 //ret为临时变量,会被销毁,所以不能引用返回!
	 return ret;
 }

 Date Date::operator-(int day)const
 {
	 Date ret = *this;
	 return ret -= day;
 }
 Date& Date::operator-=(int day)
 {
	 if (day < 0)
	 {
		 day = (-1) * day;
	 }
	 _day -= day;
	 while (_day<0)
	 {
		 _month--;
		 if (_month == 0)
		 {
			 _month = 12;
			 _year--;
		 }
		 _day += GetMonthDay(_year, _month);
	 }
	 return *this;
 }
 Date& Date::operator++()//前置加加
 {
	 _day += 1;
	 if (_day > GetMonthDay(_year, _month))
	 {
		 _day -= GetMonthDay(_year, _month);
		 _month++;
		 if (_month == 13)
		 {
			 _month = 1;
			 _year++;
		 }
	 }
	 return *this;
 }
 Date Date:: operator++(int)//后置加加
 {
	 Date ret = *this;
	 ++* this;
	 return ret;
 }
 Date Date::operator--(int)
 {
	 Date ret = *this;
	 --* this;
	 return ret;
 }
 Date&Date:: operator--()
 {
	 _day -= 1;
	 while(_day < 1)
	 {
		 _month--;
		 if (_month == 0)
		 {
			 _month = 12;
			 _year--;
		 }
		 _day += GetMonthDay(_year, _month);
	 }
	 return *this;
 }
 int Date:: operator-(const Date& d)const//日期-日期
 {
	 int flag = 1;
	 Date min = d;
	 Date max = *this;
	 if (max < min)
	 {
		 max = d;
		 min = *this;
		 flag = -1;
	 }int count = 0;
	 while (max>min)
	 {
		 --max;
		 count++;
	 }
	 return flag * count;
 }
 int Date:: Dateweekday()const
 {
	 static Date tmp(2022, 12, 5);
	 int t = *this - tmp;
	 if (t < 0)
	 {
		 t = -t;
	 }
	 return (t % 7) + 1;
 }
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;
}

本篇文章就梳理到这里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嚞譶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值