C语言---08自定义数据类---01结构体类型struct

  • C语言中的构造数据类型有:数组类型(自定义大小)、结构体类型、共用体类型、枚举类型

(一)结构体类型定义

  • 在实际应用中,当一个结构体类型有多个文件需要使用时,可以将类型定义放在一个.h文件,需要使用的文件包含相应的头文件即可

struct 结构体数据类型名
{
	<成员变量>; // 成员变量用分号隔开
};

1、结构体类型名

  • 指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元,所以不能在定义结构体类型时给成员赋值

2、结构体变量名

  • 实际分配空间,为了能在程序中使用结构类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据

3、结构体成员变量

  • 结构体中的成员拥有独立的空间(重要)

 

(二)结构体变量的定义及初始化

1、结构体变量定义方法

  • 可以在声明的时候直接定义结构体变量,也可以先声明后定义结构体变量

  • 成员也可以是一个结构体变量

  • 结构体变量是局部变量,其成员内容不确定,需要在定义的时候进行初始化

2、初始化

  • 初始化的顺序必须和结构体成员的顺序一致

 struct people 
 {
     int id; // 编号
     char name[10]; // 姓名
     char gender[4]; // 性别
     int age; // 年龄
 }P2, P3 = { 3, "小明", "男", 15 }; // 结构体类型声名时定义变量
 
struct people P1; // 先声明后定义,提倡这种方法
  • 注意:定义一次性结构体。因为没有结构体类型名,所以不能定义别的结构体变量

3、清空结构体变量

  • 结构体变量有多大清多大

  • 在定义结构体变量时将其清空为0,然后进行结构体变量的数据输入

memset(&结构体变量名,0,sizeof(结构体变量名))

 

(三)结构体变量成员的引用

1、结构体成员引用符:' . '

2、结构体成员引用的基本格式

  • 结构体变量名.成员名

  • 除了初始化以外,不能直接改变字符数组的值。因为字符数组是常量,只能通过拷贝的方式赋值

 void test1()
 {
     struct people P1; // 结构体类型声名后定义变量,定义了一个struct people结构体类型的变量P1
     P1.id = 1;
     strcpy(P1.name, "张三"); // strcpy 字符串拷贝函数:将字符串放到数组中去
     strcpy(P1.gender, "男");
     P1.age = 18;
     printf("%d\t%s\t%s\t%d\n", P1.id, P1.name, P1.gender, P1.age)
     struct people P4 = {4,"小花","女"}; // 结构体类型声名后定义变量P4,并初始化赋值
     struct people P5;
     P5 = P2; // 一个结构体变量可以用于给另一个结构体变量赋值
 }

3、结构体变量成员获取键盘输入

struct stu
{
int num; // 4B
char name[32]; // 32B
int age; // 4B
};

struct stu lucy;
memset(&lucy, 0,sizeof(lucy)); // 清空

printf("请输入num name age:");
// &lucy.num 取的是num成员地址
scanf("%d %s %d",&lucy.num, lucy.name , &lucy.age);

printf("num=%d, name=%s, age=%d\n",lucy.num,lucy.name,lucy.age);

4、结构变量之间的相互赋值

  • 相同类型的结构体变量可以直接赋值(推荐)

struct stu lucy={100,"小法", 18};
struct stu bob;

// 需求 将lucy的值 赋值 bob
// 方式一:逐个成员赋值
bob.name = lucy.name; // 字符数组不能直接赋值

// 方法二:相同类型的结构体变量可以直接赋值(推荐),直接把空间内容复制过来
bob = lucy;

// 方法三:方法二的底层实现,根据lucy的大小创建bob
memcpy(&bob,&lucy,sizeof(struct stu));
printf("num=%d, name=%s, age=%d\n",bob.num,bob.name,bob.age);

 

(四)给结构体类型取别名(建议给结构体取别名来进行使用)

  • 在给结构体类型取别名后,就不能在结构体类型声名时定义变量

typedef struct student
{
	 int  num;
	 char name[20];
	 char sex;
}STU; // STU是别名,不是结构体变量

STU Lucy,Bob; // 使用STU去定义相应的对象类型与变量分开定义
			    
  • 对于上述的定义一次性结构体。因为已经给其取了别名,可以用来定义变量

typedef struct 
{
    int num;
    char name[32];
    int age;
}STU; // 此时的STU不是结构体变量 而是结构体类型

 

