2018-09-07(指針,多級指針,指針數組,指針數組字符串,結構體,結構體,枚舉,變量修飾符)

  • 定義一個函數, 要求能夠在函數中修改傳入變量的值
    int num = 6;
    printf("調用之前:num = %i\n", num);
//    change(num);
    change(&num);
    printf("調用之后:num = %i\n", num);
    return 0;
}
//void change(int value){
// 結論: 如果函數的形參是數組或者是指針,
// 那么就可以在函數中修改外界傳入變量的值
void change(int *value){ // 相當于 int *value = #
    *value = 888;
  • 需求: 要定定義一個函數, 在函數中交換傳入變量的值
void main(){
    int num1=10,num2=20;
    chang(&num1,&num2);
    printf("num1=%i,num2=%i",num1,num2);

}
基本數據類型作為形參, 在函數內修改形參, 不會影響到函數外的實參,所以定義為指針變量,傳進來地址
int chang(int *n1,int *n2){

    int mid=*n1;
    *n1=*n2;
    *n2=mid;
}
  • 需求: 要求定義一個函數, 可以同時返回兩個變量的和,差,積,商
     需要大家知道的是: 在C語言中, 默認情況下一個函數只能返回一個值
     如果想讓某一個函數同時返回多個值, 可以借助指針來實現
#include <stdio.h>
//int sum(int num1, int num2);
void test(int num1, int num2, int *res1, int *res2, int *res3, int *res4);
int main()
{
    int a = 10;
    int b = 20;
//    int res = sum(a, b);
//    printf("res = %i\n", res);

    int d, e, f, g;
    test(a, b, &d, &e, &f, &g);
    printf("和 = %i\n", d);
    printf("差 = %i\n", e);
    printf("積 = %i\n", f);
    printf("商 = %i\n", g);
    return 0;
//    printf("return 后面的語句\n");
}

/**先輸出/**,然后按住Ctrl旁邊的Fn+回車鍵,自動生成以下注釋
 * @brief test 可以同時返回兩個變量的和,差,積,商
 * @param num1 參與運算的第一個變量
 * @param num2 參與運算的第二個變量
 * @param res1 和
 * @param res2 差
 * @param res3 積
 * @param res4 商
 */
void test(int num1, int num2, int *res1, int *res2, int *res3, int *res4){
    *res1 = num1 + num2;
    *res2 = num1 - num2;
    *res3 = num1 * num2;
    *res4 = num1 / num2;
}

注意點:return后面的語句執(zhí)行不到
int sum(int num1, int num2){
    // 注意點:
    // return的作用是用于結束當前函數
    // 只要函數執(zhí)行到return, 就會立刻停止往下執(zhí)行
    // 所以return 后面不可以編寫任何語句, 因為執(zhí)行不到
    return num1 + num2;
//    return num1 - num2;
//    return num1 * num2;
//    return num1 / num2;
}
*/
  • 多級指針
前面我們學習的指針我們稱之為一級指針
     * 什么是多級指針
     * 指針中保存的又是其它指針的地址, 我們就稱之為多級指針
     * 如何定義多級指針
     * 在要保存的指針變量的基礎上加一顆星即可
     * 例如: int *p;  如果想保持指針變量p的地址, 只需在定義時多加一顆星即可 int **pp;
     *
     * 訪問的規(guī)則:pp
     * 如果定義就如何訪問, 例如: int *p; 訪問 *p;  例如: int **pp; 訪問 **pp;
     *
     * 定義指針的規(guī)律:
     * 1.將要指向的變量的定義拷貝過來
     * 2.再拷貝過來的代碼的類型和變量名稱中間加上一顆星
     * 3.修改變量名稱
int num=6//int num;num=6;
int *p=&num;//int *p;p=&num;定義指針規(guī)律的意思就是把int num;拷貝過來,在int和num之間加*,在中間靠近誰都沒關系,然后改變num的名稱為p
int **p2=&p;
printf("&num = %p\n", &num); // &num =  0060FEAC
printf("p = %p\n", p); // p =  0060FEAC
printf("*pp = %p\n", *pp); // *pp = 0060FEAC

printf("&pp = %p\n", &pp); // &pp = 0060FEA4
 規(guī)律: 如果想通過多級指針獲取某個變量的值, 那么是幾級指針, 前面就寫幾顆星即可
 注意點: 在企業(yè)開發(fā)中, 最多二級指針, 三級頂天了, 四級沒講過
  • 指針和數組
 int ages[3] = {1, 3, 5};

    for(int i = 0; i < 3; i++){
        printf("ages[%i] = %i\n", i, ages[i]);
    }
    數組名稱保存的就是數組占用內存最小的那個地址
    既然數組名稱保存的就是地址, 而指針也是用于保存地址的, 所以指針也可以指向數組
    int *p = &ages;
    printf("ages = %p\n", ages); // ages = 0060FEA0
    printf("&ages = %p\n", &ages); // ages = 0060FEA0
    int *p = ages;// int *p = &ages
    printf("p = %p\n", p); // 0060FEA0
    結論: 如果利用指針保存數組的地址之后, 那么 p = ages = &ages;
  • 要求你寫出三種訪問數組元素的寫法
     int ages[3] = {1, 3, 5};//第一種
     printf("ages[0] = %i\n", ages[0]);
     int *p = ages;//第二種
     printf("p[0] = %i\n", p[0]);
     printf("0[p] = %i\n", 0[p]);//第三種
  • 指針遍歷數組
int ages[3] = {1, 3, 5};
    int *p = ages;
//    printf("*p = %i\n", *p); // 1
//    printf("*(p + 1) = %i\n", *(p + 1)); // 3
//    printf("*(p + 2) = %i\n", *(p + 2)); // 5

//    printf("*p = %i\n", *p++); // 1
//    printf("*p = %i\n", *p++); // 3
//    printf("*p = %i\n", *p); // 5
//    printf("*p = %i\n", *(--p)); // 3


    for(int i = 0; i < 3; i++){
//        printf("ages[%i] = %i\n", i, ages[i]);
        printf("ages[%i] = %i\n", i, *p++);
    }
  • 指針和字符串(用指針變量保存字符串)

指針和字符串
* 字符串的本質就是數組, 所以指針也可以指向字符串
* 正式因為如此, 所以定義字符串又多了一種方式

char str1[] = {'l', 'n', 'j', '\0'};
char str2[] = "lnj";//可以寫成str2[i]
char *str4 = "lnj";//同樣可以寫成str2[i]
利用數組和指針定義字符串的區(qū)別:
     * 1. 存儲的位置不同
     * 如果是通過數組定義的字符串, 那么存儲在內存的棧中
     * 如果是通過指針定義的字符串, 那么存儲在內存的常量區(qū)中
     *
     * 2.由于在內存中存儲的位置不一樣, 所以特性也不一樣
     * 如果是通過數組定義的字符串, 我們是可以手動修改
     * 如果是通過指針定義的字符串, 我們不用手動修改
   char str[] = "lnj";
   char *str = "lnj";
   printf("str = %s\n", str);
   str2[1] = 'T';
   printf("str = %s\n", str);
     *
     * 3.由于在內存中存儲的位置不一樣, 所以特性也不一樣
     * 如果是通過數組定義的字符串, 每次定義都會重新開辟存儲空間
     * 如果是通過指針定義的字符串, 重復定義不會重新開辟存儲空間

    char str1[] = "lnj";
    char str2[] = "lnj";
    printf("str1 = %p\n", str1); // 地址不一樣
    printf("str2 = %p\n", str2);

    char *str1 = "lnj";
    char *str2 = "lnj";
    printf("str1 = %p\n", str1); // 地址一樣
    printf("str2 = %p\n", str2);
  • 接收和打印,字符串數組和指針的區(qū)別
 1.接收字符串的時候, 只能通過字符數組, 不能通過字符指針
char str[10];
char *str;//程序編譯是不報錯,運行時出現問題,這樣定義接收不到
scanf("%s", str);
rintf("str = %s\n", str);
 2.如果函數中返回的字符串是通過數組創(chuàng)建的, 那么外界無法獲取
    如果函數中返回的字符串是通過指針創(chuàng)建的, 那么外界可以獲取
    char *res = demo();
    printf("res = %s\n", res);
char* demo(){//返回值為指針類型,所以有星號
    char str[] = "lnj";//函數外面接收不到
    char *str = "lnj"
    return str;//str保存的是常量區(qū)lnj\0的地址

/ 注意點: 學習了指針之后, 建議將過去形參的數組類型修改為指針類型
int ages[3] = {1, 3, 5};
     test(ages);
     printf("ages[1] = %i\n", ages[1]);

//void test(int nums[]){
void test(int *nums){//由于test(ages)傳進來的就是地址,用指針更形象
 nums[1] = 6;
  • 字符串數組
字符串數組
    // 字符串就是一個數組, 所以字符串數組就是一個二維數組

   char str[][] = {//字符串數組表示方法
       "lnj",
       "abc",
       "def"
    };

    // 字符串數組的第二種格式
    char *str[] = {
        "lnj",
        "abc",
        "def"
    };
  • 練習
    1.實現strlen()函數
char *str = "lnj666";//此處注意字符串為char類型,形參處也為char類型
//    char str[10] = {'l', 'n', 'j', '\0'};
    // 注意點: strlen計算的是保存了多少個字符串
    // 計算規(guī)則, 就是返回字符串中\(zhòng)0之前有多少個字符
int res = myStrlen(str);
    printf("res = %i\n", res);
    return 0;
}
int myStrlen(char *str){
    // 1.定義變量記錄當前有多少個
    int count = 0;
    while(*str++){
        count++;
    }
//    while(*str++ != '\0'){   方法二
//        count++;
//    }

//    while(str[count] != '\0'){     方法三
//        count++;
//    }
   return count;
}

2.實現strcat()函數

注意點: 第一個參數必須足夠大
 //char *str1 = "lnj";  // 不能用于拼接,因為不能看出第一個字符空間大小
//    char *str2 = "it666";
    char str1[10] = "lnj";
    char str2[10] = "it666";
    myStrcat(str1, str2);
    printf("str1 = %s\n", str1);
    return 0;
}
void myStrcat(char *str1, char *str2){
方法一:
    while(*str1){
         str1++;
    }
 while(*str2){
        *str1 = *str2;
        str1++;
        str2++;
    }
*str1='\0';
}

方法二:
 // 1.拿到第一個數組\0的位置
    int index = 0;
    while(str1[index] != '\0'){
        index++;
    }
    // 2.將第二個數組添加到第一個數組末尾
    int count = 0;
    while(str2[count] != '\0'){
        str1[index] = str2[count];
        count++;
        index++;
    }

3.實現strcpy()

同樣不能用指針定義字符串數組,
int main()
{
      char str1[9] = "lnj";
      char str2[10] = "it";
//    strcpy(str1, str2);
    myStrcpy(str1, str2);
    printf("str1 = %s\n", str1);
    return 0;
}
void myStrcpy(char *str1, char *str2){
    while(*str2){
        *str1 = *str2;
        str1++;
        str2++;
    }
    *str1 = '\0';
}
/*
void myStrcpy(char *str1, char *str2){
    int index = 0;
    while(str2[index] != '\0'){
        str1[index] = str2[index];
        index++;
    }
    str1[index] = '\0';
}
*/

4.實現strcmp()

int main()
{
    // strcmp
    char str1[9] = "125";
    char str2[10] = "124";
    // 兩個字符串相同返回0
    // 第一個參數小于第二個參數 -1 負數
    // 第一個參數大于第二個參數 1 正數
    // 如果前面的內容都相同, 第一個參數的個數小于第二個參數, -1 負數
    // 如果前面的內容都相同, 第一個參數的個數大于第二個參數, 1 正數
//    int res = strcmp(str1, str2);
    int res = myStrcmp(str1, str2);
    printf("res = %i\n", res);
    return 0;
}
int myStrcmp(char *str1, char *str2){

//    while(*str1 != '\0' || *str2 != '\0'){
     while(*str1 || *str2){
        if(*str1 > *str2){
            return 1;
        }else if(*str1 < *str2){
            return -1;
        }
        str1++;
        str2++;
    }
    return 0;
}

/*方法二:
int myStrcmp(char *str1, char *str2){
    int index = 0; // 3
    //      \0                        \0
    while(str1[index] != '\0' || str2[index] !='\0'){
        //      3           3
        if(str1[index] > str2[index]){
            return 1;
        }else if(str1[index] < str2[index]){
            return -1;
        }
        index++;
    }
    return 0;
}
*/

  • 指向函數的指針

    • 指針變量的作用: 專門用于保存地址
      * 指向函數的指針
      * 計算機也會給函數分配存儲空間, 既然函數會分配內存空間,
      * 所以函數也有自己的地址, 所以指針變量也可以保存函數的地址
      *
      * 經過前面的學習, 我們知道數組名保存的就是數組的地址
      * 函數和數組很像, 函數名中保存的就是函數的地址
      *
      * 如何定義指針變量?
      * 1.將需要保存變量的定義拷貝過來
      * 2.在數據類型和變量名稱中間添加一個*
      * 3.修改變量名稱
      *
      * 如何定義保存函數的指針變量
      * 1.將函數的什么拷貝過來
      * 2.在函數返回值和函數名稱總監(jiān)添加一個*
      * 3.修改函數名稱
      * 4.注意點: 需要將*和變量名稱用括號括起來
      *
      * 我們說過指向函數的指針和指向數組的指針很像
      * 如果是數組, 我們可以直接將數組名稱賦值給一個指針變量
      * 如果是函數, 我們也可以直接將函數名稱賦值給一個指針變量
      *
      * 如果一個指針指向了數組, 那么訪問數組就有了三種方式
      * 數組名稱[索引];
      * 指針變量名稱[索引]
      * *(指針編碼名稱 + 索引)
      *
      * 如果一個指針指向了函數, 那么訪問方式也有多種方式
      * 函數名稱();
      * ( *指針變量名稱)();
      * 指針變量名稱();

     int num;
     int *p;
     p = &num;

    int ages[3] = {1, 3, 5};
     int *p;
     p = ages;
    printf("ages[1] = %i\n", ages[2]);//三種方式訪問數組
    printf("p[1] = %i\n", p[2]);
    printf("p[1] = %i\n", *(p+2));


    test(); // 第一種方式

    void (*funcP)();
    funcP = &test;//或者  funcP = test;
    (*funcP)(); // 第二種方式

    void (*funcP)();
    funcP = &test;//或者  funcP = test;
    funcP(); // 第三種方式
void test(){
    printf("test\n");
}
  • 練習:定義指針指向這幾個函數
    void (*p1)();
    p1 = say;
    p1();

    void (*p2)(int, int);
    p2 = sum;
    p2(10, 20);


    int (*p3)(int a, int b);
    p3 = minus;
    int res = p3(10, 20);
    printf("res = %i\n", res);

    char* (*p4)();
    p4 = test;
    char* res2 = p4();
    printf("res2 = %s\n", res2);
    return 0;
}
void say(){
    printf("Hello World\n");
}
void sum(int a, int b){
    printf("res = %i\n", a + b);
}
int minus(int a, int b){
    return a - b;
}
char* test(){
    char* name = "lnj";
    return name;
}
  • 要求一個函數既可以計算兩個變量的和, 也可以計算兩個變量的差

