C++超详细讲解运算符重载

目录
  • 概念
  • 赋值运算符重载
  • const成员
  • 取地址及const取地址操作符重载

概念

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类

型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。

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

需要注意的几点:

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

既然是对自定义类型对象之间的操作符的重载,那么它的参数一定有此类型的对象,并且需要对对象的成员进行操作,这就需要打破封装的限制,那么这个函数应该设置为全局的还是类的成员呢?

有以下几种思路:

  1. 函数设为公有,成员变量设为公有(不好);
  2. 函数设为公有另外写一个成员函数区获取成员变量的值(不好);
  3. 将函数设为类的友元函数(可以);
  4. 放入类中,作为成员函数(推荐);
// 全局的operator==
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;
};
// 这里会发现运算符重载成全局的就需要成员变量是共有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year
	&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 29);
	cout << (d1 == d2) << endl;
	return 0;
}

这样的写法就打破了封装,让类的成员都暴露了出来,这样的损失不太值得。

赋值运算符重载

赋值操作运算符重载特征如下:

  • 参数类型相同;
  • 返回值;
  • 检测是否给自己赋值;
  • 返回*this;
  • 一个类如果没有显式的定义赋值操作符重载,编译器会自动生成一个,完成对象字节序的拷贝(浅拷贝);
  • 赋值运算符在类中不显式实现时,编译器会生成一份默认的,此时用户在类外再将赋值运算符重载为全局的,就和编译器生成的默认赋值运算符冲突了,故赋值运算符只能重载成成员函数。
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(2018, 10, 1);
	// 这里d1调用的编译器生成operator=完成拷贝,d2和d1的值也是一样的。
	d1 = d2;
	d1.Display();
	d2.Display();
	return 0;
}

是不是很像自动生成的拷贝构造?那么它也存在一定的问题,对于日期类的对象他能很好的完成赋值操作,可对于指针类型呢?

下面的程序会崩溃

class String
{
public:
	String(const char* str = "songxin")
	{
		cout << "String(const char* str = \"songxin\")" << endl;
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}
	~String()
	{
		cout << "~String()" << endl;
		free(_str);
		_str = nullptr;
	}
private:
	char* _str;
};
int main()
{
	String s1("tanmei");
	String s2;
	s2 = s1;
	return 0;
}

原因也是因为浅拷贝的关系,导致同一块内存被释放了两次,程序崩溃。

可以不显式定义赋值操作符重载函数的情况

  • 成员变量没有指针;
  • 成员变量的指针没有管理内存资源;

注意:赋值操作符重载与拷贝构造不同的地方就是拷贝构造是在对象定义时,而赋值操作符重载是作用于已经存在的对象。

const成员

const修饰类的成员函数,有点奇怪,const怎么能修饰函数呢?

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

