C++类和对象(中)

类的六个默认成员函数

默认成员函数的概念

我们看这样一个类:

class ClassName {

};

这个类里面没有成员函数,也没有成员变量,那么他是不是什么都没有呢?是一个空类吗?答案是并不是,当你创建一个类的时候,就算你什么都不写,编译器会自动帮你创建六个默认的成员函数,称为默认成员函数,当需要用到时可以对其进行覆盖重写
是哪六个成员函数呢?它们的作用是什么?

  1. 构造函数。初始化对象。
  2. 析构函数。清理工作,主要完成资源释放等。
  3. 拷贝构造。使用同类对象初始化创建对象。
  4. 运算符重载。主要是把一个对象赋值给另一个对象
  5. 取地址重载。取当前对象的地址。
  6. const对象取地址重载。取const对象的地址。

构造函数

构造函数名字和类名相同,创建对象的时候自动调用构造函数,完成初始化工作,并且只调用一次。

class Stack {
public:
	//普通成员函数
	void Init(int n=4) {
		a = (int*)malloc(sizeof(int) * n);
		if (a == NULL) {
			perror("malloc fail");
			exit(-1);
		}
		capacity = n;
		size = 0;
	}
private:
	int* a;
	int size;
	int capacity;
};
int main() {
	Stack s1;
	s1.Init();
	return 0;
}

上面这个栈类,初始化的时候需要调用Init()这个函数进行初始化,这个是完全没有必要的,这样的工作可以交给构造函数,例如。

class Stack {
public:
	//构造函数
	Stack(int n = 4) {
		a = (int*)malloc(sizeof(int) * n);
		if (a == NULL) {
			perror("malloc fail");
			exit(-1);
		}
		capacity = n;
		size = 0;
	}
private:
	int* a;
	int size;
	int capacity;
};

int main() {
	Stack s1;//无参构造调用
	Stack s2(10);//有参构造调用
	return 0;
}

创建类时构造函数会自动完成初始化,但是我们不创建构造函数时,系统会自动帮我们创建一个,但是成员变量也没有初始化,那么系统自动创建的构造函数是不是没用呢?其实也不是,系统默认的构造函数会自动初始化自定义类型(class,union,struct),对于系统内置类型不做处理,这其实是C++一个历史遗留的问题,也是设计的不好的地方之一。

class Stack {
public:
	Stack(int n = 4) {
		a = (int*)malloc(sizeof(int) * n);
		if (a == NULL) {
			perror("malloc fail");
			exit(-1);
		}
		capacity = n;
		size = 0;
	}
private:
	int* a;
	int size;
	int capacity;
};
class Queue {
public:

private:
	Stack s1;
	int a;
};

Queue这个类有两个成员函数,当这个类被实例化一个对象时,系统默认的构造函数会被调用,s1这个自定义成员变量会被调用Stack构造函数初始化,但是a这个成员变量系统不会处理,因为它是内置类型,有的编译器会初始化为0,但C++标准里面没有规定必须初始化,所以不能依赖编译器,这个问题C++11之后有一个对应的处理。

class Queue {
public:

private:
	Stack s1;
	int a=0;//在声明时给成员变量一个缺省值,如果没有初始化就会被赋值缺省值。
};

析构函数

当创建的一个对象出作用域时,会自动调用析构函数,以便于完成资源的集中清理。析构函数名是类名前面加~

class Stack {
public:
	Stack(int n = 4) {
		a = (int*)malloc(sizeof(int) * n);
		if (a == NULL) {
			perror("malloc fail");
			exit(-1);
		}
		capacity = n;
		size = 0;
	}
	void Destroy() {
		free(a);
		capacity = size = 0;
	}
private:
	int* a;
	int size;
	int capacity;
};
int main() {
	Stack s1;

	//....................
	s1.Destroy();
	return 0;
}

创建栈类我们需要用malloc开辟空间,最后对象销毁之前我们还需要调用成员函数进行销毁,析构函数帮我们解决了这个问题。

class Stack {
public:
	Stack(int n = 4) {
		a = (int*)malloc(sizeof(int) * n);
		if (a == NULL) {
			perror("malloc fail");
			exit(-1);
		}
		capacity = n;
		size = 0;
	}
	~Stack(){
		free(a);
		capacity = size = 0;	
	}
private:
	int* a;
	int size;
	int capacity;
};

int main() {
	Stack s1;//无参构造调用
	Stack s2(10);//有参构造调用
	return 0;
}

析构函数一般是malloc之后需要单独释放空间需要重载析构,其他情况基本不需要,析构函数只能重写一次。

拷贝构造

拷贝构造说的通俗一点就是用另一个相同类型的对象给自己赋值。完成一个浅拷贝。

