函數與頭文件
函數
概述
**作用:**將一段經常使用的代碼封裝起來,減少重複代碼
一個較大的程序,一般分爲若干個程序塊,每個模塊實現特定的功能。
函數的定義
函數的定義一般主要有5個步驟:
1、返回值類型
2、函數名
3、參數表列
4、函數體語句
5、return 表達式
語法:
返回值类型 函数名 (参数列表)
{
函数体语句
return表达式
}
運行結果: 此代碼塊不含 main 函數,是語法、接口或分文件示例,不能獨立運行,因此沒有終端輸出。
- 返回值類型 :一個函數可以返回一個值。在函數定義中
- 函數名:給函數起個名稱
- 參數列表:使用該函數時,傳入的數據
- 函數體語句:花括號內的代碼,函數內需要執行的語句
- return表達式: 和返回值類型掛鉤,函數執行完後,返回相應的數據
**示例:**定義一個加法函數,實現兩個數相加
//函数定义
int add(int num1, int num2)
{
int sum = num1 + num2;
return sum;
}
運行結果: 此代碼塊不含 main 函數,是語法、接口或分文件示例,不能獨立運行,因此沒有終端輸出。
函數的調用
**功能:**使用定義好的函數
語法: 函数名(参数)
示例:
//函数定义
int add(int num1, int num2) //定义中的num1,num2称为形式参数,简称形参
{
int sum = num1 + num2;
return sum;
}
int main() {
int a = 10;
int b = 10;
//调用add函数
int sum = add(a, b);//调用时的a,b称为实际参数,简称实参
cout << "sum = " << sum << endl;
a = 100;
b = 100;
sum = add(a, b);
cout << "sum = " << sum << endl;
return 0;
}
運行結果:
sum = 20
sum = 200
總結:函數定義裏小括號內稱爲形參,函數調用時傳入的參數稱爲實參
值傳遞
- 所謂值傳遞,就是函數調用時實參將數值傳入給形參
- 值傳遞時,==如果形參發生,並不會影響實參==
示例:
void swap(int num1, int num2)
{
cout << "交换前:" << endl;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
int temp = num1;
num1 = num2;
num2 = temp;
cout << "交换后:" << endl;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
//return ; 当函数声明时候,不需要返回值,可以不写return
}
int main() {
int a = 10;
int b = 20;
swap(a, b);
cout << "mian中的 a = " << a << endl;
cout << "mian中的 b = " << b << endl;
return 0;
}
運行結果:
交换前:
num1 = 10
num2 = 20
交换后:
num1 = 20
num2 = 10
mian中的 a = 10
mian中的 b = 20
總結: 值傳遞時,形參是修飾不了實參的
函數的常見樣式
常見的函數樣式有4種
- 無參無返
- 有參無返
- 無參有返
- 有參有返
示例:
//函数常见样式
//1、 无参无返
void test01()
{
//void a = 10; //无类型不可以创建变量,原因无法分配内存
cout << "this is test01" << endl;
//test01(); 函数调用
}
//2、 有参无返
void test02(int a)
{
cout << "this is test02" << endl;
cout << "a = " << a << endl;
}
//3、无参有返
int test03()
{
cout << "this is test03 " << endl;
return 10;
}
//4、有参有返
int test04(int a, int b)
{
cout << "this is test04 " << endl;
int sum = a + b;
return sum;
}
運行結果: 此代碼塊不含 main 函數,是語法、接口或分文件示例,不能獨立運行,因此沒有終端輸出。
函數的聲明
作用: 告訴編譯器函數名稱及如何調用函數。函數的實際主體可以單獨定義。
- 函數的聲明可以多次,但是函數的定義只能有一次
示例:
//声明可以多次,定义只能一次
//声明
int max(int a, int b);
int max(int a, int b);
//定义
int max(int a, int b)
{
return a > b ? a : b;
}
int main() {
int a = 100;
int b = 200;
cout << max(a, b) << endl;
return 0;
}
運行結果:
200
函數的分文件編寫
**作用:**讓代碼結構更加清晰
前面幾章我們主要在 src/main.cpp 裏寫代碼。這樣適合學習語法,但是程序稍微大一點,就會變得很亂。
函數的分文件編寫,就是把代碼拆成:
- 頭文件
.h:寫函數聲明。 - 源文件
.cpp:寫函數定義。 main.cpp:調用函數。
從 C++開發環境與第一個工程 開始,本教程一直使用同一個 CMake 模板。寫到這裏時,你的工程大致還是這個結構:
src/
├── CMakeLists.txt
├── main.cpp
├── lib1/
│ ├── CMakeLists.txt
│ ├── inc/lib1/eigen3_test.hpp
│ └── src/eigen3_test.cpp
└── lib2/
├── CMakeLists.txt
├── inc/lib2/eigen3_test.hpp
└── src/eigen3_test.cpp
lib1 和 lib2 是模板自帶的兩個示例庫。本節第一次整理它們:刪掉暫時用不到的 lib2,把 lib1 改成我們自己的 swap 小庫,再把模板自帶的示例頭文件和源文件分別重命名爲 swap.h 和 swap.cpp,用它們完成本節的函數分文件編寫示例。
改完之後,目錄結構變成:
src/
├── CMakeLists.txt
├── main.cpp
└── swap/
├── CMakeLists.txt
├── inc/swap/swap.h
└── src/swap.cpp
也就是說:
- 刪除本例暫時用不到的
lib2。 - 把
lib1目錄重命名爲swap。 - 把
inc/lib1目錄重命名爲inc/swap,再把其中的eigen3_test.hpp重命名爲swap.h。 - 把
src/eigen3_test.cpp重命名爲src/swap.cpp。
整理 lib1 和 lib2
首先刪除掉lib2,這裏不需要.

