---
# System prepended metadata

title: '程式設計 [C]'
tags: [C]

---

# 程式設計 [C]

練習ideone網址 https://ideone.com/myrecent

## 變數屬性 
1. 類別 (int, float, str...)
2. 名字 (不能是數字開頭)
3. 值 (宣告變數記得要給初始值，否則變數有可能會是任意值)
4. 記憶體位址 ()

* 一個位元組是8個位元，char是1個位元組，int是4個位元組，float是4個位元組，double是8個位元組，pointer是8個位元組

## printf / scanf
* printf / scanf 屬於 <stdio.h> standard I/O library 程式庫的函數，要記得先include才能使用
* 我們把 scanf 以十進位的形式讀到的 value 要 assign 給變數 i 的時候，就必須知道變數 i 的地址
* 計算機使用固定數目的位元組代表一個整數， 所以 int 所能存的整數大小是有一定範圍的。 這個範圍與一個 int 在記憶體中佔幾個位元組有關，可以使用 sizeof 察知變數所占的位元組，如果計算的結果超過一個變數所能記錄的範圍就是==溢位(overflow)==，溢位會使計算的結果不正確
```c=
#include<stdio.h>
int main(){
    int i = 0;
    scanf("%d", &i)
    printf("%d",i);
    printf("%d", sizeof(i))
    return 0;
}
```
* % 代表 printf 要印的資料，
* d 代表 decimal(以十進位的形式)印出，
* 參數 i 代表要印出存在變數 i 的值，
* &i 代表變數 i 的地址 


## 換行符號


| 動作 | 符號 | 備註    |
| ---- | ---- | --- |
| 換行 | \n   | newline    |
| 以16進位讀取 | %p  |pointer，大部分是讀取記憶體位置   |

