C++:类和对象(中)

1.类的6个默认成员函数:

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。


默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
class Date {};

2.构造函数:

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

int main()
{
    Date d1;
    d1.Init(2022, 7, 5);
    d1.Print();

    Date d2;
    d2.Init(2022, 7, 6);
    d2.Print();

    return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置
信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

特性:

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

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

注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

class Date
{
public:
    //Date()
    //{

    //}
    //Date(int year, int month, int day)
    //{
    //    _year = year;
    //    _month = month;
    //    _day = day;
    //}

    Date(int year = 10, int month = 10, int day = 10)
    {
        _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(2023,10,31);
    d2.Print();

    return 0;
}

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

注意:

        如果用户显式定义了构造函数,编译器将不再生成;(即自己写了一个构造函数)

        将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数;

        将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用(因为这里放开后需要的是一个无参的构造函数,取消注释后,不在构造一个默认的无参构造函数,就导致没法与构造函数匹配)

class Date
{
public:
	/*
	// 如果用户显式定义了构造函数,编译器将不再生成
	Date(int year, int month, int day)
	{
	    _year = year;
	    _month = month;
	    _day = day;
	}
	*/
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
    // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
	// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
	Date d1;
	return 0;
}

        6. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??
        解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。内置类型不做处理,自定义类型编译器会去调用默认构造函数。

内置类型:int、char、double、……以及指针

自定义类型:如上面的Date、Time就是我们自己定义的类型。

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在
类中声明时可以给默认值。

即可以:

private:

        int _year = 10;

        int _month = 10;

        int _day = 10;

class Time
{
public:
    Time()
    {
        cout << "Time()" << endl;
        _hour = 0;
        _minute = 0;
        _second = 0;
    }
private:
    int _hour;
    int _minute;
    int _second;
};

class Date
{
private:
    // 基本类型(内置类型)
    int _year;
    int _month;
    int _day;
    // 自定义类型
    Time _t;
};

int main()
{
    Date d;
    return 0;
}

        7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:1.无参构造函数、2.全缺省构造函数、3.我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。可以不传参数就调用构造,都可以叫默认构造,这三个函数不能同时存在,只能存在一个。

总结: 

1.一般情况下,我们都要自己写构造函数;

2.成员都是自定义类型,或者声明时给了缺省值,可以考虑让编译器自己生成构造函数。

3.析构函数:

概念:

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

特性:

析构函数是特殊的成员函数,其特征如下:
        1. 析构函数名是在类名前加上字符 ~
        2. 无参数无返回值类型
        3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

注意:析构函数不能重载
        4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	// 其他方法...
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
void TestStack()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}//结束自动调用析构函数

int main()
{
    TestStack();
    return 0;
}

        5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}

程序运行结束后输出:~Time(),
在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
        因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,
_day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。

但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

        6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

4.拷贝构造函数:

概念:

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

特征:

拷贝构造函数也是特殊的成员函数,其特征如下:
        1. 拷贝构造函数是构造函数的一个重载形式。

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

        3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的。

        4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

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);
	s1.Push(2);

	Stack s2(s1);
	return 0;
}

在这个代码中我们为什么会崩溃呢?

此时,s1,s2都指向了同一块malloc开辟的空间,程序运行结束时,调用析构函数,先将s2释放了,但是s1也需要释放,所以就造成同一块空间释放了两次,造成程序崩溃!!!

注意:类中如果没有涉及资源申请时,拷贝构造函数写不写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝/值拷贝。

 上面代码正确的书写是:

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(const Stack& stt)
	{
		_array = (DataType*)malloc(sizeof(DataType) * stt._capacity);
		if (_array == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_array, stt._array, sizeof(DataType) * stt._size);
		_capacity = stt._capacity;
		_size = stt._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);
	s1.Push(2);

	Stack s2(s1);
	return 0;
}

5. 拷贝构造函数典型调用场景:
        1.使用已存在对象创建新对象
        2.函数参数类型为类类型对象
        3.函数返回值类型为类类型对象