//    int res1 = sum(10, 20);
//    printf("res1 = %i\n", res1);

//    int res2 = minus(10, 20);
//    printf("res2 = %i\n", res2);

    int res3 = test(10, 20, sum);//把sum地址傳給test
    printf("res3 = %i\n", res3);

    int res4 = test(10, 20, minus);
    printf("res4 = %i\n", res4);
    return 0;
}
 注意點: 指向函數的指針,作為函數的形參時, 指針變量的名稱, 就是形參的名稱
// 如果指向函數的指針作為函數的參數, 那么這個可稱之為回調函數
// 這里相當于, 給test函數傳入了一個sum函數或者minus函數
// 然后又在test函數中調用了sum函數或者minus函
int test(int num1, int num2, int (*funP)(int, int)){
    return funP(num1, num2);//把num1和num2的值傳給sum函數

int sum(int num1, int num2){
    return num1 + num2;
}
int minus(int num1, int num2){
    return num1 - num2;
}
  • 作業(yè)
    1.要求從鍵盤輸入一個字符串, 并且字符串中可以出現空格
  • 2.將用戶輸入的字符串, 單詞的首字母變成大寫, 單詞用空格劃分
  • hello world; --> Hello World;
  • 3.將用戶輸入的字符串, 單詞的首字母編程小寫, 單詞用空格劃分
  • Hello World; --> hello world;
  • 4.要求定義一個函數, 既可以處理將首字母變?yōu)榇髮? 也可以處理將首字母變?yōu)樾?/li>
  • 需要用到指向函數的指針

  • 結構體
    /*
     * 什么是結構體?
     * 結構體時構造類型的一種
     *
     * 構造類型前面我們已經學習過了數組:
     * 數組的作用是用于存儲一組相`同類型`的數據
     * 結構體的作用是用于存儲一組`不同類型`的數據
     *
     * 保存一個人的信息
     * 姓名/年齡/身高 ...
     * char *
     * int
     * double
     *
     * 如何定義結構體變量
     * 1.先定義結構體類型
     * 2.通過結構體的類型定義結構體變量
     *
     * 如何定義結構體類型?
     * struct 結構體類型名稱{
     *   數據類型 屬性名稱;
     *   數據類型 屬性名稱;
     *   ... ...
     * };
     *
     * 如何定義結構體變量
     * struct 結構體類型名稱 變量名稱;
     *
     * 如何訪問結構體的屬性
     * 結構體變量名稱.結構體屬性名稱;
     */
    // 1.定義結構體類型
    struct Person{
        char *name; // name我們稱之為結構體的屬性名稱
        int age; // age也稱之為結構體的屬性名
        double height; // height也稱之為結構體的屬性名稱
    };
    // 2.定義結構體變量
    struct Person p;

    // 3.使用結構體變量
