音影片學習之路--C語言(2)

語言: CN / TW / HK

前言

C和C++作為學習音影片技術首要具備的語言基礎,所以十分必要學習和複習一下之前學習的C語言基礎。

正文

前面有一篇文章已經介紹了不少關於C的知識點,下面我們繼續。

結構體

不論是C還是Java,都不能只有那幾種基本資料型別,當然也需要一種類的概念,在Java中是面向物件,也就是類,在C中我們需要使用結構體。

結構體允許C語言建立一種自定義的資料型別,使用struct關鍵字,這個也非常容易理解,程式碼如下:

```

include

include

struct Book{ char title[50]; char author[50]; char subject[50]; int book_id; };

int main() { struct Book androidBook; strcpy(androidBook.title,"第一行程式碼"); strcpy(androidBook.author,"郭霖"); strcpy(androidBook.subject,"android"); androidBook.book_id = 100;

printf("book info : title = %s \n "
       "author = %s \n "
       "subject = %s \n "
       "id = %d \n",
       androidBook.title,androidBook.author,androidBook.subject,androidBook.book_id);
return 0;

} ``` 這裡注意如果中文顯示不出來,需要設定IDE的編碼,可以設定未UTF-16,預設是UTF-8,上面程式碼列印是:

image.png

由於結構體不像Java類可以定義什麼get/set函式,所以這裡賦值就直接使用strcpy或者直接賦值,在獲取結構體成員時使用.呼叫。

結構體指標

既然結構體屬於自定義的型別,那一定就可以定義指向這種型別的結構體指標了,這裡也非常簡單,主要點就是通過指標獲取結構體的成員可以使用箭頭 -> 來進行,當然也可以先取值,用點 . 是一樣的,下面是程式碼:

```

include

include

include

struct Book{ char title[50]; char author[50]; char subject[50]; int book_id; };

int main() { struct Book androidBook; strcpy(androidBook.title,"第一行程式碼"); strcpy(androidBook.author,"郭霖"); strcpy(androidBook.subject,"android"); androidBook.book_id = 100;

printf("book info : title = %s \n "
       "author = %s \n "
       "subject = %s \n "
       "id = %d \n",
       androidBook.title,androidBook.author,androidBook.subject,androidBook.book_id);

//使用結構體指標
struct Book *pBook;
pBook = &androidBook;
printf("book info : title = %s \n "
       "author = %s \n "
       "subject = %s \n "
       "id = %d \n",
       pBook -> title,
       (*pBook).author,
       pBook -> subject,
       pBook -> book_id);
return 0;

} ``` 這裡使用pBook指標,列印如下:

image.png

位域

不得不說,C的記憶體使用還是講究啊,就比如這個位域,在結構體中有些資訊在儲存時並不需要一個完整的位元組,也就是8個二進位制位,這時就可以按二進位制來儲存資訊,減小記憶體使用。比如存放一個開關變數,只需要0和1即可,比如下面程式碼: ``` struct Bean{ unsigned a:1; //這裡空7位,可以不定義成員直接空著 int :7; unsigned b:6; //範圍是0到63 //一個成員變數不會儲存到2個位元組中,所以這裡預設也是空2位 unsigned c:7; //範圍是0到127 };

int main() {

struct Bean bean;
struct Bean *pBean;
bean.a = 0;
//64無法正確儲存到b中,b會全是0
bean.b = 64;
bean.c = 100;
printf("bean的值分別是 %d %d %d \n",bean.a,bean.b,bean.c);

pBean = &bean;
pBean -> a = 1;
pBean -> b = 62;
pBean -> c = 129;
printf("bean的值分別是 %d %d %d \n",pBean -> a,pBean ->b,pBean -> c);

return 0;

} ```

上面註釋已經寫的很清楚了,所以列印可以預知如下:

image.png

所以這裡的位域只對儲存資訊比較小的時候有用,也就是小於8個位元組,給拆開的情況。

共用體

這個共用體感覺有點奇怪又合理,它是一種特殊的資料型別,允許在相同的記憶體位置儲存不同的資料型別,但是可以定義一個帶多成員的共用體,任何時候只有一個成員帶有值,直接看示例程式碼就明白:

``` union Data{ int i; float f; char str[20]; };

int main() {

union Data data;

//這種訪問共用體是錯誤的
data.i = 10;
data.f = 2.9f;
strcpy(data.str,"android");
printf("data.i : %d \n",data.i);
printf("data.f :%f \n",data.f);
printf("data.str : %s \n",data.str);

//這種才是正確的
data.i = 10;
printf("data.i : %d \n",data.i);
data.f = 2.9f;
printf("data.f :%f \n",data.f);
strcpy(data.str,"android");
printf("data.str : %s \n",data.str);

return 0;

} ``` 上面程式碼定義了共用體以及錯誤和正確的訪問方式,看一下列印:

image.png

由於共用體只能由一個成員帶值,所以第一種訪問肯定是不對的,很容易理解。

typedef

這個很容易理解,從名字就看的出來型別定義,也就是給型別取一個新名字。但是也有一個關鍵字也可以實現,那就是#define,這個其實是預處理指令,它倆還是有區別的:

  • typedef僅僅用於型別符號的別名,#define不僅可以為型別起別名,也可以為數值,比如定義Π為3.14。

  • typedef是由編譯器執行解釋的,#define語句是由預編譯器進行處理的。

