【黑马程序员 C++教程从0到1入门编程】【笔记4-2】C++核心编程(类和对象——运算符重载)(左操作数、右操作数)(仿函数)

news2024/12/26 22:07:49

文章目录

    • 4 类和对象(类属性【成员属性】,类函数【成员函数】)
      • 4.5 运算符重载(对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型)
        • 4.5.0.1 可重载运算符和不可重载运算符
        • 4.5.0.2 重载运算符基本语法规则
        • 4.5.1 加号运算符重载(operator+)(可以用成员函数实现和全局函数实现,注意区别,注意有的有限制,只能眼红成员函数或者只能用全局函数实现)
          • 示例
          • 用成员函数和全局函数(或类外友元函数)实现运算符重载的区别(成员函数运算符重载使用隐式的 this 指针作为左操作数)(全局函数运算符重载需要将左操作数和右操作数作为参数传递)(成员函数的优先级更高)
        • 4.5.2 左移运算符重载(输出自定义数据类型)(ostream& operator<<)(注意全局函数实现时,如果用到类中私有成员,不要忘记在类中声明友元)
          • 示例
        • 4.5.3 递增运算符重载(前置递增和后置递增)(++)
          • 示例
        • 4.5.4 赋值运算符重载(对属性值进行拷贝)(这个用得多不多啊,搞得语法有点乱不是么?用得很多哎,特别是自己实现一个类的时候!)
          • 示例
        • 4.5.5 关系运算符重载(等号=与不等号!=)
          • 示例
        • 4.5.6 函数调用运算符重载(仿函数)(把对象名,当作函数那样去调用)
          • 示例

黑马程序员C++教程

4 类和对象(类属性【成员属性】,类函数【成员函数】)

4.5 运算符重载(对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型)

C++中的运算符重载是一种特性,允许重新定义已存在的运算符的行为,以适应自定义类型的操作。通过运算符重载,可以为用户定义的类创建自定义的运算符行为,使其与内置类型的操作一样自然和直观。

4.5.0.1 可重载运算符和不可重载运算符

在这里插入图片描述

4.5.0.2 重载运算符基本语法规则

运算符函数必须是类的成员函数或友元函数。如果想将运算符重载为类的成员函数,则运算符函数必须在类定义中声明和定义。如果想将运算符重载为友元函数,则需要在类定义中声明友元函数,但在类外部定义该函数。

运算符函数的名称必须遵循特定的命名约定。运算符函数的名称由关键字operator后面跟随要重载的运算符组成。例如,如果要重载加法运算符+,则函数名称为operator+。

运算符函数的参数列表取决于运算符的操作数。对于二元运算符(例如加法、减法),运算符函数应该有一个参数,该参数表示运算符的右操作数。对于一元运算符(例如取负、自增),运算符函数不带参数。

运算符函数可以作为成员函数或非成员函数进行定义。如果将运算符函数定义为成员函数,那么左操作数将自动成为调用对象,右操作数作为函数的参数传递。如果将运算符函数定义为非成员函数(友元函数),则需要在参数列表中显式地传递两个操作数。

运算符函数的返回类型通常是该运算符操作结果的类型。对于赋值运算符,通常返回引用以支持连续赋值。

以下是一个示例,演示了如何重载加法运算符+:

#include <iostream>
#include <string>
using namespace std;

class MyNumber
{
public:
	int value;

public:
	MyNumber(int val) : value(val) {}

	MyNumber operator+(const MyNumber &other)
	{
		int sum = value + other.value;
		return MyNumber(sum);
	}
};

int main()
{
	MyNumber num1(5);
	MyNumber num2(10);

	MyNumber sum = num1 + num2; // 调用重载的+运算符

	cout << sum.value << endl; // 15

	return 0;
}

在上述示例中,operator+函数被定义为MyNumber类的成员函数。它接受一个MyNumber类型的参数作为右操作数,并返回一个新的MyNumber对象,表示两个操作数的和。

请注意,不是所有的运算符都可以被重载,例如.(成员访问运算符)和::(作用域解析运算符)不能被重载。此外,一些运算符的优先级和关联性是固定的,无法通过重载进行更改。因此,在重载运算符时,应该遵循C++的规则和约定,以保持代码的可读性和可维护性。

4.5.1 加号运算符重载(operator+)(可以用成员函数实现和全局函数实现,注意区别,注意有的有限制,只能眼红成员函数或者只能用全局函数实现)

作用:实现两个自定义数据类型相加的运算

