【C++】类和对象(上)

目录

1.面向过程和面向对象

2.类的定义

3.类的访问限定符及封装

3.1访问限定符

3.2封装

4.类的作用域

5.类的实例化

6 类对象模型

6.1类成员储存方式

6.2类对象的大小

7.this指针

7.1 this指针的引出

7.2 this指针的特性

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

8.1构造函数

8.1.1概念

8.1.2特性

8.2析构函数 

8.2.1 概念

8.2.2特性

8.3拷贝构造函数

8.3.1概念

8.3.2特性

8.4赋值运算符重载

8.4.1运算符重载

8.4.2 赋值运算符重载

9.const成员

10.取地址及const取地址操作符重载(随便看看/了解)

11.日期类的实现


1.面向过程和面向对象

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。

【C++】类和对象(上)_第1张图片

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

【C++】类和对象(上)_第2张图片

2.类的定义

C++中类的关键字既可以用class,也可以用struct。常用的是class。

类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。

class className
{
    // 类体:由成员函数和成员变量组成
}; 

例如:一个学生类

class Student
{
public:
    void StudentInfoInit(const char* name, const char* gender, int age)
    {
        strcpy(_name, name);
        strcpy(_gender, gender);
        _age = age;
    }
    void Print()
    {
        cout << _name << " " << _gender << " " << _age << endl;
    }

private:
    char _name[20];
    char _gender[3];
    int _age;
};


int main()
{
    Student s;
    s.StudentInfoInit("张三", "男", 18);
    s.Print();
    return 0;
}

类的两种定义方式:

1. 声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。(例如:上面的学生类)

2. 声明放在.h文件中,类的定义放在.cpp文件中。
例如:【C++】类和对象(上)_第3张图片

【C++】类和对象(上)_第4张图片

一般情况下,使用第二种方式。
 

3.类的访问限定符及封装

3.1访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。

访问限定符包括:

  1. public(公有)
  2. private(私有)
  3. protected(保护)

