C++ 筆記
v0.36.12 - by 晴☆
憧憬
欸對,順便講一下我的目標,我想要一個
使用 JavaScript 開發 256 x 144, 2D 像素遊戲的平台
- 只有 _init()、_update() 和 _draw() 的空腳本,可分頁(像 Pico-8)
- 可用函式的 API 以及視覺化編輯器(像 Scratch 但仍是程式語言)
- 強大的像素繪製工具(像 Blockbench)
- 好用的音樂編輯器
預定要做但還沒做完(暫時寫在這)
- 改基礎講義的順序!!!
- STL 例題
- 遞迴
- 繼承與多型
- 計算機
- 一堆演算法
可用圖片


Slides.com
就是你現在正在看的簡報網站,按 吧


使用方法
右下角的箭頭可以換頁,但簡報頁面的排列方式不是網格狀的,而是像很多串投影片,而它會記住使用者在每一串投影片的垂直位置(預設是最上方),所以先往下滑到底看完一個章節,再往右滑至另一個章節吧!
Title A
Title B
Title C
Title D
A1
B1
C1
D1
A2
C2
為什麼要用
- 線上方便共用
- 編輯介面簡單
- 支援數學嵌入
- 支援程式嵌入
- 很容易分章節
- 看起來就很酷

小提示:
-
也可以用鍵盤的方向鍵換頁
-
按 [Esc] 可以看縮圖
-
回到標題頁可以重置簡報順序
前言
做這份簡報的原因

我是誰
我是晴☆啊
- 市井小民
- 國小都在玩 Scratch
- 國中時用 Lua 寫 Pico-8 遊戲
- 會用 Node.js 跑程式碼
- 從高一開始學 C++
- 不喜歡 Python
製作這份簡報的原因
- 這是我學習 C++ 的筆記
- 可以當作社團成果
- 高二社團若擔任幹部會需要
- 我學習歷程沒東西放了
因為我高一上從無到有一點點學習 C++ 的過程中
覺得 C++ 挺有趣的
於是想記錄一下
這份簡報會以互動教學的角度,呈現我學 C++ 的歷程
備註與謝誌
備註:
- 你好意思嗆我寫的爛?我又沒有收你錢
感謝:
- 劉 三睪 羊原
- 佑佑
- 小海
- 學長們的簡報
- 社團同學們的意見
- 說我寫很爛的 xelento
編輯器
總不能用記事本來寫程式吧


Microsoft Visual Studio
編譯「簡單的輸入輸出程式碼」時可以省略一堆複雜的步驟,而且有自己的專案系統,做大型的專案時也很好用。當然也有另一種作專案的方法,可以用 G++ 自己編譯,但我覺得有點麻煩。

要勾選哪些功能
據我所知,現階段只要這個:

安裝清單應該會長這樣:

使用方法
首先,新增一個專案:

選擇這個模板:

最後,打上名字,按「建立」就完成了!
小提示
現在 VS 應該會是新細明體,很醜
可以在 工具 > 選項 > 環境 > 自型和色彩 自訂你的編輯介面!
字體列表中粗體字代表等寬
推薦試試 consolas 或 fixedsys 喔!
認識 C++
初始專案、編譯器與電腦內部

現在程式會長這樣:
// <檔案名稱>.cpp : 此檔案包含 'main' 函式。程式會於該處開始執行及結束執行。
//
#include <iostream>
int main()
{
std::cout << "Hello World!\n";
}
// 執行程式: Ctrl + F5 或 [偵錯] > [啟動但不偵錯] 功能表
// 偵錯程式: F5 或 [偵錯] > [啟動偵錯] 功能表
// 開始使用的提示:
// 1. 使用 [方案總管] 視窗,新增/管理檔案
// 2. 使用 [Team Explorer] 視窗,連線到原始檔控制
// 3. 使用 [輸出] 視窗,參閱組建輸出與其他訊息
// 4. 使用 [錯誤清單] 視窗,檢視錯誤
// 5. 前往 [專案] > [新增項目],建立新的程式碼檔案,或是前往 [專案] > [新增現有項目],將現有程式碼檔案新增至專案
// 6. 之後要再次開啟此專案時,請前往 [檔案] > [開啟] > [專案],然後選取 .sln 檔案先執行看看
後會看到一個視窗跳出來:

按

解釋
這些是註解,純粹用來告訴別人(或者之後的自己)這裡在幹嘛
// 一行中,兩個斜線後面都是註解
/*
一整區塊的註解
很好用的
*/真正會被執行的程式內容
#include <iostream>
int main()
{
std::cout << "Hello World!\n";
}程式的根本
大多數程序都是用高階語言編寫的
例如 C++、Java 或更高階的 Python
然而,由於電腦只理解數字,因此與計算機交談就像與一個語言不通的人交談
需要翻譯人員才能正確溝通,而這正是編譯器和直譯器所做的
人類語言使人與人之間的交流變得容易
而程式語言則是讓「命令電腦做工作」變得簡單
註:此段是翻譯自這篇文章的第一段
編譯 vs 直譯
編譯器和直譯器的區別,就是翻譯的時機
編譯器會在按下執行按鈕時,把所有程式碼翻譯成一些電腦的指令代碼
而直譯器則是一邊翻譯一邊執行
| 種類 | 編譯式語言 | 直譯式語言 |
| 優點 | 執行起來很快速 | 較容易檢查錯誤及修改 |
| 缺點 | 每次編譯都需要花很多時間 | 執行起來慢了一些 |
如何編譯
C++ 是編譯式語言,也就是說按下

時,系統會把程式碼編譯成
基本上只有電腦才看得懂的「組合語言」,然後執行它。
那什麼是組合語言?
就是一些刻在主機板上的、執行起來非常快速的
只有超強開發者才看得懂的操作指令!
通常都是邏輯運算和記憶體操作

電腦怎麼跑,還是不知道啊
拆開電腦
電腦內部有什麼?
| CPU | 中央處理單位 | 處理一大堆有的沒的雜事,例如算數學 |
| GPU | 圖形處理單位 | 畫圖,例如在螢幕上畫一個正方形 |
| 記憶體 | 暫存資料 | 暫存的資料,例如正在看的網頁資料,關掉就沒了 |
| 硬碟 | 永久資料 | 永久的資料,例如儲存的照片、影片等等 |
| 輸入 | 輸入資料到 CPU | 例如鍵盤、滑鼠等等 |
| 輸出 | 從 CPU 輸出資料 | 例如螢幕、揚聲器等等 |
接下來的程式碼大部分都在 CPU 做運算(基本上無圖形)並暫存資料在記憶體中
Online Judge
寫題目來進步!


網站
線上解題系統可以測試你的程度
分為很多題目,要根據不同輸入作相對應的輸出
一組輸入及輸出稱為測資 (測試資料)
跳過來講一下 Online Judge,常用的有:
怎麼用
-
登入\( / \)註冊
-
選個題目
-
按
-
貼上程式碼
-
看看有沒有通過,如果沒有的話要檢查程式碼是否有誤
-
測試完,按
-
查看結果


結果
| 簡稱 | 全名 | 白話的意思 |
| AC | Accept | 通過 |
| NA | Not Accept | 沒通過,有些是錯的 |
| WA | Wrong Answer | 全錯 |
| TLE | Time Limit Exceeded | 超時,程式太慢了 |
| MLE | Memory Limit Exceeded | 沒有那麼多記憶體可以用 |
| RE | Runtime Error | 執行到一半出現錯誤 |
| CE | Compile Error | 編譯錯誤,請自己先試過再丟上來 |
程式語言
指令、函式庫與輸出

