C++构造函数 | 析构函数 | 拷贝构造与运算符重载

目录

前言:六个默认成员函数

Ⅰ.构造函数

1.构造函数引入

2.构造函数注意事项

3.默认构造函数

Ⅱ.析构函数

1.析构函数概念

2.析构函数的特性

3.默认析构函数

Ⅲ.拷贝构造函数

1.拷贝构造函数的概念与特性

2.默认拷贝构造

Ⅳ. 运算符重载

运算符重载引入

1.赋值运算符

前言:

赋值运算符引入

需要手写赋值运算符情况

2.加号运算符重载

3.左移运算符重载

4.递增运算符重载

5.关系运算符重载


前言:六个默认成员函数

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

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

分别是:

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

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

Ⅰ.构造函数

1.构造函数引入

#include <iostream>
 
class Date {
public:
    void SetDate(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1;
    d1.SetDate(2022, 3, 8);
    d1.Print();
 
    Date d2;
    d2.SetDate(2022, 3, 12);
    d2.Print();
 
    return 0;
}
对于 Date 类,可以通过 SetDate 公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信 息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
答:使用构造函数;

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

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

构造函数的特性:

  1. 没有返回值。
  2. 函数名与类名相同
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数可以重载
        构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象

构造函数的用法: 

#include <iostream>
 
class Date {
public:
    /* 无参构造函数 */
    Date() {
        _year = 0;
        _month = 1;
        _day = 1;
    }
    /* 带参构造函数 */
    Date(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1;   // 对象实例化,此时触发构造,调用无参构造函数
    d1.Print();
 
    Date d2(2022, 3, 9);   // 对象实例化,此时触发构造,调用带参构造函数
    d2.Print();
 
    return 0;
}

2.构造函数注意事项

一、构造函数是特殊的,不是常规的成员函数,不能直接调

int main(void)
{
    Date d1;
    d1.Date(); // 不能这么去调,构造函数是特殊的,不是常规的成员函数!
 
    return 0;
}

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

int main(void)
{
    //带参这么调:加括号(),在括号中加参数列表
    Date d2(2022, 3, 9);
 
    Date d3();   //误认为是函数声明
                 // Date d3; 不要 Date d3();   

 
    return 0;
}

 三、如果你没有自己定义构造函数(类中未显式定义),C++ 编译器会自动生成一个无参的默认构造函数。当然,如果你自己定义了,编译器就不会帮你生成了。

#include <iostream>
 
class Date {
public:
    /* 如果用户显式定义了构造函数,编译器将不再生成
    Date(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }
    */
    void Print() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1;  // 这里调用的是默认生成的无参的构造函数
    d1.Print();
 
    return 0;
}

没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器默认生成的构造函数 

 如果用户显式定义了构造函数,编译器将不再生成

#include <iostream>

class Date {
public:
	// 如果用户显式定义了构造函数,编译器将不再生成
	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	
	void Print() {
		printf("%d-%d-%d\n", _year, _month, _day);
	}

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

int main(void)
{
	Date d1;  // 这里调用的是默认生成的无参的构造函数
	d1.Print();

	return 0;
}

运行报错!!!

上文提到无参默认构造函数,那么什么是默认构造函数呢?

3.默认构造函数

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:
无参构造函数全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

注意事项:

语法上无参和全缺省可以同时存在,但如果同时存在会引发二义性

无参的构造函数和全缺省的构造函数都成为默认构造函数,并且默认构造参数只能有一个,语法上他们两个可以同时存在,但是如果有对象定义去调用就会报错。

#include <iostream>
 
class Date {
public:
    Date() {//无参
        _year = 2022;
        _month = 5;
        _day = 28;
    }
    Date(int year = 2022, int month = 5, int day = 28) {//全缺省
        _year = year;
        _month = month;
        _day = day;
    }
    
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1; //错误!!
 
    return 0;
}

运行结果如下:报错!!!

  • 关于编译器生成的默认成员函数,很多童鞋会有疑惑:在我们不实现构造函数的情况下,编译器会生成 默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但d对象year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么卵用??
  • 解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如 int/char...,自定义类型就是我们使用class/struct/union自己定义的类型;

看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数

#include <iostream>
using namespace std;
 
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;
}