1. public修饰的成员在类外可以直接被访问
2. protectedprivate修饰的成员在类外不能直接被访问(此处protectedprivate是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. class的默认访问权限为privatestruct为public(因为struct要兼容C)

问题:C++中struct和class的区别是什么?

C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。
和class是定义类是一样的,区别是struct的成员默认访问方式是publicclass是的成员默认访问方式是private

3.2封装

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

通过权限来限制类中的代码外界无法看到更无法更改,只能通过接口来直接使用。

类就像一个生产车间,外界的人只需知道丢进去什么材料(参数),然后这个车间产生出来的是什么(接口),而把生产过程封装了,你不知道是怎么生产的。

一般情况下,类中的成员变量是私有的(private),即不允许被外部直接访问和修改的,只能通过成员函数访问或修改。而成员函数一般是公有的(public),即可以被外部访问的。

class A
{
public :
	void Print()// 公有成员
	{
		cout << _a << endl;
	}

private:
	int _a;// 私有成员
};

开放一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。

4.类的作用域


类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。

例如:在.cpp文件中定义成员函数时,需要在函数名前指明函数的类域。

#include"Student.h"


void Student::StudentInfoInit(const char* name, const char* gender, int age)
{
    strcpy(_name, name);
    strcpy(_gender, gender);
    _age = age;
}
void Student::Print()
{
    cout << _name << " " << _gender << " " << _age << endl;
}

5.类的实例化

用类类型创建对象的过程,称为类的实例化

【C++】类和对象(上)_第5张图片

1. 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
3. 做个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间

例如: 

#include

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

class Student
{
public:
    void StudentInfoInit(const char* name, const char* gender, int age);
       
    void Print();
        
private:
        char _name[20];
    char _gender[3];
    int _age;
};


void Student::StudentInfoInit(const char* name, const char* gender, int age)
{
    strcpy(_name, name);
    strcpy(_gender, gender);
    _age = age;
}
void Student::Print()
{
    cout << _name << " " << _gender << " " << _age << endl;
}

int main()
{
	Student s1;// 实例化对象1
	s1.StudentInfoInit("张三", "男", 18);

	Student s2;// 实例化对象1
	s2.StudentInfoInit("李四", "男", 20);

	return 0;
}

6 类对象模型

6.1类成员储存方式

class A
{
public:
    void PrintA()
    {
        cout<<_a<

C语言中的结构体的大小计算前面我们已经学习过。

结构体中存在内存对齐,那类中是否存在内存对齐?

而且类中存在成员函数,那大小又该如何计算?

将类中的成员函数存放在栈中缺陷:

每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。

那么如何解决呢?

解决方法是:类中只保存成员变量,成员函数存放在公共的代码段
这样在创建对象时就不会存放重复代码。

【C++】类和对象(上)_第6张图片

6.2类对象的大小

// 类中既有成员变量,又有成员函数
class A1 {
public:
    void f1(){}
private:
    int _a;
};

// 类中仅有成员函数
class A2 {
public:
    void f2() {}
};

// 类中什么都没有---空类
class A3
{};

sizeof(A1) : ______ sizeof(A2) : ______ sizeof(A3) : ______
类中的成员函数由于存放在公共代码段中,所以在计算类的大小时不包含成员函数的大小。

而对于成员变量,计算方法和C语言中对结构体大小的计算方法一致。

------->>>>>>>结构体大小的计算

对于没有成员变量的类和空类,类中没有成员变量,按照前面的做法他的大小为0,那么内存将不会为他分配空间。但是这个类确实是存在的,存在就应该为他分配空间。在这里,会为他分配1个字节的空间。【C++】类和对象(上)_第7张图片

结论:一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类。
 

7.this指针

7.1 this指针的引出

我们先来定义一个日期类Date

#include

using namespace std;

class Date
{
public:
	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;
	Date d2;
	d1.Init(2021, 12, 4);
	d2.Init(2021, 12, 5);

	return 0;
}

对于上述类,有这样的一个问题:
Date类中有 Init 成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

7.2 this指针的特性

  1. this指针的类型:类类型* const
  2. 只能在“成员函数”的内部使用
  3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
  4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

所以上面的Date类我们可以看成是这样的:

#include

using namespace std;


// this指针
class Date
{
public:
	// void Init(int*this,int year, int month, int day) 
    // 在运行时会自动加入this指针,但自己不能加

	void Init(int year, int month, int day)
	{
						    // 使用时可以在成员函数中使用this指针
							 
		                    // this->_year = year;
		_year = year;      
		                    // this->_month = month;
		_month = month;
		                    // this->_day = day;
		_day = day;
		                    
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date D1;
	Date D2;
	D1.Init(2021, 12, 4);// D1.Init(&D1,2021, 12, 4); 实际编译器传参
	
	D2.Init(2021, 12, 5);// D2.Init(&D2,2021, 12, 4);


	return 0;
}

如果将this指针的地址和对象的地址比较,也能证明this指针的存在:

【C++】类和对象(上)_第8张图片

问题:

1. this指针存在哪里?答:一般存放在栈中,不同的编译器不同。
2. this指针可以为空吗?答:可以为空。
 

//当this指针为空时需要注意的问题
class A
	{
	public:
		void fun1()  // 程序崩溃,原因是在fun函数中this指针为空,空指针不能访问
		{
			cout << _a << endl;
		}

		void fun2()  // 程序不会崩溃,成员函数内部不会访问成员变量,不存在访问空指针
		{
			cout << "fun2" << endl;
		}
	private:
		int _a;

	};


int main()
{
	A* p = nullptr;  

	p->fun1();        // 这里对空指针p访问不会崩溃,原因是这里其实不是对p的访问,而是调用成员 
                     
	p->fun2();        // 函数,而成员函数的地址不在类空间中,而在公共代码段

	return 0;
}

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

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

class Date {};

【C++】类和对象(上)_第9张图片

8.1构造函数

8.1.1概念

对于对象的初始化我们不能像最开始的学生类那样写初始化函数,在类中对初始化函数有特别要求。

进行初始化工作的函数称为构造函数。

在C语言中创建变量时可能会忘记初始化的情况,就会导致程序出现问题。C++中为了使每个对象实例化时都被初始化,就有了构造函数。

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

8.1.2特性

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

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数可以重载
// 默认成员函数
// 构造函数(无返回值,函数名与类名相同,对象实例化的时候程序自动调用)(编译器会自动生成,也可以自己写,大部分都需要自己写)
class Date
{
public:
	// 构造函数名与类名相同,保证对象一定会被初始化
	Date(int year = 2000, int month = 9, int day = 18)//全缺省函数
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// 构造函数重载,对象有多种初始化方式
	//Date()
	//{
	//	_year = 2000;
	//	_month = 9;
	//	_day = 18;
	//}


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


int main()
{
	Date D1(2021, 12, 4);
	D1.Print();

	Date D2; // 调用无参的构造函数
	D2.Print();
    
    //Date D3();  // 不能这样调用

	return 0;
}

对于构造函数,如果未显示定义,编译器会自动生成一个构造函数。

这个构造函数对内置类型不会处理,还是随机值;

对于自定义类型会去调用他们自己的构造函数初始化。

class A
{			// 构造函数我们自己不写,编译器会自动生成,写了编译器就不会生成了,所以叫默认函数
public:               // 内置类型(基本类型)不初始化
	A(int a = 0)     // 自定义类型(class,struct)编译器会去调用他们自己的默认构造函数初始化                   
    {                                
		_a = a;
	}
	void Print()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

class B
{
public:
	void Print()
	{
		_aa.Print();
	}
private:
	A _aa;        // 若一个类中成员变量有自定义类型,那么他的构造函数需要自己写
};


int main()
{
	B b;
	b.Print();
	return 0;
}

默认构造函数,如果理解为我们不写,编译器默认生成的那一个,这个理解是不全面的。

  1.  我们不写,编译器默认生成的
  2.  我们自己写的无参数的
  3.  我们写的全缺省的

 总结:不用传参数就可以调用的函数就是默认构造函数

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

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

private:
	int _year; // 为了区分成员变量,一般在变量名前加 _ ,当然不同的公司可能有不同的要求
	int _month;
	int _day;

};


// 总结:构造函数的细节很多,实际中大多数情况都是要自己写构造函数完成初始化,
//      并且建议一般情况都是写一个全缺省的构造函数,这种方式能够适应大多数场景。

8.2析构函数 

8.2.1 概念

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

8.2.2特性

析构函数是特殊的成员函数。

其特征如下:

  1.  析构函数名是在类名前加上字符 ~。
  2.  无参数无返回值。
  3.  一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4.  对象生命周期结束时,C++编译系统系统自动调用析构函数。
// 析构函数(资源清理工作(例如:malloc))
// 1.析构函数名时在类名前加上字符~(~fun)
// 2.无参无返回值(不能重载)
// 3.一个类有且只有一个析构函数,若未显示定义,系统会自动生成默认的析构函数
// 4.对象声明周期结束时,C++编译系统自动调用析构函数

// 注:
//    1.对于内置类型成员变量我们不需要写析构函数,编译器会自动生成,因为对象内没有资源需要清理,所以也不会做什么事情
//    2.若存在自定义类型成员(malloc,打开文件等),则需要我们自己实现析构函数,清理空间。


class Stack
{
public:
	// 构造函数
	Stack(int capacity = 4)
	{
		if (capacity == 0)
		{
			arr = NULL;
			_size = _capacity = 0;
		}
		else
		{
			arr = (int*)malloc(sizeof(int) * capacity);
			_capacity = capacity;
			_size = 0;
		}
	}

	void Push(int x)
	{
		arr[_size] = x;
		_size++;
	}


	// 析构函数
	~Stack()
	{
		free(arr);
		arr = nullptr;
		_size = _capacity;
	}
private:
	int* arr;
	int _size;
	int _capacity;
};

// 同样对于默认析构函数:对于内置类型成员不做处理,对于自定义类型会去调用他的析构函数

int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	Stack s2;
	s2.Push(1);
	s2.Push(2);
	// 因为对象是定义在函数中,函数调用会建立栈帧,
	// 栈帧中的对象构造和析构也要很符合后进先出。
	// 所以这里的顺序是: s1先构造 -> s2后构造 -> s2先析构 -> s1后析构
}

8.3拷贝构造函数

8.3.1概念

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

8.3.2特性

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
  3. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
  4. 对于像Stack这样的类不能使用默认的拷贝构造函数,使用会导致相同内存被析构两次。
// 拷贝构造函数
// 1.函数名和类名相同,即是构造函数的重载
// 2.参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
// 3.默认成员函数,我们不写编译器会自动生成拷贝构造;
//   这个拷贝构造对内置类型会完成浅拷贝(值拷贝)。
//   但是对自定义类型(Stack等),编译器还是会对他进行浅拷贝,这样就会将原来对象开辟空间的地址给拷贝了,这样的后果是:
//   其一:两个对象共用一块空间,在调用析构函数时该空间会被释放两次;
//   其二:其中一个对象插入删除数据会导致另一个对象也插入删除了数据。
// 所以像Stack这样的类,编译器默认生成的拷贝构造完成的是浅拷贝,不满足要求,需要实现深拷贝(以后讲)


class A
{
public:
	A(int a = 0)
	{
		_a = a;
	}

	A(const A& a)
	{
		_a = a._a;
	}
private:
	int _a;
};


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

	//Date(Date d)             // 若使用传值拷贝,则在传参时会先调用拷贝构造函数(因为传值拷贝时需先将原数据拷贝到临时空间       //{                        // 再使用临时空间中的数据进行赋值),这样又会出现拷贝,这样就会陷入循环往复过程。   
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}

	Date(const Date& d)            // 若使用传引用拷贝,传参时不会出现将原数据拷贝到临时空间的做法,
        						 // 会直接使用原空间中的数据直接赋值。
	{							   // 这里传参时也推荐加 const 因为被拷贝的对象是不应该被修改的
		_year = d._year;           
		_month = d._month;         // 注意:从此以后函数传参,自定义类型的对象,一般推荐使用传引用传参
		_day = d._day;             //       如果使用传值传参,每次都会调用拷贝构造函数。
		_aa = d._aa; // 若一个类中含有自定义类型会自动调用自己的拷贝构造函数
	}

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

8.4赋值运算符重载

8.4.1运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)

注意:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型或者枚举类型的操作数
  3. 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的
  5. 操作符有一个默认的形参this,限定为第一个形参
  6. .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载

例如:==

class Date
{
public:
    Date(int year = 2022, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
    // bool operator==(Date* this, const Date& d2)
    // 这里需要注意的是,左操作数是this指向的调用函数的对象

    bool operator==(const Date& d2)
    {
        return _year == d2._year;
            && _month == d2._month
            && _day == d2._day;
    }
private:
    int _year;
    int _month;
    int _day;
};
void Test ()
{
    Date d1(2021, 1, 1);
    Date d2(2021, 1, 2);
    cout<<(d1 == d2)<

8.4.2 赋值运算符重载

赋值运算符需考虑四点:

  1. 参数类型
  2. 返回值
  3.  检测是否自己给自己赋值
  4.  返回*this
  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。
class Date
{
public:
	Date(int year = 2000, int month = 9, int day = 18)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 赋值运算符的重载也是一个默认成员函数,也就是说我们不写编译器会自动生成
	// 编译器默认生成的赋值运算符重载跟拷贝构造的特性是一样的
	// 1.针对内置类型,会完成浅拷贝
	// 2.针对自定义类型,会调用他的赋值运算符重载(Stack等需要自己写)完成拷贝
	Date& operator=(const Date& d) // 有返回值是为了解决连续赋值的情况
	{
		if (this != &d) //不是自己给自己赋值才需要拷贝
		{
			_year = d._year;            // 赋值运算符重载也是拷贝行为,不一样的是,
			_month = d._month;          // 拷贝构造是创建一个对象时,将同类对象初始化的拷贝。
			_day = d._day;              // 这里的赋值拷贝时两个对象都已经存在了,都被初始化过了
		}
		return *this;
	}

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

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


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

	Date d2(2000, 12, 25);
	d2.Print();

	d2 = d1;
	d2.Print();

	Date d3;
	d3 = d2 = d1;
	d2.Print();
	d3.Print();

	Date d4 = d1; // 拷贝构造or赋值重载?
	return 0;     // 拷贝构造:拿一个已经存在的对象去靠拷贝初始化另一个要创建的对象
}                 // 赋值重载:两个已经存在的对象拷贝
                  // 所以这里应该是拷贝构造

9.const成员

看代码:

根据上面讲的运算符重载,这里实现一个==的重载

#include

using namespace std;


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

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

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

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


int main()
{
	Date d1(2021, 12, 11);
	Date d2(2021, 1, 10);

	cout << (d1 == d2) << endl;
    d1.Print();
    d2.Print();
	return 0;
}

细心的你肯定会发现,成员函数bool operator==(const Date& d)的实现有问题,平时写代码可能也会犯这种将==写成=的情况。而这里编译是不会报错的,但会影响结果和this指针指向的对象。

按照一般的方法,只需要早this指针前加const 即可,但这里const是隐藏的参数。

为了防止this指针被修改,C++的类中可以在函数的后面加上const,表示this指向的对象不可修改。
好处:函数中不小心改变的成员变量,编译时会被检查出来。
建议:成员函数中,不需要改变成员变量,都加上const。

bool operator==(const Date& d) const;

问题:

  1.  const对象可以调用非const成员函数吗?
  2.     非const对象可以调用const成员函数吗?
  3.      const成员函数内可以调用其它的非const成员函数吗?
  4.     非const成员函数内可以调用其它的const成员函数吗?

    答:非const可以调用const;const不能调用非const(权限不能放大)

10.取地址及const取地址操作符重载(随便看看/了解)

对类取地址时,其实&符号是被重载过的:

class Date
{
public :
    Date* operator&()
    {
        return this ;
    }

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

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

11.日期类的实现

Date.h

#pragma once

#include

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

class Date 
{
public:
	Date(int year = 2021, int month = 12, int day = 9);
	void Print() const;
	// 析构,拷贝构造,赋值重载,可以使用默认生成的
	// ~Date();
	// Date(const Date& d);

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

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

	Date& operator--();
	Date operator--(int);

	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;
private:
	int _year;
	int _month;
	int _day;
};

Date.cpp

#include"Date.h"

// 获取每个月的天数
inline int GetMonthDay(int year, int month)
{
	static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    int day = arr[month];
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
		day += 1;
	}
	return day;
}

// 构造函数
Date::Date(int year, int month, int day)
{
    // 判断日期的合法性
	if ((year >= 0) 
    && (month > 0 && month < 13) 
    && (day > 0 && day <= GetMonthDay(year, month)))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "输入日期错误" << endl;
	}
	
}

// 输出日期
void Date::Print() const
{
	cout << _year << "-" << _month << "-" << _day << endl;
}


// 日期 += 天数
Date& Date::operator+=(int day)// 出了作用域之后对象未被销毁,这里使用传引用返回
{                              // 注:能使用传引用返回尽量使用,减少拷贝次数
	if (day < 0)
	{
		*this -= (-day);// 若day<0,+=不能满足要求,这里复用-=
	}
	else
	{
		_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 tmp(*this);// 日期 + 天数,原对象不会改变,这里需要创建临时对象
	tmp += day;// 复用 += 操作  (能复用尽量复用)

	return tmp;// 返回临时对象(所以这里不能使用传引用返回)
}

// 日期 -= 天数
Date& 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 Date::operator-(int day) const
{
	Date tmp(*this);// 实现参考 +
	tmp -= day;// 复用 -=
	return tmp;
}


// 前置++
Date& Date::operator++()
{
	*this += 1;// 复用 +=
	return *this;// 返回+1之后的对象
}

// 后置++
// 为了与前置++区分,这里使用函数重载,对后置++加了一个参数,且这个参数没有任何作用,所以可以只写一个int
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;// 返回+1之前的对象
}

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

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

// d1 < d2
bool Date::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;
}

// d1 <= d2 
bool Date::operator<=(const Date& d) const
{
	return (*this < d) || (*this == d);// 复用 <= 、==
}

// d1 > d2
bool Date::operator>(const Date& d) const
{
	return !(*this <= d); // 复用 <=
}

// d1 >= d2
bool Date::operator>=(const Date& d) const
{
	return !(*this < d);// 复用 <
}

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

// d1 != d2
bool Date::operator!=(const Date& d) const
{
	return !(*this == d);// 复用 ==
}

// 日期-日期
int Date::operator-(const Date& d) const
{
	Date d1(*this);
	Date d2(d);
	int falg = 1;
	if (d1 < d2)// 小 - 大
	{
		d1 = d2;
		d2 = *this;
		falg = -1;
	}
	int count = 0;
	while (d1 != d2)
	{
		d2++;       // 复用 ++ 
		count++;
	}
	
	
	return count*falg;
}

最后对6个默认成员函数来一个简单的总结:

【C++】类和对象(上)_第10张图片

 创作不易,点个赞吧~

你可能感兴趣的:(C++,c++,类和对象)