C语言自定义数据类型——结构体、共用体、枚举
目录
- 一、 结构体的基本用法
- 1.1 定义
- 1.2 定义格式
- 1.3 结构体变量
- 1.3.1 概念
- 1.3.2 格式
- 1.3.3 定义结构体变量
- (1) 先定义结构体,再定义结构体变量。
- (2) 定义结构体的同时,定义结构体变量。
- 1.3.4 结构体变量赋值
- (1) 定义结构体变量时直接用花括号赋值
- (2) 定义结构体变量时直接用点等法赋值:
- (3) 定义结构体变量时未初始化,需要挨个单独赋值:
- 1.3.5 访问结构体成员变量
- 1.3.6 重定义 typedef
- (1) 定义结构体的同时重定义
- (2) 先定义结构体,再进行重定义
- 二、结构体数组
- 2.1 概念
- 2.2 定义格式
- (1)定义结构体同时定义结构体数组
- (2)先定义结构体,再定义结构体数组
- 2.3 初始化和赋值
- (1)定义结构体数组的同时赋值
- (2)先定义结构体数组,再对数组内每个元素分别赋值
- 2.4 结构体数组输入输出(通过循环)
- 2.5 结构体大小
- 三、结构体大小以及对齐原则
- 3.1 结构体大小用sizeof计算
- 3.2 字节对齐原则
- 3.3 节省空间原则
- 四、结构体指针
- 4.1 概念
- 4.2 定义格式
- 4.3 结构体指针成员变量赋值
- 五、结构体内包含结构体
- 结构体总结:
- 六、共用体
- 6.1格式
- 6.2 定义共用体union变量
- 6.3 特性
- 6.4 使用共用体测试大小端:🎯
- 七、枚举
- 7.1 定义
- 7.2 格式
- 八、数据类型总结
- 八、数据类型总结
一、 结构体的基本用法
1.1 定义
结构体是由一批数据组合而成的结构型数据。组成结构型数据的每个数据称为结构型数据的“成员” ,其描述了一块内存区间的大小及解释意义。
1.2 定义格式
struct 结构体名
{
数据类型 成员变量1;
数据类型 成员变量2;
数据类型 成员变量3;
...
};
举例:构造一个新的数据类型叫student,用来描述学生。
struct student
{
char name[32];
int age;
float score;
};
1.3 结构体变量
1.3.1 概念
通过结构体数据类型定义的变量
1.3.2 格式
struct 结构体名 变量名;
1.3.3 定义结构体变量
(1) 先定义结构体,再定义结构体变量。
struct 结构体名
{
成员变量;
};
struct 结构体名 变量名;
例如:
struct student
{
char name[32];
int age;
float score;
};
struct student s1;
(2) 定义结构体的同时,定义结构体变量。
struct 结构体名
{
成员变量;
} 变量名;
例如:
struct person
{
char name[32];
int age;
} per1;
也可以缺省结构体名定义结构体变量:
struct
{
成员变量;
} 变量名;
1.3.4 结构体变量赋值
先定义一个结构体:
struct student
{
int id;
char name[32];
int age;
};
(1) 定义结构体变量时直接用花括号赋值
struct student stu1 ={1,"xiaofang", 18};
(2) 定义结构体变量时直接用点等法赋值:
struct student stu2 ={
.id = 2,
.name = "xiaoming",
.age = 42
};
(3) 定义结构体变量时未初始化,需要挨个单独赋值:
struct student stu3;
stu3.id = 3;
stu3.age = 69;
strcpy(stu3.name,"laowang");
1.3.5 访问结构体成员变量
通过.访问: 结构体变量名.结构体成员变量名。
struct student stu4;
scanf("%d %s %d",&stu4.id,stu4.name,&stu4.age);
printf("%d %s %d\n",stu4.id,stu4.name,stu4.age);
练习:创建一个名为student的结构体,包含姓名,学号,班级, 从终端输入学生的信息并打印。
#include <stdio.h>
#include <string.h>
struct student
{
char name[32];
int id;
int class;
};
int main()
{
struct student stu1;
scanf("%s %d %d",stu1.name,&stu1.id,&stu1.class);
printf("%s %d %d\n",stu1.name,stu1.id,stu1.class);
}
1.3.6 重定义 typedef
重定义的含义就是给语句另起一个名字
例如:
typedef int size_t;
此时int a; 也可以写成 size_t a;
typedef int* int_pointer;
int a;
此时int* p=&a; 可以写成 int_pointer p=&a;
(1) 定义结构体的同时重定义
#include <stdio.h>
#include <string.h>
typedef struct flower
{
char type[32];
int size;
char color[32];
} flo;
int main()
{
flo f1 = {"rose",10,"red"};
printf("%s %d %s\n",f1.type,f1.size,f1.color);
}
(2) 先定义结构体,再进行重定义
struct animal
{
char type[32];
char class[32];
int wight;
};
typedef struct animal ANIMAL;
int main()
{
ANIMAL an1 = {"dog","buru",200};
printf("%s %s %d\n",an1.type,an1.class,an1.wight);
}
练习:创建一个描述手机的结构体叫phone, 包含品牌,型号,颜色,价格。从终端输入你自己手机的信息并打印。
typedef struct phone
{
char brand[32];
char typde[32];
char color[32];
int price;
} pho;
int main()
{
pho ph1;
scanf("%s %s %s %d", ph1.brand, ph1.typde, ph1.color, &ph1.price);
printf("%s %s %s %d\n", ph1.brand, ph1.typde, ph1.color, ph1.price);
return 0;
}
二、结构体数组
2.1 概念
本质是数组,结构体类型相同的变量组成的数组
2.2 定义格式
(1)定义结构体同时定义结构体数组
struct student
{
int id;
int age;
float score;
} stu[5];
(2)先定义结构体,再定义结构体数组
struct student
{
int id;
int age;
float score;
};
struct student stu[5];
2.3 初始化和赋值
(1)定义结构体数组的同时赋值
方法一:
#include <stdio.h>
#include <string.h>
struct hero
{
char name[32];
char postion[32];
char skill1[32];
char skill2[32];
char skill3[32];
char skill4[32];
int price;
};
int main()
{
struct hero h[2] =
{
{"ali","fashi","1","2","3","4",4300},
{"shitou","tanke","1","2","3","4",3150}
};
printf("%s %s %s %s %s %s %d\n", h[1].name, h[1].postion, h[1].skill1, h[1].skill2, h[1].skill3, h[1].skill4, h[1].price);
return 0;
}
方法二:“点等法”
#include <stdio.h>
struct student
{
int num;
float score;
char name[32];
};
int main(int argc, const char *argv[])
{
#if 0
struct student stu[3] = {1,99.9,"zhangsan",2,88.8,"lisi",3,98.9,"wangwu"};
#endif
#if 0
struct student stu[3]=
{
[0] = {
.num = 1,
.score = 99.9,
.name = "zhangsan",
},
[2] = {
.num = 3,
.score = 77.7,
.name = "lisi",
}
};
int i;
for(i = 0; i < sizeof(stu)/sizeof(struct student);i++)
{
printf("%d,%f,%s\n",stu[i].num,stu[i].score,stu[i].name);
}
#endif
struct student stu =
{
.num = 1,
.score = 99.9,
.name = "wangwu",
};
#if 0
stu.num = 1;
stu.score = 99.9;
#endif
printf("%d,%f,%s\n",stu.num,stu.score,stu.name);
return 0;
}
(2)先定义结构体数组,再对数组内每个元素分别赋值
#include <stdio.h>
#include <string.h>
struct hero
{
char name[32];
char postion[32];
char skill1[32];
char skill2[32];
char skill3[32];
char skill4[32];
int price;
};
int main()
{
struct hero he[2];
strcpy(he[0].name,"namei");
strcpy(he[0].postion,"fashi");
he[0].price = 6300;
printf("%s %s %d\n", he[0].name, he[0].postion,he[0].price);
return 0;
}
这里要注意📢,结构体成员变量为字符串类型,要为其单个赋值时,要用strcpy函数。
2.4 结构体数组输入输出(通过循环)
#include <stdio.h>
#include <string.h>
struct hero
{
char name[32];
char postion[32];
int life;
int magic;
float speed;
int price;
};
int main()
{
struct hero h[5] =
{
{"ali", "magicer", 596, 481, 53.04, 6300},
{"gailun", "tank", 700, 0, 55.04, 450},
{"jee", "ad", 596, 200, 55.04, 6300},
{"cat", "helfer", 400, 400, 45, 6300},
{"ez", "adc", 550, 450, 53.04, 6300},
};
for (int i = 0; i < 5; i++)
{
printf("%s %s %d %d %f %d\n",h[i].name,h[i].postion,h[i].life,h[i].magic,h[i].speed
,h[i].price);
}
return 0;
}
2.5 结构体大小
(1)= 结构体类型大小 × 元素个数
(2)用sizeof(struct 结构体名) 或者sizeof(结构体变量名)
例如接着上面的练习:
printf("struct hero:%d %d\n",sizeof(struct hero),sizeof(h));//80 400
三、结构体大小以及对齐原则
3.1 结构体大小用sizeof计算
#include <stdio.h>
#include <string.h>
struct st
{
char a;
char b;
int c;
};
struct s
{
char a;
int b;
char c;
};
int main()
{
printf("%d\n",sizeof(struct st)); //8
printf("%d\n",sizeof(struct s)); //12
return 0;
}
3.2 字节对齐原则
(1)第一个成员在相对于结构体变量起始位置偏移量为0的地址处。通俗点来说,就是第一个成员变量的地址与结构体起始位置的地址是相同的。如下图所示:
(2)用结构体成员里面最大的数据类型的大小和字节进行比较,然后按照字节数小的为单位开辟空间。例如上图例子,每个成员都要给他以4字节为单位开辟空间。
3.3 节省空间原则
为了减少空间浪费,把占用空间小的成员集中到一起
四、结构体指针
4.1 概念
指向结构体变量的指针
4.2 定义格式
*struct 结构体名 结构体指针名;
例如:
struct student
{
int id;
char name[32];
}s1,s2;
struct work
{
int id;
int age;
}w1,w2;
struct student *sp = &s1;
//struct student *p=&w1; //错误,结构体类型不同
4.3 结构体指针成员变量赋值
格式:指针变量名->成员变量名 或者:*(指针变量名).成员变量名
#include <stdio.h>
#include <string.h>
struct student
{
int id;
char name[32];
}s1,s2;
int main()
{
struct student *sp = &s1;
strcpy(sp->name,"xiaoming");
sp->id=123;
printf("%s %d\n",s1.name,s1.id);
printf("%s %d\n",sp->name,sp->id);
return 0;
}
注:结构体指针大小:4字节,因为本质还是指针。
五、结构体内包含结构体
如果结构体内成员本身属于另一种结构体,得用若干个成员运算符一级级找到最低级的成员变量。
#include <stdio.h>
#include <string.h>
struct work
{
int ip;
};
struct student
{
int id;
int age;
struct work w1;
} stu1, stu2;
int main()
{
stu1.w1.ip = 2;
stu1.id = 1;
stu1.age = 20;
printf("%d %d %d\n", stu1.w1.ip, stu1.id, stu1.age);
return 0;
}
结构体总结:
- 不能把结构体类型变量作为整体引用,只能对结构体内变量中的各个成员分别引用。
- 如果成员本身属于另一种结构体类型,要用若干个成员运算符.一级级找到最低级成员变量。
- 可以把成员变量当成普通变量运算
- 数组中,成员之间不能彼此赋值,但是结构体变量可以互相赋值。
练习1:
创建一个结构体数组,数组名为book,结构体成员包含编号,书名,售价(数据类型自己设定)。写一个函数,包含两个形参,分别接收结构体数组的首地址和一个指定的售价,函数的功能为打印结构体数组中售价大于指定售价的书的信息。
#include <stdio.h>
#include <string.h>
typedef struct book
{
int number;
char name[32];
int price;
} BOOK;
BOOK b[5]={
{1,"santi",25},
{2,"hongloumeng",45},
{3,"jinpingmei",50},
{4,"xiyouji",87},
{5,"sanguo",67}
};
void book_Pri(BOOK *p, int n)
{
for(int i=0;i<5;i++)
{
if(p->price >= n)
printf("%d %s %d\n",p->number,p->name,p->price);
p++;
}
}
int main()
{
book_Pri(b,50);
return 0;
}
练习2:创建一个名为student的结构体数组,包含学号,姓名,分数,(数据类型自己定义),从终端输入学生的信息并打印分数及格的学生信息(输入3人即可)。封装函数实现把其中第几个学生信息清零。
#include <stdio.h>
#include <string.h>
typedef struct student
{
int id;
char name[32];
float score;
} STU;
STU s[3];
void delStudent(STU *p, int n)
{
n--;
/*第一种清零方式 */
// (p+n)->id=0;
// strcpy((p+n)->name,"\0");
// (p+n)->score=0;
/*第二种清零方式 */
//memset((p+n),0,sizeof(struct student));
/*第三种清零方式*/
STU stu2 ={0,"\0",0};
p[n] = stu2;
}
int main()
{
printf("pls input student information:\n");
for(int i=0;i<3;i++)
scanf("%d %s %f",&s[i].id,s[i].name,&s[i].score);
for(int i=0;i<3;i++)
{
if(s[i].score >= 60)
printf("%d %s %f\n",s[i].id,s[i].name,s[i].score);
}
delStudent(s,2);
printf("student information:\n");
for(int j=0;j<3;j++)
printf("%d %s %f\n",s[j].id,s[j].name,s[j].score);
return 0;
}
练习3:创建一个结构体数组,数组名为lolHero,成员包含名称, 位置, 血量和价格。给出每个lol英雄信息,封装函数实现按价格从低到高打印英雄信息。(用冒泡排序)
#include <stdio.h>
#define N 5
typedef struct hero
{
char *name;
char *position;
int blood;
int price;
} HERO;
void sort(HERO *p, int n)
{
HERO temp;
for (int i = 1; i < N ; i++)
{
for (int j = 0; j < N - i; j++)
{
if (p[j].price > p[j + 1].price)
{
temp = p[j];
p[j] = p[j + 1];
p[j + 1] = temp;
}
}
}
}
int main(void)
{
HERO lolHero[N] = {
{"ali", "ap", 596, 6300},
{"gailun", "tank", 700, 450},
{"jee", "ad", 596, 6000},
{"cat", "helfer", 400, 6300},
{"ez", "adc", 550, 6500}
};
sort(lolHero, N);
for (int i = 0; i < N; i++)
printf("%d. %8s %8s %8d %8d\n",i+1, lolHero[i].name, lolHero[i].position, lolHero[i].blood, lolHero[i].price);
}
练习4:已知字符数组a[10]和b[10]中元素的值递增有序,封装函数并用指针实现将两个数组中元素按照递增顺序输出。
如:char a[10]=”acdgjmno” ; char b[10]=”befhil”;->”abcdefghijlmno”
思路:利用指针比较两字符数组中的元素,若a中的小,打印a中字符,然后a的指针移动到下一个元素继续比较,反之打印b然后移动b指针到下一个元素继续比较。最后判断字符串是否结束,打印没有结束的字符串剩下部分
#include <stdio.h>
void fun(char *p1, char *p2)
{
while (*p1 != '\0' && *p2 != '\0') //判断是否到两个数组的结尾
{
if (*p1 < *p2) //进行比较,把小的数据打印出来
{
printf("%c", *p1);
p1++;
}
else
{
printf("%c", *p2);
p2++;
}
}
if (*p1 == '\0') //把其中剩下的数据打印出来
printf("%s\n", p2);
else if (*p2 == '\0')
printf("%s\n", p1);
}
int main(int argc, char const *argv[])
{
char a[10] = "acegijkm";
char b[10] = "bdfh";
fun(a, b);
return 0;
}
六、共用体
共用体有时候也称联合体,用法类似结构体struct,区别在于结构体所占内存大小等于所有成员占用内存总和。而共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新成员赋值,就会把原来成员的值覆盖掉。
6.1格式
union 共用体名
{
成员列表;
};
6.2 定义共用体union变量
#include <stdio.h>
union val
{
int a;
char ch;
};
union val v;
int main(int argc, char const *argv[])
{
v.a=10;
v.ch='c';
printf("%d\n",v.a);
return 0;
}
得出共用体变量共用一块空间
6.3 特性
-
(1) 共用体成员共同使用一块内存空间
-
(2) 赋值顺序以最后依次赋值为准
-
(3) 共用体的大小为成员中数据类型最大的数据为大小
6.4 使用共用体测试大小端:🎯
#include <stdio.h>
union val
{
int a;
char ch;
};
union val v;
int main(int argc, char const *argv[])
{
v.a=10;
if(v.ch == 10)
printf("small\n");
else if(v.ch == 0)
printf("big\n");
printf("%d\n",v.ch);
return 0;
}
七、枚举
7.1 定义
用户自定义数据类型,可以用于声明一组常数。
在实际编程中,有些数据的取值往往是有限的,只能是非常少量的整数,并且最好为每个值都起一个名字。
例如以每周七天为例子,我们可以使用宏定义#define来为每天命名:
#define MON 1
#define TUES 2
…
虽然能解决问题但是宏名过多导致代码看起来很松散,所以C语言提供了一种枚举enum,能够列出所有可能的取值,并给它们起一个名字。
7.2 格式
enum 枚举名
{
value 1,
value 2,
value 3,
...
};
未赋初值时,第一个常数会默认为0,然后下面的依次加一。每个元素都可以赋值,如果有元素没有赋值则默认值为上一个元素加一。
例子:
#include <stdio.h>
enum week
{
MON,
THES = 2,
WED,
};
int main(int argc, char const *argv[])
{
int day = 0;
scanf("%d", &day);
switch (day)
{
case MON:
printf("%d\n", MON);
break;
case THES:
printf("%d\n", THES);
break;
case WED:
printf("%d\n", WED);
break;
default:
break;
}
printf("MON:%d\n", MON);
printf("THES:%d\n", THES);
printf("WED:%d\n", WED);
return 0;
}
八、数据类型总结
- 基本数据类型:基本数据类型主要的特点是其值不可以再分解为其他类型,也就是说,基本数据类型是自我说明的。如: short int long float double signed unsigned
- 构造数据类型:构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说一个构造型的值可以分解成若干个“成员”或者“元素”。每个“成员”都是一个基本数据类型或者又是一个构造型。struct结构体、union联合体、enum枚举和数组。
- 指针类型:指针是一个特殊的数据类型。其值用来标识某个变量再内存中的地址。虽然指针变量的取值类似于整型,但这两个类型完全不同,不能混淆。定义时要加*。
例子:
#include <stdio.h>
enum week
{
MON,
THES = 2,
WED,
};
int main(int argc, char const *argv[])
{
int day = 0;
scanf("%d", &day);
switch (day)
{
case MON:
printf("%d\n", MON);
break;
case THES:
printf("%d\n", THES);
break;
case WED:
printf("%d\n", WED);
break;
default:
break;
}
printf("MON:%d\n", MON);
printf("THES:%d\n", THES);
printf("WED:%d\n", WED);
return 0;
}
八、数据类型总结
- 基本数据类型:基本数据类型主要的特点是其值不可以再分解为其他类型,也就是说,基本数据类型是自我说明的。如: short int long float double signed unsigned
- 构造数据类型:构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说一个构造型的值可以分解成若干个“成员”或者“元素”。每个“成员”都是一个基本数据类型或者又是一个构造型。struct结构体、union联合体、enum枚举和数组。
- 指针类型:指针是一个特殊的数据类型。其值用来标识某个变量再内存中的地址。虽然指针变量的取值类似于整型,但这两个类型完全不同,不能混淆。定义时要加*。
- 空类型:空类型void用来定义任意类型的指针和无返回值的函数。