Android 音視訊入門:C語言基礎

renxhui發表於2020-03-31

概述

C語言對於Android開發來說還是非常必要的,不管你是要閱讀原始碼,還是想要學習NDK,音視訊,效能優化等,都不可避免需要接觸到C,而且C語言屬於系統級的語言,作業系統核心都有C的身影,所以我今天學習一下C語言,本篇部落格作為筆記,以防以後忘記

Android 音視訊入門:C語言基礎

C簡介

C語言最初適用於系統開發工作的,特別是組成作業系統的程式,由於C語言產生的程式碼執行速度與彙編編寫的程式碼執行速度幾乎相同,所以採用C語言作為系統開發語言,下面列舉幾個使用C的例項

  • 作業系統
  • 文字編輯器
  • 印表機
  • 網路驅動器等

C環境的設定

寫在原始檔的中程式碼,使我們人類可以看懂的,他需要編譯轉換為機器語言,這樣CPU可以按照指令執行程式,而C語言可以通過GUN編譯器,把原始碼編譯為機器語言

Mac上直接下載Xcode就可以使用GUN編譯器,開發工具可以使用CLion或者直接用文字編譯器

先寫一個HelloWord

#include <stdio.h>
 
int main()
{
   /* 我的第一個 C 程式 */
   printf("第一個c程式Hello, World! \n");
   
   return 0;
}
複製程式碼

一個C程式包括

  • 前處理器指令 : 程式的第一行#include <stdio.h>是預處理指令,告訴C編譯器實際編譯之前需要包括stdio.h檔案
  • 函式 : 下一行int main()是主函式,程式從這裡開始執行,printf()是程式中另一個可用的函式作用是在螢幕上顯示Hello, World!
  • 變數
  • 語句&表示式
  • 註釋: /* ... */這就是一個註釋
  • return 0;標識此函式結束,並返回0

編譯執行C程式

  • 首先開啟文字編輯器新增上方程式碼
  • 儲存檔案為hello.c
  • 開啟命令列,進入檔案的資料夾
  • 命令列輸入gcc hello.c,編譯程式碼
  • 如果沒有錯誤的話,資料夾就會生成a.out的可執行檔案
  • 命令列輸入a.out,執行程式
  • 然後螢幕上就可以看到Hello, World!
Last login: Mon Feb 17 14:53:00 on ttys002
L-96FCG8WP-1504:~ renxiaohui$ cd Desktop/
L-96FCG8WP-1504:Desktop renxiaohui$ cd c/
L-96FCG8WP-1504:c renxiaohui$ gcc hello.c 
L-96FCG8WP-1504:c renxiaohui$ a.out 
第一個c程式 Hello, World! 
L-96FCG8WP-1504:c renxiaohui$ 
複製程式碼

C語言入門

1 基本語法

識別符號

標記符是用來標記變數,函式或者任何使用者自定的變數名稱,一個識別符號以字母A—Z,a-z,或者_下劃線開始,後面跟0個或多個字母,數字,下劃線

識別符號中不允許出現標點字元,比如@%,識別符號是區分大小寫的

關鍵字

這些關鍵字不能作為常量名或者變數名,其他識別符號的名稱

關鍵字 說明
continue 結束當前迴圈,開始下一輪迴圈
switch 用於開關語句
case 開關語句分支
default 開關語句中的其他分支
break 跳出當前迴圈
do 迴圈語句的迴圈體
while 迴圈語句的迴圈條件
if 條件語句
else 條件語句否定分支與if一起使用
for 一種迴圈語句
goto 無條件跳轉語句
return 子程式返回語句,可帶引數可不帶引數
char 宣告字元變數或者返回值型別
double 宣告雙精度浮點型別物件或函式返回值型別
float 宣告浮點型變數或返回值型別
short 宣告短整形變數或者返回值型別
int 宣告整形變數或者返回值型別
long 宣告長整形變數或返回值型別
unsigned 宣告無符號型別變數或返回值型別
void 宣告函式無返回值或無引數,宣告無型別指標
enum 宣告列舉型別
static 宣告靜態變數
auto 自動宣告變數
const 定義常量,如果一個變數被const修飾,則它的值不能被改變
extern 宣告變數或函式在其他檔案或本檔案的其他位置定義
register 宣告暫存器變數
signed 宣告有符號型別的變數
sizeof 計算資料型別或變數的長度
struct 宣告結構體型別
union 宣告共用體
typedef 給資料型別取別名
volatile 說明變數在執行過程中可以隱含的改變

可以看到70%都是java中有的,學習起來並不是很難

2 資料型別

C中的資料型別可以分為以下幾種

型別 描述
基本資料型別 他們是算術型別,包含倆種型別,整數型別和浮點數型別
列舉型別 他們也是算術型別,被用來定義在程式中只能賦予其一定的離散整數值得變數
void 標識沒有可用的值
派生型別 他包括指標型別,陣列型別,結構型別,共用體型別和函式型別

我們先介紹基本資料型別

整數型別