 可见,C++ 里面把类型分为两类:内置类型(基本类型)和 自定义类型

  • C++ 规定:我们不写编译器默认生成构造函数,对于内置类型的成员变量,不做初始化处理。
  • 但是对于自定义类型的成员变量会去调用它的默认构造函数(不用参数就可以调的)初始化
  • 如果没有默认构造函数(不用参数就可以调用的构造函数)就会报错!

这里我们故意写个带参的默认构造函数,让编译器不默认生成:

#include <iostream>
using namespace std;
 
class A {
public:
    // 如果没有默认的构造函数,会报错。
    A(int a) {    // 故意给个参
        cout << " A() " << endl;
        _a = 0;
    }
private:
    int _a;
};
 
class Date {
public:
private:
    // 如果没有默认构造函数就会报错
 
    int _year;
    int _month;
    int _day;
 
    A _aa;
};
 
int main(void)
{
    Date d1;
 
    return 0;
}

运行结果:报错!!!

总结:

  1. 初始化,在对象实例化时候自动调用,保证实例化对象一定被初始化。
  2. 构造函数是默认成员函数,我们不写编译器会自己生成一份,我们写了编译器就不会生成。
  3. 对于内置类型成员变量不处理。
  4. 对于自定义类型的成员变量会调用它的默认构造函数。

Ⅱ.析构函数

1.析构函数概念

析构函数与构造函数的功能相反。

构造函数是特殊的成员函数,主要任务是初始化,而不是开空间;析构函数也一样,主要任务是清理,而不是做对象销毁的工作。(局部对象销毁工作是由编译器完成的)

概念:对象在销毁时会自动调用析构函数,完成对象的一些资源清理工作。

2.析构函数的特性

构造函数是特殊的成员函数,主要特征如下:

  1.  析构函数名是在类名前面加上字符 
  2.  析构函数既没有参数也没有返回值(因为没有参数,所以也不会构成重载问题)
  3.  一个类的析构函数有且仅有一个(如果不写系统会默认生成一个析构函数)
  4.  析构函数在对象生命周期结束后,会自动调用。

析构用法:

#include <iostream>
using namespace std;

class Date {
public:
	Date(int year = 1, int month = 0, int day = 0) {
		_year = year;
		_month = month;
		_day = day;
		
	}
	void Print() {
		printf("%d-%d-%d\n", _year, _month, _day);
	}

	~Date() {
		// Date 类没有资源需要清理,所以Date不实现析构函都是可以的
		cout << "~Date() 析构调用 " <<_year<< endl;  // 测试一下,让他吱一声
	}

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

int main(void)
{
	Date d1;
	d1.Print();
	Date d2(2022, 3, 9);
	d2.Print();

	return 0;
}

 可见,因为析构的顺序在局部的栈中是相反的,栈帧销毁清理资源时 d2 先清理,然后再清理 d1 。所以析构与构造顺序相反

3.默认析构函数

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

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

#include <iostream>
using namespace std;
 
class String {
public:
	String(const char* str = "jack") {
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}
	~String() {
		cout << "~String()" << endl;
		free(_str);
	}
private:
	char* _str;
};
 
class Person {
private:
	String _name;
	int _age;
};
 
int main()
{
	Person p;
 
	return 0;
}

 总结:

  1. 完成对象中自愿的清理。如果类对象需要资源清理,才需要自己实现析构函数。
  2. 析构函数在对象生命周期到了以后自动调用,如果你正确实现了析构函数,保证了类对象中的资源被清理。
  3. 什么时候生命周期到了?如果是局部变量,出了作用域。全局和静态变量,整个程序结束。
  4. 我们不写编译器会默认生成析构函数,我们实现了,编译器就不会实现了。
  5. 对于内置类型成员变量不处理。
  6. 对于自定义类型的成员变量会调用它的析构函数。
     

Ⅲ.拷贝构造函数

1.拷贝构造函数的概念与特性

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

它也是一个特殊的成员函数,所以他符合构造函数的一些特性:

1. 拷贝构造函数是构造函数的一个重载形式。函数名和类名相同,没有返回值。

2. 拷贝构造函数的参数只有一个,并且必须要使用引用传参!(使用传值方式会引发无穷递归调用!)

#include <iostream>
 
class Date {
public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }
 