教電腦做事
這是一個簡單的範例:
起床;
吃飯;
睡覺;不過這樣當然不會有效,因為電腦不知道「起床、吃、睡覺」到底是什麼
「起床、吃、睡覺」是指令,在 C++ 中每個指令後面要加上 ; 表示結束
所以接下來要開始講基礎語法了!
#include <iostream>
int main() {
std::cout << "Hello World!\n";
}
先看到第 1 行
意思是:這個專案「包含」了 iostream 這個函式庫
而「函式庫」就是已經寫好,可以直接引用的功能
也就是別人已經寫好「怎麼輸入輸出」,只要使用就好了!
iostream 就是「input/output stream」,中文是「輸入/輸出流」,輸入輸出會用到!
接下來 3 到 5 行是在定義 main()
而 main() 之中就是會被執行的主程式
第 4 行就是「輸出 "Hello World!" 到主控台視窗」
剛才的程式碼
#include <iostream>
int main() {
std::cout << "Hello World!\n";
}
標準函式庫 std
看到第四行,首先什麼是 std::cout 呢? 就是 std 這個「命名空間」裡面的 cout 這個東西
吃(薯條);但電腦可能會以為是「吃肯德基的薯條」,所以要這樣寫:
吃(麥當勞::薯條);想像一下,今天我跟電腦說「吃麥當勞的薯條」
所以「std 裡面的 cout」是什麼? std 是標準函式庫,就是一些常用的模板 cout 的意思就是 console output (主控台輸出)
std::cout << "Hello World!\n";輸出
接下來要講那個 << 了
std::cout << "Hello World!\n";簡單來說它就是把右邊的東西丟到左邊的意思 把 "Hello World!" 丟到 cout 就是你看到的輸出了!

註:它是製作者自己定義的功能,原本的 << 不是這個作用
輸入
答案是:不行
寫 iostream 的人沒幫你做這樣的功能
"Hello World!\n" >> std::cout;那 >> 不就沒有用了?
你可能會好奇,輸出可不可以反過來寫?
當然有,將使用者輸入存入變數!
std::cin >> a;
// 等待使用者輸入,輸入什麼 a 就是什麼[User] ------ input stream ---> [Computer]
[User] <---- output stream ---- [Computer]「流」的概念
(之後會教變數怎麼用)
換行的 '\n'
"Hello World!\n" 最後面的 '\n' 沒有被印出來對吧
因為那是「Escape Sequence (跳脫序列)」,也就是特殊的字元!

最常用的: '\n' 換行 如果你單純想放個反斜線呢? '\\' 打兩個反斜線!
其他基本上不會用到
使用 using
剛才有「Namespace (命名空間)」概念了,但每次都打 std:: 手超痠的!
可以省略:
#include <iostream>
using namespace std;
// 以後沒有指定的話,就是用 std!
int main() {
cout << "Hello World!\n";
}若覺得這樣太籠統,也可以再分細一點:
using std::cin;
using std::cout;
// 以後 cin 和 cout 都是指 std 的!註:若以專案開發來說,反而不要用 using 比較好
題目 zj d483
請輸出「hello, world」 (不含引號)
#include <iostream>
using namespace std;
int main() {
cout << "hello, world\n";
return 0;
}
注意:先自己試跑過再上傳,否則會像我:

if & else
條件

條件
判斷情況,做不同的事情
// 如果……就……否則就……
if (我想睡覺) {
去睡覺;
}
else {
打電動;
}註:以上中文字都是範例,只是比較容易理解,並非正式寫法
// 如果……就……
if (我想睡覺) {
去睡覺;
}省略大括號
程式碼要力求簡潔!
如果大括號裡面只有一行,可以省略
怕會看不懂的話就不要省略
if (餓了) {
吃東西;
}
else {
睡覺;
}
// 可以這樣寫
if (餓了)
吃東西;
else
睡覺;
// 或者這樣也可以
if (餓了) 吃東西;
else 睡覺;順帶一提,這種力求讓程式碼更容易閱讀的方式稱為「排版」
如果不排版的話其實所有程式碼也可以全部擠在同一行,完全看不懂
else if
else if 否則如果……就
它其實是長這樣:
if (...) {
// do something
}
else {
if (...) {
// do something
}
else {
// do something
}
}裡面的 if () {} else {} 算是一行,可省略大括號
if (...) {
// do something
}
else
if (...) {
// do something
}
else {
// do something
}if (...) {
// do something
}
else if (...) {
// do something
}
else {
// do something
}因為太醜了
我們整理一下成為:
布林值
前面提到的條件 也就是放在 if () 裡的 叫做 boolean(布林值) 它其實只有兩種值:
true = 1 (是)
false = 0 (否)if (true) {
// 通過
}
if (1) {
// 通過
}
if (87) {
// 通過
}
if (-1) {
// 通過
}
if (false) {
// 不通過
}
if (0) {
// 不通過
}其實布林值是電腦記憶體的基本元素
像是常常會看到這種照片:
電腦中東西皆以二進位表示
所以其他所有「不是 0」的值
都會被判定為「true」

題目 zj a053
答對題數在 0~10 者,每題給6分。
題數在 11~20 者,從第11題開始,每題給2分。(前10題還是每題給6分)
題數在 21~40 者,從第21題開始,每題給1分。
題數在 40 以上者,一律100分。
變數
存放東西

變數 variable
宣告變數
int a;
// 宣告一個叫做 a 的 int,他的值沒定義,會是亂數。
unsigned int b = 10;
// 宣告一個叫做 b 的 unsigned int,他的值是 10。
char c = '!';
// 宣告一個叫做 c 的 char,他的值是 '!'。bool wow = 0;
// 宣告一個叫做 wow 的 bool,他的值是 false
int a = 0;
// 宣告一個叫做 a 的 int,他的值是 0
a = 999;
// 現在 a 的值是 999 了
int a = 20;
// a 已存在,沒關係,一樣宣告 a 的值為 20
char a = '?';
// a 已經宣告過了,但他不是 char 而是 int,所以會爛掉
const int b = 0;
// 這邊用到了 const,意思是「常數」
b = 123;
// b 已被宣告為常數,不能修改修改變數
宣告型別有:
bool (boolean) 布林值 (是/否)
int (integer) 整數
short 短整數 (範圍只有一半)
long 就是整數 (完全就跟 int 一樣)
long long 超長整數 (範圍 2 倍大)
float 浮點數 (也就是小數)
double 雙倍精細的浮點數
以上如果加上 unsigned (例如 unsigned int)
就只能存正數,而最大值會變成兩倍
char (charater) 字元
……其他變數名稱和正負標記
變數名稱不能有空格,所以一般我們都分成兩種形式,看你喜歡哪個:
int how_much_money = 100; // 底線式
int howMuchMoney = 100; // 駝峰式其實沒有寫 unsigned 的都是 signed (有標記正負的),例如:
int n = -10;
// 完全等同於
signed int n = -10;之後會提到關於 signed 和 unsigned 的原理
上限和下限
變數空間不是無限大的!
| 變數類型 | 別名(不用記) | 位元組 | 範圍 |
| short | short int, __int16, int16_t | 2 | \( -{2}^{15} \leq x < {2}^{15} \) |
| int | long, __int32, int32_t | 4 | \( -{2}^{31} \leq x < {2}^{31} \) |
| long long | long long int, __int64, int64_t | 8 | \( -{2}^{63} \leq x < {2}^{63} \) |
| float | 4 | 範圍詭異, 7 位小數 | |
| double | 8 | 範圍詭異, 15 位小數 | |
| char | __int8, int8_t | 1 | \( -{2}^{7} \leq x < {2}^{7} \) |
註:兩個底線開頭的是非標準寫法(VS 才有)
上限和下限
變數空間不是無限大的!
| 變數類型 | 別名(只列出特殊的,不用記) | 範圍 |
| unsigned short | uint16_t | \( 0 \leq x < {2}^{16} \) |
| unsigned int | uint32_t | \( 0 \leq x < {2}^{32} \) |
| unsigned long long | uint64_t | \( 0 \leq x < {2}^{64} \) |
| unsigned char | uint8_t | \( 0 \leq x < {2}^{8} \) |
註:兩個底線開頭的是非標準寫法(VS 才有)
註:標記成 unsigned 並不會改變占用的空間
字元
字元其實存的是它的編碼,是數字 稱作 Ascii Code 不要背!絕對不要背!完全沒有用! 只要記住 a~z、A~Z、0~9 是排在一起的
單引號 '' 裡面只能放一個字,代表字元 雙引號 "" 則可以放很多個字,代表字串(之後會講) 而字串就是很多個字元的「Array(陣列)」,之後會講到

