程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 從引用傳遞到設計模式 (上),傳遞設計模式

從引用傳遞到設計模式 (上),傳遞設計模式

編輯:C++入門知識

從引用傳遞到設計模式 (上),傳遞設計模式


1  值傳遞

  值傳遞是拷貝實參的值傳給形參,常用於“小對象” (small objects),如下面計算階乘的 fact 函數

int fact(int val)   // factorial of val 
{
    int ret = 1; 
   
    while (val > 1)  // assign ret * val to ret and decrement val
        ret *= val--; 
    
    return ret;
}

  調用該函數時,參數傳遞便是值傳遞:

cout << "5! is " << fact(5) << endl;

  <Effective C++> 中提及,值傳遞適用的“小對象”為:內置類型(built-in types),STL迭代器,函數對象類型(function object types)

  而 <C++ Programming Lauguage> 中有一個小例子, 只包含一對 int 型數據成員(x 和 y) 的 Point 類,也可視為小對象

void Point::operator+=(Point delta); // pass-by-value

 

2  引用傳遞

  引用傳遞不涉及拷貝,傳遞給形參的是實參變量的引用,其有兩個優點:更高效和防切斷,常用來傳遞“大數值” (large values) 

2.1  更高效

  下面是 Person 基類,及其派生類 Student 的定義

class Person{
private: std::string name; std::string address; }; class Student: public Person{
private: std::string schoolName; std::string schoolAddress; };

  現有一個驗證學生身份的函數,形參為值傳遞,則拷貝實參給形參的代價是:調用基類 Person 的構造函數一次,基類內 string 型數據成員的構造函數兩次,

派生類 Student 的構造函數一次,派生類內 string 型數據成員兩次,最後還會調用相應的析構函數六次,共計十二次調用,自然效率低下

  而使用引用傳遞,並不涉及拷貝操作,故而顯著的提高了效率。

bool validateStudent(Student s);    // pass-by-value 

bool validateStudent(const Student& s);  // pass-by-reference-to-const 

2.2  防切斷

  下面的例子中,派生類 WindowWithScrollBars 中,重寫了基類 Window 的虛函數 display

class Window {
public: 
    std::string name() const;        // return name of window
    virtual void display() const;    // draw window and contents
};

class WindowWithScrollBars : public Window {
public:
    virtual void display() const;
};

  在 printNameAndDisplay 函數中,調用了 dispaly 函數,而形參若采用值傳遞方式,則會發生“切斷” (slicing),即 wwsb 調用的是 Window::display()

  因為在 printNameAndDisplay 中,並不修改傳遞進來的參數,假如采用 pass-by-const-reference 的形式,則會避免“切斷”的發生

 

void printNameAndDisplay(Window w)
{
    std::cout << w.name();
    w.display();
}

// WindowWithScrollBars object will be sliced off
WindowWithScrollBars  wwsb;
printNameAndDisplay(wwsb);

 

 

3  動態綁定

  上面"切斷"的例子,實際上涉及的是 C++ 的動態綁定機制 (dynamic binding), 而動態綁定的一個關鍵就是引用傳遞,看下面例子:

class Quote {
public:
    std::string isbn() const;
    virtual double net_price(std::size_t n) const;
};

class Bulk_quote : public Quote { 
public:
    double net_price(std::size_t) const override;
};

  在 cal_total 函數中,需要調用 net_price,采用 pass-by-const-reference 形式

double cal_total(const Quote &item, size_t n)  // calculate the price
{
    double ret = item.net_price(n);
    return ret;
}

  調用 Quote::net_price 還是 Bulk_quote::net_price, 取決於傳遞進來的參數

// basic is type Quote; bulk is type Bulk_quote
cal_total(basic, 20); //  Quote::net_price
cal_total(bulk, 20);  //  Bulk_quote::net_price

   C++ 的動態綁定也叫“遲邦定”,它使程序直到運行時,才基於引用或指針綁定的對象類型,來選擇調用哪個虛函數

 

 

4  設計模式 

 

   前面說到,動態綁定的一個關鍵是引用傳遞。它還有另一個關鍵 — 虛函數,例 2.2 中的 display 為虛函數,例 3 中的 net_price 同樣也是虛函數。

 

4.1  模板方法

 

  有一種編程慣例叫做 NVI (non-virtural interface) — 非虛擬接口: 將所有的公有函數 (public) 聲明為非虛擬的,也即虛函數聲明為私有或保護 (private or protected)

 

  該 NVI 慣例的實現,是通過一種設計模式 —— 模板方法 (template method) 來完成的

 

 1)  AbstractClass: TemplateMethod 為非虛成員函數 (public),函數體內調用 PrimitiveOperation1 和 PrimitiveOperation2 兩個虛函數(protected)

 2)  ConcreteClass: 繼承自 AbstractClass, 重寫了兩個虛函數 PrimitiveOperation1 和 PrimitiveOperation2

4.2  代碼實現

  按該模式則例 3 中 Quote 裡,可將 cal_total 聲明為公有非虛成員函數,net_price 則聲明為保護型,Bulk_quote 公有繼承自 Quote,且重寫虛函數 net_price

  但在實際中,只有當 cal_total 內至少包含兩個類似 net_price 的操作函數 (比如先調用 net_price 再 print_price),才有使用設計模式的必要

  下面是模板方法模式的簡單示例:

class AbstractClass {
public:
    void TemplateMethod();
protected:
    virtual void PrimitiveOperation1() = 0;
    virtual void PrimitiveOperation2() = 0;
};

class ConcreteClass : public AbstractClass {
protected:
    void PrimitiveOperation1() override;
    void PrimitiveOperation2() override;
};

void AbstractClass::TemplateMethod()
{
    PrimitiveOperation1();
    PrimitiveOperation2();
}

   由上面的例子可以看到,模板方法是關於基類如何調用派生類內操作函數的,是一種反向控制結構,常用於代碼復用。

  這種反向結構也體現了一個設計原則,即好萊塢原則 — “不要給我們打電話,我們會打給你”

 

小結:

1) use pass-by-value for small objects;use pass-by-const-reference to pass large values that you don’t need to modify

2) dynamic binding happens when a virtual function is called through a reference (or a pointer) to a base class

3) Tempalte method pattern - define the skeleton of an algorithm in an operation, deferring some steps to subclasses.

 

參考資料:

 <C++ Programming Language_4th> ch 12.2.1

 <Effective C++_3rd> item 20

 <C++ Primer_5th> ch 6.2,  ch 15.1

 <Design Patterns> template method

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved