Home
Explore Uedu
Student Console
Register as Member/Login
(2) In future presentations of the research findings, in addition to the course project website and public presentations, your real name and personal information will not appear in this research report. If you are interested in the research results, we can provide you with an executive summary after the study is completed.
問卷中心
Teacher Console
Course Setup
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

0

Local AI

--

CISOSE26 本地 AI UG26
政治大學 AQI 18 29°C

AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

變數、型別與輸入輸出

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++ 變數、型別與輸入輸出:讓編譯器替你把關〉的內容回答。可以問我「解釋某段」「舉個例子」「出題考我」,或反白文中段落後點下方「解釋選取段落」。