//    int ages[3] = {1, 3, 5};
//    ages[0] = 1;
    // 格式: 結構體變量名稱.結構體屬性名稱
    p.name = "lnj";
    p.age = 35;
    p.height = 1.9;
    printf("name = %s\n", p.name);
    printf("age = %i\n", p.age);
    printf("height = %lf\n", p.height);
    return 0;
}
  • 結構體變量初始化的幾種方式
1.定義結構體類型
    struct Dog{
        char *name;
        int age;
        double height;
2.1先定義后初始化
//    struct Dog dd;
//    dd.name = "ww";
//    dd.age = 1;
//    dd.height = 1.5;
2.2定義的同時初始化
//    struct Dog dd = {"ww", 1, 1.5};
注意點: 如果在定義的同時初始化, 那么初始化的順序必須和結構體類型中的順序一致
struct Dog dd = {.age = 1, .name = "ww", .height = 1.5};//也可以指定初始化,可以部分初始化.
3.特殊的初始化方式
   數組只能在定義的同時完全初始化, 不能先定義再完全初始化
   但是結構體既可以在定義的同時完全初始化, 也可以先定義再完全初始

//    int ages[3] = {1, 3, 5};
//    int ages[3];
//    ages = {1, 3, 5};//數組不可以

 // 企業(yè)開發(fā)不推薦這樣編寫
    struct Dog{
        char *name;
        int age;
        double height;
    };
     struct Dog dd;
     dd = (struct Dog){"ww", 1, 1.5};//強制轉換
     printf("name = %s\n", dd.name);
     printf("name = %i\n", dd.age);
     printf("name = %lf\n", dd.height);
  • 定義結構體變量的方式
       1.先定義結構體類型, 再定義結構體變量
    /*
    struct Person{
        char *name;
        int age;
        double height;
    };
    struct Person p1;
    struct Person p11;
    */

     2.定義結構體類型的同時定義結構體變量
    /*
    struct Person{
        char *name;
        int age;
        double height;
    } p2;
    p2.name = "lnj";
    printf("name = %s\n", p2.name);
    struct Person p22;
    */
   3.定義結構體類型的同時省略結構體名稱, 同時定義結構體變量
    // 匿名結構體
    // 特點: 結構體類型只能使用一次
    struct{
        char *name;
        int age;
        double height;
    } p3;
    p3.name = "it666";
    printf("name = %s\n", p3.name);
    return 0;
  • 結構體作用域(和變量相同)
  • 結構體數組及初始化
  struct Person{
        char *name;
        int age;
        double height;
    };
方法一:先定義在初始化
 //    struct Person p1 = {"lnj", 35, 1.90};
//    struct Person p2 = {"zs", 22, 1.2};
//    struct Person p3 = {"ls", 33, 1.4};
//    struct Person p4 = {"ww", 56, 1.8};
   數據類型 數組名稱[元素個數];
    struct Person ps[4];
     ps[0] = p1;//ps[0] = {"lnj", 35, 1.90};下面也如此
     ps[1] = p2;
     ps[2] = p3;
     ps[3] = p4;
方法二:定義同時初始化
 struct Person ps[4] ={
         {"lnj", 35, 1.90},
         {"zs", 22, 1.2},
         {"ls", 33, 1.4},
         {"ww", 56, 1.8},
     };
  • 結構體內存分析

注意點:
* 給整個結構體變量分配存儲空間和數組一樣, 從內存地址比較大的開始分配
* 給結構體變量中的屬性分配存儲空間也和數組一樣, 從所占用內存地址比較小的開始分配
*
* 注意點:
* 和數組不同的是, 數組名保存的就是數組首元素的地址
* 而結構體變量名, 保存的不是結構體首屬性的地址

 struct Person{
        int age;
        int score;
    };
    struct Person p;
    printf("p = %p\n", p); // p = 00000077
    printf("&p = %p\n", &p); // &p = 0060FEA8
    printf("&p.age = %p\n", &p.age); // &p.age = 0060FEA8
    printf("&p.score = %p\n", &p.score); // &p.score = 0060FEAC
    //結構體在分配內存的時候, 會做一個內存對齊的操作
    // 會先獲取所有屬性中占用內存最大的屬性的字節(jié)
    // 然后再開辟最大屬性字節(jié)的內存給第一個屬性, 如果分配給第一個屬性之后還能繼續(xù)分配給第二個屬性, 那么就繼續(xù)分配
    // 如果分配給第一個屬性之后, 剩余的內存不夠分配給第二個屬性了, 那么會再次開辟最大屬性直接的內存, 再次分配
    //以此類推
  • 結構體指針

結構體指針
* 因為結構體變量也會分配內存空間, 所以結構體變量也有內存地址, 所以也可以使用指針保存結構體變量的地址
*
* 規(guī)律:
* 定義指向結構體變量的指針的套路和過去定義指向普通變量的一樣
*
* 如果指針指向了一個結構體變量, 那么訪問結構體變量的屬性就有3種方式
* 結構體變量名稱.屬性名稱;
* (*結構體指針變量名稱).屬性名稱;
* 結構體指針變量名稱->屬性名稱;

struct Person{
        char *name;
        int age;
        double height;
    };
    struct Person per = {"lnj", 35, 1.9};//變量名為per
    struct Person *p;//定義一個指針變量p,struct Person為結構體類型
    p = &per;
   printf("per.name = %s\n", per.name);
    printf("per.name = %s\n", (*p).name);
    printf("per.name = %s\n", p->name);
  • 結構體的嵌套(提高代碼的復用性)
struct Person{
        char *name;
        int age;

        // 出生年月
        int year;
        int month;
        int day;

        // 死亡日期
        int year2;
        int month2;
        int day2;

        // 讀幼兒園日期
        // 讀小學日期
        // 讀中學日期
所以可以把年月日寫成一個結構體
// 1.定義了一個日期的結構體類型
    struct Date{
        int year;
        int month;
        int day;
    };
    // 2.定義一個人的結構體類型
    struct Person{
        char *name;
        int age;
        struct Date birthday;
    };
    struct Person p = {"lnj", 35, {2020, 12, 12}};

    printf("name = %s\n", p.name);
    printf("name = %i\n", p.age);
    printf("name = %i\n", p.birthday.year);
    printf("name = %i\n", p.birthday.month);
    printf("name = %i\n", p.birthday.day);
  • 結構體和函數(強調結構體作用域和變量相同)

1.雖然結構體是構造類型, 但是結構體變量之間的賦值
* 和基本數據類型賦值一樣, 是拷貝
2. 注意點: 定義結構體類型不會分配存儲空間
* 只有定義結構體變量才會分配存儲空間

struct Person{
        char *name;
        int age;
    };
    struct Person p1 = {"lnj", 35};
    struct Person p2;
    p2 = p1;
    p2.name = "zs";
    printf("p1.name = %s\n", p1.name); // lnj
    printf("p2.name = %s\n", p2.name); //  zs


把p1的name更改寫在函數中,更改后內存會釋放掉,最終name值不變
struct Person p1 = {"lnj", 35};
    printf("p1.name = %s\n", p1.name); // lnj
    test(p1);
    printf("p1.name = %s\n", p1.name); // lnj
    return 0;
}
void test(struct Person per){
    per.name = "zs";
  }
  • 共用體(企業(yè)開發(fā)中用的比較少)

共用體
*
* 共用體的格式:
* union 共用體名稱{
* 數據類型 屬性名稱;
* 數據類型 屬性名稱;
* ... ...
* }
* 共用體定義的格式和結構體只有關鍵字不一樣, 結構體用struct,共用體用union
*
* 共用體特點:
* 結構體的每個屬性都會占用一塊單獨的內存空間, 而共用體所有的屬性都共用同一塊存儲空間(同樣先分配占用屬性最大的字節(jié)數)
* 只要其中一個屬性發(fā)生了改變, 其它的屬性都會受到影響
*
* 應用場景:
* 同一個變量, 在不同的時刻,需要表示不同類型數據的時候, 我們就可以使用共用體

     union Test{
        int age;
        char ch;
    };
    union Test t;
    printf("sizeof(p) = %i\n", sizeof(t));

    t.age = 33;
    printf("t.age = %i\n", t.age); // 33
    t.ch = 'a';
    printf("t.ch = %c\n", t.ch); // a
    printf("t.age = %i\n", t.age); // 97,,此時原來age的空間已經給ch用了
  • 枚舉(開發(fā)中經常用到)

枚舉?
* 枚舉用于提升代碼的閱讀性, 一般用于表示幾個固定的值
* 所以還有一個名稱, 叫做枚舉常量
*
* 如果某些變量的取值是固定的, 那么就可以考慮使用枚舉來實現
*
* 枚舉的格式:
* enum 枚舉類型名稱{
* 取值1,
* 取值2,
* };
* 注意點: 和結構體,共用體不同, 枚舉是用逗號隔開
*
* 規(guī)范:
* 枚舉的取值一般以K開頭,后面跟上枚舉類型名稱, 后面再跟上表達的含義
* K代表這是一個常量
* 枚舉類型名稱, 主要是為了有多個枚舉的時候, 方便區(qū)分
* 含義, 你想表達的意思
*
* 枚舉的取值:
* 默認情況下從0開是取值, 依次遞增
* 也可以手動指定從幾開始, 依次遞增,例如KGenderMale = 9,從9開始遞增

enum Gender{
        KGenderMale , //打印輸出為0,可寫成male,但是 KGenderMale表達更清晰,編寫的時候會提示輸出
        KGenderFemale, // 1,可寫成female
                       // 2 ... ...
    };
 struct Person{
        char *name; // 姓名
        int age; // 年齡
        enum Gender gender; // 性別
    };
    struct Person p1;
    p1.name = "lnj";
    p1.age = 58;
    p1.gender = KGenderFemale;

    struct Person p2;
    p2.name = "周芷若";
    p2.age = 88;
    p2.gender = KGenderFemale;
  • 枚舉的作用域(和結構體類型的作用域一樣, 和變量的作用域一樣)
  • 枚舉的定義方式(和結構體相同)
   2.1先定義枚舉類型, 再定義枚舉變量
     enum Gender{
        KGenderMale,
       KGenderFemale,
     };
    enum Gender g1;

    2.2定義枚舉類型的同時, 定義枚舉變量
   enum Gender{
        KGenderMale,
        KGenderFemale,
     } g2;

    2.3定義枚舉類型的同時,定義枚舉變量 ,并且省略枚舉類型名稱
    enum{
        KGenderMale,
        KGenderFemale,
    } g3;

回顧局部變量和全局變量
* 局部變量:
* 概念: 定義在{}中,函數中,形參都是局部變量
* 作用域: 從定義的那一行開始, 直到遇到}結束或者遇到return為止
* 存儲的位置: 局部變量會存儲在內存的棧區(qū)中, 會隨著定義變量的代碼執(zhí)行分配存儲空間, 會隨著作用域的結束自動釋放
* 特點:
* 相同作用域類, 不能出現同名的局部變量
* 如果不同作用域內有相同名稱的變量, 那么在訪問時, 內部的會覆蓋外部的(就近原則)
*
* 全局變量:
* 概念: 定義在{}外或者函數外的變量, 都是全局變量
* 作用域: 從定義的那一行開始, 直到文件末尾
* 存儲的位置: 全局變量會存儲在內存的靜態(tài)區(qū)中, 會隨著程序的啟動分配存儲空間, 隨著程序的結束釋放存儲空間
* 特點:
* 如果有多個同名的全局變量, 那么也只會分配一次存儲空間, 多個同名的全局變量共用同一塊存儲空間

  • 變量修飾符(auto ,register,extern,static)
    1.修飾局部變量(auto ,register)
  • auto和register都是用于修飾局部變量的
    auto int num = 9;
    register int num = 9;
    * 它們年的作用是修飾編程的存儲特性
    * auto: 特點就是告訴編譯器, 局部變量離開作用域自動釋放
    * 默認情況下所有局部變量都是auto的, 所以這一句廢話, 所以了解--> 忘記 / 即可
    *
    * register: 特點就是告訴編譯器, 將局部變量存儲到CPU寄存器中
    * 好處就是訪問的速度會更快, 但是在不同平臺,不同編譯器下, 會做不同的優(yōu)化,寄存器存儲空間非常有限,不一定能夠存儲進去, 所以還是一句廢話, 所以了解 -->

