跳到內容

C 與 C++ 的關係

C++ 從 C 發展出來,幾乎完整包含 C,所以大部分 C 的寫法在 .cpp 檔裡也合法。但檔案是 .cpp,編譯器就會用 C++ 的規則編譯,而有少數地方兩種語言的規則不一樣。

用哪種語言編譯,看的是副檔名

Section titled “用哪種語言編譯,看的是副檔名”

沙盒依照副檔名選擇編譯器:

副檔名編譯指令語言
.cclang -std=c17C
.cppclang++ -std=c++20C++

所以在 .cpp 裡寫 #include <stdio.h>、scanf、printf,編譯出來的是「用 C 的風格寫成的 C++ 程式」,不是 C 程式。C 的標準函式庫本來就是 C++ 標準的一部分,所以這樣寫完全合法。

下面這兩個檔案的內容一模一樣,只差在副檔名:

which_lang.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__ = 201710
sizeof('a') = 4
在 Compiler Explorer 開啟 ↗可以線上修改、執行;那邊是 Linux,有些結果會和 Windows 不同
which_lang.cpp
/* 同一份程式碼,存成 .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 = 202002
sizeof('a') = 1
在 Compiler Explorer 開啟 ↗可以線上修改、執行;那邊是 Linux,有些結果會和 Windows 不同

__cplusplus 這個巨集只有在 C++ 編譯時才會被定義,它的值代表標準版本:202002 是 C++20,201710 是 C17。另外 'a' 這種字元常值的型態,在 C 是 int(4 bytes),在 C++ 是 char(1 byte),所以同一行 sizeof('a') 會得到不同的結果。

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> 的寫法。

要做的事CC++
輸出printf("%d\n", x);std::cout << x << '\n';
輸入scanf("%d", &x);std::cin >> x;
字串char s[100]; 搭配 strlen、strcpystd::string s;
動態陣列malloc / realloc / freestd::vector<int>
配置單一物件malloc / freenew / delete,更好的是智慧指標
讓函式修改參數傳指標:void f(int *p)傳參考:void f(int &x)
常數#define N 100constexpr int N = 100;
空指標NULLnullptr

C++ 的寫法通常比較安全,例如 std::string 不會像 char[] 那樣寫超過陣列範圍。所以第 2 階段的題目都限定用 C++ 寫。

最常遇到的是 malloc:C 允許把 void * 自動轉成其他指標型態,C++ 不允許。

malloc_cast.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
在 Compiler Explorer 開啟 ↗可以線上修改、執行;那邊是 Linux,有些結果會和 Windows 不同
malloc_cast.cpp
/* 和 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.
在 Compiler Explorer 開啟 ↗可以線上修改、執行;那邊是 Linux,有些結果會和 Windows 不同

在 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)。
keywords.c
/* 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
在 Compiler Explorer 開啟 ↗可以線上修改、執行;那邊是 Linux,有些結果會和 Windows 不同
  • 以為自己在寫 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) 之後,兩者就不能混用,否則輸入順序會亂掉。

(還沒寫)