C 结构体、共用体、枚举

1、结构体

结构体是一种构造类型的数据结构,是一种或多种基本类型或构造类型的数据的集合。

1.1、结构体类型定义

1.1.1、法1:先定义结构体类型,再去定义结构体变量。

struct 结构体类型名 {
    
    
	成员列表
};
================================
struct stu {
    
    
	int num;
	char name[20];
	char sex;
};

// 定义三个 struct stu 类型的变量,每个变量都有三个成员,分别是num name sex
struct stu michael, michelle, liMing;

1.1.2、法2:在定义结构体类型的时定义结构体变量,以后还可以定义结构体变量。

struct 结构体类型名 {
    
    
	成员列表;
} 变量1, 变量2;

struct 结构体类型名 变量3, 变量4;
================================
struct stu {
    
    
	int num;
	char name[20];
	char sex;
} lucy, bob, lilei;
struct stu lingHuChong, liBai;

1.1.3、法3:定义结构体类型的时,没有结构体类型名,直接定义结构体变量(之后不能再定义相关类型的数据)。

struct {
    
    
	成员列表;
} 变量1, 变量2;
================================
struct {
    
    
	int num;
	char name[20];
	char sex;
} michael, michelle;

1.1.4、常用的定义结构体的方法:

typedef struct stu {
    
    
	int num;
	char name[20];
	char sex;
} STU;

以后 STU 就相当于 struct stu
STU michael; 等价于 struct stu lucy;

1.2、结构体变量的定义初始化及使用

1、结构体变量的定义和初始化
结构体变量,是个变量,这个变量是若干个相同或不同数据构成的集合
注:
(1):在定义结构体变量之前首先得有结构体类型,然后再定义变量
(2):在定义结构体变量的时候,可以顺便给结构体变量赋初值,被称为结构体的初始化
(3):结构体变量初始化的时候,各个成员顺序初始化

struct stu{
    
    
	int num;
	char name[20];
	char sex;
};
struct stu boy;
struct stu lucy = {
    
     101, "michael", 'f'};

2、结构体变量的使用
定义了结构体变量后,要使用变量
(1).结构体变量成员的引用方法
结构体变量.成员名

struct stu{
    
    
	int num;
	char name[20];
	char sex;
};
struct stu bob;
bob.num = 101;//bob 是个结构体变量,但是bob.num 是个int 类型的变量
bob.name 是个字符数组,是个字符数组的名字,代表字符数组的地址,是个常量
bob.name = "bob";//是不可行,是个常量
strcpy(bob.name, "bob");

#include <stdio.h>

struct stu{
    
    
	int num;
	char name[20];
	int score;
	char *addr;
};

int main(int argc, char *argv[]) {
    
    
	struct stu bob;
	printf("%d\n", sizeof(bob));
	printf("%d\n", sizeof(bob.name));
	printf("%d\n", sizeof(bob.addr));
	return 0;
}


(2).结构体成员多级引用

#include <stdio.h>
struct date {
    
    
	int year;
	int month;
	int day;
};
struct stu{
    
    
	int num;
	char name[20];
	char sex;
	struct date birthday;
};


int main(int argc, char *argv[]) {
    
    
	struct stu lilei={
    
    101,"lilei",'m'};
	lilei.birthday.year=1986;
	lilei.birthday.month=1;
	lilei.birthday.day=8;
	printf("%d %s %c\n",lilei.num,lilei.name,lilei.sex);
	printf("%d %d %d\n",lilei.birthday.year,lilei.birthday.month,lilei.birthday.day);
	return 0;
}

3、相同类型的结构体变量可以相互赋值
注意:必须是相同类型的结构体变量,才能相互赋值。

#include <stdio.h>

struct stu {
    
    
	int num;
	char name[20];
	char sex;
};

int main(int argc, char *argv[]) {
    
    
	struct stu bob = {
    
    101, "bob", 'm'};
	struct stu lilei;
	lilei = bob;
	printf("%d %s %c\n", lilei.num, lilei.name, lilei.sex);
	return 0;
}

1.3、结构体数组

结构体数组是个数组,由若干个相同类型的结构体变量构成的集合
1、结构体数组的定义方法
struct 结构体类型名数组名[元素个数];

