NDK系列03—— C 語言基礎(chǔ)知識鞏固


????這是NDK系列的第三章,將會學(xué)習(xí)C語言的基礎(chǔ)知識。主要分為以下幾個內(nèi)容:

  • 函數(shù)指針與指針運(yùn)算
  • 靜態(tài)開辟內(nèi)存與動態(tài)開辟內(nèi)存
  • 指針對字符串的操作運(yùn)用
  • 結(jié)構(gòu)體與結(jié)構(gòu)體指針數(shù)組
  • 數(shù)組與數(shù)組指針
  • 加載文件 & 對文件的加密 / 解密

一、函數(shù)指針

1.1 函數(shù)指針 demo
#include <stdio.h>

void add(int num1, int num2); // 先聲明

void mins(int num1, int num2) {
    printf("num1 - num2 = %d\n", (num1 - num2));
}

// 操作 回調(diào)到  add  mins
// void(*method)(int,int)  聲明好 函數(shù)指針
// void 返回值
// (*method) 函數(shù)名
// (int,int) 兩個參數(shù)
void opreate(void(*method)(int,int), int num1, int num2) {
    method(num1, num2);

    printf("opreate函數(shù)的 method指針是多少:%p\n", method);
}

// 7.函數(shù)指針。(回調(diào))  Java接口的回調(diào)
int main() {  // 【第一種寫法】

    opreate(add,  10, 10);
    opreate(mins,  100, 10);

    // 原理是什么?

    printf("main函數(shù)的 add指針是多少:%p\n", add);
    printf("main函數(shù)的 mins指針是多少:%p\n", mins);

    // &add和add是一樣的值嗎
    printf("%p, %p\n", add, &add); //  004018CE, 004018CE  一樣的

    return 0;
}

// 再實現(xiàn) 使用
void add(int num1, int num2) {
    printf("num1 + num2 = %d\n", (num1 + num2));
}
1.2 什么是函數(shù)指針

????所謂函數(shù)指針,可以 "野蠻" 的認(rèn)為將函數(shù)作為方法參數(shù)的一部分。現(xiàn)在對上面的demo進(jìn)行分解:

  • 首先定義出兩個方法:add(int num1, int num2)mins(int num1, int num2)。
  • 定義出一個方法:

opreate(void(*method)(int,int), int num1, int num2)。

其間第一個入?yún)⒕褪?函數(shù)指針,在上一篇 NDK 的文章說過在 C 語言中一切都可以看作是指針,都有其自己的地址,因此這里我們傳進(jìn)來一個 函數(shù) 實際上傳入一個 指針,因此這個參數(shù)又叫 函數(shù)指針,后面括號里的參數(shù),就是這個函數(shù)所需要的參數(shù)。

1.3 函數(shù)指針的地址

????先來看一下上面 demo 的運(yùn)行結(jié)果:

num1 + num2 = 20
opreate函數(shù)的 method指針是多少:0000000000401635
num1 - num2 = 90
opreate函數(shù)的 method指針是多少:0000000000401550
main函數(shù)的 add指針是多少:0000000000401635
main函數(shù)的 mins指針是多少:0000000000401550
0000000000401635, 0000000000401635

????從上面的運(yùn)行結(jié)果更進(jìn)一步說明傳進(jìn)去的就是一個 指針,只不過名字叫做 函數(shù)指針 罷了。

  • 在上面的 opreate 函數(shù)中接受 addmins 函數(shù)的地址,然后在 opreate 函數(shù)中進(jìn)行調(diào)用。
  • 從最后一句運(yùn)行結(jié)果可以知道 傳入的函數(shù)無論是 add 還是 &add 其地址都是一樣的,其作用都是將此函數(shù)的地址傳進(jìn)去。
    函數(shù)指針

二、靜態(tài)開辟內(nèi)存與動態(tài)開辟內(nèi)存

2.1 靜態(tài)開辟內(nèi)存 demo
#include <stdio.h>
#include <unistd.h>

// 函數(shù)進(jìn)棧 定義一個int arr[5];  定義一個 int i;  (靜態(tài)的范疇)

// 進(jìn)棧
void staticAction() {
    int arr[5]; // 靜態(tài)開辟 棧區(qū) (棧成員)

    for (int i = 0; i <5; ++i) {
        arr[i] = i;
        printf("%d, %p\n", *(arr + i), arr + i);
    }
} // 函數(shù)的末尾會彈棧(隱式):執(zhí)行完畢會彈棧  會釋放所有的棧成員

// 2.靜態(tài)開辟。
int main() {
    // int 4 * 10 = 40M
    // int arr[10 * 1024 * 1024]; // 10M * 4 = 40M  會棧溢出

    // int arr[1 * 1024 * 1024]; 會棧溢出

    int arr[(int)(0.2 * 1024 * 1024)]; //  不會棧溢出

    // 棧區(qū):占用內(nèi)存大小 最大值: 大概 2M  大于2M會棧溢出  平臺有關(guān)系的
    // 堆區(qū):占用內(nèi)存大小 最大值: 大概80%  40M沒有任何問題,基本上不用擔(dān)心 堆區(qū)很大的
                               // 大概80%: Windows系統(tǒng) 給我們的編譯器給予的空間  的 百分之百八十

    while (9) {
        sleep(100);
        staticAction(); // 調(diào)用開辟20
    }

    return (0);
}

????對于以上的 demo 我們需要了解以下幾個知識點:

  • 靜態(tài) 指的是一般的定義一個變量或指針 / 數(shù)組等,如:int arr[5]; / int i;
  • 當(dāng)執(zhí)行一個函數(shù)開始時,從函數(shù)的起始就是 進(jìn)棧,當(dāng)函數(shù)執(zhí)行到最后一個 大括號時也就是函數(shù)的末尾時,函數(shù)會進(jìn)行 彈棧,會將函數(shù)中所有的棧成員進(jìn)行釋放操作,注意這是一個隱式操作。
  • 棧區(qū)的大?。赫加脙?nèi)存大小 最大值: 大概 2M 大于2M會棧溢出 平臺有關(guān)系的
  • 堆區(qū)的大?。赫加脙?nèi)存大小 最大值: 大概80% 40M沒有任何問題,基本上不用擔(dān)心 堆區(qū)很大的
    大概80%: Windows系統(tǒng) 給我們的編譯器給予的空間 的 百分之百八十

以下是對上面demo的畫圖解釋。

靜態(tài)開辟棧區(qū)
2.2 動態(tài)開辟內(nèi)存
所需場景:

????靜態(tài)開辟的內(nèi)存空間大小,是不能修改的,如果不需要動態(tài)修改空間大小,當(dāng)然使用棧區(qū),盡量使用 靜態(tài)開辟的,如果實在是需要動態(tài)改變,才使用動態(tài)開辟。

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

int main() {

    int num;
    printf("請輸入數(shù)的個數(shù):");

    // 獲取用戶輸入的值
    scanf("%d", &num);

    // 動態(tài)開辟 用戶輸入的值 空間的大小   【堆區(qū)】
    int * arr = malloc(sizeof(int) * num);
    // int arr2 []   ==   int * arr  一樣的了

    int print_num;
    // 循環(huán)接收
    for (int i = 0; i < num; ++i) {
        printf("請輸入第%d個的值:", i);

        // 獲取用戶輸入的值
        scanf("%d", &print_num);

        arr[i] = print_num;
        printf("每個元素的值:%d, 每個元素的地址:%p\n", *(arr + i), arr + i);
    }

    // for 循環(huán)打印
    for (int i = 0; i < num; ++i) {
        printf("輸出元素結(jié)果是:%d\n", arr[i]); // arr[i] 隱士 等價與 * (arr + i)
    }


    return 0;
}

上面看到通過 malloc(size_t _Size) API 就可以在內(nèi)存上動態(tài)開辟出一塊空間。其中所需參數(shù)是開辟內(nèi)存的大小。

2.3 動態(tài)開辟內(nèi)存-重新開辟(擴(kuò)容)

????剛才上面的 demo 已經(jīng)通過 malloc(size_t _Size) API 在內(nèi)存動態(tài)開辟一塊內(nèi)存了,那么此時如果還想再繼續(xù)再動態(tài)開辟內(nèi)存的話該如何操作呢?且看一下這個demo:

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

// 動態(tài)開辟之realloc
int mainT0() {

    int num;
    printf("請輸入個數(shù)");
    // 獲取用戶輸入的值
    scanf("%d", &num);

    // 5個值
    int * arr = (int *) malloc(sizeof(int) * num);
    for (int i = 0; i < num; ++i) {
        arr[i] = (i + 10001); // arr[i]的內(nèi)部隱士 == *(arr+i)
    }
    printf("開辟的內(nèi)存指針: %p\n", arr);

    // =================================   在堆區(qū)開辟新的空間  加長空間大小

    // C的崗位
    // C工程師的面試題:  realloc 為什么一定要傳入 arr指針,為什么要傳總大小

    // 新增
    int new_num;
    printf("請輸入新增加的個數(shù)");
    scanf("%d", &new_num);

    // 原來的大小4 + 新增加的大小4  =  總大小 8
    // void *realloc (void *前面開辟的指針, size_t總大小);
    int * new_arr = (int *) realloc(arr, sizeof(int) * (num + new_num));

    if (new_arr) { // new_arr != NULL 我才進(jìn)if  【非0即true】
        int j = num; // 4開始
        for (; j < (num + new_num); j++) { // 5 6 7 8
            arr[j] = (j + 10001);
        }

        printf("新 開辟的內(nèi)存指針: %p\n", new_arr);

        // 后 打印 內(nèi)容
        for (int i = 0; i < (num + new_num); ++i) {

            printf("新 元素的值:%d, 元素的地址:%p\n",
                   *(arr + i),
                   (arr + i)
            );
        }
    }

    // 我已經(jīng)釋放
    /*free(new_arr);
    new_arr = NULL;*/

    // 1000行代碼
    // 。。。

    //  重復(fù)釋放/重復(fù)free VS會奔潰,   CLion會優(yōu)化(發(fā)現(xiàn)不奔潰)   [錯誤的寫法]
    /*free(new_arr);
    new_arr = NULL;*/

    // 必須釋放【規(guī)則】
    /*if (arr) {
        free(arr);   // 如果不賦值給NULL,就是懸空指針了
        arr = NULL;
    }*/

    /*if (new_arr) {
        free(new_arr);   // 如果不賦值給NULL,就是懸空指針了
        new_arr = NULL;
    }*/

if (new_arr) { // new_arr != NULL 進(jìn)去if, 重新開辟的堆空間是成功的
        free(new_arr);
        new_arr = NULL;
        arr = NULL; // 他還在指向那塊空間,為了不出現(xiàn)懸空指針,指向NULL的空間
    } else { // 重新開辟的堆空間是失敗的
        free(arr);
        arr = NULL;
    }
    return 0;
}

注意點:

  • 在進(jìn)行釋放操作時應(yīng)先判斷對象是否存在,避免發(fā)生重復(fù)釋放的問題
  • 釋放完對象之后還應(yīng)該同時釋放指針對應(yīng)的空間,避免出現(xiàn)懸空指針
  • 當(dāng)使用 realloc API 時,可能會出現(xiàn)創(chuàng)建新指針失敗的情況,當(dāng)出現(xiàn)創(chuàng)建失敗的情況時(資源不夠時,程序較多時),則應(yīng)釋放原來的對象以及指針
  • realloc(void *_Memory,size_t _NewSize); 這個API中兩個入?yún)?,第一個傳進(jìn)的是第一次動態(tài)創(chuàng)建的指針,第二個傳入的是兩個指針的總大小。傳入第一個指針是為了方便拷貝(兩個內(nèi)存進(jìn)行拼接時,需要先拷貝的第一個指針)傳入總大小是為了判斷內(nèi)存是否足夠創(chuàng)建新的內(nèi)存。

三、字符串的操作

3.1 字符串兩種形式

????在 C 語言可以通過兩種方式來聲明字符串:數(shù)組指針

#include <stdio.h>

// 字符串
int mainT2() {
    char str[] = {'D', 'e', 'r', 'r', 'y', '\0'};
    str[2] = 'z'; // 這里能修改?
    printf("第一種方式:%s\n", str); // printf 必須遇到 \0 才結(jié)束

    char * str2 = "Derry"; // 隱式  Derry+\0
    str2[2] = 'z'; // 會崩潰,為什么,不允許訪問,為什么 
    printf("第二種方式:%s\n", str);

    return 0;
}

上面 demo 中通過兩種方式創(chuàng)建了兩個字符串,其中第一種方式可以修改不同位置的元素值,而第二種方式通過相同的方式則無法修改元素的值,會發(fā)生崩潰,這是為什么呢?
同時可以看到上面通過第一個方式創(chuàng)建時會看到在數(shù)組的末尾會拼接一個 '\0',這是因為當(dāng)通過 printf 方法打印通過數(shù)組創(chuàng)建字符串時需要添加一個結(jié)束標(biāo)志位,否則此時 printf 方法不知道字符串已經(jīng)結(jié)束。

我們首先要有一個概念:在程序運(yùn)行,執(zhí)行main 函數(shù)時就發(fā)生了進(jìn)棧的操作,同時程序中還存在著全局區(qū)/靜態(tài)區(qū)域的地方,里面存在一些字符串的常量等數(shù)據(jù)。

  • 第一種方式修改:通過數(shù)組的方式創(chuàng)建字符串,其實是將靜態(tài)區(qū)中 Derry 這個字符串拷貝到 main 函數(shù)棧中,其操作的是 Derry 這個字符的副本,因此不會發(fā)生錯誤。

  • 第二種方式修改:通過指針的方式創(chuàng)建字符串,則是直接將 str2 新建出來的指針的地址指向靜態(tài)區(qū)中的 Derry 字符串,此時如果通過 str2 進(jìn)行修改,則修改的是靜態(tài)區(qū)中的數(shù)據(jù),而 靜態(tài)區(qū)中的數(shù)據(jù)是不允許修改的。

以下是上面兩種方式的圖示:

兩種方式修改原理
3.2 字符串轉(zhuǎn)換,字符串的比較
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // 字符串轉(zhuǎn)換 =======================
    char * num = "1"; // 字符串
    num = "12.68";

    // 【int】
    int result =  atoi(num);
    if (result) { // 非0即ture  不是0進(jìn)入if,  0就是轉(zhuǎn)換失敗了
        printf("恭喜你轉(zhuǎn)換成功:%d\n", result);
    } else {
        printf("轉(zhuǎn)換失?。n");
    }

    // 【double】
    double resultD =  atof(num);
    printf("恭喜你轉(zhuǎn)換成功:%lf\n", resultD);


    // 字符串的比較 ======================
    char * str1 = "Derry";
    char * str2 = "derry";

    // int resultC = strcmp(str1, str2); // strcmp = 區(qū)分大小寫
    int resultC = strcmpi(str1, str2); // strcmpi = 不區(qū)分大小寫

    if (!resultC) { // 0代表是相等的, 非0代表是不相等的
        printf("相等");
    } else {
        printf("不相等");
    }

    return 0;
}

執(zhí)行結(jié)果:


字符串轉(zhuǎn)換,字符串的比較

這個demo主要演示字符串轉(zhuǎn)換,字符串的比較在C語言中相關(guān)API 的使用:

  • atoi 將字符串轉(zhuǎn)換成 int
  • double - 將字符串轉(zhuǎn)換成 double
  • strcmp 區(qū)分大小寫的比較字符串
  • strcmpi 不區(qū)分大小寫的比較字符串
3.3 字符串兩種形式字符串查找,包含,拼接
// 字符串查找,包含,拼接。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {

    char * text = "name is Derry";
    char * subtext = "D";

    char * pop = strstr(text, subtext);

    // 怎么去 字符串查找
    if (pop) { // 非NULL,就進(jìn)入if,就查找到了
        printf("查找到了,pop的值是:%s\n", pop);
    } else {
        printf("沒有查找到,subtext的值是:%s\n", subtext);
    }

    // 包含了D嗎
    if (pop) {
        printf("包含了\n");
    } else {
        printf("沒有包含\n");
    }

    // printf("pop地址%p, text地址:%p,\n", pop, text);

    // 求取位置?  數(shù)組是一塊連續(xù)的內(nèi)存空間,沒有斷層,所以可以-
    int index = pop - text; // pop="Derry" - text"name is Derry"
    printf("%s第一次出現(xiàn)的位置是:%d\n", subtext, index); // 我的D在第8個位置

    // 指針是可以:++ --  +=  -=

    // 拼接 ========================
    char destination[25]; // 容器 25的大小 已經(jīng)寫死了
    char * blank = "--到--", *CPP="C++", *Java= "Java";

    strcpy(destination, CPP); // 先Copy到數(shù)組里面去
    strcat(destination, blank); // 然后再拼接
    strcat(destination, Java); // 然后再拼接
    printf("拼接后的結(jié)果:%s\n", destination); // C++--到--Java

    return 0;
}

執(zhí)行結(jié)果為:


字符串查找,包含,拼接

其中 strstr 這個字符串的作用是查找字符串中第一個出現(xiàn)的指定字符串。兩個參數(shù)意義是:(資源字符串,查找字符串)
之前就說過,在C語言中數(shù)組與指針其實是相等的,由于數(shù)組是一塊連續(xù)的內(nèi)存空間,沒有斷層,所以可以進(jìn)行 ++ -- += -=這些操作。
因此可以通過以上這些操作結(jié)合 strstr 這個 API 獲取到第一個出現(xiàn)指定字符串的下標(biāo)。
通過 strcat 這個字符串進(jìn)行字符串的拼接操作。

3.4 大小寫轉(zhuǎn)換(手寫API)

????在這個demo中主要學(xué)習(xí)

  • C語言中指針用法的思想
  • 大小寫轉(zhuǎn)換的API
// 大小寫轉(zhuǎn)換(手寫API)

#include <stdio.h>
#include <ctype.h>

// 指針的理解
void lower(char * dest, char * name) {
    char * temp = name; // 臨時指針,你只能操作,臨時指針,不能破壞name指針
    while (*temp) {
        *dest = tolower(*temp);
        temp ++; // 挪動指針位置 ++
        dest ++; // 挪動指針位置 ++  目的是為了 挪動一個存儲一個 挪動一個存儲一個 ...
    }
    // printf '\0'
    *dest = '\0'; // 避免printf打印系統(tǒng)值

    printf("不能破壞 name:%s\n", name); // temp的好處就是,不會破壞name
}

// 全部變成小寫 derry
int mainT6() {
    char * name = "DerrY";

    // 先定義結(jié)果
    char dest[20];
    lower(dest, name);
    printf("小寫轉(zhuǎn)換后的結(jié)構(gòu)是:%s\n", dest);

    return 0;
}

執(zhí)行結(jié)果為:


大小寫轉(zhuǎn)換

注意點:

  • 將需要操作的數(shù)組傳進(jìn)去后,為了避免操作原始數(shù)據(jù)導(dǎo)致原始數(shù)據(jù)發(fā)生變化,通常需要定義一個臨時變量來存儲需要改變的數(shù)據(jù)。
  • 通過指針 ++、-- 等操作移動指針位置,獲取每一個字符串。
  • 通過 tolower API 將對應(yīng)的字符串修改為小寫。

四、結(jié)構(gòu)體與結(jié)構(gòu)體指針數(shù)組

C語言中的結(jié)構(gòu)體就類似于 Java 中類(class)的概念一樣

4.1 結(jié)構(gòu)體定義與使用
  • 第一種寫法
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>

struct Dog {
    // 成員
    char name[10]; // copy進(jìn)去
    int age;
    char sex;

};

int main() {
    struct Dog dog; // 這樣寫完,成員是沒有任何初始化的,成員默認(rèn)值 是系統(tǒng)值(name:?@, age:3133440, sex:€)
    printf("name:%s, age:%d, sex:%c \n", dog.name, dog.age, dog.sex);

    // 賦值操作
    // dog.name = "旺財";
    strcpy(dog.name, "旺財");
    dog.age = 3;
    dog.sex = 'G';
    printf("name:%s, age:%d, sex:%c \n", dog.name, dog.age, dog.sex);

    return 0;
}

運(yùn)行結(jié)果:


結(jié)構(gòu)體第一種寫法

????在C語言中定義結(jié)構(gòu)體用 struct 關(guān)鍵字。

  • 當(dāng)沒對結(jié)構(gòu)體里的數(shù)據(jù)進(jìn)行初始化時,可以看到打印出來的是一些臟數(shù)據(jù)。
  • 當(dāng)用數(shù)組定義字符串時只能用 copy (拷貝)的方式對該對象進(jìn)行賦值。
  • 結(jié)構(gòu)體第二種寫法
struct Person {
    // 成員
    char * name; // 字符指針 = "賦值"
    int age;
    char sex;
} ppp = {"Derry", 33, 'M'},
        ppp2,
        ppp3,
        pppp4,
        pppp5
// ...
;

int main() {

    // Person == ppp == struct Person ppp;
    printf("name:%s, age:%d, sex:%c \n", ppp.name, ppp.age, ppp.sex);

    // 賦值
    // strcpy(pppp5.name, "Derry5"); // Copy不進(jìn)去
    pppp5.name = "DerryO";
    pppp5.age = 4;
    pppp5.sex = 'M';

    printf("name:%s, age:%d, sex:%c \n", pppp5.name, pppp5.age, pppp5.sex);


    return 0;
}

運(yùn)行結(jié)果:


結(jié)構(gòu)體第二種寫法
  • 當(dāng)用指針定義字符串時可以用 = 的方式對該對象進(jìn)行賦值。
  • 可以直接在結(jié)構(gòu)體的末尾創(chuàng)捷對象,直接對其初始化。
  • 結(jié)構(gòu)體第三種寫法
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>

struct Study {
    char * studyContent; // 學(xué)習(xí)的內(nèi)容
};

struct Student {
    char name[10];
    int age;
    char sex;

    // Study study; // VS的寫法
    struct Study study; // Clion工具的寫法

    struct Wan {
        char * wanContent; // 玩的內(nèi)容
    } wan;
};

int main() {

    struct Student student =
            {"李元霸", 88, 'm' ,
             {"學(xué)習(xí)C"},
             {"王者農(nóng)藥"}
             };

    printf("name:%s, age:%d, sex:%c,study:%s, wan:%s \n",
           student.name, student.age, student.sex, student.study.studyContent, student.wan.wanContent);


    return 0;
}

運(yùn)行結(jié)果:


結(jié)構(gòu)體第三種寫法
  • 在這個demo中展示了結(jié)構(gòu)體嵌套的情況,被嵌套的結(jié)構(gòu)體需要先于嵌套結(jié)構(gòu)體先被定義出來。
  • 也可以直接在結(jié)構(gòu)體里直接創(chuàng)建一個新的結(jié)構(gòu)體,同時對其命名。
4.2 結(jié)構(gòu)體指針 與 動態(tài)內(nèi)存開辟
  • 結(jié)構(gòu)體指針
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>

struct Cat {
    char name[10];
    int age;
};

int main() { // 棧

    // 結(jié)構(gòu)體
    struct Cat cat = {"小花貓", 2};

    // 結(jié)構(gòu)體 指針    -> 調(diào)用一級指針成員
    // VS的寫法:Cat * catp = &cat;
    struct Cat *catp = &cat;
    catp->age = 3;
    strcpy(catp->name, "小花貓2");
    printf("name:%s, age:%d \n", catp->name, catp->age);

    return 0;
}

運(yùn)行結(jié)果


結(jié)構(gòu)體指針
  • 聲明結(jié)構(gòu)體指針跟聲明其他指針一樣也是用 *,然后用 & 取出該對象的地址。
  • 結(jié)構(gòu)體指針取出對應(yīng)的對象用 ->
  • 結(jié)構(gòu)體指針的動態(tài)內(nèi)存開辟
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct Cat2 {
    char name[10];
    int age;
};

int main() { // 堆

    // VS的寫法:Cat2 * cat = (Cat2 *) malloc(sizeof(Cat2));
    struct Cat2 *cat = malloc(sizeof(struct Cat2));

    strcpy(cat->name, "金色貓");
    cat->age = 5;

    printf("name:%s, age:%d \n", cat->name, cat->age);

    // 堆區(qū)的必須釋放
    free(cat);
    cat = NULL;

    return 0;
}
  • 對結(jié)構(gòu)體動態(tài)開辟內(nèi)存也是用 malloc 關(guān)鍵字
  • 當(dāng)使用malloc 關(guān)鍵字在堆區(qū)動態(tài)開辟內(nèi)存時千萬記得在結(jié)束時需要釋放指針,及其內(nèi)存。
  • 結(jié)構(gòu)體的數(shù)組
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Cat3 {
    char name[10];
    int age;
};

int main() {

    // 棧區(qū) 靜態(tài)范疇
    struct Cat3 cat [10] = {
            {"小黃", 1},
            {"小白", 2},
            {"小黑", 3},
            {},
            {},
            {},
            {},
            {},
            {},
            {},
    };

    // VS的寫法
    // cat[9] = {"小黑9", 9},

    // ClION的寫法
    struct Cat3 cat9 =  {"小黑9", 9};
    // cat[9] = cat9;
    *(cat + 9) = cat9;
    printf("name:%s, age:%d \n", cat9.name, cat9.age);




    // 堆區(qū) 動態(tài)范疇 ==============================
    struct Cat3 * cat2 = malloc(sizeof(struct Cat3) * 10);

    // 【1元素地址的操作】給他賦值,請問是賦值,那個元素  (默認(rèn)指向首元素地址)
    strcpy(cat2->name, "小花貓000");
    cat2->age = 9;
    printf("name:%s, age:%d \n", cat2->name, cat2->age);

    // 【8元素地址的操作】 給第八個元素賦值
    cat2 += 7;
    strcpy(cat2->name, "小花貓888");
    cat2->age = 88;
    printf("name:%s, age:%d \n", cat2->name, cat2->age);

    free(cat2);
    cat2 = NULL;


    return 0;
}