class Date
{
public:
	Date()//构造函数不写的话创建const的对象会报错。
		:
		_year(1900),
		_month(1),
		_day(1)
	{}
	void Display()
	{
		cout << "Display ()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void Display() const
	{
		cout << "Display () const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date d1;
	d1.Display();
	const Date d2;
	d2.Display();
	return 0;
}

const的对象就会调用Display函数会调用哪一个呢?注意到上面代码的第18行的函数被const修饰,那么这个const有什么作用?

实际上这个const修饰的是*this,表明 *this不可被修改,那么const的对象就会调用被const修饰的函数,否则可能会出现下面的问题。

const对象可以调用非const成员函数吗?

​ 不可以,权限放大。

非const对象可以调用const成员函数吗?

​ 可以,权限缩小。

const成员函数内可以调用其它的非const成员函数吗?

​ 不可以,权限放大。

非const成员函数内可以调用其它的const成员函数吗?

​ 可以,权限缩小。

还有一个值得注意的地方,上面的代码如果我们不显式定义构造函数的话,实例化const的对象时会报错:

“d2”: 必须初始化 const 对象

也就是说编译器认为const对象(包括成员)无法被赋值,应该有初始化操作,而默认生成的构造是没有对int有初始化操作的,因此报错;

取地址及const取地址操作符重载

取地址操作符也要重载吗?只有很少的情况会用到,通常直接使用编译器默认生成的就可以。

class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

那么什么时候我们会重载呢?

  • 想让别人获取指定的内容
  • 隐藏对象真实的地址
class Date
{
public:
	Date* operator&()//隐藏对象真实地址
	{
		return nullptr;
	}
	const int* operator&()const//让用户指定获取成员变量_day的地址
	{
		return  &(_day);
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	const Date d1;
	Date d2;
	cout << &d1 << endl;//
	cout << &d2 << endl;//
	return 0;
}

输出:

0000005597AFF770

0000000000000000

不过这样的情况确实很少,也没有什么意义。

到此这篇关于C++超详细讲解运算符重载的文章就介绍到这了,更多相关C++运算符重载内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++基础知识之运算符重载详解

    目录 运算符重载 方式一,使用成员函数重载运算符需求:把牛肉换猪肉,羊肉换猪肉 方式二,使用非成员函数[友元函数]重载运算符 两种方式的区别 两种方式的选择: 总结 运算符重载 为什么要使用运算符重载 -C/C++的运算符,支持的数据类型,仅限于基本数据类型. 问题:一头牛+一头马 = ?(牛马神兽?) 一个圆 +一个圆 = ? (想要变成一个更大的圆)一头牛 – 一只羊 = ? (想要变成4只羊,原始的以物易物:1头牛价值5只羊) 解决方案: 使用运算符重载 方式一, 使用成员函数重载运算符

  • C++运算符重载限制介绍

    目录 一.重载限制 1.必须至少有一个操作数是用户定义的类型 2.不能违反运算符原来的规则 3.不能创建新运算符 4.禁止名单 5.部分运算符只能通过成员函数重载  文章转自公众号:Coder梁(ID:Coder_LT) 一.重载限制 上一篇我们讲了在类和结构体当中重载运算符,关于运算符的重载并不是随心所欲的.C++给出了一些限制,从而保证了规范,以及程序运行的准确性. 下面我们就来一一来看下: 1.必须至少有一个操作数是用户定义的类型 这句话看不明白没有关系,我们只需要记住它的目的就好了.它的

  • 聊聊C++ 运算符重载知识

    前言 1.运算符重载是一种形式的C++多态. 2.重载运算符可以使代码看起来更加自然. 回顾类 在正常构造类的时候,有些成员方法可以不用写出来,例如在这样一个表示时间的类中,拷贝构造函数只是浅拷贝,和系统默认的步骤是一样的,可以不用写了. 同样,析构函数如果在对象死亡之前没有必须要做的事情,也可以不用写. 所以在下面的例子中,拷贝构造和析构函数可以省略. class Time { public: Time(); Time(const Time& src) { _hour = src._hour;

  • C++深入探究类与对象之友元与运算符重载

    目录 友元 1 全局函数做友元 2 类做友元 3 成员函数做友元 运算符重载 1 加号运算符重载 2 左移运算符重载 3 递增运算符重载 4 赋值运算符重载 5 关系运算符重载 6 函数调用运算符重载 友元 生活中你的家有客厅(Public),有你的卧室(Private),客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的好闺蜜好基友进去. 在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术. 友元的目的就是让一个函数

  • C++构造函数+复制构造函数+重载等号运算符调用

    目录 前言: 1.赋值和初始化的区别 2.初始化和赋值分别调用哪个函数? 3.编写测试类 前言: 初学C++发现了下面这个问题,其中Duck是一个已知的类,并以多种方式指定对象的值: Duck d1(); Duck d2(d1); Duck d3 = d1; Duck d4; d4 = d1; 问题在于,上述d1.d2.d3.d4是如何创建的呢?分别调用的哪个函数呢? 1.赋值和初始化的区别 C++中,赋值和初始化是两个不同的概念: 初始化是指对象创建之时指定其初值,分为直接初始化和复制初始化两

  • C++深度探索运算符重载和返回值优化

    目录 问题背景 具体问题 测试代码   今天遇到的是内存释放错误的问题.原因是没写拷贝构造函数,奇怪的是我之前也没写确实能正常工作的,今天深究了一下发现是编译器做了返回值优化. 问题背景   编译环境还是针对C6455 DSP,为了做一些简单的图像直方图的处理,并且尽可能不用模板类,我自己写了一个简单的类用来存放带长度信息的数组,并且可以做一些简单的运算.重载了减法运算符,从而可以对两个直方图求差. 具体问题   当类中有那种需要动态分配空间的成员的时候,要记得提醒自己重载拷贝构造函数和赋值运算

  • C++运算符重载详情介绍

    文章转自公众号:Coder梁(ID:Coder_LT) C++当中除了函数可以重载之外,其实运算符也是可以重载的.我们之前已经接触过一些,可能大家没有意识到. 举个例子,乘号*,运用在指针上,就是取值的意思,而运用在算数当中,则是乘法的意思.同样一个符号,用在不同的地方,起到了不同的效果.这其实就是一种重载,C++根据操作数的数目和类型来决定要使用哪一种操作. 另外C++允许将运算符重载扩展到用户自定义的类型,也就是结构体和类当中.比如,我们可以将重载加号,对两个对象相加. 其实这种用法也出现过

  • C++重载运算符你真的了解吗

    目录 1.重载运算符的必要性 2.重载运算符的形式与规则 3.重载运算符的运算 4.转义运算符 总结 运算符实际上是一个函数,所以运算符的重载实际上是函数的重载,.编译程序对运算符的重载的选择,遵循函数重载的选择原则.当遇到不很明显的运算时,编译程序会寻找与参数相匹配的运算符函数. 1.重载运算符的必要性 C++语言中的数据类型分为基本数据类型和构造数据类型.基本数据类型可以直接完成算术运算.例如: #include<bits/stdc++.h> using namespace std; in

  • C++中的运算符重载详解

    目录 1.引例 2.类中自动建立的函数 3.重载赋值运算符解析 总结 1.引例 class Complex { private: double Real,Image; public: Complex():Real(0),Image(0) {} Complex(double r, double i) : Real(r),Image(i) {} ~Complex() {} }; int main() { Complex c1(1.2,2.3); Complex c2(45,56); Complex

  • C++超详细讲解运算符重载

    目录 概念 赋值运算符重载 const成员 取地址及const取地址操作符重载 概念 C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类 型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似. 函数名字为:关键字operator后面接需要重载的运算符符号. 函数原型:返回值类型 operator操作符(参数列表) 需要注意的几点: 不能通过连接其他符号来创建新的操作符:比如operator@,必须是已有的操作符: 重载操作符必须有一个类类型

  • C++超详细讲解函数重载

    目录 1 函数重载的定义 2 构成函数重载的条件 3 编译器调用重载函数的准则 4 函数重载的注意事项 4.1 避开重载带有指定默认值参数的函数 4.2 注意函数重载遇上函数指针 4.3 C++编译器不能以 C 的方式编译重载函数 1 函数重载的定义 函数重载:使用同一个函数名定义不同的函数.从本质上来看,就是互相独立的不同函数,每一个函数类型不同.因此,函数重载是由函数名和参数列表决定的. 注意:函数返回值不能作为函数重载的重要依据! 2 构成函数重载的条件 当满足以下三个条件之一时,便可以构

  • C++超详细讲解RTTI和cast运算符的使用

    目录 1. RTTI 1.1 dynamic_cast运算符 1.2 typeid运算符 2. cast运算符 1. RTTI RTTI是运行阶段类型识别(Running Type Identificarion)的简称. 如何知道指针指向的是哪种对象? 这是个很常见的问题,由于我们允许使用基类指针指向派生类,所以基类指针指向的对象可能是基类对象,也可能是派生类对象.但是我们需要知道对象种类,因为我们需要使用正确的类方法. RTTI能解决上述问题. 1.1 dynamic_cast运算符 dyna

  • C++超详细讲解操作符的重载

    目录 一.需要解决的问题 二.操作符重载 三.小结 一.需要解决的问题 下面的复数解决方案是否可行? 下面看一下复数的加法操作: #include <stdio.h> class Complex { int a; int b; public: Complex(int a = 0, int b = 0) { this->a = a; this->b = b; } int getA() { return a; } int getB() { return b; } friend Comp

  • C++超详细讲解数组操作符的重载

    目录 一.字符串类的兼容性 二.重载数组访问操作符 三.小结 一.字符串类的兼容性 问题:string 类对象还具备 C 方式字符串的灵活性吗?还能直接访问单个字符吗? string 类最大限度的考虑了 C 字符串的兼容性 可以按照使用 C 字符串的方式使用 string 对象 下面看一个用 C 方式使用 string 类的示例: #include <iostream> #include <string> using namespace std; int main() { stri

  • C++函数模板与重载解析超详细讲解

    目录 1.快速上手 2.重载的模板 3.模板的局限性 4.显式具体化函数 5.实例化和具体化 6.重载解析 6.1 概览 6.2 完全匹配中的三六九等 6.3 总结 7.模板的发展 1.快速上手 函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数. #include<iostream> using namespace std; template <typename T> void Swap(T &a,T &b);//模板原型 struct apple{ st

  • Python海象运算符超详细讲解

    目录 介绍 语法 用法 if 语句 while 循环 while 循环逐行读取文件 while 循环验证输入 推导式 三元表达式 总结 介绍 海象运算符,即 := ,在 PEP 572 中被提出,并在 Python3.8 版本中发布. 海象运算符的英文原名叫Assignment Expresions,即赋值表达式. 它由一个冒号:和一个等号=组成,即:=.而它被称作walrus operator(海象运算符),是因为它长得像一只海象. 语法 海象运算符的语法格式如下: variable_name

  • C++ Boost Optional示例超详细讲解

    目录 一.概述 二.Boost.Optional 一.概述 数据结构类似于容器,因为它们可以存储一个或多个元素.但是,它们与容器不同,因为它们不支持容器通常支持的操作.例如,使用本部分介绍的数据结构,不可能在一次迭代中访问所有元素. Boost.Optional 可以很容易地标记可选的返回值.使用 Boost.Optional 创建的对象要么是空的,要么包含单个元素.使用 Boost.Optional,您无需使用空指针或 -1 等特殊值来指示函数可能没有返回值. Boost.Tuple 提供了

  • C++ Boost Variant示例超详细讲解

    目录 一.提要 二.示例 一.提要 Boost.Variant 提供了一个类似于 union 的名为 boost::variant 的类.您可以将不同类型的值存储在 boost::variant 变量中.在任何时候只能存储一个值.分配新值时,旧值将被覆盖.但是,新值的类型可能与旧值不同.唯一的要求是这些类型必须作为模板参数传递给 boost::variant,这样它们才能为 boost::variant 变量所知. boost::variant 支持任何类型.例如,可以将 std::string

  • C++ Boost Uuid超详细讲解

    目录 一.说明 二.Boost.Uuid库示例和代码 一.说明 Boost.Uuid 为 UUID 提供生成器. UUID 是不依赖于中央协调实例的通用唯一标识符.例如,没有数据库存储所有生成的 UUID,可以检查这些 UUID 是否使用了新的 UUID. UUID 由必须唯一标识组件的分布式系统使用.例如,Microsoft 使用 UUID 来识别 COM 世界中的接口.对于为 COM 开发的新接口,可以轻松分配唯一标识符. UUID 是 128 位数字.存在多种生成 UUID 的方法.例如,

随机推荐