函式 (II)

值傳遞和引用傳遞。


到目前為止,在我們見過的所有函式中,傳遞給函式的引數都是值傳遞。這意味著當呼叫帶有引數的函式時,我們傳遞給函式的是它們值的副本,而不是變數本身。 例如,假設我們呼叫了我們的第一個函式加法 (addition)使用以下程式碼

1
2
int x=5, y=3, z;
z = addition ( x , y );

在這種情況下,我們所做的是呼叫加法函式,傳遞了xy的值,即53分別傳遞了它們的值,而不是變數xy本身。



這樣,當呼叫加法函式時,其區域性變數a和 b53分別變為a和 b的值,但是在加法函式內部對xy的任何修改都不會影響xy的值,因為變數

本身沒有傳遞給函式,而只是在呼叫函式時傳遞了它們值的副本。

// passing parameters by reference
#include <iostream>
using namespace std;

void duplicate (int& a, int& b, int& c)
{
  a*=2;
  b*=2;
  c*=2;
}

int main ()
{
  int x=1, y=3, z=7;
  duplicate (x, y, z);
  cout << "x=" << x << ", y=" << y << ", z=" << z;
  return 0;
}
x=2, y=6, z=14

但是,在某些情況下,您可能需要從函式內部操作外部變數的值。 為此,我們可以使用引用傳遞的引數,如以下示例中的 duplicate 函式首先應該引起您注意的是,在duplicate&的宣告中,每個引數的型別都跟隨一個 & 符號 (

)。 這個 & 符號指定了它們對應的引數要以引用傳遞而不是值傳遞的方式傳遞。


當一個變數以引用傳遞時,我們傳遞的不是其值的副本,而是以某種方式將變數本身傳遞給函式,並且我們對區域性變數所做的任何修改都會影響在呼叫函式時作為引數傳遞的對應變數。a, 和 b換句話說,我們將與函式呼叫中傳遞的引數關聯 (x, yz),並且我們在a內所做的任何更改都會影響x外面的值。 我們對和 b所做的任何更改都會影響y換句話說,我們將z.

也是如此。這就是為什麼我們的程式的輸出,顯示儲存在x, yz呼叫首先應該引起您注意的是,在之後的值,顯示了main中所有三個變數的值都翻倍了。

如果在宣告以下函式時

1
void duplicate (int& a, int& b, int& c)

我們以這種方式宣告它

1
void duplicate (int a, int b, int c)

即,沒有 & 符號 (&),那麼我們不會透過引用傳遞變數,而是傳遞它們值的副本,因此,程式在螢幕上的輸出將是x, yz的值,而沒有被修改。

引用傳遞也是允許函式返回多個值的有效方法。 例如,這是一個函式,它返回傳遞的第一個引數的前一個和後一個數字。

// more than one returning value
#include <iostream>
using namespace std;

void prevnext (int x, int& prev, int& next)
{
  prev = x-1;
  next = x+1;
}

int main ()
{
  int x=100, y, z;
  prevnext (x, y, z);
  cout << "Previous=" << y << ", Next=" << z;
  return 0;
}
Previous=99, Next=101  

引數中的預設值。

在宣告函式時,我們可以為每個最後一個引數指定一個預設值。 如果在呼叫函式時相應的引數留空,將使用此值。 為此,我們只需在函式宣告中使用賦值運算子和引數的值。 如果在呼叫函式時未傳遞該引數的值,則使用預設值,但是如果指定了值,則忽略此預設值,而使用傳遞的值。 例如

// default values in functions
#include <iostream>
using namespace std;

int divide (int a, int b=2)
{
  int r;
  r=a/b;
  return (r);
}

int main ()
{
  cout << divide (12);
  cout << endl;
  cout << divide (20,4);
  return 0;
}
6
5

正如我們在程式體中看到的那樣,對函式divide進行了兩次呼叫。 在第一個呼叫中

1
divide (12)

我們只指定了一個引數,但是函式divide最多允許兩個引數。 因此,函式divide假定第二個引數是2,因為這是我們指定如果未傳遞此引數會發生的情況(請注意函式宣告,它以int b=2結束,而不僅僅是int b)。 因此,此函式呼叫的結果是6 (12/2).

在第二次呼叫中

1
divide (20,4)