- 把
lib1目錄重命名爲swap: - 把
inc/lib1目錄重命名爲inc/swap,再把其中的eigen3_test.hpp重命名爲swap.h: - 把
src/eigen3_test.cpp重命名爲src/swap.cpp:

修改 swap.h
打開:
src/swap/inc/swap/swap.h
寫入:
#include<iostream>
using namespace std;
void swap(int a, int b);

這是我們第一次把函數聲明單獨放進頭文件。swap.h 只負責告訴其他源文件:工程中存在一個名爲 swap、接收兩個 int 參數的函數。
補充說明:真實工程裏通常不建議在頭文件中寫
using namespace std;,函數名也儘量避免直接叫swap,因爲標準庫裏有std::swap。不過這裏先專心理解"頭文件聲明、源文件定義、主函數調用"這條線,因此暫時使用這份簡單的教學代碼。
修改 swap.cpp
打開:
src/swap/src/swap.cpp
寫入:
#include "swap/swap.h"
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}

這裏的 #include "swap/swap.h" 表示:swap.cpp 要先看到函數聲明,再提供函數定義。
這時候有VScode的報錯無所謂,是因爲CMakeLists沒修改,其次clangd也沒有被刷新,先不用管代碼提示的報錯.
修改 main.cpp
打開:
src/main.cpp
寫入:
#include "swap/swap.h"
int main()
{
int a = 100;
int b = 200;
swap(a, b);
return 0;
}
運行結果(與上面的 swap.cpp 一起編譯並鏈接):
a = 200
b = 100

