第4章 類與物件
類與物件
- 面向物件程式的基本特點
- 類和物件
- 建構函式
- 解構函式
- 類的組合
- 前向引用宣告
- UML簡介
- 結構體與聯合體
- 聯合體
- 列舉類
- 小結
面向物件程式的基本特點
抽象
-
對同一類物件的共同屬性和行為進行概括,形成類。
-
先注意問題的本質及描述,其次是實現過程或細節。
-
資料抽象:描述某類物件的屬性或狀態(物件相互區別的物理量)。
-
程式碼抽象:描述某類物件的共有的行為特徵或具有的功能。
-
抽象的實現:類。
-
抽象例項——鐘錶
-
資料抽象:
int hour,int minute,int second
- 程式碼抽象:
setTime(),showTime()
class Clock {
public:
void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour, minute, second;
};
封裝
- 將抽象出的資料、程式碼封裝在一起,形成類。
- 目的:增強安全性和簡化程式設計,使用者不必瞭解具體的實現細節,而只需要通過外部介面,以特定的訪問許可權,來使用類的成員。
- 實現封裝:類宣告中的{}
例:
class Clock {
public: void setTime(int newH, int newM, int newS);
void showTime();
private: int hour, minute, second;
};
繼承
- 在已有類的基礎上,進行擴充套件形成新的類。
多型
-
多型:同一名稱,不同的功能實現方式。
-
目的:達到行為標識統一,減少程式中識別符號的個數。
-
實現:過載函式和虛擬函式
類和物件
類和物件的定義
- 物件是現實中的物件在程式中的模擬。
- 類是同一類物件的抽象,物件時類的某一特定實體。
- 定義類的物件,才可以通過物件使用類中定義的功能。
- 設計類就是設計型別
- 此型別的“合法值”是什麼?
- 此型別應該有什麼樣的函式和操作符?
- 新型別的物件該如何被建立和銷燬?
- 如何進行物件的初始化和賦值?
- 物件作為函式的引數如何以值傳遞?
- 誰將使用此型別的物件成員?
類定義的語法形式
class 類名稱
{
public:
公有成員(外部介面)
private:
私有成員
protected:
保護型成員
};
類內初始值
(注意:類內初始化是c++11的新特性,vs2012不支援該設定,請注意編譯器版本)
- 可以為資料成員提供一個類內初始值
- 在建立物件時,類內初始值用於初始化資料成員
- 沒有初始值的成員將被預設初始化。
- 類內初始值舉例
class Clock {
public:
void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour = 0, minute = 0, second = 0;
};
類成員的訪問控制
公有型別成員
- 在關鍵字public後面宣告,它們是類與外部的介面,任何外部函式都可以訪問公有型別資料和函式。
私有型別成員
- 在關鍵字private後面宣告,只允許本類中的函式訪問,而類外部的任何函式都不能訪問。
- 如果緊跟在類名稱的後面宣告私有成員,則關鍵字private可以省略。
保護型別成員
- 與private類似,其差別表現在繼承與派生時對派生類的影響不同,詳見第七章。
物件定義的語法
-
類名 物件名;
-
例:Clock myClock;
類成員的訪問許可權
- 類中成員互相訪問
直接使用成員名訪問 - 類外訪問
使用“物件名.成員名”方式訪問 public 屬性的成員
類的成員函式
- 在類中說明函式原型;
- 可以在類外給出函式體實現,並在函式名前使用類名加以限定;
- 也可以直接在類中給出函式體,形成內聯成員函式;
- 允許宣告過載函式和帶預設引數值的函式。
內聯成員函式
- 為了提高執行時的效率,對於較簡單的函式可以宣告為內聯形式。
- 行內函數體中不要有複雜結構(如迴圈語句和switch語句)。
- 在類中宣告內聯成員函式的方式:
- 將函式體放在類的宣告中。
- 使用inline關鍵字。
類和物件程式例項
#include <iostream>
using namespace std;
//類的定義
class Clock {
public:
void setTime(int newH = 0, int newM = 0, int newS = 0);
void showTime();
private:
int hour, minute, second;
};
//成員函式的實現
void Clock::setTime(int newH, int newM, int newS) {
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime() {
cout << hour << ":" << minute << ":" << second;
}
//物件的使用
int main() {
Clock myClock;
myClock.setTime(8, 30, 30);
myClock.showTime();
return 0;
}
建構函式
建構函式的作用
- 在物件被建立時使用特定的值構造物件,將物件初始化為一個特定的初始狀態。
- 例如:
希望在構造一個Clock類物件時,將初試時間設為0:0:0,就可以通過建構函式來設定。
建構函式的形式
- 函式名與類名相同;
- 不能定義返回值型別,也不能有return語句;
- 可以有形式引數,也可以沒有形式引數;
- 可以是行內函數;
- 可以過載;
- 可以帶預設引數值。
建構函式的呼叫時機
- 在物件建立時被自動呼叫
- 例如:
Clock myClock(0,0,0);
預設建構函式
- 呼叫時可以不需要實參的建構函式
- 引數表為空的建構函式
- 全部引數都有預設值的建構函式
- 下面兩個都是預設建構函式,如在類中同時出現,將產生編譯錯誤:
Clock();
Clock(int newH=0,int newM=0,int newS=0);
隱含生成的建構函式
-
如果程式中未定義建構函式,編譯器將在需要時自動生成一個預設建構函式
-
引數列表為空,不為資料成員設定初始值;
-
如果類內定義了成員的初始值,則使用內類定義的初始值;
-
如果沒有定義類內的初始值,則以預設方式初始化;
-
基本型別的資料預設初始化的值是不確定的。
-
“=default”
-
如果程式中已定義建構函式,預設情況下編譯器就不再隱含生成預設建構函式。如果此時依然希望編譯器隱含生成預設建構函式,可以使用“=default”。
-
例如
class Clock {
public:
Clock() =default; //指示編譯器提供預設建構函式
Clock(int newH, int newM, int newS); //建構函式
private:
int hour, minute, second;
};
建構函式例題
例題1
#include <iostream>
using namespace std;
//類的定義
class Clock {
public:
Clock(int newH, int newM, int newS); //建構函式
void setTime(int newH = 0, int newM = 0, int newS = 0);
void showTime();
private:
int hour, minute, second;
};
//建構函式的實現
Clock::Clock(int newH, int newM, int newS) :hour(newH), minute(newM), second(newS) {}
//成員函式的實現
void Clock::setTime(int newH, int newM, int newS) {
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime() {
cout << hour << ":" << minute << ":" << second;
}
//物件的使用
int main() {
Clock myClock(0,0,0);//自動呼叫建構函式
myClock.showTime();
return 0;
}
例題2
#include <iostream>
using namespace std;
//類的定義
class Clock {
public:
Clock(int newH, int newM, int newS); //建構函式
Clock();//預設建構函式
void setTime(int newH = 0, int newM = 0, int newS = 0);
void showTime();
private:
int hour, minute, second;
};
//建構函式的實現
Clock::Clock(int newH, int newM, int newS) :hour(newH), minute(newM), second(newS) {}
Clock::Clock() : hour(0), minute(0), second(0) {}
//成員函式的實現
void Clock::setTime(int newH, int newM, int newS) {
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime() {
cout << hour << ":" << minute << ":" << second<<endl;
}
//物件的使用
int main() {
Clock myClock1(10,0,0);//自動呼叫建構函式
Clock myClock2;//呼叫預設建構函式
myClock1.showTime();
myClock2.showTime();
return 0;
}
委託建構函式
-
類中往往有多個建構函式,只是引數表和初始化列表不同,其初始化演算法都是相同的,這時,為了避免程式碼重複,可以使用委託建構函式。
-
回顧
Clock類的兩個建構函式:
Clock(int newH, int newM, int newS) : hour(newH),minute(newM), second(newS) { //建構函式
}
Clock::Clock(): hour(0),minute(0),second(0) { }//預設建構函式
- 委託建構函式
- 委託建構函式使用類的其他建構函式執行初始化過程
- 例如:
Clock(int newH, int newM, int newS): hour(newH),minute(newM), second(newS){
}
Clock(): Clock(0, 0, 0) { }
複製建構函式
複製建構函式定義
複製建構函式是一種特殊的建構函式,其形參為本類的物件引用。作用是用一個已存在的物件去初始化同類型的新物件。
class 類名 {
public :
類名(形參);//建構函式
類名(const 類名 &物件名);//複製建構函式
// ...
};
類名::類( const 類名 &物件名)//複製建構函式的實現
{ 函式體 }
隱含的複製建構函式
-
如果程式設計師沒有為類宣告拷貝初始化建構函式,則編譯器自己生成一個隱含的複製建構函式。
-
這個建構函式執行的功能是:用作為初始值的物件的每個資料成員的值,初始化將要建立的物件的對應資料成員。
“=delete”
-
如果不希望物件被複制構造
-
C++98做法:將複製建構函式宣告為private,並且不提供函式的實現。
-
C++11做法:用“=delete”指示編譯器不生成預設複製建構函式。
-
-
例:
class Point { //Point 類的定義
public:
Point(int xx=0, int yy=0) { x = xx; y = yy; } //建構函式,內聯
Point(const Point& p) =delete; //指示編譯器不生成預設複製建構函式
private:
int x, y; //私有資料
};
複製建構函式被呼叫的三種情況
- 定義一個物件時,以本類另一個物件作為初始值,發生複製構造;
- 如果函式的形參是類的物件,呼叫函式時,將使用實參物件初始化形參物件,發生複製構造;
- 如果函式的返回值是類的物件,函式執行完成返回主調函式時,將使用return語句中的物件初始化一個臨時無名物件,傳遞給主調函式,此時發生複製構造。
- 這種情況也可以通過移動構造避免不必要的複製(第6章介紹)
#include <iostream>
using namespace std;
class Point { //Point 類的定義
public: //外部介面
Point(int xx = 0, int yy = 0) { //建構函式
x = xx;
y = yy;
}
Point(Point &p); //拷貝建構函式
int getX() {
return x;
}
int getY() {
return y;
}
private: //私有資料
int x, y;
};
//成員函式的實現
Point::Point(Point &p) {
x = p.x;
y = p.y;
cout << "Calling the copy constructor" << endl;
}
//形參為Point類物件的函式
void fun1(Point p) {
cout << p.getX() << endl;
}
//返回值為Point類物件的函式
Point fun2() {
Point a(1, 2);
return a;
}
//主程式
int main() {
Point a(4, 5); //第一個物件A
Point b = a; //情況一,用A初始化B。第一次呼叫拷貝建構函式
cout << b.getX() << endl;
fun1(b); //情況二,物件B作為fun1的實參。第二次呼叫拷貝建構函式:首先需要進行形實結合的引數傳遞
b = fun2(); //情況三,函式的返回值是類物件,函式返回時,呼叫拷貝建構函式,構造一個臨時無名物件;賦值的時候不會呼叫拷貝建構函式,會進入賦值運算函式
cout << b.getX() << endl;
return 0;
}
解構函式
- 完成物件被刪除前的一些清理工作。
- 在物件的生存期結束的時刻系統自動呼叫它,然後再釋放此物件所屬的空間。
- 如果程式中未宣告解構函式,編譯器將自動產生一個預設的解構函式,其函式體為空。
- 建構函式和解構函式舉例
#include
using namespace std;
class Point{
public:
Point(int xx, int yy);
~Point();
private:
int x,y;
};
Point::Point(int xx, int yy){
x=xx;
y=yy;
}
Point ::~Point(){
}
類的組合
組合的概念
-
類中的成員是另一個類的物件。
-
可以在已有抽象的基礎上實現更復雜的抽象。
-
類組合的建構函式設計
原則:不僅要負責對本類中的基本型別成員資料初始化,也要對物件成員初始化。 -
宣告形式:
類名::類名(物件成員所需的形參,本類成員形參)
:物件1(引數),物件2(引數),......
{
//函式體其他語句
}
-
構造組合類物件時的初始化次序
-
首先對建構函式初始化列表中列出的成員(包括基本型別成員和物件成員)進行初始化,初始化次序是成員在類體中定義的次序。
-
成員物件建構函式呼叫順序:按物件成員的宣告順序,先宣告者先構造。
-
初始化列表中未出現的成員物件,呼叫用預設建構函式(即無形參的)初始化
-
處理完初始化列表之後,再執行建構函式的函式體。
-
#include <iostream>
#include <cmath>
using namespace std;
class Point { //Point類定義
public:
Point(int xx = 0, int yy = 0) {
x = xx;
y = yy;
}
Point(Point &p);
int getX() { return x; }
int getY() { return y; }
private:
int x, y;
};
Point::Point(Point &p) { //拷貝建構函式的實現
x = p.x;
y = p.y;
cout << "Calling the copy constructor of Point" << endl;
}
//類的組合
class Line { //Line類的定義
public: //外部介面
Line(Point xp1, Point xp2);
Line(Line &l);
double getLen() { return len; }
private: //私有資料成員
Point p1, p2; //Point類的物件p1,p2
double len;
};
//組合類的建構函式
Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) {
cout << "Calling constructor of Line" << endl;
double x = static_cast<double>(p1.getX() - p2.getX());
double y = static_cast<double>(p1.getY() - p2.getY());
len = sqrt(x * x + y * y);
}
//組合類的拷貝建構函式
Line::Line (Line &l): p1(l.p1), p2(l.p2) {
cout << "Calling the copy constructor of Line" << endl;
len = l.len;
}
//主函式
int main() {
Point myp1(1, 1), myp2(4, 5); //建立Point類的物件
Line line(myp1, myp2); //建立Line類的物件
Line line2(line); //利用拷貝建構函式建立一個新物件
cout << "The length of the line is: ";
cout << line.getLen() << endl;
cout <<