Try   HackMD
tags: Coding

C / C++ 學習筆記(partI)

參考資料(影片)
參考資料二

編譯方式

 與python不同,C/C++在運行程式前,需先編譯完成後,產生執行檔,而python則是會直接將程式碼直接吃進去,做內部編譯後,直接執行程式,因此不須產生執行檔。

C 編譯

  1. 先創建 C 的程式碼,看是要從資料夾直接寫或是從 Terminal 裡創建都可以。
    $ touch hello.c
  2. 編輯程式碼,其實沒有上面的創建( touch )也可以直接 vim ,但要記得存檔才會創建成功。
    $ vim hello.c
    按下 i ,下方狀態會變成 — INSERT — ,即可開始撰寫程式碼,簡單的輸出 Hello 。
    #include
    int main(){
    printf("Hello\n");
    return 0;
    }
    離開 vim ,按下 esc 後輸入 :wq ,按下 enter 即可存檔跳出。
  3. 編譯 hello.c ,執行完後資料夾會多一個 hello.o 的檔案。
    $ gcc -c hello.c
  4. 產生執行檔,執行完後資料夾會多一個 hello 的執行檔。
    $ gcc hello.o -o hello
  5. 執行程式碼。
    $ ./hello

C++ 編譯

  1. 創建並撰寫程式碼,方法與 C 相同。
    $ vim hello.cpp
  2. 編譯 C++ 。
    $ g++ hello.cpp -o hello
  3. 執行程式碼。
    $ ./hello

C語言

