Home
探索 Uedu
學生控制台
註冊會員/登入
研究知情同意中心
教師控制台
課程設定
支援與訊息
Uptime 數據

UeduGPTs

--

Jupyters

4

UG26 CISOSE26
臺北 AQI 46 · 臺中 AQI 28 · 臺南 AQI 24 · 高雄 AQI 33

AI 回覆桌面通知

AI 助教回覆完成時顯示桌面通知

聊天訊息通知

同學在討論區發送訊息時通知

聲音通知

每當有新通知時播放提示音

變數、型別與輸入輸出

C++ 變數、型別與輸入輸出:讓編譯器替你把關

從靜態型別、const 到 cin/cout 與型別轉換,看 C++ 如何在程式執行前就抓住錯誤,以及 Python 使用者最容易踩的雷

從一張收據開始:讓編譯器在你跑程式前先抓錯

假設你要寫一支小程式:讀進商品「單價」與「數量」,算出總價。在 Python 裡你大概不會多想,直接 price * qty 就好。但在 C++ 裡,你得先回答一個問題——「單價是整數還是小數?數量呢?」——而且這個答案必須在寫程式時就講清楚,寫進變數宣告裡。

這聽起來像是多此一舉的麻煩,卻正是 C++ 與 Python 最根本的分野。Python 是動態型別(dynamic typing):變數的型別在執行時才確定,一個 x 今天裝整數、明天裝字串都沒人攔你。C++ 是靜態型別(static typing):每個變數從出生那一刻就綁定一個固定型別,且這件事由編譯器(compiler)在程式真正執行之前就檢查完畢。打錯型別?程式根本編譯不過,連跑都跑不起來。

這篇文章會帶你認識 C++ 的型別系統、變數宣告、cin/cout 輸入輸出,以及那些 Python 使用者轉過來最容易踩的雷。我們的目標不是「把 Python 換成 C++ 語法重講一次」,而是理解 C++ 為什麼要這樣設計,以及這個設計給了你什麼。

C++ 變數、型別與輸入輸出概念示意圖

變數宣告:型別寫在最前面

C++ 變數的基本形式是「型別 名稱 = 初始值;」。型別在前,這是靜態型別語言的招牌。

#include <iostream>

int main() {
    int qty = 3;            // 整數
    double price = 49.9;    // 雙精度浮點數
    char grade = 'A';       // 單一字元(注意是單引號)
    bool passed = true;     // 布林值:true 或 false

    std::cout << qty << " " << price << " " << grade << " " << passed << "\n";
    // 輸出:3 49.9 A 1
    return 0;
}

幾個立刻要記住的點:

  • bool 印出來是 10,不是 true / false(除非你用 std::boolalpha)。
  • char單引號 'A',字串用雙引號 "A",兩者是完全不同的東西。'A' 本質上是一個整數(它的 ASCII 值是 65)。
  • 每個敘述句結尾要有分號 ;,這點 Python 使用者要適應。

相較於 Python 的 qty = 3,C++ 多寫的那個 int 不是廢話。它是一份契約:你和編譯器約定好 qty 永遠是整數,編譯器就能在後續每一次使用時替你檢查、並產生最有效率的機器碼。

四個基本內建型別

型別 用途 範例字面值 典型大小
int 整數 42-7 4 bytes(約 ±21 億)
double 浮點數(小數) 3.142.0 8 bytes
char 單一字元 'x''\n' 1 byte
bool 真假值 truefalse 1 byte

這裡藏著 C++ 與 Python 的一大差異:整數有上限。Python 的整數可以無限大(自動升級成大數),但 C++ 的 int 是固定 4 bytes,超過範圍會發生「溢位(overflow)」,得到莫名其妙的結果。需要更大的範圍時改用 long long

cout 與 cin:C++ 的輸入輸出

C++ 透過 <iostream> 標準函式庫做輸入輸出。輸出用 std::cout,搭配 <<(插入運算子);輸入用 std::cin,搭配 >>(擷取運算子)。

