1 of 55

程式設計

第2章 型別、運算子、運算式

Types, Operators, and Expressions

蘇維宗 (Wei-Tsung Su)

suwt@au.edu.tw

564D

https://reurl.cc/DyWZQd

2 of 55

目標

2

3 of 55

2-1

變數與型別

專業的程式設計師會依據需求選擇適當的型別

3

4 of 55

變數、型別、與物件

在程式語言中,變數(variable)是用來指向不同型別(type)且可以改變的資料物件(object)。依據型別的特性可以將程式語言分類

  • 依確定型別的時間點分為
    • 靜態型別(statically typed)語言在編譯(compiler-time)時確定變數型別(如,C語言)
    • 動態型別(dynamically typed)語可在執行(run-time)時確定物件型別(如,Python)
  • 依不同型別的變數是否能夠混用分為
    • 弱型別(weakly typed)語言中不同型別的變數可以混用(如,C語言)
    • 強型別(strongly typed)語言中不同型別的變數不可以混用(如,Python)

4

5 of 55

變數(Variables)

C語言屬於靜態型別語言,所以定義變數時必須指定型別(type),例如:

char 字元(或8位元的整數) character (例如,'C'、'S'、'I'、'E') �int 整數 integer (例如,125、–50)�short 短整數 (例如,25、–32)�long 長整數 (例如,36、–100)�float 單精準度實數 (例如,12.0、–365.25、0.625)�double 雙精準度實數 (例如,–12.0、365.25、0.625)

5

6 of 55

定義變數

定義變數時要給定變數的(1)型別與(2)變數名稱,例如

  1. int numOfStudents = 10; // 定義變數並給予初始值
  2. int sumOfScores; // 定義沒有初始值的變數
  3. float averageScore; // 定義沒有初始值的變數
  4. char class = 'B'; // 用單引號來定義字元
  5. char department[10] = "CSIE"; // 用雙引號來定義字串

6

提醒:有意義的變數命名有助於程式的可讀性

7 of 55

格式化輸出/輸入型別指定符號(Specifiers)

使用格式化輸出(printf)與輸入(scanf)時須注意對應的型別指定符號

char 字元(或8位元的整數) character %cint 整數 integer %dshort 短整數 %dlong 長整數 %dfloat 單精準度實數 %fdouble 雙精準度實數 %f

注意!沒有使用對應的型別指定符號就無法正確輸出/輸入變數!

7

8 of 55

Practice

  1. 練習在輸入/輸出正確的使用型別指定符號
  2. 為何以不正確的型別指定符號輸出/輸入變數會錯誤?

8

9 of 55

格式化輸出/輸入變數

  1. int numOfStudents = 10; // 定義變數並給予初始值
  2. int sumOfScores; // 定義沒有初始值的變數
  3. float averageScore; // 定義沒有初始值的變數
  4. char class = 'B'; // 用單引號來定義字元
  5. char department[10] = "CSIE"; // 用雙引號來定義字串
  6. scanf("%d", &sumOfScore);
  7. printf("%s %c\n", department, class);
  8. printf("Number of student: %d\n", numOfStudents);
  9. averageScore = sumOfScores / numOfStudents;
  10. printf("Average score: %.2f\n", averageScore);

9

提醒:有意義的變數命名有助於程式的可讀性

10 of 55

如何得知型別(或變數)佔用多少記憶體空間?

所有在程式中定義的變數對會佔用記憶體。不同型別的變數會佔用的記憶體空間不同,使用sizeof()運算子可以取得不同型別(或變數)所佔用位元組(byte)的記憶體空間。

sizeoful.c

  1. ...
  2. unsigned long stdId[60]; //可以儲存60個unsigned long變數
  3. printf("Type unsigned long requires %d bytes\n", sizeof(unsigned long));
  4. printf("Variable stdId requires %d bytes\n", sizeof(stdId));
  5. ...

10

11 of 55

Preview Questions?

  1. 請問C語言還有哪些標準型別?
  2. 為什麼整數有那麼多種型別?
  3. 單精準度實數與雙精準度實數有什麼差異?

11

12 of 55

定義正整數

透過unsigned修飾字可定義正整數(>=0),例如:

unsigned char num1 = 255; // 10進位數字� unsigned int num2 = 0xFF; // 16進位數字� unsigned short num3 = 0377; // 8進位數字� unsigned int num4 = 2.55e2; // 科學記號表示� unsigned long num5 = 4294967295;

12

13 of 55

*如何更精確的使用整數型別?

因為int,long等變數佔用的記憶體會隨著機器而不同,所以用起來有一些不確定性。這時候可以考慮使用標準整數型態函式庫(stdint)。

