数据类型分类
结构体
结构体的定义
-
定义:自定义数据类型的一种,关键字 struct ,结构体类型的变量可以存储多个不同数据类型的数据。
-
定义格式:
struct 结构体名
{
数据类型1 成员名称1;
数据类型2 成员名称2;
…
}
注意:结构体中定义的变量,我们称之为成员变量。
-
格式说明:
- 结构体名:合法的标识符,建议单词的首字母大写
- 数据类型n:C语言支持的所有类型
- 成员名称:合法的标识符,就是变量的命名标准
- 数据类型n 成员名称n:类似于定义变量,定义了结构体中的成员
-
注意:
-
结构体在定义的时候,成员不能赋值
-
举例:
struct Cat { int age = 5;// 错误,结构体定义的时候,成员不能赋值 double height; // 正确 }
-
-
常见的定义格式:
-
方式1:常规定义(命名结构体,只定义类型)—推荐
struct Student { int num;// 学号 char name[20];// 姓名 char sex;// 性别 int age;// 年龄 char address[100];// 家庭住址 }
-
方式2:定义匿名结构体(常用于作为其他结构体的成员使用)
struct Dog { char *name;// 姓名 int age;// 年龄 struct // 此时这个结构体就是匿名 { int year;// 年 int month;// 月 int day;// 日 } birthday; }
注意:定义匿名结构体的同时必须定义结构体变量,否则编译报错,结构体可以作为另一个结构体的成员。
-
总结:
-
结构体可以定义在局部位置,也可以定义在全局位置;
-
全局位置的结构体名和局部位置的结构体名可以相同,就近原则(和普通变量的定义同理)
-
结构体类型的使用:
利用结构体类型定义变量,定义数组;结构体类型的使用与基本数据类型的使用类似。
结构体变量的定义
-
三种形式定义结构体变量:
结构体变量也称为结构体的实力。
-
第一种
① 先定义结构体
② 然后使用 struct 结构体名 变量名 ;
// 先定义结构体(先定义结构体这个数据类型) struct A { int a; char b; } // 定义结构体变量 struct A x; struct A y;
-
第二种
在定义结构体的同时,定义结构体变量;
// 定义结构体的同时定义结构变量 struct A { int a; char b; } x,y; struct A z;
此时定义了一个结构体A,x和y是这个结构体类型的变量。
-
第三种:不推荐
在定义匿名结构体的同时,定义结构体变量;
struct { int a; char b; } x,y; struct { int a; char b; } z;
此时定义了一个没有名字的结构体(称为匿名结构体);y,x是这个匿名结构体类型的变量;
-
-
匿名结构体:—弊大于利(尽量少用)
-
优点:少写一个结构体名称
-
缺点:只能使用一次;定义的结构体类型的同时就必须定义变量
-
应用场景:
-
当结构体的类型只需要使用一次,并且定义类型的同时定义变量。
-
作为其他结构体的成员使用。
-
-
-
定义结构体的同时,定义结构体变量初始化
struct Cat { int age; char color[20]; } cat;
-
结构体成员部分初始化是,大括号不能省略。
-
结构体的成员,没有默认值,是不确定的数
案例:
/** * 结构体变量的定义 */ #include <stdio.h> // 先定义结构体,再定义结构体变量 void fun1() { // 先定义结构体 struct A { int a; char b; }; // 再定义结构体变量 struct A x; struct A y; } // 定义结构体的同时定义结构体变量 void fun2() { struct A { int a; char b; } x,y; struct A z; } // 定义匿名结构体的同时定义结构体变量 void fun3() { struct { int a; char b; } x,y; struct { int a; char b; } z; } int main() { fun1(); fun2(); fun3(); return 0; }
-
结构体变量的使用
-
结构体变量访问结构体成员
-
格式
结构体变量名.成员名;
可以通过访问给成员赋值(存数据)
可以通过访问获取成员的值(取数据)
-
结构体变量未初始化,结构体的成员值随机(不确定)
-
-
结构体变量在定义时,可以初始化
-
建议用大括号标明数据的范围
-
结构体成员初始化时,可以部分初始化,部分初始化时一定要带大括号标明数据的范围
-
-
案例:
/** * 结构体变量的初始化 */ #include <stdio.h> /* 全局的结构体(数据类型) */ struct Dog { char *name;// 姓名 int age;// 年龄 char sex;// M:公,W:母 }; /* 先定义,再初始化 */ void fun1() { // 定义一个结构体 // struct Dog // { // char *name;// 姓名 // int age;// 年龄 // char sex;// M:公,W:母 // }; // 定义结构体变量 struct Dog dog; // 给结构体变量的成员赋值 dog.name = "旺财"; dog.age = 5; // 访问结构体变量的成员 printf("%s,%d,%c\n",dog.name,dog.age,dog.sex); } /* 定义的同时初始化 */ void fun2() { // 定义结构体变量并初始化 struct Dog dog = {"招财",23,'M'}; // 修改成员的值 dog.name = "进宝"; // 访问结构体变量的成员 printf("%s,%d,%c\n",dog.name,dog.age,dog.sex); } int main() { fun1(); fun2(); return 0; }
结构体数组的定义
-
什么时候需要结构体数组?
-
比如:我们需要管理一个学生对象,只需要定义一个 struct Student majie;
-
假如:我们需要管理多个学生对象,此时就需要一个结构体的数组 struct Student students[64]; 。
-
-
三种形式定义结构体数组
-
第一种:先定义结构体类型,然后定义结构体变量,再将变量存储到结构体数组中
// 定义一个学生类型的结构体 struct Student { char *name; int age; float scores[3];// 三门课程的成绩 }; // 定义结构体对象 struct Student zhangsan = {"张三",23,{67.5,89.0,90.0}}; struct Student lisi = {"李四",21,{77.0,80.0,85.0}}; // 定义结构化数组 struct Student students[3] = {zhangsan,lisi};
-
第二种:定义结构体类型,然后定义结构体数组并初始化
// 定义一个学生类型的结构体 struct Student { int id; char *name; int age; float scores[3];// 三门课程的成绩 }; // 定义结构体数组并初始化 struct Student students[3] = { {1,"张三",23,{67.5,89.0,90.0}},// 注意:这里赋值的顺序需要跟成员在结构体中的顺序一致 {2,"李四",21,{77.0,80.0,85.0}} };
-
第三种:定义结构体类型同时定义结构体数组并初始化
// 定义一个学生类型的结构体 struct Student { int id; char *name; int age; float scores[3];// 三门课程的成绩 } ; students[3] = { {1,"张三",23,{67.5,89.0,90.0}},// 注意:这里赋值的顺序需要跟成员在结构体中的顺序一致 {2,"李四",21,{77.0,80.0,85.0}} };
-
第四种:定义结构体类型同时定义结构体数组,然后通过索引给结构体成员赋值
// 定义一个学生类型的结构体 struct Student { int id; char *name; int age; float scores[3];// 三门课程的成绩 } sts[3]; sts[0].id = 1; sts[0].name = "张三"; sts[0].age = 12; sts[0].scores[0] = 98;
小贴士:
结构体数组名访问结构体成员:
-
,21,{77.0,80.0,85.0}}
};
```
-
第四种:定义结构体类型同时定义结构体数组,然后通过索引给结构体成员赋值
// 定义一个学生类型的结构体 struct Student { int id; char *name; int age; float scores[3];// 三门课程的成绩 } sts[3]; sts[0].id = 1; sts[0].name = "张三"; sts[0].age = 12; sts[0].scores[0] = 98;
小贴士:
结构体数组名访问结构体成员:
格式:结构体数组名 -> 成员名
结构体类型
结构体数组
案例:
需求:对候选人得票的统计程序。设有3个候选人,每次输入一个得票的候选人的名字,要求最后输出各人得票结果。
/**
* 结构体数组案例:对候选人得票的统计程序。设有3个候选人,每次输入一个得票的候选人的名字,要求最后输出各人
得票结果。
*/
#include <stdio.h>
#include <string.h>
/**
* 定义一个候选人结构体(对象)
*/
struct Person
{
char name[20];// 名字
int count; // 票数
};
// 定义候选人数组,并初始化
struct Person persons[3] = {
{"张月",0},
{"李湘",0},
{"第五名",0}
};
void main()
{
int i,j;
char leader_name[20];// 用来接收被投票的候选人姓名
// 使用一个循环,完成10次投票
for(i = 0; i < 10; i++)
{
printf("请输入您要投票的候选人姓名:\n");
scanf("%s",leader_name);
// 给被投票的候选人+1票
for(j = 0; j < 3; j++)
{
// 如何判断两个字符串相等
if(strcmp(leader_name,persons[j].name) == 0)
{
persons[j].count++;
}
}
}
printf("\n投票结果:\n");
for(i = 0; i < 3; i++)
{
printf("%5s:%d\n",persons[i].name,persons[i].count);
}
}
结构体指针
-
定义:结构体类型的指针变量指向结构体变量或者数组的起始地址。
-
语法:struct 结构体名 *指针变量列表;
-
举例:
struct Dog { char name[20]; int age; }; struct Dog dog; struct Dog *p = &dog;
结构体成员访问
- 结构体数组名访问结构体成员
- 格式:结构体数组名 -> 成员名;
- 结构体成员访问符
. :左侧是结构体变量(结构体对象/实例),也可以叫做结构体对象访问成员符;右侧是结构体成员。
-> :左侧是一个指针,可也以叫结构体指针访问成员符;右侧是结构体成员。
-
访问结构体成员有两种类型,三种方式:
-
类型1:通过结构体对象访问成员
struct Stu { int id; char name[20]; } stu; // 访问成员 stu.name;
-
类型2:通过结构体指针访问成员
-
指针引用访问成员
struct Stu { int id; char name[20]; } stu; struct Stu *p = &stu; // 指针引用访问成员 p -> name;
-
指针解引用间接访问成员
struct Stu { int id; char name[20]; } stu; struct Stu *p = &stu; // 指针解引用间接访问成员 (*p).name;
-
结构体数组中元素的访问
struct Stu { int id; char name[20]; float scores[3]; } stus[3] = { {1,"张三",{86,88,56}}, {2,"李四",{75,66,78}}, {3,"王五",{70,99,90}} }; // 取数据 --- 下标法 printf("%s,%2f\n",stus[1].name,stus[1].scores[2]);// 李四,78 // 结构体成员引用符号:-> 指针法 printf("%s,%2f\n",stus -> name,stus -> scores[2]);// 张三,56 printf("%s,%2f\n",(stus + 1)-> name,(stus + 1)-> scores[2]);// 李四,78 printf("%s,%2f\n",(*(stus + 1)).name,(*(stus + 1)).scores[2]);// 李四,78
-
-
小贴士:
结构体是自定义数据类型,它是数据类型,用法类似于基本类型的int;
结构体数组它是存放结构体对象的数组,类似于int数组存放int数据;
基本类型数组怎么用,结构体数组就怎么用—>可以遍历,可以作为形式参数,也可以做指针等;
结构体类型的使用案例
代码:
#include <stdio.h>
// 定义结构体
struct Cat
{
char *name;// 姓名
int age;// 年龄
char color[20];// 颜色
}
// 1.结构体类型作为形式参数
void test1(struct Cat c);
// 2.结构体类型作为形式参数,结构体类型作为返回值类型
struct Cat test2(struct Cat c);
// 3.结构体数组作为形式参数
void test3(struct Cat cats[],int len);
// 4.结构体数组作为形式参数,结构体指针作为返回值数据类型
struct Cat *test4(struct Cat cats[],int len);
测试:
int main()
{
// 定义结构体对象
struct Cat cat = {"小黑",8,"baise"};
// 结构体对象作为实际参数
test1(cat);
// 定义结构体类型对象
struct Cat cat1 = {"小白",8,"heise"};
// 调用函数并接收返回值
struct Cat c1 = test2(cat1);
// 通过返回值访问结构体对象的成员
printf("%s==%d==%s\n",c1.name,c1.age,c1.color);
// 定义结构体数组
struct Cat cats[3] = {
{"汤姆",16,"蓝色"},
{"杰瑞",18,"褐色"},
{"唐老鸭",19,"白色"}
};
// 结构体数组名作为实际参数
test3(cats,3);
// 定义结构体数组并初始化
struct Cat cats1[3] = {
{"汤姆",16,"蓝色"},
{"杰瑞",18,"褐色"},
{"唐老鸭",19,"白色"}
};
// 调用函数
struct Cat *p = test4(cats1,3);
struct Cat *w;
// 通过指针运算遍历数组
for(w = p; w < p + 3; w ++)
{
// p[i][j] = *(p[i]+j) = *(*(p+i)+j) 三者等价
// 通过结构体指针访问符访问结构体的成员
printf("%s----%d----%s\n",w -> name,w -> age,w -> color);
}
}
结构体类型求大小
-
规则:字节对齐(默认,数据在内存中存储在其类型大小的整数倍上)
-
首先保证结构体中的成员存储在自身的对齐边界(类型大小的整数倍);
-
在满足1的条件下,最终大小要满足 最大成员 所占存储单元的整数倍;
-
-
为什么要使用字节对齐:
节省内存,提高访问效率。
-
在GNU标准中,可以在定义结构体时,指定对齐规则:
__attribute__((packed)); 结构体所占内存大小是所有成员所占内存大小之和 __attribute__((aligned(n))); 设置结构体占n个字节,如果n比默认值小,n不起作用;n必须是2的次方
-
柔性数组:
struct st { ... char a[0]; }
柔性数组不占有结构体的大小。
案例:
/** * 求结构体数据类型的大小 */ #include <stdio.h> // 定义测试结构体 struct TEST1 { char a;// 1 int b; // 4 }; struct TEST1_1 { char a;// 1 int b;// 4 }__attribute__((packed));// 取消字节对齐,取消之后,结构体数据类型大小就等于其所有成员的数据类型之和 struct TEST1_2 { char a __attribute__((aligned(2))); int b; }; struct TEST2 { char a;// 1 short c; // 2 int b; // 4 }; struct TEST3 { int num;// 4 char name[10];// 10 char sex;// 1 int age;// 4 double score;// 8 }; struct TEST4 { int num;// 4 short name[5];// 10 char sex;// 1 int age;// 4 int scores[2];// 8 }; int main() { // 创建结构体变量 struct TEST1 test1; struct TEST2 test2; struct TEST3 test3; struct TEST4 test4; struct TEST1_1 test1_1; struct TEST1_2 test1_2; // 计算大小 printf("%lu\n",sizeof(test1)); printf("%lu\n",sizeof(test2)); printf("%lu\n",sizeof(test3)); printf("%lu\n",sizeof(test4)); printf("%lu\n",sizeof(test1_1)); printf("%lu\n",sizeof(test1_2)); }
推导过程:
共用体/联合体类型
定义
使几个不同的变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单元,其他成员也是用该空间,他们的首地址是相同。
定义格式
union 共用体名称
{
数据类型 变量名;
数据类型 变量名;
…
};
-
共用体的定义和结构体类型类似:
-
可以有名字,也可以匿名;
-
共用体在定义时也可以定义共用体变量;
-
共用体在定义时也可以初始化成员;
-
共用体也可以作为形参和返回值类型使用;
-
共用体也可以定义共用体数组
…
也就是说,结构体的语法,共用体都支持。
-
-
注意:
-
共用体弊大于利,尽量少用,一般很少用;
-
共用体变量在某一时刻只能存一个数据,并且也只能取出一个数。
-
公用题和结构体都是自定义数据类型,用法类似于基本数据类型
- 共用体可以是共用体的成员,也可以是结构体的成员。
- 结构体可以是共用体的成员,也可以是共用体的成员。
-
-
案例:
/** * 共用体 */ #include <stdio.h> // 定义共用体 union S { char a; float b; int c; }; // 共用体作为共用体的成员 union F { char a; union S s; }; // 共用体作为结构体的成员 struct G { int a; union S s; }; // 定义一个结构体 struct H { int a; char b; }; // 结构体作为结构体成员 struct I { int a; int b; struct H h; }; // 共用体作为结构体成员 struct J { int a; char b; union S s; }; void test1() { // 定义共用体类型 union Stu { int num; char sex; double score; }; // 定义匿名共用体:匿名共用体一般作为结构体成员或者其他共用体成员 union { int a; char c; } c; printf("%lu,%lu\n",sizeof(union Stu),sizeof(c)); } void test2() { union C { int a; char b; }; // 定义变量 union C c; // 存数据 c.a = 10; c.b = 'A'; printf("%d---%d\n",c.a,c.b);// 取数据 c.a += 5; printf("%d---%d\n",c.a,c.b);// 取数据 union E { char *f; long a; int b; } e = {"hello world!"}; printf("%s,%p---%ld,%p---%d\n",e.f,&(e.f),e.a,&(e.a),e.b); } int main() { test1(); test2(); }
枚举类型
定义
我们一般情况下,定义常量使用宏定义(#define 宏名称 值),宏定义非常适合没有关联关系的常量;但是有时候我们可能需要对一组拥有关联关系的量进行定义,比如 周一~周日 、 1月~12月 等,那么使用宏定义,就不是很清晰在,这个时候就需要使用到枚举。
枚举的存在就是将多个拥有关联关系的常量组合到一起,提高代码的可读性。
说明
枚举类型定义了一组常量,我们在开发中直接使用这些常量。(常用)
当然枚举类型也可以类似于结构体一样定义变量等操作。(不常用)
枚举常量有默认值,从0开始依次加1;我们可以在定义时指定它的值,如果个别没有赋值,可以根
据赋值依次加1推导。
特点
定义了一组常量,类似于定义了多个自定义常量(宏定义)
提供了代码的可读性(避免了魔术数字)
定义语法
定义枚举类型名以后就可以定义该枚举类型的变量
enum 枚举类型名 变量表;
在定义枚举类型的同时定义该枚举类型的变量。
enum 枚举类型名{ 枚举元素列表 }变量表;
直接定义枚举类型变量。
enum { 枚举元素列表 }变量表;
-
案例:
/** * 枚举类型 */ #include <stdio.h> // 常量-宏定义 // 常量的命名:大写英文字母+下滑下,举例:MAX_VALUE #define PI 3.1415926 void test1() { // 定义枚举类型 enum Week { SUN=10,MON,TUE,WED,THU,FRI,SAT }; printf("%d,%d,%d\n",SUN,WED,SAT); // 定义枚举类型的变量(先定义变量,后赋值) enum Week w; // 初始化 w = MON; printf("%d\n",w); // 定义枚举类型的变量同时赋值(定义变量的同时赋值) enum Week w1 = THU; printf("%d\n",w1); enum H { A,B,C } x,y; x = B; y = C; printf("x=%d,y=%d\n",x,y);// 1,2 } void test2() { // 定义枚举 enum CaiQuan { SHI_TOU,JIAN_DAO,BU }; printf("请输入0~2之间的整数:\n[0-石头,1-剪刀,2-布]\n"); int number; scanf("%d",&number); switch(number) // switch和enum是天生的搭档 { case SHI_TOU: printf("石头\n"); break; case JIAN_DAO: printf("剪刀\n"); break; case BU: printf("布\n"); break; } } int main() { test1(); test2(); }
typedef
-
说明:给类型重命名,不会影响到类型本身
-
作用:给已有的类型起别名
-
格式:
typedef 已有类型名 新别名;
-
使用:
// 定义结构体 struct Student { int id; char *name; char sex; int age; }; // 类型重命名 typedef struct Student Stu; // 定义变量 struct Stu stu = {1,"张甜",'M',21}; // 定义结构体的同时类型重命名 typedef struct PersonInfo { int a; double b; } Per; // 定义变量 struct Per per = {2,5};
应用场景
- 数据类型复杂(结构体,共用体,枚举,结构体指针)时使用
- 为了跨平台兼容性,例如:
- size_t:类型重命名后的数据类型, typedef unsigned long size_t;
- unit_16:类型重命名后数据类型
案例:
//类型重命名
#include <stdio.h>
struct Student
{
int age;
char* name;
double score;
int arr[3];
};
typedef struct Student Stu_t;
typedef Stu_t* pStu_t;
void test1()
{
Stu_t s1 = {23, "zhangsan", 23.33, {11, 22, 33}};
printf("%d, %s, %f, %d\n", s1.age, s1.name, s1.score, s1.arr[0]);
//Stu_t *p = &s1;
Stu_t* p;
p = &s1;
pStu_t p2;
p2 = p;
printf("%d, %s, %f, %d\n", p2->age, p2->name, p2->score, p2->arr[0]);
}
int main()
{
test1();
return 0;
}