C-學習筆記整理-函數

第四章 類和對象

1.面向對象程序設計的基本特點

①抽象

抽象是對具體對象(問題)進行概括,抽出這一類對象的公共性質並加以描述的過程。

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

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

  • 代碼抽象:描述某類對象的共有的行爲特徵或具有的功能。

  • 抽象的實現:通過類的聲明。

//數據抽象:
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;
};//邊界

③繼承

是C++中支持層次分類的一種機制,允許程序員在保持原有類特性的基礎上,進行更具體的說明。

  • 實現:聲明派生類

④多態

多態:同一名稱,不同的功能實現方式。

  • 目的:達到行爲標識統一,減少程序中標識符的個數。

  • 實現:重載函數和虛函數


2.類和對象

①類和對象

類是具有相同屬性和行爲的一組對象的集合,它爲屬於該類的全部對象提供了統一的抽象描述,其內部包括屬性和行爲兩個主要部分。

  • 利用類可以實現數據的封裝、隱藏、繼承與派生。

  • 利用類易於編寫大型複雜程序,其模塊化程度比C中採用函數更高。

  • 定義一個新的class也就定義了一個新的類型

②類的定義

類是一種用戶自定義類型,聲明形式:

class 類名稱
{
   public:
             公有成員(外部接口)
   private:
             私有成員
   protected:
             保護型成員
};
  • 在關鍵字public後面聲明,它們是類與外部的接口,任何外部函數都可以訪問公有類型數據和函數。

  • 在關鍵字private後面聲明,只允許本類中的函數訪問,而類外部的任何函數都不能訪問。
    如果緊跟在類名稱的後面聲明私有成員,則關鍵字private可以省略。

  • protected保護型成員與private類似,其差別表現在繼承與派生時對派生類的影響不同


③對象

類的對象是該類的某一特定實體,即類類型的變量。
聲明形式:

類名  對象名;
例:Clock  myClock;
  • 類中成員互訪:直接使用成員名

  • 類外訪問:使用“對象名.成員名”方式訪問 public 屬性的成員

④類的成員函數

  • 在類中說明原型,可以在類外給出函數體實現,並在函數名前使用類名加以限定。也可以直接在類中給出函數體,形成內聯成員函數。

  • 允許聲明重載函數和帶默認形參值的函數

⑤內聯成員函數

  • 爲了提高運行時的效率,對於較簡單的函數可以聲明爲內聯形式。

  • 內聯函數體中不要有複雜結構(如循環語句和switch語句)。

    在類中聲明內聯成員函數的方式:

    • 將函數體放在類的聲明中。
    • 使用inline關鍵字。
//example.1類的實現
#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;
};
int main()
{
	Clock myClock;
	myClock.setTime(8, 30, 30);
	myClock.showTime();
	return 0;
}

void Clock::setTime(int newH, int newM,int newS)
{
   hour=newH;
   minute=newM;
   second=newS;
}
void Clock::showTime() 
{
   cout << hour << ":" << minute << ":" << second;
}


//運行結果:
8:30:30

//Point類的完整程序

class Point {   //Point 類的定義
public:
	Point(int xx=0, int yy=0) { x = xx; y = yy; }    //構造函數,內聯
	Point(const Point& p); //複製構造函數
    void setX(int xx) {x=xx;}
    void setY(int yy) {y=yy;}
	int getX() const { return x; } //常函數(第5章)
	int getY() const { return y; } //常函數(第5章)
private:
	int x, y; //私有數據
};
//成員函數的實現
Point::Point (const 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;
}

3.構造函數和析構函數

①構造函數

  • 類中的特殊函數

  • 用於描述初始化算法

  • 構造函數的作用是在對象被創建時使用特定的值構造對象,將對象初始化爲一個特定的初始狀態。

  • 在對象創建時被自動調用

  • 如果程序中未聲明,則系統自動產生出一個默認的構造函數,其參數列表爲空

  • 構造函數可以是內聯函數、重載函數、帶默認參數值的函數

//構造函數舉例
class Clock {
public:
	Clock(int newH,int newM,int newS);//構造函數
	void setTime(int newH, int newM, int newS);
	void showTime();
private:
	int hour, minute, second;
};

