C++ 类与对象(2)

目录

一.类的6个默认成员函数

二.构造函数

1.概念

2.特性

三.析构函数

1.概念

2.特性

四.拷贝构造函数

1.概念

2.特性

五.赋值操作符重载

1.运算符重载

2.赋值运算符重载

六.const成员

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

八.日期(Date)类实现

1.Date.h

2.Date.cpp

3.Test.cpp


前言:上一篇写了关于类与对象一些比较基础的知识点,这一篇则是类与对象的重点,包括构造函数、析构函数、拷贝构造函数、操作符重载等类与对象非常重要的知识点。

上一篇:C++ 类与对象(1)_糖果雨滴a的博客-CSDN博客

一.类的6个默认成员函数

        如果一个类中什么成员都没有,简称为空类,但是空类中并不是什么都没有,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。

6个默认成员函数:

(1)初始化和清理

构造函数:主要完成初始化工作

析构函数:主要完成清理工作

(2)拷贝复制

拷贝构造函数:使用同类对象初始化创建对象

赋值重载函数:把一个对象赋值给另一个对象

(3)取地址重载

取地址操作符重载

const取地址操作符重载

二.构造函数

1.概念

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

	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

int main()
{
	Date d1;
	d1.Print();

	
	d1.Init(2022, 5, 15);
	d1.Print();

	return 0;
}

        对于Date,可以通过Init公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置,比较麻烦,因此C++提供了构造函数。

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

2.特性

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

(1)构造函数可以重载:

class Date
{
public:
	//无参构造函数
	//Date()
	//{
	//	_year = 1;
	//	_month = 1;
	//	_day = 1;
	//}

	//带参构造函数
	//Date(int year, int month, int day)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	//全缺省构造函数,不能和无参构造函数一起写
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

int main()
{
	//Date d1(); // 不能这么写
    //如果写成这样,就成了函数声明

	//如果不传参,要写成下面这样
	Date d1;// 调用无参或全缺省
	d1.Print();

	Date d2(2022, 5, 15);// 调用带参
	d2.Print();

	Date d3(2022);
	d3.Print();

	Date d4(2022, 10);
	d4.Print();

	return 0;
}

(2)如果类中没有显式定义构造函数,C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器就不再生成。

(3)C++把类型分成内置类型(基本类型)自定义类型内置类型是语法已经定义好的类型:如int、char等自定义类型是我们使用class/struct/union自己定义的类型

默认生成的构造函数对于内置类型成员函数不做处理,对于自定义类型成员变量才会处理,去调用它的默认成员函数。

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

class Date
{
public:
	// 我们不写,编译器会生成一个默认无参构造函数
	// 内置类型/基本类型:int/char/double/指针...
	// 自定义类型:class/struct去定义类型对象
	// 默认生成构造函数对于内置类型成员变量不做处理,对于自定义类型成员变量才会处理
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日

	A _aa;
};


int main()
{
	Date d1;
	d1.Print();

	return 0;
}

Date的私有成员中有int类型和自定义类型,打印出的结果中,会发现Date的类对象d1去调用了A类的构造函数,而int类型的3个私有成员都是随机值。

因此可以验证:默认生成的构造函数对于内置类型成员函数不做处理,对于自定义类型成员变量才会处理,去调用它的默认成员函数。

C++11的补充:

C++编译器默认生成的构造函数对于内置类型是不处理的,但是有很多人觉得这个方式很不好,因此在C++11的时候出了一个补充,可以在定义时给缺省值(相对于缺省函数的缺省值)

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

class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
    //我们在这给出缺省值
	int _year = 2000; // 年
	int _month = 1; // 月
	int _day = 1; // 日

	A _aa;
};


int main()
{
	Date d1;
	d1.Print();

	return 0;
}

还是刚才那段代码,这里我们在定义时给了缺省值,会发现结果就是缺省值的2000 1 1,因为我们是用的编译器的默认构造函数,没有传参,就用缺省值。 

总结:

①函数名与类名相同

②无返回值

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

④构造函数可以重载

⑤如果类中没有显式定义构造函数,C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器就不再生成