## 運算符號
* 計算順序
![](https://i.imgur.com/fGaR8kz.png =400x)

* 等號 = 是指定 (assignment) 的動作，等號右邊的事會先做
```c=
int i = 0;
```
等號右邊的 value 0 會指定給等號左邊的變數 i，當成變數 i 的新值，變數 i 原本的值就會消失
```c=
int i = j = 0;
```
j 會先等於 0，i 才會等於 j

* 非!
邏輯的 not i 意思是 i 為 真 時答案為 偽。 i 為 偽 時答
案為 真。 這在 C 中 寫成 !i。

## 判斷式 condition
if condition 為 true，則A，否則B
```c=
(condition) ? A : B
```
* switch
```c=
switch(flag){
case 1:
    statement1;
    break;
case 2:
    statement2;
    break;
default:
    default_statement;    
}
```

## 迴圈 loop
* for loop
```c=
for(initialization;loop_condition;adjustment)
```

* do while 一定至少會做一次
```c=
do{
    statement;
}
while(condition)
```

## 陣列 array
* Bubble Sort 泡沫排序法
```c=
int m , n [100];
int i , j , temp ;
scanf ("%d " , & m );
for(i=0;i<m;i++){
    scanf("%d", &(n[i]));
}
for(i=m-2; i>=0; i--){
    for(j=0; j<=i;j++){
        if (n[j] > n[j+1]){
            temp = n[j];
            n[j] = n[j+1];
            n[j+1] = temp;
        }
    }
}
```
* 找出質數 prime number
```c=
int composite[101];
int i, n;
int j = 2;
int count = 0;
int column;
scanf("%d", &n);
scanf("%d", &count);
for(i=2;i<=n;i++){
    composite[i] = 0;
}
while(j*j<=n){
    while(composite[j] == 1):
        j++;
    for(i = 2*j; i<=n; i+=j){
        composite[i] = 1;
    j++;
}
for ( i = 2; i <= n ; i ++)
    if ( composite [ i ] == 0) {
        if ( count % column == ( column - 1))
            printf (" %3 d \ n ", i );
    else
        printf (" %3 d ", i );
    count ++;
}
```

* Array記憶體位址
**單維陣列**
```c=
int a[10];
printf("%d",sizeof(a[0]));   #整數一次占4個位元組
printf("%d", sizeof(a));     #10個整數占40個位元組
for(int i=0; i<10;i++){
    printf("%p", &(a[i]));   #各整數的記憶體位址，相連間隔4個位元組
}
printf("%p", &a);            #會印出第一個整數的記憶體位址來代表陣列的位址
printf("%p",a);              #在C中，陣列的值就代表是陣列的位址
```
**多維陣列**
![](https://i.imgur.com/GL6BMbZ.png =500x)


* 陣列初始化
```c=
#單維
int a[5];
int a[5]={1,2,3,4,5}; #陣列初始值
int a[]={1,2,3,4,5};  #初始可以不要給大小，陣列會自己判斷
int a[1000] = {0};    #假如初始值的數量不夠，陣列會自己補0

#多維 (幾個維度幾個括號)
int array [2][3] = {{1 , 2 , 3} , {4 , 5 , 6}};

#第一個記憶體大小位置可以不寫，但是不能全部都不寫，會影響到記憶體的位址
int array [][3] = {{1 , 2 , 3} , {4 , 5 , 6}}; 
int array [3][3] = {{1 , 2} , {4}};
int array [3][3] = {{1 , 2 , 0} ,{4 , 0 , 0} ,{0 , 0 , 0}}; #不夠會自動補0
```
:bulb:特別注意，若宣告陣列時設置的大小為變數，此時陣列無法設置初始化數值
**如果可以的話盡量避免使用變數宣告陣列大小，很容易有問題**
```c=
int A[m]={0}; #錯誤
```
* 控制列印格式
```c=
int count = 0;
int column = 一列想要印幾個數字;
if(count % column == ( column - 1))
    printf ( " %3 d \ n " , i );
else
    printf ( " %3 d " , i );
count ++;
```
%3d 代表一次會印3個空格，可以控制列印的格式(對齊)

## 浮點數 float
* float 32位元組的浮點數
* double (倍準浮點數) 是64位元組的浮點數，會比較精準
```c=
float f;
double df;
scanf("%f", &f);
scanf("%lf", &df);
printf("%f", f);
printf("%f", df);
```
* double 在 scanf 要寫成 %lf (long float) ，一般的 float 只要寫成 %f 就可以了
* printf 會通通把浮點數升到 double ，所以只要都打 %f 就可以了

### 浮點數計算
當不同類別的資料要運算的時候，就必須把低級的升到高級的才能運算
* int < float < double
* 算式中出現不同類別，潛在的類別轉換
```c=
int i;
int j ;
double d ;
scanf ( " % d " , & i );
scanf ( " % d " , & j );
scanf ( " % lf " , & d );
printf ( " % d \ n " , i / j ); #整數相除
printf ( " % f \ n " , i / d ); #整數會升級成浮點數再相除
```
* 手動類別轉換
如果對變數做類別轉換，只是在算式中將變數的值轉換為
該類別，該變數的實際類別並不會改變
```c=
int count = 100;
int sum = 250;
average = sum / count ;            #2，沒有進行類別轉換 
average = ( double ) sum / count ; #2.5，先進行類別轉換才計算
average = ( double ) ( sum / count ); #2，先計算完才進行轉換
```
## 函式 function
* 主函式
```c=
int main(void){
    return 0;
}
```
* 系統定義函式
ex. printf, scanf ... 等等，只要有include標頭檔就能用
**標頭檔查詢網址** https://www.cplusplus.com/
```c=
# include < stdio .h > /* for printf scanf */
# include < stdlib .h > /* for abs */ standard library
# include < math .h > /* for sin */
```
* 檢查回傳值
ex. scanf要怎麼知道還有沒有input，用end of file
```c=
while (scanf("%d",&i) != EOF)
    data process
```
ex. scanf回傳值會是讀到的變數個數
```c=
n = scanf ( " % d /% d /% d " , &i , &j , & k );
//如果只有一個就會是1
```
* 使用者定義函式
```c=
void foo(int array[], int n){        #這兩個是形式參數
    for(int i=0;i<n;i++)
        printf("%d", array[i]);
    //return;   void可放可不放
}
int main{
    int a[10]={0};
    foo(a,10);                       #這兩個是實際參數
    //這裡a會是array起始的地址
}
```
## 指標 pointer
* 指標變數的值代表另一個變數的記憶體位址
* 在宣告變數時在變數名稱前加星號 * 就代表這是一個指標變數
* 指標變數也有資料類別，例如指向整數的指標，則我們就將這個變數的值當作一個整數的記憶體位址

ex. iptr是一個指向int的指標變數
```c=
int * iptr ;
printf("%d", sizeof(iptr));   #8
```
* 指標的大小都是八個位元組
* 當一個指標變數前面加上星號時，就代表從這個記憶體位址取值 (dereference)

ex. 當*iptr出現在左右兩邊時代表的意思不同
```c=
i = * iptr ; #取iptr指向的地址取值assign給變數i
* iptr = i ; #把i的值存到iptr指向的位址

#一樣的意思
*( iptr + i )
iptr [ i ]
```
:bulb:*取值，&取址，不會就畫圖
![](https://i.imgur.com/jHB5OUb.png)

* 指標+1 **(可以想像成成指向下一個元素)**
```c=
iptr ++;
```
這裡的加 1 是指加一個元素的大小，所以如果是整數，指標變數就是加 sizeof(int) = 4；如果是倍準浮點數，指標變數就是加sizeof(double) = 8。

* 指標在陣列的應用
```c=
int a [5];
int i;
int * ptr ;
for (i = 0; i < 5; i ++)
    scanf ("%d " , &( a[i ]));
    
#同時初始i和ptr，用逗號分開
for (i = 0, ptr = a; i < 5; i ++ , ptr ++) {
    printf (" %p\n " , ptr );
    * ptr += 3;
}
```
* 指標相減可以得到相對位置，指標可以代替陣列，陣列也可以代替指標
* 指標主要用於動態資料結構(ex. linked-list)和字串，其餘盡量避免使用

## 字元 char
* 電腦裡的字元是用ASCII表來轉換的
```c=
char c ;
int i , j ;
printf ( " 0123456789 abcdef \ n " );
for ( i = 2; i <= 7; i ++) {
for ( j = 0; j <= 15; j ++) {
c = i * 16 + j ;
printf ( " % c " , c );
}
printf ( " \ n " );
}
```
* 特別記65是A，97是a，其他可以推論就好
![](https://i.imgur.com/smZQ5XH.png)

* 一般使用時會以單引號包住字元，電腦就會自動轉換
```c=
char c ;
c = ’m ’; 
printf ( " % c " , c );
```
* 字元的大小只有一個位元組

* 確認字元的片語
![](https://i.imgur.com/bvJ9ea6.png =400x)

* 字元轉換
![](https://i.imgur.com/RPXQRlP.png =330x)

## 字串string
**標頭檔<string.h>**

* 字串要用一個陣列的方法來宣告
```c=
char s [80];
char s [80]= { ’m ’ , ’a ’ , ’i ’ , ’n ’ , ’( ’ , ’) ’ , ’\ n ’ ,
’{ ’ , ’\ n ’ , ’} ’ , ’\ n ’} ;
int i ;
for ( i = 0; i < 11; i ++)
printf ( " % c " , s [ i ]);
```

* **一次印出所有字串string**
約定好只要看到 **'\0'** 的字元，就會自動停止併入string裡，因此想要一次印出所有字串，只需要在最後一個字元後面加入 **'\0'**
```c=
char s [80] = { ’m ’ , ’a ’ , ’i ’ , ’n ’ , ’( ’ , ’) ’ , ’\ n ’ ,
’{ ’ , ’\ n ’ , ’} ’ , ’\ n ’ , ’ \0 ’} ;
int i ;
printf ( " % s " , s );
```
<br>

* 字元常數(單引號) ’m’ / 字串常數(雙引號) " main ()\ n {\ n }\ n " 

<br>

* 空字串
陣列會自動補0，0用字元來表示就是 **'\0'**，所以至少會有一個位元組的大小要用來放 **'\0'**
```c=
char empty_string [] = " " ;
```


<br>

* 字串的輸入
string本身就已經是一個陣列，變數string代表這個陣列的位址，所以scanf不需要再特別 &string
```c=
char string [10];

while ( scanf ( " % s " , string ) != EOF ) {
printf ( " % s \ n " , string );
```

<br>

* 字元指標類別的字串也可以有初始值
編譯器會在唯讀中放一個字元陣列，使陣列初始化但是只能讀不能寫
如果是宣告陣列初始化就沒關係
```c=
char *string = "programming" ;
```

* 特殊片語
### **1. strlen**
```c=
int strlen ( char * string );

ex.
char *string = "programming"
printf("%d", strlen(string));
```
### **2. strcpy**
```c=
char *strcpy(char *destination, char *source);  #把source的資料覆蓋掉destination

#陣列原本就是地址，不用再特地加指標上去
//strcopy的時候會順便把字串末端的 \0 也一起copy
char source[100];
char destination[100];
scanf("%s\n", source);
scanf("%s\n", destination);
strcpy(source, destination);
```
### **3. strcat**
```c=
char *strcat(char *destination, char *source);  #把source的字接在destination後面
//strcat會copy source直到最後 \0 ，然後從destination \0 的地方開始接
char source[100];
char destination[100];
scanf("%s\n", source);
scanf("%s\n", destination);
strcat(source, destination);
```
### **4.strchr**
string中搜尋一個字元 c，如果找到字元 c，則回傳第一個字元 c 的index
strrchr 由字串的後面往前面找字元 c
```c=
char * strchr ( char * string , int c ); 
char * strrchr ( char * string , int c );

ex.
char str[] = "This is a sample string";
  char * pch;
  printf ("Looking for the 's' character in \"%s\"...\n",str);
  pch=strchr(str,'s');
  while (pch!=NULL)
  {
    printf ("found at %d\n",pch-str+1);
    pch=strchr(pch+1,'s');
  }
  
ex.
char pathname [40];
char file [40][40];
int file_count = 0;
char * start = pathname ;
char * slash ;
int i ;
scanf ( " % s " , pathname );
if (* start == ’/ ’)
    start ++;
while ( start != NULL ) {
    slash = strchr ( start , ’/ ’ );
    #如果找不到代表沒有/了，直接把剩下的字都抓進來就好了
    if ( slash == NULL ) {
        strcpy ( file [ file_count ] , start );
        file_count ++;
        start = NULL ;
    } else {
        strncpy ( file [ file_count ] , start ,
        slash - start );
        file [ file_count ][ slash - start ] = ’ \0 ’;
        file_count ++;
        start = slash + 1;
    }
}

```
### **5. strstr**
string 1 中尋找 string 2，若有回傳index,否則回傳NULL
```c=
char * strstr ( char * string1 , char * string2 );

ex.
char * ptr ;
char zodiac [12][40];
int i ;
for ( i = 0; i < 12; i ++)
    scanf ( " % s " , zodiac [ i ]);
for ( i = 0; i < 12; i ++) {
    ptr = strstr ( zodiac [ i ] , " er " );
    if ( ptr == NULL )
        printf ( " No er in % s \ n " , zodiac [ i ]);
    else
        printf ( " er at %d - th in % s \ n " ,ptr - zodiac [ i ] , zodiac [ i ]);
}
```
### **6. strcmp**
```c=
int strcmp(char *string1, char *string 2); #比大小，若1較小回傳負數，若1較大回傳正數，若兩字串相等則回傳0，從頭開始以ASCII碼的大小來比
```
* 如果有很多字串陣列要複製的時候可能會花費比較多的時間，我們可以改以作指標陣列的方式來進行，省去strcpy的時間
```c=
char zodiac[12][40];
char *zptr[12];
int i, j;
char *temp;
for(i=0; i<12; i++){
    scanf("%s", zodiac[i]);
    zptr[i] =zodiac[i];
}
for(i =10; i<=0; i--){
    for(j = 0; j<=i; j++){
        if(strcmp(zptr[j], strcmp(zptr[j+1]) > 0){
            temp = zptr[j];
            zptr[j] = zptr[j+1];
            zptr[j+1] = temp;
        }
    }
}
for(i = 0; i<12; i++){
    printf("%s\n", zptr[i]);
}
```

<br>

* 如果source的資料太長，destination的空間不夠接的話，會造成**緩衝區覆蓋(buffer overrun)**，字串後面的其他重要資料就會被覆蓋
解決方法：引進兩個新的函式 strncpy、strncat
```c=
//int i 可以幫忙控制最多要複製幾個字元，但是字尾要自己補 \0
char *strncpy(char *destination, char *source, int i); 
char *strncat(char *destination, char *source, int i); 
int strncmp(char *string1, char *string 2, int i);

ex.
char destination[16];
char source[80];
strncpy(destination, source, 15) #只有16格，最多只能複製15個字元，第16個要放 \0
destination[15] = '\0';
```


* string to token 
token指的是一段字元，是第一個字串中被第二個字串參數 delimeters 中的任何字元所隔開的部分，delimeters可以是多個
```c=
char * strtok ( char * string , char * delimeters );

start = strtok ( string , delimeters ); #先呼叫一次，回傳一個值給指標變數start
# 進入一個 while 迴圈檢查 start 是否為 NULL。如果start 不為 NULL，start 就會指到原字串 string 中的一個字串
while ( start != NULL ) {
    strcpy ( file [ file_count ] , start );
    file_count ++;
    
    # 唯一的差別是這邊要寫一個NULL，意思就是說你第一次進去的時候，是從字串的頭，以後你每一次給一個NULL，它就知道說，哦我是延續了上一次的那個string to token
    start = strtok ( NULL , delimeters );
}

```

<br>

###### tags: `C` 