struct stu {
    
    
	int num;
	char name[20];
	char sex;
};

struct stu edu[3];//定义了一个struct stu 类型的结构体数组edu,
这个数组有3 个元素分别是edu[0] 、edu[1]、edu[2]

1、结构体数组元素的引用数组名[下标]
2、数组元素的使用
edu[0].num =101; //用101 给edu 数组的第0 个结构体变量的num 赋值
strcpy(edu[1].name, “lucy”);

#include <stdio.h>
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 main() {
    
    
	int i;
	float sum=0;
	for(i=0; i<3; i++) {
    
    
		sum += edu[i].score;
	}
	printf("平均成绩为%f\n", sum/3);
	return 0;
}

1.4、结构体指针

即结构体的地址,结构体变量存放内存中,也有起始地址
咱们定义一个变量来存放这个地址,那这个变量就是结构体指针变量。
结构体指针变量也是个指针,既然是指针在32 位环境下,指针变量的占4 个字节,存放一个地址编号。
1、结构体指针变量的定义方法:

struct 结构体类型名* 结构体指针变量名;

struct stu {
    
    
	int num;
	char name[20];
};

==定义一个 【struct stu *】 类型的指针变量;变量名是p,p占4个字节,用来保存结构体变量的地址编号。==
struct stu * p;
struct stu michael;
p = &michael;

==访问结构体变量的成员的方法==
michael.num = 101; // 可以用【 结构体变量名.成员名 】
(*p).num = 101; // 可以用【 *p.成员名 】( *p 相当于 p 指向的变量 boy )
p->num = 101; // 可以用 【 指针变量名->成员名 】

通过结构体指针来引用指针指向的结构体的成员,前提是
指针必须先指向一个结构体变量。

结构体指针应用场景:
(1):保存结构体变量的地址

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

int main() {
    
    
	STU *p, lucy;
	p=&lucy;
	p->num = 101;
	strcpy(p->name, "baby");
	//p->name="baby";//错误,因为p->name 相当于lucy.name 是个字符数组的名字,是个常量
}

(2):传结构体变量的地址

#include<stdio.h>
#include<string.h>
typedef struct stu {
    
    
	int num;
	char name[20];
	float score;
} STU;

void fun(STU *p) {
    
    
	p->num = 101;
	(*p).score = 87.6;
	strcpy(p->name, "lucy");
}

int main() {
    
    
	STU girl;
	fun(&girl);
	printf("%d %s %f\n", girl.num, girl.name, girl.score);
	return 0;
}

(3):传结构体数组的地址
结构体数组,是由若干个相同类型的结构体变量构成的集合。存放在内存里,
也有起始地址,其实就是第0 个结构体变量的地址。

#include<stdio.h>
#include<string.h>

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

void fun(STU *p) {
    
    
	p[1].num=101;
	(*(p+1)).score=88.6;
}

int main() {
    
    
	STU edu[3];
	fun(edu);
	printf("%d %f\n",edu[1].num,edu[1].score);
	return 0;
}

(1):结构体变量的地址编号和结构体第一个成员的地址编号相同,但指针的类型不同

#include <stdio.h>
struct stu {
    
    
	int num;
	char name[20];
	int score;
};
int main(int argc, char *argv[]) {
    
    
	struct stu bob;
	printf("%p\n",&bob);
	printf("%p\n",&(bob.num));
	return 0;
}

(2):结构体数组的地址就是结构体数组中第0 个元素的地址

#include <stdio.h>

struct stu{
    
    
	int num;
	char name[20];
	int score;
};

int main(int argc, char *argv[]) {
    
    
	struct stu edu[3];
	printf("%p\n",edu);//struct stu *
	printf("%p\n",&(edu[0]));//struct stu *
	printf("%p\n",&(edu[0].num));//int *
	return 0;
}

1.5、结构体内存分配

1、结构体内存分配
结构体变量是所有成员的集合,结构体变量占用内存的大小是所有成员的大小之和。

#include<stdio.h>
struct stu{
    
    
	int num;
	int age;
} lucy;

