Try   HackMD

社課額外補充教材 : 變數與運算

如果你覺得社課還算輕鬆,這裡提供你一些額外的補充教材。
這些是我們在備課的時候覺得稍微進階,或是篇幅太多,而沒有放在正式社課的內容。


這裡有目錄,左邊也有,可以跳到自己想看的地方


建議必看的地方 :

  • 記憶體的觀念
  • 有關字串的補充(第二部分)
  • 字元運算

記憶體的觀念

記憶體是我們寫程式的好朋友,當我們宣告一個變數,就等同於在記憶體上畫出一個空間,
讓這個空間存放變數的值。所以你可以把變數理解成 "一塊儲存可變動的值的記憶體空間"
記憶體在之後的指標跟資料結構的時候都會再提到,

各種資料型態的記憶體空間

整數

  • int、unsigned int : 4 bytes
  • long long、unsigned long long : 8 bytes

浮點數

  • float : 4 bytes
  • double : 8 bytes

字元跟字串

  • char : 1 byte
  • string : 長度+1 bytes

字元與ASCII的範例Code

字元運算

整數、大寫字母、小寫字母的ASCII Code
分別都是連續的

char a = 'A'; a = char( a + 1 ); // a 的值由 A 變成 B // 當然你也可以寫 char( int(a) + 1 )
#include<iostream> using namespace std; int main(){ char a = '0'; cout << a << endl; cout << (int) a << endl; a += 1; // a == '1' if (a == '1') cout << "Yes" << endl; cout << a << endl; cout << a + 1 << endl; }

有關字串的補充(第一部分)

其實字串是由字元所組成,最後會在結尾加上 '\0' 表結束

#include<iostream> #include<string> int main(){ char a[6] = {'h','e','l','l','o','\0'}; std::string b = "hello"; //和a一樣都是"hello" char c[6] = "hello"; //也可以這樣寫 char d[5] = "hello"; //但不能這樣寫,記住要連'\0'也算進去 char e[] = "hello"; //也可以這樣寫,e的長度是6 }

那為什麼要用 std::string ?
因為 std::string 可以這樣

#include<iostream> #include<string> int main() { std::string b; std::cin >> b; //可以輸入任意長度的字串 std::cout << b; char a[] = "hello"; //a 的長度被限制在6 std::cin >> a; //輸入超過6-1個字會爆掉 }

怎麼做到的?
查查指標陣列吧,但看看就好

References:
TutorialsPoint
W3School C++ : String
慎入:
Microsoft Docs


有關字串的補充(第二部分) : stoi and to_string

上課的時候我們有給過這張圖片:


其中有兩個函式 stoi(s) 跟 to_string(i)是在比較新的C++版本,從C++11之後才出現的。而因為DevC++的默認C++版本是很古老的C++98,所以如果需要使用這兩個函式實作時,你會發現 compiler 一直跟你說:

"to_string" / "stoi" is not declared in this scope.

所以如果需要使用這兩個函式,我們需要把DevC++的版本提高。
依序點開Tools -> Compiler Options,並在General的第一個方框內打上 "-std=c++11" ,即可把你的C++版本手動升級到C++11囉!


有關浮點數的補充

  • 我想四捨五入怎麼做?

為您隆重介紹,std::setprecision!!

#include<iomanip> //setprecision要用的函式庫 #include<iostream> int main(){ std::cout << std::setprecision(3); //注意!此效果是全域的 //3表示的是總長度 std::cout << 3.1415926 << '\n'; //結果會是 3.14 std::cout << 13.75 << '\n'; //結果會是13.8 }

要怎麼只動到小數點後的位數?

#include<iomanip> //setprecision要用的函式庫 #include<iostream> int main(){ std::cout << std::fixed << std::setprecision(3); //加在setprecision前後都沒關係,效果一樣是全域 std::cout << 13.14159; //結果會是13.142 std::cout << 1.2; //結果會是1.200 }

沒錯,加了fixed後會自動補0

那要怎麼還原?

#include<iomanip> //setprecision要用的函式庫 #include<iostream> int main(){ std::cout << std::fixed << std::setprecision(3); //加在setprecision前後都沒關係,效果一樣是全域 std::cout.unsetf(ios::fixed);//這能將fixed效果關掉 //不過setprecision並不是能關的函式,只能還原成預設值 std::setprecision(6); //預設是6 //以下的輸出即是預設輸出 std::cout << 3.1415926; //結果會是3.14159 std::cout << 13.1415926; //結果會是13.1416 }

除此之外,有關輸出對齊的功能也一併放在iomanip
以下是解說
Microsoft Doc


前綴詞

  • 常數 const
  • 正數 unsigned
  • 加長 long
  • 變短 short
const int a = 1; //a之後就不能在程式中任意更改了 unsigned int b; //b的長度比int大1,從0 ~ 4,294,967,295 long long (int) c; //前一個long是前贅詞,後一個long是資料型態 short (int) d; //比int還短,能存的值更少,最多到32767

