1. 程式人生 > >第4章 類與物件

第4章 類與物件

類與物件

面向物件程式的基本特點

抽象

  • 對同一類物件的共同屬性和行為進行概括,形成類。

  • 先注意問題的本質及描述,其次是實現過程或細節。

  • 資料抽象:描述某類物件的屬性或狀態(物件相互區別的物理量)。

  • 程式碼抽象:描述某類物件的共有的行為特徵或具有的功能。

  • 抽象的實現:類。

  • 抽象例項——鐘錶

  • 資料抽象:

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