C语言——8-结构体

0 一个问题

数组 解决多个相同类型的变量的定义问题, 解决多个相同类型的对象.

假如说我们想要描述一个人 , 要把人抽象成计算机中的数据 .

人有哪些属性 , 把这些属性转换成数据, 这些数据数据都是描述人的,

​ 不能把他单独分开,

人 : 你要关注人的哪些属性(参数)

​ 学号 --> int num

​ 姓名 --> char name[32];

​ 性别 --> char gender; 'M' /' F '

​ 年龄 --> int age

​ 地址 --> char addr[100]

C语言允许程序员自定义组合的数据类型.

结构体 联合体 枚举

1 结构体

结构体是C程序员自定义的一种组合的数据类型.

1.1 定义语法

  • (1) 先定义结构体,在定义结构体变量
struct 结构体名  //定义结构体类型 新的类型
{
   类型 成员1;
   类型 成员2;
   类型 成员3;
   ...
};

结构体 ,成员名 : 标识符
   
struct 结构体名  ---> 你定义的 新类型的名字(相当于 int 类型)
   
成员的类型 :
	任意合法的类型(基本类型,自定义的类型,指针类型..)都可以

例子 :

(1) 构造一个类型 来描述我们的日期 (年/月/日)
   struct date  //--> struct date 就是我们定义的用来描述"日期"的类型
   {            //这个类型有三个成员 ; year ,month,day
       int year;//年
       int month;//月
       int day;//日
   };

or
   struct date
   {
       int year,month,day;
   };

	struct date day1;//struct date 是一个类型名  day1, day2 是一个变量名
	struct date day2;
(2) 构造一个类型来描述学生
    "学号" "姓名" "生日"
   struct student
   {
       int num;//学号
       char name[32];//姓名
       struct date birthday;//生日
   };
  • (2) 定义结构体类型时,同时定义变量.
struct 结构体名
{
   类型 成员1;
   类型 成员2;
   类型 成员3;
   ...
}变量名列表;

结构体 ,成员名 : 标识符
   
struct 结构体名  ---> 你定义的 新类型的名字(相当于 int 类型)
   
成员的类型 :
	任意合法的类型(基本类型,自定义的类型,指针类型..)都可以
       
变量名列表 : 同时定义了  多个变量

例子 :

struct date  //--> struct date 就是我们定义的用来描述"日期"的类型
{            //这个类型有三个成员 ; year ,month,day
   int year;//年
   int month;//月
   int day;//日
}day1,day2;

int a,b,c;//和int 定义变量的方式是一样的
struct date day3;
  • (3) 直接定义结构体类型变量
struct 
{
   类型 成员1;
   类型 成员2;
   类型 成员3;
   ...
}变量名列表;

如果定义这种结构体类型的变量,后续不能再定义结构体变量了,因为没有结构体名. //一次性
   
struct 
{          
   int year;//年
   int month;//月
   int day;//日
}day1,day2;

struct day3;//error 不能定义 day3 这个变量,因为没有结构体名.

1.2 "实例化" : 构造某某类型的对象

struct student s;//s 是类型  struct student 的一个变量
	s 中会有一个学号  num , int
 s 中会有一个姓名  name , char[32]
 s 中会有一个日期  birthday , struct date
     birthday 中 会有 year,month,day

1.3 结构成员变量的内存布局

结果体变量所占内存大小,是各成员变量所占内存大小之和,

结果体变量所占的内存空间,是一块连续的空间.

各成员变量是按定义的顺序依次分配空间.

image-20230714100901782

image-20230714101049955

struct student s;
sizeof(s) = sizeof(num) + sizeof(name) + sizeof(birthday)

1.4 结构体成员变量如何引用呢?

struct student s;

s 里面有num , name, birthday

(1) 域运算符(分量运算符)

.
 语法 :
		组合类型变量名.成员变量
         "整个表达式的值" 是最后那个"成员变量" 的 "值"

如 :