型別轉換
char c = 'A';
// 前提:c 是 'A'
int(c)
// 65
static_cast<int>(c)
// 65
static_cast<bool>(c)
// true| 型別(變數) | 轉換成某個特定型別,必須要可以轉換 |
| static_cast<型別>(變數) | 同上 |
註:有些型別間不能轉換
但我一時舉不出已經教過的例子
輸入的方法
宣告一個變數後,可以從使用者輸入來改變他的值
int a;
cin >> a;
cout << "let's check the value of a:\n";
cout << a;輸入會以空格和換行作為分隔,例如
int a;
int b;
int c;
int d;
cin >> a;
cin >> b;
cin >> c;
cin >> d;輸入「3 4」的話,a = 3、b = 4
很多變數
很多時候,我們的程式碼中會有非常多東西,例如:
int grade_number_one = 94;
int grade_number_two = 87;
int grade_number_three = 96;
int grade_number_four = 79;但這樣建立一堆變數,顯然不是一個好的做法
這時要使用陣列 & 標準模板庫 STL 的好用模板
STL 也是標準函式庫底下的東西,有 vector, string, queue, stack 等等
之後會提到!
運算
聽起來就像是數學

運算子 (1)
| 運算子 | 意思 | 範例 |
| + | 加 | 2 + 3 = 5 |
| - | 減 | 13 - 4 = 9 |
| * | 乘 | 3 * 7 = 21 |
| / | 除 | 8 / 4 = 2 |
| % | 取餘 | 27 % 8 = 3 |
| && | 而且 | true && false = false |
| || | 或者 | true || false = true |
| ! | 不 | !true = false |
範例
數字運算
int a = 1 + 3;
// a 是 4
int b = 12 + 8 / 4;
// b 是 14
int c = b - a;
// c 是 10
int d = b / a;
// 其實 b / a 是 3.5 但 d 被宣告為 int,
// d 會無條件捨去變成 3
float e = b / a;
// e 宣告為 float,是 3.5
邏輯運算
bool buy = false;
bool eat = true;
bool happy = buy || eat;
// eat 或者 buy,結果是 true
bool veryHappy = buy && eat;
// eat 而且 buy,結果是 false
// 還記得嗎,條件就是布林值
if (veryHappy) {
}縮短
int a = 1;
// a 是 1
a = a + 3;
// a 是 4
// 不覺得「a = a +」很長嗎
int b = 1;
// b 是 1
b += 3
// b 是 4
// 變短了!int a = 5;
// a 是 5
a += 1;
// a 是 6
// 不覺得「+= 1」如果常用的話,會懶得打嗎
int b = 5;
// b 是 5
b++
// b 是 6
// 變短了!有「+=」、「-=」、「*=」、「/=」等等
有「++」和「--」兩種
int i, a;
i = 10;
a = ++i;
// i 先自己 +1,再把 a 賦值為 i
// 結果:a 是 11, i 也是 11關於 ++ 和 --
其實它們可以寫在前面
但意思不太一樣
int i, a;
i = 10;
a = i++;
// 先把 a 賦值為 i,再把 i 自己 +1
// 結果:a 是 10, i 是 11運算子 (2)
| 運算子 | 意思 | 範例 | 結果 |
| == | 等於 | 3 == 4 | false |
| > | 大於 | 3 > 2 | true |
| < | 小於 | 4 < 2 | false |
| >= | 大於等於 | 3 >= 3 | true |
| <= | 小於等於 | 2 <= 9 | true |
| () | 優先運算 | (2 + 1) * 2 | 6 |
| != | 不等於 | 5 != 8 | true |
注意 = 和 == 不一樣(很容易弄錯)
// 一個等於代表宣告他「左邊就是右邊」
// 也就是把右邊的值複製到左邊
int a = 3;
int b = 3;
int c = 5;
// 兩個等於代表詢問「是相等的嗎?」
if (a == b) {
// 通過,這裡會被執行
}
if (a == c) {
// 不通過,這裡不會被執行
}括號
有的時候會出現一些很難看懂的東西,建議加上括號
bool isEqual = a == b;
// 看不太懂
bool isEqual = (a == b);
// 變數 isEqual 代表 a 是否等於 bbool test = a >= b && c < d && e != 0;
// 看不太懂
bool test = ( (a >= b) && (c < d) && (e != 0) );
// 比較看得懂了吧,test 有三個條件想知道運算元的優先順序可以看這裡
題目 zj a002
輸入兩個整數 a 和 b,輸出 a + b 的值
右邊是 AC Code
不過還可以更精簡
#include <iostream>
// 引入函式庫
using namespace std;
// 使用 std:: 命名空間
int main() {
int a;
int b;
// 宣告 a 和 b 為 int
cin >> a;
cin >> b;
// 輸入
long long ans = a + b;
cout << ans;
// 輸出
return 0;
// 雖然 main() 不 return 也行,但最好加一下
}注意事項:
| a、b 範圍 | \( 10^6 < 2^{31} \) 用 int 即可 |
| a + b 範圍 | \( |a + b| \) 可能會超過 \( 2^{31} \) 得用 long long 才行 |
更簡潔的方式
省略重複的步驟?
一次宣告很多變數
#include <iostream>
// 引入函式庫
using namespace std;
// 使用 std:: 命名空間
int main() {
int a, b;
// 宣告 a 和 b 為 int
cin >> a >> b;
// 輸入
long long ans = a + b;
cout << ans;
// 輸出
return 0;
// 雖然 main() 不 return 也行,但最好加一下
// 之後我都不會加,因為空間不夠了
}int a = 0, b = 1, c = 2;
int d, e, f;一次輸入很多變數
cin >> a >> b >> c;為什麼輸入輸出的部分可以這樣寫?
輸入輸出運算子的原理
// 這是以 cin 為例,cout 則是反過來
cin >> a >> b;
(cin >> a) >> b;
// 電腦會先看到「cin >> a」於是先執行
(cin) >> b
// 把使用者打的內容輸入進 a 以後,「cin >> a」會回傳一個「cin」
cin >> b
// 於是就會變成這樣
// 接著輸入 b 以後,又會回傳 cin...
cin
// 沒東西了,結束超級快的 2 進位運算
位元運算