int main() {
    
    
	printf("%d\n", sizeof(lucy)); //结果为8
	return 0;
}

但是在实际给结构体变量分配内存的时候,是规则的
#include<stdio.h>
struct stu {
    
    
	char sex;
	int age;
} lucy;

int main() {
    
    
	printf("%d\n", sizeof(lucy)); //结果为8???
	return 0;
}

规则1:以多少个字节为单位开辟内存
给结构体变量分配内存的时候,会去结构体变量中找基本类型的成员
哪个基本类型的成员占字节数多,就以它大大小为单位开辟内存,
在gcc 中出现了double 类型的,例外
(1):成员中只有char 型数据,以1 字节为单位开辟内存。
(2):成员中出现了short int 类型数据,没有更大字节数的基本类型数据。
以2 字节为单位开辟内存
(3):出现了int float 没有更大字节的基本类型数据的时候以4 字节为单位开辟内存。
(4):出现了double 类型的数据
情况1:
在vc6.0 和Visual Studio 中里,以8 字节为单位开辟内存。
情况2:
在Linux 环境gcc 里,以4 字节为单位开辟内存。
无论是那种环境,double 型变量,占8 字节。
注意:
如果在结构体中出现了数组,数组可以看成多个变量的集合。
如果出现指针的话,没有占字节数更大的类型的,以4 字节为单位开辟内存。
在内存中存储结构体成员的时候,按定义的结构体成员的顺序存储。

struct stu {
    
    
	char sex;
	int age;
} lucy;

lucy 的大小是 4 的倍数。

规则2:字节对齐
(1):char 1 字节对齐,即存放char 型的变量,内存单元的编号是1 的倍数即可。
(2):short int 2 字节对齐,即存放short int 型的变量,起始内存单元的编号是2 的倍数即可。
(3):int 4 字节对齐,即存放int 型的变量,起始内存单元的编号是4 的倍数即可
(4):long int 在32 位平台下,4 字节对齐,即存放long int 型的变量,起始内存单元的编号是4
的倍数即可
(5):float 4 字节对齐,即存放float 型的变量,起始内存单元的编号是4 的倍数即可
(6):double
a.vc6.0 和Visual Studio 环境下
8 字节对齐,即存放double 型变量的起始地址,必须是8 的倍数,double 变量占8 字节
b.gcc 环境下
4 字节对齐,即存放double 型变量的起始地址,必须是4 的倍数,double 变量占8 字节。
注意3:当结构体成员中出现数组的时候,可以看成多个变量。
注意4:开辟内存的时候,从上向下依次按成员在结构体中的位置顺序开辟空间

20//temp 8 个字节
#include<stdio.h>
struct stu{
    
    
char a;
short int b;
int c;
}temp;
int main()
{
    
    
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
printf("%p\n",&(temp.c));
return 0;
}
结果分析:
a 的地址和b 的地址差2 个字节
b 的地址和c 的地址差2 个字节

21:temp 的大小为12 个字节
#include<stdio.h>
struct stu{
    
    
char a;
int c;
short int b;
}temp;
int main()
{
    
    
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
printf("%p\n",&(temp.c));
return 0;
}
结果分析:
a 和c 的地址差4 个字节
c 和b 的地址差4 个字节
22struct stu{
    
    
char buf[10];
int a;
}temp;
//temp 占16 个字节

例23:
在vc 和Visual Studio 中占16 个字节a 和b 的地址差8 个字节
在gcc 中占12 个字节a 和b 的地址差4 个字节
#include<stdio.h>
struct stu{
char a;
double b;
}temp;
int main()
{
printf(“%d\n”,sizeof(temp));
printf(“%p\n”,&(temp.a));
printf(“%p\n”,&(temp.b));
return 0;
}


为什么要有字节对齐?
用空间来换时间,提高cpu 读取数据的效率
struct stu{
    
    
char a;
int b;
}boy;

在这里插入图片描述

指定对齐原则:
使用#pragma pack改变默认对齐原则
格式:
#pragma pack (value)时的指定对齐值value。
注意:
1.value只能是:1 2 4 8等
2.指定对齐值与数据类型对齐值相比取较小值
说明:咱们指定一个value
(1):以多少个字节为单位开辟内存
结构体成员中,占字节数最大的类型长度和value比较,
取较小值,为单位开辟内存