型別 儲存大小 值範圍
char 1位元組 -128-127或0-255
unsigned char 1位元組 0-255
signed char 1位元組 -128-127
int 2或4位元組 -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int 2或4位元組 0 到 65,535 或 0 到 4,294,967,295
short 2位元組 -32,768 到 32,767
unsigned short 2位元組 0 到 65,535
long 4位元組 -2,147,483,648 到 2,147,483,647
unsigned long 4位元組 0 到 4,294,967,295

各型別的儲存大小,與系統位數有關,為了得到準確大小可以用sizeof來計算

#include <stdio.h>
#include <limits.h>
int main(){
    printf("int 儲存大小 : %lu \n", sizeof(int));
    return 0 ;
}
複製程式碼
int 儲存大小 : 4 
複製程式碼

浮點型別

型別 儲存大小 值範圍 精度
float 4位元組 1.2E-38 到 3.4E+38 6 位小數
double 8位元組 2.3E-308 到 1.7E+308 15位小數
long double 16位元組 3.4E-4932 到 1.1E+4932 19位小數

void型別

型別 描述
函式返回為空 C 中有各種函式都不返回值,或者您可以說它們返回空。不返回值的函式的返回型別為空。例如 void exit (int status);
函式引數為空 C 中有各種函式不接受任何引數。不帶引數的函式可以接受一個 void。例如 int rand(void);
指標指向void 型別為void*指標代表物件的地址,而不是型別,例如,記憶體分配函式 void *malloc( size_t size ); 返回指向 void 的指標,可以轉換為任何資料型別。

3 變數

變數是程式可操作的儲存區名稱,C中每個變數都有特定的型別,型別決定了變數的大小和佈局,該範圍內的值都可以儲存在記憶體中

C語言中有基本資料型別的變數,也可以有其他型別的變數,比如陣列,指標,列舉,結構體,共用體等

變數的定義

變數的定義就是告訴儲存器何處建立變數的儲存,以及如何建立變數的儲存,變數定義指定資料型別,幷包含該型別的一個或多個變數列表

type variable_list;
複製程式碼

type必須是一個有效的C資料型別,variable_list為變數的名字,可以有多個

int    i, j, k;
char   c, ch;
float  f, salary;
double d;
複製程式碼

上方i,j,k等宣告並定義了變數i,j,k

變數可以在宣告的時候初始化

type variable_name = value;

例如

extern int d = 3, f = 5;    // d 和 f 的宣告與初始化
int d = 3, f = 5;           // 定義並初始化 d 和 f
byte z = 22;                // 定義並初始化 z
char x = 'x';
複製程式碼

C中變數的宣告

變數的宣告,像編輯器保證變數以指定的型別和名稱存在,這樣編輯器就可以在不知道變數完整細節的情況下,也能進一步編譯

變數宣告有倆種情況

  • 一種是需要建立儲存空間的,例如:int a 在宣告的時候就已經建立了儲存空間。
  • 另一種是不需要建立儲存空間的,通過extern關鍵字宣告變數名而不定義它,例如:extern int a 其中變數 a 可以在別的檔案中定義的
  • 除非有extern關鍵字,其他的都是變數的定義
extern int i; //宣告,不是定義
int i; //宣告,也是定義
複製程式碼

4 常量

常量是固定的值,在程式期間不會改變,這些固定的值又叫做字面量

常量可以是任何基本資料型別,常量在值定義之後不會修改

定義常量

在C中有倆種定義常量的方式

  • 使用#define前處理器
#define identifier value

#include <stdio.h>
#include <limits.h>

#define FFF 10
#define DDD 20
#define HHH '\n'
int main(){
    int a ;
    a =FFF*DDD;
    printf("值,%d",a);
    printf("\n字串,%c",HHH);

    return 0 ;
}
複製程式碼
值,200
字串,
複製程式碼
  • 使用const關鍵字
const type variable = value;

int main() {
    const int FFF =10;
    const int DDD=20;
    const char HHH='\n';
    int a;
    a = FFF * DDD;
    printf("值,%d", a);
    printf("\n字串,%c", HHH);

    return 0;
}
複製程式碼
值,200
字串,
複製程式碼

5 儲存類

儲存類定義C中變數,函式的範圍和生命週期,這些說明符放在他們所修飾的型別之前,下面列出可用的儲存類

  • auto
  • register
  • static
  • extern

auto儲存類

auto儲存類是所有區域性變數預設的儲存類

{
   int mount;
   auto int month;
}
複製程式碼

上面的兩種寫法都是一樣的,auto只能用在函式內,即只能修飾區域性變數

register

用於定義儲存在暫存器中而不是RAM的區域性變數,這意味著變數的最大大小是暫存器的大小

{
   register int  miles;
}
複製程式碼

暫存器只用於需要快速訪問的變數,比如計數器

static

編譯器在宣告週期內保持保持區域性變數的值,而不需要每次進入和離開作用域是建立和銷燬,因此使用static修飾區域性變數,可以函式呼叫間保持區域性變數的值

static也可以用於全域性變數,當static修飾全域性變數時,變數的作用域會限制在他的本檔案中,也就是隻有本檔案才可以訪問(普通的全域性變數,使用extern外部宣告後任何檔案都可以訪問)

