C++学习笔记(10)运算符重载,友元函数,友元类
c++允许我们为运算符定义专门的函数,这被称为运算符重载:
运算符可以简化字符串的操作,‘+’,以及使用关系运算符比较字符串,[ ]运算符访问向量中的元素;
例如:
#include <iostream> #include <string> #include <vector> using namespace std; int main() { string s1("Washton"); string s2("Calinifor"); cout << "First of s1 is " << s1[0] << endl; cout << "s1 + s2 = " << s1+s2 << endl; vector<int> v; v.push_back(3); v.push_back(4); cout << "The first element in v is " << v[0] << endl; return 0; }
运算符实际上是类中定义的函数,这些函数以关键字operator加上运算符来命名。
例如上面的程序:
#include <iostream> #include <string> #include <vector> using namespace std; int main() { string s1("Washton"); string s2("Calinifor"); cout << "First of s1 is " << s1.operator[](0) << endl; cout << "s1 + s2 = " << operator+(s1, s2) << endl; cout << "s1 < s2 = " << operator<(s1, s2) << endl; vector<int> v; v.push_back(3); v.push_back(4); cout << "The first element in v is " << v.operator[](0) << endl; return 0; }
上面的代码中operator[ ]是string类中的成员函数。
operator+和operator<不是string类的成员函数:
运算符函数的定义称为运算符重载。如何在我们自己定义的类中重载运算符?
1.Rational类:
有理数:能够表示为两个整数之比的数
c++为整形和浮点型提供了数据结构,但是不支持有理数。
Rational类的实现:
Ratonal.h文件: 类的定义文件
#include <iostream> #include <string> using namespace std; #ifndef RATIONAL_H #define RATIONAL_H class Rational { private: int numerator; // 有理数分子 int denominator; // 分母 static int gcd(int a, int b); // Greastest Common Divisor function public: Rational(); Rational(int numerator, int denominator); int get_numberator() const; int get_denominator() const; // 运算符重载 Rational add(const Rational& rat); Rational substract(const Rational& rat); Rational multiply(const Rational& rat); Rational divide(const Rational& rat); int compareTo(const Rational& rat); bool equalTo(const Rational& rat); int get_intValue() const; double get_floatValue() const; string get_string() const; }; #endif
Ratonal.cpp文件: 类的实现
#include <iostream> #include <string> #include <cstdlib> #include <sstream> #include "E:\back_up\code\c_plus_code\chapter14\external_file\rational.h" using namespace std; Rational::Rational() { numerator = 0; denominator = 1; } Rational::Rational(int numerator, int denominator) { int gcd_value = gcd(numerator, denominator); this->numerator = ((denominator>0)?1:-1)*numerator/gcd_value; this->denominator = abs(denominator)/gcd_value; } int Rational::gcd(int a, int b) { // 求n1, n2的最大公约数 int n1 = abs(a); int n2 = abs(b); int tmp = (n1<n2)?n1:n2; while(tmp>1) { if(a%tmp==0 && b%tmp==0) { break; } else { tmp--; } } return tmp; } int Rational::get_numberator() const { return numerator; } int Rational::get_denominator() const { return denominator; } Rational Rational::add(const Rational& rat) { int rat_num = rat.get_numberator(); // 分子 int rat_den = rat.get_denominator(); // 分母 int result_num = numerator*rat_den + denominator*rat_num; int result_den = denominator*rat_den; // int gcd_value = gcd(result_num, result_den); return Rational(result_num, result_den); } Rational Rational::substract(const Rational& rat) { int rat_num = rat.get_numberator(); // 分子 int rat_den = rat.get_denominator(); // 分母 int result_num = numerator*rat_den - denominator*rat_num; int result_den = denominator*rat_den; // int gcd_value = gcd(result_num, result_den); return Rational(result_num, result_den); } Rational Rational::multiply(const Rational& rat) { int rat_num = rat.get_numberator(); // 分子 int rat_den = rat.get_denominator(); // 分母 int result_num = numerator*rat_num; int result_den = denominator*rat_den; // int gcd_value = gcd(result_num, result_den); return Rational(result_num, result_den); } Rational Rational::divide(const Rational& rat) { int rat_num = rat.get_numberator(); // 分子 int rat_den = rat.get_denominator(); // 分母 int result_num = numerator*rat_den; int result_den = denominator*rat_num; // int gcd_value = gcd(result_num, result_den); return Rational(result_num, result_den); } int Rational::compareTo(const Rational& rat) { Rational tmp_rat = substract(rat); return (tmp_rat.numerator>0)?1:(tmp_rat.numerator==0)?0:-1; } bool Rational::equalTo(const Rational& rat) { if(compareTo(rat)==0) { return true; } else { return false; } } int Rational::get_intValue() const { return numerator/denominator; } double Rational::get_floatValue() const { return (1.0*numerator)/denominator; } string Rational::get_string() const { stringstream ss; ss << numerator; if(denominator>1) { ss << "/" << denominator; } return ss.str(); }
main.cpp文件:
#include <iostream> #include <string> #include "E:\back_up\code\c_plus_code\chapter14\external_file\rational.h" using namespace std; void displayRat(const Rational&); int main(int argc, char *argv[]) { Rational rat1(2, 7); Rational rat2(5, 9); displayRat(rat1.add(rat2)); displayRat(rat1.substract(rat2)); displayRat(rat1.multiply(rat2)); displayRat(rat1.divide(rat2)); cout << "Rat1 compare Rat2: " << rat1.compareTo(rat2) << endl; cout << "The int value of rat1 is " << rat1.get_intValue() << endl; cout << "The double value of rat2 is " << rat2.get_floatValue() << endl; return 0; } void displayRat(const Rational& rat) { cout << "The Rational number is " << rat.get_string() << endl; }
功能测试运行结果:
技巧:
1. 在对有理数的封装过程中,将分母的符号转化到分子上,这样整个有理数的大小仅由分子决定
2. 将约分的过程封装到Rational类中;
3.abs()函数定义在c++标准库cstdlib
4.定义字符流ss << numerator 将数字转化为字符串:
5.在Rational类中对运算符加减乘除进行了定义和实现
------------------------------------分割线------------------------------------
3.运算符函数
能否对两个有理数 r1, r1 进行如下操作:
例如: r1 + r2, r1*r2 等等
Yes! 我们可以在类中定义一种称为运算符函数(operator function)的函数。
函数的格式与普通的函数相比:
1.函数名必须使用operato关键字
2.operator后接真正的运算符
例如: bool operator<(const Rational& rat)
调用: r1.operator<(r2) -->简化为: r1 < r2
对上面的代码进行修改,重载运算符:
Rational.h文件添加部分:
// 运算符重载 Rational operator+(const Rational& rat); Rational operator-(const Rational& rat); Rational operator*(const Rational& rat); Rational operator/(const Rational& rat); bool operator<(const Rational& rat);
Rational.cpp添加的部分:
// 运算符重载 Rational Rational::operator+(const Rational& rat) { return add(rat); } Rational Rational::operator-(const Rational& rat) { return substract(rat); } Rational Rational::operator*(const Rational& rat) { return multiply(rat); } Rational Rational::operator/(const Rational& rat) { return divide(rat); } bool Rational::operator<(const Rational& rat) { /* if(compareTo(rat)==-1) { return true; } else { return false; } */ return (compareTo(rat)==-1)?true:false; }
main.cpp:
#include <iostream> #include <string> #include "E:\back_up\code\c_plus_code\chapter14\external_file\rational.h" using namespace std; void displayRat(const Rational&); int main(int argc, char *argv[]) { Rational rat1(2, 7); Rational rat2(5, 9); displayRat(rat1+rat2); displayRat(rat1-rat2); displayRat(rat1*rat2); displayRat(rat1/rat2); cout << "Rat1 < Rat2 is: " << ((rat1<rat2)?"true":"false") << endl; cout << "The int value of rat1 is " << rat1.get_intValue() << endl; cout << "The double value of rat2 is " << rat2.get_floatValue() << endl; return 0; } void displayRat(const Rational& rat) { cout << "The Rational number is " << rat.get_string() << endl; }
结果:
注意在调用‘<’运算符,需用括号(r1<r2),避免在输出结果时报错:
可重载的运算符:
+ | - | * | / | % | ^ | & | \ | | | ! | = |
< | > | += | -= | *= | /= | %= | ^= | &= | |= | << |
>> | >>= | <<= | == | != | <= | >= | && | || | ++ | -- |
->* | , | -> | [ ] | () | new | delete |
不可重载的运算符:
?: | . | .* | :: |
2.重载[ ]运算符:
在数组中,[ ]运算符可以: 1. 访问数组的元素, 例如a[1] 2.可以修改数组的元素,例如a[3]=9,进行赋值
我们也想通过[ ]运算符来访问对象的一些特性,比如,有理数的分子和分母:
先介绍一种不正确的 [ ] 重载方法:
rational.h文件添加:
int operator[](int index);
rational.cpp文件:
int Rational::operator[](int index) // 注意这个运算符的重载 { if(index==0) { return numerator; // 返回分子 } else { return denominator; // 返回分母 } }
在main.cpp中:
Rational rat1(2, 7); cout << "The num of r1 is " << rat1[0] << " and the den of r1 is " << rat1[1] << endl;
可以正常访问对象的元素(有理数的分子分母)
但是当编写:
rat1[0] = 1; rat1[1] = 2; displayRat(rat1);
程序编译报错: 因此我们对[ ]的重载只实现了[ ]的第一个功能:究其原因,[ ]重载函数返回的只是一个int数(即分子分母的值)
,是不能再进行赋值的。所以[ ]重载函数应该返回对象分子分母的引用,就可以实现正确的重载。
rational.h文件
int& operator[](int index);
rational.cpp
int& Rational::operator[](int index) // 注意这个运算符的重载 { if(index==0) { return numerator; // 返回分子 } else { return denominator; // 返回分母 } }
这样再编译主函数中的代码便不会报错!
左值: 任何可以出现在=左部的内容
右值:
上述实际是将r[ ]变为左值,(即进行赋值r[ ]=)
-------------------------------------------分割线--------------------------------------
2..重载简写运算符:+=, -=, *=, /=
因为简写运算符都左值运算符,同理,重载函数返回的是引用(a+=2 a = a+2)
rational.h文件添加:
Rational& operator+=(const Rational& rat);
rational.cpp文件:
Rational& Rational::operator+=(const Rational& rat) { // this 存放的是对象的地址 *this = add(rat); return *this; }
关于this个人的理解, 保存对象的地址,当一执行代码:
rat1 += Rational(1, 4);
(rat1 +)= Rational(1, 4);
括号里的部分返回一个rat的引用, 再进行rat1+Rational(1,4)计算,将计算的值赋给引用,就相当于改变对象的值。(总感觉有点绕)。
-------------------------------------这里穿插一段this指针的介绍------------------------------------------------
内容来源于博客: http://c.biancheng.net/view/170.html, 对this指针的本质作了介绍
C++ 程序到C程序的翻译:
C++ 是在C语言的基础上发展而来的,第一个 C++ 的编译器实际上是将 C++ 程序翻译成C语言程序,然后再用C语言编译器进行编译。
C语言没有类的概念,只有结构,函数都是全局函数,没有成员函数。翻译时,将 class 翻译成 struct、对象翻译成结构体变量是显而易见的,但是对类的成员函数应该如何翻译?对myCar.Modify();这样通过一个对象调用成员函数的语句,又该如何翻译呢?
C语言中只有全局函数,因此成员函数只能被翻译成全局函数;myCar.Modify();这样的语句也只能被翻译成普通的调用全局函数的语句。那如何让翻译后的 Modify 全局函数还能作用在 myCar 这个结构变量上呢?答案就是引入“this 指针”。下面来看一段 C++ 程序到C 程序的翻译。
先写一段c++代码:
#include <iostream> #include <cmath> using namespace std; // 定义一个类car class car { private: int price; public: void setPrice(int p) { price = p; } } int main(int argc, char *argv[]) { car c; // 声明一个car对象 c.setPrice(1100); // 调用类成员函数 return 0; }
翻译后的C程序(此程序应保存为扩展名为 .c 的文件后再编译):
#include <iostream> #include <cmath> using namespace std; // 定义一个结构体 struct car { int price; } // c代码中只能定义全局函数 void setPrice(struct car* this, int p) { this->price = p; // c中通过这种方式使得类成员函数作用在对象上 这就是this指针作用 } int main(int argc, char *argv[]) { struct car c; setPrice(&c, 1200); return 0; }
this 指针的作用:
实际上,现在的C编译器从本质上来说也是按上面的方法来处理成员函数和对成员函数的调用的,即非静态成员函数实际上的形参个数比程序员写的多一个。多出来的参数就是所谓的“this指针”。这个“this指针”指向了成员函数作用的对象,在成员函数执行的过程中,正是通过“Ihis指针”才能找到对象所在的地址,因而也就能找到对象的所有非静态成员变量的地址。
---------------------------------------------------------------------------------------------------------------------------
-----------------------------------------分割线------------------------------------------
3. 重载一元运算符:
一元运算符+,-可以被重载,一元运算符作用于一个运算对象,即调用它的对象本身,因此一元运算符函数没有参数。
rational.h文件中添加:
Rational operator-();
在rational.cpp文件中添加:
Rational Rational::operator-() { return Rational(-numerator, denominator); }
重载++,--运算符:
前缀加,减运算符和后缀加减运算符可以被重载,例如下面的代码
Rational r1(2,3); Rational r2 = r1++; Rational r3 = (1,3); Rational r4 = r3++; Rational r5 = ++r3;
c++如何分辨++/--是前缀还是后缀:
后缀:用一个特殊的int类型的伪参数来表示,前缀形式不需要任何参数:
前缀运算符是左值运算符,后缀运算符不是,所以他们的具体实现有区别
h文件定义:
// 重载++,--运算符 //左值运算符 ++a; 且前缀不需要参数 Rational& operator++(); // 右值运算符a++; 后缀运算符需要参数 dummy:伪参数 Rational operator++(int dummy);
rationa.cpp
//左值运算符: 前缀运算符 返回的是引用 Rational& Rational::operator++() { numerator += denominator; return *this; } // 右值运算符 Rational Rational::operator++(int dummy) { Rational temp(numerator, denominator); numerator += denominator; return temp; }
--------------------------------------2018/11/28 分割线------------------------------
友元函数和友元类
可以通过定义一个友元函数或者友元类。使得它能够访问其他类中的私有成员!
类的私有成员在类外不能被访问,如果需要一个受信任的函数或者类访问一个类的私有成员,C++通过friend关键字所定义的友元函数和友元类实现这一目的。
例子:
定义一个Date类:
#ifndef DATE_H #define DATE_H class Date { private: int year; int month; int day; public: Date(int year, int month, int day) { this->year = year; this->month = month; this->day = day; } // AccessDate类被定义为友元类 friend class AccessDate; }; #endif
main.cpp
#include <iostream> #include "E:\back_up\code\c_plus_code\test_friend\external_file\date.h" using namespace std; class AccessDate // 声明一个AccessDate类 { public: static void p() //定义静态方法 p() { Date birthdate(2010, 3, 1); birthdate.year = 1994; // 在友元类中可以访问Date的私有成员 cout << birthdate.year << endl; } }; // 类的定义在这里有分号 int main(int argc, char *argv[]) { AccessDate::p(); return 0; }
可以将友元类修改为友元函数:
date.h文件
#ifndef DATE_H #define DATE_H class Date { private: int year; int month; int day; public: Date(int year, int month, int day) { this->year = year; this->month = month; this->day = day; } // 定义友元函数 p() friend void p(); }; #endif
main.cpp
#include <iostream> #include "E:\back_up\code\c_plus_code\test_friend\external_file\date.h" using namespace std; void p() // 友元函数 { Date date(2010,4,12); date.year = 1981; cout << date.year << endl; } int main(int argc, char *argv[]) { p(); return 0; }
定义的友元函数p()虽然不是Date类的成员函数,但是他可以访问Date类的私有成员
-----------------------------------END------------------------------------
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)