sizeofstdint.c

  1. ...
  2. #include<stdint.h>
  3. ...
  4. uint64_t stdId[60];
  5. printf("Type uint64_t requires %d bytes\n", sizeof(uint64_t));
  6. printf("Variable stdId requires %d bytes\n", sizeof(stdId));
  7. ...

13

14 of 55

溢位(Overflow)

電腦運算後的結果無法以所配置的記憶體空間儲存時會發生溢位。

overflow.c

  1. ...
  2. int8_t num = 125; // two's compliment in 8 bits (-128 ~ 127)
  3. ...
  4. num = num + input; // if(input >= 3) overflow!
  5. ...

14

15 of 55

Review Questions?

  1. 有沒有加上unsigned有什麼差異?
  2. 整數型別的範圍?

15

16 of 55

實數(遵循IEEE 754浮點數運算標準)

單精準度(32位元)�float num1 = 15.7;

雙精準度(64位元)�double num2 = 0.001;

注意! 實數無法使用unsigned修飾字

16

17 of 55

字元與字串

字元(character)�char class = 'B';

字串(string)�char name[] = "Bob";

請撰寫程式取得class與name變數分別所占用的記憶體空間大小?

17

name[0]

name[1]

name[2]

name[3]

B

o

b

\0

'\0'為字串結束字元

printf("%s", name); //列印到'\0'為止

18 of 55

*字串的輸入與輸出

hellostring.c

  1. ...
  2. char name[10];
  3. char greeting[] = "Hello,";
  4. ...
  5. scanf("%s", name);
  6. printf("%s %s\n", greeting, name);
  7. ...

18

問題:

怎麼用目前學會的程式技巧將輸出

Hello, <name>

改成

Hi, <name>

19 of 55

型別轉換(Type Conversion)

C語言屬於弱型別語言,所以變數的型別是可以被改變的

19

20 of 55

型別轉換(透過函式庫)

例如,如何將字串"123"轉成整數123?�在標準函式庫(stdlib)中有提供atoi函式可以將字串轉成整數。

int atoi(const char *str);

atoitest.c

  1. ...
  2. char strNum[] = "123";
  3. printf("strNum = %s\n", strNum);
  4. printf("atoi(strNum) + 3 = %d\n", atoi(strNum) + 3);
  5. ...

20

21 of 55

強制轉型

C語言屬於也支援強制轉型的語法

(型別)運算式

例如,下面的程式碼可以將int變數強制轉型成float整數

  1. int num = 21;
  2. printf("num = %d\n", num / 2);
  3. printf("num = %f\n", (float)num / 2);

21

22 of 55

變數的可視範圍(Scope)

22

23 of 55

變數的可視範圍

變數的可視範圍(scope)是能夠存取該變數的程式區間

依據可視範圍的不同,變數主要可分為三大類

  1. 全域變數(global variable) 整個原始檔案都可以存取
  2. 區域變數(local variable) 只有定義該變數的函式可以存取
  3. 區塊變數(block variable) 只有定義該變數的程式區塊可以存取

23

24 of 55

全域變數

定義在所有函式(包含main)之外的變數,其可視範圍是

從 變數定義之後

到 整個檔案結束

varscope.c

  1. ...
  2. int globalVar = 2;
  3. ...
  4. int main() {
  5. ...
  6. }
  7. ...
  8. int power(int num, int exp) {
  9. ...
  10. }

24

25 of 55

主函式區域變數

定義在單一函式(如主函式main)之內的變數,其可視範圍是

從 變數定義之後

到 整個函式結束

varscope.c

  1. ...
  2. int main() {
  3. ...
  4. int localVar = 4;
  5. ...
  6. }
  7. ...
  8. int power(int num, int exp) {
  9. ...
  10. }

25

26 of 55

副函式區域變數

定義在單一函式(如副函式power)之內的變數,其可視範圍是

從 變數定義之後

到 整個函式結束

varscope.c

  1. ...
  2. int main() {
  3. ...
  4. }
  5. ...
  6. int power(int num, int exp) {
  7. int result = 1;
  8. ...
  9. }

26

27 of 55

區塊變數

定義在單一程式區塊之內的變數,其可視範圍是

從 變數定義之後

到 整個程式區塊結束

varscope.c

  1. ...
  2. int main() {
  3. ...
  4. if(globalVar < 100) {
  5. int blockVar;
  6. ...
  7. }
  8. ...
  9. }
  10. ...

27

28 of 55

相同名稱的變數

同一個可視範圍中不可定義相同名稱的變數。

在不同可視範園中允許定義相同名稱的變數,但其優先順序為

區塊變數 > 區域變數 > 全域變數

28

29 of 55

