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)计算,将计算的值赋给引用,就相当于改变对象的值。(总感觉有点绕)。

-----------------------------------------分割线------------------------------------------

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

猜你喜欢

转载自blog.csdn.net/zj1131190425/article/details/84404602
今日推荐