美文网首页
2018-09-07(指针,多级指针,指针数组,指针数组字符串,

2018-09-07(指针,多级指针,指针数组,指针数组字符串,

作者: know_78b8 | 来源:发表于2018-09-09 23:53 被阅读0次
    • 定义一个函数, 要求能够在函数中修改传入变量的值
        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后面的语句执行不到
    int sum(int num1, int num2){
        // 注意点:
        // return的作用是用于结束当前函数
        // 只要函数执行到return, 就会立刻停止往下执行
        // 所以return 后面不可以编写任何语句, 因为执行不到
        return num1 + num2;
    //    return num1 - num2;
    //    return num1 * num2;
    //    return num1 / num2;
    }
    */
    
    • 多级指针
    前面我们学习的指针我们称之为一级指针
         * 什么是多级指针
         * 指针中保存的又是其它指针的地址, 我们就称之为多级指针
         * 如何定义多级指针
         * 在要保存的指针变量的基础上加一颗星即可
         * 例如: int *p;  如果想保持指针变量p的地址, 只需在定义时多加一颗星即可 int **pp;
         *
         * 访问的规则:pp
         * 如果定义就如何访问, 例如: int *p; 访问 *p;  例如: int **pp; 访问 **pp;
         *
         * 定义指针的规律:
         * 1.将要指向的变量的定义拷贝过来
         * 2.再拷贝过来的代码的类型和变量名称中间加上一颗星
         * 3.修改变量名称
    int num=6//int num;num=6;
    int *p=&num;//int *p;p=&num;定义指针规律的意思就是把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
     规律: 如果想通过多级指针获取某个变量的值, 那么是几级指针, 前面就写几颗星即可
     注意点: 在企业开发中, 最多二级指针, 三级顶天了, 四级没讲过
    
    • 指针和数组
     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]
    利用数组和指针定义字符串的区别:
         * 1. 存储的位置不同
         * 如果是通过数组定义的字符串, 那么存储在内存的栈中
         * 如果是通过指针定义的字符串, 那么存储在内存的常量区中
         *
         * 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);
    
    • 接收和打印,字符串数组和指针的区别
     1.接收字符串的时候, 只能通过字符数组, 不能通过字符指针
    char str[10];
    char *str;//程序编译是不报错,运行时出现问题,这样定义接收不到
    scanf("%s", str);
    rintf("str = %s\n", str);
     2.如果函数中返回的字符串是通过数组创建的, 那么外界无法获取
        如果函数中返回的字符串是通过指针创建的, 那么外界可以获取
        char *res = demo();
        printf("res = %s\n", res);
    char* demo(){//返回值为指针类型,所以有星号
        char str[] = "lnj";//函数外面接收不到
        char *str = "lnj"
        return str;//str保存的是常量区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计算的是保存了多少个字符串
        // 计算规则, 就是返回字符串中\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.在函数返回值和函数名称总监添加一个*
        * 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;
    }
    
    • 作业
      1.要求从键盘输入一个字符串, 并且字符串中可以出现空格
    • 2.将用户输入的字符串, 单词的首字母变成大写, 单词用空格划分
    • hello world; --> Hello World;
    • 3.将用户输入的字符串, 单词的首字母编程小写, 单词用空格划分
    • Hello World; --> hello world;
    • 4.要求定义一个函数, 既可以处理将首字母变为大写, 也可以处理将首字母变为小写
    • 需要用到指向函数的指针

    • 结构体
        /*
         * 什么是结构体?
         * 结构体时构造类型的一种
         *
         * 构造类型前面我们已经学习过了数组:
         * 数组的作用是用于存储一组相`同类型`的数据
         * 结构体的作用是用于存储一组`不同类型`的数据
         *
         * 保存一个人的信息
         * 姓名/年龄/身高 ...
         * 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};//数组不可以
    
     // 企业开发不推荐这样编写
        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
        //结构体在分配内存的时候, 会做一个内存对齐的操作
        // 会先获取所有属性中占用内存最大的属性的字节
        // 然后再开辟最大属性字节的内存给第一个属性, 如果分配给第一个属性之后还能继续分配给第二个属性, 那么就继续分配
        // 如果分配给第一个属性之后, 剩余的内存不够分配给第二个属性了, 那么会再次开辟最大属性直接的内存, 再次分配
        //以此类推
    
    • 结构体指针

    结构体指针
    * 因为结构体变量也会分配内存空间, 所以结构体变量也有内存地址, 所以也可以使用指针保存结构体变量的地址
    *
    * 规律:
    * 定义指向结构体变量的指针的套路和过去定义指向普通变量的一样
    *
    * 如果指针指向了一个结构体变量, 那么访问结构体变量的属性就有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";
      }
    
    • 共用体(企业开发中用的比较少)

    共用体
    *
    * 共用体的格式:
    * union 共用体名称{
    * 数据类型 属性名称;
    * 数据类型 属性名称;
    * ... ...
    * }
    * 共用体定义的格式和结构体只有关键字不一样, 结构体用struct,共用体用union
    *
    * 共用体特点:
    * 结构体的每个属性都会占用一块单独的内存空间, 而共用体所有的属性都共用同一块存储空间(同样先分配占用属性最大的字节数)
    * 只要其中一个属性发生了改变, 其它的属性都会受到影响
    *
    * 应用场景:
    * 同一个变量, 在不同的时刻,需要表示不同类型数据的时候, 我们就可以使用共用体

         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用了
    
    • 枚举(开发中经常用到)

    枚举?
    * 枚举用于提升代码的阅读性, 一般用于表示几个固定的值
    * 所以还有一个名称, 叫做枚举常量
    *
    * 如果某些变量的取值是固定的, 那么就可以考虑使用枚举来实现
    *
    * 枚举的格式:
    * enum 枚举类型名称{
    * 取值1,
    * 取值2,
    * };
    * 注意点: 和结构体,共用体不同, 枚举是用逗号隔开
    *
    * 规范:
    * 枚举的取值一般以K开头,后面跟上枚举类型名称, 后面再跟上表达的含义
    * K代表这是一个常量
    * 枚举类型名称, 主要是为了有多个枚举的时候, 方便区分
    * 含义, 你想表达的意思
    *
    * 枚举的取值:
    * 默认情况下从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为止
    * 存储的位置: 局部变量会存储在内存的栈区中, 会随着定义变量的代码执行分配存储空间, 会随着作用域的结束自动释放
    * 特点:
    * 相同作用域类, 不能出现同名的局部变量
    * 如果不同作用域内有相同名称的变量, 那么在访问时, 内部的会覆盖外部的(就近原则)
    *
    * 全局变量:
    * 概念: 定义在{}外或者函数外的变量, 都是全局变量
    * 作用域: 从定义的那一行开始, 直到文件末尾
    * 存储的位置: 全局变量会存储在内存的静态区中, 会随着程序的启动分配存储空间, 随着程序的结束释放存储空间
    * 特点:
    * 如果有多个同名的全局变量, 那么也只会分配一次存储空间, 多个同名的全局变量共用同一块存储空间

    • 变量修饰符(auto ,register,extern,static)
      1.修饰局部变量(auto ,register)
    • auto和register都是用于修饰局部变量的
      auto int num = 9;
      register int num = 9;
      * 它们年的作用是修饰编程的存储特性
      * auto: 特点就是告诉编译器, 局部变量离开作用域自动释放
      * 默认情况下所有局部变量都是auto的, 所以这一句废话, 所以了解--> 忘记 / 即可
      *
      * register: 特点就是告诉编译器, 将局部变量存储到CPU寄存器中
      * 好处就是访问的速度会更快, 但是在不同平台,不同编译器下, 会做不同的优化,寄存器存储空间非常有限,不一定能够存储进去, 所以还是一句废话, 所以了解 -->

    2.static

    2.1static对局部变量的作用
    如果利用static修饰局部变量, 那么会将局部变量的存储区域从栈区移动到静态区
    * 静态区只有程序结束才会释放
    void calculate(int r){
    // PI使用的概率非常大, 如果是一个局部变量的话, 每次调用都会重新开辟存储空间, 这样性能不好
    // 如果PI是static的变量, 那么只会开辟一次, 那么性能就会好很多
    static double pi = 3.1415926;
    return r * r * pi;
    }
    2.2 static对全局变量的作用
    * 定义一个内部的全局变量,
    * 1.该变量只能在定义的文件中使用, 不能在其它文件中使用,例:在dashen.c文件中
    * 2.并且该变量会独占一块内存空间
    *
    * 全局变量的特性:
    * 可以定义多个同名的全局变量, 多个同名的全局变量共享一块内存空间
    * 哪怕不是同一个文件中的同名全局变量, 也会共享同一块内存空间
    * 问题:
    * 这样是不是会导致数据混乱,因此引入static,在其他文件中无法使用
    *
    * 注意点:
    * 局部变量如果没有初始化, 里面存储的是垃圾数据
    * 全局变量如果没有初始会, 系统会自动初始化为0
    2.3 static修饰函数
    *代表这事一个内部函数, 只能在当前文件中使用
    * 如果一些内部函数不想提供给外界使用, 那么就可以给函数添加一个static
    *static必须写到函数的实现中才有效, 不能写到函数的声明中
    * 并且如果一个函数已经被声明为static的了, 那么在.h文件中就不要编写该函数的声明了

    3.extern

    • extern对局部变量的作用
      * extern用于声明一个变量, 声明变量并不会开辟存储空间
      * extern一般用于全局变量, 至今没见过有人用extern来修饰局部变量
      *
      *
      * extern对全局变量的作用
      * extern用于声明一个变量, 声明变量并不会开辟存储空间
      * exter只能用于全局变量, 不能用于局部变量
      *
      * 原因:
      * 局部变量, 只有执行到那一行代码才会分配存储空间, 所以哪怕声明了 ,但是在使用时还是没有分配, 所以还是不能存储数据
      * 全局变量, 会随着程序的启动分配存储空间, 所以只要声明了, 使用时已经分配好了存储空间, 一定能够使用, 一定能够存储数据
      *
      *
      如果利用extern修饰函数, 代表这是一个外部函数, 其它文件中也可以使用
      * 注意点: 默认情况下所有函数都是外部函数, 所有的函数都可以在其它文件中访问, 所以extern是一个废物
      extern必须写到函数的实现中才有效, 不能写到函数的声明中
    
    {extern int num;
        num = 998;
    printf("num = %i\n", num);
        return 0;
    }  int num;
    
    • 企业开发中大部分都是多人开发, 多人开发就是多个人一起写一个项目,所以在企业开发中, 都是多人同时操作多个不同的文件

    例如: 现在有两个员工
    * 一个是菜鸟, 一个是大神
    * 调用大神写好的代码
    * 编写主要的业务逻辑代码
    *
    在企业开发中如何进行多人开发
    * 一般情况下会将(函数的实现), 编写到.c文件中, 同时会将.c文件中需要暴露给外界使用的方式名称的声明写到.h文件中
    * 为什么编写了.c文件还需要编写一个.h文件, 原因很简单, (函数的实现)是你编写的, 那么函数的作用,形参你最了解, 所以应该由你来编写

    注意:
    * 在企业开发中, 其它人不需要关系函数具体是如何实现的, 只需要关心如何使用这个函数即可
    * 所以(函数的实现)和声明都应该让同一个人来完成
    *
    * main函数中一般为主要业务逻辑,不能有太多冗余代码
    * #include的作用:
    * 将后面指定文件中的内容拷贝到当前文件中
    * <>从系统的环境变量中去拷贝, 一般情况下只有用到系统函数才使用<>
    * " "从指定的路径中去拷贝, 一般情况下使用同事/自己编写的.h文件都用""

        #include "ds.h" // 导入大神编写的.h文件,例:#include "D:\WWW\ds.h"
        本质就是将大神编写的.h文件中的代码拷贝过来
    

    相关文章

      网友评论

          本文标题:2018-09-07(指针,多级指针,指针数组,指针数组字符串,

          本文链接:https://www.haomeiwen.com/subject/hmyrgftx.html