C++ 类和对象

面向过程/面向对象

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

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

引入

C语言中结构体只能定义变量,但是在C++中,结构体不仅可以定义变量,也能定义函数

以栈为例,C++里对于栈的函数可以这样子写:

//C++
struct Stack
{
	//成员变量
	int* a;
	int top;
	int capacity;
	//成员函数——直接将函数与成员变量放在一起定义
	void Init()
	{
		a = nullptr;
		top = capacity = 0;
	}
	void Push(int x)
	{
		
	}
};

//C
void StackInit(struct Stack* ps)
{
    
}
void StackPush(struct Stack* ps,int x)
{
    
}

类的定义

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

};//有分号

类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者

成员函数。

两种定义方式

1.声明和定义全部放在类体中

class Date
{
public:
	void Init(int year, int month, int day)
	{
		//域搜索,一个是类域的year,一个是函数局部域的year
		//所以需要加上_来区分
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;//属于声明
	int _month;
	int _day;
};

2.类声明放在.h中,成员函数定义在.cpp文件中,且成员函数名前需要加类名

//.h
class Stack
{
public:
	//成员函数
	void Init();
	void Push(int x);
	int Top();
private://访问限定符,访问到下一个访问限定符或者到函数末尾才会结束
	//这样在主函数里就无法访问
	int* a;
	int top;
	int capacity;
};

//.cpp
void Stack::Init()//全局函数,需要在其他文件里去找,而不是到类域里去找
{
	a = nullptr;
	top = capacity = 0;
}
void Stack::Push(int x)//需要有访问符
{
	if (top == capacity)
	{
		size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
		a = (int*)realloc(a, sizeof(int) * newcapacity);
		capacity = newcapacity;
	}
	a[top++] = x;
}
int Stack::Top()
{
	return a[top - 1];
}

访问限定符

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

说明

1. public修饰的成员在类外可以直接被访问

2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的) 3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止

4. 如果后面没有访问限定符,作用域就到 } 即类结束。

5. class的默认访问权限为private,struct为public(因为struct要兼容C)

访问

以Date类为例

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year<<" ";
		cout << _month << " ";
		cout << _day << " ";
		cout << endl;
	}
//private:
	int _year;//因为此处是声明,不是定义
	//声明特点:只有一个躯体,不能访问,没有分配空间
	int _month;
	int _day;
};

1.由于private中对于成员变量,是声明而不是定义——故不能直接访问,需要先建立个对象

int  main()
{
    Date d;
    d._year=1//err
    return 0;    
}

无法访问——因为类中_year被private访问限定符限制住了,所以不能在类外访问

如何解决——将' private '注释掉

2.类外调用函数

若是想调用函数,首先就需要定义一个对象d——然后在用d.函数名()去访问

例如此处的打印函数,想打印类的内容

int main()
{
    Date d;
    d1.Init(2022,2,22);//先初始化再打印
    d1.Print();
    return 0;
}

类的大小(存储方式)

同理,我们对于日期类Date进行讨论

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

进行大小的打印

int main()
{
    Date d1;
    cout<<sizeof(d1)<<endl;
    cout<<sizeof(Date)<<endl;
    return 0;
}

类的大小仅取决于成员变量,成员函数存放在公共代码区中,不会计入类的大小,仅对象的成员变量会算入大小

 若成员变量不为同类型呢?

class A4
{
public:
	void f1() {}

private:
	char _c;
	int _a;
};

为8——同理按照编译器内存对齐的规律来进行的 

类的实例化

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

1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;

2. 一个类可以实例化出多个对象,实例化出的对象才占用实际的物理空间,存储类成员变量

int main()
{
    Date._year=1;//err
    return 0;
}

this指针

现在对于一个日期的类Date

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,3,15);
    Date d2;
    d2.Init(2023,3,15);

    return 0;
}

对于Init 和 Print 函数,看起来分别有3个和0个参数,但事实并非如此——分别有4个和1个,为什么?

这两个函数内部还有一个隐含参数——this指针,会将函数进行一个修改:

 同理,对于主函数里的初始化函数:

 建立联系

 所以函数可以写成