前情提要
這裡講得很難,只需要知道概念就好了。如果應付不來,可以跳過沒關係
另一方面,想知道更多的話可以點這裡
二進位概念:
\(0 + 1 = 1\)
\(1 + 1 = 「2」,進位變成 10\)
\(10 + 1 = 11\)
\(11 + 1 = 100\)
進位制轉換
十進位轉二進位:用二的次方取餘數,以 75 為例:
75 = 64 + 11
11 = 8 + 3
3 = 2 + 1
1 = 1
| 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 | 1 | 0 | 1 | 0 |
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
| 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1 |
二進位轉十進位:每一位乘上 \( 2^n \) 再加起來,
| 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1 |
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
= 64 + 8 + 2 + 1 = 75
以 1001011 為例:
記憶體中的存放方式
剛才講變數的上限和下限時,都寫 \( a \leq x < b \)
為什麼一邊是 \( \leq \) 而另一邊是 \( < \) 呢?
電腦中有一些空間用來存放變數值,假設是像這樣:
| 0 | 0 | 0 | 0 | 0 |
|---|
這樣是 \( 0 \)
| 0 | 0 | 4 | 0 | 1 |
|---|
這樣是 \( 401 \)
| 9 | 9 | 9 | 9 | 9 |
|---|
這樣是 \( 99999,也就是 10^5 - 1 \)
\( 99999 \) 再 + \( 1 \) 呢?
| 0 | 0 | 0 | 0 | 0 |
|---|
爆掉,變回 \( 0 \)
也就是說,「5 格」能存的數字為 \( 0 \leq x < {10}^{5} \)
但剛才是「假設」他這樣存,因為實際上電腦中是 2 進位的:
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
|---|
這樣是 \( 1 \)
| 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
|---|
這樣是 \( 13 \)
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|---|
這樣是 \( 255,也就是 2^8 - 1 \)
\( 11111111 \) 再 + \( 1 \) 呢?
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
|---|
爆掉,變回 \( 0 \)
每一格是一個 Bit (位元),每 8 個 Bit 稱為一個 Byte (位元組)
所以 8 位元 ( 1 位元組) 能存的數字為 \( 0 \leq x < {2}^{8} \)
| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|
可是剛才沒有講到負數
接下來要講 signed!
「signed」就是「標記正負」,會使用最左邊的 1 個位元來標記這個數的正負
| 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
這樣還是 \( 13 \)
| 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
這樣是 \( 127,也就是 2^7 - 1 \)
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
這樣是 \( -128 \),最左邊的位元為 1,代表是負的
\( 01111111 \) 再 + \( 1 \) 呢?
| -128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
(跟 unsigned 一樣,但注意最左邊的位元是 \( -128\) )
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
再 + \( 1 \) 是 \( -127 \)
表示二進位
使用 0b 前綴表示二進位數字,例如:
cout << 0b1010;
// 8 + 2 = 10但是 int 的輸出自動是 10 進位的
如果想要看二進位輸出就要用 bitset:
#include <iostream>
#include <bitset>
using namespace std;
int main() {
bitset<8> s = 75;
// 8 位元的 bitset
cout << s;
// 01001011
}
NOT
直接看範例!
bitset<8> a = 0b00000101;
cout << " A - " << a << "\n";
cout << " ~A - " << ~a << "\n"; A - 00110101
~A - 11001010就是把每個位元都變成相反
對記憶體中的每個位元進行邏輯運算就是位元運算的根本
兩個東西比對的邏輯
在看程式之前,先看看示意圖

AND [&]
OR [|]

XOR [^]
兩個都有
任一個是
只有其中一個是
0 & 0 = 0 1 & 0 = 0 0 & 1 = 0 1 & 1 = 1
0 & 0 = 0 1 & 0 = 1 0 & 1 = 1 1 & 1 = 1
0 ^ 0 = 0 1 ^ 0 = 1 0 ^ 1 = 1 1 ^ 1 = 0
AND OR XOR
把兩個物件(位元數必須相同!)每個位元拿去做邏輯比對
#include <iostream>
#include <bitset>
using namespace std;
int main() {
bitset<8> a = 0b11110001;
bitset<8> b = 0b01011011;
bitset<8> f = a & b;
cout << " A - " << a << "\n";
cout << " B - " << b << "\n";
cout << "----------------" << "\n";
cout << "A & B - " << f << "\n";
}
A - 11110001
B - 01011011
----------------
A & B - 01010001用途
寫「x & 1」可以判別基偶數 因為與 1 做 AND 就是「最後一位是不是 1」的意思
| 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| -128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
|---|
x:
1:
&
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
true
左移 & 右移
在使用 cout 時會打的「<<」其實就是左移的意思
看看例子就懂了:
int main() {
bitset<8> a = 0b00000101;
cout << " A - " << a << "\n";
cout << " A << 1 - " << (a << 1) << "\n";
cout << " A << 2 - " << (a << 2) << "\n";
cout << " A << 3 - " << (a << 3) << "\n";
cout << " A << 4 - " << (a << 4) << "\n";
} A - 00000101
A << 1 - 00001010
A << 2 - 00010100
A << 3 - 00101000
A << 4 - 01010000左移就是乘以 0b10 的意思,也就是 \(\times2\),相對的,右移則是 \(\div2\)
神之編譯器
講了那麼多,其實如同我一開始所說的,知道概念就好了,不會學也沒差!
因為 C++ 編譯器會自動找到最快的方法,像是把 \(\times2\) 轉換成 << \(1\) 等等
所以,程式中完全不需要寫出位元運算!
迴圈
簡化重複的事。while & for loop, break & continue

while 迴圈
寫法是這樣:
// while 迴圈:當……的時候就……
while (生氣) {
吃東西;
打電動;
}註:以上中文字都是範例,只是比較容易理解,並非正式寫法
所以當生氣時
就會一直吃東西、打電動、吃東西、打電動……
直到不生氣為止
break
註:以上中文字都是範例,只是比較容易理解,並非正式寫法
while (生氣) {
吃東西;
if (吃太飽) {
break;
// 跳脫出迴圈
}
}
// 跳脫到這裡迴圈可以用 break 跳脫出來
但只會跳脫一層
// while 迴圈:當……的時候就……
while (想吃壽司) {
走進迴轉壽司;
while (還沒飽) {
拿壽司來吃;
if (有點太貴了) {
break;
// 這裡會跳脫
}
}
// 會跳脫到這裡
休息一下;
}for 迴圈
模組化的 while 迴圈
int i = 1;
while (i <= 20) {
cout << i << " ";
i++;
}
// 輸出 1 2 3 ... 18 19 20// For 迴圈:從……開始,當……的時候,每次做……事
for (int i = 1; i <= 20; ++i) {
cout << i << " ";
}想像一個情況:
使用 for 迴圈!
continue
for (int i = 1; i <= 20; ++i) {
// 如果是 3 的倍數就跳過
if (i % 3 == 0) {
continue;
}
cout << i << " ";;
}
// 跳脫到這裡迴圈可以用 continue 跳過一次
題目 zj a147
輸入一個整數 \(n (n \leq 10000\))
若 n = 0 表示資料結束
輸出所有符合下列條件的數字
- 大於 0
- 整數
- 不可以被 7 整除
- 小於 n
注意事項:
| n 範圍 | \( 10^5 < 2^{31} \), 用 int 即可 |
// 從……開始,當……的時候,每次做……事
for (int i = 1; i < n; ++i) {
// 如果 i 除以 7 的餘數不是 0
if (i % 7 != 0)
cout << i << " ";
}// 完整程式碼
#include <iostream>
using namespace std;
int main() {
for (int i = 1; i < n; ++i) {
if (i % 7 != 0)
cout << i << " ";
}
cout << "\n"
}可以使用 for 迴圈:
優化
判斷餘數 (假設是 3)
if (i % 3 != 0) {}看看 i % 3 的可能性
| 0 | 1 | 2 |
解析為布林值
if (i % 3) {}| false | true | true |

