zoukankan      html  css  js  c++  java
  • c语言基础学习09_复合类型

    =============================================================================
    涉及到的知识点有:
    一、结构体
    1、定义结构体struct和初始化、2、访问结构体中的成员、3、结构体的内存对齐模式、4、结构体中元素的位字段、
    5、结构体中的数组、6、结构体的嵌套、7、结构体的赋值、8、通过指针访问结构体成员(即指向结构体的指针)、
    9、通过指针访问结构体数组(即指向结构体数组的指针)、10、结构体变量的指针成员与浅拷贝、深拷贝的操作(即结构中的数组成员和指针成员)。
    二、联合体
    三、枚举类型
    1、c语言中枚举的定义、2、改变枚举的默认值。
    四、typedef
    课堂练习:在堆中处理结构体的指针成员。
    =============================================================================
    =============================================================================
      之前学过的数据类型叫做基本数据类型,也叫做单一数据类型。
      例如:整型、浮点型、指针类型。

      结构体、联合体、枚举类型、typedef叫做符合类型。

    一、结构体

    1、定义结构体struct和初始化
    --------------------------------------
    01、定义结构体struct不初始化

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 #include <string.h>
     3 #pragma warning(disable:4996)
     4 
     5 struct student
     6 {
     7     char name[100];
     8     int age;
     9 };
    10 
    11 int main()
    12 {
    13     auto struct student st;    //定义了一个student类型的结构体变量,名字叫st。不初始化成员变量的值。
    14     //st.name = "刘德华";    //不能这么写,因为name是一个数组名,是一个数组变量,而"刘德华"是常量。
    15     strcpy(st.name, "刘德华");
    16     st.age = 20;
    17     printf("name = %s, age = %d
    ", st.name, st.age);    //name = 刘德华, age = 20
    18 
    19     return 0;
    20 }

    -----------------------------------------------------------------------------

    02、定义结构体struct并初始化

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 #include <string.h>
     3 #pragma warning(disable:4996)
     4 
     5 struct student
     6 {
     7     char name[100];
     8     int age;
     9 };
    10 
    11 int main01()
    12 {
    13     auto struct student st = { "周星驰", 40 };    //定义了一个student类型的结构体变量,名字叫st。并同时初始化成员变量的值。
    14     printf("name = %s, age = %d
    ", st.name, st.age);    //name = 周星驰, age = 40
    15 
    16     return 0;
    17 }
    18 
    19 int main02()
    20 {
    21     auto struct student st = { "周杰伦" };    //定义了一个student类型的结构体变量,名字叫st。并同时初始化成员变量的值。
    22     printf("name = %s, age = %d
    ", st.name, st.age);    //name = 周杰伦, age = 0
    23 
    24     return 0;
    25 }
    26 
    27 int main03()
    28 {
    29     auto struct student st = { 0 };    //定义了一个student类型的结构体变量,名字叫st。并同时初始化成员变量的值。
    30     printf("name = %s, age = %d
    ", st.name, st.age);    //name = , age = 0
    31 
    32     return 0;
    33 }
    34 
    35 int main()
    36 {
    37     auto struct student st = { .age = 30, .name = "张学友" };    //定义了一个student类型的结构体变量,名字叫st。并同时初始化成员变量的值。
    38     printf("name = %s, age = %d
    ", st.name, st.age);    //name = 张学友, age = 30
    39 
    40     return 0;
    41 }

    =============================================================================
    2、访问结构体中的成员

      . 点操作符
    =============================================================================
    3、结构体的内存对齐模式

      编译器在编译一个结构的时候采用内存对齐模式。
      结构体总是以最大的成员变量作为对齐单位。(即一个结构体变量的成员总是以最大的那个元素作为对齐单位的。)

      结构体对齐说明,如下图所示:

      结构体成员出现的先后顺序不同,导致结构体会占用不同的内存,如下图所示:

      如果结构体成员出现数组,那么是以数组的具体每个成员作为对齐标准。(即:不会以数组本身的大小作为对齐单位的。)

      如果一个结构体中所有成员都是一种类型,那么这个结构体变量在内存中就基本和一个数组类似了。

      结构体变量的地址就是这个结构体首元素的地址
    -----------------------------------------------------------------------------
    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 
     3 struct D
     4 {
     5     char a[10];
     6     char b;
     7 };
     8 
     9 struct E
    10 {
    11     char a1;
    12     short a2;
    13     int a3;
    14 };
    15 
    16 //结构体对齐小规律:总是以2的倍数字节对齐的,因为计算机是二进制的,且c语言里面没有3个字节的数据。
    17 //1个字节,2个字节,4个字节,8个字节。1个字节可以等同于占用2个字节。
    18 struct F
    19 {
    20     char a1;
    21     short a2;
    22     int a3;
    23     short a4;
    24     char a5;
    25 };
    26 
    27 int main()
    28 {
    29     struct D d = { 0 };
    30     printf("%u
    ", sizeof(d));    //11
    31 
    32     char *s = (char *)&d;    //结构体D *类型和char *类型不兼容,需要强转。
    33     s[0] = 'a';
    34     s[1] = 'b';
    35     s[10] = 'c';
    36     printf("%s%c
    ", d.a, d.b);    //abc
    37     //由以上可知,所有的数据类型,都可以把它抽象成一个char的数组。包括结构体变量也不例外。
    38     //但是呢结构体变量本身也是个变量,所以不能直接把它当数组名来用,必须要取地址。
    39     printf("%p, %p, %p
    ", &d, d.a, &s[0]);    //00CFF9A4, 00CFF9A4, 00CFF9A4
    40     //结构体变量的地址就是这个结构体首元素的地址。
    41 
    42     //可以通过指针来利用被闲置的内存。
    43     struct E e = { 1, 2, 3 };
    44     s = &e;
    45     s[1] = 10;
    46     printf("%p
    ", &e);    //可以通过调试的方法看下内存。
    47 
    48     return 0;    //在这一行加一个断点。
    49 }
    结构体对齐小规律:总是以2的倍数字节对齐的。如下图所示:

    =============================================================================
    4、结构体中元素的位字段

    定义一个结构体的时候可以指定具体元素的位长。

    小知识补充:

    一个字节的二进制表示的最大整数和最小整数是多少?
    首先分清楚8个bit可以表示256种可能,但是表现为无符号的整数时是0到255,因为0到255共计256个数。

    无符号位时:
      无符号的正数的最大值:max = 1111 1111 = 2^8 - 1 = +255
      无符号的正数的最小值:min = 0000 0000 = 2^0 = 1 = +0

    有符号位时:
    注意:在计算机内部,正数和负数都是以补码的方式存放的。又因为正数的补码是其本身,所以简言之:所有的负数都是以补码的方式存放的。
    而打印的时候是以原码输出的。

      有符号的正数的最大值:max = 0111 1111 = (2^8) / 2 - 1 = +127
      有符号的负数的最小值:min = 1000 0000 = -128
      -128到127共计256个数。
    -----------------------------------------------------------------------------
    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 struct A
     3 { 
     4     unsigned char a : 2;    //a有2个bit大小。无符号:00~11            0~3
     5     unsigned char b : 4;    //b有4个bit大小。无符号:0000~1111        0~15
     6     char c : 4;                //c有4个bit大小,有符号:最高位是符号位    -8~7    
     7     char d;                    //d有8个bit大小,有符号:最高位是符号位    -128~127
     8 };
     9 
    10 //结构体位字段的应用:霓虹灯管、音乐喷泉。每个小灯只有两种状态:亮或灭。
    11 //硬件特别差的情况下使用c语言写程序。
    12 struct B
    13 {
    14     unsigned char a1 : 1;
    15     unsigned char a2 : 1;
    16     unsigned char a3 : 1;
    17     unsigned char a4 : 1;
    18     unsigned char a5 : 1;
    19     unsigned char a6 : 1;
    20     unsigned char a7 : 1;
    21     unsigned char a8 : 1;
    22 };
    23 
    24 struct C
    25 {
    26     char a1 : 1;
    27     int a2 : 1;    //这样写是不合理的!
    28 };
    29 
    30 int main()
    31 {
    32     struct A a;
    33     a.a = 5;
    34     printf("%x
    ", a.a);    //101-->01-->1(无符号的16进制的1)
    35     a.b = 16;
    36     printf("%x
    ", a.b);    //1 0000-->0000-->0(无符号的16进制的0)
    37     a.c = 13;    //1101
    38     printf("%d
    ", a.c);    //1101-->最高位为符号位,-101-->-3(有符号的10进制-3)
    39     a.d = 13;
    40     printf("%d
    ", a.d);    //13(有符号的10进制13)
    41 
    42     struct B b;
    43     printf("%u
    ", sizeof(b));    //1
    44     b.a1 = 1;
    45     b.a2 = 0;
    46     b.a3 = 1;
    47     b.a4 = 0;
    48     b.a5 = 1;
    49     b.a6 = 0;
    50     b.a7 = 1;
    51     b.a8 = 0;
    52 
    53     struct C c;
    54     printf("%u
    ", sizeof(c));    //8    因为是以int对齐的。
    55 
    56     return 0;
    57 }

    =============================================================================
    5、结构体中的数组

      结构体既可以定义变量,又可以定义数组!

      对于cpu来讲,处理int的效率是最高的,但是int比char要多占内存而已。如下例子:
      int i;    //由于i < 100;按道理来讲,我们应该定义为char i;的,但是我们却不这样做,原因如上,且i是栈变量,只是在内存中呆一会就会消失的。
      for(i = 0 ; i < 100; i++)
      {

      }
    -----------------------------------------------------------------------------
    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 #include <string.h>
     3 #pragma warning(disable:4996)
     4 
     5 struct student
     6 {
     7     char name[20];
     8     unsigned char age;
     9     int sex;
    10 };
    11 
    12 int main()
    13 {
    14     struct student st[3];    //这句话的意思是:定义一个结构体数组,有3个成员,每个成员类型都是struct student的。
    15     //可以通过strcpy来进行赋值。
    16     //strcpy(st[0].name, "刘德华");
    17     
    18     //也可以使用手动输入赋值。
    19     //scanf("%s", &st[0].name);    //等价于scanf("%s", st[0].name);
    20     //scanf("%d", &st[0].age);
    21     //scanf("%d", &st[0].sex);
    22     //printf("%s, %d, %d
    ", st[0].name, st[0].age, st[0].sex);
    23 
    24     //用循环给每个结构体数组成员赋值
    25     int i;
    26     for (i = 0; i < 3; i++)
    27     {    
    28         printf("please input name ");
    29         scanf("%s", st[i].name);    //等价于scanf("%s", &st[0].name);
    30         printf("please input age ");
    31         scanf("%d", &st[i].age);
    32         printf("please input sex ");
    33         scanf("%d", &st[i].sex);
    34     }
    35 
    36     for (i = 0; i < 3; i++)
    37     {
    38         if (st[i].sex == 1)
    39         {
    40             printf("%s, %d, 男
    ", st[i].name, st[i].age);
    41         }
    42         else
    43         {
    44             printf("%s, %d, 女
    ", st[i].name, st[i].age);
    45         }    
    46     }
    47 
    48     return 0;
    49 }

    --------------------------------------
    //也可以定义一个结构体数组的同时进行初始化

    struct student st[3];    //这句话的意思是:定义一个结构体数组,有3个成员,每个成员类型都是struct student的。
    
    struct student st[3] = { {"刘德华", 20, 1}, {"安倍", 30, 0}, {"苍老师", 25, 0} };
    输出结果是:
    刘德华, 20, 男
    安倍, 30, 女
    苍老师, 25, 女
    
    struct student st[3] = { {"刘德华", 20, 1}, {"安倍", 30, 0} };
    输出结果是:
    刘德华, 20, 男
    安倍, 30, 女
    , 0, 女
    
    struct student st[] = { {"刘德华", 20, 1}, {"安倍", 30, 0} };
        
        //此时循环条件需要变一下。如下:
        for (i = 0; i < sizeof(st) / sizeof(st[0]); i++)
        {
    
        }

    -----------------------------------------------------------------------------
    //小练习:把一个结构体数组按照年龄大小排序。
    //方法:使用冒泡排序结构体数组。

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 #include <string.h>
     3 #pragma warning(disable:4996)
     4 
     5 struct student
     6 {
     7     char name[20];
     8     unsigned char age;
     9     int sex;
    10 };
    11 
    12 void swap_str(char *a, char *b)
    13 {
    14     char tmp[20] = { 0 };
    15     strcpy(tmp, a);
    16     strcpy(a, b);
    17     strcpy(b, tmp);
    18 }
    19 
    20 void swap_int(int *a, int *b)
    21 {
    22     int tmp = *a;
    23     *a = *b;
    24     *b = tmp;
    25 }
    26 
    27 int main()
    28 {
    29     struct student st[] = { { "刘德华", 54, 1 }, { "苍井空", 23, 0 }, { "陈冠希", 56, 1 } };
    30     int i, j;
    31     for (i = 0; i < sizeof(st) / sizeof(st[0]); i++)
    32     {
    33         for (j = 1; j < sizeof(st) / sizeof(st[0]) - 1; j++)
    34         {
    35             if (st[j].age < st[j - 1].age)
    36             {
    37                 swap_str(st[j].name, st[j - 1].name);
    38                 swap_int(&st[j].age, &st[j - 1].age);
    39                 swap_int(&st[j].sex, &st[j - 1].sex);
    40             }
    41         }
    42     }
    43 
    44     for (i = 0; i < sizeof(st) / sizeof(st[0]); i++)
    45     {
    46         if (st[i].sex == 1)
    47         {
    48             printf("%s, %d, 男
    ", st[i].name, st[i].age);
    49         }
    50         else
    51         {
    52             printf("%s, %d, 女
    ", st[i].name, st[i].age);
    53         }
    54     }
    55 
    56     return 0;
    57 }
    58 输出结果是:
    59 苍井空, 23, 女
    60 刘德华, 54, 男
    61 陈冠希, 56, 男

    =============================================================================
    6、结构体的嵌套

      一个结构的成员还可以是另外一个结构体类型。

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 
     3 struct A
     4 {
     5     char a1;
     6 };
     7 
     8 struct B
     9 {
    10     struct A a;
    11     char a2;        //如下图所示:
    12     int a1;
    13 };
    14 
    15 //error C2016 : C 要求一个结构或联合至少有一个成员。
    16 //但是在c++中,下面这个语法是成立的。大小为1个字节。
    17 /*
    18 struct D
    19 {
    20 
    21 };
    22 */
    23 
    24 //注意与上面的区别:
    25 struct A1
    26 {
    27     int a1;
    28     char a2;
    29 };
    30 
    31 struct A2
    32 {
    33     struct A1 a1;
    34     char a2;        //上面结构体变量是作为一个整体存在的,占用了8个字节。不可能把a2补到结构体a1的后面去的,所以此时的a2是单独的一个对齐单位。如下图所示:
    35     int a3;
    36 };
    37 
    38 int main()
    39 {
    40     struct B b;
    41     printf("%u
    ", sizeof(b));    //8
    42     b.a.a1= 1;
    43     b.a2 = 2;
    44     printf("%p
    ", &b.a.a1);    //005FF8B4
    45 
    46     struct A2 a2;
    47     printf("%u
    ", sizeof(a2));    //16
    48 
    49     return 0;
    50 }

    如下图所示:结构体嵌套说明

    =============================================================================
    7、结构体的赋值

    结构体变量之间的赋值就是简单的结构体变量之间的内存拷贝。
    =============================================================================
    8、通过指针访问结构体成员(即指向结构体的指针)

      -> 操作符
      (*p).a 等同于 p->a

    vs2017下示例代码如下:

     1 #include <stdio.h> 
     2 #include <string.h>
     3 #pragma warning(disable:4996)
     4 
     5 struct student
     6 {
     7     char name[20];
     8     int age;
     9 };
    10 
    11 int main()
    12 {
    13     struct student st1 = { "abc", 30 };
    14     struct student st2;
    15     st2 = st1;    //结构体变量之间的赋值就是简单的结构体变量之间的内存拷贝。等价于下句:
    16     //memcpy(&st2, &st1, sizeof(st1));
    17     printf("%s, %d
    ", st2.name, st2.age);    //abc, 30
    18 
    19     struct student *p;
    20     p = &st1;    //通过指针访问结构体成员(即指向结构体的指针)
    21     //strcpy((*p).name, "hello");
    22     //(*p).age = 50;
    23     strcpy(p->name, "hello");
    24     p->age = 50;
    25     printf("%s, %d
    ", st1.name, st1.age);    //hello, 50
    26 
    27     return 0;
    28 }

    =============================================================================
    9、通过指针访问结构体数组(即指向结构体数组的指针)

    vs2017下示例代码如下:

     1 #include <stdio.h> 
     2 #include <string.h>
     3 #pragma warning(disable:4996)
     4 
     5 struct student
     6 {
     7     char name[20];
     8     int age;
     9 };
    10 
    11 int main()
    12 {
    13     struct student st[3] = { { "张三", 34 }, { "李四", 52 }, { "王二麻子", 82 } };
    14     struct student *p = st;
    15     p->age = 100;
    16     p++;
    17     p->age = 200;
    18     p--;
    19 
    20     int i;
    21     for (i = 0; i < 3; i++)
    22     {
    23         printf("%s, %d
    ", p[i].name, p[i].age);
    24     }
    25 
    26     return 0;
    27 }

    =============================================================================
    10、结构体变量的指针成员与浅拷贝、深拷贝的操作(即结构中的数组成员和指针成员)

      一个结构中可以有数组成员,也可以有指针成员;
      如果是指针成员,结构体成员在初始化和赋值的时候就需要提前为指针成员分配内存!

    vs2017下示例代码如下:

     1 #include <stdio.h> 
     2 #include <string.h>
     3 #pragma warning(disable:4996)
     4 
     5 struct student
     6 {
     7     char name[20];
     8     int age;
     9 };
    10 
    11 struct man
    12 {
    13     char *name;
    14     int age;
    15 };
    16 
    17 //浅拷贝。
    18 int main()
    19 {
    20     struct student st = { 0 };
    21     struct student st1 = { 0 };
    22     strcpy(st.name, "刘德华");
    23     st.age = 30;
    24     st1 = st;    //结构体变量之间的赋值就是简单的结构体变量之间的内存拷贝。
    25     printf("%s, %d
    ", st1.name, st1.age);    //刘德华, 30
    26 
    27     struct man m = { 0 };    //m里面的name是什么?答:是空指针null。没有指向有效地址,就不能进行strcpy的操作。用堆内存解决如下:
    28     struct man m1 = { 0 };
    29     m.name = calloc(20, sizeof(char));    //因为struct man里面的name是char *,即通过堆内存分配空间的地址编号给它,它才能进行strcpy的操作。
    30     strcpy(m.name, "张学友");
    31     m.age = 40;
    32 
    33     m1 = m;        //浅拷贝。结构体变量之间的赋值就是简单的结构体变量之间的内存拷贝。
    34     free(m.name);    //把m.name释放掉了,m1就指向了一块未知的空间了。变成野指针了。输出就变成未知的了。
    35     printf("%s, %d
    ", m1.name, m1.age);    //葺葺葺葺葺葺葺葺葺葺葺葺, 40
    36 
    37     return 0;
    38 }
    39 浅拷贝说明如下图所示:
    40 
    41 //深拷贝。
    42 int main()
    43 {
    44     struct student st = { 0 };
    45     struct student st1 = { 0 };
    46     strcpy(st.name, "刘德华");
    47     st.age = 30;
    48     st1 = st;    //结构体变量之间的赋值就是简单的结构体变量之间的内存拷贝。
    49     printf("%s, %d
    ", st1.name, st1.age);    //刘德华, 30
    50 
    51     struct man m = { 0 };    //m里面的name是什么?答:是空指针null。没有指向有效地址,就不能进行strcpy的操作。用堆内存解决如下:
    52     struct man m1 = { 0 };
    53     m.name = calloc(20, sizeof(char));    //因为struct man里面的name是char *,即通过堆内存分配空间的地址编号给它,它才能进行strcpy的操作。
    54     strcpy(m.name, "张学友");
    55     m.age = 40;
    56 
    57     m1.name = calloc(20, sizeof(char));
    58     memcpy(m1.name, m.name, 20);    //深拷贝。
    59     m1.age = m.age;
    60     free(m.name);    
    61     printf("%s, %d
    ", m1.name, m1.age);    //张学友, 40
    62     free(m1.name);
    63 
    64     return 0;
    65 }

    =============================================================================
    11、在堆中创建的结构体变量

      如果结构体中有指针类型成员,同时结构体在堆中创建,那么释放堆中的结构体之前需要提前释放结构体中的指针成员指向的内存。

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #pragma warning(disable:4996)
     5 
     6 struct student        
     7 {
     8     char name[20];    //这个不在内存里,这是c语言的写法而已,还没有成为变量,只能叫结构体,只有在代码中明确定义后才成为结构体变量了。
     9     int age;
    10 };
    11 
    12 struct man
    13 {
    14     char *name;
    15     int age;
    16 };
    17 
    18 int main()
    19 {
    20     struct student st = { 0 };    //请问st.name在堆里面还是栈里面?答:在栈里面。    ((*p).name 等价于 p->name)
    21     struct student *p = malloc(sizeof(struct student));    //请问(*p).name在栈里面还是在堆里面?答:在堆里面。因为本句等价于,如下:
    22     //struct student *p;
    23     //p = malloc(sizeof(struct student));
    24     free(p);
    25 
    26     struct man *p1 = malloc(sizeof(struct man));    //p1->name在栈里还是堆里?答:在堆里面,但是是一个野指针!需要进行驯化!
    27     p1->name = malloc(20);
    28     strcpy(p1->name, "苍老师");
    29     p1->age = 20;
    30     printf("%s, %d
    ", p1->name, p1->age);    //苍老师, 20
    31     free(p1->name);    //先释放p1->name的堆内存。
    32     free(p1);        //再释放p1的堆内存。
    33 
    34     return 0;
    35 }

    在堆中创建的结构体变量中有指针变量和无指针变量时的内存说明图,如下:

    =============================================================================
    12、将结构体作为函数的参数、将结构体的指针作为函数的参数

      在定义一个和结构体有关的函数时,到底是使用结构体,还是使用结构体的指针呢?
      答:指针作为参数时,只需要传递一个地址,所以代码效率高。

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #pragma warning(disable:4996)
     5 
     6 struct student
     7 {
     8     char name[20];
     9     int age;
    10 };
    11 
    12 void set_student(struct student st)
    13 {
    14     strcpy(st.name, "123456");
    15     st.age = 0;
    16 }
    17 
    18 void set_student01(struct student *st)
    19 {
    20     strcpy(st->name, "123456");
    21     st->age = 0;
    22 }
    23 
    24 void print_student(struct student st)    //因为st是形参,当调用该函数的时候,在栈里面会有一个浅拷贝的过程,是结构体变量之间的赋值,即st = s;
    25 {                                        //假如结构体变量很大时,拷贝的代价很大,并不好!如何优化提升效率呢?如下:
    26     printf("%s, %d
    ", st.name, st.age);
    27 }
    28 
    29 //既然函数的形参是指针,我们不希望被内部改变,那就一般加上const。
    30 void print_student_new(const struct student *st)    //st = &s;    //相当于一个整数的赋值。
    31 {
    32     printf("%s, %d
    ", st->name, st->age);
    33 }
    34 
    35 int main()
    36 {
    37     struct student s = { "abc", 40 };
    38     set_student(s);        //s的值在函数调用完成之后会改变吗?答:不会,因为形参的值的改变不会影响实参的值。
    39     set_student01(&s);    //那么现在就想通过形参的值来改变实参的值,怎么办呢?答:用指针即可。
    40 
    41     //print_student(s);    
    42     print_student_new(&s);
    43 
    44     return 0;
    45 }

    =============================================================================
    =============================================================================
    二、联合体

      联合 union 是一个能在同一个存储空间存内储不同类型数据的类型。

      联合体所占用内存的长度等于其最长成员的长度,有时也叫做共用体。

      联合体虽然可以有多个成员,但同一时间只能存放其中的一种。

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 
     3 union A
     4 {
     5     int a1;
     6     short a2;
     7     char a3;
     8     int a4;
     9     int a5;
    10 
    11     char *p;
    12 };
    13 
    14 int main()
    15 {
    16     union A a;
    17     a.a1 = 1;
    18     printf("%u
    ", sizeof(a));    //4
    19     printf("%p, %p, %p
    ", &a.a1, &a.a2, &a.a3);    //012FFD70, 012FFD70, 012FFD70
    20 
    21     a.a3 = 10;
    22     a.a1 = 0;
    23     printf("%d
    ", a.a3);        //0
    24 
    25     a.a1 = 0x12345678;
    26     a.a3 = 0;
    27     printf("%x
    ", a.a1);        //12345600
    28 
    29     a.a1 = 0x12345678;
    30     a.a2 = 0;
    31     printf("%x
    ", a.a1);        //12340000
    32 
    33     a.p = malloc(10);    //假设这块内存的编号为0x12345。此时p的值为0x12345,即:*p = 0x12345;
    34     a.a1 = 0;            //因为它们共享一块内存,所以就导致了p的值也成了0了,即:*p = 0。
    35     free(a.p);            //那么用malloc申请的堆内存不能够被free了,且申请的堆内存丢失了,找不着。
    36 
    37     return 0;
    38 }

    =============================================================================
    =============================================================================
    三、枚举类型

    1、c语言中枚举的定义

      c语言中的枚举比较松散,c++语言中的枚举比较严谨。

      可以使用枚举(enumerated type)声明代表整数常量的符号名称,关键字enum创建一个新的枚举类型。

      实际上,enum常量是int类型的。

      枚举的基本用法:可以快速的去定义很多整型常量,使得代码可读性又得以增强。

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 #include <string.h>
     3 #pragma warning(disable:4996)
     4 
     5 //用宏常量的方法,使得代码可读性增强。但是呢,如果要定义的常量特别多呢,用宏定义就不方便了。答:那就用枚举。
     6 //#define WOMAN 0
     7 //#define MAN 1
     8 
     9 //枚举的基本用法:可以快速的去定义很多整型常量,使得代码可读性又得以增强。
    10 enum sex { woman, man, intersex, shemale };
    11 
    12 struct man
    13 {
    14     char name[20];
    15     int age;
    16     int sex;
    17 };
    18 
    19 int main()
    20 {
    21     struct man m;
    22     strcpy(m.name, "tom");
    23     m.age = 20;
    24     //m.sex = MAN;    //m.sex = 1;
    25 
    26     enum sex s;    //在c语言中,左边这三句等价于一句,如下:
    27     s = man;    //m.sex = man;
    28     m.sex = s;
    29 
    30     printf("%d, %d
    ", man, shemale);    //1, 3
    31     //man = 6;            //不能这么写,因为man是一个整型的常量,常量不能作为左值的。
    32     //int *p = &man;    //也不能这么写,因为man是一个整型的常量,常量是不能取地址的。等同于,如下所示:
    33     //int *p = &100;    //因为100本身在系统内是由cpu产生的立即数(cpu中有立即数发生器),不在内存里面。
    34     //const char *p = "hello";    //"hello"是一个字符串常量,又因为cpu不能自动生成这样一个字符串,在内存的常量区里面。
    35     //int a = 100;        //这句话映射到cpu底层的意思是:cpu生成一个立即数100,cpu在内存的栈中分配一个4个字节的空间,然后把这个空间的值设置为100。
    36 
    37     return 0;
    38 }

    =============================================================================
    2、改变枚举的默认值

    enum sex { woman, man = 100, intersex, shemale };
    0, 100, 101, 102
    
    enum sex { woman, man, intersex, shemale = 100 };
    0, 1, 2, 100

    =============================================================================
    =============================================================================
    四、typedef

      typedef是一种高级数据特性,它能使某一类型创建自己的名字。
      为了简化代码,简化语法,增加可读性、可维护性,减少代码量。

    typedef的特点是:
      1、与#define不同,typedef仅限于数据类型,而不能是表达式或具体的值。
      2、typedef是编译器处理的,而不是预编译指令。
      3、typedef比#define更灵活。

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 
     3 typedef unsigned char BYTE;
     4 
     5 struct man
     6 {
     7     char name[20];
     8     BYTE age;    // unsigned char age;
     9 };
    10 
    11 typedef struct man M;
    12 typedef short SHORT;
    13 
    14 int main()
    15 {
    16     M m;        //struct man m;
    17     m.age = 20;
    18     BYTE a = 2;    //unsigned char a = 0;
    19 
    20     SHORT a1;    //short a1;
    21     SHORT a2;    //short a2;
    22     SHORT a3;    //short a3;
    23     SHORT a4;    //short a4;
    24     SHORT a5;    //short a5;
    25 
    26     return 0;
    27 }

    =============================================================================
    题外话:在现代编程里面,微软在出很多版本的windows时不需要维护很多源代码。他每次通过某些宏来实现。
      当一个项目做得特别大时,做完后还得需要去维护、去升级、去修改。
      微软一开始就把代码写的很优秀,一开始就把c语言能用的构架都给用了,那么最后再去维护代码的时候,就会很容易。
      其实都是通过c语言这些基本的关键字来实现的。

      他们不是必须的,但是有的时候有了这些构架后,会发现工作效率会提升很多,所以换个角度说,这些前期工作有时必须的。
      我们在写程序时,既要实现功能本身,又要有高效的产出。


    //wchar_t是2个字节的字符类型,在宽码操作系统内部处理字符串的时候一般用wchar_t。
    //在不是宽码操作系统内部处理字符串的时候一般用char。
    --------------------------------------
    #ifdef UNICODE
    typedef wchar_t TCHAR;
    #else
    typedef char TCHAR;
    #endif
    --------------------------------------
    #define UNICODE

    int main()
    {
      TCHAR s[100];
      return 0;
    }
    =============================================================================
    课堂练习:在堆中处理结构体的指针成员

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #pragma warning(disable:4996)
     5 
     6 struct man
     7 {
     8     char *name;
     9     int age;
    10     int classid;
    11 };
    12 
    13 int main()
    14 {
    15     struct man *p = calloc(3, sizeof(struct man));        //在堆内存中分配了10个连续的man。相当于别样的数组!
    16     
    17     int i;
    18     for (i = 0; i < 3; i++)
    19     {
    20         printf("please input name:");
    21         char tmp[1024] = { 0 };
    22         scanf("%s", tmp);
    23         p[i].name = malloc(strlen(tmp) + 1);
    24         strcpy(p[i].name, tmp);
    25         
    26         printf("please input age:");
    27         scanf("%d", &p[i].age);
    28 
    29         printf("please input classid:");
    30         scanf("%d", &p[i].classid);
    31     }
    32 
    33     for (i = 0; i < 3; i++)
    34     {
    35         printf("%s, %d, %d,
    ",p[i].name, p[i].age, p[i].classid);
    36         //free(p[i].name);    //分别释放离散的堆内存空间。放到这也可以。
    37     }
    38 
    39     for (i = 0; i < 3; i++)
    40     {
    41         free(p[i].name);    //分别释放离散的堆内存空间。
    42     }
    43     free(p);    //一次性释放连续的堆内存空间(其实就是堆中的数组)。
    44 
    45     return 0;
    46 }

    课堂练习:在堆中处理结构体的指针成员_堆内存占用如下图所示:


    -----------------------------------------------------------------------------
    改进:到底堆中有多少个数组成员,不确定,取决于用户的输入,用户输入3个人,就有3个成员,用户输入100个人就有100个成员。

    vs2017下示例代码如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 #pragma warning(disable:4996)
     5 
     6 struct man
     7 {
     8     char *name;
     9     int age;
    10     int classid;
    11 };
    12 
    13 int main()
    14 {
    15     struct man *p = NULL;    //calloc(3, sizeof(struct man));//在堆内存中分配了10个连续的man。相当于别样的数组!
    16     
    17     //int i;
    18     //for (i = 0; i < 3; i++)
    19     
    20     int i = 1;
    21     while(1)
    22     {
    23         printf("please input name:");
    24         char tmp[1024] = { 0 };
    25         scanf("%s", tmp);
    26         //如果用户输入的名字等于exit,则我们就退出循环。
    27         if (strcmp(tmp, "exit") == 0)
    28         {
    29             break;
    30         }
    31 
    32         p = realloc(p, sizeof(struct man) * i);
    33         p[i - 1].name = malloc(strlen(tmp) + 1);
    34         strcpy(p[i - 1].name, tmp);
    35         
    36         printf("please input age:");
    37         scanf("%d", &p[i - 1].age);
    38 
    39         printf("please input classid:");
    40         scanf("%d", &p[i - 1].classid);
    41 
    42         i++;
    43     }
    44 
    45     int a;
    46     for (a = 0; a < i - 1; a++)
    47     {
    48         printf("%s, %d, %d,
    ",p[a].name, p[a].age, p[a].classid);
    49     }
    50 
    51     for (a = 0; a < i - 1; a++)
    52     {
    53         free(p[a].name);    //分别释放离散堆空间。
    54     }
    55     free(p);    //一次性释放连续的堆内存空间(其实就是堆中的数组)。
    56 
    57     return 0;
    58 }

    例如:
      int a[20];//这个数组一旦定义,那么就不能改变他的大小,因为栈里面的数组都不能动态改变大小。那就用堆。

    =============================================================================
    =============================================================================

  • 相关阅读:
    Windows下MemCache多端口安装配置
    PL/SQL Developer 9.x 注册码
    SQL Server 表变量和临时表的区别
    SQL 存储过程入门(事务)
    把存储过程结果集SELECT INTO到临时表
    在T-SQL语句中访问远程数据库(openrowset/opendatasource/openquery)
    解决SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的 STATEMENT'OpenRowset/OpenDatasource' 的访问的方法
    C#性能优化实践
    sql server 存储过程中使用变量表,临时表的分析(续)
    Request.Url.Port 获取不到正确的端口号
  • 原文地址:https://www.cnblogs.com/chenmingjun/p/8321307.html
Copyright © 2011-2022 走看看