Practice

  1. 試試看在同一個變數可視範圍定義名稱相同的變數?
  2. 試試看在不同的變數可視範圍定義名稱相同的變數?

29

30 of 55

變數的修飾字

30

31 of 55

變數修飾字:const

在變數定義前加上const修飾字代表此變數無法再被改變。

例如:

  1. const int maxScore = 100;
  2. maxSxore = 150; // error

請問編譯器出現的錯誤是什麼意思?

31

32 of 55

變數修飾字:static

在變數定義前加上static修飾字代表此變數會一直存在記憶體中直到程式結束為止。

staticvar.c

  1. ...
  2. void static_count() {
  3. static uint8_t num = 0;
  4. printf("num = %d\n", num);
  5. num = num + 1;
  6. }
  7. ...

32

第一次呼叫static_count()後會在整個程式結束之前將num變數(初始值為0)保存在記憶體中

因此,當再次呼叫static_count()時,會繼續使用已經存在記憶體中的num變數

33 of 55

2-2

運算子(Operator)

33

34 of 55

運算子的種類

  • 算術運算子:+ - * / %
  • 指派運算子:= += -= *= /= %=
  • 遞增運算子:++
  • 遞減運算子:--
  • 位元運算子:<< >> & | ^ ~
  • 關係運算子:< <= > >= == !=
  • 邏輯運算子:&& || !
  • 條件運算子:?:
  • ...

34

35 of 55

算術運算子

回傳對運算元進行算術運算後的結果,例如� int num1 = 5, num2 = 2;

+ 加 printf("%d\n", num1 + num2); // ?�- 減 printf("%d\n", num1 - num2); // ?�* 乘 printf("%d\n", num1 * num2); // ?�/ 除 printf("%f\n", (float)num1 / num2); // ?�% 模除 printf("%d\n", num1 % num2); // ?

35

36 of 55

練習:計算王

隨機出一題加法運算,給定被加數與答案,請使用者輸入加數。如果使用者答對輸出YES,否則輸出NO. ANS=[答案]

如何隨機產生數字?

#include<math.h>

#include<time.h>

...

//以時間為種子產生亂數

srand(time(NULL));

//產生0 ~ 99的亂數

int num = rand() % 100;

輸入 輸出

50+B=100� B=50 YES

55+B=30� B=-20 NO. ANS=-25

藍字部分為使用者輸入

36

37 of 55

指派運算子

將運算子右邊運算式的結果指派給運算子左邊的運算元,例如� int32_t num1 = 5, num2 = 2;

= 指派 num1 = num2; // num1 = ? �+= 加指派 num1 += num2; // ≡ num1 = num1 + num2 ?�-= 減指派 num1 -= num2; // ≡ num1 = num1 - num2 ?�*= 乘指派 num1 *= num2; // ≡ num1 = num1 * num2 ?�/= 除指派 num1 /= num2; // ≡ num1 = num1 / num2 ?�%= 模除指派 num1 %= num2; // ≡ num1 = num1 % num2 ?

37

38 of 55

遞增(減)運算子

直接對運算元加1(減1),例如� int32_t num = 5;

++ 遞增 num++; // ≡ num = num + 1 = ?� ++num; // ≡ num = num + 1 = ?-- 遞減 num--; // ≡ num = num - 1 = ?� --num; // ≡ num = num - 1 = ?

問題:num++與++num有什麼不同?

38

39 of 55

練習:計算兩整數間所有整數的總和

試寫一個程式,輸入兩個整數,並計算兩整數間所有整數的總和。

輸入 輸出

10 100 5005

10 1 55

39

40 of 55

位元運算子

回傳對運算元進行位元運算後的結果,例如� char pat1 = 0x11, pat2 = 0x21;

<< 左移運算 char pat = pat1 << 1; // pat = ? �>> 右移運算 char pat = pat1 >> 2; // pat = ?�& AND運算 char pat = pat1 & pat2; // pat = ?�| OR運算 char pat = pat1 | pat2; // pat = ?�^ XOR運算 char pat = pat1 ^ pat2; // pat = ?�~ NOT運算 char pat = ~pat1; // pat = ?

40

41 of 55

練習:二補數

輸入一個8位元的整數(-128 ~ 127),輸出對應與經過二補數運算後的16進制數值

何謂二補數?

Bit pattern 00000001

1補數 11111110

2補數 11111111

輸入 輸出

1 0x01 0xff

127 0x7f 0x81

41

42 of 55

關係運算子

回傳比較運算元之關係後的結果(0為假,1為真),例如� int32_t num1 = 5, num2 = 2;

