【C++ 筆記】函數多載 / 運算子多載 - part 24

很感謝你點進來這篇文章。

你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧!

本篇將詳細說明上篇所提及的兩個多型觀念:函數多載與運算子多載,探討其可多載與不可多載情形,並附上詳細範例。

函數多載(Function Overloading)

在 C++ 中,函數多載就是可以讓你有同名函數存在,但是他們的參數數量可以不一樣。而它是編譯期多型(靜態多型,只有編譯完成時才會進行)的一種形式,函數會根據傳遞給它的不同參數執行不同的工作。此為物件導向程式設計的特性,可提高程式的可讀性。

而函數多載其實不僅限於使用類別(class)內的成員函數,一般的函數也可使用,在 GeeksForGeeks 上用 add 函數舉了一個例子。

1
2
3
int add2(int a, int b) {
return a + b;
}
1
2
3
int add3(int a, int b, int c) {
return a + b + c;
}

若要命名諸如此類的函數多達數十個,可能會寫到手軟而且不易記憶,所以可透過函數多載的方式統一為同名,而其不同參數數量及其功能仍可保留。

最終用上函數多載會變成下面那樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;

int add(int a, int b) {
return a + b;
}

// 一樣的函數有著不同的參數量
// Same function with different arguments
int add(int a, int b, int c) {
return a + b + c;
}

int main() {
int a = 5, b =7, c = 11;

// add 函數用來相加兩個數字
// Add function to add two numbers
cout << add(a, b) << endl;

// add 函數用來相加三個數字
// Add function to add three numbers
cout << add(a, b, c);

return 0;
}

輸出結果:

1
2
12
23

從上面這個例子可以看出,同名函數會根據不同參數量來執行相關的功能。

可函數多載的情形

以下三種情形就可以用函數多載:

  • 不同數量的參數
  • 不同資料型態的參數
  • 不同數量且不同資料型態的參數

不同數量的參數


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

void printInfo(string name);

void printInfo(string name, int age);

int main() {
printInfo("LukeTseng");
printInfo("王奕翔", 87);
return 0;
}

void printInfo(string name) {
cout << "名字: " << name << endl;
}

void printInfo(string name, int age) {
cout << "名字: " << name << ", 年齡: " << age << endl;
}

輸出結果:

1
2
名字: LukeTseng
名字: 王奕翔, 年齡: 87

不同資料型態的參數


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

void displayValue(int value);

void displayValue(double value);

int main() {
displayValue(42);
displayValue(3.14);
return 0;
}

void displayValue(int value) {
cout << "整數值為: " << value << endl;
}

void displayValue(double value) {
cout << "浮點數值為: " << value << endl;
}

輸出結果:

1
2
整數值為: 42
浮點數值為: 3.14

不同數量且不同資料型態的參數


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

void describe(char grade);

void describe(char grade, string comment);

int main() {
describe('A');
describe('B', "表現良好");
return 0;
}

void describe(char grade) {
cout << "等級: " << grade << endl;
}

void describe(char grade, string comment) {
cout << "等級: " << grade << ", 評語: " << comment << endl;
}

輸出結果:

1
2
等級: A
等級: B, 評語: 表現良好

不能做函數多載的情形

以下是不能做函數多載的三種情形:

  1. 存取權限不同但名稱和參數相同:如果兩個成員函數名稱和參數列表相同,但存取權限(例如 public 或 private)不同,不能多載。
  2. 僅指標 * 與陣列 [] 不同:如果兩個函數的參數只有指標(*)和陣列([])的差別,不能多載。
  3. 傳遞方式不同:如果兩個函數的參數相同,但一個用值傳遞(by value),另一個用參考傳遞(by reference),不能多載。註:這裡只是舉這兩個參數傳遞方式當例子,並不是絕對的。

存取權限不同


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

class MyClass {
public:
void show(); // public

private:
void show(); // error : 重複宣告函數,僅存取權限不同
};

void MyClass::show() {
cout << "Public show()" << endl;
}

int main() {
MyClass obj;
obj.show();
return 0;
}

輸出結果:

1
2
3
4
5
6
7
ERROR!
/tmp/Yf6PVaUCX2/main.cpp:9:10: error: 'void MyClass::show()' cannot be overloaded with 'void MyClass::show()'
9 | void show(); // error : 重複宣告函數,僅存取權限不同
| ^~~~
/tmp/Yf6PVaUCX2/main.cpp:6:10: note: previous declaration 'void MyClass::show()'
6 | void show(); // public
| ^~~~

參數中的指標與陣列之不同


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

void process(int* data) {
cout << "Pointer version" << endl;
}

// error : 陣列和指標在參數上視為相同
void process(int data[]) {
cout << "Array version" << endl;
}

int main() {
int arr[5] = {1, 2, 3, 4, 5};
process(arr);
return 0;
}

輸出結果:

1
2
3
4
5
6
7
ERROR!
/tmp/N7VHK3WpoH/main.cpp:9:6: error: redefinition of 'void process(int*)'
9 | void process(int data[]) {
| ^~~~~~~
/tmp/N7VHK3WpoH/main.cpp:4:6: note: 'void process(int*)' previously defined here
4 | void process(int* data) {
| ^~~~~~~

參數傳遞方式不同


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

void update(int x) {
cout << "Pass by value: " << x << endl;
}

// error : 參數型態相同,僅傳遞方式不同(值傳與參考傳遞)
void update(int& x) {
cout << "Pass by reference: " << x << endl;
}

int main() {
int a = 10;
update(a);
return 0;
}

輸出結果:

1
2
3
4
5
6
7
8
9
10
11
ERROR!
/tmp/0UdbmXAtr4/main.cpp: In function 'int main()':
/tmp/0UdbmXAtr4/main.cpp:15:11: error: call of overloaded 'update(int&)' is ambiguous
15 | update(a);
| ~~~~~~^~~
/tmp/0UdbmXAtr4/main.cpp:4:6: note: candidate: 'void update(int)'
4 | void update(int x) {
| ^~~~~~
/tmp/0UdbmXAtr4/main.cpp:9:6: note: candidate: 'void update(int&)'
9 | void update(int& x) {
| ^~~~~~

運算子多載(Operator Overloading)

運算子多載可以讓程式設計師重新定義或是自訂內建運算子行為的特性。透過運算子多載,可讓自定義的類別(class)或結構(struct)使用這些運算子,使其行為與內建型態(如 int、double)類似,從而提高程式碼的可讀性和直觀性。

如 C++ STL 中的 string 就用 + 多載運算子,使兩個字串 str1 + str2 拼接,較為直觀且不用直接呼叫函數。

而運算子多載也同屬於編譯期多型(靜態多型)。

至於運算子多載的格式通常會如下所示:

1
類別名稱 operator運算子(參數);
1
2
3
4
5
6
7
8
// 範例
class MyClass {
public:
// 以下是運算子多載的函數
MyClass operator+(const MyClass& other){
// 實現加法邏輯
}
};

運算子多載範例 1:多載 +


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
using namespace std;

class Vector2D {
public:
float x, y;

Vector2D(float x = 0, float y = 0) : x(x), y(y) {}

// 多載 +
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}

void print() const {
cout << "(" << x << ", " << y << ")" << endl;
}
};

int main() {
Vector2D a(1.0, 2.0), b(3.5, 4.5);
Vector2D c = a + b;
c.print(); // (4.5, 6.5)
// 因為是使用類別內部的成員函數印出座標
// 而非在主函數用 cout << 印出
// 所以不用多載 << 運算子
}

運算子多載範例 2:多載 ==


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

class Point {
public:
int x, y;

Point(int x = 0, int y = 0) : x(x), y(y) {}

// 多載 ==
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};

int main() {
Point p1(3, 4), p2(3, 4), p3(1, 2);
cout << (p1 == p2) << endl; // 1
cout << (p1 == p3) << endl; // 0
}

由於輸出的並非帶兩個括號的點座標,而是一個 int 值,因此也不用多載 << 運算子。

運算子多載範例 3:多載 <<


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

class Person {
public:
string name;
int age;

Person(string name, int age) : name(name), age(age) {}

// 多載 << (需用 friend)
friend ostream& operator<<(ostream& os, const Person& p) {
os << "Name: " << p.name << ", Age: " << p.age;
return os;
}
};

int main() {
Person p("LukeTseng", 18);
cout << p << endl; // Name: LukeTseng, Age: 18
}

多載 << 輸出運算子可以自訂輸出內容。

反之 >> 輸入運算子也可以。

運算子多載範例 4:多載 ++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
using namespace std;

class Counter {
private:
int value;

public:
Counter(int v = 0) : value(v) {}

// 前置++
Counter& operator++() {
++value;
return *this;
}

// 後置++ (int 代表後置版本)
Counter operator++(int) {
Counter temp = *this;
++value;
return temp;
}

void print() const {
cout << "Value: " << value << endl;
}
};

int main() {
Counter c(5);
++c; // 前置遞增
c.print(); // Value: 6
c++; // 後置遞增
c.print(); // Value: 7
}

前置就是本身變數如 c 先 +1,之後再設定給其他變數。

後置就是先設定給其他變數,之後再給本身變數 +1。

程式碼中的第 18 行加入 int 只是為了告訴編譯器這是一個後置的遞增(減)運算子,並非表示整數型態的意義。

:::info
前置用 operator++()
後置用 operator++(int)
:::

而前置的多載上,使用到 this pointer 進行回傳,原因是為了能夠支援鏈式呼叫,可做到如 ++(++obj) 的運算,這在之前章節有說過。

另外在這個前置多載運算子的函數中,執行順序也符合內建運算子的執行順序,就是先加後設定變數。

在後置的多載運算子的函數中,先用了 Counter temp = *this; 複製自身副本,之後再自增,最後回傳自增前的副本,就符合後置的執行順序,也就是先設定變數後加。

運算子多載範例 5:多載 []


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
using namespace std;

class IntArray {
private:
int data[10];

public:
IntArray() {
for (int i = 0; i < 10; ++i)
data[i] = i * 10;
}

// 非 const 版本
// 可被賦值
int& operator[](int index) {
return data[index];
}

// const 版本
// 唯讀存取
const int& operator[](int index) const {
return data[index];
}
};

int main() {
IntArray arr;
cout << arr[3] << endl; // 30
arr[3] = 99;
cout << arr[3] << endl; // 99
}

運算子多載範例 6:多載 ()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
using namespace std;

class Multiplier {
private:
int factor;

public:
Multiplier(int f) : factor(f) {}

// 運算子多載 () 函數呼叫運算子
int operator()(int value) const {
return factor * value;
}
};

int main() {
Multiplier triple(3); // 用途 : 將數字乘3
Multiplier quadruple(4); // 用途 : 將數字乘4

cout << "triple(5) = " << triple(5) << endl; // 15
cout << "quadruple(5) = " << quadruple(5) << endl; // 20

return 0;
}

總結

函數多載(Function Overloading)

定義

  • C++ 支援同名函數存在,但參數數量或型態不同。
  • 屬於編譯期多型(靜態多型),根據傳入參數決定執行哪個函數。

特性

  • 提高程式可讀性,適用於一般函數和類別成員函數。
  • 範例:add(int a, int b)add(int a, int b, int c) 根據參數數量執行不同加法函數。

可多載的情形

  1. 不同參數數量
    • 範例:printInfo(string name)printInfo(string name, int age)
  2. 不同參數型態
    • 範例:displayValue(int value)displayValue(double value)
  3. 不同數量且不同型態
    • 範例:describe(char grade)describe(char grade, string comment)

不可多載的情形

  1. 僅存取權限不同
    • 如 public 和 private 同名函數,會導致編譯錯誤。
  2. 僅指標與陣列不同
    • process(int* data)process(int data[]),視為相同參數。
  3. 僅傳遞方式不同
    • update(int x)update(int& x),會導致呼叫歧義。

運算子多載(Operator Overloading)

定義

  • 重新定義內建運算子行為,使自定義類別或結構使用運算子時使其行為類似於內建型態(如 int、double)。
  • 屬於編譯期多型

特性

  • 提高程式碼直觀性和可讀性。
  • 應用:C++ STL 的 string 使用 + 進行字串拼接。
  • 格式:類別名稱 operator運算子(參數);

參考資料

Operator Overloading in C++ | GeeksforGeeks

Function Overloading in C++ | GeeksforGeeks

C++ 重载运算符和重载函数 | 菜鸟教程