static函式:和上方的全域性變數一樣(非靜態函式可以在另一個檔案中直接引用,甚至不必使用extern宣告)

extern

用於提供一個全域性變數的引用,全域性變數對所有的程式檔案都可見

當在A檔案定義一個全域性變數a,B檔案想要使用A檔案的變數a,可以在B檔案使用extern關鍵字拿到a的引用

第一個檔案

#include <stdio.h>

int count =5;

extern void add();

int main() {
    add();
    return 0;
}
複製程式碼

第二個檔案

#include <stdio.h>

extern int count;

void add(){
    printf("count=%d\n",count);
}
複製程式碼

執行後

bogon:untitled renxiaohui$ gcc text.c tex1.c
bogon:untitled renxiaohui$ a.out 
count=5
複製程式碼

6 運算子

同java,不在記錄

7 判斷

同java

8 迴圈

同java

9 函式

定義函式

return_type function_name( parameter list )
{
   body of the function
}
複製程式碼

一個函式的組成部分

  • 返回值型別:一個函式可以返回一個值,return_type是一個函式返回值的資料型別,有些函式不返回資料,這種情況下return_type的關鍵字是void
  • 函式名稱:函式的實際名稱function_name
  • 函式引數:當呼叫函式是,需要向函式傳遞一個值,就是引數,引數可以有多個,也可以沒有
  • 函式主體:函式內實現邏輯的語句
int Max(int num1,int num2){
    int result;
    if(num1>num2){
        result=num1;
    }else{
        result=num2;
    }
    
    return result;
}
複製程式碼

這就是一個函式的簡單例項

函式宣告

函式的宣告會告訴編譯器,函式的名稱和如何呼叫函式,函式的實際主體可以單獨定義

函式宣告包括以下幾個部分

return_type function_name( parameter list );
複製程式碼

針對上方的函式我們可以宣告

int Max(int num1,int num2);
複製程式碼

在函式宣告中,引數名稱並不是很重要,可以省略掉

int Max(int ,int);
複製程式碼

當你在一個原始檔定義一個函式,在另一個檔案呼叫這個函式時,函式宣告是必要的,這種情況下,你需要在呼叫函式檔案的頂部宣告函式

呼叫函式

#include <stdio.h>

int Max(int,int);
int main() {
    int num1 = 100;
    int num2 = 120;
    int rec;
    rec = Max(num1,num2);
    printf("比較結果為%d\n",rec);
    return 0;
}

int Max(int num1, int num2) {
    int result;
    if (num1 > num2) {
        result = num1;
    } else {
        result = num2;
    }

    return result;
}
複製程式碼

結果為

比較結果為120
複製程式碼

C的作用域規則

區域性變數

在某個函式塊內宣告的變數為區域性變數,他只能被該函式或者該程式碼塊內的函式語句使用,區域性變數外部是不可知的

#include <stdio.h>
 
int main ()
{
  /* 區域性變數宣告 */
  int a, b;
  int c;
 
  /* 實際初始化 */
  a = 10;
  b = 20;
  c = a + b;
 
  printf ("value of a = %d, b = %d and c = %d\n", a, b, c);
 
  return 0;
}
複製程式碼

這裡的a,b,c 都是區域性變數

全域性變數

全域性變數定義在函式的外部,通常是程式的頂部,全域性變數在整個程式的生命週期內都是有效的,在任意的函式內部可以訪問全域性變數。

全域性變數可以被任意函式訪問,也就是說全域性變數在宣告後在整個程式中都是可用的

區域性變數和全域性變數名稱可以相同,但是在函式內,如果倆個名字相,會使用區域性變數,不會使用全域性變數

#include <stdio.h>

//全域性變數
int a = 10;
int c = 40;
int main(){
    //區域性變數
    int a = 20;
    int b =30;
    printf("a=%d\n",a);
    printf("b=%d\n",b);
    printf("c=%d\n",c);
    return 0;
}
複製程式碼

輸出

a=20
b=30
c=40
複製程式碼

全域性變數與區域性變數的區別

  • 全域性變數儲存在記憶體的全域性儲存區,佔用靜態的儲存單元
  • 區域性變數儲存在棧中,只有在函式被呼叫時,才動態的為變數分配儲存單元

10 陣列

宣告陣列

type arrayName [ arraySize ];

複製程式碼

這是一個一維陣列,arraySize必須是大於0的常量,type是任意有效的c資料型別,宣告一個含有10個double資料的nums陣列

double nums [10];
複製程式碼

初始化陣列

初始化固定數量的陣列

int nums [3] = {10,2,3}
複製程式碼

初始化數量不固定的陣列

int nums []={1,2,3,4,5}
複製程式碼

為某個陣列賦值

nums[3] = 6;
複製程式碼

訪問陣列元素

int a = num[3];
複製程式碼

例項

#include <stdio.h>


int main(){
    int n[10];
    int i,j;

    for(i=0;i<10;i++){
        n[i]=i*10;
    }

    for (int j = 0; j < 10; ++j) {
        printf("%d = %d\n",j,n[j]);
    }

}
複製程式碼