(五)结构体数组

  • 一个结构体变量中可以存放一组数据:如一个学生的学号、姓名、成绩等数据

  • 如果有10个学生的数据,定义10个结构体变量很不方便,这时候我们可以使用结构体数组

  • 结构体数组与以前介绍过的数值型数组不同之处:每个数组元素都是一个结构体类型的数据它们都分别包括各个成员

1、定义

  • 数组里面每个元素是整个结构体的信息

 struct student S[3]; // 结构体数组:用于保存3个人的相关信息

2、结构体数组元素的使用

  • 结构体数组名[数组下标].成员变量名

 S[0].id=1; // 这里引用了struct student类型的结构体数组S的第1个元素S[0]的成员id,使其值等于1
 printf(“%s\n”,S[2].name);

3、练习:定义一个结构体数组,求学生平均成绩

typedef struct student
{
    int   num;
    char  name[20];
    float score;
}STU;

STU edu[3]={ 
			  {101,”Lucy”,78},
			  {102,”Bob”,59.5}, 
			  {103,”Tom”,85}, 
		   };

int n = sizeof(edu)/sizeof(edu[0]);
int i = 0;
int sum = 0;

for(i=0;i<3;i++)
{
	sum += deu[i].score;
}
printf("平均成绩为%d\n",sum/n);

 

(六)结构体的嵌套

  • 一种结构体类型中可以有其他结构体类型的成员

struct A
{
	int id;
	char name[10];
};
struct B // 在B结构体里面存放了A,调用相关成员
{
	struct A a1; // 其他结构体类型的成员
	float score;
};
int main()
{
	struct B b1 = {{2021, "小明"}, 100}; // 定义结构变量b1
	b1.score = 99.5;
	b1.a1.id = 1; // 结构体嵌套成员的引用
	strcpy(b1.a1.name, "小强");
	printf("%d\t%s\t%f\n", b1.a1.id, b1.a1.name, b1.score);
}

 

(七)结构体指针

  • 指向结构体变量首地址的指针通过结构体指针即可访问该结构体变量

  • 结构体指针定义:struct 结构体数据类型名 *结构体指针变量;

  • 定义结构体指针时需要给定指向的对象或指向空(NULL),避免出现野指针

  • 结构体指针主要用于结构体变量传参以及顺序表、链表中

1、结构体指针的使用

 int main()
 {
     typedef struct Student
     {
         int num; // 学号
         string name; // 姓名
         char sex; // 性别
         float score; // 成绩
     }STU,*STU_P;

     STU stu = {1998,"wangchao",'m',100};
     STU *p = &stu; // *p = stu;
     STU_P p = &stu';

     cout << p->name << " " << p->num++ << " " << p->score << " " << p->sex++ << endl; // wangchao 1998 100 m 
     cout << p->num << p->sex; // 1999 n m的ASCII值+1得到n
     cout << ++p->num << ++p->sex; // 2000 o
 }
  • 注意
    • STU是结构体类型 

    • STU_P是结构体指针类型

    • 指针p占4字节,在32位平台指针占4字节

    • 通过结构体指针访问成员变量使用 ->,结构体变量访问用 .

2、案例: 从堆区给结构体申请一个空间

STU *p = NULL;
// 从堆区申请结构体空间
p = (STU *)calloc(1,sizeof(STU));

if(NULL == p)
{
    perror("calloc");
    return;
}

 // 获取键盘输入
 printf("请输入一个学生的信息num name age\n");
 scanf("%d %s %d", &p‐>num, p‐>name, &p‐>age);

 // 遍历
 printf("num = %d, name=%s, age=%d\n",p‐>num, p‐>name, p‐>age);

 // 释放空间,避免重复释放
 if(p != NULL)
 {
     free(p);
     p=NULL;
 }

 3、结构体指针作为函数参数

void mySetSTUData(STU *p) // p=&lucy
{
	printf("请输入一个学生的信息num name age\n");
	scanf("%d %s %d", &p‐>num, p‐>name, &p‐>age);
}

void myPrintSTUData(const STU *p) // p =&lucy *p只读,p的指向可以改变
{
	printf("sizeof(p)=%d\n", sizeof(p));
	printf("num = %d, name=%s, age=%d\n",p‐>num, p‐>name, p‐>age);
}

void test03()
{
	STU lucy;
	memset(&lucy,0,sizeof(lucy));

	mySetSTUData(&lucy); // 定义一个函数 给lucy的成员获取键盘输入
	myPrintSTUData(&lucy); // 定义一个函数 打印lucy的成员信息
}

4、案例:从堆区申请申请一个结构体数组,分函数实现 

STU * get_array_addr(int n)
{
    return (STU *)calloc(n,sizeof(STU));
}

