Android JNI和NDK學習(基礎篇):C語言基礎

概述

C語言對於Android開發來說還是非常必要的,不管你是要閱讀源碼,還是想要學習NDK,音視頻,性能優化等,都不可避免需要接觸到C,而且C語言屬於系統級的語言,操作系統內核都有C的身影,所以我今天學習一下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

10 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

全局變量與局部變量的區別

  • 全局變量保存在內存的全局儲存區,佔用靜態的儲存單元
  • 局部變量保存在棧中,只有在函數被調用時,才動態的爲變量分配儲存單元

11 數組

聲明數組

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

12 枚舉

枚舉是C語言中的一種基本數據類型

enum 枚舉名 {枚舉元素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
你喜歡紅色

13 指針

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

#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

14 函數指針和回調函數

函數指針是指向函數的指針變量,函數指針可以向普通函數一樣,傳遞參數,調用函數

函數返回值類型 (* 指針變量名) (函數參數列表);
#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

15 字符串

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

16結構體

結構體可以存儲不同類型的數據項

定義一個結構體

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

17 共用體

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

定義共用體

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
數據測試數據

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

18 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

19 輸入和輸出

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

20 文件讀寫

#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

21 預處理器

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

22 頭文件

頭文件是後綴名爲.h的文件,包含了函數聲明和宏定義,有倆種頭文件系統頭文件和c程序員自己編寫的頭文件

在C程序中建議把所有的常量,宏,系統全局變量,和函數原型寫入頭文件中

引入頭文件

使用預處理命令#include 引入頭文件,有倆種類型

#include <file>

這種形式用於引用系統頭文件,他在系統目錄搜索頭文件

#include "file"

這種形式用於引用用戶頭文件,他在當前的文件目錄搜索頭文件

只引用一次頭文件

如果一個頭文件引用被引用倆次,編譯器會處理倆次頭文件的內容,這將產生錯誤,爲了防止這種情況,標準的做法

#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif

如果沒有定義HEADER_FILE則定義引入頭文件,如果下次在引用因爲HEADER_FILE已經定義則不會再次引入

關於.h和.c的區別請看這篇文章

https://www.cnblogs.com/laojie4321/archive/2012/03/30/2425015.html

23 錯誤處理

當C語言發生錯誤時,大多數的C會返回1或NULL,同時會設置一個錯誤代碼errno,改錯誤代碼是全局變量,表示執行函數期間發生了錯誤,可以在 errno.h頭文件中找到各種錯誤代碼

所以程序員可以通過返回值來判斷是否有錯誤,開發人員在初始化時吧errno初始化爲0,是一個良好的編程習慣,0表示沒有錯誤

errno、perror() 和 strerror()

C語言提供了perror() 和 strerror()來顯示errno相關的文本信息

  • perror()函數:他將顯示你傳入的文字後面跟一個冒號和來errno相關的文本信息
  • strerror()函數:返回一個指針,指向當前 errno 值的文本表示形式。
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main() {
    FILE *pf;
    int errnum;
    pf = fopen("aaa.txt", "rb");

    if (pf==NULL){
        //stderr,輸出到屏幕
        printf("錯誤號爲%d\n",errno);
        fprintf(stderr,"錯誤爲%s\n",strerror(errno));
        perror("perror顯示錯誤");
    }
    return 0;
}
錯誤號爲2
錯誤爲No such file or directory
perror顯示錯誤: No such file or directory

24 內存管理

C語言爲內存的分配提供了幾個函數,在<stdlib.h>文件頭中可以找到

函數 描述
void *calloc(int num, int size); 在內存中動態的分配num個長度爲size的連續空間,並將每一個字節初始化爲0
void free(void *address); 該函數釋放address所指向的內存塊,釋放的是動態分配的內存空間
void *malloc(int num); 在堆區分配一塊指定大小的內存空間,用來儲存數據,該內存空間在函數執行完之後不會初始化,他們的值是位置的
void *realloc(void *address, int newsize); 該函數重新分配內存,新內存分配到newsize大小

注意:void* 表示未確定類型的指針,void*類型指針可以通過強制轉換轉換爲任意其他類型的指針

動態分配內存

編程時如果你預先知道數組的大小,那麼定義數組時會比較容易,比如一個可以容納100個字符的數組

char name[100];

但是如果不知道需要儲存文本長度的,那就需要動態分配內存了

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

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

    strcpy(a, "固定數組100");

    //動態分配內存
    b = (char *) malloc(200 * sizeof(char));
//    b= calloc(200, sizeof(char));
    if (b == NULL) {
        printf("分配內存失敗");
    } else {
        strcpy(b, "動態分配內存成功");
    }
    printf("值爲=%s\n",b);
    //重新分配內存
    b = realloc(b, 500 * sizeof(char));
    if (b == NULL) {
        printf("分配內存失敗");
    } else {
        strcpy(b, "重新分配內存成功");
    }

    printf("值爲=%s\n",b);

    //釋放內存
    free(b);
    return 0;
}
值爲=動態分配內存成功
值爲=重新分配內存成功

上面分配內存的代碼也可以替換爲

    b= calloc(200, sizeof(char));

25 命令行參數

執行程序時可以從命令行傳遞參數給程序,這些值爲命令行參數,命令行參數是通過main函數參數來處理的,其中,argc 是指傳入參數的個數,argv[] 是一個指針數組,指向傳遞給程序的每個參數

#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc == 2) {
        printf("參數爲 %s\n", argv[1]);
    } else if (argc > 2) {
        printf("參數超過倆個\n");
    } else {
        printf("只有一個參數\n");
    }
    return 0;
}
L-96FCG8WP-1504:untitled renxiaohui$ gcc text30.c 
L-96FCG8WP-1504:untitled renxiaohui$ a.out text
參數爲 text

參考

https://www.runoob.com/cprogramming/c-tutorial.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章