栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言结构体和其他构造数据类型详解

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言结构体和其他构造数据类型详解

结构体和其他构造数据类型 结构体

“结构”是一种构造类型,它是由若干“成员”组成的。

每一个成员可以是一个基本数据类型或者又是一个构造类型。

结构既然是一种“构造”而成的数据类型,那么说明和使用之前必须先定义它,也就是构造它。

结构体定义
  • 结构类型一般在函数外定义,其一般定义格式:

    struct 结构名
    {
        类型名 成员名;   //int num;  这就是结构成员
        类型名 成员名;
        …
        类型名 成员名;
    };
    

    特别注意:在定义结构类型时大括号“{}”后的分号;不可缺少,结构类型定义是一个说明性语句。

    在C语言中,关键字struct和结构名合起来构成结构类型名。

    结构是一种复杂的数据类型,是数目固定、类型不同的若干有序变量的集合。

    结构成员的类型:可以是标量、数组、指针,甚至是其他结构体。

结构变量定义
  • 其一般定义格式:struct 结构名 变量名;

    说明结构变量有以下3种方法

    • 先定义结构,再说明结构变量。

      struct student
      {
          int num;
          char name[20];
          char sex;
          float score;
      };
      struct student stu1, stu2; //说明了两个结构变量stu1和stu2为student结构类型
      //也可以用一个宏定义使用一个符号常量来表示一个结构类型
      #define STU struct student
      STU
      {
          int num;
          char name[20];
          char sex;
          float score;
      };
      STU stu1, stu2;
      
    • 在定义结构类型的同时说明结构变量。例如:

      struct student
      {
          int num;
          char name[20];
          char sex;
          float score;
      }stu1, stu2;
      //这种类型的说明的一般形式:
      struct 结构名
      {
          成员表
      }变量名表;
      
    • 直接说明结构变量。例如:

      struct
      {
          int num;
          char name[20];
          char sex;
          float score;
      }stu1, stu2;
      //这种类型的说明的一般形式:
      struct
      {
          成员
      }变量名;
      
结构变量的赋值

结构变量的赋值就是给各成员赋值,可用输入语句或赋值语句来完成。结构变量可以整体赋值,但是不能整体引用,只能按成员变量分别引用。

struct student
{
	char name[20];
	int age;
	char id[20];
}stu;
int main()
{
	struct student stu = {"张三", 30, "202105549"};  //整体赋值
}
结构变量的初始化

结构变量的初始化其实就是在定义结构变量时对结构变量进行赋值。

struct student
{
	char name[20];
	int age;
	char id[20];
}stu = {"张三", 30, "202105549"}   //结构变量的初始化
结构变量成员

在程序中使用结构变量时,往往不把它作为一个整体来使用。一般对结构变量的使用,包括赋值,输入,输出,运算等都是通过结构变量的成员来实现的。(注意与结构成员作区分)

表达结构变量成员的一般形式:结构变量名 . 成员名。例如:

stu1.num   //第一个人的学号
stu2.sex   //第二个人的性别

也可以直接给结构变量成员直接赋值:

struct student 
{
    char name[20];
    int age;
    char id[20];   
}stu;
strcpy(stu.name, "张三");    //不能用赋值运算符“=”将一个字符串直接赋值,只能用strcpy函数来复制字符串
stu.age = 30;
strcpy(stu.id, "202105549"); //不能用赋值运算符“=”将一个字符串直接赋值,只能用strcpy函数来复制字符串
结构数组的定义和引用

数组的元素也可以是结构类型,因此可以构成结构性数组,结构数组的每一个元素都是具有相同结构类型的下标结构变量。

结构数组的定义

结构数组的定义方法和结构变量类似,只需说明它为数组类型即可。例如:

struct student
{
    int num;
    char name[20];
    char sex;
    float score;
}stu[5];  //定义了一个结构数组stu,共有5个元素,stu[0]~stu[4]。每个数组元素都具有 struct student 的结构形式。
结构数组的初始化
struct student
{
    int num;
    char name[20];
    char sex;
    float score;
}stu[4] = {
    {101, "张三", 'M', 55},
    {102, "李四", 'M', 68.5},
    {103, "王五", 'F', 93.5},
    {104, "赵六", 'F', 97}
};//当全部元素进行初始化赋值时,也可不给出数组长度。
结构数组的输入与输出 指向结构变量的指针

一个指针变量当用来指向一个结构变量时,称为结构指针变量。结构指针变量中的值是所指向的结构变量的首地址。

通过结构指针可以访问该结构变量,与数组指针和函数指针类似。

结构指针变量的定义和引用
  • 结构指针变量说明的一般形式:struct 结构名* 结构指针变量名

  • 结构指针必须先赋值后才能使用;赋值是把结构变量的首地址赋予给该指针变量,不能把结构名赋予给该指针变量。

    • struct student
      {
          int num;
          char name[20];
          char sex;
          float score;
      }stu1;
      struct student* pstu;
      pstu = &stu1;  //这是正确的,将变量首地址赋给指针变量
      pstu = &student;  //这是错误的,因为 student 是结构名称,不是变量,不会分配 内存空间。
      
    • 结构名和结构变量是两个不同的概念:

      结构名表示一个结构形式,编译系统不对它分配内存空间;只有当某变量被说明为这种类型的结构时,才对该变量分配内存。

    • 有了结构指针变量,可以更好的访问结构变量的各个成员,其访问的一般形式:

      结构指针变量->成员名或者(*结构指针变量).成员名(注意括号不可少,因为成员符“.”的优先级高于“*”)

指向结构数组的指针

指针变量可以指向一个结构数组,这时结构指针变量的值是整个结构数组的首地址。

指针变量也可指向结构数组的一个元素,这时结构指针变量的值是该结构数组元素的首地址。

结构变量与函数参数

可以将一个结构变量的值传递给另一个函数,可以采用以下两种方法:(值传递)

  • 结构变量的成员做参数,用法和普通参数做实参一样,属“值传递”方式

  • 形参和实参都用结构变量,将实参结构变量直接传递给形参的结构变量,此时实参和形参类型应当完全相等

    #include 
    struct A
    {
    	int data[1000];
    	int num;
    }stu = { {1,2,3,4}, 500};
    void print1(int num)
    {
    	printf("%dn", num);
    }    //结构变量成员作为参数
    void print2(struct A stu)
    {
    	printf("%dn", stu.num);
    }    //结构变量作为参数
    int main()
    {
    	print1(stu.num);
    	print2(stu);
    	return 0;
    }//编译结果:500	500
    

也可以用指针变量做函数参数进行传送(传地址)

  • 由实参传向形参的只是地址,可以减少时间和空间上的开销

    #include 
    struct A
    {
    	int data[1000];
    	int num;
    }stu1 = { {1,2,3,4}, 500};
    void print3(struct A* ps)
    {
    	printf("%dn", ps->num);
    }    //结构指针变量作为参数
    int main()
    {
    	print3(&stu1);
    	return 0;
    }//编译结果:500
    
复杂结构体 结构体作为结构成员
#include 

struct date
{
	int year;
	int month;
	int day;
};
struct student
{
	int num;
	char name[20];
	char sex;
	struct date birthday;   //在student这个结构体里面包含了date这个结构体
	float score;
}stu1 = { 101,"张三", 'M', {2003, 5, 19}, 98 };  //结构体嵌套初始化

int main()
{
	printf("%d %s %c %d %d %d %lf", stu1.num, stu1.name, stu1.sex, stu1.birthday.year, stu1.birthday.month, stu1.birthday.day, stu1.score); //这种成员变量的引用方式称为“逐级引用” 
	return 0;
}//编译结果:101 张三 M 2003 5 19 98.000000

那student结构体内部能不能包含student结构体本身呢?(在结构中包含一个类型为该结构本身的成员是否可以呢?)

结构体的自我引用

结构体自我引用是指在结构体内部,包含指向自身类型结构体的指针,此方式的引用在数据结构类型定义应用中较多。

