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