class Date
{
public:
	//构造函数
	Date(int year, int minute, int day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}
	//拷贝构造函数
	Date(const Date& d)
	{
		cout << "Date(const Date& d):" << this << endl;
	}
	//析构函数
	~Date()
	{
		cout << "~Date():" << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

Date Test(Date d)
{
	Date temp(d);
	return temp;
}

int main()
{
	Date d1(2022, 1, 13);
	Test(d1);
	return 0;
}

        为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

总结:

        1.浅拷贝/值拷贝:内置类型成员完成的拷贝;

        2.深拷贝:本质是熬吧指向的资源,让新对象拥有跟拷贝对象一样大的空间和一样的值;自定义类型成员调用这个成员的拷贝构造;

        比如:Stack就需要自己写拷贝构造完成深拷贝,顺序表、链表、二叉树等等的类都需要深拷贝(需要开辟资源!!!)。

5.赋值运算符重载:

运算符重载:

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型  operator操作符(参数列表)
注意:
        1.不能通过连接其他符号来创建新的操作符:比如operator[ 运算符 ] 
        2.重载操作符必须有一个类类型参数,比如:不能int operator+(int x,int y)这样就不会调用内置的+,导致改变了内置的运算规则
        3.用于内置类型的运算符,其含义不能改变,例如:内置的整型"+",不能改变其含义
        4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
        5.[ .* ] [ :: ] [ sizeof ] [ ?: ] [ . ] 注意以上5个运算符不能重载。这个经常在笔试选择题中出
现。

// 全局的operator==
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//private:
	int _year;
	int _month;
	int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
    //这个是类外定义!!!
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

void Test()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 27);
	cout << (d1 == d2) << endl;
}

int main()
{
	Test();
	return 0;
}

这里会发现运算符重载成全局需要成员变量是公有的,那么问题来了,封装性(自己的不让被别人使用)如何保证?

赋值运算符重载:

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

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

bool operator>(const Date& y)//(const Date* this,const Date& y)
	{
		if (_year > y._year)
		{
			return true;
		}
		else if (_year == y._year && _month > y._month)
		{
			return true;
		}
		else if (_year == y._year && _month == y._month && _day > y._day)
		{
			return true;
		}

		return false;
	}

bool operator==(const Date& y)//(const Date* this,const Date& y)
	{
		return _year == y._year
			&& _month == y._month
			&& _day == y._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    Date d1(2023,11,2);
    Dare d2(2000,1,1);
	bool ret1 = d1 > d2;  // operator>(d1, d2)-> d1.operator>(&d1, d2)
	bool ret2 = d1 == d2; // operator==(d1, d2)-> d1.operator==(&d1, d2)

}

 

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

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
	if (&left != &right)
	{
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

int main()
{
	Date d1(2000, 1, 1);
	Date d2(2000, 1, 2);

	(d1 = d2);

	return 0;
}

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

运算符重载和函数重载 他们之间没有关联!

        运算符重载:自定义类型可以直接使用运算符

        函数重载:可以允许参数不同的同名函数,

 operator= 我们不写,编译器会生成默认的operator =
 跟拷贝构造的行为类似,内置类型值拷贝,自定义类型调用他的赋值
 比如:Date MyQueue (用两个栈实现队列)也可以不写,默认生成operator =就可以用
 Stack必须自己实现operator=,实现深拷贝

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

注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	Date d2;
	d1 = d2;
	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);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2;
	s2 = s1;
	return 0;
}

 

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

我们知道cout<<、cin>>是打印和输入,如果我们想对“ << ”、“ >> ”重载呢! 

此时我们看到" cout << d16 '无法通过,需要d16在前才能通过,但是这样看着不顺,所以我们需要写成全局函数,但是又无法保证成员变量的封装性,此时引入友元函数,就能保证封装性了。 

 

//cout <<
ostream& operator<<(ostream& out,Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;

	return out;
}

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

	return in;
}

总结:

        其他运算度一般是实现成成员函数,“<<” ">>" 流运算符必须实现到全局,这样才能让流对象在第一个参数位置,才符合可读性。