結果

0 = 0
1 = 10
2 = 20
3 = 30
4 = 40
5 = 50
6 = 60
7 = 70
8 = 80
9 = 90

複製程式碼

11 列舉

列舉是C語言中的一種基本資料型別

enum&emsp;列舉名&emsp;{列舉元素1,列舉元素2,……};
複製程式碼

假如我們定義一週七天,假如不用列舉,用常量

#define MON  1
#define TUE  2
#define WED  3
#define THU  4
#define FRI  5
#define SAT  6
#define SUN  7
複製程式碼

假如用列舉

enum  Day{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
}
複製程式碼

這樣看起來會很簡潔

注意:預設第一個成員變數為0,後面的順序加1,如果第一個是1,後面是2,以此類推

列舉變數的定義

1 先定義列舉型別在定義變數

enum DAY{
  MON=1, TUE, WED, THU, FRI, SAT, SUN
};

enum DAY day;
複製程式碼

2 定義列舉型別的同時定義列舉變數

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
複製程式碼

3 省去列舉名稱直接定義列舉變數

enum
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
複製程式碼

例項

#include <stdio.h>
enum DAY {
    MON = 1, TUE, WED, THU, FRI, SAT, SUN
};
int main() {
   enum DAY day;
   day=THU;
   printf("enun= %d\n",day);
    return 0;
}
複製程式碼

結果

enun= 4
複製程式碼

在C語言中,列舉是被當做int或者 unsigned int來處理的,所以按照C語言規範是沒有辦法遍歷列舉的

列舉在switch中使用


#include <stdio.h>
#include <stdlib.h>

enum Color {
    red=1, green, blue
};

int main() {
    enum Color enumColor;

    printf("請選擇你喜歡的顏色(1 紅色 2 綠色 3 藍色)");
    scanf("%d",&enumColor);

    switch (enumColor){
        case red:
            printf("你喜歡紅色\n");
            break;
        case green:
            printf("你喜歡綠色\n");
            break;
        case blue:
            printf("你喜歡藍色\n");
            break;
    }
    return 0;
}
複製程式碼

結果

請選擇你喜歡的顏色(1 紅色 2 綠色 3 藍色)1
你喜歡紅色
複製程式碼

12 指標

每一個變數都有一個記憶體位置,每一個記憶體位置都定義了可使用&符號訪問地址,他表示在記憶體中的一個地址

#include <stdio.h>

int main(){

    int a ;

    int b[10];

    printf("a 的記憶體地址=%p\n",&a);
    printf("b 的記憶體地址=%p\n",&b);

    return 0;
}
複製程式碼

結果

a 的記憶體地址=0x7ffee5e086c8
b 的記憶體地址=0x7ffee5e086d0
複製程式碼

什麼是指標

指標是一個變數,其值為另一個變數的記憶體地址,使用指標之前需要對其進行宣告

type *var-name;
複製程式碼

type是指標的型別,他必須是一個有效的C資料型別,var-name是指標的名稱,*用來表示這個變數是指標。

int *aa;//一個整形指標
double  *bb;//一個double型別的指標
複製程式碼

如何使用指標

使用指標會頻繁的進行如下幾個操作,定義一個指標變數,把變數地址賦值給指標,訪問指標變數中的可用地址

#include <stdio.h>

int main(){

    int a =20;//定義int值
    int *b;//定義指標

    b=&a;//為指標賦值

    printf("變數的地址值=%p\n",&a);
    printf("指標的值=%p\n",b);
    printf("指標的地址值=%d\n",*b);

    return 0;

}
複製程式碼

結果

變數的地址值=0x7ffeef4356f8
指標的值=0x7ffeef4356f8
指標的地址值=20
複製程式碼

C中的NULL指標

變數宣告的時候,如果沒有明確的地址可以賦值,為指標賦值一個NULL是一個良好的習慣,賦值為NULL被稱為空指標

int main(){
    int *var = NULL;

    printf("var的地址為=%p\n",var);

    return 0;
}
複製程式碼
var的地址為=0x0
複製程式碼

指標指向指標

#include <stdio.h>

int main(){
    int a;
    int *b;
    int **c;

    a=40;

    b=&a;

    c=&b;

    printf("a的值為=%d\n",a);

    printf("b的值為=%d\n",*b);

    printf("c的值為=%d\n",**c);

    return 0;
}
複製程式碼
a的值為=40
b的值為=40
c的值為=40
複製程式碼

傳遞陣列給函式,函式返回陣列

#include <stdio.h>

int sum(int *arr,int size);

int* getarr();

int main() {
    int a[4] = {1, 2, 3, 4};
    int v = sum(a,4);
    printf("sum=%d\n",v);

    int *p  = getarr();

    for (int i = 0; i < 4; ++i) {
        printf("陣列=%d\n",*(p+i));
        printf("陣列=%d\n",p[i]);

    }
    return 0;
}