    /* Date d2(d1); */
    Date(Date& d) {         // 这里要用引用,否则就会无穷递归下去
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    void Print() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1(2022, 3, 9);
    Date d2(d1);          // 拷贝复制
 
    // 看看拷贝成功没
    d1.Print();
    d2.Print();
 
    return 0;
}

如果函数内不需要改变,建议把 const 也给它加上

原因:防止写反

/* Date d2(d1); */
Date(Date& d) {
    d._year = _year;
    d._month = _month;
    d._day = _day;
}

这段代码不会报错,但是结果显而易见是错的(随机值),这里加一个 const 就安全多了,这些错误就会被检查出来了

2.默认拷贝构造

  1. 若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
  2. 置类型的成员,会完成按字节序的拷贝(把每个字节依次拷贝过去)。
  3. 自定义类型成员,会再调用它的拷贝构造。

对于内置类型:

编译器自动生成的拷贝构造会完成浅拷贝或者说是值拷贝,默认的拷贝构造函数对象按内存存储按字节序完成拷贝

#include<iostream>
using namespace std;
 
class Date {
    public:
        Date(int year = 0, int month = 1, int day = 1) {
            _year = year;
            _month = month;
            _day = day;
        }
 
        // Date(Date& d) {
        //     _year = d._year;
        //     _month = d._month;
        //     _day = d._day;
        // }
 
        void Print() {
            printf("%d-%d-%d\n", _year, _month, _day);
        } 
    
    private:
        int _year;
        int _month;
        int _day;
};
 
int main(void)
{
    Date d1(2002, 4, 8);
 
    // 拷贝复制
    Date d2(d1);
 
    // 没有写拷贝构造,但是也拷贝成功了
     这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。Dated2(d1);
    d1.Print();
    d2.Print();
 
    return 0;
}

对于自定义类型:

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

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

运行报错!! 

原因:

        实现栈的时候,栈的结构问题,导致这里如果用默认的拷贝构造,按字节把所有东西都拷过来会产生问题,如果 Stack st1 拷贝出另一个 Stack st2(st1) 会导致他们都指向那块开辟的内存空间,导致他们指向的空间被析构(free)两次,导致程序崩溃

总结:对于常见的类,比如日期类,默认生成的拷贝构造就够用了。但是对于栈这样的类,默认生成的拷贝构造不能用,需要深拷贝

Ⅳ. 运算符重载

运算符重载引入

C++为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
注意:
  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型或者枚举类型的操作数
  • 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的
  • 操作符有一个默认的形参this,限定为第一个形参
  • .* 、:: 、sizeof 、? : 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
.          (点运算符)
 
::         (域运算符)
 
.*         (点星运算符,)
 
?:         (条件运算符)
 
sizeof

虽然点运算符( . )不能重载,但是箭头运算符( -> )是支持重载的

解引用(*)是可以重载的,不能重载的是点星运算符( .* )

举个栗子:

举一个运算符重载的例子: ==

#include <iostream>
using namespace std;
 
class Date {
public:
	Date(int year = 1970, int month = 1, int day = 1) {
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
 
// private:    
	int _year;
	int _month;
	int _day;
};
 
/* d1 == d2 */
bool operator==(const Date& d1, const Date& d2) {
	return d1._year == d2._year
		&& d1._month == d2._month 
		&& d1._day == d2._day;
}
 
int main(void) {
	Date d1(2022, 3, 8);
	Date d2(2022, 5, 1);
	
	cout << (d1 == d2) << endl;  
 
	return 0;
}
// 这里会发现运算符重载成全局的就需要成员变量是共有的(都为public),那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数
#include <iostream>
using namespace std;
 
class Date {
public:
	Date(int year = 1970, int month = 1, int day = 1) {
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
 
	/* d1 == d2 
	bool operator==(Date* this, const Date& d2)   (打回原形)  
	*/
	bool operator==(const Date& d2) {
		return (
			   this->_year == d2._year
			&& this->_month == d2._month
			&& this->_day == d2._day
			);
	}
 
private:
	int _year;
	int _month;
	int _day;
};
 
int main(void) {
	Date d1(2022, 3, 8);
	Date d2(2022, 5, 1);
	
	cout << (d1 == d2) << endl;  
 
	return 0;
}
这里需要注意的是,左操作数是 this 指向的调用函数的对象

1.赋值运算符

前言:

c++编译器至少给一个类添加4个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 赋值运算符 operator=, 对属性进行值拷贝

默认的赋值运算符会给浅拷贝,对于一般类来说是够用的,如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题;

所以说,只有在类中有属性指向堆区,或者有需要重复释放内存的操作时才要自己写赋值运算符

赋值运算符引入

下面我们先来造轮子(做编译器默认生成的赋值运算符) 

赋值运算符重载主要有以下四点:

