数据类型分类
1.基本类型
- 整数型
- 短整型:short(2个字节)
- 整型(默认):int(4个字节)
- 长整型:long(8个字节)
- 长长整型:long long
- 浮点型
- 单精度:float(4个字节)
- 双精度:double(8个字节)
- 字符型:char(1个字节)
注意:整数型和字符型分有符号signed和无符号unsigned,默认是有符号,有符号可以省略关键字signed
2.指针类型
- 数据类型*:int*,char*,float*等
- void*:任意数据类型的指针
3.空类型
- void:没有返回值或没有形参(不能定义变量)
4.自定义类型/构造类型
- 结构体类型:struct
- 共用体类型(联合体):union
- 枚举类型:enum
结构体
- 定义:自定义数据类型的一种,关键字struct,结构体类型的变量可以存储多个不同类型的数据。
- 定义格式:
struct 结构体名
{
数据类型1 成员名称1;
数据类型2 成员名称2;
...
}
注意:结构体中定义的变量,我们称之为成员变量。
- 格式说明:
- 结构体名:合法的标识符,建议单词的首字母大写
- 数据类型:C语言支持的所有类型
- 成员名称:合法的标识符,就是变量的命名标准
- 数据类型n 成员名称n:类似于定义变量,定义了结构体中的成员
- 注意:
- 结构体在定义时,成员不能赋值
- 举例:
struct Cat
{
int age = 5;//错误,结构体定义时,成员不能赋值
double heught;//正确
}
- 常见的定义格式:
- 方式一:常规定义(只定义类型)——推荐
struct Student
{
int num;//学号
char name[20];//姓名
char sex;//性别
int age;//年龄
}
- 方式2:定义匿名结构体(常用于作为其他结构体的成员使用)
struct Dog
{
char *name;// 姓名
int age;// 年龄
struct // 此时这个结构体就是匿名
{
int year;// 年
int month;// 月
int day;// 日
} birthday;
}
- 注意:定义匿名结构体的同时必须定义结构体变量,否则编译报错,结构体可以作为另一个结 构体的成员。
- 总结:
1. 结构体可以定义在局部位置,也可以定义在全局位置;
2. 全局位置的结构体名和局部位置的结构体名可以相同,就近原则(和普通变量的定义
同理)
- 结构体类型的使用:
利用结构体类型定义变量,定义数组;结构体类型的使用与基本数据类型的使用类似。
结构体变量的定义
- 三种形式定义结构体变量:
结构体变量也称为结构体的实力。
- 第一种
① 先定义结构体
② 然后使用
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;
小贴士:
结构体数组名访问结构体成员:格式:结构体数组名 -> 成员名
结构体指针
定义:结构体类型的指针变量指向结构体变量或者数组的起始地址。
语法:struct 结构体名 *指针变量列表
举例:
struct Dog
{
char name[20];
int age;
}
struct Dog dog;
struct Dog *p = &dog;
-
结构体成员访问
- 结构体数组名访问结构体成员
- 格式:结构体数组名 -> 成员名
- 结构体成员访问符:
- 结构体数组名访问结构体成员
. :左侧是结构体变量(结构体对象、实例);也可以叫做结构体对象访问成员符;右侧是结构体成员
- ->:左侧是一个指针,也可以叫结构体指针访问成员符;右侧是结构体成员
- 访问结构体成员有两种类型,三种方式:
类型一:通过结构体对象访问成员
struct Stu
{
int id;
char name[20];
}stu;
//访问成员
stu.name;
类型二:通过结构体指针访问成员
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);
}
}
结构体类型求大小
- 规则:字节对齐(数据在内存中存储在其类型大小的整数倍上)
1. 首先保证结构体中的成员存储在自身的对齐边界(类型大小的整数倍);
2. 在满足
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));
}
- 快速计算结构体大小:
https://blog.csdn.net/weixin_72357342/article/details/131135555https://blog.csdn.net/x2528238270/article/details/120798606
共用体/联合体类型
定义:
使几个不同的变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单元,其他成员也是用该空间,他们的首地址是相同。
定义格式:
union 共用体名称
{
数据类型 变量名;
数据类型 变量名;
...
};
共用体的定义和结构体类型类似:
1. 可以有名字,也可以匿名;
2. 共用体在定义时也可以定义共用体变量;
3. 共用体在定义时也可以初始化成员;
4. 共用体也可以作为形参和返回值类型使用;
5. 共用体也可以定义共用体数组
...
也就是说,结构体的语法,共用体都支持。
注意:
- 共用体弊大于利,尽量少用,一般很少用;
- 共用体变量在某一时刻只能存一个数据,并且也只能取出一个数。
- 共用体和结构体都是自定义数据类型,用法类似于基本数据类型
- 共用体可以是共用体的成员,也可以是结构体的成员。
- 结构体可以是共用体的成员,也可以是共用体的成员。
案例:
/**
* 共用体
*/
#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
推导。
特点:
定义了一组常量,类似于定义了多个自定义常量(宏定义)
提供了代码的可读性(避免了魔术数字)
定义语法:
1. 定义枚举类型名以后就可以定义该枚举类型的变量
enum
枚举类型名 变量表;
2.在定义枚举类型的同时定义该枚举类型的变量。
enum 枚举类型名{ 枚举元素列表 }变量;
3.直接定义枚举类型变量。
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}
应用场景
1.
数据类型复杂(结构体,共用体,枚举,结构体指针)时使用
2.
为了跨平台兼容性,例如:
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;
}