int sum(int *arr, int size) {
     int sum = 0;
    for (int i = 0; i < size; ++i) {
        printf("i=%d\n", arr[i]);
        printf("i=%d\n", *(arr+i));

        sum+=arr[i];
    }
    return sum;
}

int * getarr(){
    static int arr[4]={2,4,5,7};

    return arr;
}
複製程式碼
i=1
i=1
i=2
i=2
i=3
i=3
i=4
i=4
sum=10
陣列=2
陣列=2
陣列=4
陣列=4
陣列=5
陣列=5
陣列=7
陣列=7

複製程式碼

指標運算

C指標用數字表示地址,因此可以進行算術運算,++,--,+,-等,假如prt是一個int型別的地址1000,那麼執行prt++,prt將指向1004,即當前位置移動4個位元組,假如prt是一個char型別的地址1000,那麼執行prt++,prt將指向1001,這個跟型別也是相關的

  • 指標的每一次遞增,他實際上會指向下一個元素的儲存單元
  • 指標的每一次遞減,他實際上指向上一個元素的儲存單元
  • 指標在遞增和遞減跳躍的位元組數,取決於指標所指向變數的資料型別的長度,比如int就是4個位元組

遞增一個指標

#include <stdio.h>

const int Max = 3;

int main() {
    int arr[3] = {1, 2, 3};
    int i ,*p;
    //給p指標賦值陣列中第一個元素的地址
    p = arr;

    for (int i = 0; i < Max; ++i) {
        printf("元素的地址=%p\n", p);
        printf("元素的地址=%d\n", *p);
        //移動到下個位置
        p++;
    }
    return 0;
}
複製程式碼
元素的地址=0x7ffee165b6ac
元素的地址=1
元素的地址=0x7ffee165b6b0
元素的地址=2
元素的地址=0x7ffee165b6b4
元素的地址=3
複製程式碼

指標陣列

有一種情況,我們陣列內可以儲存記憶體地址值

#include <stdio.h>

const int Max= 3;
int main(){
    int arr[3]={1,2,3};
    int *p[Max];

    for (int i = 0; i <Max ; ++i) {
        p[i]=&arr[i];
    }

    for (int j = 0; j < Max; ++j) {
        printf("指標陣列資料=%p\n",p[j]);
        printf("指標陣列資料值=%d\n",*p[j]);

    }
    return 0;
}
複製程式碼
指標陣列資料=0x7ffee7cda6ac
指標陣列資料值=1
指標陣列資料=0x7ffee7cda6b0
指標陣列資料值=2
指標陣列資料=0x7ffee7cda6b4
指標陣列資料值=3

複製程式碼

指向指標的指標

指標也可以指向指標


#include <stdio.h>

int main(){
    int a = 10;
    int *b;
    int **c;

    b=&a;
    c=&b;

    printf("a的值為=%d\n",a);
    printf("b的值為=%d\n",*b);
    printf("c的值為=%d\n",**c);

}
複製程式碼
a的值為=10
b的值為=10
c的值為=10
複製程式碼

傳遞指標給函式

#include <stdio.h>
#include <time.h>

void getlong(unsigned long *a);
int main() {
 unsigned long b;

 getlong(&b);

 printf("b的值為=%ld\n",b);
}

void getlong(unsigned long *a) {
    *a = time(NULL);
    return;
}
複製程式碼
b的值為=1585048748
複製程式碼

13 函式指標和回撥函式

函式指標是指向函式的指標變數,函式指標可以向普通函式一樣,傳遞引數,呼叫函式

函式返回值型別 (* 指標變數名) (函式引數列表);
複製程式碼
#include <stdio.h>

int max(int, int);

int main() {
    //p是函式指標
    int (*p)(int, int) = &max;//&可以省略
    int a, b, c, d;

    printf("請輸入三個數字:");
    scanf("%d,%d,%d", &a, &b, &c);
    d = p(p(a, b), c);//相當於呼叫max(max(a,b),c);
    printf("d的值為=%d\n", d);
    return 0;
}

int max(int a, int b) {
    return a > b ? a : b;
}
複製程式碼

輸出

請輸入三個數字:1,2,3
d的值為=3
複製程式碼

將指標函式當做引數傳遞給函式

#include <stdio.h>
#include <stdlib.h>


void setvalue(int *arr, int b, int(*p)(void)) {
    for (int i = 0; i < b; ++i) {
        arr[i] = p();
    }
}

int stett(int(*p)(void)){
    return p();
}

int getvalue(void) {
    return rand();
}

int main() {
    int arr[10];

    setvalue(arr, 10, getvalue);
    for (int i = 0; i < 10; ++i) {
        printf("i=%d\n", arr[i]);
    }

    int b;
    b= stett(getvalue);
    printf("b=%d\n", b);


    return 0;
}


複製程式碼

結果

i=16807
i=282475249
i=1622650073
i=984943658
i=1144108930
i=470211272
i=101027544
i=1457850878
i=1458777923
i=2007237709
b=823564440
複製程式碼

14 字串

C語言定義字串Hello,兩種形式,字串和字元陣列的區別:最後一位是否是空字元

#include <stdio.h>