const比你想的常見

1 的型態是 const int '1' 的型態是 const char "1" 的型態是 const char[2] 1.0 的型態是 const double 1.0f 的型態是 const float

不過沒有所謂的 const bool

true和false是保留字


後綴詞

這要從auto說起

auto a = 1 //a的型態是int auto b = '1' //b的型態是char

看出來了嗎?
auto會隨著後面指定的值轉換型態
可是我想讓a變成long long怎麼辦?
這時候就是後綴詞出馬的時候了

各個資料型態的後綴

  • u : 表示 unsigned
  • l : 表示 long 或 long double
  • ll : 表示 long long
  • ul : 表示 unsigned long
  • ull : 表示 unsigned long long
  • f : 表示float

用法

1l //型態為long 1.0l //型態為long double 1u //型態為unsigned int 1.0f //型態為float 1.0 //依編譯器不同,可能為float或double //然而大部分都是double

其他類似的用法

  • 十六進位要加前導0x
0xA1 //等於161

有關左右值

Microsoft Docs

說人話

int a; 4 = a; //不合法,4是右值不能放左邊 const int b; b = 5; //不合法,const是不能改的左值(non-modifiable lvalue) a = b; //合法

++的好玩之處 : a++ 和 ++a 差別

int a = 4; int b; b = a++; std::cout << "a = " << a << " , "; std::cout << "b = " << b; //結果是 a = 5 , b = 4 int c = 4; int d; d = ++c; std::cout << "c = " << c << " , "; std::cout << "d = " << d; //結果是 c = 5 , d = 5

a++會先回傳a的值,再把a + 1
++a會先把a + 1,再回傳a的值
說人話:
a++是先運算後再加 , ++a是先加後再運算

猜猜看:

int a = 10; int b; int c = 5; b = ++a - c++; std::cout << b++ + a++ + c;

答案是15 * 6 - 10 / 2 + 7 - 69的值喔

當然 a-- 還有 a 是相同的概念喔

有關輸入、輸出

  • 為何要用cin/cout?
std::cout << "因為" << "cout" << "可以這樣做" << '\n'; printf("當然printf也可以這樣做\n"); std::cout << "但我相信這樣子" << '\n' << "比"; printf("這樣子做\n直觀");

當然scanf和cin也是相同的概念

為什麼要用endl?

這要提到緩衝區的觀念
從上面的實驗也可以知道緩衝區也拖慢了速度
那為什麼要有緩衝區,或著說,甚麼是緩衝區?

緩衝區(buffer)

緩衝區大小:看各電腦系統
Windows是32768個字元
超過時就會直接輸入
在讀取輸入的時候分為三種

  1. 字緩衝
  2. 行緩衝
  3. 全緩衝

字緩衝

字緩衝以空格' '為分界,當遇到空格時會將前面的東西寫入程式,再把空格清除。
常用的cin >> , printf都是這樣


行緩衝

行緩衝以行'\n'為分界,當用戶按下enter的時候才會把前面的東西寫入程式。
在 C++ 中可以用 getline() 來達到這件事

#include<iostream> #include<string> int main(){ std::string a; getline(std::cin,a); //這意思是把cin中讀取到的東西寫入a那裏 std::cout << a; //a 可以連空格也一起讀取 }

全緩衝

全緩衝直到buffer滿了之後才會寫入,或是使用特殊的函式強制寫入緩衝區的內容
用法:我不知道


所以為什麼要有緩衝區?

因為將讀取到的東西寫入很花時間,
所以比起一個字一個字的寫入,還不如一次寫入多個資料效率較高


那有沒有不緩衝的

有,像是cerr就是因為必須要盡快輸出錯誤訊息所以沒有緩衝區
也可以使用cin.get()

include<iostream> int main(){ char a = char(std::cin.get()) //因為cin.get()回傳的是ASCII碼,要把它換回字元 std::cout << a; }

邪教-不用變數的輸入

#include<iostream> int main(){ std::cout << char(std::cin.get()); //只能讀一個字元 return 0; }

強制清空buffer

可以使用 std::flush 或 std::fflush(stdout)

std::cout << std::flush; std::cout << std::fflush(stdout); //這兩行是相同的意思

所以什麼是endl

std::cout << endl; std::cout << '\n' << std::flush; std::cout << '\n' << std::fflush(stdout); //沒錯這三行是一樣的

所以這到底什麼時候會用到?

當有人嘗試要用std::fstream的時候。
std::ofstream中寫到的東西是全緩衝,直到程式結束後才會輸入。
聽不懂嗎 ? 聽不懂沒關係我們不會教(絕對不會在正課教)。

有興趣的我們放連結在下面:
Microsoft Doc
W3School C++ Files


buffer overflow

自己查不想教
還有這東西現在基本上都有應對方式

或是問問網管小組的,他們都超電(小聲