说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!
结构体
1) 概述
数组:描述一组具有相同类型数据的有序集合,用于处理大量相同类型的数据运算。
结构体:将多个相同或不同类型的数据存在在一块连续的内存空间中。
有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理。
C
语言中给出了另一种构造数据类型——结构体。
2) 结构体变量的定义和初始化
定义结构体变量的方式:
- 先声明结构体类型再定义变量名
- 在声明类型的同时定义变量
- 直接定义结构体类型变量(无类型名)
结构体类型和结构体变量关系:
- 结构体类型:指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元。
- 结构体变量:系统根据结构体类型(内部成员状况)为之分配空间。
示例1:定义一个结构体数据类型
// 定义一个结构体数据类型
// 关键字 struct 代表这个是一个结构体类型,
// stu 是这个结构的名字
// 整个结构体的类型是 struct stu
// 结构体类型struct stu {}中是结构体的成员,一个有3个成员,每个成员的类型可以是
// 任意的类型
// 定义结构体类型时,{}后面记得加分号
// 注意定义结构struct stu,它只是一个类型,一个模板,没有空间,不可以给结构体成员赋值
struct stu
{
int id;
int age;
char name[128];
};
int main()
{
return 0;
}
示例2:定义结构体变量
// 第一种常用定义方式
struct stu
{
int id;
int age;
char name[128];
}a; //定义类型时,同时定义了一个结构体变量,相当于struct stu a;
struct stu2
{
int id;
int age;
char name[128];
}a,b; //定义类型时,同时定义了两个结构体变量,相当于struct stu a,b;
// 第二种常用定义方式(用的最多)
struct stu
{
int id;
int age;
char name[128];
};
struct stu c;
int main()
{
return 0;
}
// 第三种常用定义方式(用的很少)
struct
{
int id;
int age;
char name[128];
}a;
示例3:结构体变量的初始化
//结构体类型的定义
struct stu
{
int id;
int age;
char name[128];
};
//先定义类型,再定义变量(常用)
struct stu s1 = { 1, 18, "cdtaogang" };
//定义类型同时定义变量
struct stu2
{
int id;
int age;
char name[128];
}s2 = { .age=22 }; // 给部分成员初始化,其他成员内容为0
struct
{
int id;
int age;
char name[128];
}s3 = { "yuri", 25 };
3) 结构体成员的使用
示例1:通过结构体变量操作结构体成员,使用点域.
操作
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
// 定义一个结构体数据类型
struct stu
{
int id;
int age;
char name[128];
}a; //定义类型时,同时定义了一个结构体变量,相当于struct stu a;
struct stu b;
int main()
{
//struct stu c = {1,20,"cdtaogang"};
//struct stu c = { .age=20 };//给部分成员初始化,其他成员内容为0
struct stu d;
//如何给结构体成员赋值(如何操作结构体成员)
//通过结构体变量操作结构体成员,使用点域.操作
d.id = 2;
d.age = 20;
//d.name = "cdtaogang"; // error 数组名是一个常量,不能被赋值
strcpy(d.name , "cdtaogang");
printf("%d %d %s\n",d.id,d.age,d.name);
return 0;
}
示例2:通过结构体的地址操作结构体成员,使用指向->
操作
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
// 定义一个结构体数据类型
struct stu
{
int id;
int age;
char name[128];
}a; //定义类型时,同时定义了一个结构体变量,相当于struct stu a;
struct stu b;
int main()
{
struct stu d;
// 通过结构体的地址操作结构体成员,使用->
(&d)->id = 3;
(&d)->age = 21;
strcpy((&d)->name, "cdtaogang");
printf("%d %d %s\n", (&d)->id, (&d)->age, (&d)->name);
return 0;
}
4) 结构体数组
结构体数组:是一个数组,数组的每一个元素都是结构体
示例:定义一个结构体数组
#include <stdio.h>
struct stu
{
int id;
int age;
char name[128];
};
int main()
{
// 定义一个结构体数组,结构体数组有5个元素,每个元素是struct stu类型
struct stu data[5] = { {1,20,"laowang"},{2,21,"lihao"},{3,22,"cdtaogang"},{4,23,"zhaoqiang"},{5,24,"wangwu"} };
for (int i = 0; i < sizeof(data)/sizeof(data[0]); i++)
{
printf("%d %d %s\n", data[i].id, data[i].age, data[i].name);
}
return 0;
}
输出结果
1 20 laowang
2 21 lihao
3 22 cdtaogang
4 23 zhaoqiang
5 24 wangwu
案例:统计学生成绩
#include <stdio.h>
//统计学生成绩
struct stu2
{
int num;
char name[20];
char sex;
float score;
};
int main()
{
//定义一个含有5个元素的结构体数组并将其初始化
struct stu2 boy[5] = {
{ 101, "Li ping", 'M', 45 },
{ 102, "Zhang ping", 'M', 62.5 },
{ 103, "He fang", 'F', 92.5 },
{ 104, "Cheng ling", 'F', 87 },
{ 105, "Wang ming", 'M', 58 } };
int i = 0;
int c = 0;
float ave, s = 0;
for (i = 0; i < 5; i++)
{
s += boy[i].score; //计算总分
if (boy[i].score < 60)
{
c += 1; //统计不及格人的分数
}
}
printf("s=%f\n", s);//打印总分数
ave = s / 5; //计算平均分数
printf("average=%f\ncount=%d\n\n", ave, c); //打印平均分与不及格人数
for (i = 0; i < 5; i++)
{
printf(" name=%s, score=%f\n", boy[i].name, boy[i].score);
// printf(" name=%s, score=%f\n", (boy+i)->name, (boy+i)->score);
}
return 0;
}
5) 结构体套结构体
示例:结构体套结构体
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
struct stu
{
int id;
int age;
char name[128];
};
struct cls_stu
{
/*int id;
int age;
char name[128];*/
struct stu s;
char subject[128];
};
int main()
{
struct cls_stu cs;
cs.s.id = 1;
cs.s.age = 20;
strcpy(cs.s.name, "cdtaogang");
strcpy(cs.subject, "c/c++");
printf("id=%d, age=%d, name=%s, subject=%s", cs.s.id, cs.s.age, cs.s.name, cs.subject);
return 0;
}
输出结果
id=1, age=20, name=cdtaogang, subject=c/c++
6) 结构体赋值
示例1:第一种方法
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
struct stu
{
int id;
int age;
char name[128];
};
void memcpy_str(struct stu *x, struct stu* y)
{
memcpy(x, y, sizeof(*x));
}
int main()
{
struct stu a;
struct stu b = { 1, 22, "cdtaogang" };
//memcpy(&a, &b, sizeof(a));
memcpy_str(&a, &b); //将上面改为函数调用实现形参改变实参的值
printf("%d %d %s\n", a.id, a.age, a.name);
return 0;
}
示例2:第二种方法
int main()
{
struct stu a;
struct stu b = { 1, 22, "cdtaogang" };
// 第一种方法
//memcpy(&a, &b, sizeof(a));
//memcpy_str(&a, &b);
// 第二种方法
a.id = b.id;
a.age = b.age;
strcpy(a.name, b.name);
printf("%d %d %s\n", a.id, a.age, a.name);
return 0;
}
示例3:第三种方法
int main()
{
struct stu a;
struct stu b = { 1, 22, "cdtaogang" };
// 第一种方法
//memcpy(&a, &b, sizeof(a));
//memcpy_str(&a, &b);
// 第二种方法
/*a.id = b.id;
a.age = b.age;
strcpy(a.name, b.name);*/
// 第三种方法
// a = b 内核实现的原理就是memcpy(&a, &b, sizeof(a));
a = b; // 相同类型的变量是可以相互赋值
printf("%d %d %s\n", a.id, a.age, a.name);
return 0;
}
7) 结构体和指针*
7.1 指向普通结构体变量的指针
示例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct stu
{
int id;
int age;
char name[128];
};
int main()
{
//struct stu* p; // error 野指针 没有初始化,指向随机
// 第一种方式
struct stu s;
struct stu* p = &s;
(*p).id = 1;
(*p).age = 20;
strcpy((*p).name,"cdtaogang");
printf("(*p).id=%d, (*p).age=%d, (*p).name=%s", (*p).id, (*p).age, (*p).name);
return 0;
}
输出结果
(*p).id=1, (*p).age=20, (*p).name=cdtaogang
7.2 堆区结构体变量
示例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct stu
{
int id;
int age;
char name[128];
};
int main()
{
//struct stu* p; // error 野指针 没有初始化,指向随机
// 第一种方式
/*struct stu s;
struct stu* p = &s;*/
// 第二种方式
struct stu* p = malloc(sizeof(struct stu));
p->id = 1;
p->age = 20;
strcpy(p->name, "cdtaogang");
printf("p->id=%d, p->age=%d, p->name%s\n", p->id, p->age, p->name);
printf("(*p).id=%d, (*p).age=%d, (*p).name=%s", (*p).id, (*p).age, (*p).name);
free(p);
p = NULL;
return 0;
}
输出结果
p->id=1, p->age=20, p->namecdtaogang
(*p).id=1, (*p).age=20, (*p).name=cdtaogang
7.3 结构体套一级指针
示例1:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct std
{
int age;
char* name; //一级指针
};
int main()
{
struct std* p = (struct std*)malloc(sizeof(struct std));
p->age = 18;
p->name = "cdtaogang";
printf("%d %s\n", p->age, p->name);
return 0;
}
输出结果
18 cdtaogang
示例2:
struct std
{
int age;
char* name; //一级指针
};
int main()
{
struct std* p = (struct std*)malloc(sizeof(struct std));
p->age = 18;
//p->name = "cdtaogang";
strcpy(p->name, "cdtaogang"); // error 野指针 p没有指向 相当于 char *name; strcpy(name, "cdtaogang")一个道理
printf("%d %s\n", p->age, p->name);
return 0;
}
示例3:
struct std
{
int age;
char* name; //一级指针
};
int main()
{
struct std* p = (struct std*)malloc(sizeof(struct std));
p->age = 18;
//p->name = "cdtaogang"; // ok
p->name = (char*)malloc(128);
strcpy(p->name, "cdtaogang"); // error 野指针 p没有指向
printf("%d %s\n", p->age, p->name);
return 0;
}
输出结果
18 cdtaogang
示例4:简化分析p->name = "cdtaogang";
和strcpy(p->name, "cdtaogang");
两种方式,这里的name
在以下示例以p
来表示
// 正确示例
int main()
{
char* p;
p = "cdtaogang";
}
// 错误示例
int main()
{
int* p;
strcpy(p, "cdtaogang");// 将cdtaogang的内容拷贝到p所指向的空间(地址)
}
// 正确示例
int main()
{
char* p;
p = (int*)malloc(128);
strcpy(p, "cdtaogang");// 将cdtaogang的内容拷贝到p所指向的空间(地址)
}
示例5:结构体套结构体一级指针
struct s
{
int a;
};
struct std
{
int age;
char* name; //一级指针
struct s* num; // 指针需要申请开辟空间
//struct s num2; // 变量不需要申请开辟空间
};
int main()
{
struct std* p = (struct std*)malloc(sizeof(struct std));
p->age = 20;
p->name = (char*)malloc(128);
strcpy(p->name, "cdtaogang");
//p->num2.a = 100; // num2是变量用.不能用->
p->num->a = 200; // error num是野指针,不能直接赋值给num所指向的空间
printf("%d %s\n", p->age, p->name);
return 0;
}
解决方法一样申请开辟空间即可
struct s
{
int a;
};
struct std
{
int age;
char* name; //一级指针
struct s* num; // 指针需要申请开辟空间
//struct s num2; // 变量不需要申请开辟空间
};
int main()
{
struct std* p = (struct std*)malloc(sizeof(struct std));
p->age = 20;
p->name = (char*)malloc(128);
strcpy(p->name, "cdtaogang");
//p->num2.a = 100; // num2是变量用.不能用->
//p->num->a = 200; // error num是野指针,不能直接赋值给num所指向的空间
p->num = (struct s*)malloc(sizeof(struct s));
p->num->a = 200;
printf("%d %s %d\n", p->age, p->name, p->num->a);
return 0;
}
释放空间,从里往外释放,有多少个malloc
就释放free
多少个(p的空间是定义的时候就有的 ,而p指向的空间是malloc之后才有的,即才有了age、name、num的空间而name和num指向的空间是malloc之后才有的)
int main()
{
struct std* p = (struct std*)malloc(sizeof(struct std));
p->age = 20;
p->name = (char*)malloc(128);
strcpy(p->name, "cdtaogang");
//p->num2.a = 100; // num2是变量用.不能用->
//p->num->a = 200; // error num是野指针,不能直接赋值给num所指向的空间
p->num = (struct s*)malloc(sizeof(struct s));
p->num->a = 200;
printf("%d %s %d\n", p->age, p->name, p->num->a);
free(p->name);
free(p->num);
free(p);
return 0;
}
8) 结构体做函数参数*
8.1 结构体普通变量做函数参数
示例:结构体普通变量做函数参数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include <string.h>
//结构体类型的定义
struct stu
{
char name[50];
int age;
};
//函数参数为结构体普通变量
void set_stu(struct stu tmp)
{
strcpy(tmp.name, "cdtaogang");
tmp.age = 18;
printf("tmp.name = [%s], tmp.age = [%d]\n", tmp.name, tmp.age);
}
int main()
{
struct stu s = { 0 };
set_stu(s); //值传递
printf("s.name = [%s], s.age = [%d]\n", s.name, s.age);
return 0;
输出结果
tmp.name = [cdtaogang], tmp.age = [18]
s.name = [], s.age = [0]
8.2 结构体指针变量做函数参数
示例:结构体指针变量做函数参数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include <string.h>
//结构体类型的定义
struct stu
{
char name[50];
int age;
};
//函数参数为结构体指针变量
void set_stu_pro(struct stu* tmp)
{
strcpy(tmp->name, "cdtaogang");
tmp->age = 18;
printf("tmp->name = [%s], tmp->age = [%d]\n", tmp->name, tmp->age);
}
int main()
{
struct stu s = { 0 };
set_stu_pro(&s); //地址传递
printf("s.name = [%s], s.age = [%d]\n", s.name, s.age);
return 0;
}
输出结果
tmp->name = [cdtaogang], tmp->age = [18]
s.name = [cdtaogang], s.age = [18]
8.3 结构体数组名做函数参数
示例:结构体数组名做函数参数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include <string.h>
struct stu
{
int id;
char name[128];
};
//void set_tmp(struct stu tmp[5], int n)
void set_tmp(struct stu *p, int n)
{
for (int i = 0; i < n; i++)
{
// 第一种方式用点域
(*(p + i)).id = 22;
p[i].id = 22; // 上面的简写
// 第二种方式用指向
//(p + i)->id = 22;
char buf[128];
sprintf(buf, "%s%d", "cdtaoang", i+1);
strcpy(p[i].name, buf);
printf("p[%d].id = %d, p[%d].name = %s\n", i, p[i].id, i, p[i].name);
}
}
int main()
{
struct stu tmp[5] = { 0 };
int n = sizeof(tmp) / sizeof(tmp[0]);
set_tmp(tmp, n); // tmp = &tmp[0]
printf("\n");
for (int i = 0; i < n; i++)
{
printf("tmp[%d].id = %d, tmp[%d].name = %s\n", i, tmp[i].id, i, tmp[i].name);
}
return 0;
}
8.4 const修饰结构体指针形参变量
示例:const
修饰结构体指针形参变量
#include<stdio.h>
struct stu
{
int id;
char name[128];
};
int main()
{
struct stu a;
struct stu b;
struct stu const *p = &a;
// p->id = 10; // error const修饰的是*,不能通过指针p去修改p指向的那块空间里面内容(指向)
struct stu * const p2 = &a;
p2->id = 20; // ok
//p2 = &b; // error const修饰的是指针变量p, 不能修改变量p本身的内容(指向)
return 0;
}
共用体(联合体)
- 联合union是一个能在同一个存储空间存储不同类型数据的类型;
- 联合体所占的内存长度等于其最长成员的长度倍数,也有叫做共用体;
- 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用;
- 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖;
- 共用体变量的地址和它的各成员的地址都是同一地址。
1) 共用体的定义和初始化
示例1:定义一个共用体数据类型
union test
{
char a;
short b;
int c;
};
示例2:定义共用体变量,跟结构体定义方式一样
union test
{
char a;
short b;
int c;
}tmp;
union test
{
char a;
short b;
int c;
};
union test tmp;
union
{
char a;
short b;
int c;
}tmp;
示例3:共用体变量的初始化(共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖)
#include <stdio.h>
union test
{
char a;
short b;
int c;
};
int main()
{
union test tmp;
tmp.a = 0x01;
tmp.b = 0x0a0b;
tmp.c = 0x01020304;
printf("tmp.a = %x\n", tmp.a); // 04
printf("tmp.b = %x\n", tmp.b); // 0304
printf("tmp.c = %x\n", tmp.c); // 01020304
return 0;
}
#include <stdio.h>
union test
{
char a;
short b;
int c;
};
int main()
{
union test tmp;
tmp.a = 0x01;
tmp.c = 0x01020304;
tmp.b = 0x0a0b;
printf("tmp.a = %x\n", tmp.a); // 0b
printf("tmp.b = %x\n", tmp.b); // 0a0b
printf("tmp.c = %x\n", tmp.c); // 01020a0b
return 0;
}
示例4:共用体的大小
printf("sizeof(tmp)=%d", sizeof(tmp)); //sizeof(tmp)=4 即 sizeof(int)=4 联合体所占的内存长度等于其最长成员的长度倍数
2) 共用体判断大小端
小端: 低位存低地址,高位存高地址(大型服务器)
大端: 低位存高地址,高位存低地址(小型计算机)
示例:共用体判断大小端
#include <stdio.h>
union test
{
short b;
char buf[2];
};
int main()
{
union test tmp;
tmp.b = 0x0102;
if (tmp.buf[0] == 0x01)
{
printf("大端\n");
}
else
{
printf("小端\n");
}
return 0;
}
输出结果
小端
枚举
枚举: 将枚举类型的变量的值一一列举出来,变量的值只限于列举出来的值的范围内。
枚举类型定义:
enum 枚举名
{
枚举值表
};
- 在枚举值表中应列出所有可用值,也称为枚举元素。
- 枚举值是常量,不能在程序中用赋值语句再对它赋值。
- 举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2 …
1) 枚举定义
示例1:为了避免需要多次定义宏,C语言提供了枚举
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define Mon 1
#define Tue 2
#define Wed 3
int main()
{
int num;
scanf("%d", &num);
if (Mon == num)
{
printf("游泳\n");
}
else if (Tue == num)
{
printf("跑步\n");
}
else if (Wed == num)
{
printf("游戏\n");
}
return 0;
}
// 枚举定义
// 默认枚举里面的值从0开始
enum week
{
Mon,
Tue,
Wed
};
int main()
{
printf("%d %d %d", Mon, Tue, Wed); // 0 1 2
return 0;
}
2) 枚举赋值
示例2:枚举赋值
enum week
{
Mon=1,
Tue,
Wed
};
int main()
{
printf("%d %d %d", Mon, Tue, Wed); // 1 2 3
return 0;
}
enum week
{
Mon,
Tue=5,
Wed
};
int main()
{
printf("%d %d %d", Mon, Tue, Wed); // 0 5 6
return 0;
}
3) 定义枚举变量
示例1:先定义枚举类型,再定义枚举变量
enum week
{
Mon,
Tue=5,
Wed
};
int main()
{
enum week day = Wed; //给枚举变量 day 赋值,值就是Wed枚举元素
printf("%d %d %d", Mon, Tue, Wed); // 0 5 6
return 0;
}
示例2:定义枚举类型的同时定义枚举变量
enum week
{
Mon,
Tue=5,
Wed
}day;// 表示 定义了一个枚举类型 enum week,同时定义了一个变量 day(类型是 enum week)
int main()
{
day = Wed; //给枚举变量 day 赋值,值就是某个枚举元素
printf("%d %d %d", Mon, Tue, Wed); // 0 5 6
return 0;
}
示例3:省略枚举名称,直接定义枚举变量
```c
enum
{
Mon,
Tue=5,
Wed
}day;//这样使用枚举,该枚举类型只能使用一次
案例:使用枚举实现#define false 0 #define true 1
#include <stdio.h>
enum weekday
{
sun = 2, mon, tue, wed, thu, fri, sat
} ;
enum bool
{
flase, true
};
int main()
{
// 先定义枚举类型,再定义枚举变量
enum weekday a, b, c;
a = sun;
b = mon;
c = tue;
printf("%d,%d,%d\n", a, b, c);
enum bool flag;
flag = true;
if (flag == 1)
{
printf("flag为真\n");
}
return 0;
}
typedef
typedef
为C
语言的关键字,作用是为一种数据类型(基本类型或自定义数据类型)定义一个新名字,不能创建新类型。
- 与
#define
不同,typedef
仅限于数据类型,而不是能是表达式或具体的值 #define
发生在预处理,typedef
发生在编译阶段
示例1:使用typedef
起别名
#include <stdio.h>
typedef int u32;
struct stu
{
int id;
int age;
};
typedef struct stu ST;
int main()
{
int a = 10;
u32 b = 10;
ST tmp;
tmp.id = 2;
tmp.age = 20;
printf("%d %d\n", sizeof(a), sizeof(b));
printf("%d %d\n", tmp.id, tmp.age);
return 0;
}
输出结果
4 4
2 20
示例2:宏与typedef
起别名
#include <stdio.h>
#define CHAR char
typedef char CHAR32;
int main()
{
CHAR a;
CHAR32 b;
a = 10;
b = 20;
printf("%d %d\n", sizeof(a), sizeof(b));
return 0;
}
输出结果
1 1
示例3:使用宏起别名存在的问题和风险
#include <stdio.h>
#define CHAR char*
typedef char* CHAR32;
int main()
{
CHAR x, y; // char *x,y;
CHAR32 j, k; // char *j,*k;
printf("%d %d\n", sizeof(x), sizeof(y));
printf("%d %d\n", sizeof(j), sizeof(k));
return 0;
}
输出结果
4 1
4 4
在实际编程中,取别名广泛应用,如下图所示查看VS
编译器中size_t
类型其实就是int
类型