分析:

        流本质是为了解决自定义类型的输入输出问题,printf 、scanf 无法解决自定义类型的输入问题,所以用:面向对象 + 运算符重载解决。

前置++和后置++重载:

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 前置++:返回+1之后的结果
	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
	Date& operator++()
	{
		_day += 1;
		return *this;
	}
	// 后置++:
	// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
	// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
	//自动传递
	// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
	//一份,然后给this + 1
	// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
	Date operator++(int)
	{
		Date temp(*this);
		_day += 1;
		return temp;
	}

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

int main()
{
	Date d;
	Date d1(2022, 1, 13);
	d = d1++; // d: 2022,1,13 d1:2022,1,14
	d = ++d1; // d: 2022,1,15 d1:2022,1,15
	return 0;
}

前置++:返回+1之后的结果
注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
后置++:

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1而temp是临时对象,因此只能以值的方式返回,不能返回引用。

6.日期类的实现:

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
	assert(_year >= 1 && _month >= 1);
	int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

	if (_month == 2 && ((year % 4 == 0 && year != 100) || (year % 400 == 0)))
	{
		return 29;
	}
	return arr[month];
}

//打印
void Date::Print()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

// 全缺省的构造函数
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;

	if (_year < 1 ||
		_month < 1 || _month > 12 ||
		_day < 1 || _day > GetMonthDay(_year, _month))
	{
		//assert(false);
		Print();
		cout << "日期非法" << endl;
	}
}

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

	if (_year < 1 || _month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month))
	{
		方法一
		//exit(-1);
		方法二
		//Print();
		//cout << "输入格式错误" << endl;
		//方法三
		assert(false);
	}
}

// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

// 析构函数
Date::~Date()
{
	_year = 0;
	_month = 0;
	_day = 0;
}

// 日期+=天数
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)
{
	Date tmp(*this);
	tmp += day;

	return tmp;
}

// 日期-天数
Date Date::operator-(int day)
{
	Date tmp(*this);
	tmp -= day;

	return tmp;
}

// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}

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

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

	return *this;
}

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

	return tmp;
}

// 后置--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;

	return tmp;
}

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

	return *this;
}

// >运算符重载
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;
	}

	return false;
}

// ==运算符重载
bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

// >=运算符重载
bool Date::operator>= (const Date& d)
{
	if (*this > d || *this == d)
	{
		return true;
	}

	return false;

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

	//return false;
    
    //可以复用尽量复用
	return !(*this >= d);
}

// <=运算符重载
bool Date::operator<= (const Date& d)
{
	//if (*this < d || *this == d)
	//{
	//	return true;
	//}

	//return false;

	return !(*this > d);
}

// !=运算符重载
bool Date::operator!= (const Date& d)
{
	//if (*this == d)
	//{
	//	return false;
	//}

	//return true;

	return !(*this == d);
}

// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
	Date max = d;
	Date min = *this;
	int flag = 1;

	if (*this > d)
	{
		max = *this;
		min = d;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}

	return n * flag;
}

这里需要了解我们程序员在工作上能写效率更高的代码,就写效率更高的,如下: 

7.const成员:

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

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	//const Date* this
	void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

void Test()
{
	Date d1(2022, 1, 13);
	d1.Print();//&d1:const Datr* d1
	const Date d2(2022, 1, 13);
	d2.Print();//&d2:const Datr* d2
}

int main()
{
	Test();

	return 0;
}

在成员函数后面加上‘ const ’ 即在传过来的参数前都加上const一般用于不改变传递过来的参数的情况:+ 、-、判断……

成员函数定义原则:

1。能定义成const的成员函数都应该定义成const,这样const对象和非对象都可以调用(即:权限平移或权限缩小)

2.要修改成员变量的成员函数,不可以定义成const,

8.取地址及const取地址操作符重载:

下面这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
注意:只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

	Date* operator&()
	{
		return this;
	}

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

特殊情况:

我们不想让别人知道这个地址时才会自己创建,此时:

        1.返回一个假地址;

        2.返回空指针

	Date* operator&()
	{
		//return this;

		//给一个假地址
		return (Date*)0x1111111;
	}

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

		//不给看地址
		return nullptr;
	}

 以上就是个人学习见解和学习的解析,欢迎各位大佬在评论区探讨!

感谢大佬们的一键三连! 感谢大佬们的一键三连! 感谢大佬们的一键三连!

                                              

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/115448.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

全志H6-LicheePi调试记录

LPDDR3&#xff1a;K4E6E304ED-EGCG和K4E6E304ED-EGCF 最高速率不一样&#xff0c;CG是2133MHz&#xff0c;CF是1866MHz

VMware产品收集日志方法汇总

概述 vCenter日志是一个用于存储与vSphere环境相关的各种活动、事件和警告的日志系统。通过收集并分析vCenter日志&#xff0c;管理员可以获得有关其虚拟化环境的重要洞察和故障排除信息。 vCenter日志由多个组件组成&#xff0c;包括vCenter Server、ESXi主机和其他vSphere组…

Apache ECharts简介和相关操作

文章目录 一、Apache ECharts介绍二、快速入门1.下载echarts.js文件2.新建index.html文件3.准备一个DOM容器用于显示图表4.完整代码展示5.相关配置 三、演示效果四、总结 一、Apache ECharts介绍 Apache ECharts 是一款基于 Javascript 的数据可视化图表库&#xff0c;提供直观…

C#文件操作从入门到精通(3)——Txt文件读写操作

前言: 我们在开发c#程序时,经常需要对txt文本文件进行操作,有时候是读取txt文本中的内容,有时候是将程序运行过程中的一些数据写入到txt文本中做记录,为了帮助大家学习txt文本的操作,我特意开发了一个“txt文件操作学习”winform小软件,该软件调用了我封装的txt文件操作…

项目管理之如何估算项目工作时间

在项目管理中&#xff0c;项目工作时间的估算是一个关键环节&#xff0c;它直接影响到项目的进度、预算和资源分配。本文将介绍几种常用的时间估算技术和时间估算的十步法&#xff0c;帮助你更好地估算项目工作时间。 常用时间估算技术 类比估算 参照以往同类同规模项目时间数…

Vue3 如何在<script setup>里设置组件name属性

Vue3 如何在<script setup>里设置组件name属性 文章目录 Vue3 如何在\<script setup>里设置组件name属性一、Vue组件中 name 的用处二、难看但实用的方法三、使用第三方插件支持安装插件插件基本配置插件基本使用 四、Vue官方解决方法4.1 Vue3.3版本之前安装插件插…

高防IP的原理

高防IP&#xff0c;把域名解析到高防IP上(web事务只要把域名指向高防IP 即可。非web事务&#xff0c;把事务IP换成高防IP即可)一起在高防IP上设置转发规矩;所有公网流量都会走高防IP&#xff0c;通过端口协议转发的方法将用户的拜访通过高防IP转发到源站IP&#xff0c;一起将歹…

IDEA集成Docker插件打包服务镜像与运行【附Docker命令汇总】

Docker官网 Docker官网&#xff1a;https://www.docker.com/ Docker Hub官网&#xff1a;http://hub.docker.com/ 什么是Docker Docker 是一个开源的容器引擎&#xff0c;可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。开发者和系统管理员在笔记本上编…

lua-web-utils库