//構造函數的實現:
Clock::Clock(int newH, int newM, int newS): hour(newH), minute(newM), second(newS) {
	}
建立對象時構造函數的作用:
int main() {
  Clock c(0,0,0); //此處將自動調用構造函數
  c.showTime();
	return 0;
}


②複製構造函數

複製構造函數是一種特殊的構造函數,其形參爲本類的對象引用。作用是用一個已存在的對象去初始化同類型的新對象。

class 類名 {
public :
    類名(形參);//構造函數
    類名(const  類名 &對象名);//複製構造函數
           ...
};
類名::類( const  類名 &對象名)//複製構造函數的實現
{    函數體    }

複製構造函數被調用的三種情況

  • 定義一個對象時,以本類另一個對象作爲初始值,發生複製構造;

  • 如果函數的形參是類的對象,調用函數時,將使用實參對象初始化形參對象,發生複製構造;

  • 如果函數的返回值是類的對象,函數執行完成返回主調函數時,將使用return語句中的對象初始化一個臨時無名對象,傳遞給主調函數,此時發生複製構造。

有時不應該進行復制和賦值

  • 例如,房屋中介系統有一個類描述待售房屋
class HomeForSale{…}
//通常沒有完全一樣的房屋,因此不應有複製構造
//解決方法一:將不應該有的默認函數定義爲私有

class HomeForSale{
public:
    …
private:
    …
    HomeForSale(const HomeForSale&);
    HomeForSale& operator=(const HomeForSale&)
        
//解決方法二:定義一個Uncopyable類作爲基類

class Uncopyable{
protected:
    Uncopyable(){}
    ~ Uncopyable(){}
private:
     Uncopyable(const Uncopyable&);
     Uncopyable operator=(const Uncopyable&);
};

③默認構造函數

調用時可以不需要參數的構造函數都是默認構造函數。

  • 當不定義構造函數時,編譯器自動產生默認構造函數

  • 在類中可以自定義無參數的構造函數,也是默認構造函數

  • 全部參數都有默認形參值的構造函數也是默認構造函數

下面兩個都是默認構造函數,如果在類中同時出現,將產生編譯錯誤:

Clock();
Clock(int newH=0,int newM=0,int newS=0);
//example.1
class Clock {
public:
	Clock(int newH,int newM,int newS);//構造函數
	Clock();//默認構造函數
    void setTime(int newH, int newM, int newS);
	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) { }

//建立對象時構造函數的作用:
int main() {
  Clock c(8,10,0); //調用有參構造函數
  Clock c2();//調用無參構造函數
  c.showTime();
  c2.showTime();
	return 0;
}

void Clock::setTime(int newH, int newM,int newS)
{
   hour = newH;
   minute = newM;
   second = newS;
}
void Clock::showTime() 
{
   cout << hour << ":" << minute << ":" << second;
}

有時不應該有默認的構造函數

  • 有些類,不應該有默認初始化。
    例如,沒有姓名的學生對象是沒有意義的
    解決:
    至少定義一個有參數的構造函數
    不定義默認構造函數

  • 需要深層複製時,默認的複製構造會引起錯誤。
    解決:
    自定義實現深層複製的複製構造函數

④隱含的複製構造函數

如果程序員沒有爲類聲明拷貝初始化構造函數,則編譯器自己生成一個隱含的複製構造函數。

這個構造函數執行的功能是:用作爲初始值的對象的每個數據成員的值,初始化將要建立的對象的對應數據成員。

⑤函數參數儘量傳遞常引用而不是值

  • 傳遞對象值會引起復制構造和析構,增加時間空間開銷。
  • 傳常引用可避免這一問題。以引用做參數時,儘量使用常引用。
//例如:
void fun1(const Point &p) {
	cout << p.getX() << endl;
   p.setX(1); //語法錯誤:p是常引用而setX不是常函數
}

基本類型、STL的迭代器和函數對象傳值較好。

⑥返回值優化

  • 當函數返回一個對象時,會構造臨時對象用以返回,這會增加開銷。

  • 用返回引用或者指針替代不是好辦法。返回指向局部對象的指針或者引用,會引發錯誤。返回指向動態內存的指針或引用容易忘記動態空間釋放,引起內存泄露。