  1. 参数类型
  2. 返回值年
  3. 检查是否给自己复制
  4. 返回 *this 

编译器默认生成复制重载,跟拷贝构造做的事情完全类似:

① 内置类型成员,会完成字节序值拷贝 —— 浅拷贝。

② 对于自定义类型成员变量,会调用它的 operator= 赋值。

#include <iostream>
using namespace std;

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

	/* 赋值运算符重载:d1 = d3 */
	Date& operator=(const Date& d) {
		if (this != &d) {   // 防止自己跟自己赋值(这里的&d是取地址)
			this->_year = d._year;
			this->_month = d._month;
			this->_day = d._day;
		}

		return *this;   // 返回左操作数d1
	}

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

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

int main(void)
{
	Date d1(2022, 3, 10);
	
	Date d2(2022, 7, 1);
	Date d3;

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

	d2.Print();

	d3.Print();

	return 0;
}
  • 定要防止自己和自己赋值!我们这里加 if 语句来判断就是为了防止极端情况下,自己给自己赋值,加上这条判断后就算遇到自己给自己赋值,就会不做处理,直接跳过。
  • 因为有连等情况,所以必须要用引用,return *this;来返回自身,这样才能和常规意义上的“=”一致

需要手写赋值运算符情况

当我们new或者malloc了一个空间,需要用析构函数释放时,就需要手写赋值运算符构建深拷贝,否则会重复delete或者free一块内存导致程序崩溃!!

如图我们new了一个对象,申请了编号为0x0011的空间,如果是浅拷贝(默认赋值运算),第二个对象就任然指向0x0011的空间,当我们析构时就回析构两次,也就是delete两次,导致堆区内存重复释放程序崩溃!

 解决方法非常简单,就是多申请一块内存,把新对象指向新空间即可,这样就解决了重复释放的问题了(简称深拷贝)

#include <iostream>

using namespace std;
class Person
{
public:

	Person(int age)
	{
		//将年龄数据开辟到堆区
		m_Age = new int(age);
	}

	//重载赋值运算符 
	Person& operator=(Person& p)
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		//编译器提供的代码是浅拷贝
		//m_Age = p.m_Age;

		//提供深拷贝 解决浅拷贝的问题
		m_Age = new int(*p.m_Age);//要new *p.m_Age 这么大的空间
 
		//返回自身
		return *this;
	}


	~Person()
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}

	//年龄的指针
	int* m_Age;

};


void test01()
{
	Person p1(18);

	Person p2(20);

	Person p3(30);

	p3 = p2 = p1; //赋值操作

	cout << "p1的年龄为:" << *p1.m_Age << endl;

	cout << "p2的年龄为:" << *p2.m_Age << endl;

	cout << "p3的年龄为:" << *p3.m_Age << endl;
}

int main() {

	test01();

	return 0;
}

赋值运算符只能作为类的成员函数重载,赋值运算符在类中不显式实现时,编译器会生成一份默认的,此时用户在类外再将赋值运算符重载为全局的,就和编译器生成的默认赋值运算符冲突了,故赋值运算符只能重载成成员函数

2.加号运算符重载

两种情况

p2=p1+p2;

p2=p1+C;(C为常数)

成员函数实现:

Person operator+(const Person& p) {
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}

全局函数实现:

Person operator+(const Person& p1, const Person& p2) {
		Person temp;
		temp.m_A = p1.m_A + p2.m_A;
		temp.m_B = p1.m_B + p2.m_B;
		return temp;
	}