#include <iostream>

int main() {
    int age;
    std::cout << "請輸入你的年齡:";
    std::cin >> age;
    std::cout << "明年你就 " << age + 1 << " 歲了\n";
    return 0;
}

<< 可以串接,由左往右把東西「推」進輸出串流。"\n" 是換行字元。你也會看到有人用 std::endl,它除了換行還會強制清空緩衝區(flush),通常較慢;除非你真的需要立刻刷新輸出,平常用 "\n" 就好。

那一長串 std:: 是什麼?std 是 standard library 的命名空間(namespace)。許多教材會在開頭寫 using namespace std; 來省略它,但這在大型專案中容易造成名稱衝突,是業界普遍不建議的反模式。比較好的折衷是只引入你要的:

#include <iostream>
using std::cout;
using std::cin;

本文後續為清楚起見保留 std:: 前綴。

const:宣告「這個值不會變」

如果某個值在程式執行期間不該被改動——例如圓周率、稅率、班級人數上限——就用 const 把它鎖起來。

const double TAX_RATE = 0.05;
const int MAX_STUDENTS = 50;

// TAX_RATE = 0.07;  // 編譯錯誤!不能修改 const 變數

const 不只是「給人看的提醒」,它是編譯期強制的。一旦你試圖修改它,編譯器直接報錯。這呼應了 C++ 的核心哲學:把錯誤盡量提前到編譯期。相較於 Python 靠命名慣例(全大寫變數「約定俗成」不要改,但語言其實攔不住你),C++ 的 const 是真有牙齒的保證。養成習慣:只要一個值邏輯上不該變,就加 const

auto:讓編譯器幫你推導型別

寫多了 intdouble,你可能會想:既然初始值已經透露了型別,能不能省下那個字?可以,用 auto

auto qty = 3;          // 編譯器推導為 int
auto price = 49.9;     // 推導為 double
auto name = 'K';       // 推導為 char

注意:auto 不是動態型別。它只是叫編譯器「看右邊的初始值,幫我把型別填上」。一旦推導完成,qty 就永遠是 int,跟你親手寫 int qty 完全一樣,之後想塞字串照樣編譯不過。這跟 Python 的 qty = 3 看似神似,本質卻天差地遠:Python 的 qty 隨時能換型別,C++ 的 auto qty 是「型別被推導後就定終身」。

auto 真正發光的場合是型別名稱很長的時候(例如迭代器、複雜的範本型別),這在你之後學到容器與範本時會深刻體會。對基本型別,初學階段我建議:簡單時把型別寫清楚有助於閱讀,等你熟練了再依場合用 auto

整數與浮點:那個會咬人的除法

這是初學者最常中招的地方。在 C++ 中,兩個整數相除得到的是整數,小數部分直接被截掉(無條件捨去,不是四捨五入)。

#include <iostream>

int main() {
    int a = 7, b = 2;
    std::cout << a / b << "\n";        // 輸出:3   ←整數除法!
    std::cout << 7.0 / 2 << "\n";      // 輸出:3.5 ←只要有一個是浮點
    std::cout << a % b << "\n";        // 輸出:1   ←取餘數
    return 0;
}

7 / 2 給你 3 而不是 3.5,因為兩個運算元都是 int,C++ 認定你要的是整數運算。想要小數結果,至少要讓其中一個是浮點數,例如 7.0 / 2,或把變數轉型(下一節談)。

這與 Python 3 的行為不同:Python 3 的 / 永遠回傳浮點(7 / 23.5),整數除法要用 //。從 Python 轉來的人請特別小心這個差異。

浮點數還有另一個無關語言、但 C++ 不會替你藏起來的坑——精度。double 用二進位表示小數,有些十進位小數無法精確表示:

std::cout << (0.1 + 0.2 == 0.3) << "\n";  // 輸出:0(也就是 false!)

