• C 语言基础汇总,主要包括一些容易混淆的、容易忘记的知识点,经常需要查询的。好吧,其实就是菜~
  • 持续更新ing

struct & union

union

定义

union [union tag]
{
   member definition;
   member definition;
   ...
   member definition;
} [one or more union variables];

示例

  • Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。您可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。
union Data
{
   int i;
   float f;
   char  str[20];
} data;
  • 共用体占用的内存应足够存储共用体中最大的成员。例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。下面的实例将显示上面的共用体占用的总内存大小:
#include <stdio.h>
#include <string.h>
 
union Data
{
   int i;
   float f;
   char  str[20];
};
 
int main( )
{
   union Data data;     
   // Memory size occupied by data : 20
   printf( "Memory size occupied by data : %d\n", sizeof(data));
   
 
   data.i = 10;
   data.f = 220.5;
   strcpy( data.str, "C Programming");
 
   printf( "data.i : %d\n", data.i);
   printf( "data.f : %f\n", data.f);
   printf( "data.str : %s\n", data.str);
 
   return 0;
}

data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming
  • 共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。

作用

  • 节省内存,有两个很长的数据结构,不会同时使用,比如一个表示老师,一个表示学生,如果要统计教师和学生的情况用结构体的话就有点浪费了!用共用体的话,只占用最长的那个数据结构所占用的空间,就足够了!
  • 通信中的数据包会用到共用体:因为不知道对方会发一个什么包过来,用共用体的话就很简单了,定义几种格式的包,收到包之后就可以直接根据包的格式取出数据。

大小分配

  • 结构体变量所占内存长度是其中最大字段大小的整数倍(参考:结构体大小的计算)。
  • 共用体变量所占的内存长度等于最长的成员变量的长度。例如,教程中定义的共用体Data各占20个字节(因为char str[20]变量占20个字节),而不是各占4+4+20=28个字节。
  • union的长度取决于其中的长度最大的那个成员变量的长度。即union中成员变量是重叠摆放的,其开始地址相同。
  union   mm{   
   char   a;//元长度1   
   int   b[5];//元长度4   
   double   c;//元长度8   
   int   d[3]; //元长度4
  };   
  • 本来mm的空间应该是sizeof(int)*5=20;但是如果只是20个单元的话,那可以存几个double型(8位)呢?两个半?当然不可以,所以mm的空间延伸为既要大于20,又要满足其他成员所需空间的整数倍,,因为含有double元长度8,故大小为24。

struct

定义

struct tag { 
    member-list
    member-list 
    member-list  
    ...
} variable-list ;

示例

  • 在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。以下为实例:
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct 
{
    int a;
    char b;
    double c;
} s1;
 
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
 
//也可以用typedef创建新类型
typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
  • 在上面的声明中,第一个和第二声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的,如果令 t3=&s1,则是非法的。
  • 结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
//此结构体的声明包含了其他的结构体
struct COMPLEX
{
    char string[100];
    struct SIMPLE a;
};
 
//此结构体的声明包含了指向自己类型的指针
struct NODE
{
    char string[100];
    struct NODE *next_node;
};
  • 如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:
struct B;    //对结构体B进行不完整声明
 
//结构体A中包含指向结构体B的指针
struct A
{
    struct B *partner;
    //other members;
};
 
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
    struct A *partner;
    //other members;
};
  • 实际使用示例
#include <stdio.h>
#include <string.h>
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
/* 函数声明 */
void printBook( struct Books *book );
int main( )
{
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;
 
   /* Book2 详述 */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
   /* 通过传 Book1 的地址来输出 Book1 信息 */
   printBook( &Book1 );
 
   /* 通过传 Book2 的地址来输出 Book2 信息 */
   printBook( &Book2 );
 
   return 0;
}
void printBook( struct Books *book )
{
   printf( "Book title : %s\n", book->title);
   printf( "Book author : %s\n", book->author);
   printf( "Book subject : %s\n", book->subject);
   printf( "Book book_id : %d\n", book->book_id);
}

Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700

大小分配

  • 博客园:结构体大小的计算
  • 结构体中成员变量分配的空间是按照成员变量中占用空间最大的来作为分配单位,同样成员变量的存储空间也是不能跨分配单位的,如果当前的空间不足,则会存储到下一个分配单位中。
  • 结构体变量的首地址能够被其最宽基本类型成员的大小所整除。
  • 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding)。即结构体成员的末地址减去结构体首地址(第一个结构体成员的首地址)得到的偏移量都要是对应成员大小的整数倍。
  • 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在成员末尾加上填充字节。

弹性数组

  • 定义数组时,没有指明其长度,此为弹性数组。
  • 弹性数组只能存在于结构体中,并且必须满足如下条件
    • 弹性数组必须为结构体的最后一个成员
    • 该结构体必须包含一个非弹性数组的成员
    • 编译器需要支持 C99 标准。

示例

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <stdint.h>
 ​
 typedef struct {
     int32_t id;
     int32_t grade;
     int8_t name[];
 }student_info_struct;
 ​
 int main() {
     int32_t *tmp = 0;
     int8_t *name = "sdc";
     student_info_struct *si = NULL;
 ​
     printf("sizeof(struct) = %d\n", sizeof(student_info_struct));
 ​
     si = (student_info_struct *)malloc(sizeof(student_info_struct) + strlen(name) + 1); // +1 是为了存储 '\0'
     if(NULL == si)
     {
         printf("malloc failed\n");
         return -1;
     }
     memset((void *)si, 0, sizeof(student_info_struct) + strlen(name) + 1);
 ​
     si->id = 123;
     si->grade = 6;
     memcpy((void *)si->name, name, strlen(name));
 ​
     printf("addr:\n");
     printf("si: 0x%p\n", si);
     printf("si->grade: 0x%p\n", &si->grade);
     printf("si->name: 0x%p\n", &si->name);
     printf("si->name: 0x%p\n", si->name);
 ​
     return 0;
 }
  • 输出结果:
 sizeof(struct) = 8 // 弹性数组不占空间
 addr: 
 &si: 0x00000000003C21C0
 &si->grade: 0x00000000003C21C4
 &si->name: 0x00000000003C21C8
 si->name: 0x00000000003C21C8