所以可以寫成
順便講一下 IO 優化
在文字被輸出前,會先到一個「緩衝區」
等到特定時間點才會一次輸出,這樣比較快
而 cin 會強制將緩衝區的資料釋放,才不會把使用者輸入的字跟輸出的字搞混
但這在題目並不需要,多一步驟反而會佔時間
所以我們可以在 main() 的一開始就取消它:
ios_base::sync_with_stdio(false);
cin.tie(0);建議在上傳程式碼前再加上去,否則自己會很困擾
函數
傳入傳出

函數 function
定義一個函數
根據傳入的數值,回傳出結果
// 這樣寫
int myFunction(int a, int b) {
int answer = a + b;
return answer;
}
// 解釋
回傳型別 函數名字(型別1 傳入物件1, 型別2 傳入物件2……) {
// 要做的事情
return 要回傳的值;
}呼叫一個已經定義好的函數
傳入一些數值,就會回傳出結果
int a = myFunction(20, 30);
// 宣告 a 為 myFunction(20, 30) 的結果,也就是 50
int b;
b = myFunction(20, 30);
// 這樣可以
int c = myFunction(30, 40) + myFunction(80, 90);
// 這樣也可以注意:函數需要先被宣告,後面的程式碼才能呼叫它!
預設值
傳入的值可以有預設值,會自動填滿
#include <iostream>
using namespace std;
int myFunction(int a = 10, int b = 0) {
int answer = a + b;
return answer;
}
int main() {
cout << myFunction(3, 4) << " "
<< myFunction(3) << " "
<< myFunction() << "\n";
}7 3 10若沒傳入 a 則 a = 10,若沒傳入 b 則 b = 10
void 和 ()
函數不一定要傳入值,也不一定要回傳值
可以用在簡略重複的事情
// 使用 void (虛空) 表示不回傳值,括號內留空表示不傳入值
void sunday() {
if (goToCramSchool) {
happyValue -= 10;
}
else {
happyValue += 5;
}
}
//別的地方可以呼叫 sunday
sunday()改良 if
三原運算子、標籤與跳躍、switch & case

三原運算子
// 概念
條件 ? 如果通過的話 : 如果沒有的話// 打招呼的情境
cout << (isCloseFriend ? "yo! bro!" : "hello!");程式碼要力求簡潔
所以如果只有一個 else 可以寫成三原運算子
a ? b : c
// 想像它是一個函數
if (a) {
return b;
}
else {
return c;
}標籤 & 跳躍
有概念就可以了,絕對不要這樣寫!
這樣寫沒有任何好處
而且會讓程式碼超混亂
所以概念是什麼?
標記一個位置,可以跳躍到那裡
#include <iostream>
using namespace std;
int main() {
int num;
start:
cout << "here is the start!\n";
middle:
cout << "here is the middle!\n";
cin >> num;
if (num == 1) {
goto start;
}
else if (num == 2) {
goto middle;
}
}// 標記程式碼的這個地方
標籤:
// 回到標籤的位置繼續執行
goto 標籤;switch
switch (東西) {
case 第一種情況:
// do something
break;
case 第二種情況:
// do something
break;
// 不一定要有 default
default:
// do something
break;
}int num;
cin >> num;
switch (num) {
case 1:
cout << "one!\n";
break;
case 2:
cout << "two!\n";
break;
case 3:
cout << "three!\n";
break;
default:
cout << "idk...\n";
break;
}程式碼要力求簡潔
所以如果有很多的 else if
可以寫成 switch & case
舉個例子:
case & default
仔細看,case 沒有換行,而且每個 case 都有 break!
因為它是標記,也就是說
電腦看到 switch 以後,把 () 裡面的東西拿去比較
跳躍相對應的 case 位置,開始執行,直到有 break 才跳脫出 switch
這樣設計是有它的原理:
-
可以不同情況做同樣的事
-
可以分成多組
(雖然很少會出現這些情況)
switch (num) {
case 1:
case 4:
case 7:
cout << "good!\n";
break;
default:
cout << "i don't like it...\n";
break;
}題目 zj a058
輸入一個正整數 n
代表接下來有 n 個數
輸出接下來 n 個數中
\( 3k, 3k+1, 3k+2 (k \in \N ) \)
各有幾個
#include<iostream>
using namespace std;
int main() {
int n;
cin >> n;
int zero = 0, one = 0, two = 0;
int k;
for (int i = 0; i < n; ++i) {
cin >> k;
switch (k % 3) {
case 0:
zero += 1;
break;
case 1:
one += 1;
break;
case 2:
two += 1;
break;
}
}
cout << zero << " " << one << " " << two << "\n";
}簡單題,右邊是 AC Code
如何判斷?
使用 switch & case!
排版
超級重要

排版技巧
排版不會影響程式執行,但會使程式碼比較容易閱讀。
排版並沒有絕對的好壞,也會依照不同人的習慣而改變
但最根本的規則就是要「全部一致」。
// 壞的排版示範
#include <iostream>
int main()
{
int a= 0;
if (a == 0) {std::cout
<< "hello";
};return 0;
}// 好的排版示範
#include <iostream>
int main() {
int a = 0;
if (a == 0) {
std::cout << "hello";
}
return 0;
}晴☆的排版方式
有些人的大括號 {} 前會換行,也可以
再次重申,排版會依照個人習慣而改變
#include <iostream>
using namespace std;
int main() {
int a = 0;
if (a != 0) {
cout << "wow! the value of a is "
<< a << " and it's so cool!";
}
return 0;
}- 小括號和大括號中間空一格
- 運算子前後空一格
- 不同層的程式碼以四個空格作為縮排
- 能清楚判斷對應的括號在哪
- 不同段落間空一行
- 一行寫不下的東西,換行再加一層縮排
(相似的東西盡量對齊)
array & std::vector
陣列們

陣列 array
| 8 | 2 | 3 | 5 |
內容物型別 名稱[大小] = { 內容物…… };
int arr[4] = { 8, 2, 3, 5 };[0]
[1]
[2]
[3]
編號(索引值)是「和第一個東西的距離」!
First Item
↓
電腦記憶體中的樣子
把很多東西排成一排儲存,編上編號,寫法是:
注意:陣列大小只能放常數,不能放變數
Size
↓
\( 0 < 編號 \leq \) size
也可以用不初始化的宣告:
內容物型別 名稱[大小];
int arr[4];自動判定大小:
取得某一項的值:
int arr[4] = { 8, 3, 4, -5 };
int n = arr[2];
// n 的值設為 arr 的第 2 項,也就是 4int arr[] = {0, 1, 2};
// 大小自動填入 3 了可變陣列 std::vector
雖然陣列是一個很基本的東西,但它很難用,完全可以被 std::ector 取代
其實 Vector 在數學中是「向量」,但在 C++ 中卻是改良版陣列
改良版陣列跟向量的關係是?沒有關係,純粹同名。
#include <vector>記得引用 vector 函式庫
vector<內容物型別> 名稱 = { 內容物…… };
vector<int> v = { 8, 2, 3, 5 };不用指定大小!
也可以用不初始化的宣告:
vector<內容物型別> 名稱;
vector<int> v;全部初始化成同一值:
vector<內容物型別> 名稱(大小, 初始值);
vector<int> v(100, 0);
// 0 ~ 99 項都是 0「取得某一項的值」與陣列的方法一樣:
vector<int> vec = { 8, 3, 4, -5 };
int n = vec[2];
// n 的值設為 vec 的第 2 項,也就是 4大小怎麼辦
既然 vector 不用指定大小,因為它能屈能伸、可大可小!
vector<int> vec = { 8, 6, 4 };
vec.push_back(2);
// vec 尾端新增「2」,變成 { 8, 6, 4, 2 }
cout << vec.size();
// 輸出現在 vec 的大小,為 4
vec.pop_back();
// vec 尾端刪除一項,變成 { 8, 6, 4 }
for (int i = 0; i < vec.size(); ++i) {
cout << vec[i] << " ";
}
// 輸出 vec 的每一項,以空格分隔
關於方法
為什麼是 vec.push_back(n) 而不是 push_back(vec, n) 呢?
因為 push_back(n) 的結果只會關係到 vector 自己,是 vector 的方法
std::vector::size() 是一個函數,如果把它丟在 For 迴圈「當……時」那一格
它就會每執行一次都呼叫一次,等待回傳值,會有點慢!
for (int i = 0, s = vec.size(); i < s; ++i) {
cout << vec[i];
}
// 把 s 在「從……開始」那一格宣告成 vec 的 size,之後只要讀變數 s 的值,就會比較快!(之後講到 struct 和 class 時會詳細說明)
char[] & std::string
字串們