int main(){
    char hello[6]= {'H','e','l','l','o','\0'};
    char hello1[]= "hello";
    char *hello2="hello";

    printf("測試=%s\n",hello);
    printf("測試=%s\n",hello1);
    printf("測試=%s\n",hello2);

    return 0;
}
複製程式碼
測試=Hello
測試=hello
測試=hello
複製程式碼

15 結構體

結構體可以儲存不同型別的資料項

定義一個結構體

struct tag { 
    member-list
    member-list 
    member-list  
    ...
} variable-list ;
複製程式碼
  • tag:結構體標籤
  • member-list:結構體資料項
  • variable-list:結構體變數
struct Book {
    int book_id ;
    char title[50];
    char author[50];
    char subject[50];
} book;
複製程式碼

在一般情況下,tag、member-list、variable-list 這 3 部分至少要出現 2 個。以下為例項:

//此宣告宣告瞭擁有3個成員的結構體,分別為整型的a,字元型的b和雙精度的c
//同時又宣告瞭結構體變數s1
//這個結構體並沒有標明其標籤
struct 
{
    int a;
    char b;
    double c;
} s1;
 
//此宣告宣告瞭擁有3個成員的結構體,分別為整型的a,字元型的b和雙精度的c
//結構體的標籤被命名為SIMPLE,沒有宣告變數
struct SIMPLE
{
    int a;
    char b;
    double c;
};
//用SIMPLE標籤的結構體,另外宣告瞭變數t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
 
//也可以用typedef建立新型別
typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;
//現在可以用Simple2作為型別宣告新的結構體變數
Simple2 u1, u2[20], *u3;
複製程式碼

結構體中可以含有其他結構體和指標

//結構體包含其他結構體
struct AA{
    int a;
    struct Book b;
};
//結構體包含自己的指標
struct BB{
    int b;
    struct BB *nextbb
};

複製程式碼

結構體的初始化

struct Book {
    int book_id ;
    char title[50];
    char author[50];
    char subject[50];
} book= {1,"c語言","小明","bb"};
複製程式碼
title : c語言
author: 小明
subject: bb
book_id: 1
複製程式碼

訪問結構體的成員

訪問結構體的成員可以用.符號,比如上方的book.title;

int main(){

    struct Book book1;

    strcpy(book1.title,"學習書");
    strcpy(book1.author,"小紅");
    strcpy(book1.subject,"111");

    book1.book_id=222;


    printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book1.title, book1.author, book1.subject, book1.book_id);

    return 0;
}
複製程式碼
title : 學習書
author: 小紅
subject: 111
book_id: 222
複製程式碼

結構體作為函式的引數


void printstuct(struct Book book);
int main(){

    struct Book book1;

    strcpy(book1.title,"學習書");
    strcpy(book1.author,"小紅");
    strcpy(book1.subject,"111");

    book1.book_id=222;


    printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book1.title, book1.author, book1.subject, book1.book_id);
    printstuct(book1);
    return 0;
}


void printstuct(struct Book book){
    printf( "Book title : %s\n", book.title);
    printf( "Book author : %s\n", book.author);
    printf( "Book subject : %s\n", book.subject);
    printf( "Book book_id : %d\n", book.book_id);
}
複製程式碼
title : 學習書
author: 小紅
subject: 111
book_id: 222
Book title : 學習書
Book author : 小紅
Book subject : 111
Book book_id : 222
複製程式碼

指向結構體的指標

定義,賦值,呼叫

struct Books *struct_pointer;

struct_pointer = &Book1;

struct_pointer->title;
複製程式碼
int main() {

    struct Book book1;

    strcpy(book1.title, "學習書");
    strcpy(book1.author, "小紅");
    strcpy(book1.subject, "111");

    book1.book_id = 222;
    printstuct1(&book1);
    return 0;
}

void printstuct1(struct Book *book) {
    printf("Book title : %s\n", book->title);
    printf("Book author : %s\n", book->author);
    printf("Book subject : %s\n", book->subject);
    printf("Book book_id : %d\n", book->book_id);
}
複製程式碼
Book title : 學習書
Book author : 小紅
Book subject : 111
Book book_id : 222
複製程式碼

16 共用體

共用體是一個特殊的資料型別,允許在相同的儲存位置,儲存不同的資料型別,可以定義一個帶有多個成員的共用體,但是任何時候只能一個成員帶值

定義共用體

union定義共用體,

union [union tag]
{
   member definition;
   member definition;
   ...
   member definition;
} [one or more union variables];
複製程式碼

union tag是可選的,每個member definition;都是標準的變數定義,如int i char b等,在分號之前可以定義一個或多個共用體變數是可選的

定義一個成員有int,float,char[]的共用體


#include <stdio.h>
#include <string.h>


union Data {
    int a;
    char b[100];
    float c;
};

int main() {

    union Data data;

    printf("資料長度=%lu\n", sizeof(data));

    data.a = 1;
    data.c = 10.00;
    strcpy(data.b, "測試資料");
    
    printf("資料%d\n",data.a);
    printf("資料%f\n",data.c);
    printf("資料%s\n",data.b);
  

    return 0;
}
複製程式碼
資料長度=100
資料-393497114
資料-5278115000342806695772160.000000
資料測試資料