所以永遠不要用 == 直接比較兩個浮點數,而要檢查它們的差是否小於一個很小的容許誤差(如 1e-9)。

型別轉換:隱式與顯式

當不同型別混在一起運算,C++ 會做隱式轉換(implicit conversion),通常把「小」的型別自動升級成「大」的:

int n = 5;
double result = n / 2.0;   // n 被隱式轉成 double,得 2.5

但你也可以顯式轉換,明確表達意圖。現代 C++ 慣用 static_cast<目標型別>(值)

int a = 7, b = 2;
double exact = static_cast<double>(a) / b;  // 先把 a 變成 7.0,再除
std::cout << exact << "\n";                 // 輸出:3.5

你可能在舊教材看過 C 風格的 (double)a,能動,但 static_cast 更清楚、更安全(編譯器會做更多檢查),是 C++ 推薦寫法。記住一條原則:寧可顯式,不要依賴隱式。當轉換是你刻意要的,就寫出來給讀者看。

動手寫一段:BMI 計算機

把上面的東西串起來。這支程式讀進身高(公尺)與體重(公斤),算出 BMI,並用 const 鎖住判斷門檻。

#include <iostream>

int main() {
    const double NORMAL_UPPER = 24.0;   // 正常範圍上限

    double height, weight;
    std::cout << "請輸入身高(公尺):";
    std::cin >> height;
    std::cout << "請輸入體重(公斤):";
    std::cin >> weight;

    double bmi = weight / (height * height);

    std::cout << "你的 BMI 是:" << bmi << "\n";

    bool overweight = bmi > NORMAL_UPPER;
    std::cout << "是否超過正常上限:" << std::boolalpha << overweight << "\n";

    return 0;
}

假設輸入身高 1.7、體重 70,預期輸出:

請輸入身高(公尺):1.7
請輸入體重(公斤):70
你的 BMI 是:24.2215
是否超過正常上限:true

注意三件事:我用 double 而非 int 來存身高體重(否則 1.7 會被截成 1,整個算錯);用 const 標示門檻是固定值;用 std::boolalphabool 印成 true 而非 1

常見錯誤

初學 C++ 型別系統,這幾個雷幾乎人人踩過一次,先認得它們:

  1. 整數除法吃掉小數int total = 7 / 2; 得到 3。要小數結果,記得讓運算元帶浮點,或用 static_cast<double>。這是最高頻的邏輯錯誤。
  2. 忘了初始化int x; 之後直接使用 x,它的值是未定義的垃圾值(不像 Python/Java 會給預設值,也不像它們會報錯)。養成宣告即初始化的習慣:int x = 0;
  3. char 與字串搞混'A' 是字元、"A" 是字串,不能互換。char c = "A"; 會編譯失敗。
  4. == 比較浮點數:因為精度問題 0.1 + 0.2 != 0.3。改用 std::abs(a - b) < 1e-9
  5. 整數溢位無聲無息int 超過約 21 億會回繞成負數,且不會報錯。處理大數請用 long long

重點回顧

  • C++ 是靜態型別:型別寫在變數宣告裡,編譯器在執行前就檢查完畢。
  • 四大基本型別:intdoublecharbool;整數有大小上限,浮點有精度限制。
  • const 鎖住不可變的值,是編譯期強制的保證,不只是慣例。
  • auto編譯期型別推導,不是動態型別;推導完就定終身。
  • cin >> 讀輸入、cout << 印輸出;型別轉換優先用 static_cast,寧可顯式。

深入探討(研究所視角)

靜態型別的真正價值:把錯誤鎖在編譯期

我們一直說 C++「在執行前就檢查型別」,這句話的份量值得展開。型別檢查可以發生在兩個時間點:編譯期(compile time)或執行期(run time)。Python 把它推遲到執行期——一個型別錯誤的程式碼路徑,只有當程式真的跑到那一行、那一筆資料時才會炸開。如果那條路徑藏在某個罕見的 if 分支裡,你可能上線數週後才在使用者面前出包。