示例
#include <iostream>
#include <string>
using namespace std;

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

	Person(int a, int b) : m_A(a), m_B(b)
	{
	}

	// 成员函数实现 + 号运算符重载(调用它的对象(this)+(p)?)
	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+(int val)
	{
		Person temp;
		temp.m_A = this->m_A + val;
		temp.m_B = this->m_B + val;
		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;
}

运行结果:

mA:30 mB:30
mA:40 mB:40
用成员函数和全局函数(或类外友元函数)实现运算符重载的区别(成员函数运算符重载使用隐式的 this 指针作为左操作数)(全局函数运算符重载需要将左操作数和右操作数作为参数传递)(成员函数的优先级更高)
  1. 成员函数实现运算符重载:

    • 成员函数运算符重载是将重载函数定义为类的成员函数。在重载函数内部,可以直接访问类的成员变量和成员函数。
    • 成员函数运算符重载使用隐式的 this 指针作为左操作数,而右操作数作为函数的参数。
    • 成员函数运算符重载对于类的私有成员变量和函数有直接的访问权限,无需使用访问器或者修改类的封装性。
    • 在代码中,Person operator+(const Person &p)Person operator+(int val) 是成员函数运算符重载的例子。
  2. 全局函数实现运算符重载:

    • 全局函数运算符重载是将重载函数定义为全局函数或者类外的友元函数。它们与类的成员函数没有直接的关联。
    • 全局函数运算符重载需要将左操作数和右操作数作为参数传递
    • 全局函数运算符重载对于类的私有成员变量和函数没有直接的访问权限,如果需要访问私有成员,需要使用类的公有接口(访问器或者友元函数)。

总结:

  • 成员函数运算符重载可以直接访问类的成员变量和成员函数,但对于私有成员的访问权限比较方便。
  • 全局函数运算符重载无法直接访问类的私有成员,需要通过公有接口访问,但可以用于扩展类的操作符功能或处理与类无关的类型。
  • 如果同时定义了成员函数和全局函数的运算符重载,成员函数的优先级更高,会优先调用成员函数的重载运算符。

4.5.2 左移运算符重载(输出自定义数据类型)(ostream& operator<<)(注意全局函数实现时,如果用到类中私有成员,不要忘记在类中声明友元)

https://www.bilibili.com/video/BV1et411b73Z?p=122
看了视频教程才看明白,

作用:可以输出自定义数据类型

示例
#include <iostream>
#include <string>
using namespace std;

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) // arnold:对ostream对象重载了左移运算符
{
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out;
}

void test()
{

	Person p1(10, 20);

	// cout << p1 << "hello world" << endl; // 链式编程
	// 相当于:
	cout << p1;
	cout << "hello world";
	cout << endl;
}

int main()
{

	test();

	system("pause");

	return 0;
}

运行结果:

a:10 b:20hello world

总结:重载左移运算符配合友元可以实现输出自定义数据类型

4.5.3 递增运算符重载(前置递增和后置递增)(++)

详细分析可见此:C++运算符重载,前置自增(前置++)和后置自增(后置++)代码分析

在C++中,前置递增(++)和后置递增(++)运算符可以通过函数重载进行区分。重载函数可以根据参数的不同来实现不同的行为。

  • 前置递增运算符使用单个参数,并且不需要额外的参数来区分前置和后置递增。因此,通过重载函数的名称和参数列表就可以区分前置递增运算符的重载。

  • 后置递增运算符重载函数需要使用一个额外的(并且没有实际意义的)int参数来区分它们与前置递增运算符的重载。这个额外的int参数是一个占位符,用于在编译器中区分前置和后置递增。因为后置递增运算符在递增操作之前返回对象的副本,所以它需要一个区分标记来指示是使用后置递增重载。

示例
#include <iostream>
using namespace std;

class MyInteger
{

	friend ostream &operator<<(ostream &out, MyInteger myint);

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

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

private:
	int m_Num;
};

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

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

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

	MyInteger myInt;
	cout << myInt++ << endl; // 0
	cout << myInt << endl;	 // 1
}

int main()
{

	test01();
	test02();

	system("pause");

	return 0;
}

总结: 前置递增返回引用,后置递增返回值

4.5.4 赋值运算符重载(对属性值进行拷贝)(这个用得多不多啊,搞得语法有点乱不是么?用得很多哎,特别是自己实现一个类的时候!)

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

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

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

示例
#include <iostream>
#include <string>
using namespace std;

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

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

		// 提供深拷贝 解决浅拷贝的问题
		m_Age = new int(*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();

	int a = 10;
	int b = 20;
	int c = 30;

	c = b = a;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;

	system("pause");

	return 0;
}

运行结果:

p1的年龄为:18
p2的年龄为:18
p3的年龄为:18
a = 10
b = 10
c = 10