⑥默认生成的构造函数对于内置类型成员函数不做处理,对于自定义类型成员变量才会处理,去调用它的默认成员函数

⑦无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

注意:无参构造函数、全缺省构造函数、构造函数等,如果我们没写,那么编译器默认生成的构造函数,都可以认为是默认成员函数

⑧成员变量的命名风格:一般建议在变量前加_

可以在定义时给缺省值

三.析构函数

1.概念

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

2.特性

        析构函数与构造函数类似,编译器生成的默认析构函数,会去对自定义类型成员调用它的析构函数。

总结:

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

②无参数无返回值

③一个类有且只有一个析构函数,若未显式定义,系统会自动生成默认的析构函数

④对象生命周期结束时,C++编译器会自动调用析构函数

⑤编译器生成的默认析构函数,会去对自定义类型成员调用它的析构函数

四.拷贝构造函数

1.概念

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

2.特性

(1)拷贝构造函数的参数有且只有一个且必须使用引用传参

class Date
{
public:
    //构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// Date d2(d1);
	//Date(Date& d)
    //拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		//d._year = _year;
		//d._month = _month;
		//d._day = _day;
	}
    
    //普通构造函数
	Date(const Date* d)
	{
		_year = d->_year;
		_month = d->_month;
		_day = d->_day;

		//d._year = _year;
		//d._month = _month;
		//d._day = _day;
	}

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

private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};


//void Func(Date& d)
// 自定义类型对象,拷贝初始化规定要调用拷贝构造完成
void Func(Date d)
{}

int main()
{
	Date d1(2022, 5, 15);
	Func(d1);
	int x = 0;
	Func(x);

	Date d2(d1); // 拷贝构造
	d2.Print();
	d1.Print();

	Date d3(&d1); // 普通构造函数
	d3.Print();
	d1.Print();

	return 0;
}

 拷贝构造函数必须要使用引用传参,如果传地址或是其它,都只相对于是普通的构造函数。

调用函数Fun传对象时,如果传值,且不加引用时,需要调用拷贝构造函数。

(2)对应字符串、数组这种类型的,不能使用编译器默认生成的构造函数,因为编译器默认生成的构造函数是浅拷贝,如果浅拷贝就会导致两个两个类对象指向了同一块空间,在析构时就会导致析构了其中一个对象后,另一个对象指向的空间被释放掉了,因此出现野指针的情况。

总结:

①拷贝构造函数时构造函数的一个重载形式

②拷贝构造函数的参数有且只有一个且必须使用引用传参,使用传值范式会引发无穷递归调用

③若未显式定义,系统会生成默认拷贝构造函数。默认拷贝构造函数是对象内存存储按字节完成拷贝,这种拷贝叫做浅拷贝(值拷贝)

④注意:需要深拷贝的类,不能用编译器默认生成的拷贝构造函数(因为是浅拷贝)

⑤一般的类,编译器生成的拷贝构造就够用了,只有需要直接管理资源的类(比如Stack)需要自己实现深拷贝。

五.赋值操作符重载

1.运算符重载

(1)概念

        C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型和参数列表与普通的函数类似。

(2)特性

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

	int GetYear()
	{
		return _year;
	}

    //一般操作符重载写在类内
    bool operator==(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

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

//private:
	int _year;
	int _month;
	int _day;
};
//
// 运算符重载 -- 函数
// 函数名:operator 运算符
// 参数:运算符操作数
// 返回值:运算符运算后结果

//写在类外面,无法调用私有成员,如果想调用只能再写getYear这样的函数返回私有成员
//所有一般都会选择实现在类内(但是要注意在类内时,有一个隐藏的this指针)
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

int main()
{
	Date d1(2022, 5, 16);
	Date d2(2022, 5, 16);

	// 内置类型,可以直接用各种运算符
	// 自定义类型,不能直接用各种运算符,
	// 为了自定义类型可以使用各种运算符,运算符重载的规则

    //下面两种写法一样,但是几乎都是用下面这种写法
	if (operator==(d1, d2))
	{
		cout << "==" << endl;
	}

	//一般写成这样
	if (d1 == d2) // 编译器会处理成对应重载运算符调用 if (operator==(d1, d2))
	{
		cout << "==" << endl;
    }

    if (d1 < d2) // 编译器会处理成对应重载运算符调用 if (d1.operator<(d2))
	{
		cout << "<" << endl;
	}

	return 0;
}

