1. 程式人生 > >C++學習筆記(10)運算子過載,友元函式,友元類

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