IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 《C++初阶之路》类和对象(中) -> 正文阅读

[C++知识库]《C++初阶之路》类和对象(中)

一、本章重点

  1. 六个默认成员函数
  2. 构造函数
  3. 析构函数
  4. 拷贝构造
  5. 赋值运算符重载
  6. const修饰成员函数
  7. 实现一个日期类
  8. 取地址及const取地址运算符重载

二、六个默认成员函数

我们写的每个类,编译器都会自动生成6个默认的成员函数。

分别是:

  1. 默认构造函数
  2. 默认析构函数
  3. 拷贝构造(浅拷贝/值拷贝)
  4. 赋值运算符重载
  5. 取地址运算符重载(一般不用)
  6. 加了const的取地址运算符重载(一般不用)

本篇文章,我们将分别介绍这6个默认的成员函数。

三、默认构造函数

首先介绍一下,什么是构造函数?

构造函数就是一个特殊的成员函数,在创建对象时,编译器会自动调用,完成对象初始化工作。

构造函数的特性:

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

?构造函数可以重载

#include<iostream>
using namespace std;
class Date
{
public:
	Date()//无参构造
	{
		cout << "Date" << endl;
	}
	Date(int year, int month, int day)//有参构造
	{
		_year = year;
		_month = month;
		_day = day;
        cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;//调用无参构造
	Date d2(2022, 5, 2);//调用有参构造
	return 0;
}

?什么是默认构造函数?

默认构造函数指的是编译器自动生成的构造函数?

这句话不是很准确,默认构造函数一般是指我们在实例化对象不用传参就可自动调用的构造函数。

一般有3个

  1. 编译器自动生成的
  2. 我们自己写的无参的
  3. 我们自己写的全缺省的

我们先来看看编译器自动生成的默认构造函数做了什么?

#include<iostream>
using namespace std;
class Date
{
public:
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;//调用编译器自动生成的默认构造函数
	d1.Print();
	return 0;
}

我们发现打印的都是随机值,那么编译器自动生成的默认构造函数好像什么事情都没做。

但真的什么都没做吗?实际上这个自动生成的默认构造函数还是有做事情的

我们在日期类中加上一个A类型的对象。

#include<iostream>
using namespace std;

class A
{

public:
	A()
	{
		cout << "A()" << endl;
	}
};

class Date
{
public:
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;

	A a;
};

int main()
{
	Date d1;//调用编译器自动生成的默认构造函数
	d1.Print();
	return 0;
}

?我们发现,编译器自动生成的默认构造函数调用了A类的默认构造函数。

总结:

编译器自动生成的默认构造函数对内置类型(基本类型)不处理,对自定义类型会调用它的默认构造函数。

如果A类没有默认构造函数呢?

#include<iostream>
using namespace std;

class A
{
public:
	A(int a)
	{
		cout << "A(a)" << endl;
	}
};

class Date
{
public:
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;

	A a;
};

int main()
{
	Date d1;//调用编译器自动生成的默认构造函数
	d1.Print();
	return 0;
}

?先实例化d1,调用编译器自动生成的日期类的默认构造函数,它再去调用A类的默认构造函数,但A类没有默认构造函数,只有一个有参构造(需要传参才能调用,不是默认构造函数),因此才会报错。

如果将A类的有参构造改为全缺省、无参、或者使用编译器生成的默认构造函数都可以解决。

需要说明一点的是,默认构造函数只能有一个,否则编译器不知道该调用那个。

同时,只要你重载了构造函数,编译器便不会自动生成默认构造函数。

还有就是不要这样去实例化对象。

四、默认析构函数

析构函数的概念:

与构造函数一样,是一个特殊的成员函数。

在对象销毁时会自动调用析构函数,主要完成对资源的清理。

特性:

  1. 函数名:在类名前面加个~
  2. 没有返回值
  3. 没有参数
  4. 一个类只能有一个析构函数
  5. 在对象生命周期结束自动调用

默认析构函数,就是我们不写编译器自动生成的析构函数。

与编译器自动生成的默认构造函数一样,默认析构函数对内置类型不处理,对自定义类型(class、struct自定义的类型)会调用它们的默认析构函数。

在与堆区有关的类需要自己写一个析构函数,完成堆区的释放,不然会内存泄露。

五、拷贝构造

拷贝构造:特殊的构造函数,用一个对象复制(拷贝)来初始化另一个对象。

关于拷贝构造的使用

?拷贝构造能这样写吗?

答:不能,否则引发无穷递归。

当你用d1去调用拷贝构造初始化d2时,需要传参,传参需要调用拷贝构造,调用拷贝构造需要传参,传参需要拷贝构造。。。。。会引发无穷的递归。

因此这里应该使用引用传参,而不是传值传参。

?顺便提一下,传地址虽然也可以完成拷贝,不过调用时需要传地址,但这用起来不方便,因此不建议这样。

?需要说明的是,我们不写拷贝构造,编译器会自动生成一个拷贝构造。

编译器自动生成的拷贝构造完成的浅拷贝或者说值拷贝

class Stack
{
public:
	Stack(int capacity = 4)
	{
		if (capacity == 0)
		{
			_a = nullptr;
			_capacity = _size = 0;
		}
		else
		{
			_a = (int*)malloc(sizeof(int) * capacity);
			_size = 0;
			_capacity = capacity;
		}
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}
private:
	int* _a;
	int _capacity;
	int _size;
};

?

编译器自动生成的拷贝构造会完成浅拷贝或者说是值拷贝。

当类与堆区有联系的话,浅拷贝会带来堆区内存重复释放的问题,需要深拷贝解决。

六、赋值运算符重载

什么是运算符重载?

简单来说就是让运算符可以处理自定义类型。

赋值运算符重载,我们不写编译器会自动生成一个,完成的是值拷贝(浅拷贝)

class Date
{
public:
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	Date(int year = 1, int month = 2, int day = 3)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	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(2022, 5, 2);
	d1 = d2;
	return 0;
}

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

拷贝构造是拿一个对象去复制初始化另一个对象。

而赋值重载是两个对象都存在,那一个对象去赋值给另一个对象。

一般的类,编译器生成的够用了。

但对于与堆区有联系的类,我们需要自己写一个赋值重载完成深拷贝。

七、实现一个日期类

class Date
{
public:
	void Print()const
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	int GetMonthDay(int year, int month)
	{
		int m_arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		int day = m_arr[month];
		if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
		{
			day++;
		}
		return day;
	}

	Date(int year = 1, int month = 2, int day = 3)
	{
		_year = year;
		_month = month;
		_day = day;
		if (year < 0 || month <= 0 || month>12 || day <= 0 || day > GetMonthDay(year,month))
		{
			cout << "无效日期" << endl;
			cout << _year << "年" << _month << "月" << _day << "日" << endl;
		}
	}

	Date(const Date& d)//浅拷贝
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

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

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

	Date operator-(int day)const
	{
		Date temp = *this;
		temp -= day;
		return temp;
	}

	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

	int operator-(const Date& d)const
	{
		int flag = 1;
		Date max = *this;
		Date min = d;
		if (*this < d)
		{
			max = d;
			min = *this;
			flag = -1;
		}
		int day = 0;
		while (min != max)
		{
			min++;
			day++;
		}
		return day * flag;
	}

	Date operator++(int)//后置++
	{
		Date temp = *this;
		*this += 1;
		return temp;
	}

	Date& operator++()//前置++
	{
		*this += 1;
		return *this;
	}

	Date& operator--()//前置--
	{
		*this -= 1;
		return *this;
	}

	Date operator--(int)//后置--
	{
		Date temp = *this;
		*this -= 1;
		return temp;
	}

	bool operator==(const Date& d)const
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

	bool operator!=(const Date& d)const
	{
		return !(*this == d);
	}

	bool operator>(const Date& d)const
	{
		if (_year > d._year)
		{
			return true;
		}
		else if (_year == d._year)
		{
			if (_month > d._month)
			{
				return true;
			}
			else if (_month == d._month)
			{
				if (_day > d._day)
				{
					return true;
				}
			}
		}
		return false;
	}

	bool operator>=(const Date& d)const
	{
		return (*this > d) || (*this) == d;
	}

	bool operator<(const Date& d)const
	{
		return !(*this >= d);
	}

	bool operator<=(const Date& d)const
	{
		return !(*this > d);
	}

	Date* operator&()
	{
		return this;
	}

	const Date* operator&()const
	{
		return this;
	}

	friend ostream& operator<<(ostream& out, Date& d);
	friend istream& operator>>(istream& in, Date& d);

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

ostream& operator<<(ostream& out, Date& d)
{
	cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

八、加const的成员函数

加上const成员函数,这个const修饰的是this,如果不想修改this指向对象的内容可以加const修饰成员函数。

如下:

?那么_year和_month和_day都不能被修改了。

思考下面的问题:

1. const对象可以调用非const成员函数吗?
2. 非const对象可以调用const成员函数吗?
3. const成员函数内可以调用其它的非const成员函数吗?
4. 非const成员函数内可以调用其它的const成员函数吗?
答:本质上就是传地址给this指针的问题。
1.const对象传给不加const的this,权限放大,不能调用。
2.非const对象传给加const的this,权限缩小,能调用。
3.加了const的this传给不加const的this,权限放大,不能调用。
4.没加const的this传给加了const的this,权限缩小,能调用。
因此2,4能够调用,1和3不能调用。

九、取地址运算符重载(一般不用)

	Date* operator&()
	{
		return this;
	}

极少情况会重载这个取对象地址的运算符。

十、加const取地址运算符重载

const Date* operator&()const
	{
		return this;
	}

与七相同,一般不用这个,加const取地址运算符是为了让加const对象能够取地址。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 10:59:30  更:2022-05-05 11:00:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:13:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码