C语言学习笔记—复合类型

结构体

  • 声明结构体struct类型:

  1. C语言中,结构体也是一种数据类型,它由程序员自己定义,可以使用结构体(Struct)来存放一组不同类型的数据,其定义形式如下:
    struct 结构体名
    {
        成员列表(类型名 成员名);
    };
  2. 注意:大括号后面的分号' ; '不能少,这是一条完整的语句。
    struct student
    {
        char *name;
        int age;
        float weight;
    };
  3. 定义结构体只是指定了一个结构体类型,但其中并无具体数据,系统对之也不分配实际内存单元。在定义了结构体变量后,系统会为之分配内存单元。
  • 定义结构体变量:

  1. 在声明类型的同时定义变量。如下:定义了 students 为 struct student 类型的变量。
    struct student       // 结构体类型名
    {
        char *name;
        int age;
        float weight;
    }students;          // 结构体变量名
  2. 先声明结构体类型再定义变量名。如下:
    struct student      // 结构体类型名
    {
        char *name;
        int age;
        float weight;
    };    
    
    struct student students;    // 定义一个struct student类型的结构体变量,名字叫students      
  3. 直接定义结构体变量。这种方式表明只需要当前结构体变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名。如下:
    struct                // 无结构体类型名 
    {
        char *name;
        int age;
        float weight;
    }st1,st2;            // 定义了2个结构体变量st1和st2,之后就不能再定义新的变量
  • 访问结构体成员:

  1. 不能将一个结构体变量作为一个整体进行输入和输出,结构体使用成员(分量)运算符' . ' 获取单个成员,获取结构体成员的一般形式为:
    结构体变量名.成员名
  2. 如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低一级的成员。只能对最低的成员进行赋值或存取以及运算。
  3. 例子:
    /**** 输入:2 60 ****
    ***** 输出:name:zhang, age:18, num:3, score:70 ****/
    #include <stdio.h>
    #include <string.h>
    
    struct student
    {
        char name[100];
        int age;
        int num;
        int score;
    };
    
    int main()
    {
        struct student st;
        st.age = 18;
        strcpy(st.name, "zhang");
        scanf("%d%d", &st.num, &st.score);
        st.num++;
        st.score = st.score + 10;
        printf("name:%s, age:%d, num:%d, score:%d\n",st.name, st.age, st.num, st.score);
        return 0;
    }
  • 结构体变量初始化:

  1. 对结构体变量可以在定义时指定初始值。如:
    struct student       
        char *name;
        int age;
        float weight;
    }students = {"zhang",18,1.72};        
    ​或
    struct student     
    {
        char *name;
        int age;
        float weight;
    };    
    struct student students =  {"zhang",18,1.72};          
    
    
  • 结构体的内存对齐方式:

  1. 编译器在编译一个结构的时候采用内存对齐模式,结构体总是以最大的成员为对齐单位。
    /**** 输出:A:8, B:12, C:8 
    ***** 三个结构体中,int类型占用字节最大,应以int对齐,结构体占用总字节数应是int占用的倍数****/
    #include <stdio.h>
    
    /* 占用8个字节,int占4个,char占一个,空3个 */
    struct A
    {
        char a1;
        int a2;
    };
    
    /* 占用12个字节,int占4个,char占5个,空3个 */
    struct B
    {
        char b1;
        char b2;
        char b3;
        char b4;
        char b5;
        int b6;
    };
    
    /* 占用8个字节。int占4个;char占1个,闲置1个;short占2个 */
    struct C
    {
        char c1;
        short c2;
        int c3;
    };
    
    int main()
    {
        struct A a;
        struct B b;
        struct C c;
        printf("A:%u, B:%u, C:%u\n", sizeof(a), sizeof(b), sizeof(c));
        return 0;
    }
  2. 同一类型的成员定义时应应紧挨着一起,否则可能占用多余的内存,如下:
    /**** 输出:A:12, B:8 ****/
    #include <stdio.h>
    
    /* 占用12个字节。int占4个;char a2占1个,闲置1个;short占2个,char a3占1个,闲置1个 */
    struct A
    {
        char a1;
        short a2;
        char a3;
        int a4;
    };
    
    /* 占用8个字节。int占4个;char占2个;short占2个 */
    struct B
    {
        char b1;
        char b2;
        short b3;
        int c4;
    };
    
    int main()
    {
        struct A a;
        struct B b;
        printf("A:%u, B:%u\n", sizeof(a), sizeof(b));
        return 0;
    }
  3. 如果结构体中的成员都是同一个类型,可以看成一维数组。
  • 指定结构体元素的位字段:

  1. 在C语言中,定义一个结构体的时候可以指定具体元素的位长(bit),这样可以充分利用内存资源。有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可,比如对于霓虹灯的控制可以采用这种方法。
    /**** 输出:a1 = 2, a2 = 0
    ***** A:1 字节, B:1 字节 ****/
    #include <stdio.h>
    
    struct A
    {
        unsigned char a1 : 2;    // a1只有2个bit大小
        unsigned char a2 : 4;     // a2只有4个bit大小
    };
    
    struct B
    {
        char b1 : 1;
        char b2 : 1;
        char b3 : 1;
        char b4 : 1;
        char b5 : 1;
        char b6 : 1;
        char b7 : 1;   
        char b8 : 1;
    };
    
    int main()
    {
        struct A a;
        struct B b;
        a.a1 = 2;     // 用bit表示:0010,取前2位,刚好是2
        a.a2 = 16;    // 用bit表示:1 0000,取前4位,刚好是0
        printf("a1 = %x, a2 = %x\n", a.a1, a.a2);
        printf("A:%u 字节, B:%u 字节\n", sizeof(a), sizeof(b)); // 不指定位字段,结构体变量b将会占用8个字节
        return 0;
    }
  2. C语言标准规定,位字段的宽度不能超过它所依附的数据类型的长度。
    struct A
    {
        unsigned char a1 : 2;      // 正确
        unsigned char a2 : 10;     // 错误,一个char最多有8个bit
    };
  3. C语言标准规定,只有有限的几种数据类型可以用于位域。在 ANSI C 中,这几种数据类型是 int、signed int 和 unsigned int(int 默认就是 signed int);到了 C99,_Bool 也被支持了。
  4. 注意:1)当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。2)当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC会压缩存储,而 VC/VS 不会。3)如果成员之间穿插着非位字段成员,那么不会进行压缩。
  • 结构体数组:

  1. 结构体数组是指数组中的每个元素都是一个结构体。在实际应用中,常被用来表示一个拥有相同数据结构的群体。如下:
    /**** 输出:zhang, 20, 1 ****/
    #include <stdio.h>
    #include <string.h>
    
    struct student
    {
        char name[2];
        unsigned char age;
        int sex;
    };
    
    int main()
    {
        struct student st[3]; // 定义一个结构体数组,有三个成员,每个成员都是struct student
        
        /* 访问数组成员中的结构体成员 */
        strcpy(st[0].name, "zhang");   
        st[1].age = 20;
        st[2].sex = 1;
        printf("%s, %d, %d\n", st[0].name, st[1].age, st[2].sex);
    
        return 0;
    }
  2. 可以在定义结构体变量的同时初始化结构体数组。
    struct student
    {
        char name[2];
        unsigned char age;
        int sex;
    };
    struct student st[3] = {{"a", 20, 1}, {"b", 18, 1}, {"c", 30, 0}};
    或
    struct student
    {
        char name[2];
        unsigned char age;
        int sex;
    }st[3] = {{"a", 20, 1}, {"b", 18, 1}, {"c", 30, 0}};
        
    
  • 嵌套结构:

  1. 一个结构体里的成员还可以是另一个结构类型。如下:
    struct A
    {
        int a1;
        char a2;
    };
    
    struct B
    {
        struct A a;
        int b1;
        int b2;
    };
    
    struct B b;
    /* 访问成员 */
    b.a.a1 = 0;
    b.b1 = 2;
  2. 例子:
    /**** 输出:A1:8, A2:16 ****/
    #include <stdio.h>
    #include <string.h>
    
    struct A1
    {
        int a1;         // 占用4个字节
        char a2;        // 占用1个字节,闲置3个
    };
    
    struct A2
    {
        struct  A1 a1;  // 嵌套的结构体
        char a2;        // 结构体变量a1作为一个整体存在,不可能吧a2补到a1后面,所以a2一定是单独的对齐单位.占用1个字节,闲置3个
        int a3;         // 占用4个字节
    };
    
    int main()
    {
        printf("A1:%u, A2:%u\n", sizeof(struct A1), sizeof(struct A2));
        return 0;
    }
  • 结构体赋值:

  1. 结构体变量之间的赋值就是简单的结构体变量zhi之间的内存拷贝。
    struct student
    {
        char name[20];
        int age;
    };
    struct student st1 = {"zhang", 20};
    struct student st2;
    st2 = st1;    // 类似于使用内存拷贝函数:memcpy(&st2, &st1, sizeof(st1));
  • 指向结构体的指针:

  1. (*p).a等价于p->a。
    struct student
    {
        char name[20];
        int age;
    };
    struct student st1;
    struct student *p;
    p = &st1;
    strcpy(p->name,"zhang"); // 或strcpy((*p).name, "zhang");
    p->age = 20;             // 或(*p).age = 20;
    
  • 指向结构体数组的指针:

  1. 例子说明:
    /**** 输出:a, 18
                b, 28
                c, 40 ****/
    #include <stdio.h>
    
    struct student
    {
        char name[20];
        int age;
    };
    
    int main()
    {
        struct student st[3] = {{"a", 20}, {"b", 30}, {"c", 40}};
        struct student *p = st;
        p->age = 18;
        p++;
        p->age = 28;
        p--;
        int i;
        for(i = 0; i < 3; i++)
            printf("%s, %d\n", p[i].name, p[i].age);
        return 0;
    }
    
  • 结构体中的数组成员和指针成员:

  1. 一个结构体可以有数组成员,也可以有指针成员,如果是指针成员,结构体成员在初始化和赋值的时候需要提前为指针成员分配内存。如下面例子:
    /**** 输出:name:zhang, age:20 ****/
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    struct student
    {
        char *name;
        int age;
    };
    
    int main()
    {
        struct student st = { 0 }; // st里面的成员name是空指针,需要指向有效地址,否则会出错
        st.name = calloc(20, sizeof(char));   // 给成员name分配内存空间
        strcpy(st.name, "zhang");
        st.age = 20;
        printf("name:%s, age:%d\n", st.name, st.age);
        free(st.name);
        return 0;
    }
    
  2. 含有指针成员的结构体变量赋值。浅拷贝和深拷贝。如下:
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    struct student
    {
        char *name;
        int age;
    };
    
    int main()
    {
        struct student st = { 0 }; // st里面的成员name是空指针,需要指向有效地址,否则会出错
        struct student st1 = { 0 };
        st.name = calloc(20, sizeof(char));   // 给成员name分配内存空间
        strcpy(st.name, "zhang");
        st.age = 20;
        st1 = st;    // 浅拷贝,只是简单的赋值
        free(st.name); 
        /* 输出结果中,name则会出现乱码。因为free后,堆空间释放掉了,
         st2中name变成野指针,所以之后再printf结构体变量st1的name成员时,出现乱码 */
        printf("st1 name:%s, st1 age:%d\n", st1.name, st1.age);   //将free函数放在printf函数后面则程序输出正确
        return 0;
    }
    
    /**** 输出:st1 name:zhang, st1 age:20 ****/
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    struct student
    {
        char *name;
        int age;
    };
    
    int main()
    {
        struct student st = { 0 }; // st里面的成员name是空指针,需要指向有效地址,否则会出错
        struct student st1 = { 0 };
        st.name = calloc(20, sizeof(char));   // 给成员name分配内存空间
        strcpy(st.name, "zhang");
        st.age = 20;
        st1.age = st.age;
    
        /* 深拷贝 */
        st1.name = calloc(20, sizeof(char));
        memcpy(st1.name, st.name, sizeof(st.name) + 1);
    
        free(st.name); 
        printf("st1 name:%s, st1 age:%d\n", st1.name, st1.age); 
        free(st1.name);
        return 0;
    }
    
  • 在堆中创建的结构体:

  1. 如果结构体有指针类型成员,同时结构体在堆中创建,那么释放堆中的结构体之前需要提前释放结构体中的指针成员指向的内存。例子说明:
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    struct student
    {
        char name[20];
        int age;
    };
    
    struct person
    {
        char *name;
        int age;
    };
    
    int main()
    {
        struct student st;  // st.name在栈里面
        struct student *p = malloc(sizeof(struct student)); // p变量在栈里面。创建struct student大小的堆空间,该堆空间包括char name[20]和int age,p指向这个堆空间,所以p->name在堆里面
        free(p);
        struct person *p1 = malloc(sizeof(struct person)); // 申请一个堆空间,p1->name在堆里面,但是个野指针,没有指向有效地址
        p1->name = malloc(20);     // 分配一个内存空间
        strcpy(p1->name, "zhang");
        p1->age = 20;
        free(p1->name);     // 必须先释放p1->name指向的堆空间,然后释放p1指向的堆空间
        free(p1);           // 若先释放p1指向的堆空间,在执行free(p1->name)时,找不到p1->name指向的空间了
        return 0;
    }
    
  • 将结构作为函数参数:

  1. 结构体作为函数参数。
    /**** 输出:li, 18 ****/
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    struct student
    {
        char name[20];
        int age;
    };
    
    void print_demo(struct student st)  // st是形参,调用函数时,在栈里面有一个浅拷贝过程st = s
    {
        printf("%s, %d\n", st.name, st.age);
    }
    
    void set_demo(struct student *st)
    {
        strcpy(st->name, "li");
        st->age = 18;
    }
    
    int main()
    {
        struct student s = {"zhang", 20};
        set_demo(&s); // 通过指针方式,传地址来改变实参的值
        print_demo(s);
        return 0;
    }
    
  2. 结构体指针作为函数参数。结构体变量名代表的是整个集合本身,作为函数参数时传递的整个集合,也就是所有成员,而不是像数组一样被编译器转换成一个指针。如果结构体成员较多,尤其是成员为数组时,传送的时间和空间开销会很大,影响程序的运行效率。所以最好的办法就是使用结构体指针,这时由实参传向形参的只是一个地址,非常快速。
    /**** 输出:li, 18 ****/
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    /**** 输出:li, 18 ****/
    struct student
    {
        char name[2000];
        int age;
    };
    
    void print_demo(struct student *st)  // st = &s 当结构体成员数组过多或数组过大,这时效率远远高于上一个程序的的操作
    {
        printf("%s, %d\n", st->name, st->age);
    }
    
    void set_demo(struct student *st)
    {
        strcpy(st->name, "li");
        st->age = 18;
    }
    
    int main()
    {
        struct student s = {"zhang", 20};
        set_demo(&s); // 通过指针方式,传地址来改变实参的值
        print_demo(&s);
        return 0;
    }
    