4.5.5 关系运算符重载(等号=与不等号!=)

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

示例
#include <iostream>
#include <string>
using namespace std;

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)	// string类型能用=比较,那是因为string类型也重载了等号运算符
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	bool operator!=(Person &p) // 为啥不直接写个equal函数?(因为本来就是要重载=运算符啊!)
	{
		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;
}

运行结果:

a和b相等
a和b相等

4.5.6 函数调用运算符重载(仿函数)(把对象名,当作函数那样去调用)

  • 函数调用运算符 () 也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
示例
#include <iostream>
#include <string>
using namespace std;

class MyPrint
{

public:
	void operator()(string text)
	{
		cout << text << endl;
	}
};
void test01()
{
	// 重载的()操作符 也称为仿函数
	MyPrint myFunc;
	myFunc("hello world");
}

class MyAdd
{
public:
	int operator()(int v1, int v2)
	{
		return v1 + v2;
	}
};

void test02()
{
	MyAdd add;
	int ret = add(10, 10);
	cout << "ret = " << ret << endl;

	// 匿名对象调用
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

int main()
{

	test01();
	test02();

	system("pause");

	return 0;
}

运行结果:

hello world
ret = 20
MyAdd()(100,100) = 200

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

案例:创建一个学生管理系统(PXSCJ1)的数据库(SQL)

1、新建数据库&#xff1a;PXSCJ1 --创建数据库CREATE DATABASE PXSCJ1 --创建并确认属性&#xff1a;XSB、KCB、CJB&#xff08;以下代码用于2、3、4、5题&#xff09; use PXSCJ1 create table XSB (学号 char(6) primary key check(学号 like [0][8][1][12][0-9][0-9])…

SM3_Robotics,轴组函数调用

1轴组状态&#xff1a; AXIS_GROUP_REF_SM3 (FB) 2使能&#xff1a; MC_GroupEnable &#xff08;使能&#xff09; 默认在&#xff1a; MC_GroupDisable &#xff08;轴组关闭&#xff09;位置 1&#xff1a;用 MC_GroupEnable &#xff08;使能&#xff09;进入 Gro…

chatgpt赋能python:Python怎么定义主函数:完整指南

Python怎么定义主函数&#xff1a;完整指南 Python是当今最流行的编程语言之一&#xff0c;因为它提供了简单易学、高效率、高度可读性和可维护性的代码编写方式。在Python中定义主函数是一个重要的编程技能&#xff0c;使您能够将Python程序变成可执行的Python应用程序。在本…

chatgpt赋能python:Python多次输入——如何自动化处理数据输入

Python多次输入——如何自动化处理数据输入 作为一名有10年Python编程经验的工程师&#xff0c;我曾遇到过很多需要重复输入数据的情况。这不仅浪费时间&#xff0c;而且容易出错&#xff0c;影响我们的工作效率和准确性。作为程序员&#xff0c;我们需要借助Python的自动化技…

openGauss5 企业版之SQL语法和数据结构

文章目录 1.openGauss SQL 语法2. 数据类型2.1数值类型2.2 布尔类型2.3 字符类型2.4 二进制类型2.5日期/时间类型2.6 几何类型2.7 网络地址类型2.8 位串类型2.9 文本搜索类型2.10 UUID数据类型2.11 JSON/JSONB类型2.11 HLL数据类型2.12 范围类型2.13 索引2.14 对象标识符类型2.…

【MySQL】SQL的高阶用法

文章目录 函数聚合函数Count()Max()Min()Sum()Avg() 其他常用函数时间函数字符串函数数学函数 条件查询使用关系运算符查询使用IN关键字查询使用BETWEEN AND关键字查询使用空值查询使用AND关键字查询使用OR关键字查询使用LIKE关键字查询(模糊查询)使用LIMIT限制查询结果的数量使…

用ChatGPT生成测试数据

大家好&#xff0c;欢迎来到 Crossin的编程教室 &#xff01; 在之前的文章 用ChatGPT写一个数据采集程序 中&#xff0c;我们演示了如何用 ChatGPT 辅助编写代码。 除了直接让ChatGPT写代码&#xff0c;我们也可以让它生成一些开发中使用的测试数据。 比如在开发和测试时&…

Alloy Tutorial(3)Traces Modelling —— Cache Memory

文章目录 Cache Memory完整代码 Cache Memory //Addresses and data sig Addr {} sig Data {}//A cache system consists of main memory and cached memory, but mapping addresses to data one sig CacheSystem {var main, cache: Addr -> lone Data }//Initially there …

yolov5——从未见过注释比代码还多的源码解析You Only Look Once And You get it——训练部分

目录 一&#xff1a;前言 二&#xff1a;先介绍v5源码中必须知道的一些文件&#xff08;了解的可直接加入第三代码部分&#xff09; ​编辑 三&#xff1a;训练 参数配置 模式选择 搭建网络 加载预训练和自定义模型的参数 是否需要冻结层数 定义累计梯度的次数 设置…

零基础小白如何自学 Unity 游戏开发?(送 Unity 教程)

如何自学 Unity&#xff1f;初级阶段&#xff1a;学习编程语言初级阶段&#xff1a;编程实践中级阶段&#xff1a;继续学习编程语言 Unity 教程赠书活动内容简介作者简介赠书方式 如何自学 Unity&#xff1f; 有很多同学对 游戏开发 很感兴趣&#xff0c;但都不知道从何学起&a…

PostgreSQL如何查看事务所占有的锁?

表级锁命令LOCK TABLE 在PG中&#xff0c;显式地在表上加锁的命令为“LOCK TABLE”&#xff0c;此命令的语法如下&#xff1a; LOCK [TABLE] [ONLY] name [,...][IN lockmode MODE] [NOWAIT]语法中各项参数说明如下&#xff1a; name&#xff1a;表名lockmode&#xff1a;表…

GPT1,2,3

GPT1 transformer解码器因为有掩码所以不看后面的东西 gpt就是transformer的解码器&#xff0c;bert 是transformer的编码器 gpt核心卖点&#xff1a;不管输入如何&#xff0c;transformer模型不会变 半监督&#xff1a;先在没有标号上进行训练&#xff0c;再到有标号上进行微…

配置Nexus私服

私服是一种特殊的远程仓库&#xff0c;它代理广域网上的远程仓库&#xff0c;供局域网下的maven用户使用。 目前Nexus3的官方下载地址为 https://help.sonatype.com/repomanager3/product-information/download &#xff0c;由于下载较慢&#xff08;真的很慢&#xff09;&…

【MySQL】库和表的增删查改

目录 一、库的操作 1、创建数据库 2、数据库所使用的编码 2.1查询编码集和校验集 2.2查看数据库的字符集和校验集 2.3创建数据库指定字符集和校验集 2.4不同的校验集所筛选的数据结果不一样 3、查看数据库 4、修改数据库 5、删除数据库 6、数据库的备份和恢复 6.1备…

数字图像处理期末复习习题 SCUEC part2

1.连续图像在空间位置上的离散化称为采样&#xff1b;图像空间分辨率由灰度级决定。 2.坐标的离散化叫采样&#xff0c;幅值的离散化叫量化。 3. 4.图像分割方法多种多样&#xff0c;一般可以根据像素灰度取值的特性将分割方法分为两类&#xff08;阈值分割&#xff0c;区域分割…

软件工程开发文档写作教程(15)—概要设计书的编写

本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl本文参考资料&#xff1a;电子工业出版社《软件文档写作教程》 马平&#xff0c;黄冬梅编著 概要设计书的编写 按照国家《概要设计说明书GB8567—88&#xff09;所定义的标准&#xff0…

算法刷题-链表-链表相交

链表相交 面试题 02.07. 链表相交思路其他语言版本JavaPythonGojavaScript 面试题 02.07. 链表相交 同&#xff1a;160.链表相交 力扣题目链接 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0…

chatgpt赋能python:Python如何填充空值

Python如何填充空值 在数据处理过程中&#xff0c;经常会遇到数据集中存在空值的情况。这些空值&#xff08;或缺失值&#xff09;可能会影响数据分析的准确性&#xff0c;因此我们需要对这些空值进行填充。Python作为一种流行的编程语言&#xff0c;提供了许多有效的方法来处…

【C++ 笔记四】STL 标准模板库 —— 容器基础

【C 笔记四】STL 标准模板库 —— 容器基础 文章目录 【C 笔记四】STL 标准模板库 —— 容器基础I - 概述 STL1.1 - 范围与定义1.2 - 组成与关系1.3 - 实用举例 II - 概述容器2.1 - 迭代器2.2 - 容器的结构与分类2.3 - 序列式容器2.4 - 关联式容器2.5 - 不定序容器2.6 - 总述 I…

.mdf.locked加密sql server完美恢复---惜分飞

有可能用友ERP软件的sql server 数据库所在机器被勒索病毒加密,扩展名为.locked和昨天恢复的基本类似(.locked加密勒索数据库级别恢复),通过分析确认sql server被这种病毒加密,也可以完美恢复 通过恢复之后数据库正常挂载成功 测试应用一切正常 对于类似这种被加密的勒索的数…