24#pragma pack(2)
struct stu{
    
    
char a;
int b;
} ;2个字节为单位开辟内存

#include<stdio.h>
#pragma pack(2)
struct stu{
    
    
char a;
int b;
}temp;
int main()
{
    
    
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}
temp的大小为6个字节
a和b的地址差2个字节
25#pragma pack(8)
struct stu{
    
    
char a;
int b;
} ;4个字节为单位开辟内存

#include<stdio.h>
#pragma pack(8)
struct stu{
    
    
char a;
int b;
}temp;
int main()
{
    
    
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}
temp的大小为8个字节
a和b的地址差4个字节

(2):字节对齐
结构体成员中成员的对齐方法,各个默认的对齐字节数和value相比,
取较小值

26#include<stdio.h>
#pragma pack(2)
struct stu{
    
    
char a;
int b;
}temp;
int main()
{
    
    
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}
b成员是2字节对齐,a和b的地址差2个字节


27#include<stdio.h>
#pragma pack(8)
struct stu{
    
    
char a;
int b;
}temp;
int main()
{
    
    
printf("%d\n",sizeof(temp));
printf("%p\n",&(temp.a));
printf("%p\n",&(temp.b));
return 0;
}
a和b都按原先的对齐方式存储
如:如果指定对齐值:
设为1:则shortintfloat等均为1
设为2:则char 仍为1short2int 变为2

1.6、位段

一、位段
在结构体中,以位为单位的成员,咱们称之为位段(位域)。
struct stu{
unsigned int a:2;
unsigned int b:6;
unsigned int c:4;
unsigned int d:4;
unsigned int i;
} data;

在这里插入图片描述

注意:不能对位段成员取地址

28#include<stdio.h>
struct stu{
    
    
unsigned int a:2;
unsigned int b:6;
unsigned int c:4;
unsigned int d:4;
unsigned int i;
} data;
int main()
{
    
    
printf("%d\n",sizeof(data));
printf("%p\n",&data);
printf("%p\n",&(data.i));
return 0;
}

位段注意:
1、对于位段成员的引用如下:
data.a =2
赋值时,不要超出位段定义的范围;
如段成员a定义为2位,最大值为3,即(11)2
所以data.a =5,就会取5的低两位进行赋值101
2、位段成员的类型必须指定为整型或字符型
3、一个位段必须存放在一个存储单元中,不能跨两个单元
第一个单元空间不能容纳下一个位段,则该空间不用,
而从下一个单元起存放该位段
位段的存储单元:
(1):char 型位段存储单元是1 个字节
(2):short int 型的位段存储单元是2 个字节
(3):int 的位段,存储单元是4 字节
(4):long int 的位段,存储单元是4 字节
struct stu {
char a:7;
char b:7;
char c:2;
} temp;//占3 字节,b 不能跨存储单元存储

#include<stdio.h>
struct stu{
    
    
char a:7;
char b:7;
char c:2;
}temp;
int main()
{
    
    
printf("%d\n",sizeof(temp));
return 0;
}

结果为:3 ,证明位段不能跨其存储单元存储
注意:不能取temp.b 的地址,因为b 可能不够1 字节,不能取地址。

4、位段的长度不能大于存储单元的长度
(1):char 型位段不能大于8 位
(2):short int 型位段不能大于16 位
(3):int 的位段,位段不能大于32 位
(4):long int 的位段,位段不能大于32 位

30#include<stdio.h>
struct stu{
    
    
char a:9;
char b:7;
char c:2;
}temp;
int main()
{
    
    
printf("%d\n",sizeof(temp));
return 0;
}

分析:
编译出错,位段a 不能大于其存储单元的大小
5、如一个段要从另一个存储单元开始,可以定义:
unsigned char a:1;
unsigned char b:2;
unsigned char :0;
unsigned char c:3;(另一个单元)
由于用了长度为0 的位段,其作用是使下一个位段从
下一个存储单元开始存放
将a、b 存储在一个存储单元中,c 另存在下一个单元

