第11章 使用類
- 如果函數使用常規參數而不是引用參數,將發生什麼情況呢?
- 如果忽略了析構函數,又將發生什麼情況呢?
- 運算符重載,它允許將標準
C++
運算符用於類對象。 - 友元,這種
C++
機制使得非成員函數可以訪問私有數據。 - 介紹如何命令
C++
對類執行自動類型轉換。
11.1 運算符重載
- 運算符重載是一種形式的
C++
多態。 - 要重載運算符,需使用被稱爲運算符函數的特殊函數形式。
運算符函數格式如下:
operatorop(argument-list)
op
必須是有效的C++
運算符,不能虛構一個新的符號。
11.2 計算時間:一個運算符重載示例
注意參數是引用,但返回類型卻不是引用。
將參數聲明爲引用的目的是爲了提高效率。
警告
不要返回指向局部變量或臨時對象的引用。函數執行完畢後,局部變量和臨時對象將消失,引用將指向不存在的數據。
11.2.1 添加加法運算符
11.2.2 重載限制
C++
對用戶定義的運算符重載的限制:
- 重載後的運算符必須至少有一個操作數是用戶定義的類型,這將防止用戶爲標準類型重載運算符。
- 使用運算符時不能違反運算符原來的句法規則。
– 不能修改運算符的優先級。 - 不能創建新運算符。
- 不能重載下面的運算符。
- 下表中的大多數運算符都可以通過成員或非成員函數進行重載。
下面的運算符只能通過成員函數進行重載。
=
:賦值運算符()
:函數調用運算符[]
:下標運算符->
:通過指針訪問類成員的運算符
11.2.3 其他重載運算符
11.3 友元
C++
控制對類對象私有部分的訪問,公有類方法提供唯一的訪問途徑,但有時這種限制太嚴格。
C++
提供了另外一種形式的訪問權限:友元。
- 友元函數:通過讓函數成爲類的友元,可以賦予該函數與類的成員函數相同的訪問權限。可以訪問類的私有成員。
- 友元類(第15章介紹)
- 友元成員函數(第15章介紹)
11.3.1 創建友元
創建友元函數的第一步是將其原型放在類聲明中,並在原型聲明前加上關鍵字friend;
第二步是編寫函數定義。
- 雖然友元函數在類聲明中聲明的,但它不是成員函數,因此不能使用成員運算符來調用。
- 雖然友元函數不是成員函數,但它與成員函數的訪問權限相同。
友元是否有悖於OOP
- 應將友元函數看作類的擴展接口的組成部分。
- 類聲明仍然控制了那些函數可以訪問私有數據。
提示:
如果要爲類重載運算符,並將非類的項作爲其第一個操作數,則可以用友元函數來反轉操作數的順序。
11.3.2 常用的友元:重載<<運算符
1. <<的第一種重載版本
函數成爲Time類的一個友元函數。
2. <<的第二種重載版本
代碼:mytime0.h
#include <iostream>
class Time{
private:
int hours;
int minutes;
public:
Time();
Time(int h,int m = 0);
void AddMin(int m);
void AddHr(int h);
void Reset(int h=0,int m=0);
Time Sum(const Time & t) const;
Time operator+(const Time & t) const;
Time operator-(const Time & t) const;
Time operator*(double mult) const;
void Show() const;
friend Time operator*(double m, const Time & t){
return t*m;
};
friend std::ostream & operator<<(std::ostream & os, const Time & t);
};
代碼:mytime0.cpp
#include <iostream>
#include "mytime0.h"
Time::Time(){
hours = minutes = 0;
}
Time::Time(int h,int m){
hours = h;
minutes = m;
}
void Time::AddMin(int m){
minutes += m;
hours += minutes/60;
minutes = minutes%60;
}
void Time::AddHr(int h){
hours += h;
}
void Time:: Reset(int h,int m){
hours = h;
minutes = m;
}
Time Time::Sum(const Time & t) const{
Time sum;
sum.minutes = minutes + t.minutes;
sum.hours = hours + t.hours + sum.minutes/60;
sum.minutes %= 60;
return sum;
}
Time Time::operator+(const Time & t) const{
Time sum;
sum.minutes = minutes + t.minutes;
sum.hours = hours + t.hours + sum.minutes/60;
sum.minutes %= 60;
return sum;
}
Time Time::operator-(const Time & t) const{
Time diff;
int tot1,tot2;
tot1 = t.minutes + 60*t.hours;
tot2 = minutes + 60*hours;
diff.minutes = (tot2-tot1)%60;
diff.hours = (tot2-tot1)/60;
return diff;
}
Time Time::operator*(double mult) const{
Time result;
long totalminutes = hours*60*mult + minutes*mult;
result.hours = totalminutes/60;
result.minutes = totalminutes%60;
return result;
}
void Time::Show() const{
std::cout << hours << " hours, " << minutes << " mintues" << std::endl;
}
std::ostream & operator<<(std::ostream & os, const Time & t){
os << t.hours << " hours, " << t.minutes << " minutes;" << std::endl;
return os;
}
int main(){
using std::cout;
using std::endl;
Time planning;
Time coding(2, 40);
Time fixing(5, 55);
Time total;
Time total2;
Time total3;
cout << "planning time = ";
planning.Show();
cout << endl;
cout << "coding time = ";
coding.Show();
cout << endl;
cout << "fixing time =";
fixing.Show();
cout << endl;
cout << "coding.Sum(fixing) = ";
total = coding.Sum(fixing);
total.Show();
cout << endl;
cout << "coding.operator+(fixing) = ";
total2 = coding.operator+(fixing);
total2.Show();
cout << endl;
cout << "coding.+(fixing) = ";
total3 = coding+fixing;
total3.Show();
cout << endl;
Time morefixing(3, 28);
cout << "more fixing time = ";
morefixing.Show();
cout << endl;
cout << "more fixing time + total = ";
total3 = morefixing + total3;
total3.Show();
cout << endl;
Time weeding(4, 35);
Time waxing(2, 47);
Time total4;
Time diff;
Time adjusted;
total4 = weeding + waxing;
cout << "weeding + waxing = ";
total4.Show();
cout << endl;
diff = weeding - waxing;
cout << "weeding - waxing = ";
diff.Show();
cout << endl;
adjusted = total4 * 1.5;
cout << "adjusted_work time = ";
adjusted.Show();
cout << endl;
Time aida(3, 35);
Time tosca(2, 48);
Time temp;
cout << "aida and tosca:" << endl;
cout << aida << tosca;
temp = aida + tosca;
cout << "aida+ tosca = "<< temp;
temp = aida * 1.17;
cout << "aida * 1.17: " << temp;
cout << "10.0 * tosca: " << 10.0 * tosca;
return 0;
}
11.4 重載運算符:作爲成員函數還是非成員函數
注意:
非成員版本的重載運算符函數所需的行參數目與運算符使用的操作數數目相同;而成員版本所需的參數數目少一個,因爲其中的一個操作數是被隱式地傳遞的調用對象。
11.5 再談重載:一個矢量類
矢量,是一個有大小(長度)和方向(角度)的量。
11.5.1 使用狀態成員
mode,這樣的成員被稱爲狀態成員state member
,因爲這種成員描述的是對象所處的狀態。
11.5.2 爲Vector類重載算術運算符
注意
因爲運算符重載是通過函數來實現的,所以只要運算符函數的特徵標不同,使用的運算符數量與相應的內置C++運算符相同,就可以多次重載同一個運算符。
代碼:vector.h
#include <iostream>
namespace VECTOR{
class Vector{
public:
enum Mode{RECT, POL};
private:
double x;
double y;
double mag;
double ang;
Mode mode;
void set_mag();
void set_ang();
void set_x();
void set_y();
public:
Vector();
Vector(double n1,double n2,Mode form = RECT);
void reset(double n1,double n2,Mode form = RECT);
~Vector();
double xval() const{return x;}
double yval() const{return y;}
double magval() const{return mag;}
double angval() const{return ang;}
void polar_mode();
void rect_mode();
Vector operator+(const Vector & b)const;
Vector operator-(const Vector & b)const;
Vector operator-() const;
Vector operator*(double n)const;
friend Vector operator*(double n,const Vector & a );
friend std::ostream & operator<<(std::ostream & os,const Vector & v);
};
};
代碼:vector.cpp
#include <iostream>
#include <cmath>
#include "vector.h"
using std::sin;
using std::cos;
using std::sqrt;
using std::atan;
using std::atan2;
using std::cout;
using std::endl;
namespace VECTOR{
const double Rad_to_deg = 45.0/atan(1.0);
void Vector::set_mag(){
mag = sqrt(x*x + y*y);
}
void Vector::set_ang(){
if(x==0.0&&y==0.0){
ang = 0.0;
}else{
ang = atan2(y,x);
}
}
void Vector::set_x(){
x = mag*cos(ang);
}
void Vector::set_y(){
y = mag*sin(ang);
}
Vector::Vector(){
x = y = mag = ang = 0.0;
mode = RECT;
}
Vector::Vector(double n1,double n2,Mode form){
mode = form;
if(form == RECT){
x = n1;
y = n2;
set_mag();
set_ang();
}else if(form == POL){
mag = n1;
ang = n2/Rad_to_deg;
set_x();
set_y();
}else{
cout << "Incorrect 3rd argument to Vector()--";
cout << "vector set to 0\n";
x = y = mag = ang = 0.0;
mode = RECT;
}
}
void Vector::reset(double n1,double n2,Mode form){
mode = form;
if(form == RECT){
x = n1;
y = n2;
set_mag();
set_ang();
}else if(form == POL){
mag = n1;
ang = n2/Rad_to_deg;
set_x();
set_y();
}else{
cout << "Incorrect 3rd argument to Vector()--";
cout << "vector set to 0\n";
x = y = mag = ang = 0.0;
mode = RECT;
}
}
Vector::~Vector(){
}
void Vector::polar_mode(){
mode = POL;
}
void Vector::rect_mode(){
mode = RECT;
}
Vector Vector::operator+(const Vector & b)const{
return Vector(x+b.x, y+b.y);
}
Vector Vector::operator-(const Vector & b)const{
return Vector(x-b.x, y-b.y);
}
Vector Vector::operator-() const{
return Vector(-x,-y);
}
Vector Vector::operator*(double n) const{
return Vector(n*x,n*y);
}
Vector operator*(double n,Vector & a){
return a*n;
}
std::ostream & operator<<(std::ostream &os,const Vector & v){
if(v.mode == Vector::RECT){
os << "(x,y) = (" << v.x << ", " << v.y << ")" << endl;
}else if(v.mode == Vector::POL){
os << "(m,a) = (" << v.mag << ", " << v.ang*Rad_to_deg <<")" << endl;
}else{
os << "Vector object mode is invalid\n";
}
return os;
}
}
int main(){
VECTOR::Vector shove;
shove.reset(100,300);
cout << shove;
return 0;
}
11.5.3 對實現的說明
11.5.4 使用Vector類來模擬隨機漫步 (🏁沒看這一節)
11.6 類的自動轉換和強制類型轉換
- 將一個標準類型變量的值賦給另一種標準類型的變量時,如果這兩種類型兼容,則
C++
自動將這個值轉化爲接受變量的類型。 - 在C++中,接受一個參數的構造函數爲將類型與該參數相同的值轉換爲類提供了藍圖。
- C++新增了關鍵字explicit,用於關閉這種自動特性,就只能顯示轉換。
11.6.1 轉換函數
- 構造函數只用於從某種類型到類類型的轉換。要進行相反的轉換,必須使用特殊的C++運算符函數——轉換函數。
請注意一下幾點: - 轉換函數必須是類方法;
- 轉換函數不能指定返回類型;
- 轉換函數不能有參數。
自動應用類型轉換
代碼:stonewt.h
class Stonewt{
private:
enum {Lbs_per_stn = 14};
int stone;
double pds_left;
double pounds;
public:
Stonewt(double lbs);
Stonewt(int stn, double lbs);
Stonewt();
~Stonewt();
void show_lbs() const;
void show_stn() const;
operator int() const;
operator double() const;
};
代碼:stonewt.cpp
#include <iostream>
#include "stonewt.h"
using std::cout;
Stonewt::Stonewt(double lbs){
stone = int (lbs)/Lbs_per_stn;
pds_left = int (lbs)%Lbs_per_stn + lbs - int(lbs);
pounds = lbs;
}
Stonewt::Stonewt(int stn,double lbs){
stone = stn;
pds_left = lbs;
pounds = stn*Lbs_per_stn + lbs;
}
Stonewt::Stonewt(){
stone = pounds = pds_left = 0;
}
Stonewt::~Stonewt(){
}
void Stonewt::show_stn() const{
cout << stone << " stone, " << pds_left << " pounds \n";
}
void Stonewt::show_lbs() const{
cout << pounds << " pounds \n";
}
Stonewt::operator int() const{
return int (pounds + 0.5);
}
Stonewt::operator double() const{
return double(pounds);
}
void display(const Stonewt & st,int n);
int main(){
Stonewt incognito = 275;
Stonewt wolfe(285.7);
Stonewt taft(21,8);
cout << "The celebrity weighed ";
incognito.show_stn();
cout << "The detective weighed ";
wolfe.show_stn();
cout << "The President weighed ";
taft.show_lbs();
incognito = 276.8;
taft = 325;
cout << "After dinner,The celebrity weighed";
incognito.show_stn();
cout << "After dinner,TThe President weighed";
taft.show_lbs();
display(taft, 2);
cout << "The wrestler weighed even more .\n";
display(422, 2);
cout << "No stone left unearned\n";
Stonewt poppins(9, 2.8);
double p_wt = poppins;
cout << "Convert to double = >";
cout << "Poppins: " << p_wt << " pounds\n";
cout << "Convert to int = >";
cout << "Poppins: " << int(poppins) << " pounds\n";
return 0;
}
void display(const Stonewt & st,int n){
for(int i=0;i<n;i++){
cout << "wow!";
st.show_stn();
}
}
11.6.2 轉換函數和友元函數
11.7 總結
- 一般來說,訪問私有類成員的唯一方法是使用類方法。
C++
使用友元函數來避免這種限制。要讓函數成爲友元,需要在類聲明中聲明該函數,並在聲明前加上關鍵字freind
。 C++
擴展了對運算符的重載,允許自定義特殊的運算符函數,這種函數描述了特定的運算符與類之間的關係。運算符函數可以是類成員函數,也可以是友元函數(有一些運算符函數只能是類成員函數)。要調用運算符函數,可以直接調用該函數,也可以以通常的句法使用被重載的運算符。C++
允許指定在類和基本類型之間進行轉換的方式。首先,任何接受唯一一個參數的構造函數都可被用作轉換函數,將類型與該參數相同的值轉換爲類。如果將類型與該參數相同的值賦給對象,則C++
將自動調用該構造函數。如果在該構造函數的聲明前加上了關鍵詞explicit,則該構造函數將只能用於顯示轉換。- 要將類對象轉換爲其他類型,必須定義轉換函數,指出如何進行這種轉換。轉換函數必須是成員函數。