2.static

2.1static對局部變量的作用
如果利用static修飾局部變量, 那么會將局部變量的存儲區(qū)域從棧區(qū)移動到靜態(tài)區(qū)
* 靜態(tài)區(qū)只有程序結束才會釋放
void calculate(int r){
// PI使用的概率非常大, 如果是一個局部變量的話, 每次調用都會重新開辟存儲空間, 這樣性能不好
// 如果PI是static的變量, 那么只會開辟一次, 那么性能就會好很多
static double pi = 3.1415926;
return r * r * pi;
}
2.2 static對全局變量的作用
* 定義一個內部的全局變量,
* 1.該變量只能在定義的文件中使用, 不能在其它文件中使用,例:在dashen.c文件中
* 2.并且該變量會獨占一塊內存空間
*
* 全局變量的特性:
* 可以定義多個同名的全局變量, 多個同名的全局變量共享一塊內存空間
* 哪怕不是同一個文件中的同名全局變量, 也會共享同一塊內存空間
* 問題:
* 這樣是不是會導致數據混亂,因此引入static,在其他文件中無法使用
*
* 注意點:
* 局部變量如果沒有初始化, 里面存儲的是垃圾數據
* 全局變量如果沒有初始會, 系統(tǒng)會自動初始化為0
2.3 static修飾函數
*代表這事一個內部函數, 只能在當前文件中使用
* 如果一些內部函數不想提供給外界使用, 那么就可以給函數添加一個static
*static必須寫到函數的實現中才有效, 不能寫到函數的聲明中
* 并且如果一個函數已經被聲明為static的了, 那么在.h文件中就不要編寫該函數的聲明了