運(yùn)行結(jié)果:


結(jié)構(gòu)體數(shù)組+堆區(qū)動態(tài)開辟內(nèi)存
  • 之前說過在C語言中數(shù)組與指針可以認(rèn)為是一個概念,因此默認(rèn)的結(jié)構(gòu)體指針指向的就是結(jié)構(gòu)體數(shù)組的第一條數(shù)據(jù)。
  • 移動結(jié)構(gòu)體指針,就相當(dāng)于移動結(jié)構(gòu)體數(shù)組,需要記住的是下標(biāo)都是從 0 開始。
  • 同樣地,在堆區(qū)開辟內(nèi)存的話最后要記得釋放內(nèi)存。
4.3 結(jié)構(gòu)體與結(jié)構(gòu)體指針-別名

大家看到上面demo中有很多注釋說在 VS 編譯器中代碼是怎么寫的,在 Clion 中代碼是怎么寫的。由于編譯器的差異導(dǎo)致了不同平臺有不同的代碼要求,如果要根據(jù)不同平臺去維護(hù)多套代碼這顯然成本太大了,因此有了別名來解決這個問題:

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

struct Workder_ {
    char name[10];
    int age;
    char sex;
};

// VS的寫法:typedef Workder_
typedef struct Workder_ Workder_; // 給結(jié)構(gòu)體取別名

typedef Workder_ * Workder; // 給結(jié)構(gòu)體指針取別名

// 源碼是這樣寫的
// 給結(jié)構(gòu)體AV 取了一個別名等于AV
typedef struct {
    char name[10];
    int age;
    char sex;
} AV;

int main() {
    // 以前 Clion工具 必須加上 struct   VS又不用加  代碼差異化大
    // struct Workder_ workder1 = malloc(sizeof(struct Workder_));

    // 現(xiàn)在 (兼容代碼的寫法,保持一致)
    Workder_* workder2 = malloc(sizeof(Workder_));

    // VS  CLion  他們都是一樣的寫法
    Workder workder = malloc(sizeof(Workder_));


    AV av = {"VideoInfo", 54, 'M'}; // 結(jié)構(gòu)體  VS  Clion  xxx工具  兼容寫法

    AV * avp = malloc(sizeof(AV)); // 結(jié)構(gòu)體指針

    free(workder);
    workder = NULL;

    free(workder2);
    workder2 = NULL;
    return 0;
}
  • 通過 typedef 關(guān)鍵字對結(jié)構(gòu)體和結(jié)構(gòu)體指針設(shè)置別名,之后操作這個結(jié)構(gòu)體就可以兼容所有平臺的語法,既可以使用 VS 的語法,也可以使用 Clion 的語法。
  • 可以直接在聲明結(jié)構(gòu)體時加上 typedef 以及別名,這樣新建的結(jié)構(gòu)體就是一個兼容所有平臺的結(jié)構(gòu)體。
4.4 枚舉

C語言中的枚舉類似于Java中的枚舉,同樣的為了解決上面說到的不同平臺不同語法的問題,也采用 typedef 關(guān)鍵字 對枚舉取別名,從而兼容所有平臺的語法:

#include <stdio.h>

// 枚舉 int 類型的
enum CommentType {
    TEXT = 10,
    TEXT_IMAGE,
    IMAGE
};

typedef enum CommentType CommentType;

// 作業(yè):處理好 差異化代碼
int main() {
    // Clion工具的寫法如下:
//    enum CommentType commentType = TEXT;
//    enum CommentType commentType1 = TEXT_IMAGE;
//    enum CommentType commentType2 = IMAGE;

    // VS工具的寫法如下:
    CommentType commentType = TEXT;
    CommentType commentType1 = TEXT_IMAGE;
    CommentType commentType2 = IMAGE;

    printf("%d, %d, %d \n", commentType, commentType1, commentType2);

    return 0;
}

運(yùn)行結(jié)果:


C語言枚舉
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容