靜態型別把這類錯誤前移到編譯期。std::string s = 42; 這種錯誤不可能逃過編譯,程式根本生不出來。從軟體工程角度,這是一種「左移(shift left)」:缺陷發現得越早,修復成本越低。型別系統在這裡扮演的是一個輕量級、永遠在跑、零執行成本的證明器——它在編譯期證明了「所有對這個變數的使用都符合它的型別」這條性質。

而且這份保證是零執行期成本的。型別資訊在編譯後大多被「抹除」進機器碼,執行時 CPU 不需要再帶著型別標籤到處跑、不需要在每次運算前查表確認型別。這正是 C++「零成本抽象(zero-overhead abstraction)」哲學的一個面向:你享受到型別安全的好處,卻不為它付出執行期的代價。相較之下,動態語言每個值都得在執行期攜帶型別資訊、每次操作都要動態分派(dynamic dispatch),這份彈性是有速度與記憶體代價的。

隱式轉換的陷阱

靜態型別不是萬靈丹。C++ 為了相容 C、為了書寫方便,保留了大量隱式轉換規則,而這些「貼心」的自動轉換正是 bug 的溫床。

考慮無號與有號整數的混用:

unsigned int u = 3;
int s = -1;
if (s < u) {
    std::cout << "s 比較小\n";
} else {
    std::cout << "u 比較小\n";   // 實際印出這行!
}

直覺上 -1 < 3 應該成立,但這裡輸出竟是「u 比較小」。原因是當有號數 s 與無號數 u 比較時,C++ 會把 s 隱式轉成無號數-1 在無號數的世界裡會回繞成一個很大的正數(約 42 億),於是 s < u 變成 false。這類「無號數比較」陷阱在迴圈邊界、容器 .size()(回傳無號型別)時特別常見,是 C++ 老手都會多看兩眼的地方。

另一個經典是窄化轉換造成的精度與資訊流失:把 double 指定給 int 會默默截斷小數,把大的 int 塞進 char 會溢位。編譯器對這些多半只給警告、甚至不吭聲。所以開發時請務必開啟嚴格警告旗標(-Wall -Wextra),讓編譯器把這些可疑轉換攤在你眼前。

窄化(narrowing)與 C++11 的補救

C++11 引入了大括號初始化(brace initialization),順帶提供了一道對付窄化的防線。用 {} 初始化時,禁止窄化轉換——會把資訊流失的隱式轉換從「警告」升級為「編譯錯誤」:

int a = 3.9;     // 合法,但默默截斷成 3(傳統初始化容忍窄化)
int b{3.9};      // 編譯錯誤!大括號初始化禁止窄化轉換
int c{3};        // OK

int b{3.9} 直接編譯失敗,因為 doubleint 會流失小數,屬於窄化。這正是大括號初始化被現代 C++ 推崇的理由之一:它把一類靜默的資料流失錯誤,提升為編譯期就攔得住的硬錯誤,徹底貫徹「錯誤越早發現越好」的精神。許多現代 C++ 風格指南因此建議:優先使用 {} 初始化

把這幾點串起來,你會看見 C++ 型別系統的設計張力:它一方面用靜態型別、const、窄化檢查把大量錯誤鎖在編譯期;另一方面又因歷史包袱保留了隱式轉換這類危險彈性。當一名稱職的 C++ 開發者,意味著你既要善用前者給的保護,也要熟知後者埋的地雷——而這份「知道編譯器何時會替你做主」的意識,正是從寫得出程式到寫得出可靠程式之間的距離。

AI 共讀助教正在陪你讀:C++ 變數、型別與輸入輸出:讓編譯器替你把關
嗨!我是這篇文章的共讀助教,只根據〈C++ 變數、型別與輸入輸出:讓編譯器替你把關〉的內容回答。可以問我「解釋某段」「舉個例子」「出題考我」,或反白文中段落後點下方「解釋選取段落」。