有兩個引數,因此和 b (int b=2的預設值被忽略,並且和 b採用作為引數傳遞的值,即4,使返回的結果等於5 (20/4).

過載函式。

在 C++ 中,如果兩個函式的引數型別或數量不同,則它們可以具有相同的名稱。 這意味著,如果多個函式具有不同數量的引數或引數中的不同型別,則可以為它們提供相同的名稱。 例如

// overloaded function
#include <iostream>
using namespace std;

int operate (int a, int b)
{
  return (a*b);
}

float operate (float a, float b)
{
  return (a/b);
}

int main ()
{
  int x=5,y=2;
  float n=5.0,m=2.0;
  cout << operate (x,y);
  cout << "\n";
  cout << operate (n,m);
  cout << "\n";
  return 0;
}
10
2.5

在這種情況下,我們定義了兩個具有相同名稱的函式,operate,但是其中一個接受兩個int型別的引數,而另一個接受float型別的引數。 編譯器透過檢查呼叫函式時作為引數傳遞的型別來知道在每種情況下呼叫哪個函式。 如果使用兩個 int 作為其引數呼叫它,它將呼叫具有兩個int引數的函式,如果在原型中呼叫它,並且如果使用兩個浮點數呼叫它,它將呼叫在原型中具有兩個float引數的函式。

在第一次呼叫operate時,傳遞的兩個引數的型別為int,因此,呼叫具有第一個原型的函式; 此函式返回兩個引數相乘的結果。 而第二次呼叫傳遞了兩個float型別的引數,因此呼叫具有第二個原型的函式。 這一個具有不同的行為:它將一個引數除以另一個引數。 因此,呼叫operate的行為取決於傳遞的引數的型別,因為該函式已被過載

請注意,不能僅透過函式的返回型別來過載函式。 它的引數中至少一個必須具有不同的型別。

行內函數。

要放回的字元的inline說明符向編譯器指示,對於特定函式,最好使用內聯替換而不是通常的函式呼叫機制。 這不會更改函式本身的行為,而是用於向編譯器建議將函式體生成的程式碼插入到呼叫函式的每個點,而不是僅插入一次並對其執行常規呼叫,這通常會增加執行時間上的額外開銷。

其宣告格式為

inline type name ( arguments ... ) { instructions ... }

並且呼叫就像呼叫任何其他函式一樣。 呼叫函式時不必包含inline關鍵字,僅在其宣告中。

大多數編譯器已經最佳化程式碼以在更方便時生成行內函數。 此說明符僅指示編譯器對於此函式首選內聯。

遞迴。

遞迴是函式具有被自身呼叫的屬性。 它對於許多工很有用,例如排序或計算數字的階乘。 例如,要獲得數字的階乘 (n!),數學公式為

n! = n * (n-1) * (n-2) * (n-3) ... * 1

更具體地說,5! (5 的階乘) 將是

5! = 5 * 4 * 3 * 2 * 1 = 120

在 C++ 中用於計算此值的遞迴函式可能是

// factorial calculator
#include <iostream>
using namespace std;

long factorial (long a)
{
  if (a > 1)
   return (a * factorial (a-1));
  else
   return (1);
}

int main ()
{
  long number;
  cout << "Please type a number: ";
  cin >> number;
  cout << number << "! = " << factorial (number);
  return 0;
}
Please type a number: 9
9! = 362880  

請注意在函式factorial中,我們包括了對自身的呼叫,但前提是傳遞的引數大於 1,因為否則該函式將執行一個無限遞迴迴圈,一旦到達0,它將繼續乘以所有負數(可能在執行時引發堆疊溢位錯誤)。

由於我們在其設計中使用的資料型別 (long) 為了更簡單,此函式具有限制。 給出的結果對於遠大於 10! 或 15! 的值將無效,具體取決於您編譯它的系統。

宣告函式。

到目前為止,我們已經在原始碼中首次出現對它們的呼叫之前定義了所有函式。 這些呼叫通常在函式main中,我們始終將其保留在原始碼的末尾。 如果您嘗試重複到目前為止描述的一些函式示例,但在放置函式main之前,放置從中呼叫它的任何其他函式,則很可能會獲得編譯錯誤。 原因是,為了能夠呼叫函式,必須在程式碼的某個較早的點對其進行宣告,就像我們在所有示例中所做的那樣。

但是,還有另一種方法可以避免在函式可以在 main 或其他函式中使用之前編寫函式的全部程式碼。 這可以透過在使用函式之前僅宣告該函式的原型來實現,而不是整個定義。 此宣告比整個定義短,但對於編譯器確定其返回型別和引數型別而言,已經足夠重要。

其形式為

type name ( argument_type1, argument_type2, ...);

它與函式定義相同,除了它不包括函式本身的主體(即,在普通定義中括在大括號中的函式語句{ }),而是我們以強制性分號 (;).

來結束原型宣告。 引數列舉不需要包括識別符號,而只需要型別說明符。 在原型宣告中,為每個引數(如在函式定義中)包含名稱是可選的。 例如,我們可以宣告一個名為protofunction的函式,該函式帶有兩個int引數,可以使用以下任何宣告

1
2
int protofunction (int first, int second);
int protofunction (int, int);

無論如何,為每個變數包括名稱會使原型更具可讀性。

// declaring functions prototypes
#include <iostream>
using namespace std;

void odd (int a);
void even (int a);

int main ()
{
  int i;
  do {
    cout << "Type a number (0 to exit): ";
    cin >> i;
    odd (i);
  } while (i!=0);
  return 0;
}

void odd (int a)
{
  if ((a%2)!=0) cout << "Number is odd.\n";
  else even (a);
}

void even (int a)
{
  if ((a%2)==0) cout << "Number is even.\n";
  else odd (a);
}
Type a number (0 to exit): 9
Number is odd.
Type a number (0 to exit): 6
Number is even.
Type a number (0 to exit): 1030
Number is even.
Type a number (0 to exit): 0
Number is even.

此示例實際上不是效率的示例。 我確信在這一點上,您已經可以製作一個具有相同結果的程式,但僅使用此示例中使用的一半程式碼行。 無論如何,此示例說明了原型的工作方式。 此外,在此具體示例中,至少需要對兩個函式之一進行原型設計才能編譯程式碼而不會出錯。

我們首先看到的是函式odd:

1
2
void odd (int a);
void even (int a);

evenmain的宣告。 這允許在使用這些函式之前定義它們,例如,在

中,現在該函式位於某些人認為對於程式的開始而言更合乎邏輯的位置:原始碼的開頭。odd無論如何,此程式需要至少一個函式在其定義之前宣告的原因是因為在中,存在對無論如何,此程式需要至少一個函式在其定義之前宣告的原因是因為在odd的呼叫。 如果兩個函式都沒有事先宣告,則會發生編譯錯誤,因為odd將無法從看到(因為它尚未宣告),或者將無法從odd(出於同樣的原因)。

將所有函式的原型放在原始碼中的同一位置被一些程式設計師認為是實用的,並且可以透過在程式開頭宣告所有函式原型來輕鬆實現此目的。
Index
目錄