同时,结构体还可以相互引用,也就是在多个结构体中,都包含指向其他结构体的指针。

  • struct student
    {
    	int num;
    	char name[20];
    	char sex;
    	float score;
    	struct student* link;
    }stu1;
    
    • link指针是指向struct student类型的指针,作为自身的一个指针类型成员变量
  • struct student
    {
    	int num;
    	char name[20];
    	char sex;
    	float score;
    	struct student next;  //错误
    }stu2;
    
    • student结构体内部不能包含student结构体本身
    • 两种做法的区别:
      • 采用结构体指针作为成员变量,由于指针变量的内存空间大小是确定的,编译程序能够事先根据变量类型为变量分配确定的内存空间。所以可行。
      • 采用结构体成员作为成员变量,由于在编译器中无法确认结构变量的实际内存空间大小,无法正确分配变量内存空间,不符合“变量先定义后使用”的基本原则。所以不行。
结构体内存对齐
  • 结构体的对齐规则

    • 第1个成员在结构体变量偏移量为0的地址处存放
    • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
      • 对齐数 = 编译器默认的一个对齐数与该成员大小的较小值;VS编译器默认对齐数为8.
    • 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
    • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的成员的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的成员的最大对齐数)的整数倍。
  • 例:

    • #include 
      struct s1
      {
      	char c1;  //1个字节  对齐数为1
      	int i;    //4个字节  对齐数为4
      	char c2;  //1个字节  对齐数为1
      };
      int main()
      {
      	printf("%dn", sizeof(struct s1));
      	return 0;
      }//编译结果:12
      

  • #include 
    struct s1
    {
    	double d;  //对齐数为8
    	char c;    //对齐数为1
    	int i;     //对齐数为4
    };
    
    struct s2
    {
    	char c1;   //对齐数为1
    	struct s1 s2;  //对齐数为8
    	double d;  //对齐数为8
    };
    
    int main()
    {
    	printf("%dn", sizeof(struct s2));//嵌套了结构体的情况
    	return 0;
    }//编译结果:32
    
  • 图解(结合对齐规则来看)

  • 总体来说:结构体的内存对齐是拿空间来换取时间

  • 设计结构体的时候,既要满足对齐,又要节省空间:让占用空间小的成员尽量集中在一起

  • 修改默认对齐数

    #pragma这个预处理指令可以改变默认对齐数

    #include 
    #pragma pack(8)  //设置默认对齐数是8
    struct s1
    {
    	char c1;
    	int i;
    	char c2;
    };
    #pragma pack()   //取消设置的默认对齐数,还原为默认
    #pragma pack(1)  //设置默认对齐数是1
    struct s2
    {
    	char c1;
    	int i;
    	char c2;
    };
    #pragma pack()   //取消设置的默认对齐数,还原为默认  
    int main()
    {
    	printf("%dn", sizeof(struct s1));
    	printf("%dn", sizeof(struct s2));
    	return 0;
    }//编译结果:12	6
    

    结构在对齐方式不合适时,可以自己更改默认对齐数

共用体(联合体)

共用体是在计算机的内存区域中申请一个特殊的空间,该空间由若干个连续的内存单元构成,这片内存空间可以存储各种不同类型的数据,是各种类型数据的“公共存储区”,可以在这个特殊的空间存放各种不同类型的数据。

在C语言中,不同类型的数据存取方式不同,其所占内存单元的字节数也不同,因此,数据的存取与数据类型必须匹配,否则就会引起数据存取混乱,导致数据读写错误。

共用体类型的定义与变量定义
  • 共用体类型一般在函数外定义,其定义的一般格式:

    union 类型名
    {
        类型名 成员;
           ...
        类型名 成员;
    };
    
    • 共用体类型是几种不同数据类型共用一块存储空间,其大小由值域最大的成员所占空间大小决定
  • 共用体变量定义:

    union data u1, u2;

共用体大小的计算

满足以下2个条件:

  • 共用体的大小至少是最大成员的大小

  • 当最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍(对齐数在结构体内存对齐讲到)

    union data
    {
        int i;     //占用4个字节  对齐数为4
        char a;    //占用1个字节  对齐数为1
        float x;   //占用8个字节  对齐数为8
    };
    sizeof(union data) == 8;   //最大成员的大小是8个字节,是最大对齐数8的整数倍
    
    #include 
    union data
    {
    	char c[5];  //5个字节  对齐数为1 
    	int i;      //4个字节  对齐数为4
    };              //大小为最大对齐数的整数倍:8
    union bs
    {
    	short c[7]; //10个字节  对齐数为2
    	int i;      //4个字节   对齐数为4
     };             //大小为最大对齐数的整数倍;12
    int main()
    {
    	printf("%dt", sizeof(union data));
    	printf("%dt", sizeof(union bs));  
    	return 0;
    }//编译结果:8	16
    
共用体变量的使用

共用体变量的使用需要通过成员变量来实现,其形式如下:共用体变量名.成员名

在共用体变量定义的同时只能用第一个成员的类型值进行初始化,共用体变量初始化的一般形式:
union 共用体类型名 共用体变量 = {第一个成员类型名}

例如:union data u1 = {8};

使用共用体,注意
  • 在对共用体变量进行初始化时,只能给第一个成员变量赋值,但必须用花括号括起来

  • 不能对共用体变量名直接赋值,不能通过引用变量名得到其成员的值。

  • 不可以在定义共用体变量结构类型时对它初始化,在定义共用体类型的同时不能初始化

  • 共用体变量的地址及其各成员的地址都是同一地址,因为各成员地址的分配都是从共用体变量空间的起点开始

    #include 
    union data
    {
        int i;    
        char a;   
        float x;  
    };
    union data u;
    int main()
    {
        printf("%pt", &u);     //共用体变量的地址
        printf("%pt", &(u.i)); //共用体变量成员的地址
        printf("%pt", &(u.a)); //共用体变量成员的地址
        printf("%pt", &(u.x)); //共用体变量成员的地址
        return 0;
    }
    //编译结果:0086A13C	  0086A13C   0086A13C    0086A13C
    
  • 不能使用共用体变量作为函数参数,也不能使用函数返回共用体变量,但可以使用它指向共用体变量的指针(与结构体类似)

  • 共用体变量可以出现在结构体类型的定义中,也可以定义结构体数组

  • 同一时间,只能使用共用体内变量成员的一个值

    #include 
    int main()
    {
    	union temp
    	{
    		char a;
    		int b;
    	}t;
    	t.a = 66;
    	t.b = 266;
    	printf("t.a:%d, t.b:%dn", t.a, t.b);
    	return 0;
    }//编译结果:t.a:10, t.b:266
    

    我们发现,将 66 赋值给t.a,但是输出结果却是 10,这是怎么回事呢?

位域

在实际编程过程中,有些信息在储存时,并不需要占用一个完整的字节,而只需占几个或一个二进制位;

为了节省存储空间,并使处理简便,C语言提供了一种数据结构,称为 “位域”或“位段”。

所谓“位域”,就是把一个字节中的二进制位划分为几个不同的区域,并说明每个区域的位数。每个域都有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

位域定义形式
  • 位域定义和结构定义相似,但有两个不同:

    • 位域的成员必须是char、unsigned char、int、unsigned int 或者 signed int 。
    • 位域的成员名后面有一个冒号和一个数字。

    其形式如下:

    struct 位域结构名
    {
        位域列表
    };
    //其中,位域列表的定义形式如下:
    类型说明符 位域名:位域长度
    
  • 例如:

    #include 
    struct A
    {
        int a : 2;   //a成员占2个bit位
        int b : 5;   //b成员占5个bit位
        int c : 10;  //c成员占10个bit位
        int d : 30;  //d成员占30个bit位
    };//A就是一个位域类型
    int main()
    {
        printf("%dn", sizeof(struct A));
        return 0;
    }//编译结果:8  
    

    说明A这个位域在内存中占了8个字节,那么8是怎么来的呢?

    一个int类型有32个bit位的存储空间,能够存下a、b和c位域,而d位域需要另外开辟一个int类型的空间,所以是8个字节。

  • 位域的内存分配

    • 位域的空间是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的

    • 位域涉及很多不确定因素,位域是不跨平台的,注重可移植的程序应该避免使用位域

    • 在VS编译器下的内存分配

    struct s
    {
    	char a : 3;
    	char b : 4;
    	char c : 5;
    	char d : 4;
    };
    int main()
    {
    	struct s stu = { 0 };
    	stu.a = 10;
    	stu.b = 12;
    	stu.c = 3;
    	stu.d = 4;
    	return 0;
    }
    

  • 对于位域的定义和使用有几点说明:

    • 一个位域必须存储在同一个字节中,不能跨2个字节

    • 由于位域不能跨2个字节,因此位域的长度不能大于1个字节的长度,也就是说不能超过8个二进制位

    • 位域可以无位域名,这时它只能用来做填充或调整位置。无名的位域是不能使用的

    • 如果相邻位域字段类型相同(如上例中char类型),且其位宽之和(如上例中位域a的位宽为3bit)小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;

      如果相邻位域字段类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;而如果相邻的位域字段的类型不同,则各编译器的具体实现有差异。

      • 一个字节为8个bit位
  • 位域的跨平台问题:

    • int位域被当成有符号数还是无符号数是不确定的
    • 位域中最大的数目不能确定。(16位机器最大16,32位机器最大32,如果位域长度位27时(或者其他>16),在16位机器会出现问题)
    • 位域中的成员在内存中从左向右分配,还是从右向左分配标准尚无定义
    • 当一个结构包含两个位域,第二个位域成员比较大,无法容纳于第一个位域剩余的位时,是舍弃剩余的位还是利用,是不确定的
  • 位域变量的说明和结构变量说明的方式相同。可采用先定义后说明,同时定义说明或者直接说明3种方式

    例如:

    struct bs
    {
        int a:8;
        int b:2;
        int c:6;
    }data;
    //说明 data 为bs变量,共占2个字节。其中位域a占8位,位域b占2位,位域c占6位。
    