字串
| 'a' | 'p' | 'p' | 'l' | 'e' | '\0' |
char 名稱[大小] = "字串……";
char name[6] = "apple";[0]
[1]
[2]
[3]
First Item
↓
其實就是字元的陣列,寫法是:
Length
↓
[4]
我知道 "apple" 是五個字,但最後面會有一個隱形的 '\0' 代表字串結束
所以它的字串長度理論上是 5 但 size 卻是 6!
Size
↓
[5]
改良字串 std::string
雖然字串是一個很基本的東西,但它很詭異,所以我們用 std::string 取代它
其實 String 在英文中本來就是「字串」的意思!
#include <string>記得引用 string 函式庫
string 名稱 = { 內容物…… };
string favoriteFruit = "apple";不用指定大小!
註:有興趣的話,可以查一下 string 與 vector<char> 的差別
同樣可自由伸縮
string str = "apple";
str.push_back('!');
// str 尾端新增「!」,變成 "apple!"
cout << str.length();
// 輸出現在 str 的長度,為 6
str.pop_back();
// str 尾端刪除一項,變成 "apple"
str.insert(3, "...");
// str 從第 4 項插入「...」,變成 "app...le"
cout << str;
// 輸出 strqueue, deque & priority_queue (std)
隊列們

(+ #define)
隊列 std::queue

先進先出,沒有索引號
可以想像是在排隊
試試看
#include <queue>別忘了引用 queue 函式庫
queue<int> q;
// 一個嶄新的 queue
q.push(1);
// 尾端加入 1
q.push(2);
// 尾端加入 2 變成 1 2
q.push(3);
// 加入 3 變成 1 2 3
cout << q.front() << "\n";
// 輸出最前面,是 1
cout << q.back() << "\n";
// 輸出最後面,是 3
q.pop();
// 前端刪除一項,變成 2 3
cout << q.front() << "\n";
// 現在最前面的是 2// 把東西都刪掉
while (q.size() > 0) {
q.pop()
}
// 或者可以寫
while (!q.empty()) {}
// 或者這樣,因為不是 0 都會被判定為 true
while (q.size()) {}std::deque
其實是雙頭的 queue,沒什麼好解釋的:

看 VS 的提示:


優先隊列 std::priority_queue
顧名思義,是有優先順序、會自己排序的 queue 啦
priority_queue<int> q;
// 一個嶄新的 PQ
q.push(3);
q.push(5);
q.push(4);
cout << q.top();
// 排序後取得最優先的(這裡是最大的)值,是 5(但是它其實是 heap)
排序方式
可以變更排序方式!
priority_queue<內容物型別, 儲存方式, 比較方式>
priority_queue<int, vector<int>, less<int>>
// 由大排到小,top() 是最大的
priority_queue<int, vector<int>, greator<int>>
// 由小排到大,top() 是最小的另外也可以自訂排序方式,但是有點進階又很少用
想知道的可參考這篇文章
縮短名字的方法
template<typename T, typename C = vector<T>, typename P = less<T>>
using pq = priority_queue<T, C, P>;#define pq priority_queue用 #define A B 的意思就是用 A 取代程式碼中所有的 B
更好的方式是「template + using」,但是這種寫法有點進階,之後才會教
std::stack
堆疊

堆疊 std::stack
先進先出,沒有索引號
可以想像是在疊一疊書

使用方法
#include <stack>嗯,記得引用 stack 函式庫
stack<int> stk;
// 一個嶄新的 stack
stk.push(1);
stk.push(2);
stk.push(3);
// 現在是 1 2 3
cout << stk.top();
// 最上面是 3
stk.pop();
cout << stk.top();
// 最上面是 2std::set
集合

哭啊 SET
想寫的東西越來越多了
std::map
關聯式容器

哭啊 MAP
想寫的東西越來越多了
多維陣列
陣列裡有陣列

(+ 中斷點 & EOF)
兩層 for 迴圈
我們看看這個例子:
for (int i = 1; i <= 3; ++i) {
// 注意這裡不能再用 j 了
for (int j = 1; j <= 4; ++j) {
// i-j
cout << i << "-" << j << " ";
}
cout << "\n";
}1-1 1-2 1-3 1-4
2-1 2-2 2-3 2-4
3-1 3-2 3-3 3-4外面的 for 迴圈是每一行
裡面的 for 迴圈是每一行中的每一項
存進陣列
又……燒雞了……
題目 zj a015
輸入一個矩陣的行數、列數與內容
輸出「行與列相反」的矩陣
| 3 | 1 | 2 |
| 8 | 5 | 4 |
輸入:
2 3 (行數 列數)
| 3 | 8 |
| 1 | 5 |
| 2 | 4 |
輸出:
註:還是別忘了測資範圍啊!
如何儲存矩陣?
多維的陣列!
也就是陣列裡有陣列!
|
|
|---|
|
|
| 3 | 1 | 2 |
| 8 | 5 | 4 |
(原本題目給的矩陣歪掉了,哈哈)
輸入矩陣
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 幾行、幾列
int numRows, numColumns;
cin >> numRows >> numColumns;
// 暫時宣告一個空的行
vector<int> tempRow(numColumns, 0);
// 是 vector 的 vector,並將每行填入剛才的空行
vector<vector<int>> vec(numRows, tempRow);
for (int r = 0; r < numRows; ++r) {
for (int c = 0; c < numColumns; ++c) {
// 輸入內容
cin >> vec[r][c];
}
}
return 0;
}輸入順序會長這樣:
1 2
3 4 5 6 7
8 9 a b c
d e f g h
i j k l m所以我們的 for 迴圈要設定好:
for (/* 跑過每一行,要放行數 */) {
for (/* 跑過一行中的每一項,要放列數 */) {
// 此時輸入
// 順序就會是正確的
}
}中斷點
如何知道輸入是不是正確的?
暫停程式,看變數的值!
在 VS 中,把滑鼠移動到剛才的 Code 第 23 行的數字左邊
就會有一個圈圈,點下去以後會變成紅色的
此時執行程式,就會在 23 行停下來!

查看變數的值
既然程式暫停了
我們可以看看程式碼有沒有問題
這種行為叫做 debug
回到 VS,左下角應該有:

點開 vec 即可看到裡面有什麼!