 函数重载:

	Person operator+(const Person& p2, int val)
	{
		Person temp;
		temp.m_A = p2.m_A + val;
		temp.m_B = p2.m_B + val;
		return temp;
	}

class Person {
public:
	Person() {};
	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}
	//成员函数实现 + 号运算符重载
	Person operator+(const Person& p) {
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}


public:
	int m_A;
	int m_B;
};

//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
//	Person temp(0, 0);
//	temp.m_A = p1.m_A + p2.m_A;
//	temp.m_B = p1.m_B + p2.m_B;
//	return temp;
//}

//运算符重载 可以发生函数重载 
Person operator+(const Person& p2, int val)  
{
	Person temp;
	temp.m_A = p2.m_A + val;
	temp.m_B = p2.m_B + val;
	return temp;
}

void test() {

	Person p1(10, 10);
	Person p2(20, 20);

	//成员函数方式
	Person p3 = p2 + p1;  //相当于 p2.operaor+(p1)
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;


	Person p4 = p3 + 10; //相当于 operator+(p3,10)
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;

}

int main() {

	test();

	system("pause");

	return 0;
}

3.左移运算符重载

只能在全局;

ostream& operator<<(ostream& out, Person& p) {
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out;
}

直接背诵输出模板 ,就可以实现输出自定义数据类型

class Person {
	friend ostream& operator<<(ostream& out, Person& p);

public:

	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}

	//成员函数 实现不了  p << cout 不是我们想要的效果
	//void operator<<(Person& p){
	//}

private:
	int m_A;
	int m_B;
};

//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person& p) {
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out;
}

void test() {

	Person p1(10, 20);

	cout << p1 << "hello world" << endl; //链式编程
}

int main() {

	test();

	system("pause");

	return 0;
}

同理右移运算符也是一样的 

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

4.递增运算符重载

通过重载递增运算符,实现自己的整型数据

前置++:

MyInteger& operator++() {
		//先++
		m_Num++;
		//再返回
		return *this;
	}

必须传引用因为是对一个数本身++,符合++(++a)情况

后置++:

MyInteger operator++(int) {
		//先返回
		MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,
                                //但是返回的是以前的值,达到先返回后++;
		m_Num++;
		return temp;
	}

不需要引用,因为返回的是一个临时变量 ;需要占位参数int来区分前后置

#include <iostream>

using namespace std;

class MyInteger {

public:
	MyInteger() {
		m_Num = 0;
	}
	//前置++
	MyInteger& operator++() {
		//先++
		m_Num++;
		//再返回
		return *this;
	}

	//后置++
	MyInteger operator++(int) {
		//先返回
		MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
		m_Num++;
		return temp;
	}


	int m_Num;
};


ostream& operator<<(ostream& out, MyInteger myint) {
	out << myint.m_Num;
	return out;
}


//前置++ 先++ 再返回
void test01() {
	MyInteger myInt;
	cout << ++myInt << endl;
	cout << myInt << endl;
}

//后置++ 先返回 再++
void test02() {

	MyInteger myInt;
	cout << myInt++ << endl;
	cout << myInt << endl;
}

int main() {

	//test01();
	test02();

	return 0;
}

5.关系运算符重载

非常简单,如图所示

bool operator==(Person & p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	bool operator!=(Person & p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return false;
		}
		else
		{
			return true;
		}
	}

以此类推>= 、 <= 、> 、< 也一样 

class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	};

	bool operator==(Person & p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	bool operator!=(Person & p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	string m_Name;
	int m_Age;
};

void test01()
{
	//int a = 0;
	//int b = 0;

	Person a("孙悟空", 18);
	Person b("孙悟空", 18);

	if (a == b)
	{
		cout << "a和b相等" << endl;
	}
	else
	{
		cout << "a和b不相等" << endl;
	}

	if (a != b)
	{
		cout << "a和b不相等" << endl;
	}
	else
	{
		cout << "a和b相等" << endl;
	}
}


int main() {

	test01();

	system("pause");

	return 0;
}

(笔记篇:资料来源 - 比特科技)

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

NO.-LL

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

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

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

打赏作者

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

抵扣说明:

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

余额充值