例:31
#include<stdio.h>
struct stu{
    
    
unsigned char a:1;
unsigned char b:2;
unsigned char :0;
unsigned char c:3;
};
int main()
{
    
    
struct m_type temp;
printf("%d\n",sizeof(temp));
return 0;
}

6、可以定义无意义位段,如:
unsigned a: 1;
unsigned : 2;
unsigned b: 3;

2、共用体

2.1、共用体的定义

共用体和结构体类似,也是一种构造类型的数据结构。
定义共用体类型的方法和结构体非常相似,把 struct 改成 union 就可以了。
在进行某些算法的时候,需要使几种不同类型的变量存到同一段内存单元中,几个变量所使用空间相互重叠,这种几个不同的变量共同占用一段内存的结构,在C语言中,被称作“共用体”类型结构。
共用体所有成员占有同一段地址空间。
共用体的大小是其占内存长度最大的成员的大小。

typedef struct CustomData {
    
     // 结构体
	short int i;
	char ch;
	float f;
} DATA;
DATA temp1;

结构体变量 temp1 最小占 7 个字节(不考虑字节对齐)。
typedef union CustomData {
    
     // 共用体
	short int i;
	char ch;
	float f;
} DATA;
DATA temp2;

共用体变量 temp2 占 4 个字节,即i、ch、f 共用 4 个字节。
#include<stdio.h>
typedef union MyUnionType {
    
    
	short int i;
	char ch;
	float f;
} DATA;

int main() {
    
    
	DATA temp2;
	printf("%d\n",sizeof(temp2));
	printf("%p\n",&temp2);
	printf("%p\n",&(temp2.i));
	printf("%p\n",&(temp2.ch));
	printf("%p\n",&(temp2.f));
	return 0;
}

结果:temp2 的大小为4 个字节,下面几个地址都是相同的,证明了共用体的各个成员占用同一块内存。

2.2、共用体的特点

1、同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用。
2、共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖。
3、共用体变量的地址和它的各成员的地址都是同一地址。
4、共用体变量的初始化 union MyUnionType v1 = { 123 }; 初始化共用体只能为第一个成员赋值,不能给所有成员都赋初值。

#include<stdio.h>
typedef union data {
    
    
	unsigned char a;
	unsigned int b;
} DATA;

int main() {
    
    
	DATA temp;
	temp.b = 0xffffffff;
	printf("temp.b = %x\n", temp.b);
	temp.a = 0x0d;
	printf("temp.a = %x\n", temp.a);
	printf("temp.b = %x\n", temp.b);
	return 0;
}

结果:
temp.b = ffffffff
temp.a = d
temp.b = ffffff0d

3、枚举

将变量的值逐个列举出来,变量的值只限于列举出来的值的范围内。
枚举类型也是个构造类型,类型定义类似结构体类型的定义。

3.1、枚举类型的定义方法

enum 枚举类型名 {
    
    
	枚举值列表;
};

在枚举值列表中需列出所有枚举元素。
枚举元素是常量,默认是从0开始编号的。枚举变量仅能取枚举值列表所列元素。

3.2、枚举变量的定义方法

星期日:

enum 枚举类型名 枚举变量名;

enum week {
    
     // 枚举类型
	Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
};

enum week workday, weekday;//枚举变量
weekend 与 weekday 只能取 Sunday...Saturday 中的值。

  1. 枚举值是常量,不能在程序中用赋值语句再对它赋值。
    例如:sun=5; mon=2; sun=mon; 都是错误的.

  2. 枚举元素本身由系统定义了一个表示序号的数值。
    默认是从0开始顺序定义为0,1,2…
    如在week中,mon值为0,tue值为1, …,sun值为6

  3. 可以改变枚举值的默认值

enum week {
    
     // 枚举类型
	mon=3, tue, wed, thu, fri=4, sat, sun
};

说明:mon = 3 tue = 4,以此类推
说明:fri=4 以此类推

注意:
在定义枚举类型的时候枚举元素可以用等号给它赋值,用来代表元素从几开始编号。在程序中,不能再次对枚举元素赋值,因为枚举元素是常量。

猜你喜欢

转载自blog.csdn.net/Michael_lcf/article/details/129072707