可以看到 vec[0] (第一行)是 { 3, 1, 2 }
(請忽略那個 [capacity] 和 [allocator],那是內建的複雜東西)
輸出矩陣
再宣告一個旋轉後的矩陣然後輸出?
那樣很佔空間又很慢!
其實只要輸出時改順序就好了
// 輸出!注意,這邊是「列在外、行在內」
// 把每直列當作每行
for (int c = 0; c < numColumns; ++c) {
// 輸出原本直列中的每一項
for (int r = 0; r < numRows; ++r) {
cout << vec[r][c] << " ";
}
cout << "\n";
}#include <iostream>
#include <vector>
using namespace std;
int main() {
int numRows, numColumns;
cin >> numRows >> numColumns;
vector<int> tempRow(numColumns, 0);
vector<vector<int>> vec(numRows, tempRow);
for (int r = 0; r < numRows; ++r) {
for (int c = 0; c < numColumns; ++c) {
cin >> vec[r][c];
}
}
for (int c = 0; c < numColumns; ++c) {
for (int r = 0; r < numRows; ++r) {
cout << vec[r][c] << " ";
}
cout << "\n";
}
}右邊是我的 Code,但它不是 AC 解!
EOF
這題有很多矩陣在同一組測資
如何判斷結束?
那什麼是 EOF?
就是未完成的輸入
在 windows 系統中
就是按 Ctrl + Z 然後按 Enter
(應該會顯示 "^Z" )
寫法:
int num;
// 比較推薦的寫法
// 不會有問題,而且可以用 getline (之後會教)
while (true) {
cin >> num;
if (cin.eof()) break;
// do something
}
// 也可以這樣寫
// 因為 cin 如果出現 eof 會回傳 0
while (cin >> num) {
// do something
}許多題目以輸入 0 表示結束
但這題是到 EOF 為止!
正解
#include <iostream>
#include <vector>
using namespace std;
int main() {
while (true) {
int numRows, numColumns;
cin >> numRows >> numColumns;
if (cin.eof()) break;
vector<int> tempRow(numColumns, 0);
vector<vector<int>> vec(numRows, tempRow);
for (int r = 0; r < numRows; ++r) {
for (int c = 0; c < numColumns; ++c) {
cin >> vec[r][c];
}
}
for (int c = 0; c < numColumns; ++c) {
for (int r = 0; r < numRows; ++r) {
cout << vec[r][c] << " ";
}
cout << "\n";
}
}
}這邊還要加題目!
晴☆燒雞啦!
遞迴
stack overflow 出現了

工作堆疊 work stack
電腦需要處理的事情會是一個 stack,裡層先處理
#include <iostream>
using std::cout;
void b() {
cout << "-----> B start & end\n";
}
void a() {
cout << "--> A start\n";
b();
cout << "--> A end\n";
}
int main() {
cout << "MAIN start\n";
a();
cout << "MAIN end\n";
}MAIN start
--> A start
-----> B start & end
--> A end
MAIN endmain()
a()
b()
遞迴概念
#include <iostream>
#include <string>
using namespace std;
void test(int num) {
cout << num << " ";
test(num - 1);
}
int main() {
test(30);
}函數呼叫自己!
上方的程式碼中,test(num) 會輸出 num 並呼叫 test(num - 1)
執行看看就會發現它輸出 30 29 28…… 到 -3000 多的時候就出錯了!
堆疊溢位 stack overflow
因為不斷呼叫自己就會一直堆疊,最終超過限制,堆疊溢位了!
需要最終有一層停止呼叫自己才能停止!
main()
test(30)
test(29)
test(28)
#include <iostream>
#include <string>
using namespace std;
void test(int num) {
cout << num << " ";
if (num <= 0) return;
test(num - 1);
}
int main() {
test(30);
// 30 29 28 ... 2 1 0
}程式論壇 stack overflow
其實有一個網站就叫做 stack overflow 欸
它是一個有非常多資訊的程式論壇
可以在上面問問題,或者搜尋別人問過的問題
如果有關於程式的問題,找 stack overflow 就對了!

(它的 icon 很有 stack overflow 的感覺吧!)
題目 zj e357
喔
指著一個變數
記憶體位置、指標、變數範圍與參考

變數的位置
用 & 取得變數存放的位置:
int test;
cout << &test << "\n";
// 記憶體位置每次分配不會一樣000000DB38EFF8F4我跑了一次程式碼,得到:
跑過陣列試試看:
我再跑一次程式碼,得到:
000000C65A10FB24int arr[10];
for (int i = 0; i < 10; ++i)
cout << &arr[i] << "\n";00000046FBF1FBB8
00000046FBF1FBBC
00000046FBF1FBC0
00000046FBF1FBC4
00000046FBF1FBC8
00000046FBF1FBCC
00000046FBF1FBD0
00000046FBF1FBD4
00000046FBF1FBD8
00000046FBF1FBDC排在一起,每項 4 位元組:
000000279D8FFBF4我跑第三次程式碼,得到:
進位方式
剛才那串看似數字,但又混入英文字母的是什麼呢?
是 16 進位!
由 0 ~ f 組成,所以 9 + 1 會是 a,f + 1 才是 10
以 0x 作為前綴表示 16 進位數字:
int n = 0xd;
// n 在 16 進位中是 d
cout << n;
// 以 10 進位輸出,是 13之前提過二進位是以 0b 作為前綴:
int n = 0b11;
// n 在 2 進位中是 11
cout << n;
// 以 10 進位輸出,是 3指標 pointer
指標簡單來講就是:指著一個變數
address of n: 00000048C9EFF764
value of p: 00000048C9EFF764int n = 3;
// 一個變數 n ,它的值是 3
int* p = &n;
// 一個指著 n 的指標 p,它的值是「n 的位置」
cout << "address of n: " << &n << "\n"
<< " value of p: " << p << "\n";結果:
更動 *p,就是更動 n!
int n = 3;
// 一個變數 n ,它的值是 3
int* p = &n;
// 一個指著 n 的指標 p,它的值是「n 的位置」
cout << "before: " << n << "\n";
*p += 1;
cout << " after: " << n << "\n";結果:
before: 3
after: 4參考 reference
表示 a 是參考 b 的變數
int& a = b使用
int a = 77;
int& ref_a = a;
cout << ref_a << "\n";
// 77
ref_a = 20;
cout << a << "\n";
// 20也就是說,a 完全就是 b!
註:參考一定要有初始值
名字、值 & 址
| 名字 (name) | 我們可以將變數命名,讓我們比較好理解 |
| 值 (value) | 存放的「內容」,也是最重要的部分 |
| 址 (address) | 存放的「位置」,是變數的首位位元組的編號 |
你可能會好奇,變數型態需要儲存嗎?
答案是不用。
因為在編譯時轉換成組合語言,可以想像它什麼都不知道,只是執行命令操作記憶體,所以我們必須確保程式不會出錯,否則就沒救了!
複雜的型別
| int | 32 位元整數 |
| int* | 指向 int 的指標 |
| int& | 指向 int 的參考 |
需要注意的是: 「int*」是一種型別,代表指向 int 的指標;「*x」是取得指標 x 指向的變數 「int&」是一種型別,代表指向 int 的參考;「&x」是取得變數 x 的址
很容易搞混!
來製作 swap 吧
假設今天你需要把兩個變數的值互換
int a = 10, b = 3;
int temp = a;
a = b;
b = temp;
那我們寫成函數?
#include <iostream>
using namespace std;
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
return;
}
int main() {
int x = 4, y = 1;
swap(x, y);
// 呼叫 swap,執行 swap 裡面的程式碼
cout << "x: " << x << "\n"
<< "y: " << y << "\n";
}原理就是:
- 把 a 存到「暫存變數」
- 把 a 變成 b 的值
- 把 b 變成「暫存變數」的值,也就是 a 原本的值
試試看,就會發現它沒有用!
變數範圍 scope
為什麼剛才的 swap 函數沒有用呢?
這就要談到變數的作用範圍了
在把 x 和 y 傳入時,就像這樣:
int a = x, b = y;
int temp = a;
a = b;
b = temp;所以 a 和 b 是存在另外的位置,改變 a 和 b 不會連動到 x 和 y!
main()
swap()
傳入值
外面
回傳值
x y
a b
函數傳入參考
傳入值 (call by value):
in main(): 000000AA36BCFB34
in test(): 000000AA36BCFB10#include <iostream>
using namespace std;
void test(int a) {
cout << "in test(): " << &a << "\n";
}
int main() {
int t;
cout << "in main(): " << &t << "\n";
test(t);
}使用參考 (call by reference):
in main(): 00000045BDF2F784
in test(): 00000045BDF2F784#include <iostream>
using namespace std;
void test(int& a) {
cout << "in test(): " << &a << "\n";
}
int main() {
int t;
cout << "in main(): " << &t << "\n";
test(t);
}用途
來看看正確的 swap 吧!
順便說說 reference 的用途
-
當需要在函數內變更函數外的變數
-
當保證不會更動到函數傳入的變數
前者顯而易見,而後者是因為比較快!
畢竟少做了「複製一份」的動作
#include <iostream>
using namespace std;
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
return;
}
int main() {
int x = 4, y = 1;
swap(x, y);
// 呼叫 swap,執行 swap 裡面的程式碼
cout << "x: " << x << "\n"
<< "y: " << y << "\n";
}最後要注意的是,使用參考不能傳入非變數的值,例如這邊的 swap()
如果呼叫 swap(a, 3) 的話,編譯器會出錯:const int 不能轉換為 int&
函數分身
在 C++ 中,函數可以根據不同的「傳入值」做不同的處理,例如:
#include <iostream>
#include <string>
using namespace std;
void test(int n) {
// 傳入的是整數!
cout << "Wow! this is a integer: " << n << "\n";
}
void test(string str) {
// 傳入的是字串!
cout << "Wow! this is a string: " << str << "\n";
}
int main() {
test(95);
test("hey!");
}Wow! this is a integer: 95
Wow! this is a string: hey!物件 & 分類
Struct, Class & Namespace