void Init(int year,int month,int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
	void Print()
	{
		cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
	}

注意!!!!

this指针在实参和形参位置不能写,但是在类里可以直接写出来使用(但没必要)-指向当前对象

若在函数里嵌套另一个成员函数呢?指向两个Print函数的this指针是否一样?

void Init(int year,int month,int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;

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

不一样——一个对象中,两个this指针是各自的不同的指针变量,但是指向的地方是一样的。

若是两个不同的对象,那么这两个this指针不仅不同,指向的空间也不同

特性

1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。

2. 只能在“成员函数”的内部使用

3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给

this形参。所以对象中不存储this指针。

4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传 递,不需要用户传递

两个例题

1

有以下代码

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
     void Print()
     {
     cout << "Print()" << endl;
     }
private:
     int _a;
};
int main()
{
 A* p = nullptr;
 p->Print();
 return 0;
}

由于定义了一个对象p,但是为空指针,要去调用成员函数——需要在对象里找吗?

不,成员函数定义在公共函数区域里,所以可以直接调用

2

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:
    void PrintA() 
   {
        cout<<_a<<endl;
   }
private:
 int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}

此处运行崩溃——因为p是空指针,且要访问_a,需要用到this指针去访问this->_a

但此处this指针为空,所以会导致运行崩溃

综上,类的访问都是先传递this指针,再去找到函数的地址并且使用函数

this指针指向的是需要作用的对象

类的6个默认成员函数

若有

class Date{};

那么这个类成为空类——但是这个类里什么都没有吗????

实际上,编译器会自动生成以下6个默认成员函数

构造函数

构造函数负责初始化,函数名就是类名

特征

1. 函数名与类名相同。

2. 无返回值。

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

4. 构造函数可以重载。

class Date
{
public:
    //正常初始化
    void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    //构造函数
    Date()//初始化
    {
        _year=1;
        _month=1;
        _day=1;
    }
    Date(int year, int month, int day)//直接输入,用缺省参数
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
    int _year;
    int _month;
    int _day;   
};

为了方便,我们可以直接将两个构造函数合并

Date(int year=1, int month=1, int day=1)//直接输入,用全缺省参数,减少了代码量
	{
		_year = year;
		_month = month;
		_day = day;
	}

且考虑到其他函数内部会没有定义的值,所以可以在声明成员变量的时候,给予其缺省值

故类可以修改为

class Date
{
public:
	