  • 解決顯式構造臨時對象返回。此舉表面上還是構造了一個臨時對象,但是編譯器通常都會進行優化,使之不產生臨時對象。

//例如:
Point fun2() {
	return Point(1, 2);
}

⑦析構函數

  • 完成對象被刪除前的一些清理工作。

  • 在對象的生存期結束的時刻系統自動調用它,然後再釋放此對象所屬的空間。

  • 如果程序中未聲明析構函數,編譯器將自動產生一個隱含的析構函數。

//構造函數和析構函數舉例

#include <iostream>
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() {
}
//...其他函數的實現略

3.類的組合

①組合

  • 類中的成員數據是另一個類的對象。

  • 可以在已有抽象的基礎上實現更復雜的抽象。

類組合的構造函數設計

  • 原則:不僅要負責對本類中的基本類型成員數據賦初值,也要對對象成員初始化。
//聲明形式:
類名::類名(對象成員所需的形參,本類成員形參)
       :對象1(參數),對象2(參數),......
{  
//函數體其他語句
}

構造組合類對象時的初始化次序

  • 首先對構造函數初始化列表中列出的成員(包括基本類型成員和對象成員)進行初始化,初始化次序是成員在類體中定義的次序。

  • 成員對象構造函數調用順序:按對象成員的聲明順序,先聲明者先構造。

  • 初始化列表中未出現的成員對象,調用用默認構造函數(即無形參的)初始化

  • 處理完初始化列表之後,再執行構造函數的函數體

//類的組合,線段(Line)類


#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;
}

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 << "The length of the line2 is: ";
	cout << line2.getLen() << endl;
	return 0;
}


//運行結果如下:
Calling the copy constructor of Point
Calling the copy constructor of Point
Calling the copy constructor of Point
Calling the copy constructor of Point
Calling constructor of Line
Calling the copy constructor of Point
Calling the copy constructor of Point

前向引用聲明

  • 類應該先聲明,後使用

  • 如果需要在某個類的聲明之前,引用該類,則應進行前向引用聲明。

  • 前向引用聲明只爲程序引入一個標識符,但具體聲明在其他地方。

//example

class B;  //前向引用聲明
class A {
public:
  void f(B b);
};
class B {
public:
  void g(A a);
};

前向引用聲明注意事項

使用前向引用聲明雖然可以解決一些問題,但它並不是萬能的。需要注意的是,儘管使用了前向引用聲明,但是在提供一個完整的類聲明之前,不能聲明該類的對象,也不能在內聯成員函數中使用該類的對象

class Fred; //前向引用聲明
class Barney {
   Fred x; //錯誤:類Fred的聲明尚不完善
};
class Fred {
   Barney y;
};

//更正
class Fred;	//前向引用聲明
class Barney {
public:
  ……
  void method() {
    x.yabbaDabbaDo();	//錯誤:Fred類的對象在定義之前被使用
  }
 private:
  Fred &x;//正確,經過前向引用聲明,可以聲明Fred類的對象引用
};
 
class Fred {
public:
  void yabbaDabbaDo();
private:
  Barney &y;
}; 

前向引用聲明注意事項

  • 應該記住:當使用前向引用聲明時,只能使用被聲明的符號,而不能涉及類的任何細節。

4.UML圖形標識

①UML簡介

UML(Unified Modeling Language)語言是一種可視化的的面向對象建模語言。

UML有三個基本的部分

  • 事物(Things)UML中重要的組成部分,在模型中屬於最靜態的部分,代表概念上的或物理上的元素

  • 關係(Relationships)關係把事物緊密聯繫在一起

  • 圖(Diagrams)圖是很多有相互相關的事物的組

②UML類圖

舉例:Clock類的完整表示:

Clock
- hour: int
- minute: int
- second: int
+ showTime(): void
+ setTime(newH:int=0,newM:int=0,newS:int=0)

Clock類的簡潔表示

Clock

③對象圖

myClock:Clock
- hour: int
- minute: int
- second: int

④幾種關係的圖形標識

依賴關係

圖中的“類A”是源,“類B”是目標,表示“類A”使用了“類B”,或稱“類A”依賴“類B”

作用關係—關聯