3.extern

  • extern對局部變量的作用
    * extern用于聲明一個變量, 聲明變量并不會開辟存儲空間
    * extern一般用于全局變量, 至今沒見過有人用extern來修飾局部變量
    *
    *
    * extern對全局變量的作用
    * extern用于聲明一個變量, 聲明變量并不會開辟存儲空間
    * exter只能用于全局變量, 不能用于局部變量
    *
    * 原因:
    * 局部變量, 只有執(zhí)行到那一行代碼才會分配存儲空間, 所以哪怕聲明了 ,但是在使用時還是沒有分配, 所以還是不能存儲數據
    * 全局變量, 會隨著程序的啟動分配存儲空間, 所以只要聲明了, 使用時已經分配好了存儲空間, 一定能夠使用, 一定能夠存儲數據
    *
    *
    如果利用extern修飾函數, 代表這是一個外部函數, 其它文件中也可以使用
    * 注意點: 默認情況下所有函數都是外部函數, 所有的函數都可以在其它文件中訪問, 所以extern是一個廢物
    extern必須寫到函數的實現中才有效, 不能寫到函數的聲明中

{extern int num;
    num = 998;
printf("num = %i\n", num);
    return 0;
}  int num;
  • 企業(yè)開發(fā)中大部分都是多人開發(fā), 多人開發(fā)就是多個人一起寫一個項目,所以在企業(yè)開發(fā)中, 都是多人同時操作多個不同的文件