联合体(union)

  • union定义:

  1.  联合体union是一个能在同一个存储空间存储不同类型数据的类型,所占的内存长度等于其最长成员的长度,也叫共用体。可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。其定义和结构体类似,格式如下:
    union 共用体名
    {
        成员列表(类型 成员);
    };
  2. 结构体和共用体的区别:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
  3. 结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
  • 联合体应用:

  1. 联合体union的使用方法和结构体类似,举例说明:
    #include <stdio.h>
    
    union A
    {
        int a1;
        short a2;
        char a3;
        char *p;
    
    };
    
    int main()
    {
        union A a;
        a.a1 = 0;
        printf("%u\n", sizeof(a)); // 所有成员共用一块内存,所占内存长度为最长成员的长度,即int,结果为4
        a.a3 = 10;
        a.a1 = 1;
        printf("%d\n",a.a3); // a3的值为0,因为a1,a3共享一块内存,a1为0后,a3也被设置为0
        printf("%p, %p, %p\n", &a.a1, &a.a2, &a.a3); //发现三个成员地址一样,共用一块地址,验证上行代码
    
        /* 联合体不能用这种形式 */
        a.p = malloc(10); // p和a1共享一个内存,假设这块堆的内存为0x12345,则p的值为0x12345
        a.a1 = 0;         // 这行代码导致p的值变为0,从而导致malloc这块内存丢失掉,同时free不了,造成内存泄漏,出现双重错误
        free(a.p);
    
        return 0;
    }