// arr代表的是空间首元素地址
void my_input_stu_array(STU *arr, int n)
{
    int i=0;

    for(i=0;i<n;i++)
    {
        printf("请输入第%d个学生的信息\n",i+1);
        // scanf("%d %s %d", &arr[i].num, arr[i].name, &arr[i].age);
        scanf("%d %s %d", &(arr+i)‐>num , (arr+i)‐>name, &(arr+i)‐>age);
    }
}

void my_print_stu_array(const STU *arr, int n)
{
    int i=0;
    for(i=0;i<n;i++)
    {
        printf("num=%d, name=%s, age=%d\n", (arr+i)‐>num, (arr+i)‐>name,(arr+i)‐>age);
    }
    return;
}

void test04()
{
    int n = 0;
    STU *arr=NULL;

    printf("请输入学生的个数:");
    scanf("%d", &n);

    // 根据学生的个数从堆区申请空间
    arr = get_array_addr(n);
    
    if(arr == NULL)
    {
        perror("get_array_addr");
        return;
    }

    my_input_stu_array(arr, n); // 从键盘给结构体数组arr输入数据
    my_print_stu_array(arr, n); // 遍历结构体数组

    // 释放空间
    if(arr != NULL)
    {
        free(arr);
        arr=NULL;
    }
}

 

(八)结构体内存空间分配

1、什么是内存对齐?

  • 实际上系统在分配储存单元时,以字为单位,一个字包括四个字节(32位CPU,一次性读取4字节)

  • 一般情况下结构体类型所占内存大小为所有数据成员的大小之和,但是结构体类型所占内存大小存在内存对齐(内存补齐)

  • 内存对齐:如果一个结构体类型所占内存大小不够存储整数个最大成员,则会补齐结构体内存

2、默认对齐原则

  • 确定分配单位:每一行应该分配的字节数。由结构体最大的基本类型长度确定

  • 确定成员的起始位置的偏移量:成员自身类型的整数(0~n)倍

  • 收尾工作:结构体的总大小为分配单位的整数

  • 案例1

占用8字节
  •  案例2

占用12字节

3、结构体嵌套结构体的内存大小

  • 确定分配单位:每一行应该分配的字节数。由所有结构体最大的基本类型长度决定

  • 确定成员的偏移量:自身类型的整数(0~n)倍

    • 结构体成员偏移量:被嵌套的结构体中最大的基本类型整数倍

    • 结构体成员中的成员偏移量是相对于被嵌套的结构体的

  • 收尾工作:结构体的总大小为分配单位的整数倍

    • 结构体成员的总大小为被嵌套的结构体里最大基本类型的整数倍

  • 案例1 

typedef struct
{
    short d;
    char e;
}DATA2;

typedef struct
{
    short a;
    int b;
    DATA2 c;
    char f;
}DATA;

void test08()
{
    DATA data;
    printf("%d\n",sizeof(DATA)); // 16

    printf("a:%u\n", &data.a); // a:1703628
    printf("b:%u\n", &data.b); // b:1703632
    printf("c中d:%u\n",&data.c.d); // c中d:1703636
    printf("c中e:%u\n",&data.c.e); // c中e:1703638
    printf("f:%u\n",&data.f); // f:1703640
}
结构体占据16字节,被嵌套结构体占据4字节
  •  案例2

typedef struct
{
    short d;
    char e;
}B;
typedef struct
{
    int a;
    short b;
    B c;
    short f;
}A;

void test09()
{
    DATA data;
    printf("%d\n",sizeof(DATA)); // 12

    printf("a:%u\n", &data.a); // a:1703632
    printf("b:%u\n", &data.b); // b:1703636
    printf("c中d:%u\n",&data.c.d); // c中d:1703638
    printf("c中e:%u\n",&data.c.e); // c中e:1703640
    printf("f:%u\n",&data.f); // f:1703642
}
结构体占据12字节,被嵌套结构体占据4字节
  • 案例3

typedef struct
{
    char a;
    int b;
    short c;
}DATA;

void test10()
{
    DATA data={'a',100, 20};
    char *p = &data;
    
    printf("c = %hd\n", data.c);
    // 需求:借助p访问20
    printf("c = %hd\n", *(short *)(p+8)); 
}
  • 指针是char *类型的,只能读取一个字节宽度。强转为short *类型,这样就可以读取20了 

