构造类型:不是基本类型的数据结构也不是指针,它是 若干个相同或不同类型的数据构成的集合
。
有时我们需要将不同类型的数据组合成一个有机的整体,以便于引用。
假如一个人姓名/性别/年龄属性
int name[50];
int sex[10];
int age;
结构体可以将以上内容组成一个整体。
结构体 是一种构造类型的数据结构,是 一种 或 多种 基本类型或构造类型的数据的集合。
关键字:struct
语法1:推荐使用
struct 结构体名称
{
成员(一个结构体中可以有多个成员)
};
struct 结构体名称 变量名;
示例:
#include
struct stu
{
char namr[50];
char sex[10];
int age;
};
void fun01()
{
//定义结构体变量语法
struct stu p1;
//一个结构体可以定义多个结构体变量
struct stu p2;
}
int main(int argc, char const *argv[])
{
fun01();
return 0;
}
注意:
语法2:定义结构体时,顺便定义一个结构体变量
struct 结构体名称
{
成员(一个结构体中可以有多个成员)
}变量名;
示例:
struct stu
{
char namr[50];
char sex[10];
int age;
} s1;
void fun01()
{
struct stu s2;
}
语法3:一个结构体只定义一个结构体变量
struct
{
成员(一个结构体中可以有多个成员)
}变量名;
注意:
- 以后没法再定义这个结构体类型的数据了,因为没有类型名
示例:
struct
{
char namr[50];
char sex[10];
int age;
}s1;
void fun01()
{
/*因为结构体变量定义的语法为:
struct 结构体名称 结构体变量名;
但是在定义该结构体时,没有说明结构体名称,所以不能再定义结构体变量了
后面只能使用s1
*/
}
语法:
struct 结构体名称 结构体变量名 = {值1, 值2, 值3...};
注意:
- 定义结构体与初始化不能分开
- 值的顺序就是结构体成员的顺序
获取结构体成员
结构体变量名.成员变量
赋值
普通类型
结构体变量名.成员变量名 = 值;
字符数组
strcpy(结构体变量名.成员变量名,值);
整体赋值
结构体变量名1 = 结构体变量名2;
使用memcpy
内存拷贝
memcpy(目标结构体变量地址, 原结构体变量地址, 长度); //注意看下面示例,取地址
示例:
struct stu
{
char name[50];
char sex[10];
int age;
};
void fun01()
{
//定义结构体变量
struct stu s1 = {"张三", "男", 18};
struct stu s2;
//方式2
s2 = s1; //将结构体变量s1成员的值 逐个 赋值给s2的成员
struct stu s3;
//方式3:内存拷贝
memcpy(&s3, &s1, sizeof(struct stu));
printf("s1.name=%s\n", s1.name); //s1.name=张三
printf("s1.sex=%s\n", s1.sex); //s1.sex=男
printf("s1.age=%d\n", s1.age); //s1.age=18
printf("s2.name=%s\n",s2.name);
printf("s2.sex=%s\n",s2.sex);
printf("s2.age=%d\n",s2.age);
printf("s3.name=%s\n",s3.name);
printf("s3.sex=%s\n",s3.sex);
printf("s3.age=%d\n",s3.age);
s1.age = 19;
s2.age = 29;
s3.age = 39;
printf("s1.name=%s\n", s1.name); //s1.name=张三
printf("s1.sex=%s\n", s1.sex); //s1.sex=男
printf("s1.age=%d\n", s1.age); //s1.age=19
printf("s2.name=%s\n",s2.name);
printf("s2.sex=%s\n",s2.sex);
printf("s2.age=%d\n",s2.age); //s2.age=29
printf("s3.name=%s\n",s3.name);
printf("s3.sex=%s\n",s3.sex);
printf("s3.age=%d\n",s3.age); //s3.age=39
}
示例2:注意在录入时,数组名就是数组的首地址,所以不用取地址,但是 普通类型记得取地址。
#include
#include
#include
struct person
{
char name[50];
char sex[10];
char color[10];
int age;
};
void fun04()
{
struct person p1 = {"小明","男","黑",18};
//让键盘录入一个人的信息
struct person p2;
scanf("%s %s %s %d",p2.name,p2.sex,p2.color,&p2.age);
//从文件中读取到小张信息如下
//小张 女 白 19
struct person p3;
strcpy(p3.name,"小张");
strcpy(p3.sex,"女");
strcpy(p3.color,"白");
p3.age = 19;
printf("%s %s %s %d\n",p1.name,p1.sex,p1.color,p1.age);
printf("%s %s %s %d\n",p2.name,p2.sex,p2.color,p2.age);
printf("%s %s %s %d\n",p3.name,p3.sex,p3.color,p3.age);
}
int main(int argc, char const *argv[])
{
fun04();
return 0;
}
作用:简化结构体
语法:
typedef struct 结构体名
{
成员
}别名;
示例:
#include
#include
typedef struct person
{
char name[50];
int age;
}Person;
int main(int argc, char const *argv[])
{
Person p1 = {"张三", 18};
Person p2;
strcpy(p2.name, "李四");
p2.age = 19;
printf("p1\t%s %d\n", p1.name, p1.age);
printf("p2\t%s %d\n", p2.name, p2.age);
return 0;
}
// p1 张三 18
// p2 李四 19
概念:
结构体
A中的成员
是结构体B的变量
#include
typedef struct data1
{
int num1;
}Data1;
typedef struct data2
{
int num2;
Data1 num3;
}Data2;
int main(int argc, char const *argv[])
{
Data2 d2 = {20, {200}};
printf("num2: %d\n", d2.num2); //num2: 20
printf("num3: %d\n", d2.num3.num1); //num3: 200
return 0;
}
概念:指向结构体变量的指针
语法:
struct 结构体名称 *指针变量名 = &结构体变量;
语法:
指针变量名->成员变量
示例1:
#include
#include
typedef struct person
{
char name[50];
int age;
}Person;
void fun01()
{
Person p = {"张三", 18};
//结构体指针变量
struct person *p1 = &p;
Person *p2 = &p;
//结构体变量操作其中的成员
//结构体变量名.成员名
p.age = 19;
//结构体指针操作其中成员
//结构体指针->成员名
p1->age = 29;
printf("p: %s %d\n", p.name, p.age); //p: 张三 29
printf("p1: %s %d\n", p1->name, p1->age); //p2: 张三 29
printf("p2: %s %d\n", p2->name, p2->age); //p3: 张三 29
strcpy(p.name, "李四");
printf("p: %s %d\n", p.name, p.age); //p: 李四 29
strcpy(p2->name, "王五");
printf("p: %s %d\n", p.name, p.age); //p: 王五 29
}
int main(int argc, char const *argv[])
{
fun01();
return 0;
}
示例:
#include
#include
#include
typedef struct person
{
char name[50];
int age;
}Person;
void fun02()
{
//堆区申请空间存储结构体变量
Person *p = (Person *)calloc(1,sizeof(Person));
if (p == NULL)
{
printf("空间开辟失败\n");
return;
}
strcpy(p->name, "陈平安");
p->age = 999;
printf("p: %s %d\n", p->name, p->age); //p: 陈平安 999
if (p != NULL)
{
free(p);
p = NULL;
}
}
示例2:键盘录入,注意普通变量要取地址(&)
void fun03()
{
//堆区申请空间存储结构体变量
Person *p = (Person *)calloc(1,sizeof(Person));
if (p == NULL)
{
printf("空间开辟失败\n");
return;
}
scanf("%s %d", p->name, &(p->age));
printf("p: %s %d\n", p->name, p->age); //p: 崔瀺 1000
if (p != NULL)
{
free(p);
p = NULL;
}
}
语法:
struct 结构体名称 数组名[长度];
语法:
struct 结构体名称 数组名[长度] = {{成员值1, 成员值2,...},{成员值1, 成员值2,...},...};
示例1:
#include
struct student
{
char name[50];
int age;
};
void fun01()
{
struct student s1 = {"宁姚", 18};
//结构体数组:存储同一类型的结构体变量
struct student stus[3] = {s1, {"左右", 43}, {"崔东山", 16}};
for (int i = 0; i < 3; i++)
{
//struct student s = stus[i];
//printf("s2[%d]\t%s %d\n", i, s.name, s.age);
printf("s2[%d]\t%s %d\n", i, stus[i].name, stus[i].age);
}
}
// s2[0] 宁姚 18
// s2[1] 左右 43
// s2[2] 崔东山 16
int main(int argc, char const *argv[])
{
fun01();
return 0;
}
示例2:结构体数组与结构体指针变量结合,注意指针调用成员变量用 ->
。
void fun02()
{
struct student s1 = {"宁姚", 18};
struct student *p1 = &s1;
struct student s2 = {"裴钱", 14};
struct student *p2 = &s2;
struct student s3 = {"暖树", 16};
struct student *p3 = &s3;
//p这个指针数组存放的是地址
struct student *p[3] = {p1,p2,p3};
for (int i = 0; i < 3; i++)
{
struct student *ps = p[i];
// printf("s2[%d]\t%s %d\n", i, s.name, s.age);
printf("s2[%d]\t%s %d\n", i, ps->name, ps->age);
}
}
// s2[0] 宁姚 18
// s2[1] 裴钱 14
// s2[2] 暖树 16
struct person
{
char *name;
int age;
};
注意:
void fun01() { //1,不要操作结构体变量中未初始化指针 //定义结构体变量,但不初始化,此时其中的指针为野指针 // struct person p1; // printf("%s\n",p1.name); //定义结构体变量,使用memset清0,此时其中的指针为空指针 struct person p2; memset(&p2,0,sizeof(p2)); printf("%s\n",p2.name); }
#include
typedef struct person
{
char *name;
int age;
}Person;
int main(int argc, char const *argv[])
{
Person p1;
p1.name = "张三";
p1.age = 18;
printf("%s, %d\n", p1.name, p1.age); //张三, 18
return 0;
}
注意:
此时
结构体变量p1
中指针成员指向文字常量区
- 不能修改其指向地址中的值;
- 只能修改其指向的地址
#include
#include
typedef struct person
{
char *name;
int age;
}Person;
void fun02()
{
Person p1;
p1.name = (char *)calloc(1,sizeof(char));
strcpy(p1.name, "tom");
p1.age = 23;
printf("%s, %d\n", p1.name, p1.age);
p1.name[0] = 'H';
printf("%s, %d\n", p1.name, p1.age);
}
//tom, 23
//Hom, 23
注意:
指针成员指向堆区,那么就可以对其进行读写
步骤:
- 先在堆区初始化结构体指针变量
- 然后在堆区初始化成员指针变量
- …
- 释放成员指针变量堆区空间
- 释放结构体指针变量堆区空间
void fun03()
{
Person *p1 = (Person *)malloc(sizeof(Person));
p1->age = 18;
p1->name = (char *)malloc(20);
strcpy(p1->name, "张三");
printf("%s %d\n", p1->name, p1->age); //张三 18张三 18
//释放空间
if (p1->name != NULL)
{
free(p1->name);
p1->name = NULL;
}
if (p1 != NULL)
{
free(p1);
p1 = NULL;
}
}
步骤:
- 先在堆区初始化结构体指针数组
- 再在堆区初始化结构体指针变量
- 然后在堆区初始化成员指针变量
- …
- 释放成员指针变量堆区空间
- 释放结构体指针变量堆区空间
- 释放结构体指针数组堆区空间
示例:
#include
#include
#include
typedef struct student
{
char *name;
int age;
}Stu;
void fun02()
{
//存储结构体指针变量的数组的地址
Stu **ps = (Stu **)calloc(3,sizeof(Stu));
for(int i = 0; i < 3; i++)
{
Stu *p = (Stu *)calloc(1,sizeof(Stu));
p->name = (char *)malloc(20);
ps[i] = p;
printf("请输入第%d个学员信息\n",(i+1));
scanf("%s %d",p->name,&p->age);
}
for(int i = 0; i < 3; i++)
{
Stu *p = ps[i];
printf("%s %d\n",p->name,p->age);
//先释放成员变量,再释放指针,再释放最外边的数组
if (p->name != NULL)
{
free(p->name);
p->name = NULL;
}
if(p != NULL)
{
free(p);
p = NULL;
}
}
if(ps != NULL)
{
free(ps);
ps = NULL;
}
}
// 请输入第1个学员信息
// 张三 12
// 请输入第2个学员信息
// 李四 13
// 请输入第3个学员信息
// 王五 14
// 张三 12
// 李四 13
// 王五 14
int main(int argc, char const *argv[])
{
fun02();
return 0;
}
浅拷贝,拷贝的是地址,p1 和 p2 指向同一块地址,所以修改一个,另一个也会生改变
示例:
#include
#include
#include
typedef struct person
{
char *name; //指针变量 存储的地址编号
int age;
}Person;
void fun01()
{
char str[] = "tom"; //字符数组,在栈中,所以是指针常量,可改值不可改地址
char *strP = str; //指向str数组,所以是普通指针
Person p1 = {strP, 20};
Person p2 = p1; //全赋值,将p1中成员所有内容全部复制一份给p2
printf("p1.name=%s\n", p1.name);
printf("p2.name=%s\n", p2.name);
strcpy(p2.name, "jerry");
printf("p1.name=%s\n", p1.name);
printf("p2.name=%s\n", p2.name);
}
// p1.name=tom
// p2.name=tom
// p1.name=jerry
// p2.name=jerry
int main(int argc, char const *argv[])
{
fun01();
return 0;
}
示例2:
void fun03()
{
//浅拷贝:只拷贝地址不拷贝内容,当其中一个的指针变量指向的地址中的内容发生改变,其另外一个也将被影响
//如:将其中一个成员指针变量指向的地址释放后,另一个对应的成员指针变量的地址也将被使用
//所以在此释放另一个该成员指针变量指向的地址,将报错
//因为内存重复释放
Person *p1 = (Person *) calloc(1,sizeof(Person));
p1->name = (char *) malloc(20);
strcpy(p1->name,"tom");
p1->age = 19;
Person *p2 = (Person *) calloc(1,sizeof(Person));
p2->name = p1->name;//结构体变量全赋值
printf("p1.name %p\n",p1->name);
printf("p2.name %p\n",p2->name);
if (p1->name != NULL)
{
printf("p1 name\n");
free(p1->name);
p1->name = NULL;
}
if (p2->name != NULL)
{
printf("p2 name\n");
free(p2->name);
p2->name = NULL;
}
if (p1 != NULL)
{
free(p1);
p1 = NULL;
}
if (p2 != NULL)
{
free(p2);
p2 = NULL;
}
}
示例:
void fun02()
{
//深拷贝:拷贝内容不拷贝地址
Person *p1 = (Person *) calloc(1,sizeof(Person));
p1->name = (char *) malloc(20);
strcpy(p1->name,"tom");
p1->age = 19;
Person *p2 = (Person *) calloc(1,sizeof(Person));
p2->name = (char *) malloc(20);
strcpy(p2->name,p1->name);
p2->age = p1->age;
printf("p1\t%s %d\n",p1->name,p1->age);
printf("p2\t%s %d\n",p2->name,p2->age);
strcpy(p1->name,"jek");
printf("p1\t%s %d\n",p1->name,p1->age);
printf("p2\t%s %d\n",p2->name,p2->age);
if (p1->name != NULL)
{
free(p1->name);
p1->name = NULL;
}
if (p1 != NULL)
{
free(p1);
p1 = NULL;
}
if (p2->name != NULL)
{
free(p2->name);
p2->name = NULL;
}
if (p2 != NULL)
{
free(p2);
p2 = NULL;
}
}
// p1 tom 19
// p2 tom 19
// p1 jek 19
// p2 tom 19
注意:
深拷贝
和浅拷贝
都发生在 结构体有指针成员变量的时候,所以尽量不要再结构体中使用指针成员
#include
typedef struct data1
{
char a;
int c;
} Data1;
typedef struct data2
{
char a;
short b;
int c;
} Data2;
typedef struct data3
{
char a;
int c;
short b;
} Data3;
typedef struct data4
{
char a;
short b;
int c;
long d;
} Data4;
typedef struct data5
{
long d;
char a;
short b;
int c;
} Data5;
typedef struct data6
{
int c;
char s[17]; //数组会展开,以所属类型存储,这个就会以17个char存储,所以是24
} Data6;
typedef struct data7
{
int c;
char *s;
} Data7;
typedef struct data8
{
Data7 s;
int a;
} Data8;
typedef struct data9
{
char a;
int b;
} Data9;
int main(int argc, char const *argv[])
{
printf("data1的长度=%ld\n",sizeof(Data1)); //data1的长度=8
printf("data2的长度=%ld\n",sizeof(Data2)); //data2的长度=8
printf("data3的长度=%ld\n",sizeof(Data3)); //data3的长度=12
printf("data4的长度=%ld\n",sizeof(Data4)); //data4的长度=16
printf("data5的长度=%ld\n",sizeof(Data5)); //data5的长度=16
printf("data6的长度=%ld\n",sizeof(Data6)); //data6的长度=24
printf("data7的长度=%ld\n",sizeof(Data7)); //data7的长度=16
printf("data8的长度=%ld\n",sizeof(Data8)); //data8的长度=24
printf("data9的长度=%ld\n",sizeof(Data9)); //data9的长度=8
return 0;
}
不对齐
优点:占用空间小
缺点:速度慢
对齐
优点:速度快
缺点:占用空间大
- 结构体中的第一个成员在与结构体变量,偏移量为0的地址处
- 确定结构体的分配单位(结构体体中最大的基本类型长度)
- 其他成员的偏移量==成员自身长度的整数倍
- 收尾工作:结构体的总大小==分配单位的整数倍。
注意:按成员顺序从上向下
#pragma pack (value)时的指定对齐值value为1 2 4 8等
#include
typedef struct data1
{
char a;
int c;
} Data1;
//#pragma pack (value)
//value取值1,2,4,8
#pragma pack (1)
typedef struct data2
{
char a;
int c;
} Data2;
int main(int argc, char const *argv[])
{
printf("data1的长度=%ld\n",sizeof(Data1)); //data1的长度=8
printf("data2的长度=%ld\n",sizeof(Data2)); //data2的长度=5
return 0;
}
在结构体中,以位为单位的成员,被称之为位段(位域)。
unsigned int a:2;
表示 a只占int类型中2位二进制位。- a的类型还是 unsigned int,
abcd 叫相邻位域
,可以压缩,不能超过自身类型大小。- 示例中的结构体共占:2+6+4+4+32=48位
注意:
- 对位段成员不能取地址
- 给位域赋值 不要超过位域的大小 (会溢出)
示例:
#include
typedef struct data01
{
unsigned char a:2;
unsigned char b:2;
unsigned char c:2;
unsigned char d:2;
}Data01;
void fun01()
{
printf("%ld\n",sizeof(Data01));//长度1字节
}
int main(int argc, char const *argv[])
{
fun01();
return 0;
}
data.a=2;
- 赋值时,不要超出位段定义的范围;
- 如段成员a定义为
2位
,最大值为3
,即(11) 2- 所以
data.a =5
,就会取5的低两位
进行赋值 101
typedef struct data02
{
unsigned char a:2;
unsigned char b:2;
unsigned char c:2;
unsigned char :0;
unsigned char d:2;
}Data02;
void fun02()
{
printf("%ld\n",sizeof(Data02));//长度2
unsigned char :0;
表示另起一行。
typedef struct data03
{
unsigned char a:2;
unsigned char b:2;
unsigned char :2;
unsigned char d:2;
}Data03;
void fun03()
{
printf("%ld\n",sizeof(Data03));//长度1
unsigned char :2;
表示为空,无意义位段。
关键字:union
特点:所有成员公用一块空间
示例1:
#include
//a b c共用同一块空间(最大的成员类型决定)。
union Data09
{
char a;
short b;
int c;
};
void fun06()
{
union Data09 d01;
printf("%ld\n",sizeof(d01)); //长度4
}
int main(int argc, char const *argv[])
{
fun06();
return 0;
}
//4
虽然共用体成员在同一块空间,但是每个成员操作空间的大小 是由成员自身类型决定。
示例2:
#include
//a b c共用同一块空间(最大的成员类型决定)。
union Data09
{
char a;
short b;
int c;
};
void fun06()
{
union Data09 d01;
d01.a = 1;
d01.b = 2;
d01.c = 3;
printf("%d\n",d01.a+d01.b+d01.c);
}
int main(int argc, char const *argv[])
{
fun06();
return 0;
}
//9
//b覆盖a,c覆盖b,此时内存中存储的是c的值,3,
//d01.a=3
//d01.b=3
//d01.c=3
//所以最终结果是9
示例3:
#include
//a b c共用同一块空间(最大的成员类型决定)。
union Data09
{
char a;
short b;
int c;
};
void fun06()
{
union Data09 d01;
d01.a = 0x01;
d01.b = 0x0102;
d01.c = 0x01020304;
printf("%p\n",d01.a+d01.b+d01.c);
}
int main(int argc, char const *argv[])
{
fun06();
return 0;
}
//0x102060c
/*
同上覆盖原理:
d01.a = 0x00000004;
d01.b = 0x00000304;
d01.c = 0x01020304;
结果 0x0102060c
*/
关键字:enum
特点:限定其值的范围
概念:枚举就是将枚举变量可以赋的值一一列举出来
语法:
enum 枚举名称
{
值1,
值2,
值3,
...
值n
}
注意:
- 枚举中的值为符号常量,也是枚举变量可以赋的值
- 枚举列表中的值从0递增
示例1:
#include
enum Type
{
OK,
ERROR,
OVER
};
void fun08()
{
enum Type t1 = OK;
enum Type t2 = ERROR;
enum Type t3 = OVER;
printf("%d %d %d\n",OK,ERROR,OVER);
}
int main(int argc, char const *argv[])
{
fun08();
return 0;
}
// 0 1 2
示例2:
#include
enum Type2
{
NUM01,
NUM02 = 3,
NUM03
};
void fun09()
{
enum Type2 t1 = NUM01;
enum Type2 t2 = NUM02;
enum Type2 t3 = NUM03;
printf("%d %d %d\n",NUM01,NUM02,NUM03);
}
int main(int argc, char const *argv[])
{
fun09();
return 0;
}
// 0 3 4