枚举类型

  • 枚举定义:

  1.  枚举是 C 语言中的一种基本数据类型,可以使用枚举声明代表整数常量的符号名称,关键字enum创建一个新的枚举类型。格式如下:
    enum 枚举名 {枚举元素1,枚举元素2,……};
  2. 实际上,枚举常量是int或unsigned类型的。如下:red默认是0,后面的成员在前面成员基础上加1。
    enum color {red, yellow, green, blue, white, black};
  • 默认值:

  1. 枚举值默认从 0 开始,往后逐个加 1(递增)。
  2. 也可以给每个名字都指定一个值:
    enum color {red = 1, yellow = 3, green = 5, blue =  7, white = 9, black = 11};
  3. 可以给指定名字赋值,后面没赋值的名字都是在前面基础上加1。如:red=0,yellow=3,green=4,blue=5,white=9,black=10
    enum color {red, yellow = 3, green, blue, white = 9, black};
  • 枚举变量的定义:

  1. 先定义枚举类型,再定义枚举变量:
    enum COLOR 
    {
        red, yellow, green, blue, white, black
    };
    enum COLOR color;
  2. 定义枚举类型的同时定义枚举变量:
    enum COLOR 
    {
        red, yellow, green, blue, white, black
    }color;
  3. 省略枚举名称,直接定义枚举变量:
    enum 
    {
        red, yellow, green, blue, white, black
    }color;
  4. 注意:1)枚举列表中的 red,yellow等这些标识符的作用范围是全局的(严格来说是 main() 函数内部),不能再定义与它们名字相同的变量。不能用 & 取得它们的地址2)这些标识符都是常量,不能对它们赋值,只能将它们的值赋给其他的变量。3)枚举和宏其实非常类似:宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。我们可以将枚举理解为编译阶段的宏。