例如: 現在有兩個員工
* 一個是菜鳥, 一個是大神
* 調用大神寫好的代碼
* 編寫主要的業(yè)務邏輯代碼
*
在企業(yè)開發(fā)中如何進行多人開發(fā)
* 一般情況下會將(函數的實現), 編寫到.c文件中, 同時會將.c文件中需要暴露給外界使用的方式名稱的聲明寫到.h文件中
* 為什么編寫了.c文件還需要編寫一個.h文件, 原因很簡單, (函數的實現)是你編寫的, 那么函數的作用,形參你最了解, 所以應該由你來編寫

注意:
* 在企業(yè)開發(fā)中, 其它人不需要關系函數具體是如何實現的, 只需要關心如何使用這個函數即可
* 所以(函數的實現)和聲明都應該讓同一個人來完成
*
* main函數中一般為主要業(yè)務邏輯,不能有太多冗余代碼
* #include的作用:
* 將后面指定文件中的內容拷貝到當前文件中
* <>從系統(tǒng)的環(huán)境變量中去拷貝, 一般情況下只有用到系統(tǒng)函數才使用<>
* " "從指定的路徑中去拷貝, 一般情況下使用同事/自己編寫的.h文件都用""

    #include "ds.h" // 導入大神編寫的.h文件,例:#include "D:\WWW\ds.h"
    本質就是將大神編寫的.h文件中的代碼拷貝過來
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容