複製程式碼

我們看到資料a,c成員的值有損壞,是因為最後賦值的變數佔用了的記憶體位置,這也是b變數可以正確輸出的原因

我們同一時間只能使用一個變數


#include <stdio.h>
#include <string.h>


union Data {
    int a;
    char b[100];
    float c;
};

int main() {

    union Data data;

    printf("資料長度=%lu\n", sizeof(data));

    data.a = 1;
    printf("資料%d\n",data.a);

    data.c = 10.00;
    printf("資料%f\n",data.c);

    strcpy(data.b, "測試資料");
    printf("資料%s\n",data.b);




    return 0;
}
複製程式碼
資料長度=100
資料1
資料10.000000
資料測試資料
複製程式碼

在這裡所有的成員都可以正確輸出,是因為同一時間只用到了一個變數

17 typedef

C語言提供typedef關鍵字,可以使用它為型別起一個新的名字

#include <stdio.h>

typedef unsigned int TEXT;
int main(){
    TEXT a = 11;

    printf("引數為=%d\n",a);
    return 0;
}
複製程式碼
引數為=11
複製程式碼

也可以為使用者自定義的資料型別去一個新的名字,比如結構體

#include <stdio.h>
#include <string.h>

typedef unsigned int TEXT;

typedef struct BOOKS {
    char a[50];
    char b[50];
} Book;

int main() {
    TEXT a = 11;
    printf("引數為=%d\n", a);

    Book book;

    strcpy(book.a, "測試1");
    strcpy(book.b, "測試2");
    printf("a=%s\n", book.a);
    printf("b=%s\n", book.b);


    return 0;
}
複製程式碼
引數為=11
a=測試1
b=測試2
複製程式碼

typedef和#define 的區別

#define是一個C指令,用於為各種資料型別定義別名,與typedef類似,但是他有以下幾種不同

  • typedef僅限於為型別定義符號名稱,#define不僅為型別定義別名,也可以為數值定義別名
  • typedef為編譯器解釋執行,#define為預編譯器進行處理
#define TRUE 0
#define FALSE 1

int main() {
    printf("數值為=%d\n", TRUE);
    printf("數值為=%d\n", FALSE);
    return 0;
}
複製程式碼
數值為=0
數值為=1
複製程式碼

18 輸入和輸出

getchar() & putchar() 函式

int getchar(void) 函式從螢幕讀取下一個可用的字元,並把它返回為一個整數。這個函式在同一個時間內只會讀取一個單一的字元。您可以在迴圈內使用這個方法,以便從螢幕上讀取多個字元。

int putchar(int c) 函式把字元輸出到螢幕上,並返回相同的字元。這個函式在同一個時間內只會輸出一個單一的字元。您可以在迴圈內使用這個方法,以便在螢幕上輸出多個字元。

#include <stdio.h>

int main(){
    printf("請輸入一個字元\n");

    int  a = getchar();

    printf("你的輸入為\n");

    putchar(a);

    printf("\n");
}
複製程式碼
請輸入一個字元
text
你的輸入為
t
複製程式碼

gets() & puts() 函式

char *gets(char *s)函式從stdin讀取一行到s指向的快取區,直到一個終止符或一個EOF int puts(const char *s)函式吧一個字串s和一個尾隨的換行符寫入stdout

#include <stdio.h>

int main(){
    char a[100];

    printf("輸入你的字元\n");
    gets(a);

    printf("你輸入的字元為\n");

    puts(a);
    return 0;
}
複製程式碼
輸入你的字元
text
你輸入的字元為
text

複製程式碼

scanf() 和 printf() 函式

int scanf(const char *format, ...)函式從標準輸入流 stdin 讀取輸入,並根據提供的 format 來瀏覽輸入。

int printf(const char *format, ...)函式把輸出寫入到標準輸出流 stdout ,並根據提供的格式產生輸出。

format 是一個簡單的常量字串,但是你可以定義%s,%d,%c,%f來讀取字串,數字,字元,浮點數

#include <stdio.h>

int main() {
    char a[100];
    int b;

    printf("輸入文字\n");

    scanf("%s%d", a, &b);

    printf("你輸入的文字=%s,%d\n",a,b);
    return 0;
}
複製程式碼
輸入文字
text 123
你輸入的文字=text,123
複製程式碼

19 檔案讀寫

#include <stdio.h>

int main() {
    FILE *file;

    //開啟檔案允許讀寫,如果不存在則建立該檔案
    file = fopen("/Users/renxiaohui/Desktop/test.txt", "w+");
    //寫入一行
    fprintf(file, "this is a text\n");
    //再寫入一行
    fputs("this is a text aaaa\n", file);

    fclose(file);

}
複製程式碼

會在建立檔案test.txt,並寫入倆行文字

#include <stdio.h>