s.num
s.name
s.birthday.year
s.birthday.month
s.birthday.day

引用组合类型的成员变量 和 引用普通变量是一样的, 也有左值,也有右值

s.num = 1;
int b = s.num;

如果取 成员变量的地址,如何操作呢?

&(s.num) ==> &s.num

练习 :

1.定义一个学生信息结构体, 从键盘上输入一个学生的各种信息,然后输出,

//先定义类型  ,后定义变量

scanf("%d",&s.num);

printf("学号 : %d\n",s.num);

#include <stdio.h>

struct date  //日期这个类型
{
 int year;//年
 int month;//月
 int day;//日
};

struct student //学生这个类型
{
 int num;//学号
 char name[32];//姓名
 struct date birthday;//生日
};



int main()
{
 struct student s;

 //将键盘上输入给学生的各项信息存储到 s 中去
 //学号 姓名  年 月 日
 printf("请输入学生的信息 : 学号 姓名 年 月 日\n");
 scanf("%d%s%d%d%d",&s.num,s.name,&s.birthday.year,&s.birthday.month,&s.birthday.day);

 printf("学号 : %d\n",s.num);
 printf("姓名 : %s\n",s.name);
 printf("生日 : %d/%d/%d\n",s.birthday.year,s.birthday.month,s.birthday.day);


 return 0;
}

(2) (*结构体指针).成员变量

struct student s;//定义了一个结构体变量

struct student *p;
p = &s;

p的右值是 &s
 *p ==> *&s ==> s
 (*p).num ==> s.num

(3) -> 域运算符 分量运算符 (结构体指针)

用来访问组合类型(结构体,共用体)的成员变量的

​ 组合类型的指针->成员变量

表达式的值 就是 最右边的那个 成员变量的值

p->num
p->name
p->birthday.year
p->birthday.month
p->birthday.day
...

练习 :

  1. 用动态创建内存的方式保存学生的各项信息, 从键盘上输入一个学生的各种信息, 然后输出.
int main()
{
    struct student *p = malloc();
    
    scanf();
    
    printf();
    
    return 0;
}

#include <stdio.h>
#include <stdlib.h>

struct date  //日期这个类型
{
    int year;//年
    int month;//月
    int day;//日
};

struct student //学生这个类型
{
    int num;//学号
    char name[32];//姓名
    struct date birthday;//生日
};



int main()
{
    struct student *p = (struct student *)malloc(sizeof(struct student));

    //将键盘上输入给学生的各项信息存储到 s 中去
    //学号 姓名  年 月 日
    printf("请输入学生的信息 : 学号 姓名 年 月 日\n");
    scanf("%d%s%d%d%d",&(p->num),p->name,&p->birthday.year,&p->birthday.month,&p->birthday.day);
    
    printf("学号 : %d\n",p->num);
    printf("姓名 : %s\n",p->name);
    //printf("生日 : %d/%d/%d\n",p->birthday.year,p->birthday.month,p->birthday.day);
    printf("生日 : %d/%d/%d\n",(*p).birthday.year,(*p).birthday.month,(*p).birthday.day);

    free(p);
 

    return 0;
}


1.5 定义并初始化结构体变量的方法

在C语言中, 构造类型(数组,结构体,共用体) 的初始化用 { }

struct date 
{            
     int year;//年
     int month;//月
     int day;//日
};

struct student
{
     int num;//学号
     char name[32];//姓名
     struct date birthday;//生日
};

(1) 按定义的顺序依次初始化各成员变量,个成员变量之间用逗号隔开

如 :

struct student s = {
 	1,//学号
 	{"zhangsan"},//姓名  可以不用{}
 	{
         2023,7,14
     }
};

(2) 不按顺序,用.成员变量名 = 值

struct student s = {
 .name = {"zhangsan"},
 .birthday = {
     .year = 2000
 }
};

(3) 结构体数组的初始化

  • <1> 按数组元素的顺序依次初始化
