C 與 C++ 的關係
C++ 從 C 發展出來,幾乎完整包含 C,所以大部分 C 的寫法在 .cpp 檔裡也合法。但檔案是 .cpp,編譯器就會用 C++ 的規則編譯,而有少數地方兩種語言的規則不一樣。
用哪種語言編譯,看的是副檔名
Section titled “用哪種語言編譯,看的是副檔名”沙盒依照副檔名選擇編譯器:
| 副檔名 | 編譯指令 | 語言 |
|---|---|---|
.c | clang -std=c17 | C |
.cpp | clang++ -std=c++20 | C++ |
所以在 .cpp 裡寫 #include <stdio.h>、scanf、printf,編譯出來的是「用 C 的風格寫成的 C++ 程式」,不是 C 程式。C 的標準函式庫本來就是 C++ 標準的一部分,所以這樣寫完全合法。
下面這兩個檔案的內容一模一樣,只差在副檔名:
/* 同一份程式碼,存成 .c 和 .cpp 會用不同的語言編譯。 which_lang.c 和 which_lang.cpp 的內容完全一樣,比較兩個的執行結果。 */#include <stdio.h>
int main(void) {#ifdef __cplusplus printf("compiled as C++, __cplusplus = %ld\n", (long)__cplusplus);#else printf("compiled as C, __STDC_VERSION__ = %ld\n", (long)__STDC_VERSION__);#endif /* 字元常值 'a' 的型態:C 是 int,C++ 是 char */ printf("sizeof('a') = %zu\n", sizeof('a')); return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
compiled as C, __STDC_VERSION__ = 201710sizeof('a') = 4/* 同一份程式碼,存成 .c 和 .cpp 會用不同的語言編譯。 which_lang.c 和 which_lang.cpp 的內容完全一樣,比較兩個的執行結果。 */#include <stdio.h>
int main(void) {#ifdef __cplusplus printf("compiled as C++, __cplusplus = %ld\n", (long)__cplusplus);#else printf("compiled as C, __STDC_VERSION__ = %ld\n", (long)__STDC_VERSION__);#endif /* 字元常值 'a' 的型態:C 是 int,C++ 是 char */ printf("sizeof('a') = %zu\n", sizeof('a')); return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
compiled as C++, __cplusplus = 202002sizeof('a') = 1__cplusplus 這個巨集只有在 C++ 編譯時才會被定義,它的值代表標準版本:202002 是 C++20,201710 是 C17。另外 'a' 這種字元常值的型態,在 C 是 int(4 bytes),在 C++ 是 char(1 byte),所以同一行 sizeof('a') 會得到不同的結果。
標頭檔的兩種寫法
Section titled “標頭檔的兩種寫法”| C 的寫法 | C++ 建議的寫法 | 差別 |
|---|---|---|
#include <stdio.h> | #include <cstdio> | <cstdio> 保證 std::printf 這種寫法可以用,名字放在 std 命名空間裡 |
#include <stdlib.h> | #include <cstdlib> | 同上 |
#include <string.h> | #include <cstring> | 同上(注意:和 C++ 的 <string> 是不同的東西) |
C++ 裡兩種寫法都可以用,但新寫的 C++ 程式建議用 <cxxx> 的寫法。
同一件事,C 風格和 C++ 風格
Section titled “同一件事,C 風格和 C++ 風格”| 要做的事 | C | C++ |
|---|---|---|
| 輸出 | printf("%d\n", x); | std::cout << x << '\n'; |
| 輸入 | scanf("%d", &x); | std::cin >> x; |
| 字串 | char s[100]; 搭配 strlen、strcpy | std::string s; |
| 動態陣列 | malloc / realloc / free | std::vector<int> |
| 配置單一物件 | malloc / free | new / delete,更好的是智慧指標 |
| 讓函式修改參數 | 傳指標:void f(int *p) | 傳參考:void f(int &x) |
| 常數 | #define N 100 | constexpr int N = 100; |
| 空指標 | NULL | nullptr |
C++ 的寫法通常比較安全,例如 std::string 不會像 char[] 那樣寫超過陣列範圍。所以第 2 階段的題目都限定用 C++ 寫。
C 可以、C++ 不行的寫法
Section titled “C 可以、C++ 不行的寫法”最常遇到的是 malloc:C 允許把 void * 自動轉成其他指標型態,C++ 不允許。
/* 在 C 裡,malloc 回傳的 void * 會自動轉成 int *,不需要轉型。 */#include <stdio.h>#include <stdlib.h>
int main(void) { int *p = malloc(3 * sizeof(int)); if (p == NULL) return 1; p[0] = 1; p[1] = 2; p[2] = 3; printf("%d %d %d\n", p[0], p[1], p[2]); free(p); return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
1 2 3/* 和 malloc_cast.c 一模一樣的程式碼,存成 .cpp 就編譯不過: C++ 不允許把 void * 自動轉成其他指標型態。 */#include <stdio.h>#include <stdlib.h>
int main(void) { int *p = malloc(3 * sizeof(int)); if (p == NULL) return 1; p[0] = 1; p[1] = 2; p[2] = 3; printf("%d %d %d\n", p[0], p[1], p[2]); free(p); return 0;}在這台電腦編譯的結果(這個範例預期會編譯失敗):
malloc_cast.cpp:8:10: error: cannot initialize a variable of type 'int *' with an rvalue of type 'void *' 8 | int *p = malloc(3 * sizeof(int)); | ^ ~~~~~~~~~~~~~~~~~~~~~~~1 error generated.在 C++ 裡要寫成 int *p = (int *)malloc(...) 或 static_cast<int *>(...)。不過在 C++ 裡更好的做法是根本不用 malloc,改用 new 或 std::vector。
其他 C 可以、C++ 不行的情況:
- 把 C++ 的關鍵字當成名字:
new、delete、class、template、this、namespace在 C 裡只是普通的名字(見下方範例)。 - 可變長度陣列,例如
int a[n];:C99 引進(C11 起改為選用),C++ 標準不支援(clang 允許,但會警告)。 int f()的意思不一樣:在 C17 代表「參數不指定」,在 C++ 代表「沒有參數」。所以 C 的習慣是寫int f(void)。
/* new、class、delete 在 C 只是普通的名字,在 C++ 是關鍵字。 把這個檔案另存成 .cpp 試試看,會出現一堆編譯錯誤。 */#include <stdio.h>
int main(void) { int new = 1, class = 2, delete = 3; printf("%d\n", new + class + delete); return 0;}在這台電腦執行的結果(Windows x64、clang 23、-O2,沒有開 Sanitizer):
6- 以為自己在寫 C,其實在寫 C++:副檔名是
.cpp就是 C++。如果題目「只限 C」,答案檔一定要是main.c。 - 在 C++ 裡用
malloc配置有建構子的物件,例如std::string:malloc只配置記憶體,不會呼叫建構子,物件沒有初始化就使用是未定義行為。C++ 裡請用new或容器。 printf印std::string:要寫printf("%s", s.c_str()),不能直接傳s。- 混用
scanf和std::cin一般沒問題。但呼叫了std::ios::sync_with_stdio(false)之後,兩者就不能混用,否則輸入順序會亂掉。
用到這個主題的題目
Section titled “用到這個主題的題目”- 01|Hello, World! — ✔ 已完成
- 02|兩數相加 — ✔ 已完成
- 03|時間換算 — ✔ 已完成
- 維基百科:C 與 C++ 的相容性(中文)
- Wikipedia: Compatibility of C and C++(英文,比中文版詳細)
- cppreference:C++ 標準函式庫的標頭檔,包含
<cxxx>這一類(英文)
(還沒寫)