class Date {
public:
	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	void show() {
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main() {
	Date d1(2012, 1, 1);
	Date d2(d1);
	d1.show();
	d2.show();
	return 0;
}

在这里插入图片描述

d2调用了系统默认生成的拷贝构造完成了对象的初始化,那么系统默认的拷贝构造是怎么实现的呢?

class Date {
public:
	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d) {
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void show() {
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

拷贝构造名字和类名相同,参数是引用类型,不能进行普通传值,否则会无限递归,因为对象只要有拷贝行为,就会自动调用拷贝构造。

在这里插入图片描述

使用值传递,形参是实参的拷贝,只要拷贝就会自动调用拷贝构造,只有引用传参才可以。

什么是深拷贝,什么是浅拷贝
上面我提到了系统默认生辰的拷贝构造会实现浅拷贝,就是按照成员变量的字节数去拷贝到另一个对象,如果成员变量有指针指向数组空间就会出错。

class Stack {
public:
	Stack(int n = 4) {
		a = (int*)malloc(sizeof(int) * n);
		if (a == NULL) {
			perror("malloc fail");
			exit(-1);
		}
		capacity = n;
		size = 0;
	}
	~Stack(){
		free(a);
		capacity = size = 0;	
	}
private:
	int* a;
	int size;
	int capacity;
};

栈这个类就必须实现深拷贝,浅拷贝会出错。假如给s2插入数据就会把s1的数据覆盖,s1调用析构函数,释放空间,s2不能再释放。
在这里插入图片描述
至于深拷贝,我会给大家一个后续的分享,这里不做过多介绍,拷贝构造一般情况之下只有需要深拷贝才会去重写实现,浅拷贝系统默认的拷贝构造可以完成。

运算符重载

赋值运算符重载

我们用普通内置类型时,可以用赋值运算符对其他变量进行赋值

int main() {
	int a = 10;
	int b = a;//使用赋值运算符赋值。
	return 0;
}

那么内置类型可以这样做,自定义类型是否可以呢?答案是可以的
在这里插入图片描述
这个其实是类里面有一个自动生成的成员函数(赋值运算符),帮我们做了这个事情,到底怎么写的我给大家演示。

class Date {
public:
	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d) {
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	//返回值是一个Date& 要符合连续赋值的原理,
	Date& operator=(const Date& d) {
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}
	void show() {
		std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

这就是类里面六个默认的成员函数之一,operator是运算符重载的关键字。默认生成的赋值重载函数和拷贝构造是一样的,按成员变量的字节数拷贝,只能完成浅拷贝,深拷贝还需要自己进行覆盖重写。

其他运算符重载

既然赋值运算符可以重写,那么其他的运算符是否也可以呢,大部分是可以的。例如:

//左操作数是*this,右操作数是d  (*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 _year != d._year || _month != d._month || _day != d._day;
}
//d1<d2
bool Date::operator<(const Date& 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 Date::operator<=(const Date& d) {
	return *this < d || *this == d;
}
bool Date::operator>(const Date& 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 Date::operator>=(const Date& d) {
	return *this > d || *this == d;
} 
Date& Date::operator+=(int day) {
	_day += day;
	while (_day > GetMonthDay(_year, _month)) {
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month > 12) {
			_year++;
			_month = 1;
		}
	}
	return *this;
}
Date Date::operator+(int day) {
	Date tem(*this);
	return tem += day;
}
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 n = 0;
	while (min != max) {
		++min;
		++n;
	}
	return n * flag;
}
Date& Date::operator-=(int day) {
	if (day < 0) {
		*this += -day;
		return *this;
	}
	_day -= day;
	while (_day <= 0) {
		--_month;
		if (_month == 0) {
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}
Date Date::operator-(int day) {
	Date tem(*this);
	tem -= day;
	return tem;
}
//前置++
Date& Date::operator++() {
	*this += 1;
	return *this;
}
//后置++,形参需要用一个int占位,这是C++规定
Date Date::operator++(int) {
	Date tem(*this);
	*this += 1;
	return tem;
}

这就是Date类实现的运算符重载,但是也不所有运算符都可以重载

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
    藏的this
  5. * :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出
    现。

取地址操作符重载

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

这个操作符也是类里面自动生成的,了解即可,工作时很少对这个运算符重载进行重写。

const取地址操作符重载

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

const修饰的变量取地址操作符重载,很少需要我们进行重写,了解即可。

const成员

在这里插入图片描述
我们发现d1变量被const修饰了,而调用show()这个成员函数时会报错,这是因为show里面有一个隐含的this指针,这个函数里面的指针没有被const修饰,也就导致了*this是可以修改的,但是d1不可以修改,这是一个权限放大的问题。如果const修饰的变量也想调用成员函数,就必须把成员函数里面的指针也修饰成const类型。
在这里插入图片描述

把const写在函数名后,一般const修饰不需要改变值得成员函数内,但是需要改变*this的成员函数不可以被const修饰,比如:

Date& Date::operator+=(int day) {
	_day += day;
	while (_day > GetMonthDay(_year, _month)) {
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month > 12) {
			_year++;
			_month = 1;
		}
	}
	return *this;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值