struct student s[2] = {
   {
       .num = 1,
       .name = {"lisi"},
       .birthday = {
           2023,7,14
       }
   },//s[0]
   {
       
   }//s[1]
};

  • <2> 不按数组元素的顺序, [下标] =
struct student s[5] = {
   [0] = {
       .num = 1,
       .name = {"lisi"},
       .birthday = {
           2023,7,14
       }
   },//s[0]
   [2] = {
       
   }//s[2]
};

2 共用体

2.1 共用体(联合体 union)

共用体也是一种C程序员自定义的组合类型.

union 共用体名
{
类型 成员变量1;
类型 成员变量2;
...
};

共用体在语法形式上 和 使用上 ,和结构体是一样的.

引用共用体的对象的成员也是 :

.
->

共用体和结构体的区别在哪里呢?

  • (1) 共用体的所有的成员变量 共用一块内存

    共用体对象所占内存的大小是其最大的那个成员所占的大小

    共用体对象的所有成员变量共用一块内存,共用体所有成员变量的地址都是一样的

    "共用体的成员变量一般不能同时使用",共用体是在资源比较紧张的时候提出来的.

  • (2) 结构体对象所占内存大小是各成员变量所占内存大小之和.

image-20230714151847386

2.2 大端模式 和 小端模式

从存储器开始讲起 :

所有运算的操作数, 必须在CPU的内部寄存器,才能参与运算.

对于CPU来说,寄存器才是他真正的存储空间.但是CPU寄存器的个数以及

容量非常有限.所以下设计CPU时,必须要有个他的存储器 Memory.

把CPU寄存器中的数据 存储到 存储器memory中,同时把存储器memory中数据

加载到 CPU寄存器中来.

CPU寄存器 <---> 存储器memory之间 的数据交换.

但是 : 寄存器 和 存储器

寄存器是按bit位存储, "高字节 低字节" b31 b30 b29 ...b1 b0

存储器memory ,是按字节来连续编址.

R0  32bits
 b31 b31 ... b1 b0
 (4) (3) (2) (1)
 高字节      低字节
 
 memory,按字节连续编址
 	|--------|--------|--------|--------|...
		0x3000   0x3001   0x3002   0x30003  0x3004 ....

考虑 寄存器 与 存储器 之间的数据交换.

image-20230714162923336

	高字节       低字节
R : (4) (3) (2) (1)


M :
	___ ___ ___ ___
低地址         高地址

小端模式 :  小存小
	存储中的低地址  存储 寄存器中  低字节
(1) (2) (3) (4)
___ ___ ___ ___
低地址       高地址
     
大端模式 :
	存储器中的低地址 存储 寄存器中的 高字节       
(4) (3) (2) (1)     
___ ___ ___ ___
低地址       高地址

练习 :

1.写一个程序 ,测试一下运行这个程序的机器, 是大端模式 ,还是小端模式?

union test
{
 int a;
 char b;
};

union test t;
t.a = 1;//0x 00 00 00 01
	==>
     	01  00  00   00
     内存 ___ ___ ___ ___ 小端
     	00   00 00   01
     内存 ___ ___ ___ ___ 大端
     
     &t.b
     
 判断是大端还是小端模式 ,只需要判断内存中的低地址的内容是 01 还是 00
 if(t.b) //t.b != 0
 {
     //小端
 }
	else
 {
     //大端
 }
     
     
     

3 枚举

0 一个问题

假如在你的C程序中,需要弄一个变量,来表示星期几?
int weekday;// 1 2 3 4 5 6 7 
		
	weekday = 6;
	weekday = 7;
	...
weekday = 250;?

1 枚举

把该类型变量所有可能的值都列出来, 并且每个值,还可以有一个名字("字面意思")

枚举也是C程序员自定义的一种类型.

​ 枚举值 就是一个 整数值 , 默认第一个值是 0 ,后面自动增加.

定义格式如下:

enum 枚举名
{
枚举值的名(标识符)
};

如 :