前置++和后置++不同:

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

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

总结:

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

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

例如:bool operator==(const Date& d1, const Date& d2)

③不能通过连接其他符号(C/C++未出现过的操作符)来创建新的操作符:比如operator@

④重载操作符必须有一个类类型或者枚举类型的操作数

⑤用于内置类型的操作符,其含义不能改变。例如:内置整型+,不能改变其含义,依旧是与整型进行+的含义

⑥作为类成员的重载函数时,其形参看起来比操作数数目少1个(实际上操作符有一个默认的形参this,且限定为第一个形参)

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

⑧前置++与后置++不同

前置++:Date operator++()    后置++:Date operator++(int) 

2.赋值运算符重载

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

	// d2 = d1; -> d2.operator=(&d2, d1)
	// d1 = d1
	
	//自己实现赋值操作符重载
	//Date& operator=(const Date& d)
	//{
	//	if (this != &d)
	//	{
	//		_year = d._year;
	//		_month = d._month;
	//		_day = d._day;
	//	}

	//	return *this;
	//}
private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	Date d1(2022, 5, 16);
	Date d2(2022, 5, 18);

	Date d3(d1); // 拷贝构造  -- 一个存在的对象去初始化另一个要创建的对象
	d3 = d2 = d1; // 赋值重载/复制拷贝 -- 两个已经存在对象之间赋值
	(d3 = d2) = d1; // 赋值重载/复制拷贝 -- 两个已经存在对象之间赋值

	d1 = d1;

	return 0;
}

赋值运算符重载就是重载了=运算符。

①参数类型为一个构造出的类对象

②返回值为该类对象的引用

③里面要检查是否自己给自己赋值

④一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝

注意:①两个已经构造出的对象用 = 符号才是调用赋值运算符,如果有是在创建对象时用=,是调用拷贝构造函数。

②赋值运算符只能作为类的成员函数重载(因为赋值运算符为类的6个默认成员函数之一)

Date d1(2022, 5, 18);
Date d2(2022, 5, 19)

Date d3 = d1; // 调用的拷贝构造函数
d1 = d2; // 调用的赋值运算符重载

六.const成员

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

const修饰的成员不能被修改,因为const不能在()内显式的修饰this指针,因此要想用this修饰this指针,应该在函数后面加const(确定该函数不修改类成员变量)

Date Date::operator+(int day) const
{
	Date ret(*this);
	ret  += day;

	return ret;
}

在下面的Date类实现中,就将确定不修改类成员变量的函数后加了const

权限可以缩小不能放大:

非const对象可以调用非const成员函数和const成员函数

const对象只能调用const成员函数,不能调用非const成员函数

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

这两个一般不用重新定义,编译器默认生成的就够了。

只有特殊情况,才需要重载,比如想让别人获取到指定的内容,或者不想让别人取到地址

class Date
{ 
public :
    Date* operator&()
    {
        return nullptr;
        //return this ;
    }
 