typedef

  • 定义:

  1.  C 语言提供了 typedef 关键字,它是一种高级数据特性,可以使用它来为类型取一个新的名字。如下:单字节数字定义了一个术语 BYTE
    typedef unsigned char BYTE;
  2. 类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写。如:
    BYTE  a1, a2;   // a1,a2为unsigned char类型
  3. 例子:
    #include <stdio.h>
    
    typedef unsigned char BYTE;
    
    struct student
    {
        BYTE sex;
        int age;
    };
    
    typedef struct student ST;
    
    int main()
    {
        ST st;
        st.age = 20;
        BYTE a = 10;
        return 0;
    }
  4. 例子:输入10组学生的name,age,class id,然后打印出来。
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    struct student
    {
        char *name;
        int age;
        int classid;
    };
    
    int main()
    {
        struct student *p = calloc(10, sizeof(struct student)); // 在堆中分配10个struct student
        int i;
        for(i = 0; i < 10; i++)
        {
            printf("please input name");
            char tmp[1024] = { 0 };
            scanf("%s", tmp);
            p[i].name = malloc(strlen(tmp) + 1);
            strcpy(p[i].name, tmp);
            printf("please input age");
            scanf("%d", &p[i].age);
            printf("please input class id");
            scanf("%d", &p[i].classid);
        }
        for(i = 0; i < 10; i++)
            printf("%s, %d, %d\n", p[i].name, p[i].age, p[i].classid);
    
        /* 离散的堆空间不能一次释放,必须一个一个释放(p[i].name指向的堆空间)。
           离散空间释放完后,一次释放连续堆空间(p指向的堆空间) */
        for(i = 0; i < 10; i++)
            free(p[i].name);   // 分别释放离散堆空间
        free(p);               // 释放堆中连续的空间(就是堆中的数组)
    
        return 0;
    }

猜你喜欢

转载自blog.csdn.net/qq_34935373/article/details/88812729