enum weekday
{
MON,
TUE,
WED,
THU,
FRI,
SAT,
SUN
};

enum weekday w;//w就是一个 类型 enum weekday 的变量

w = MON;

if(w == SUN)
{
happy day;
}

//构造一个类型来表示几种常用的颜色
enum color
{
RED,
GREEN,
BLUE,
YELLOW,
BLACK,
WHITE
};

enum color c = YELLOW;

枚举类型在实现时, 枚举的值 ,是用一个整数值来表示, 如 上面的例子 enum weekday

MON 0
TUE 1
WED 2
...

我们在定义枚举类型时,也可以指定枚举的值,如 :

enum weekday //新的类型名
{
//该类型变量所有可能的值,都在这个{}内
MON = 250,
TUE,
WED,
THU,
FRI = 600,
SAT,
SUN
};

因为枚举类型的实现是通过整数来实现,所有枚举类型的变量都可以 ++ ,-- 等等整数可以参与的运算.

enum weekday c = MON;
c++;//TUE

4 typedef用法

0 一个问题

int a[10];//不仅定义了一个数组a, 同时也声明了一个新的类型 "像a这样的类型"
			//int[10]
定义一个与a类型一样的变量 b;
//int b[10]

typeof(a) b;

有没有办法声明一个 int[10] 这种类型呢?

1 typedef

typedef 用来声明(定义) 一个新的类型名

这个类型已经有的 ,只不过我去一个新的名字而已.

语法 :

typedef 已经有的类型名  新的类型名 ;

新的类型名  就等同于 已经有的类型名

例子 :

typedef int zhengshu;//zhengshu 就是一个新的类型名 , 他等同与  int

int a;
zhengshu a;

typedef unsigned char uint8_t;
typedef char u8;
typedef short s16;
...

struct student
{
 int num;
 char name[32];
 //...
};

typedef struct student stu_t;
stu_t s;

==>
typedef struct student
{
  int num;
  char name[32];
  //...
}stu_t;

struct student
{
  int num;
  char name[32];
  //...
}stu;
	//上面定义一个类型 struct student 同时定义了一个这种类型的  变量stu

typedef struct
{
int a;
int b;
}TEST;
//TEST 也是一个新类型名

typedef struct node
{
//...
}Node,Node2;
//Node ,Node2 都是新类型名 , 代表的是 struct node

typodef struct node
{
//...
}Node ,*pNode;

==> 
typedef struct node Node;
	typedef struct node* pNode;

//Node ,pNode都是新类型名,但是:
//Node  <==> struct node
//pNode <==> struct node*  pNode是结构体指针类型名

2 typedef 一些比较高级的用法

int b;//b是一个变量名
typedef int b;//b是一个类型名

int NUM[10];//NUM是一个数组名
typedef int NUM[10];//NUM是一个类型名 ,typedef int[10] NUM;

NUM a;//a就是一个数组名,里面含有 10个int类型的元素

void (*p)(int , float);//p是一个函数指针变量

typedef void (*p)(int , float);//p一个类型名 , 函数指针类型


5 字节对齐

1 什么是字节对齐

从内存访问的效率出发, CPU底层或编译器一般会要求 ,所有对象的地址按某种方式对齐.

这种对齐,我们称之为"字节对齐"

为了提升内存的访问效率 ?

一般来说,字节地址就是要求对象的地址是 n 的倍数.

2 对齐方式

  • (1) n-字节对齐 : 对象地址和大小必须是 n 的倍数(n一般为2的x次幂)

如 :

4-字节对齐 : 所有对象的地址必须为4的倍数

8-字节对齐 : 所有对象的地址必须为8的倍数

16-字节对齐 :

...

  • (2) 自然对齐

    编译器默认的一种对齐方式.

对象的地址是对象长度的倍数

