跳到正文

Wiki

函式提高

約 5 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

1.函式提高

1.1.函式預設引數

在C++中,函式的形參列表中的形參是可以有預設值的。

語法: 返回值类型 函数名 (参数= 默认值){}

示例:

int func(int a, int b = 10, int c = 10) {
	return a + b + c;
}

//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
	return a + b;
}

int main() {

	cout << "ret = " << func(20, 20) << endl;
	cout << "ret = " << func(100) << endl;


	return 0;
}

執行結果:

ret = 50
ret = 120

1.2.函式佔位引數

C++中函式的形參列表裡可以有佔位引數,用來做佔位,呼叫函式時必須填補該位置

語法: 返回值类型 函数名 (数据类型){}

在現階段函式的佔位引數存在意義不大,但是後面的課程中會用到該技術

示例:

//函数占位参数 ,占位参数也可以有默认参数
void func(int a, int) {
	cout << "this is func" << endl;
}

int main() {

	func(10,10); //占位参数必须填补


	return 0;
}

執行結果:

this is func

1.3.函式過載

1.3.1.函式過載概述

**作用:**函式名可以相同,提高複用性

函式過載滿足條件:

  • 同一個作用域下
  • 函式名稱相同
  • 函式引數型別不同 或者 個數不同 或者 順序不同

注意: 函式的返回值不可以作為函式過載的條件

示例:

//函数重载需要函数都在同一个作用域下
void func()
{
	cout << "func 的调用!" << endl;
}
void func(int a)
{
	cout << "func (int a) 的调用!" << endl;
}
void func(double a)
{
	cout << "func (double a)的调用!" << endl;
}
void func(int a ,double b)
{
	cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a ,int b)
{
	cout << "func (double a ,int b)的调用!" << endl;
}

//函数返回值不可以作为函数重载条件
//int func(double a, int b)
//{
//	cout << "func (double a ,int b)的调用!" << endl;
//}

int main() {

	func();
	func(10);
	func(3.14);
	func(10,3.14);
	func(3.14 , 10);
	

	return 0;
}

執行結果:

func 的调用!
func (int a) 的调用!
func (double a)的调用!
func (int a ,double b) 的调用!
func (double a ,int b)的调用!

1.3.2.函式過載注意事項

  • 引用作為過載條件
  • 函式過載碰到函式預設引數

示例:

//函数重载注意事项
//1、引用作为重载条件

void func(int &a)
{
	cout << "func (int &a) 调用 " << endl;
}

void func(const int &a)
{
	cout << "func (const int &a) 调用 " << endl;
}

//2、函数重载碰到函数默认参数

void func2(int a, int b = 10)
{
	cout << "func2(int a, int b = 10) 调用" << endl;
}

void func2(int a)
{
	cout << "func2(int a) 调用" << endl;
}

int main() {
	
	int a = 10;
	func(a); //调用无const
	func(10);//调用有const

	//func2(10); //碰到默认参数产生歧义,需要避免


	return 0;
}

執行結果:

func (int &a) 调用
func (const int &a) 调用

1.4.弱函式

C 和 C++ 都可以使用弱函式(Weak Function)機制,但需要編譯器、目標平臺和連結器支援。下面的示例採用 C++ 寫法。

在支援弱符號的目標平臺上,GCC、Clang 等編譯器可以使用 __attribute__((weak)) 將一個函式定義為弱函式。

弱函式機制不屬於標準 C 或標準 C++,weak 和 __weak 也不是這兩種語言的標準關鍵字,而是工具鏈提供的擴充套件功能。

語法:

__attribute__((weak)) 返回值类型 函数名(参数列表)
{
    // 默认实现
}

例如:

#include <iostream>
using namespace std;

__attribute__((weak)) void func()
{
    cout << "weak func" << endl;
}

int main()
{
    func();

    return 0;
}

如果參與連結的目標檔案中沒有對應同一連結符號的普通函式定義(強定義),那麼呼叫:

func();

時,就會使用這個弱函式。


1.4.1.弱函式的作用

弱函式最主要的作用是:

提供一個預設實現,同時允許使用者在其他原始檔中重新定義這個函式。

例如在 default.cpp 中:

#include <iostream>
using namespace std;

__attribute__((weak)) void func()
{
    cout << "default func" << endl;
}

在另一個 user.cpp 中重新定義:

#include <iostream>
using namespace std;

void func()
{
    cout << "user func" << endl;
}

此時兩個原始檔分別提供同一個函式的弱定義和強定義:

__attribute__((weak)) void func();   // 弱符号

void func();                         // 普通符号,也叫强符号