位域应用

位域的使用和结构成员的使用相同,其一般形式为:位域变量名.位域名

位域允许用各种格式输出

枚举类型

C语言中提供了一种称为“枚举”的类型。在“枚举”类型定义中列举出所以可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。

应该说明的是:枚举类型是一种基本数据类型,而不是一种构造类型。

枚举类型的定义和枚举变量的定义
  • 枚举的定义:

    枚举定义的一般形式:enum 枚举名{ 枚举值表 }

    在枚举值中应罗列出所有可用值。这些值被称为枚举元素,也叫枚举常量

    例:

    enum weekday{ sun, mon, tue, wed, thu, fri, sat};
    
  • 枚举变量的说明:枚举变量可用不同的方式说明,即先定义后说明、同时定义说明或者直接说明

    enum weekday{ sun, mon, tue, wed, thu, fri, sat};
    enum weekday a, b, c;  //第一种说明方式
    
    enum weekday{ sun, mon, tue, wed, thu, fri, sat}a, b, c;  //第二种说明方式
    
    enum { sun, mon, tue, wed, thu, fri, sat}a, b, c; //第三种说明方式
    
枚举类型变量的赋值和使用
  • 枚举类型在使用时有以下规定:

    • 枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值,但是可以在定义时对其赋初值。

    • 枚举元素本身由系统定义了一个表示序号的数值,从0开始顺序定义为0,1,2 ……

      • 在定义的时候也可以赋初值,但其后的值还是依次递增

        enum Color
        {
            RED = 1;
            GREEN;
            BLUE;
        };  //打印枚举值: 1,2,3
        
    • 枚举元素不是字符常量也不是字符串常量,使用时不要加单引号、双引号

    • 只能把枚举值赋给枚举变量,不能把枚举元素的数值直接赋值给枚举变量;若一定要把数值赋给枚举变量,则必须用强制类型转换。

      enum Color
      {
          RED = 1; //1
          GREEN;   //2
          BLUE;    //3
      }clr;
      clr = GREEN;  //正确  clr被赋值为2
      clr = 2;   //错误不能将枚举元素的数值直接赋给枚举变量
      clr = (enum Color)2;  //正确,使用强制类型转换 
      
枚举的优点
  • 增加代码的可读性和可维护性
  • 和#define定义的标识符比较,枚举有类型检查,更加严谨
  • 防止了命名污染(封装)
  • 便于调试
  • 使用方便,一次可以定义多个常量
类型定义符typedef

C语言不仅提供了丰富的数据类型,而且还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”

类型说明符 typedef 即可用来完成此功能。

  • typedef 定义的一般形式:typedef 原类型名 新类型名
    • 其中原类型名中含有定义部分,新类型名一般用大写表示,以便区别
    • 有时也可用宏定义来代替 typedef的功能,但是宏定义是由预处理完成的,而 typedef 则是在编译时完成的,后者更为方便
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1029456.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号