輸入和輸出

這裡先說輸入和輸出是鍵盤和螢幕,其中涉及3類方法,程式碼如下:

``` int main() {

//scanf和printf
float f;
printf("輸入一個float值 \n");
scanf("%f",&f);
printf("輸入的值是 %f \n",f);

//getchar和putchar
int c;
printf("輸入一個char值 \n");
c = getchar();
putchar(c);

//gets和puts
char str[100];
printf("輸入一個字串");
gets_s(str,10);
puts(str);

return 0;

} ``` - scanf()和printf(),用於從標準鍵盤讀取並且格式化,標準輸出到螢幕。

  • getchar()和putchar(),用於讀取一個字元和輸出一個字元。

  • gets()和puts(),用於讀取和輸出字串。

檔案讀寫

其實檔案讀寫和上面說的輸入和輸出是一樣的,包括API設計思想也是類似,這裡主要是有個FILE指標就是用來控制檔案的,直接看程式碼即可:

``` int main() {

//檔案寫入
FILE *fp = NULL;
//返回一個FILE指標
fp = fopen("C:\Users\wayee\CLionProjects\Ctest\test.txt","a+");
//通過fprintf寫檔案,其中fp的第一個引數
fprintf(fp,"fprintf新增 \n");
//通過fputs寫檔案,其中fp是第二個引數
fputs("fputs新增\n",fp);
fclose(fp);

//檔案讀取
FILE  *p = NULL;
p = fopen("C:\Users\wayee\CLionProjects\Ctest\test.txt","r");
//需要一個緩衝區
char buffer[255];
//使用fscanf讀取檔案
fscanf(p,"%s",buffer);
printf("通過fscanf讀取 : %s \n",buffer);
//通過fgets讀取檔案
fgets(buffer,255,p);
printf("通過fgets讀取 : %s \n",buffer);
fgets(buffer,255,p);
printf("通過fgets讀取 : %s \n",buffer);
fclose(p);

return 0;

} ``` 其中txt檔案如下:

image.png

列印如下:

image.png

其中主要就是通過fscanf和fgets這2個函式來讀取檔案。

前處理器

C語言有前處理器,這個還是比較特殊的,至少在Java中沒有這個概念,說編譯就編譯了,那這個前處理器是啥意思呢,其實就是一個文字替換工具而已。

C的前處理器也就是CPP,會在實際編譯器完成處理,所有預處理命令都是以#開頭。

預處理命令.png

其實看著多,都非常好理解,不外乎就是判斷某個巨集是否定義了,或者條件判斷,預處理在C語言程式碼中編譯有著很重要的作用。

除了上面的幾個,還有一些預處理運算子在程式碼中也非常有用,

巨集運算子.png

下面是簡單的示例程式碼,加強記憶:

```

include

include

include

//使用巨集延續運算子 #define message_for(a,b) \ printf(#a " and " #b ": love \n") //使用貼上##,把token和n給貼上為一個標記

define tokenPaster(n) printf("token"#n" = %d \n",token##n)

//引數化的巨集,來定義一個x*x的函式

define square(x) ((x) * (x))

int main() { //使用字串常量化運算子 message_for(Carole,Debra); //貼上 int token34 = 40; tokenPaster(34); //引數化的巨集 int j = square(5); printf("j = %d",j); return 0; } ```

列印結果是:

image.png

標頭檔案

在C語言中有標頭檔案的概念,是以.h為副檔名,這類檔案在Java中是不存在的,所以為什麼在C語言中要搞一個頭檔案的概念呢?

我查閱了相關文章,其實如果你不要這個標頭檔案也可以,學過Java的都知道這個#include其實和import是一樣的功能,但是Java中一般是匯入一個類或者變數等,而#include是匯入標頭檔案,當然#include也可以匯入方法、變數,那為什麼不直接用#include來匯入方法或者變數呢,就不用定義標頭檔案了。

原因還是C中有了標頭檔案可以更方便的判斷編譯,如果沒有標頭檔案的概念,那條件編譯會有很多判斷,所以我們來看一下要把什麼東西放到標頭檔案中:

標頭檔案.png

當然這裡就不細說了,等後面具體程式碼再討論,標頭檔案主要就是為了讓程式碼檔案結構更清晰和條件編譯。

可變引數

可變引數這個在kotlin中用的很多,尤其是陣列arrayOf類似的函式,但是在C語言中這個可變引數是如何定義和解析的呢?

其實這個還是比較複雜的,我們直接看程式碼和註釋即可:

``` //多引數 其中num是多引數的個數,...表示引數 double average(int num,...){ //先定義一個va_list變數 va_list vaList; double sum = 0.0; //開始解析多引數,解析num個,放入vaList中 va_start(vaList,num); for (int j = 0; j < num; ++j) { //使用va_arg獲取多引數的每個引數值 sum += va_arg(vaList,int ); } //結束解析 va_end(vaList); return sum/num; }

int main() { printf("average of 2,3,4,5 = %f \n", average(4,2,3,4,5)); return 0; } ``` 這裡程式碼是求2,3,4,5這4個數的平均數,列印如下:

image.png

總結

C學習大概先到這裡,等後面學習具體專案再進行補充,這2篇C語言的文章只是複習一些C語言的基礎知識,上一篇文章是:

# 音影片學習之路--C語言(1)

可以結合一起看,用來複習一下C語言。