圖中的“重數A”決定了類B的每個對象與類A的多少個對象發生作用,同樣“重數B”決定了類A的每個對象與類B的多少個對象發生作用。

包含關係—聚集和組合

聚集表示類之間的關係是整體與部分的關係,“包含”、“組成”、“分爲……部分”等都是聚集關係。共享聚集:部分可以參加多個整體;組成聚集:整體擁有各個部分,整體與部分共存,如果整體不存在了,那麼部分也就不存在了。

採用UML方法來描述例4-4中Line類和Point類的關係

繼承關係—泛化

帶有註釋的Line類和Point類關係的描述

註釋

在UML圖形上,註釋表示爲帶有褶角的矩形,然後用虛線連接到UML的其他元素上,它是一種用於在圖中附加文字註釋的機制。

5.結構體和聯合體

①結構體

結構體是一種特殊形態的類

  • 與類的唯一區別:類的缺省訪問權限是private,結構體的缺省訪問權限是public

  • 結構體存在的主要原因:與C語言保持兼容

什麼時候用結構體而不用類

  • 定義主要用來保存數據、而沒有什麼操作的類型

  • 人們習慣將結構體的數據成員設爲公有,因此這時用結構體更方便

結構體的定義和初始化

//結構體定義
struct 結構體名稱 {
	 公有成員
protected:
    保護型成員
private:
     私有成員
};


//一些結構體變量的初始化可以用以下形式
類型名 變量名 = { 成員數據1初值, 成員數據2初值, …… };

//1.用結構體表示學生的基本信息

#include <iostream>
#include <iomanip>
#include <string>
using namespace std;

struct Student {	//學生信息結構體
	int num;		//學號
	string name;	//姓名,字符串對象,將在第6章詳細介紹
	char sex;		//性別
	int age;		//年齡
};

int main() {
	Student stu = { 97001, "Lin Lin", 'F', 19 };
	cout << "Num:  " << stu.num << endl;
	cout << "Name: " << stu.name << endl;
	cout << "Sex:  " << stu.sex << endl;
	cout << "Age:  " << stu.age << endl;
	return 0;
}

//運行結果:
Num:  97001
Name: Lin Lin
Sex:  F
Age:  19

②聯合體

//聲明形式
union 聯合體名稱 {
    公有成員
protected:
    保護型成員
private:
    私有成員
};

特點:

  • 成員共用相同的內存單元

  • 任何兩個成員不會同時有效

聯合體的內存分配

union Mark {	//表示成績的聯合體
	char grade;	//等級制的成績
	bool pass;	//只記是否通過課程的成績
	int percent;	//百分制的成績
};

無名聯合

無名聯合沒有標記名,只是聲明一個成員項的集合,這些成員項具有相同的內存地址,可以由成員項的名字直接訪問。

例:
union {
  int i;
  float f;
}
//在程序中可以這樣使用:
i = 10;
f = 2.2;
//example.使用聯合體保存成績信息,並且輸出。

#include <string>
#include <iostream>
using namespace std;
class ExamInfo {
private:
	string name;	//課程名稱
	enum { GRADE, PASS, PERCENTAGE } mode;//採用何種計分方式
	union {
		char grade;	//等級制的成績
		bool pass;	//只記是否通過課程的成績
		int percent;	//百分制的成績
	};

public:
	//三種構造函數,分別用等級、是否通過和百分初始化
	ExamInfo(string name, char grade)
		: name(name), mode(GRADE), grade(grade) { }
	ExamInfo(string name, bool pass)
		: name(name), mode(PASS), pass(pass) { }
	ExamInfo(string name, int percent)
		: name(name), mode(PERCENTAGE), percent(percent) { }
	void show();
}

void ExamInfo::show() {
	cout << name << ": ";
	switch (mode) {
	  case GRADE: cout << grade;  break;
	  case PASS: cout << (pass ? "PASS" : "FAIL"); break;
	  case PERCENTAGE: cout << percent; break;
	}
	cout << endl;
}

int main() {
	ExamInfo course1("English", 'B');
	ExamInfo course2("Calculus", true);
	ExamInfo course3("C++ Programming", 85);
	course1.show();
	course2.show();
	course3.show();
	return 0;
}

//運行結果:
English: B
Calculus: PASS
C++ Programming: 85

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章