在32bits x86机器,编译器:
		Microsoft Visual C++
		Borland/Code Gear(c++ builder)
		Digital Mars(DMC)
		GNU(gcc)
	对  A char(1byte) 一字节对齐
		a short(2bytes) 二字节对齐
		an int(4bytes) 四字节对齐
		a long(4bytes) 四字节对齐
		a float(4bytes) 四字节对齐
		a  double(8bytes)  8字节对齐 On windows
						  4字节对齐  On linux(除非 -malign-double 8字节对齐)
		a  long long(8bytes) 4字节对齐
		a  long doulbe(10bytes) ?? 具体的对齐方式和编译器、系统有关
		

		any pointer (4bytes)  4字节对齐


​		
在64bits x86机器下,编译器 
​				Microsoft Visual C++
​				Borland/Code Gear(c++ builder)
​				Digital Mars(DMC)
​				GNU(Gcc)
​			和32bits机子,只有以下不同:
​			A long (8 bytes)  8字节对齐.
​			A double (8 bytes) 8字节对齐.
​			A long long (8 bytes) 8字节对齐.
​			A long double (8 bytes with Visual C++,   8字节对齐在Visual C++
​						16 bytes with GCC,			  16字节对齐在GCC
​			Any pointer (8 bytes) 8字节对齐.

例子 :

sizeof(int) = 4;

int a:
	&a 必须为4的倍数
 if &a 不是4的倍数,则说明 a不是自然对齐
     
每个变量(包括结构中的成员变量)都会有一个默认的对齐方式 : 自然对齐

对结构体的对齐方式?

两个重要的要求

(1) 结构体变量按其最大的自然类型的成员变量的对齐方式对齐;

(2) 结构体的大小必须为其对齐方式的整数倍(一般向上取整).

例子 : 假设机器 32bits

struct test
{
 char a;//a按 1-字节对齐
 int b;//b按 4-字节对齐
 short c;//从按 2-字节对齐
};

struct test  按其最大成员(int b)的对齐方式来对齐  struct test 按4字节对齐.

image-20230717114902714

#include <stdio.h>

struct test
{
 char a;//a按 1-字节对齐
 int b;//b按 4-字节对齐
 short c;//c按 2-字节对齐
};

struct test2
{
 char a;
 short c;
 int b;
};

struct test3
{
 short c;
 int b;
 char a;
};


int main()
{
 struct test2 t2;
 struct test3 t3; 
 printf("sizeof(struct test) = %ld\n",sizeof(struct test));//12
 printf("sizeof(t2) = %ld\n",sizeof(t2));//8
 printf("sizeof(t3) = %ld\n",sizeof(t3));//12
 return 0;
}

3 练习

分析如下程序, 假设机器是 32bits

(1)

struct MixedData
{
 char Data1;
 short Data2;
 char Data4;
 int Data3; 
};
struct MixedData 按4-字节对齐
sizeof(struct MixedData) = ? 12  

(2)

struct FinalPad
{
 float x;
 char n[1];
};
struct FinalPad 按4-字节对齐
sizeof(struct FinalPad) = ? 8
//数组的对齐方式是按 数组元素类型的对齐方式  对齐

(3)

struct FinalPadhort
{
 short x;
 char n[3];
};
struct FinalPadhort 按2-字节对齐
sizeof(struct FinalPadhort) = ? 6
 
struct test
{
 short x;//
 int n[3];
};
struct test 按4-字节对齐
sizeof(struct test) = ? 16

(4)

struct MixedData
{
 char Data1;
 short Data2;
 int Data3; 
 char Data4;
};

struct test
{
 char s;
 struct MixedData m;
};

sizeof(struct test) = ?

image-20230717145628808

#include <stdio.h>

struct MixedData
{
 char Data1;
 short Data2;
 int Data3; 
 char Data4;
};

struct test
{
 char s;
 struct MixedData m;
};



int main()
{
 struct test t;
 printf("sizeof(t) = %ld\n",sizeof(t));//16

 return 0;
}

posted @ 2023-07-22 15:31  风恬月淡时  阅读(100)  评论(0)    收藏  举报