一個人與他的屬性
假設這裡有個人叫做 Jack,他 180 公分高、80 公斤重
string person_1_name = "Jack";
unsigned int person_1_height = 180;
unsigned int person_1_weight = 80;
double person_1_BMI
= person_1_weight / pow(static_cast<double>(person_1_height) / 100, 2);
// 註:pow 是 <cmath> 中的函數
// pow(a, b) 就是 a 的 b 次方這樣有四個變數,而且名字還很長……
這時,我們需要一個叫做「人」的物件!
結構體 Struct
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
struct person {
string name;
unsigned int height;
unsigned int weight;
double BMI;
}; // <-- 不要忘記分號(其實 VS 會幫你加)
int main() {
person one;
one.name = "Jack";
cout << one.name;
}Jack把一堆變數包在一起,變成一個新的自訂型別!
建構式 constructor
struct person {
string name = "unknown";
unsigned int height = 150;
unsigned int weight = 50;
double BMI = 0;
person(string n_, unsigned int h_, unsigned int w_) {
name = n_;
height = h_;
weight = w_;
BMI = weight / pow(static_cast<double>(height) / 100, 2);
}
};這個 person() 不是一個用來呼叫的函數,而是建構一個 person 時會做的事!
int main() {
person one("Jack", 180, 80);
cout << one.BMI;
}24.6914解構式 destructor
跟建構式相反,是被移除時要做的事(雖然目前不需要)
struct person {
string name = "unknown";
unsigned int height = 150;
unsigned int weight = 50;
double BMI = 0;
~person(string n_, unsigned int h_, unsigned int w_) {
cout << "just remove a person";
}
};just remove a personint main() {
person one;
return 0;
}結果:
如果 main() 裡面這樣寫:
person one("Ann", 160, 50);
one.weight = 100;
cout << one.BMI;體重變成 100,BMI 卻仍是 19.5312!
因為 BMI 只有在被宣告的時候計算
改變體重並沒有連動到 BMI!
反而是這樣改會有用……
person one("Ann", 160, 50);
one.BMI = 999;
cout << one.BMI;出問題了
註:程式出問題時稱為 bug
想知道典故點此
寫成方法
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
struct person {
string name;
unsigned int height;
unsigned int weight;
double BMI;
person(string n_, unsigned int h_, unsigned int w_) {
name = n_;
height = h_;
weight = w_;
calculateBMI();
}
void calculateBMI() {
BMI = weight / pow(static_cast<double>(height) / 100, 2);
}
};
int main() {
person one("Ann", 160, 50);
one.weight = 100;
one.calculateBMI();
cout << one.BMI;
}39.0625寫一個方法計算 BMI!
結果:
但是這樣就要每次都計算一次,很麻煩!
(物件底下的函數稱為方法)
換個做法
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
struct person {
string name;
unsigned int height;
unsigned int weight;
double BMI;
person(string n_, unsigned int h_, unsigned int w_) {
name = n_;
height = h_;
weight = w_;
calculateBMI();
}
// 在改變體重時計算 BMI (理論上身高也要,但是我這邊沒有做)
void setWeight(unsigned int w_) {
weight = w_;
BMI = weight / pow(static_cast<double>(height) / 100, 2);
}
};
int main() {
person one("Ann", 160, 50);
one.setWeight(100);
cout << one.BMI;
}寫在外面
在 C++ 中,函數及方法可以先在正確的位置宣告後
在其它地方實作
struct myStruct {
string name = "unknown";
void setName(string n_);
};
// 這裡實作 myStruct 的 setName()
void myStruct::setName(string n_) {
name = n_;
}函數:
int test(string a);
int main() {
// 因為宣告過了,所以可以使用
test("nothing :D")
}
// 傳入的值必須和宣告時相同
int test(string a) {
cout << "wow! it's " << a;
}方法:
類別 class
就是進階版 struct 啦
class myClass {
public:
void changeName(string n_);
string getName();
private:
string name;
};
void myClass::changeName(string n_) {
name = n_;
}
string myClass::getName() {
return name;
}| public | 外部可以存取的東西 |
| private | 只有自己可以存取的東西 |
好處:
- 避免修改到一些會連動的東西
- 看起來很帥
像是 string 和 vector 其實都是 class,但 vector 還有用到模板,非常複雜
命名空間 namespace
之前有提到過,命名空間就是用來區分同樣名稱的不同功能
#include <iostream>
#include <string>
using namespace std;
namespace myNamespace {
void chatMessage(string name, string content);
void joinBroadcast(string name);
};
void myNamespace::chatMessage(string name, string content) {
cout << "<" << name << "> " << content << "\n";
}
void myNamespace::joinBroadcast(string name) {
cout << name << " joined the game\n";
}
int main() {
myNamespace::joinBroadcast("Jack");
myNamespace::chatMessage("Jack", "Hello Everyone!");
}Jack joined the game
<Jack> Hello Everyone!計算機
太長了,只好當一個章節來講

但是只是計畫要寫
畢竟進度還差很遠
還沒結束
之後再繼續做囉!
C++ 筆記
By 晴☆
C++ 筆記
- 177