一、数据类型分类
1、基本类型
-
整数型
- 短整型:short(2个字节);
- 整型(默认):int(4个字节);
- 长整型:long(8个字节);
- 长长整型:long long;
-
浮点型
- 单精度:float(4个字节);
- 双精度:double(8个字节);
-
字符型:char(1个字节)
2、指针类型
- 数据类型*:int* ,char* ,float*等;
- void*:任意数据类型的指针
3、空类型
- void:没有返回值或没有形参(不能定义变量)(定义变量的目的是存储数据)
4、自定义类型/构造类型
- 结构体类型:struct;
- 共用体类型(联合体):union;
- 枚举类型:enum;
注意:整数型和字符型分有符号signed和无符号unsigned,默认是有符号,有符号可以省略关键字signed。
二、结构体数组
-
定义:自定义数据类型的一种,关键字 struct ,结构体类型的变量可以存储多个不同数据类型的数据。
-
定义格式:
struct 结构体名 { 数据类型1 成员名称1; }
注意:结构体中定义的变量,我们称之为成员变量。
-
格式说明:
- 结构体名:合法的标识符,建议单词的首字母大写
- 数据类型n:C语言支持的所有类型;
- 成员名称:合法的标识符,就是变量的命名标准
- 数据类型n 成员名称n:类似于定义变量,定义了结构体中的成员
-
注意:
- 结构体在定义的时候,成员不能赋值;
- 举例:
struct Cat { int age = 5;//错误,结构体定义时,成员不能赋值 double height;//正确 }
1、常见的定义格式:
- 方式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;//变量名
};
注意:定义匿名结构体的同时必须定义结构体变量,否则编译报错,结构体可以作为另一个结构体的成员。
总结:
- 结构体可以定义在局部位置,也可以定义在全局位置;
- 全局位置的结构体名和局部位置的结构体名可以相同,就近原则(和普通变量的定义同理)。
- 结构体类型的使用:利用结构体类型定义变量,定义数组:结构体类型的使用与基本数据类型的使用类似。
2、结构体变量的定义:
-
三种形式定义结构体变量:
结构体变量也称为结构体的实力。
1.第一种
①先定义结构体;
②然后使用 struct 结构体名 变量名;
//先定义结构体(先定义结构体这个数据类型)
struct A
{
int a;
char b;
}
//定义结构体变量
struct A x;
struct A y;
2.第二种
在定义结构体同时,定义结构体变量:
//定义结构体的同时定义结构体变量
struct A
{
int a;
char b;
}x;
此时定义了一个结构体A,x是这个结构体类型的变量。
3.第三种:不推荐
在定义匿名结构体的同时,定义结构体变量:
struct
{
int a;
char b;
}x,y;
此时定义了一个没有名字的结构体(成为匿名结构体);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; }
3、结构体变量的使用
-
结构体变量访问结构体成员
- 格式
结构体变量名.成员名;
可以通过访问给成员赋值(存数据);可以通过访问获取成员的值(取数据)
- 结构体变量未初始化,结构体的成员值随机(不确定)
-
结构体变量在定义时,可以初始化
- 建议用大括号标明数据的范围;
- 结构体这个成员初始化时,可以部分初始化,部分初始化时一定要带大括号标明数据的范围
举例:结构体变量的初始化
/** * 结构体变量的初始化 */ #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; }
4、结构体数组的定义
-
什么时候需要结构体数组?
比如:我们需要管理一个学生对象,只需要定义一个 struct Student{...};
假如:我们需要管理多个学生对象,此时就需要一个结构体的数组 struct Student students[64];
-
三种形式定义结构体数组
-
第一种:先定义结构体类型,然后定义结构体变量,再将变量存储在结构体数组中
//定义一个学生类型的结构体 struct Student { char *name; int age; float scores[3];//三门课程的成绩 } //定义结构体对象 struct Student zhangsan = {"张三",23,{67.5,43.0,90.0}}; struct Student lisi = {"李四",21,{22.4,65.7,98.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;
小贴士:
结构体数组名访问结构体成员:
格式:结构体数组名 --> 成员名;
举例:结构体数组案例_对候选人得票的统计程序。设有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];//用来接受待投票的候选人姓名 //使用一个循环,完成十次投票 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("\n%5s: %d\n",persons[i].name,persons[i].count); } }
5、结构体指针(即变量指针,非真的结构体指针)
-
定义:结构体类型的指针变量指向结构体变量或者数组的起始地址。
-
语法:struct结构体名 *指针变量列表;
- 举例:
struct Dog { char name[20]; int age; }; struct Dog dog; struct Dog *p = &dog;
-
结构体成员访问
- 结构体数组名访问结构体成员 1)格式:结构体数组名 -> 成员名;
- 结构体成员访问符 1) . :左侧是结构体变量(结构体对象/实例),也可以叫做结构体对象访问成员符;左侧是结构体成员; 2) -> :左侧是一个指针,也可以叫结构体指针访问成员符;右侧是结构体成员。
-
访问结构体成员有两种类型,三种方式:
-
类型1:通过结构体对象访问成员
struct Stu { int id; char name[20]; } stu; // 访问成员 stu.name;
-
类型2:通过结构体指针访问成员 1)指针引用访问成员;
struct Stu { int id; char name[20]; } stu; struct Stu *p = &stu; // 指针引用访问成员 p -> name;
2)指针解引用间接访问成员;
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); } }
6、结构体类型求大小(字节对齐)
规则:字节对齐(数据在内存中存储在其类型大小的整数倍上)
1、首先保证结构体中的成员存储在自身的对齐边界(类型大小的整数倍);
2、在满足1的条件下,最终大小要满足最大成员所占存储单元的整数倍。
举例:求结构体数据类型大小
/** * 求结构体数据类型的大小 */ #include <stdio.h> //定义测试结构体 struct test1 { char a;// 1 int b;// 4 short c;// 2 }; int main() { //创建结构体变量 struct test1 tt1; //计算大小 printf("%lu\n",sizeof(tt1));//%lu --> 无符号的long类型 }
/** * 求结构体数据类型的大小 */ #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)));// 2 设置对齐的字节数,字节数必须是2的n次 幂 int b;// 4 }; struct test2 { char a;// 1 short c;// 2 int b;// 4 //short c;// 2 }; 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 tt1; struct test1_1 tt1_1; struct test1_2 tt1_2; struct test2 tt2; struct test3 tt3; struct test4 tt4; //计算大小 printf("%lu\n",sizeof(tt1));//%lu --> 无符号的long类型 printf("%lu\n",sizeof(tt1_1)); printf("%lu\n",sizeof(tt1_2)); printf("%lu\n",sizeof(tt2)); printf("%lu\n",sizeof(tt3)); printf("%lu\n",sizeof(tt4)); }
- 快速计算结构体大小:
https://blog.csdn.net/weixin_72357342/article/details/131135555 https://blog.csdn.net/x2528238270/article/details/120798606
三、共用体/联合体类型
- 定义: 使几个不同的变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单 元,其他成员也是用该空间,他们的首地址是相同。
- 定义格式:
union 共用体名称 { 数据类型 变量名; 数据类型 变量名; ... };
-
共用体的定义和结构体类型相似:
- 可以有名字,也可以匿名;
- 共用体在定义时也可以定义共用体变量;
- 共用体在定义时也可以初始化成员;
- 共用体也可以作为形参和返回值类型使用;
- 共用体也可以定义共用体数组...
也就是说,结构体的语法,共用体都支持。
注意:
- 共用体弊大于利,尽量少用;
- 共用体变量在某一时刻只能存一个数据,并且也只能取出一个数;
- 公用题和结构体都是自定义数据类型,用法类似于基本数据类型
1)共用体可以是共用体的成员,也可以是结构体的成员;
2)结构体可以是共用体的成员,也可以是共用体的成员。
举例:共用体
/** * 共用体 */ #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 a; char *name; char sex; int age; }; //类型重命名 typedef struct Student Stu; //定义变量 struct Stu stu = {1,"张甜",'M',23}; //定义结构体的同时重命名 typedef struct PersonInfo { int a; double b; }Per; //定义变量 struct Per per = {2,5};
- 应用场景
- 数据类型复杂(结构体,共用体,枚举,结构体指针)时使用
- 为了跨平台兼容性,例如:1)size_t :类型重命名后的数据类型, typedef unsigned long size_t; 2)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; }
六、作业
1. 设计一个结构体,存放一个学员信息并显示,存放两个学员信息,算他们的平均分。
代码:
/** * 设计一个结构体,存放一个学员信息并显示,存放两个学员信息,算他们的平均分 */ #include <stdio.h> #include <string.h> //全局的结构体(数据类型) struct Student { char name[20];//姓名 int age;//年龄 char sex;//性别 M:男 W:女 float score;//分数 }stu; //先定义再初始化 void one_stu() { // typedef Student stu; printf("\n请输入学员信息:姓名,年龄,性别(M:男;W:女)\n"); printf("请输入学员姓名:\n"); scanf("%s",stu.name); printf("请输入学员%s的年龄,性别:\n",stu.name); scanf("%d,%c",&stu.age,&stu.sex); printf("姓名:%s,年龄:%d,性别:%c\n",stu.name,stu.age,stu.sex); } void multi_stu() { int i = 0,n; float sum = 0.0; printf("请选择要输入几名学员的信息___:%d\n",n); scanf("%d",&n); // Student stu[n]; // for(i = 0;i < n;i++) do{ printf("\n请输入学员信息:姓名,年龄,性别(M:男;W:女),成绩:\n"); printf("请输入学员姓名:\n"); scanf("%s",stu.name); printf("请输入学员%s的年龄,性别,分数:\n",stu.name); scanf("%d,%c,%f",&stu.age,&stu.sex,&stu.score); printf("姓名:%s,年龄:%d,性别:%c,分数:%.2f\n",stu.name,stu.age,stu.sex,stu.score); i++; // sum += stu[i].score; // float aver = sum / (n-1); // return aver; }while(i <= (n-1)); int main() { // stu s[2]; int l,n; printf("请选择输入几个学员的信息(一个(0),多个(1))___:%d\n",l); scanf("%d",&l); switch(l) { case 0: one_stu(); break; case 1: // stu s[2]; // default: multi_stu(); // float aver = calcuAver(s,count); // printf("平均分为:%.2f\n",aver); break; default: printf("程序错误!\n"); } // one_stu(); // multi_stu(); return 0; }
运行效果:
2. 设计一个描述商品的结构体,存放一个商品信息,并显示。
代码:
/** * 设计一个描述商品的结构体,存放一个商品信息,并显示 */ #include <stdio.h> #include <string.h> struct Information { char name[20];//名称 int number;//数量 double price;//价格 }info; void entering() { printf("请输入商品信息:___\n"); printf("请输入商品名称:\n"); scanf("%s",info.name); printf("请输入商品数量:\n"); scanf("%d",&info.number); printf("请输入商品价格:\n"); scanf("%lf",&info.price); printf("您已录入商品%s的数量%d价格%5.2lf\n",info.name,info.number,info.price); } int main() { entering(); return 0; }
运行效果:
3. 作业2的基础之上完成以下功能:
(1)存储多个商品的信息,后显示
(2)查询价格最高的商品的信息
(3)用静态分配(结构体变量)和动态分配分别实现。
代码:
运行效果:
4. 开发一个简易的成绩管理系统: 存储多个学员信息并处理 要求如下:
(1)申请多个空间,存入学员信息
(2)求分数最高学员姓名
(3)求总分和平均分
(4)统计查询 查询平均分以上学员的姓名
(5)按照成绩排序
代码:
运行效果:
5. 确认电脑的主机字节序。大端(高字节的数据存放在低地址上,低字节的数据存放在高地址上), 小端(高字节的数据存放在高地址上,低字节的数据存放在低地址上)
代码:
运行效果:
f图像 小部件