    const Date* operator&()const
    {
        return nullptr;
        //return this ;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};

八.日期(Date)类实现

1.Date.h

#pragma once

#include <iostream>
#include <assert.h>

using std::cout;
using std::cin;
using std::endl;

class Date
{
    friend std::ostream& operator<<(std::ostream& out, const Date& d);
	friend std::istream& operator>>(std::istream& out, Date& d);
public:
	bool isLeapYear(int year)
	{
		return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
	}

	int GetMonthDay(int year, int month);

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

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

	Date operator+(int day) const;
	Date& operator+=(int day);

	Date operator-(int day) const;
	Date& operator-=(int day);
	
	// ++d1
	Date operator++() //前置
	{
		*this += 1;
		return *this;
	}

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

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

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

	int operator-(const Date& d) const;

	bool operator==(const Date& d) const;
	bool operator<(const Date& d) const;
	
	//inline不支持声明和定义分别放到.h和.cpp中
	//所以成员函数中要成为inline最好直接在类里面定义
	//类里面定义默认就是inline
	bool operator<=(const Date& d)
	{
		return *this < d || *this == d;
	}

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

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

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

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

2.Date.cpp

#include "Date.h"

int Date::GetMonthDay(int year, int month)
{
	int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31 ,30, 31 };
	if (month == 2 && isLeapYear(year))
	{
		return 29;
	}
	else
	{
		return monthDayArray[month];
	}
}

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

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

Date::Date(int year, int month, int day)
{
	if (year >= 1 &&
		month >= 1 && month <= 12 &&
		day >= 1 && day <= GetMonthDay(year, month))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "日期非法" << endl;
	}
}

Date Date::operator+(int day) const
{
	Date ret(*this);
	ret  += day;

	return ret;
}

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 == 13)
		{
			++_year;
			_month = 1;
		}
	}

	return *this;
}

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

Date& Date::operator-=(int day)
{
	if (day < 0)
		return *this += -day;

	_day -= day;
	while (_day < 0)
	{
		--_month;
		if (_month == 0)
		{
			_month = 12;
			--_year;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

int Date::operator-(const Date& d) const
{
	int flag = 1;
	Date max = *this;
	Date min = d;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++n;
		++min;
	}

	return n * flag;
}

std::ostream& operator<<(std::ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day << endl;
	return out;
}

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

3.Test.cpp

#include "Date.h"

void TestDate1()
{
	Date d1(2022, 5, 18);
	Date d2(2023, 3, 20);
	Date d3(2023, 3, 20);

	cout << (d1 < d2) << endl;
	cout << (d1 > d2) << endl;
	cout << (d1 == d3) << endl;
	cout << (d2 <= d3) << endl;
	cout << (d2 == d3) << endl;
}

void TestDate2()
{
	Date d1(2022, 5, 18);
	Date d2 = d1 + 15;
	Date d3;
	d3 = d1 + 15;

	//Date d3 = d1; // ¿½±´ Date d3(d1);
	//d1 = d2;

	d2.Print();
	d1.Print();

	d1 += 15;
	d1.Print();
}

void TestDate3()
{
	Date d1(2022, 5, 18);
	Date d2 = d1 - 30;
	d2.Print();
	d1 -= 30;
	d1.Print();

	Date d3(2022, 5, 18);
	d3 += 10000;
	d3.Print();

	d3 -= 10000;
	d3.Print();
}


void TestDate4()
{
	Date d1(2022, 5, 18);
	d1 -= -100;
	d1.Print();

	d1 += -100;
	d1.Print();

	Date d2(2022, 5, 18);
	Date ret1 = ++d2; // d2.operator++()
	ret1.Print();
	d2.Print();

	Date ret2 = d2++; // d2.operator++(0)
	ret2.Print();
	d2.Print();
}

void TestDate5()
{
	Date d1(2022, 5, 18);
	Date d2(2020, 2, 4);
	cout << (d1 - d2) << endl;
	cout << (d2 - d1) << endl;
}

//Print需要加const,否则会报错
void Func(const Date& d)
{
	d.Print(); // d1.Print(&d); -> const Date*
}

void TestDate6()
{
	Date d1(2022, 5, 18);
	d1.Print(); // d1.Print(&d1); -> Date* 
	Func(d1);
}

void TestDate7()
{
	//Date d1(2022, 5, 18);
	Date d1, d2;
	//cin >> d1;
	//cout << d1;
	/*d1.operator<<(cout);
	d1 << cout;*/
	cin >> d1 >> d2;
	cout << d1 << d2;
	//operator<<(cout, d1);

	int i = 1;
	double d = 2.2;
	cout << i;
	//cout.operator<<(i);
	cout << d;
}

int main()
{
	TestDate1();
	TestDate2();
	TestDate3();
	TestDate4();
	TestDate5();
	TestDate6();
	TestDate7();

	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冰果滴

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

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

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

打赏作者

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

抵扣说明:

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

余额充值