int main() {
    FILE *file1;

    char a[255];
    //開啟一個檔案,只讀
    file1 = fopen("/Users/renxiaohui/Desktop/test.txt", "r");
    //讀取檔案,當遇到第一個空格或者換行符會停止讀取
    fscanf(file1, "%s", a);
    printf("1= %s\n", a);

    //讀取字串到緩衝區,遇到換行符或檔案的末尾 EOF返回
    fgets(a, 255, file1);
    printf("2= %s\n", a);

    fgets(a, 255, file1);
    printf("3= %s\n", a);

    //關閉檔案  
    fclose(file1);
}
複製程式碼

讀取剛才建立的檔案

1= this
2=  is a text
3= this is a text aaaa
複製程式碼

20 前處理器

C前處理器不是編譯器的組成部分,他是編譯過程中的一個單獨步驟,他們會在編譯器實際編譯之前完成所需的預處理

所有的前處理器都是以(#)開頭的,他必須是第一個非空字元,為了增強可讀性,前處理器應該從第一列開始,下面列出比較重要的前處理器

指令 描述
#define 定義巨集
#undef 取消定義的巨集
#include 包含一個原始檔程式碼
#ifdef 如果巨集已經定義則返回真
#ifndef 如果巨集沒有定義則返回真
#if 如果給定條件為真則編譯一下程式碼
#else #if的替代方案
#elseif 如果前面的 #if 給定條件不為真,當前條件為真,則編譯下面程式碼
#endif 結束一個#if。。#else 語句
#error 遇到標準錯誤是,輸出錯誤訊息
#pragma 使用標準化方法,向編譯器釋出特殊指令到編譯器中去

預編譯器例項

#define MAX 20
複製程式碼

這個指令表示,把所有的MAX定義為20,使用#define定義常量可以增加可讀性

#include <stdio.h>
#include "myheader.h"
複製程式碼

第一個表示從系統庫獲取stdio.h庫,並新增到原始檔中,一個是從本地目錄獲取myheader.h,並新增到原始檔中

#undef FILE 
#define FILE 99
複製程式碼

取消已經定義的FILE,並把它重新定義為99

#ifdef MESSAGE
    #define MESSAGE "you wish"
#endif
複製程式碼

這個表示只有MESSAGE未定義時,才定義MESSAGE為you wish

預定義巨集

ANSI C中預定義巨集,我們可以在程式設計中使用這些巨集,但不可以改變他的值

巨集 描述
1__DATE__ 當前日期,一個以 "MMM DD YYYY" 格式表示的字元常量。
1__TIME__ 當前時間,一個以 "HH:MM:SS" 格式表示的字元常量。
1__FILE__ 這會包含當前檔名,一個字串常量。
1__LINE__ 這會包含當前行號,一個十進位制常量。
1__STDC__ 當編譯器以 ANSI 標準編譯時,則定義為 1。

看下使用

#include <stdio.h>
int main() {

    printf("data=%s\n",__DATE__);
    printf("file=%s\n",__FILE__);
    printf("time=%s\n",__TIME__);
    printf("line=%d\n",__LINE__);
    printf("stdic=%d\n",__STDC__);

    return 0;
}
複製程式碼
data=Mar 31 2020
file=text27.c
time=15:31:49
line=19
stdic=1
複製程式碼

引數化巨集

引數化巨集可以模擬函式,例如下面是一個計算數字平方的方法

int square(int x) {
   return x * x;
}
複製程式碼

可以用引數化巨集來表示

#define square(x) ((x)*(x))
複製程式碼

在使用引數化巨集之前必須使用#define來定義,引數列表必須包含在圓括號內的,並且必須緊跟在巨集名稱之後,不允許有空格

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

int main() {

    printf("平方為=%d\n",square(5));

    return 0;
}
複製程式碼
平方為=25
複製程式碼

預處理運算子

巨集延續運算子(\)

一個巨集通常寫在一個單行上,但是如果巨集太長,一個單行容不下,則使用巨集延續運算子(\)例如

#define message_for(a,b) \
            printf(#a"and "#b"we are friend\n")
複製程式碼

字串常量化運算子(#)

在巨集定義中,需要把一個巨集的引數轉化為字串常量時,則使用字串常量化運算子(#)例如:

#define message_for(a,b) \
            printf(#a"and "#b"we are friend\n")

int main() {

    message_for("xiaoming","xiaohong");

    return 0;
}
複製程式碼
"xiaoming"and "xiaohong"we are friend
複製程式碼

** 標記貼上運算子(##)**

直接看程式碼比較

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


int main() {

    int token34 =40;

    add(34);

    return 0;
}
複製程式碼
token34 = 40
複製程式碼

這個是怎麼發生的,因為這個實際會從編譯器中產出以下實際輸出

printf ("token34 = %d", token34);
複製程式碼

defined()運算子

前處理器defined運算子是在常量表示式中的,用來確定一個識別符號是否已經#define定義過,如果定義過為真,如果沒有定義過值為假

#if !defined(TEXT)
#define TEXT "text\n"
#endif


int main() {

    printf(TEXT);

    return 0;
}
複製程式碼
text
複製程式碼

21 標頭檔案

相關文章