簡單介紹

  1. 為一種高階語言

  2. 具有可攜性(容易編譯成不同機器所使用的機器語言

  3. 可寫出較有效率的程式
    3.1 可以利用較為接近機器碼的方式存取記憶體
    3.2 語法可以對應到較有效率的機器碼

  4. 具有多版本:最常見的版本為C 89(ANSI C),而最新版本為C 11(ISO C),因此在討論前要先知道為使用哪個版本

  5. 具有向前相容性

  6. 使用不同編譯器的差別:
    6.1 效率

    • 從程式產生機器碼的效率
    • 機器碼執行起來的效率(C語言只有規定編譯完成的執行動作,而沒有規定編譯完成的機器碼要相同,因此可能出現,同行為,但是執行效率不同的情況發生)

    6.2 相容性

    • 編譯器在標準外的自訂擴充功能(建議不要使用非標準內功能,這樣相容性才比較廣)

    6.3 行為一致性

    • 實作定義(implementation-defined)行為 - 不同版本或者使用不同的編譯器,可能會產生不同的行為。

main()

 在使用C語言時,通常會在程式碼的執行區域內將main()區域的回傳值設定為int,也就是如下:

int main(void) { 
    /* ... */ 
}

這其實是在標準文件中所定義的,main()此程式的回傳值須為int型態,但即使真實情況下回傳值不是int,程式碼也可照常執行,只會會給予警告(warning),且若回傳值為int型態時,可能會依照回傳型態跑到相對應的exit(若為return 0 則可能執行exit(0),有點像網站給予的error訊息)。


編寫規則

字元跳脫

 在C語言內的文字描述中,會使用雙引號("),包住所要印出的字樣或是字元,而在當中,若是想要使得特殊字元能夠跳出規則,則需要使用跳脫的手法,常見手法如下:

跳脫序列 代表字元
\n 換行
\" 雙引號
\\ 反斜線
\' 單引號
\t 跳位

行數限制
 在高階語言中,即使重複執行一個重複碼多次,在此舉例printf而言,在經過編譯後未必會執行較多次(機器碼未必比較難),但是依然還是可以使用BigO做基礎判斷。


printf / scanf

輸出整數時,常見的格式符

格式符 輸出格式 以十進位的123為例
d or i 十進位有號整數 123
u 十進位無號整數 123
o 八進未無號整數 173
x(小寫) 十六進位無號整數(小寫) 7b
X(大寫) 十六進位有號整數(大寫) 7B

輸出浮點數時常見的格式符

格式符 輸出格式 範例
f 十進位浮點數 123.45
e 科學記號表示法(小寫) 1.234500e+02
E 科學記號表示法(大寫) 1.234500E+02

scanf輸入時常見的格式符

格式符 輸入格式 範例
i 任何格式的整數 123
d 十進位的有號整數 123
u 十進位的無號整數 123
f 十進位的浮點數 123.45

 在C語言中,如果有多組相鄰字串放在同個printf,則會視為相同字串,以方便可讀性,如下:

#include <stdio.h>

int main(){
    printf("H     H     A\n"
           "H     H    A  A\n"
           "HHHHHHH   A    A\n"
           "H     H  AAAAAAAA\n"
           "H     H A        A\n"
          );
return 0;
}

輸入兩數字並做加法運算

註解:在C語言內,scanf必須直接指定要寫入的地址,而不是指定寫入的變數

#include<stdio.h>
int main(){
    int interger1,interger2,sum;
    printf("please input your first number:");
    scanf("%d",&interger1); // input number to the specific address 
    printf("please input your second number:");
    scanf("%d",&interger2); // input number to the specific address 
    sum = interger1 + interger2;
    printf("Sum is : %d \n",sum);
    return 0;
}

輸入多數字並做加法運算

#include<stdio.h>
int main(){
    int interger,sum,amount;
    printf("please input amount of number that is used to sum:");
    scanf("%d",&amount);
    for (int i=1;i <=amount;i++){
        printf("please input your %d number:",i);
        scanf("%d",&interger);
        sum += interger;
    }
    printf("Sum is : %d \n",sum);
    return 0;
}

scanf上的特殊問題

 當今天想要使用多個scanf輸入時(可能出現在while迴圈),會由於scanf本身特性,再輸入完成後點擊的迴車鍵(enter),會被儲存在緩衝區,導致被重複輸入,若要使用scanf連續輸入,解法如下:

  1. 在字串前多加空格,以抵銷點擊的迴車鍵(scanf會忽略空白字元)
int main() {
	int a;
	char ch;
	printf("請輸入一個整數:"); 
	scanf("%d", &a);

	printf("請輸入一個字元:");
	/* 在%c前面加上一個空格 */ 
	scanf(" %c", &ch);
	printf("a=%d, c1=%c", a, ch);
	return 0;
}
  1. 在第一個scanf後加上ffluch(),清空緩衝區內的資料,這樣第二個scanf就不會吸收到enter(回車)了
int main() {
	int a;
	char ch;
	printf("請輸入一個整數:");
	scanf("%d", &a);

    /* 清空緩衝區內的資料 */
	fflush(stdin);

	printf("請輸入一個字元:");
	scanf("%c", &ch);
	printf("a=%d, c1=%c", a, ch);
	return 0;
}
  1. 可以用getchar()去吸收enter(回車)這個字元
int main() {
	int a;
	char ch;
	printf("請輸入一個整數:"); 
	scanf("%d", &a);

	/* 吸收空格這個字元 */
	getchar();

	printf("請輸入一個字元:");	
	scanf("%c", &ch);
	printf("a=%d, c1=%c", a, ch);
	return 0;
}

判別scanf是否有成功讀入資料

 可使用sanf的回傳值判讀

if (scanf("%d",&var) == 1){
    若回傳值為1,則判讀有成功讀取到資料,則執行此段程式
}

資料型態

資料型態 名稱 大小(位元組) 例子
短整數(short) short int 2* 32
整數(integer) int 4* 32
長整數(long integer) long int 4* 32
字元(character) char 1 '3'
單精度浮點數(single-precision floating point) float 4* 3.2
倍精度浮點數(double-precision floating point) double 8* 3.2
void ?

 在查看佔用記憶體大小時,可使用sizeof觀看,因為在不同的編譯器內不同型態對記憶體的佔用情況可能會有所不同。

  • 例如:long int未必佔用4位元,在部份64 bit編譯器上面,也可能是佔用8位元或其他大小。
printf("long int: %d .\n",sizeof(long int));
//sizeof 會計算出該型態所佔用的位元組

 由於C語言是一種靜態語言,因此在使用變數前,需要先定義變數的資料型態。
不同資料型態間的差異:

  • 資料意含不同:
    • int vs char
  • 原理不同:
    • int vs float
  • 可表示的範圍不同:
    • short int vs long int
  • 可表示精度不同:
    • float vs double
  • 有無正負號:
    • int vs unsigned int

整數

一個位元(bit)可存一個資料,分別為0 or 1,而一個位元組(byte)內有八個位元
 當使用long int型態時,代表記憶體內有232bit能儲存資料,因此則有同樣多數量的unsigned 整數,以及1/2倍的signed整數。
整數的溢位(overflow),代表運算出的結果超過原本可儲存範圍,則代表會成為未定義行為


浮點數

  • 可用來表達實數的一種方法
    • 可以表示帶小數的數值
    • 代表的只是一個約略值
  • 在佔用相同記憶體空間的情況下,可表示的範圍大於整數表示法
    • 同樣占用4個位元組,整數表示法大概可表示到10位數(232),而浮點數可表示到接近40位數
  • 可以想像成一種科學記號表示法
  • 在浮點數中,同樣也可能會發生溢位的情況,浮點數溢位會造成「未定義行為」

優點

  • 相較整數格式,浮點數可涵蓋的範圍較廣,較不容易發生溢位
  • 可以表示出小數

缺點

  • 表示的可能是約略值,在做精確計算時,可能發生不如預期的結果(可能有誤差)
  • 由於表示的格式較為複雜,因此在運算時,可能會造成效率較差
    • 現代電腦當中,通常會有專門為浮點數運算設計的硬體
有效數字
  • 圓周率3.14159262
    • 取成3.14 (三位)
    • 取成3.141 / 3.142 (四位)
  • 有效位數越多 -> 值越精確(精度高)
  • 有效位數越多 -> 占用的記憶體空間也越多
  • 在整數的表示法中,並沒有精度問題(沒有誤差)
  • 以科學記號表示法表示時候 -> 123.45 -> 1.2345 x 102 -> 1.23 x 102 -> 儲存123(有效數字) & 2(指數),則使用此方法代表,允許誤差發生,但是可以讓能表示的位數增加。
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

型別運算

 在運算時,運算完的型別會與運算過程的型別相同:

  • int + int -> int
  • float + float -> float
  • double + double -> double

字面常數:

常見字面常數 型別
123 int,long int
123.45 double
123.45f float

 所以如果要用除法或乘法轉型成double,則當中必定需要有一個double的型態,若兩者皆為int相除,則會變成捨去餘數,求得商數。因此將3轉成3.這個過程稱為隱性轉型(從int轉為double),而若是兩個不同型態的數值做運算時,會將結果隱性轉成範圍較大的型別,並輸出該型別的值。

  • 隱性轉型:當運算有需要時自動發生(利用字面常數規則)
    • double average = (num1 + num2 + num3) / 3.;
  • 顯性轉型:當我們有需要時,直接強加(例如下式當中的將3轉為double)
    • double average = (num1 + num2 + num3) / (double) 3;

字元

儲存時將長怎樣以及意義分開

  • 外觀
    • 字型
    • 互動時才須使用
  • 字本身的資訊(代表為哪個字)
    • 編碼(encoding)
    • 字元型別是種「整數」型別
  • C語言中主要的字元型別
    • char : 占用的記憶體大小為1個位元組
    • wchar_t : 使用在寬字元(例如中文,超過256種選擇)

中文字在使用上可使用: 1.多個char 2.wchar_t

char 的特色
  • 常見為ASCII編碼
  • 占用1個位元組
  • 為一種整數型別
char的字面常數
  • 在C語言中,單個字元用單引號括住:'A' ,雙引號用來括住字串
  • 在printf / scanf 中,使用%c

使用char型別做運算:

# include<stdio.h>

int main() {

    char ch = 'A' +1; //在C中 char視為整數型別
    printf("%c\n",ch);
    return 0;
    // 此方程式輸出值會為B (因為A在ASCII中編碼為65,65+1後為66,及為B)
}

大小寫轉換練習表:

# include <stdio.h>

int main(){
    char ch,ch_change;
    printf("please input your character:");
    scanf("%c",&ch);
    if (ch<91 & ch>64){
        ch_change = ch + 32;
    }
    else if (ch>96 & ch<123){
        ch_change = ch - 32;
    }
    printf("the character before change is:%c\n",ch);
    printf("the character after change is:%c\n",ch_change);
}

表示式

真假表示式

  • 「非零」的值皆為「真」,為「零」的值皆為「假」
    • 1 -> 真
    • 0 -> 假
    • -1 -> 真
    • 2 -> 真
    • 0.001 -> 真
    • 'A' -> 真
    • '\0' -> 假 (編號為0的字元)

關係運算

在C語言內做大小比較時,只能兩兩相比,例如:如果寫成3>2>1,則會先比較3>2回傳1(因為成立),之後用回傳的1與1相比,則會回傳0,因此不能做連續比較。

邏輯運算


在"或"的情況下,只要一者為正確,及回傳1

複合賦值運算

運算子 意義 範例 相等於
+= 加等於 a += 2 a = a + 2
-= 減等於 a -= 2 a = a - 2
*= 乘等於 a *= 2 a = a * 2
/= 除等於 a /= 2 a = a / 2
%= 求餘等於 a %= 2 a = a % 2

if 練習

滿額折扣

 吃到飽餐廳,1人300元(無額外費用)。今日每桌消費滿3000打八折,試寫出一程式讓服務生輸入一桌的消費人數後,輸出該桌顧客應付的總額。

# include <stdio.h>

int main(){
    int costumer;
    float cost;
    printf("please input the costumer number:");
    scanf("%d",&costumer);
    if (costumer*300>=3000){
        cost = costumer *300 * 0.8;
    }
    else if (costumer*300<3000){
        cost = costumer*300.0;
    }
    printf("total cost is %f",cost);
    return 0;
}
正三角形判斷練習

 輸入某三角形的三邊(皆為整數),判斷此三角形是否為正三角形?

#include <stdio.h>

int main(){
    int length1,length2,length3;
    printf("please input the lengths:");
    scanf("%d%d%d",&length1,&length2,&length3);
    if (length1 ==length2 && length1==length3){

        printf("this is regular triangle");
    }
    else{
        printf("this is not regular triangle");
    }
    return 0;
}
直角三角形判斷練習

 輸入某三角形的三邊(皆為整數),判斷此三角形是否為直角三角形?

#include <stdio.h>
int main(){
    int length1,length2,length3;
    printf("print insert three lengths for triangle:");
    scanf("%d%d%d",&length1,&length2,&length3);
    if (length1^2+length2^2 == length3^2 || length1^2+length3^2 == length2^2 || length2^2+length3^2 == length1^2){
        printf("this triangle is straight triangle!");
    }
    else {
        printf("this triangle is not straight triangle!");
    }
    return 0;
}

switch 練習

 基本switch述句

switch (整數值){
    case 整數常數值:
        程式片段;
        break;
    default: //在所有case都不符合時使用
        程式片段;
}

 如果沒有break,則會運行完完整的程式碼,因此可以利用break分隔不同case,如下

#include <stdio.h>

int main(){
    int id;
    printf("please input your ID:");
    scanf("%d",&id);
    switch (id){
        case 1:
        case 2:
        case 3:
            printf("group 1");
            break;
        case 4:
            printf("group 2");
            break;
        case 5:
            printf("group 3");
            break;
        default:
            printf("not in group");
            break;
    }
    return 0;
}

while

 猜數字直到完全猜對為止

#include <stdio.h>
#include <stdlib.h>
#include <time.h>   /* 時間相關函數 */
int main() {
      /* 設定亂數種子 */
    srand( time(NULL) );
    int x = rand();
    int input;
    int count = 0;
    printf("%d\n",x);
    while(1){
        printf("please input the number you guess:");
        scanf("%d",&input);
        count += 1;
        if (input > x){
            printf("your input number is too large!\n");
            continue;
        }
        else if( input <x){
            printf("your input number is too small!\n");
            continue;
        }
        else if (input == x ){
            printf("you got the correct number!\n");
            break;
        }
    }
    printf("you use totoal %d times to guess",count);
    return 0;
}

do while

do {程式片段} while (表示式); //分號為必要
  • 當後段while內「表示式」成立時,會執行前段do之後接著的「程式片段」(第一次執行時,不論是否有符合,都會做程式片段)

藉由do-while搭配switch編寫簡易點算錢系統:

#include <stdio.h>
int main() {
    int TotalMoney = 0;
    char order,check;
    printf(
        "| class A | NT:100|\n"
        "| class B | NT:80|\n"
        "| class C | NT:60|\n"
        "| class D | NT:40|\n"
        "| class E | NT:20|\n"
        "| class F | NT:0 |\n"
    );
    do { 
        printf("please input your order:");
        scanf(" %c",&order);
        switch (order)
        {
        case 'A'|'a':
            TotalMoney += 100; 
            break;
        case 'B'|'b':
            TotalMoney += 80;
            break;
        case 'C'|'c':
            TotalMoney += 60;
            break;
        case 'D'|'d':
            TotalMoney += 40;
            break;
        case 'E'|'e':
            TotalMoney += 20;
            break;
        case 'F'|'f':
            TotalMoney += 0;
            break;
        default:
            printf("please input the correct order");
            break;
        }
        printf("Do you have more order to input? (y/n):");
        scanf(" %c",&check);
    } while (check =='y' | check == 'Y');
    printf("your total price is: %d",TotalMoney);
    return 0;
}

for loop

  • for (初始條件; 條件式; 迴圈式){程式片段}
    • 執行 - 初始條件
    • 當條件式成立時,則執行「程式片段」,否則跳出迴圈
    • 若條件成立,則會在執行完「程式片段」後,執行迴圈式,並跳回條件式,以此反覆

韓信點兵

 韓信命令士兵 3 人一排,結果多出 2 名;接著命令士兵 5 人一排,結果多出 3 名;他又命令士兵 7 人一排,結果又多出 2 名,試輸入一值,並輸出從零開始到該值中間所有符合韓信點兵之數值。

#include <stdio.h>

int main(){
    int region,answer;
    printf("please input the region for the question:");
    scanf(" %d",&region);
    for (int i=0;i<=region;i++){
        if (i % 3 == 2 && i % 5 == 3 && i % 7 == 2){
            printf(" %d",i);
        }
    }
    return 0;
}

質數確認

#include <stdio.h>

int main(){
    int input_num;
    char check = 'y';
    printf("please input the number you want to check for prime:");
    scanf(" %d",&input_num);
    for (int i = 2; i*i<input_num;i++){
        if (input_num % i == 0){
            check = 'n';
            break;
        }
    }
    if (check == 'y'){
        printf("input number is prime!");
    }
    else if (check == 'n'){
        printf("input nubmer is not prime!");
    }
    return 0;
}

for loop繪製長方形

#include <stdio.h>

int main(){
    int height;
    printf("please input your height for rectangle:");
    scanf(" %d",&height);

    for (int i=1; i<=height*height;i++){
        if (i % height == 0){
            printf("*\n");
        }
        else {
            printf("*");
        }
    }
    return 0;
}

函式(function)

回傳值的資料型態 函式名稱(參數的資料型態 參數名稱,...){
    /*程式碼片段*/
    return 回傳值;
}

註解:函式內無法再定義其他函式
 在呼叫函式前,需要先宣告或是定義函數,此原因是由於編譯前,會由前往後編譯,因此需要在呼叫前先編譯過函式,若今天函式本體需要在main()之後定義,則可以在main()前先"宣告"函式。

C標準函式庫

主要提供下列功能:

  • 字串處理 <string.h>
  • 數學運算 <math.h>
    • sqrt(平方根),ceil(無條件進位),abs(絕對值)
  • 輸入與輸出處理 <stdio.h>
    • printf,scanf
      • printf 本身會回傳字元數作為return值
  • 記憶體管理 <stdlib.h>
  • 其他跟作業系統服務相關功能

標頭檔:

  • 以h為副檔名
  • 標頭檔通常以檔案的形式存放在編譯器知道的位置
  • 引用標頭檔通常使用#include 作為前置指令

變數名稱的宣告

  1. 全域變數(Global variable)
    • 宣告在函式之外
    • 容易造成變數污染
  2. 區域變數(Loval variable)
    • 宣告在函式定義內
  3. 函式參數(Formal Parameter)

注意事項:

  • 在同一組區塊內{},相同名稱的變數只能有一個
  • 同名的全域變數與函式只能有一個

繪製底n,高n的三角形

 #include <stdio.h>

void draw_triangle(int);

int main(){
    int height;
    printf("Please insert your height for triangle:");
    scanf(" %d",&height);
    draw_triangle(height);
    return 0;
}

void draw_triangle(int a){
    int time;
    for (int i=1;i<=a;i++){
        time = 0;
        while (time != i)
        {
            printf("*");
            time += 1;
        }
        printf("\n");
    }
}
 

遞迴函式

int f(int);

int main(){
    printf("%d\n",f(0));
    return 0;
}

int f(int i){
    if (i == 2){
        return i;  //設定停止遞迴點,避免函式無限遞迴。
    }
    return f(i+1)   //主要產生於此,在使用的函式內會再度呼叫本身。

}

counting by recursive
#include <stdio.h>

void count(int,int);


int main(){
    int start,end;
    printf("Please input your start and end numbers:\nstart:");
    scanf("%d",&start);
    printf("end:");
    scanf("%d",&end);
    printf("start counting:\n");
    count(start,end);
    return 0;
}

void count(int start, int end){
    if (start > end){
        printf("end!!");
        return;
    }
    printf("%d\n",start);
    count(start += 1,end);
}
費氏數列(by recursive)
#include <stdio.h>

int Fibonaccic(int);
int main(){
    int number;
    printf("please input your number for Fibonaccic:");
    scanf("%d",&number);
    printf("the answer is %d",Fibonaccic(number));
    return 0;
}

int Fibonaccic(int a){
    if (a<3){
        return 1;
    }
    return Fibonaccic(a-1)+Fibonaccic(a-2);
}

C/C++ 學習筆記(PartII)