main.cpp 只負責調用函數,不再把 swap 函數的具體實現寫在自己裏面。
修改 src/swap/CMakeLists.txt
打開:
src/swap/CMakeLists.txt
這個文件原來來自 lib1,所以裏面還有 lib1 和 Eigen 示例的痕跡。
先把第一行從:
set(PREFIX "lib1")
改成:
set(PREFIX "swap")
這樣 CMake 會創建一個名爲:
swap_src_lib
的庫。
swap 這個例子不需要 Eigen,所以刪除 Third-party dependencies 區塊裏的 Eigen 依賴:
find_package(Eigen3 REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PUBLIC
Eigen3::Eigen
)
只用修改以上兩項,其他的均不用修改,下面介紹一下比較重點的地方:
整理後,src/swap/CMakeLists.txt 可以寫成:
set(PREFIX "swap")
file(GLOB_RECURSE ${PREFIX}_SRC_LIST CONFIGURE_DEPENDS
"${CMAKE_CURRENT_LIST_DIR}/src/*.c"
"${CMAKE_CURRENT_LIST_DIR}/src/*.cpp"
)
add_library(${PREFIX}_src_lib SHARED
${${PREFIX}_SRC_LIST}
)
target_include_directories(${PREFIX}_src_lib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/inc>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_link_libraries(${PREFIX}_src_lib
PUBLIC
project_options
PRIVATE
project_warnings
)
# ========================
# Third-party dependencies
# ========================
# =======
# Install
# =======
install(TARGETS ${PREFIX}_src_lib
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/inc/"
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
這裏最重要的幾行是:
file(GLOB_RECURSE ${PREFIX}_SRC_LIST CONFIGURE_DEPENDS
"${CMAKE_CURRENT_LIST_DIR}/src/*.c"
"${CMAKE_CURRENT_LIST_DIR}/src/*.cpp"
)
它會把 src/swap/src/ 裏面的 .cpp 文件收集進來,所以 swap.cpp 會參與編譯。
還有:
target_include_directories(${PREFIX}_src_lib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/inc>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
它把 src/swap/inc/ 加入頭文件搜索路徑,所以代碼裏才能寫:
#include "swap/swap.h"
注意:不要寫成 #include "inc/swap/swap.h"。CMake 已經把 src/swap/inc/ 告訴編譯器了,所以 include 時從 inc/ 裏面繼續往後寫,也就是 swap/swap.h。
完整 CMake 寫法會在 CMake工程模板 中系統講解。這裏先知道"頭文件放進 inc/,源文件放進 src/,庫名由 PREFIX 決定"即可。
修改 src/CMakeLists.txt
打開:
src/CMakeLists.txt
原來模板裏有:
add_subdirectory(lib1)
add_subdirectory(lib2)
改成:
add_subdirectory(swap)
原來的模板裏允許lib1調用lib2,現在lib2沒了,所以刪掉這部分:
target_link_libraries(lib1_src_lib
PRIVATE
lib2_src_lib
)
上面的這個直接刪除.
原來模板裏還會鏈接:
target_link_libraries(${PROJECT_NAME}
PRIVATE
lib1_src_lib
lib2_src_lib
)
改成:
target_link_libraries(${PROJECT_NAME}
PRIVATE
swap_src_lib
)
只用修改以上三項,其他的均不用修改,下面介紹一下比較重點的地方:
整理後,src/CMakeLists.txt 可以寫成:
add_executable(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
project_options
project_warnings
)
add_subdirectory(swap)
target_link_libraries(${PROJECT_NAME}
PRIVATE
swap_src_lib
)
set_target_properties(${PROJECT_NAME} PROPERTIES
INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
)
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
這兩處最關鍵:
add_subdirectory(swap)
意思是:讓 CMake 進入 src/swap/,讀取 src/swap/CMakeLists.txt。
target_link_libraries(${PROJECT_NAME}
PRIVATE
swap_src_lib
)
意思是:主程序調用了 swap 函數,所以主程序要鏈接 swap_src_lib,否則鏈接器找不到 swap 函數的定義。
重新構建並運行
編譯

此時你再看clangd的代碼提示就不報錯了.

然後運行:

輸出類似:
a = 200
b = 100
可以看到,swap.cpp 裏的函數確實被主程序調用到了。
分文件編寫的核心理解
在這個模板中,函數分文件編寫不是隻把文件拆開,還要讓 CMake 知道這些文件屬於哪個庫。
對應關係如下:
| 文件 | 作用 |
|---|---|
src/swap/inc/swap/swap.h | 寫函數聲明,讓其他 .cpp 文件知道怎麼調用 |
src/swap/src/swap.cpp | 寫函數定義,提供真正的實現 |
src/swap/CMakeLists.txt | 把 swap.cpp 編譯成 swap_src_lib |
src/CMakeLists.txt | 把 swap_src_lib 鏈接到主程序 |
src/main.cpp | #include "swap/swap.h" 後調用 swap(a, b) |
如果漏了 #include "swap/swap.h",編譯器不知道 swap 函數的聲明。
如果漏了 add_subdirectory(swap),CMake 不會進入 src/swap。
如果漏了:
target_link_libraries(${PROJECT_NAME}
PRIVATE
swap_src_lib
)
鏈接器找不到 swap 函數的實現。
頭文件的組織方式
剛纔我們把 swap 函數的聲明放進了 swap.h。其他源文件只要包含這個頭文件,就能知道這個庫提供了哪些函數。這些提供給外部代碼使用的函數、類型和變量,通常統稱爲庫的 API。
頭文件一般負責放置:
- 函數聲明。
- 類型、結構體和類的聲明或定義。
- 必要的常量與變量聲明。
- 使用這些聲明所必需的其他頭文件。
頭文件本身不能單獨運行。修改頭文件後,需要重新編譯包含它的 .c 或 .cpp 文件來驗證。
純 C++ 工程的頭文件
本章的 swap 庫由 swap.cpp 實現,並由 main.cpp 調用,所有源文件都按照 C++ 代碼編譯。因此,我們先來看普通 C++ 頭文件的完整寫法。
純 C++ 工程推薦使用下面的頭文件模板:
#ifndef __FILE_NAME_H_ // 防止头文件被重复包含,使用时要修改宏名
#define __FILE_NAME_H_
/* 头文件内容开始 */
// 在这里编写必要的预处理指令、类型定义、函数声明和变量声明
/* 头文件内容结束 */
#endif // __FILE_NAME_H_
使用模板時,要根據實際文件名修改保護宏。例如:
swap.h可以使用__SWAP_H_。camera.hpp可以使用__CAMERA_HPP_。
本章的 swap.h 可以整理成:
#ifndef __SWAP_H_
#define __SWAP_H_
#include <iostream>
using namespace std;
void swap(int a, int b);
#endif // __SWAP_H_
這纔是C++頭文件最規範的形態.
編譯執行後:

這裏開頭的 #ifndef 和 #define,以及末尾的 #endif,組成了 頭文件保護宏。同一個頭文件即使被間接包含多次,其中的內容也只會參與一次編譯,避免出現重複定義等問題。
純 C++ 項目中也經常使用 .hpp 擴展名,用來強調這是 C++ 頭文件;.h 同樣可以使用。項目自己的頭文件通常應該保留擴展名,例如:
#include "swap/swap.h"
標準庫中的 <iostream>、<string> 等名字沒有擴展名,是 C++ 標準規定的命名方式,不代表項目中的頭文件也應該省略擴展名。
C 接口以及 C/C++ 混合工程的頭文件
一個工程中可以同時存在 .c 和 .cpp 文件。例如,既可以讓 C++ 源文件xxx.cpp調用 .c 文件中的函數,也可以讓 main.c 調用 .cpp 文件中的函數。
但是,C 與 C++ 編譯器處理函數名稱的方式並不完全相同。只要調用跨越了 C 和 C++ 的邊界,就應提供一個兼容的接口頭文件。
頭文件中的 extern "C" 只會在它被 C++ 編譯器處理時生效,作用是告訴 C++ 編譯器:這些函數按照 C 語言的鏈接規則處理。
對於需要在 C 和 C++ 之間使用的接口,推薦使用下面的頭文件模板,且建議擴展名爲.h:
#ifndef __FILE_NAME_H_ // 防止头文件被重复包含,使用时要修改宏名
#define __FILE_NAME_H_
#ifdef __cplusplus // 当前文件被 C++ 编译器处理时才会成立
extern "C" {
#endif
/* 头文件内容开始 */
// 在这里编写必要的预处理指令、类型定义、函数声明和变量声明
/* 头文件内容结束 */
#ifdef __cplusplus
}
#endif
#endif // __FILE_NAME_H_
下面的兩個例子是兩種獨立的工程形式,都在前面完成的 swap 工程基礎上修改。可以只選擇一種練習;如果連續練習,需要按照第二個例子的說明先恢復文件名。
main.cpp 調用 swap.c
這個例子保留 C++ 編寫的 main.cpp,把 swap 函數改爲使用 C 語言實現。
首先將 swap.cpp 重命名爲 swap.c:
改完後的主要目錄結構爲:
src/
├── CMakeLists.txt
├── main.cpp
└── swap/
├── CMakeLists.txt
├── inc/swap/swap.h
└── src/swap.c

因爲 src/swap/CMakeLists.txt 已經會同時收集 .c 和 .cpp 文件,所以這裏只需重命名文件,不用修改這個 CMake 文件。
把 src/swap/inc/swap/swap.h 改成可以同時被 C 和 C++ 包含的形式:
#ifndef __SWAP_H_
#define __SWAP_H_
#ifdef __cplusplus
extern "C" {
#endif
void swap(int a, int b);
#ifdef __cplusplus
}
#endif
#endif // __SWAP_H_
注意,這個頭文件可能被 C 編譯器處理,所以不能再在其中包含 <iostream> 或編寫 using namespace std;。
在 src/swap/src/swap.c 中使用 C 語言實現 swap:
#include "swap/swap.h"
#include <stdio.h>
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
printf("a = %d\n", a);
printf("b = %d\n", b);
}
src/main.cpp 仍然是 C++ 源文件,且不用做任何更改:
#include "swap/swap.h"
int main()
{
int a = 100;
int b = 200;
swap(a, b);
return 0;
}
運行結果(與上面的 swap.c 一起編譯並鏈接):
a = 200
b = 100

編譯 swap.c 時,C 編譯器不會定義 __cplusplus,因此會跳過 extern "C"。編譯 main.cpp 時,C++ 編譯器會看到 extern "C",從而按照 C 語言的鏈接規則尋找 swap.c 中的 swap 函數。
main.c 調用 swap.cpp
難點,在STM32HAL庫中常用
這個例子反過來:入口文件使用 C 語言,而 swap 庫繼續使用 C++ 實現。
如果剛做完上一個例子,先把 swap.c 改回 swap.cpp:
接着把入口文件 main.cpp 重命名爲 main.c:

改完後的主要目錄結構爲:
src/
├── CMakeLists.txt
├── main.c
└── swap/
├── CMakeLists.txt
├── inc/swap/swap.h
└── src/swap.cpp
文件名發生變化後,還要打開 src/CMakeLists.txt,把:
add_executable(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)
改成:
add_executable(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/main.c
)
src/swap/inc/swap/swap.h 繼續使用前面的兼容寫法:
#ifndef __SWAP_H_
#define __SWAP_H_
#ifdef __cplusplus
extern "C" {
#endif
void swap(int a, int b);
#ifdef __cplusplus
}
#endif
#endif // __SWAP_H_
在 src/swap/src/swap.cpp 中使用 C++ 實現 swap:
#include "swap/swap.h"
#include <iostream>
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
std::cout << "a = " << a << std::endl;
std::cout << "b = " << b << std::endl;
}
swap.cpp 必須包含 swap.h。C++ 編譯器讀取頭文件後,纔會知道這個 swap 函數需要按照 C 語言的鏈接規則對外提供。
最後在 src/main.c 中調用它,main.c仍然沿用main.cpp不需要做任何修改:
#include "swap/swap.h"
int main(void)
{
int a = 100;
int b = 200;
swap(a, b);
return 0;
}
main.c 只通過頭文件調用 swap,不需要理解 std::cout 等 C++ 寫法。C++ 代碼全部留在 swap.cpp 中。
只有需要跨越 C/C++ 邊界的函數才需要這種兼容寫法,其他 .cpp 文件之間仍然按照普通 C++ 方式調用。
模板的頂層 CMakeLists.txt 已經通過 LANGUAGES C CXX 啓用了 C 和 C++。CMake 會用 C 編譯器編譯 main.c,用 C++ 編譯器編譯 swap.cpp,並把 swap.cpp 構建成模板中的 swap_src_lib 共享庫,最後再將它鏈接給主程序。
extern "C" 只改變鏈接規則,並不會把 C++ 代碼自動轉換成 C。放在其中的接口應使用 C 能理解的類型和寫法,不要使用函數重載、類、模板或 C++ 引用等 C++ 專有功能。
main.c 調用 swap.cpp(進階)
難點,在STM32HAL庫中常用
上一個例子直接把 swap 函數做成了 C 接口。這樣可以跑通,但也帶來一個限制:swap.h 必須讓 C 編譯器看得懂,所以裏面不能寫命名空間、類、模板、函數重載、C++ 引用等 C++ 專有寫法。
更常見的做法是再加一層 C 接口文件:
main.c只調用一個 C 能理解的函數,例如cpp_main()。cpp_interface.cpp負責進入 C++ 世界。swap.cpp保持普通 C++ 寫法,可以使用命名空間。
改完後的主要目錄結構爲:
src/
├── CMakeLists.txt
├── cpp_interface.cpp
├── cpp_interface.h
├── main.c
└── swap/
├── CMakeLists.txt
├── inc/swap/swap.h
└── src/swap.cpp
先在 src/ 下創建 cpp_interface.h:
#ifndef __CPP_INTERFACE_H_
#define __CPP_INTERFACE_H_
#ifdef __cplusplus
extern "C" {
#endif
int cpp_main(void);
#ifdef __cplusplus
}
#endif
#endif // __CPP_INTERFACE_H_
這個頭文件纔是真正給 main.c 使用的 C 接口。它裏面只聲明 cpp_main(void),參數和返回值都是 C 能理解的類型。
然後修改 src/main.c:
#include "cpp_interface.h"
int main(void)
{
return cpp_main();
}
此時 main.c 不再包含 swap/swap.h,也不再直接調用 swap。它只負責啓動 C++ 側的 cpp_main()。
接着把 src/swap/inc/swap/swap.h 改回普通 C++ 頭文件,並給 swap 加上命名空間:
#ifndef __SWAP_H_
#define __SWAP_H_
namespace swap_demo
{
void swap(int a, int b);
}
#endif // __SWAP_H_
注意,這個 swap.h 現在已經不是 C 接口頭文件了。它裏面有 namespace,所以不能再讓 main.c 直接包含。
這裏也不需要給 swap.h 加:
#ifdef __cplusplus
extern "C" {
#endif
原因是:swap_demo::swap 本來就是一個普通 C++ 函數,我們希望它保留 C++ 的命名空間寫法。extern "C" 應該只放在 C 代碼真正要調用的那層接口上,也就是前面的 cpp_interface.h 裏的 cpp_main(void)。
換句話說,進階版中有兩個頭文件:
| 頭文件 | 誰來包含 | 是否需要 extern "C" |
|---|---|---|
cpp_interface.h | main.c 和 cpp_interface.cpp | 需要,因爲它是 C/C++ 邊界 |
swap/swap.h | swap.cpp 和 cpp_interface.cpp | 不需要,因爲它是純 C++ 頭文件 |
繼續修改 src/swap/src/swap.cpp:
#include "swap/swap.h"
#include <iostream>
namespace swap_demo
{
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
std::cout << "a = " << a << std::endl;
std::cout << "b = " << b << std::endl;
}
}
最後創建 src/cpp_interface.cpp:
#include "cpp_interface.h"
#include "swap/swap.h"
int cpp_main(void)
{
int a = 100;
int b = 200;
swap_demo::swap(a, b);
return 0;
}
cpp_interface.cpp 是 C 和 C++ 的分界線。它包含了 cpp_interface.h,所以 cpp_main() 會按照 C 語言的鏈接規則對外提供;它本身又是 .cpp 文件,所以內部可以正常調用 swap_demo::swap(a, b) 這種 C++ 寫法。
文件增加後,還要打開 src/CMakeLists.txt,把 cpp_interface.cpp 加入主程序:
add_executable(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/main.c
${CMAKE_CURRENT_SOURCE_DIR}/cpp_interface.cpp
)
這裏不用把 cpp_interface.h 寫進 add_executable()。CMake 主要負責把 .c、.cpp 這類源文件交給編譯器編譯,頭文件本身不會單獨編譯成一個目標文件。
cpp_interface.h 是通過代碼裏的 #include 被找到的:
#include "cpp_interface.h"
因爲 main.c、cpp_interface.cpp 和 cpp_interface.h 都在 src/ 同一層目錄下,所以這裏使用雙引號包含時,編譯器可以從當前源文件所在目錄找到它。
如果以後把 cpp_interface.h 移到單獨的 inc/ 目錄,就需要再用 target_include_directories() 把那個目錄加入頭文件搜索路徑。當前這個例子放在同一層目錄,所以不需要額外修改 CMake。
src/swap/CMakeLists.txt 不需要修改,因爲它本來就會收集 src/swap/src/ 目錄下的 .cpp 文件,swap.cpp 仍然會被編譯進 swap_src_lib。
編譯運行後,輸出仍然類似:
a = 200
b = 100
這個進階寫法的重點是:extern "C" 只放在最外層、最簡單的接口上,也就是 cpp_main(void)。真正的 C++ 代碼放在 cpp_interface.cpp 後面,swap 可以使用命名空間,以後也可以繼續使用類、模板、函數重載等 C++ 功能,只要這些 C++ 專有內容不要直接暴露給 main.c。
#include <...> 和 #include "..."
兩種寫法都用於包含頭文件,但常見用途不同:
#include <iostream>
#include <opencv2/opencv.hpp>
尖括號通常用於標準庫或已經安裝的第三方庫頭文件。
#include "swap/swap.h"
雙引號通常用於當前工程自己的頭文件。這裏能夠寫成 "swap/swap.h",是因爲 CMake 已經把 src/swap/inc/ 添加到了編譯器的頭文件搜索路徑中。
無論使用 C 還是 C++,都應爲頭文件添加保護宏;只有頭文件需要作爲 C 與 C++ 之間的接口時,纔在保護宏內部增加 extern "C" 兼容部分。