將兩個原始檔編譯生成的目標檔案一起連結時,強定義會優先於弱定義,因此最終呼叫的是:

user func

可以簡單理解為:

只有弱函数
    ↓
使用弱函数提供的默认实现

弱函数 + 普通函数
    ↓
普通函数优先

因此,弱函式常用於:

库提供默认实现
        ↓
用户需要时可以重新实现
        ↓
不需要修改库本身的源代码

1.4.2.弱函式使用條件

以下機制在 C 和 C++ 中都可使用;實際支援情況還取決於目標檔案格式、目標平臺和連結器。

編譯器 弱函式支援 推薦寫法 __attribute__((weak)) __weak
GCC ✅ __attribute__((weak)) ✅ ❌ 原生沒有,通常通過巨集定義實現
Clang / LLVM ✅ __attribute__((weak)) ✅ ❌ 一般不是原生關鍵字
ARMCC 5 ✅ __weak ⚠️ 不作為主要寫法 ✅ 原生支援
ArmClang / ARM Compiler 6 ✅ __attribute__((weak)) ✅ ⚠️ 可通過相容標頭檔案或宏提供
MSVC ⚠️ 無直接弱函式屬性,可通過連結器機制實現類似功能 /alternatename 等 ❌ ❌

1.4.3.弱函式和函式過載的區別

弱函式與前面學習的函式過載並不是同一種機制。

函式過載:

void func();
void func(int a);
void func(double a);

這些函式的引數列表不同,因此它們本身就是不同的函式。

而弱函式:

__attribute__((weak)) void func()
{
}

和另一個檔案中的:

void func()
{
}

引數列表、函式名等都是相同的。

它們表示的是同一個連結符號的弱實現和強實現。

因此可以簡單區分:

函数重载
    ↓
编译阶段根据参数选择不同函数

弱函数
    ↓
链接阶段在弱符号和强符号之间选择

1.4.4.C 和 C++ 混合時的弱函式

需要已經學會C 介面以及 C/C++ 混合工程的標頭檔案

如果弱函式本身是按照 C 的方式編譯的,例如在 .c 檔案中:

__attribute__((weak)) void func(void)
{
}

那麼在 .cpp 檔案中重新定義這個函式時,需要使用:

extern "C" void func(void)
{
    // 用户实现
}

這是因為 C++ 預設會對函式名進行名稱修飾,而:

extern "C"

表示按照 C 的連結方式生成函式符號,從而保證兩個函式對應同一個符號。

如果弱函式本身就在 .cpp 中,並且使用普通的 C++ 連結方式:

__attribute__((weak)) void func()
{
}

那麼在另一個 .cpp 中重新定義時直接寫:

void func()
{
}

即可,不需要新增:

extern "C"

所以:

是否需要 extern "C"
        ↓
取决于原函数使用 C linkage 还是 C++ linkage
        ↓
与 weak 本身没有直接关系
弱函式來源 弱函式連結方式 C++ 中強定義
.c C linkage extern "C" void foo()
.cpp 普通函式 C++ linkage void foo()
.cpp + extern "C" C linkage extern "C" void foo()

1.4.5.__weak

在一些工程中還可能看到:

__weak void func()
{
}

__weak 在 C 和 C++ 中的含義取決於工具鏈:ARMCC 5 將它作為原生擴充套件關鍵字;在使用 GCC 等編譯器的工程中,它也可能只是對編譯器屬性進行了一層宏封裝,例如:

#define __weak __attribute__((weak))

在採用上述巨集定義的工程中:

__weak void func()
{
}

實際上等價於:

__attribute__((weak)) void func()
{
}

這樣做主要是為了:

  • 讓程式碼更加簡潔;
  • 隱藏具體的編譯器屬性寫法;
  • 方便工程根據不同編譯器統一處理弱符號語法。

例如 STM32 的部分工程中就可能通過這種巨集定義來使用 __weak。


總結:

标准 C 和标准 C++ 本身都没有 weak 或 __weak 关键字

C 和 C++ 都可使用工具链提供的弱函数扩展

GCC / Clang 等编译器(目标平台支持时):
__attribute__((weak))

ARMCC 5:
__weak

MSVC 没有直接对应的弱函数属性,可通过链接器机制实现类似功能。

弱函数:
提供默认实现
        ↓
没有强定义 → 使用弱函数
有强定义   → 使用强函数

.c 中的弱函数
        ↓
.cpp 中重新定义时通常需要 extern "C"

.cpp 中的 C++ 弱函数
        ↓
.cpp 中重新定义时不需要 extern "C"