lua--导入所需的库local web_utilsrequire("lua-web-utils")--定义要下载的URLlocal url"https://jshk.com.cn/"--定义代理服务器的主机名和端口号local proxy_port8000--使用web_utils的download函数下载URLlocal file_pathweb_utils.download(url,proxy_…

网络安全好就业吗?

现目前而言&#xff0c;网络安全工程师仍是比较紧缺的。但网络安全行业前景好并≠网络安全的入学门槛低&#xff0c;也≠网络安全行业适合所有转岗的人。行业紧缺的一直是技术过硬&#xff0c;德才兼备的优秀人才。 一、网络安全适合哪些人&#xff1f; 首先&#xff0c;是对…

Jenkins自动化部署相关shell命令

1. 只后台启动&#xff1a; nohup java -jar jar/demo*.jar & 2. 增加命令启动日志输出位置&#xff0c;防止超时处理配置&#xff1a; nohup java -jar /soft/gitee-demo-0.0.1-SNAPSHOT.jar >mylog.log 2>&1 & 简化写法&#xff1a; nohup java -jar /s…

Unity地面交互效果——3、曲面细分基础知识

大家好&#xff0c;我是阿赵。   之前介绍了使用动态法线贴图混合的方式模拟轨迹的凹凸感&#xff0c;这次来讲一下更真实的凹凸感制作。不过在说这个内容之前&#xff0c;这一篇先要介绍一下曲面细分着色器(Tessellation Shader)的用法。 一、为什么要做曲面细分 之前通过法…

【APUE】并发 — 线程

目录 一、线程的概念 1.1 定义 1.2 POSIX 线程标准 1.3 线程标识 1.4 相关函数 1.5 一些补充 二、线程的创建、终止与取消 2.1 创建 2.2 终止 2.2.1 return 2.2.2 pthread_exit 2.3 取消 2.3.1 函数介绍 2.3.2 禁止线程被取消 2.3.3 线程取消方式 2.4 清…

开发一款直播弹幕游戏需要多少钱?

开发一款直播弹幕游戏需要多少钱&#xff1f;有好多朋友在咨询过弹幕游戏的开发价格后&#xff0c;都会比较吃惊&#xff0c;一款体量这么小的游戏为什么动辄就要几万块甚至十几万&#xff1f; 我来给你们说分析一下原因&#xff0c;这种游戏如果脱离开直播间&#xff0c;可以…

matlab矩阵的输入

一段时间不操作感觉有些模糊&#xff1b;下面复习一下&#xff1b; 矩阵的数字之间用空格分开&#xff0c;每一行以分号结尾&#xff1b; 空格多几个也是可以识别的&#xff1b; 数字也可以用逗号隔开&#xff0c;只要一行的结尾是分号即可&#xff1b; 空格多输入几个是可…

NEFU数字图像处理(5)图像压缩编码

一、概述 1.1简介 图像压缩编码的过程是在图像存储或传输之前进行&#xff0c;然后再由压缩后的图像数据&#xff08;编码数据&#xff09;恢复出原始图像或者是原始图像的近似图像 无损压缩&#xff1a;在压缩过程中没有信息损失&#xff0c;可由编码数据完全恢复出原始图像有…

Java web(五):会话技术

文章目录 一、会话跟踪技术二、Cookie三、Session四、Cookie和Session的区别五、项目实战 一、会话跟踪技术 二、Cookie 在JSP页面如何获取Cookie&#xff1f; 方式&#xff1a;${cookie.key.value} //key指存在cookie的键的名称 三、Session Session是基于Cookie实现的&…

python 机器学习 常用函数

一 np.random.randint "randint" 是 "random integer" 的缩写&#xff0c;表示生成随机整数。 np.random.randint 是 NumPy 库中的一个函数&#xff0c;用于生成随机整数。以下是该函数的一般语法&#xff1a; np.random.randint(low, high, size)其中…

Javaweb之HTML,CSS的详细解析

2. HTML & CSS 1). 什么是HTML ? HTML: HyperText Markup Language&#xff0c;超文本标记语言。 超文本&#xff1a;超越了文本的限制&#xff0c;比普通文本更强大。除了文字信息&#xff0c;还可以定义图片、音频、视频等内容。 标记语言&#xff1a;由标签构成的语言…

王道计算机网络

一、计算机网络概述 (一)计算机网络基本概念 计算机网络的定义、组成与功能 定义&#xff1a;以能够相互共享资源的方式互连起来的自治计算机系统的集合。 目的&#xff1a;资源共享&#xff0c; 组成单元&#xff1a;自治、互不影响的计算机 网络协议 从不同角度计算机网络…