	Date(int year=1, int month=1, int day=1)//直接输入,用全缺省参数,减少了代码量
	{
		 cout << "Date(int year=1, int month=1, int day=1)" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()//实际上为1个参数
	{
		cout << _year << "/" << _month << "/" << _day << endl;
		//cout << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

如果用户显式定义了构造函数,那么编译器就不会生成默认的构造函数

构造函数的出现,减少了代码量

int main()
{   
    //非构造
    Date d1;
    d1.Init(2022,2,22);

    Date d2(2022,2,22);
    return 0;
}

一行代码就能实现对象的创建和初始化

注意

不实现构造函数的情况下,编译器会生成默认的构造函数,虽然日期类Date对象d调用了默认构造函数但是其成员变量_year/_month/_day仍然为随机值,这是为什么?

——C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型。

因此,为了解决对于内置类型成员不初始化的缺陷,我们可以在成员变量声明时给予默认值

private:
	int _year=1;
	int _month=1;
	int _day=1;

析构函数

与构造函数相反,析构函数是用于对于对象中资源的清理

特征

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

同理以Date为例

class Date
{
public:
	Date(int year=1, int month=1, int day=1)//直接输入,用全缺省参数,减少了代码量
	{
		 cout << "Date(int year=1, int month=1, int day=1)" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()//实际上为1个参数
	{
		cout << _year << "/" << _month << "/" << _day << endl;
		//cout << this << endl;
	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year=1;//声明,默认会用给的缺省值进行初始化,内置类型不作处理
	int _month=1;//作为缺省值,会补充其他函数里没有定义的值
	int _day=1;
};

若不写析构函数,那么编译器就会默认生成析构函数来进行资源的清理

以栈来展现构造和析构函数

class Stack
{
public:
	/*Stack()
	{
		a = nullptr;
		top = capacity = 0;
	}*/
	
	Stack(size_t n = 4)//不传入值则就默认4个空间
	{
		cout << "Stack(size_t n = 4)" << endl;
		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if (a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			top = 0;
			capacity = n;
		}
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(a);
		a = nullptr;
		top = capacity = 0;
	}
	void Push(int x)
	{
		if (top == capacity)
		{
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			if (tmp = a)
			{
				cout << capacity << "原地扩容" << endl;//原地扩容还是异地扩容呢?
				//若打印就原地,否则就异地
			}
			else
			{
				cout << capacity << "异地扩容" << endl;
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[top++] = x;
	}
	int Top()
	{
		return a[top - 1];
	} 
	void Pop()
	{
		assert(top > 0);
		--top;
	}
	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}
	bool Empty()
	{
		if (top == 0)
			return 1;
		return 0;
	}
private:
	int* a;
	int top;
	int capacity;
};

在构造函数处,我们选择用了缺省参数,这样可以随机初始化自己想要的栈的大小

int main()
{
    Stack st1;
    Stack st2(10);
    return 0;
}

 那么,对于类中这种定义的函数,我们可以这样子调用

int main()
{    
    Stack st2(10);
    st2.Push(1);
	st2.Push(2);
	st2.Push(3);
	st2.Push(4);
	st2.Push(5);
	st2.Push(6);
	st2.Push(7);
	st2.Push(8);
	st2.Push(9);
	st2.Push(10);
	while (!st2.Empty())
	{
		cout << st2.Top() << " ";
		st2.Pop();
	}
	cout << endl;
    return 0;
}

也可以直接传入1000的量,这样子无需要扩容

    Stack st2(1000);    
	for (size_t i = 0; i < 1000; i++)
	{
		st2.push(i);
	}

注意:

1.会自动构造 
2.后定义,先析构,若没有写析构函数,那么编译器就会默认生成一个析构函数
3.内置类型成员不处理,但是会自定义类型成员会调用在这个成员的析构函数

但是栈里一定要写析构函数!!!——析构函数是浅处理,就是处理像int /char 这种普通类型的变量,随着函数的结束就会销毁,但是栈里有定义——int* a,也就是数组,需要手动处理,不然会导致内存泄漏的问题!!!!

特殊

class MyQueue
{
private:
	Stack _pushst;//初始化的时候已经生成
	Stack _popst;

};

对于此类,尽管有int* a指针定义,也需要写——因为定义Stack的时候就已经生成了相关的构造和析构函数,此时就不需要我们去写

除此之外,构造函数和析构函数的调用就类似于栈,先构造的最后析构

拷贝构造函数

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

特征:

1. 拷贝构造函数是构造函数的一个重载形式。

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

无穷递归错误情况代码:

class Date
{
public:
    Date(Date d)   // 错误写法:编译报错,会引发无穷递归
     {
     _year = d._year;
     _month = d._month;
     _day = d._day;
     }
private:
     int _year;
     int _month;
     int _day;
};

 同理,在自定义函数里,也不能直接传值传入

void func2(Stack s)
{
	s.Push(1);
	s.Push(2);
}

这里直接传参的话,那么就是用形参s接收一个对象,此时对象和形参都指向一个空间,那么函数结束后形参指向的空间会进行销毁,那么原本对象指向的空间将会不存在,会导致报错(析构两次)

那怎么解决?——用引用,获取对象的地址 ,改为如下:

class Date
{
public:
    Date(Date& d)   // 错误写法:编译报错,会引发无穷递归
     {
     _year = d._year;
     _month = d._month;
     _day = d._day;
     }
private:
     int _year;
     int _month;
     int _day;
};

void func2(Stack& s)
{
	s.Push(1);
	s.Push(2);
}

如何定义?

——有' () '  也有 ' = ' 两种形式

int main()
{
    Date d1(2022,2,22);
    Date d2(d1);
    Date d3=d1;
    d1.Print();
    d2.Print();
	d3.Print();
    return 0;
}

先构造d1,然后直接将d1拷贝构造给d2和d3,最后再逐一析构

若不写拷贝构造函数呢?

—— 编译器会自己生成

class Date
{
public:
    //Date(Date& d) 
     //{
    // _year = d._year;
     //_month = d._month;
     //_day = d._day;
     }
private:
     int _year;
     int _month;
     int _day;
};

int main()
{
    Date d1(2022,2,22);
    Date d2=d1;//pass
    return 0;
}

 但是对于栈,若不写拷贝构造函数——则会报错err

Stack s1;
Stack s2=s1;

 所以对于栈一定要进行自定义拷贝函数

Stack(const Stack& s)//用const防止代码赋值写反导致改变了原对象的内容
	{
		a = (int*)malloc(sizeof(int) * s.capacity);
		if (a == NULL)
		{
			perror("malloc申请失败");
			return;
		}
		memcpy(a, s.a, sizeof(int) * s.top);
		capacity = s.capacity;
		top = s.top;
	}

总结:

1.内置类型,值拷贝
2.自定义类型,调用他的拷贝
总结:日期类Date 不需要我们自己实现拷贝构造,默认生成就可以用
          栈类Stack 需要我们自己实现深拷贝的拷贝构造,默认生成会出现问题
          因为Stack需要释放空间,防止栈溢出内存泄漏,但是Date里的变量会随着空间的销毁而销毁。

运算符重载

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

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

再次以日期类Date为例

实现日期类的比较——两个不同日期的大小区分

全局

bool operator<(const Date& d1, const Date& d2)//用一个函数去比较,有别名接收
{
	//1.访问对象,将private注释掉
	if (d1._year < d2._year)
	{
		return true;
	}
	else if (d1._year == d2._year && d1._month < d2._month)
	{
		return true;
	}
	else if (d1._year == d2._year && d1._month == d2._month && d1._day < d2._day)
	{
		return true;
	}
	else
		return false;
}

此处需要用到拷贝构造函数,用两个形参承载,并且进行比较(此时不能用private进行限定)

如果写入类里面呢?

注意

1.不能通过连接其他符号来创建新的操作符:比如operator@

2.重载操作符必须有一个类类型参数

3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this

5.    .      *   ::     sizeof     ?:    .这五个运算符不能重载

类内部定义运算符重载函数

bool operator<(const Date& d)//用一个函数去比较,用别名接收,参数个数为运算数-1
	{
		//1.访问对象,将private注释掉
		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;
	}

在主函数里调用

int main()
{
	//自定义类型怎么比较大小?——本身不支持比较,需要通过运算符重载才可以进行比较
	Date d1(2023, 7, 21);
	Date d2(2022, 8, 21);
	cout << (d1 < d2) << endl;
	cout << (operator<(d1, d2)) << endl;
	cout << (d1.operator<(d2)) << endl;
    return 0;
}

需要注意的是,左操作数是this,指向调用函数的对象

bool operator<(Date* this,const Date& d)

实现日期是否相同

bool operator<(const Date& d)//用一个函数去比较,用别名接收
	{
		//1.访问对象,将private注释掉
		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 operator==(const Date& d)
{
		return _year == d._year && _month == d._month && _day == d._day;
}

this指针的复用!!!

对于判断日期是否大于,可以通过对两个函数的复用来实现

bool operator<=(const Date& d)//此时d1作为operator,this指向d1,然后d2作为d
		
{
		return *this < d || *this == d;//直接用this指针实现复用其它函数
}

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

日期天数的改变

因位需要改变天数,也就是在原对象上进行数据修改,所以要拷贝构造

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


Date& operator+=(int day)
		//传值返回需要用拷贝构造,所以用引用返回
{
	 _day += day;
	 while (_day>GetMonthDay(_day,_month))
	 {
		 _day -= GetMonthDay(_day, _month);
		 ++_month;
		 if (_month == 13)
		 {
			 _year++;
			 _month = 1;
		 }
	 }
	 return *this;//此时要返回一个日期,日期存在于this指针里,所以要返回*this
}//出了作用域this指针仍存在,所以可以使用引用符号

日期天数的增加

由于仅进行天数的增加然后拷贝给别人,所以需要先拷贝构造一个载体tmp,然后对tmp进行修改,最后返回载体,这样子原对象的内容就得到了保证

Date operator+(int day)
{
	Date tmp(*this);//进行对this指针的拷贝构造,需要一个载体
	tmp += day; //this指向的就是d1里的数据,此时tmp就是*this,可以实现复用
	//用+=的运算符重载函数
	return tmp;
}
Date ret1 = d1 += 50;
ret1.Print();
d1.Print();//由于是+=,所以d1也会被改变

Date ret=d1+50;//赋值一个日期,实际上也就是一个拷贝构造
ret.Print();
d1.Print();

实现类中成员函数的声明与定义分离

以日期类Date为例子

ps:若实现分离,那么缺省值由声明给,定义不能有缺省值

//Date.h
class Date
{
public:
	
	Date(int year = 1, int month = 1, int day = 1);//必须有缺省参数
	//为什么定义和声明不能分开?——因为要先找到声明再找到定义,类中会出现重定义
	//若想实现分开,那么就让声明给出缺省值,定义不用给
	void Print();
	int GetMonthDay(int year, int month);
	//连续赋值—返回指针
	Date& operator=(const Date& d);//实际上有两个参数,第一个参数就是this* d1,表示作用的对象,但是被隐藏了

	//只读函数可以加const,内部不涉及修改生成—是否const,都可以调用
	//若调用了,定义里也要加上const
	bool operator<(const Date& d);
	bool operator==(const Date& d);
	bool operator<=(const Date& d);
	bool operator>=(const Date& d);
	bool operator!=(const Date& d);

	Date& operator+=(int day);//用引用返回
	Date operator+(int day);
private:
	int _year;
	int _month;
	int _day;
};

//Date.cpp  定义里需要用到访问符' :: '
int Date::GetMonthDay(int year, int month)//如果用这个函数时会改变其内容,那么就需要用const修饰
{
	const static int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if ((month == 2) && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
	{
		return 29;
	}
	return monthArray[month];
}

//Date::Date(int year = 1, int month = 1, int day = 1)//err
Date::Date(int year, int month, int day)//pass
{
	    _year = year;
	    _month = month;
	    _day = day;
		if (month < 1 || month>12 || day<1 || day>GetMonthDay(year, month))
		{
			cout << "非法日期" << endl;
			return;
		}
		
}
//连续赋值—返回指针
Date& Date::operator=(const Date& d)//实际上有两个参数,第一个参数就是this* d1,表示作用的对象,但是被隐藏了
{
	if (this != &d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	return *this;//this这里指向的是d1,因为作用的对象就是d1,出了作用域this还在,所以能用引用返回
	//自定义类型不能传值拷贝,需要调用一个拷贝构造
}

bool Date::operator<(const Date& d) //用一个函数去比较,用别名接收
{
	//1.访问对象,将private注释掉
	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 _year == d._year && _month == d._month && _day == d._day;
}

bool Date::operator<=(const Date& d) const //此时d1作为operator,this指向d1,然后d2作为d
//再通过d1<d2 和d1==d2的判断 来得出<=的结果
{
	return *this < d || *this == d;//直接用this指针实现复用其它函数
}

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

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

Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		return *this -= (-day);
	}
	_day += day;
	while (_day > GetMonthDay(_day, _month))
	{
		_day -= GetMonthDay(_day, _month);
		++_month;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;//此时要返回一个日期,日期存在于this指针里,所以要返回*this
}


Date Date::operator+(int day) //若不想改变原内容,那么就需要一个变量来承载d1的this指针,然后改变变量,返回载体
{
	Date tmp(*this);
	//Date tmp = *this;
	tmp += day;
	return tmp;
}

赋值运算符重载

1.赋值运算符重载格式

参数类型:const T&,传递引用可以提高传参效率

返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

检测是否自己给自己赋值

返回*this :要复合连续赋值的含义

倘若在类中进行了拷贝不想原对象被改变,可以在定义的时候给予const进行修饰

Date (const Date& d);//拷贝构造
bool operator<(const Date& d);
bool operator<=(const Date& d);
bool operator>=(const Date& d);
bool operator>(const Date& d);

2.const重载

倘若定义对象的时候不想对象被改变,那么就会有以下的代码

void Print();

void TestDate2()
{
	const Date d1(2023, 7, 28);//若在Print函数里就是权限的放大
	d1.Print();
}

但是此时Print()函数只能兼容普通类型,那怎么办?——仅需要加个关键字

void Print() const;

//于此同时,定义也需要改变

//void Date::Print(const Date* this)
void Date::Print() const//在结尾加上const,这样可以自由实现权限的缩小
{
	cout << _year <<"年" <<_month<<"月"<<_day<<"日"<< endl;
}

那么,为了代码的严谨并且保护原对象,那么对于只读函数,都可以加上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;

运算符赋值

我们知道有拷贝构造,那么对于运算符'=',应该也能实现两个对象之间的拷贝

void operator=(const Date& d)//实际上有两个参数,第一个参数就是this* d1,表示作用的对象,但是被隐藏了
{//赋值拷贝
	//此处用不用&? —都行,为什么不会无穷递归?——此处直接传入值,不会再进行多余的拷贝构造
	//为了方便,默认都用引用&
	this->_year = d._year;
	this->_month = d._month;
	this->_day = d._day;
}

但是若是想实现多次赋值呢?例如这样的:

int  main()
{
    int i,j,k;
    k=10;
    i=j=k;
    return 0;
}

那么我们需要一个返回值,也就是需要返回一个拷贝,才能实现多次赋值

Date& operator=(const Date& d)//实际上有两个参数,第一个参数就是this* d1,表示作用的对象,但是被隐藏了
{
	if (this != &d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	return *this;//this这里指向的是d1,因为作用的对象就是d1,出了作用域this还在,所以能用引用返回
	//自定义类型不能传值拷贝,需要调用一个拷贝构造
}

赋值重载和拷贝构造的区别?

赋值,两个已经存在的对象进行拷贝
拷贝构造,一个已经存在对象,去初始化另一个要创建的对象

前置++和后置++重载

在类里,由于都有'++'操作符,所以为了区分,我们用' int '类型来区分

Date& operator++();//前置

Date& Date::operator++()//改变自身,所以直接作用于this指针
{
	*this += 1;
	return *this;
}


Date operator++(int);//后置

Date Date::operator++(int)//仅对变量赋值作改变,所以需要用一个载体来进行数据的改变
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

顺序表类的实现及使用

struct SeqList
{
public:
	void PushBack(int x)
	{
		_a[_size++] = x;
	}

	void CheckCapacity()
	{
		;
	}
	size_t size() const
	{
		return _size;
	}
	//打印
	void Print()
	{
		for (int i = 0; i < _size; i++)
		{
			cout << _a[i] << endl;
		}
	}

	 //读
	const int& operator[](size_t i) const //operator作用于类的对象
	{
		assert(i < _size);
		return _a[i];
	}
	 //写
	int& operator[](size_t i)//operator作用于类的对象
	{
		assert(i < _size);
		return _a[i];
	}
private:
	int* _a=(int*)malloc(sizeof(int)*10);
	int _size=0;
	int _capacity=0;
};

在C语言中,我们实现一个顺序表数据的放入会及其麻烦,但是在C++中有了类,那么就可以极大的简化代码

int main()
{
    SeqList st;
	st.PushBack(1);
	st.PushBack(2);
	st.PushBack(3);
	st.PushBack(4);
    
    
    return 0;
}

访问——可以通过运算符重载实现

for (size_t i = 0; i < st.size(); i++)
{
	cout << st[i] << " ";//用运算符重载,这样不仅实现了特定位置数据的返回,还可以进行遍历
	cout << st.operator[](i) << endl;
}

通过流输出打印对象的内容

ostream& operator<<(ostream& out, const Date& d)//类的流插入
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}

流输入输入对象的内容

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

以上两个不做介绍,需要用到out相关类的知识

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

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

相关文章

41. linux通过yum安装postgresql

文章目录 1.下载安装包2.关闭内置PostgreSQL模块:3.安装postgresql服务:4.初始化postgresql数据库:5.设置开机自启动:6.启动postgresql数据库7.查看postgresql进程8.通过netstat命令或者lsof 监听默认端口54329.使用find命令查找了一下postgresql.conf的配置位置10.修改postgre…

ARM将常数加载到寄存器方法之LDR伪指令

一、是什么&#xff1f; LDR Rd,const伪指令可在单个指令中构造任何32位数字常数,使用伪指令可以生成超过MOV和MVN指令 允许范围的常数. 实现原理: (1)如果可以用MOV或MVN指令构造该常数,则汇编程序会生成适当的指令 (2)如果不能用MOV或MVN指令构造该常数,则汇编程序会执行下列…

QEMU源码全解析19 —— QOM介绍(8)

接前一篇文章&#xff1a;QEMU源码全解析18 —— QOM介绍&#xff08;7&#xff09; 本文内容参考&#xff1a; 《趣谈Linux操作系统》 —— 刘超&#xff0c;极客时间 《QEMU/KVM》源码解析与应用 —— 李强&#xff0c;机械工业出版社 特此致谢&#xff01; 上一回讲到了Q…

用C语言实现堆排序算法

1.设计思路 排序的思想将一个数组按递增的顺序进行排序&#xff0c;将数组的第一个位置空下&#xff08;下标为0&#xff09;&#xff0c;因为会导致子节点和本身同一个结点&#xff08;i和2i一致&#xff09;&#xff0c;每次堆排序在下标1的位置放上了最大值&#xff0c;然后…

我对排序算法的理解

排序算法一直是一个很困惑我的问题&#xff0c;早在刚开始接触 数据结构的时候&#xff0c;这个地方就很让我不解。就是那种&#xff0c;总是感觉少了些什么的感觉。一开始&#xff0c;重新来过&#xff0c;认真来学习这一部分&#xff0c;也总是学着学着就把概念记住了。过了一…

版本适配好帮手 Android SDK Upgrade Assistant / Android Studio Giraffe新功能

首先是新版本一顿下载↓&#xff1a; Download Android Studio & App Tools - Android Developers 在Tools中找到Android SDK Upgrade Assistant 可以在此直接查看SDK升级相关信息&#xff0c;不用跑到WEB端去查看了。 例如看一下之前经常要对老项目维护的android 12蓝牙…

RAID相关知识

简介 RAID &#xff08; Redundant Array of Independent Disks &#xff09;即独立磁盘冗余阵列&#xff0c;通常简称为磁盘阵列。RAID技术将多个单独的物理硬盘以不同的方式组合成一个逻辑磁盘&#xff0c;从而提高硬盘的读写性能和数据安全性。 数据组织形式 分块&#x…

给定长度值length,把列表切分成每段长度为length的N段列表,Kotlin

给定长度值length&#xff0c;把列表切分成每段长度为length的N段列表&#xff0c;Kotlin import kotlin.random.Randomfun main(args: Array<String>) {var source mutableListOf<String>()val end Random.nextInt(30) 1for (i in 0 until end) {source.add(i.…

ubuntu22.04 DNSSEC(加密DNS服务) configuration

/etx/systemd/resolved.conf是ubuntu下DNS解析服务配置文件&#xff0c;systemd为ubuntu下system and service配置目录 step 1——修改resolved.conf参数 管理员权限打开 /systemd/resolved.conf sudo nano /etc/systemd/resolved.conf修改如下&#xff1a; # This file i…

DAY14_FilterListenerAjaxAxiosJsonfastjson综合案例-axios和html交互

目录 1 Filter1.1 Filter概述1.2 Filter快速入门1.2.1 开发步骤1.2.2 代码演示 1.3 Filter执行流程1.4 Filter拦截路径配置1.5 过滤器链1.5.1 概述1.5.2 代码演示1.5.3 问题 1.6 案例1.6.1 需求1.6.2 分析1.6.3 代码实现1.6.3.1 创建Filter1.6.3.2 编写逻辑代码1.6.3.3 测试并抛…

51单片机定时器/计数器

目录 1、定时器/计数器0/1介绍 1.1 定时器介绍 1.2 单片机定时/计数器原理 2、定时器/计数器0和1的相关寄存器 2.1 定时器/计数器控制寄存器TCON 2.2 定时器/计数器工作模式寄存器TMOD 2.3 定时器/计数器工作模式 2.3.1 模式0(13位定时器/计数器) 2.3.2 模式1(16位定…

SpringBoot运维

能够掌握SpringBoot程序多环境开发 能够基于Linux系统发布SpringBoot工程 能够解决线上灵活配置SpringBoot工程的需求 Windows打包运行 你的电脑不可能一直开着机联网作为服务器&#xff1a; 我们将我们项目打包放到外部的服务器上&#xff0c;这样其他用户才能正常访问&#x…

设计模式之四:工厂模式

引言&#xff1a;除了使用new操作符之外&#xff0c;还有更多制造对象的方法。同时&#xff0c;实例化这个活动不应该总是公开地进行。 1.简单工厂模式 这里有一些相关的具体类&#xff0c;要在运行时有一些具体条件来决定究竟实例化哪个类。这样的代码&#xff08;if..elseif…

MFC自定义控件使用

用VS2005新建一个MFC项目,添加一个Custom Control控件在窗体 我们需要为自定义控件添加一个类。项目,添加类,MFC类 设置类名字,基类为CWnd,你也可以选择CDialog作为基类 类创建完成后,在它的构造函数中注册一个新的自定义窗体,取名为"MyWindowClass" WNDCL…

深入了解HTTP代理在网络爬虫与SEO实践中的角色

随着互联网的不断发展&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;成为各大企业和网站重要的推广手段。然而&#xff0c;传统的SEO方法已经难以应对日益复杂和智能化的搜索引擎算法。在这样的背景下&#xff0c;HTTP代理爬虫作为一种重要的工具&#xff0c;正在逐渐被…

JUC中其他常用类

1.CopyOnWriteArrayList ArrayList是线程不安全的&#xff0c;Vector是线程安全的(方法被Synchronized修饰)&#xff0c;CopyOnWriterArrayList是在Vector的基础上再做优化&#xff0c;因为当读取操作较多时&#xff0c;Vector的效率不高。CopyOnWriterArrayList中读操作并没有…

ceph-mon运行原理分析

一、流程&#xff1a;ceph-deploy部署ceph-mon组建集群 1.ceph-deploy部署ceph-mon的工作流程及首次启动 1&#xff09;通过命令创建ceph-mon&#xff0c;命令为&#xff1a;ceph-deploy create mon keyring def mon(args):if args.subcommand create:mon_create(args)elif…

苍穹外卖day07——缓存菜品套餐+购物车功能实现

缓存菜品——需求设计与分析 问题说明 用户访问量过大带来的一个直接效果就是响应速度慢&#xff0c;使用体验下降。 实现思路 使用redis缓存菜品数据&#xff0c;减少数据库查询操作。 页面展示上基本就是同一个分类在同一页&#xff0c;所以key-value结构可以使用不同的分…

Vue没有node_modules怎么办

npm install 一下 然后再npm run serve 就可以运行了

记一次偶然的网站sql注入

自己学了点渗透的内容后就开始尝试挖漏洞了&#xff0c;偶然发现了这个yp网站&#xff0c;由于好奇心就浏览了一下里面的内容&#xff0c;突然注意到有个id的地方跳转页面&#xff0c;于是就想试试看有没有注入&#xff0c;就有了以下的内容。。。 界面如下 当时就是好奇点进去…