跳到正文

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"