文章目录
一、数据类型分类
(一)基本类型
1. 整数型
- 短整型:short (2个字节)
- 整型(默认):int (4个字节)
- 长整型:long (8个字节)
- 长长整型:long long
2. 浮点型
- 单精度:float (4个字节)
- 双精度:double (8个字节)
3. 字符型
- char (1个字节)
(二)指针类型
- 数据类型*:
int*,char*float*
等 - void*:任意数据类型的指针
(三)空类型
- void:没有返回值或没有形参(不能定义变量)
(四)自定义类型/构造类型
- 结构体类型:struct
- 共用体类型(联合体):union
- 枚举类型:enum
注意:整数型和字符型分有符号signed和无符号unsigned,默认的是有符号的,有符号可以省略关键字。
二、结构体类型
(一)结构体类型的定义
1. 定义:
自定义数据类型的一种,关键字struct
,结构体类型的变量可以存储多个不同数据类型的数据。
2. 定义格式:
struct 结构体名
{
数据类型1 成员名称1;
数据类型2 成员名称2;
...
};
注意:结构体中定义的变量,我们称之为成员变量。
3. 格式说明:
- 结构体名:合法的标识符,建议单词的首字母大写
- 数据类型n:C语言支持的所有类型
- 成员名称:合法的标识符,就是变量的命名标准
- 数据类型n 成员名称n:类似于定义变量,定义了结构体中的成员
注意:
-
结构体在定义的时候,成员不能赋值
-
举例:
struct Cat { int age = 5; // 错误,结构体在定义的时候,成员不能赋值 double height; // 正确 };
4. 常见的定义格式:
-
方式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; };
-
注意:定义匿名结构体的同时必须定义结构体变量,否则编译报错,结构体可以作为另一个结构体的成员。
-
总结:
- 结构体可以定义在局部位置,也可以定义在全局位置;
- 全局位置的结构体名和局部位置的结构体名可以相同,就近原则(和普通变量的定义同理)
5. 结构体类型的使用:
利用结构体类型定义变量,定义数组;结构体类型的使用与基本数据类型的使用类似。
(二)结构体变量的定义
1. 三种形式定义结构体变量
结构体变量也称为结构体的实例
-
第1种:先定义结构体,然后使用
struct 结构体名 变量名
;// 先定义结构体(先定义结构体这个数据类型) struct A { int a; char b; }; // 定义结构体变量 struct A x; struct A y;
-
第2种:在定义结构体的同时,定义结构体变量;
// 在定义结构体的同时定义结构体变量 struct A { int a; char b; } x,y;
此时定义了一个结构体A,x和y是这个结构体类型的变量。
-
第3种:在定义匿名结构体的同时,定义结构体变量(不推荐);
struct { int a; char b; } x,y;
此时定义了一个没有名字的结构体(称为匿名结构体);y,x是这个匿名结构体类型的变量
2. 匿名结构体:弊大于利(尽量少用)
-
优点:少写一个结构体名称
-
缺点:只能使用一次,定义的结构体类型的同时就必须定义变量
-
应用场景:
- 当结构体的类型只需要使用一次,并且定义类型的同时定义变量;
- 作为其他结构体成员使用。
3. 定义结构体的同时,定义结构体变量初始化
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;
}
(三)结构体变量的使用
1. 结构体变量访问结构体成员
-
格式
结构体变量名.成员名;
-
可以通过访问给成员赋值(存数据)
-
可以通过访问获取成员的值(取数据)
-
-
结构体变量为初始化,结构体的成员值随机(不确定)
2. 结构体变量在定义时,可以初始化
- 建议用大括号表明数据的范围
- 结构体成员初始化时,可以部分初始化,部分初始化时一定要带大括号标明数据的范围
案例:
/**
* 结构体变量的初始化
*/
#include <stdio.h>
/* 全局的结构体(数据类型) */
struct Dog
{
char *name; // 姓名
int age; // 年龄
char sex; // M:公,W:母
};
/* 先定义,再初始化 */
void fun1()
{
// 定义结构体变量
struct Dog dog;
// 给结构体变量赋值
dog.name = "旺财";
dog.sex = 10;
// 访问结构体变量的成员
printf("%s,%d,%c\n",dog.name,dog.sex,dog.sex);
}
/* 定义的同时初始化 */
void fun2()
{
// 定义结构体变量并初始化
struct Dog dog = {"大黄",16,'M'};
// 访问结构体变量的成员
printf("%s,%d,%c\n",dog.name,dog.sex,dog.sex);
}
int main()
{
fun1();
fun2();
return 0;
}
(四)结构体数组
结构体数组的定义
1. 说明时候需要结构体数组?
- 比如:我们需要管理一个学生对象,只需要定义一个
struct Student zhangsan;
- 假如:我们需要管理多个学生对象,此时就需要一个结构体的数组
struct Student students[64]
。
2. 三种形式定义结构体数组
-
第1种:先定义结构体类型,然后定义结构体变量,再将变量存储到结构体数组中。
// 定义一个学生类型的结构体 struct Student { int id; char *name; int age; float scores[3]; // 三门课程成绩 }; // 定义结构体对象 struct Student zhangsan = {1,"张三",23,{67.5,89.0,90.0}}; struct Student lisi = {2,"李四",21,{77.0,80.0,85.0}}; // 定义结构化数组 struct Student students[3] = {zhangsan,lisi};
-
第2种:定义结构体类型,然后定义结构体数组并初始化
// 定义一个学生类型的结构体 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}} };
-
第3种:定义结构体类型同时定义结构体数组并初始化
// 定义一个学生类型的结构体 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}} };
-
第4种:定义结构体类型同时定义结构体数组,然后通过索引给结构体成员赋值
// 定义一个学生类型的结构体 struct Student { int id; char *name; int age; float scores[3]; // 三门课程成绩 } students[3]; students[0].id = 1; students[0].name = "张三"; students[0].age = 12; students[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]; // 用来接收被投票的候选人姓名
printf("请输入您要投票的候选人姓名(10名):\n");
// 使用一个循环,完成10次投票
for(i = 0; i < 10; i++)
{
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);
}
}
(五)结构体指针
1. 定义:
结构体类型的指针变量指向结构体变量或者数组的起始地址。
2. 语法:
struct 结构体名 *指针变量列表;
3. 举例:
struct Dog
{
char name[20];
int age;
};
struct Dog dog;
struct Dog *p = &dog;
(六)结构体成员访问
1. 结构体数组访问结构体成员
- 格式:结构体数组名 -> 成员名;
2. 结构体成员访问符
.
:左侧是结构体变量(结构体对象/实例),也可以叫做结构体对象访问成员符;右侧是结构体成员。->
:左侧是一个指针,也可以叫结构体指针访问成员符;右侧是结构体成员。
3. 访问结构体成员有两种类型,三种方式:
-
类型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[2] = { {1,"张三",{89,88,86}, {2,"李四",{75,66,88}, {3,"王五",{70,99,90} }; // 取数据 --- 下标法 printf("%s,%2f\n",stus[1].name,stus[1].scores[2]); // 李四,88 // 结构体成员引用符号:-> 指针法 printf("%s,%2f\n",stus -> name,stus -> scores[2]); // 张三,86 printf("%s,%2f\n",(stus + 1) -> name,(stus + 1) -> scores[2]); // 李四,88 printf("%s,%2f\n",(*(stus + 1)).name,(*(stus + 1)).scores[2]); // 李四,88
小贴士:
结构体是自定义数据类型,它是数据类型,用法类似于基本类型的int;
结构体数组它是存放结构体对象的数组,类似于int数组存放int数据;
基本类型数组怎么用,结构体数组就怎么用—>可以遍历,可以作为形式参数,也可以做指针等。
4. 结构体类型的使用案例
-
代码:
#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++) { // 通过结构体指针访问符访问结构体成员 printf("%s----%d----%s\n",w -> name,w -> age,w -> color); } }
(七)结构体类型求大小
1. 规则:
字节对齐(数据在内存中存储在其类型大小的整数倍上)
- 首先保证结构体中的成员存储在自身的对齐边界(类型大小的整数倍);
- 在满足1的条件下,最终大小要满足最大成员所占存储单元的整数倍;
2. 为什么要使用字节对齐:
节省内存,提高访问效率。
3. 在GNU标准中,可以在定义结构体时,指定对齐规则:
__attribute__((packed)); 结构体所占内存大小是所有成员所占内存大小之和
__attribute__((aligned(n))); 设置结构体占n个字节,如果n比默认值小,n不起作用;n必须是2的次方
4. 柔性数组:
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));
}
推导过程:
快速计算结构体大小网页链接:
https://blog.csdn.net/weixin_72357342/article/details/131135555
https://blog.csdn.net/x2528238270/article/details/120798606
三、共用体/联合体类型
(一)定义
使几个不同的变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单元,其他成员也是用该空间,它们的首地址是相同的。
1. 定义格式
union 共用体名称
{
数据类型 变量名;
数据类型 变量名;
...
};
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 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:类型重命名后数据类型
- size_t:类型重命名后的数据类型,
案例:
//类型重命名
#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;
}