< 小於 uint8_t isT = (num1 < num2); // isT = ?�<= 小於等於 uint8_t isT = (num1 <= num2); // isT = ? �> 大於 uint8_t isT = (num1 > num2); // isT = ?�>= 大於等於 uint8_t isT = (num1 >= num2); // isT = ?�== 等於 uint8_t isT = (num1 == num2); // isT = ?�!= 不等於 uint8_t isT = (num1 != num2); // isT = ?

42

43 of 55

邏輯運算子

回傳運算元(0為假,非0為真)經過邏輯運算後的結果(0為假,1為真),例如� int num1=5, num2=0;

&& AND unsigned char isT = (num1 && num2); // isT = ?

|| OR unsigned char isT = (num1 || num2); // isT = ?

! NOT unsigned char isT = !num1; // isT = ?� unsigned char isT = !(num1 && num2); // isT = ?

43

44 of 55

複雜的邏輯判斷

透過結合關係運算子與邏輯運算子就可以組合出複雜的邏輯判斷。例如,

"如果成績介於55~ 60分之間,則自動將成績改為60"

  1. int score;
  2. scanf("%d", &score);
  3. if(score >= 55 && score < 60) {
  4. score = 60;
  5. }
  6. printf("調分後的成績:%d\n", score);

44

45 of 55

複雜的邏輯判斷(續)

透過結合關係運算子與邏輯運算子就可以組合出複雜的邏輯判斷。例如,

"如果(成績介於55~ 60分之間)或(成績介於50~ 54分之間且作業繳交數量超過10個),則自動將成績改為60"

  1. int score, assignment;
  2. scanf("%d %d", &score, &assignment);
  3. if(/*如何撰寫邏輯判斷?*/) {
  4. score = 60;
  5. }
  6. printf("調分後的成績:%d\n", score);

45

46 of 55

練習:計算1~N內能被2跟3整除,但不能被12整除的整數總和

撰寫一個程式,輸入一正整數 N , 找出 1 ~ N 的整數裡,可以被 2 與 3 整除,但不能被 12 整除的整數,並將這些數字做加總。

輸入 輸出

20 24

80 294

60 150

46

47 of 55

練習:找大小

輸入一個M或m的字元與兩個整數。如果輸出字元為M,輸出兩數的最大值,如果輸入字元為m,輸出兩數的最小值。

輸入 輸出

M 10 5 10

M 5 10 10

m 10 5 5

m 5 10 5

47

48 of 55

條件運算子

條件運算子的結構如下

(判斷條件) ? 條件成立的回傳值 條件不成立的回傳值

例如,

int num1 = 5, num2 = 0;�int max = num1 >= num2 ? num1 : num2; // max = ?

48

49 of 55

練習:找大小

輸入一個M或m的字元與兩個整數。如果輸出字元為M,輸出兩數的最大值,如果輸入字元為m,輸出兩數的最小值。

限制:使用條件運算子

輸入 輸出

M 10 5 10

M 5 10 10

m 10 5 5

m 5 10 5

49

50 of 55

2-3

運算子的優先順序

50

51 of 55

運算子的優先順序

當運算式中出現多個運算子時,

  1. 當運算子優先順序(precednece)不同時,依據優先順序決定順序。�例如,x & y == 0 與 (x & y) == 0 // x = 1, y = 2
  2. 當運算子優先次序相同時,則依據結合規則(associativity)決定順序。�例如,a = b = c // a = 5, b = 10, c = 15

"寫一個結果會取決於計算次序的程式是個不好的習慣"�- B. Kernighan and D. Ritchie

51

52 of 55

2-4

常數

52

53 of 55

定義常數

利用#define可定義常數,例如

constant.c

  1. ...
  2. #define MAX 100;
  3. #define MIN 0;
  4. ...
  5. if(score > MAX || score < MIN) {
  6. printf("NO. %d ~ %d\n", MIN, MAX);
  7. } else {
  8. printf("YES\n");
  9. }
  10. ...

為何不直接寫?

差異在哪?

  1. ...
  2. if(score > 100 || score < 0) {
  3. printf("NO. %d ~ %d\n", 0, 100\n");
  4. } else {
  5. printf("YES\n");
  6. }
  7. ...

53

54 of 55

列舉(enumerate)

利用enum定義常數集合,例如

enum.c

  1. ...
  2. enum boolean {False, True}; // False = 0, True = 1
  3. enum day {MON=1, TUE, WED, THU, FRI, SAT, SUN}; // TUE=2, WED=3, THU=4, FRI=5, SAT=6,SUN=7
  4. enum day today = TUE;
  5. enum boolean isSAT = False;
  6. while(isSAT == False) {
  7. printf("Weekday %d\n", today);
  8. today++;
  9. }
  10. printf("Weeken %d\n" today);
  11. ...

54

55 of 55

Q & A

55

Computer History Museum, Mt. View, CA