4、强制类型对齐

  • 使用#pragma pack改变默认对其原则

  • 格式#pragma pack (value)时的指定对齐值value

  • 注意

  • value只能是1、2、4、8等

  • 指定对齐值与数据类型对齐值相比,取较小值

  • 如果指定对齐值:

    • 设为1:则short、int、float等均为1

    • 设为2:则char仍为1,short为2,int 变为 

  • 步骤

  • 确定分单位:每一行应该分配的字节数min(value,默认分配单位)

  • 成员偏移量 = 成员自身类型的整数(0~n)倍

  • 收尾工作 = 分配单位的整数(0~n)倍

  • 案例1

#include<stdio.h>
#pragma pack(2) // 指定对齐规则

typedef struct
{
    char a;
    int b;
    short c;
}DATA1;

void test01()
{
    printf("%d\n", sizeof(DATA1)); // 8
    return;
}

 5、结构体数组所占内存大小

  • 结构体的内存大小*数组元素个数

 struct people { // 保存人的相关信息的结构体
     int id; // 编号 4
     char name[10]; // 姓名 10
     char gender[4]; // 性别 4
     int age; // 年龄 4
 };
 struct test {
     char ch; // 1
     int i; // 4
     float f; // 4
     double d; // 8
 };
 int main()
 {
     struct people P[10]; // 结构体数组:用于保存10个人的相关信息
     P[0].id = 0; // 结构体数组元素成员的引用
     P[2].name;
     P[9].age = 30;
     printf("%d\n", sizeof(struct people)); // 24   22/8除不尽 向上取24
     printf("%d\n", sizeof(struct test)); // 24     17/8除不尽 向上取24  
     printf("%d\n", sizeof(P)); // 240 
     return 0;
 }

(九)位段、位域

1、位段

  • 信息在计算机的存取长度一般以字节为单位有时存储一个信息不必用一个或多个字节

  • 例如:"真"或"假":用0或1表示,只需一位即可

  • 在计算机用于过程控制、参数检测或数据通信领域控制信息往往只占一个字节中的一个或几个二进制位

  • 怎样向一个字节的一个或几个二进制位赋值和改变它的值?

    • 位运算符:<< >> & | ~ ^

    • 结构体中定义位段,利用位段可以减少存储空间并简化位操作(将其看作一个变量进行操作)

2、位段的使用

  • C语言允许在一个结构体中以位为单位指定其成员所占内存长度,以位为单位的成员称为“位段”或称“位域

struct packed_data{ 
    unsigned int a:2; // a只占一个字节中的两位二进制位
    unsigned int b:6;
    unsigned int c:4;
    unsigned int d:4;

    unsigned int i; // 不是位段
} data; 
// 其中a,b,c,d分别占2位,6位,4位,4位,i为整型,占4个字节

  •  相邻位域可以压缩(压缩的位数不能超过成员自身大小)

typedef struct
{
    unsigned char a:2;
    unsigned char b:2;
    unsigned char c:5;

}DATA2;

void test02()
{
    DATA2 data;

    printf("%d\n", sizeof(DATA2)); // 2

    // 位段 不能取地址
    // printf("%p\n", &data.a);

    // 位段的赋值不要超过位段的大小 a:2
    data.a = 6; // 0110
    printf("%u\n", data.a); // 2
}

 3、注意事项

  • 位段不能取地址

  •  对于位段成员的引用如下:data.a = 2。赋值时,不要超出位段定义的范围;如段成员a定义为2位,最大值为3,即(11)2,所以data a = 6,就会取6的低两位进行赋值 

  • 位段成员的类型一般只考虑unsigned int类型也可以考虑unsigned char

  • 一个位段必须存放在一个存储单元中,不能跨两个单元。第一个单元空间不能容纳下一个位段,则该空间不用,从下一个单元起存放该位段

  • 位段的长度不能大于存储单元的长度

4、无意义位段的定义

typedef struct
{
    unsigned char a:2; // 00
    unsigned char :4; // 无意义的位段(占有4位)
    unsigned char b:2; // 11
}DATA3;

void test03()
{
    DATA3 data;
    memset(&data, 0, 1);
    
    data.a = 0; // 00
    data.b = 3; // 11
    
    printf("%d\n",sizeof(DATA3));
    
    printf("%#x\n", *(unsigned char *)&data); // 1100 0000  0xc0
}
  • a是低位,b是高位 

  • 应用1

  • 如一个段要从另一个存储单元开始,可以定义

     unsigned a:1;
	 unsigned b:2;
	 unsigned  :0;
	 unsigned c:3; // 另一个单元
// ​​​​​​​由于用了长度为0的位段,其作用是使下一个位段从下一个存储单元开始存放
// 将a、b存储在一个存储单元中,c另存在下一个单元

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盾山狂热粉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值