结构体
什么是结构体
结构体是一种用户自定义的数据类型,可以组合多个相关值成为一个单一类型。它是由一批数据组合而成的结构型数据,结构体可以包含多个不同类型的字段,如基本数据类型、其他结构体、枚举类型等。在Rust中,结构体有着重要的作用,可以创建更复杂的数据结构,并定义它们的行为。结构体使用struct关键字定义,如`struct Person { name: String, age: i32, }`。结构体实例的创建需要使用构造函数。结构体字段可以是可变的,也可以是不可变的,默认情况下,结构体字段不可变。如需修改结构体字段,需要使用mut关键字。结构体也可以绑定方法,方法允许你以面向对象的方式操作结构体实例。结构体还提供了一种更新语法,使用..运算符(也称为点运算符或展开运算符)。结构体在不同的编程语言中都有类似的实现,如C++和Rust等。
结构体的解释
结构体(Structure)是C语言中一种组织多个变量的方式,它允许我们将不同的数据类型组合在一起,形成一个单一的实体。
结构体在内存中占用的空间等于其所有成员占用的空间之和。
在C语言中,结构体是通过关键字 `struct` 定义的
下面是一个简单的结构体定义的例子:
struct Person {
char name[50];
int age;
float height;
};
在这个例子中,我们定义了一个名为 `Person` 的结构体,它包含三个成员:`name`(字符数组,用于存储姓名),`age`(整型,用于存储年龄),和 `height`(浮点型,用于存储身高)。
要使用这个结构体,我们可以声明一个 `Person` 类型的变量:
struct Person p1;
然后,我们可以像访问普通变量一样访问 `p1` 的成员:
p1.name = "Alice";
p1.age = 30;
p1.height = 165.5;
我们也可以通过指针来访问结构体的成员,这可以提高代码的灵活性:
struct Person *p2;
p2 = &p1;
printf("%s\n", p2->name); // 输出: Alice
在上述代码中,`p2` 是一个指向 `Person` 结构体的指针。通过 `->` 操作符,我们可以访问它指向的结构体的成员。
结构体在实际编程中的应用非常广泛,例如,它可以用来表示现实世界中的对象或实体,如学生、员工等,每个实体都有其相应的属性。结构体也可以用来组织数据,使得数据管理更加方便和高效。
结构体的基本知识
结构是值的变量
也就是值的集合,这些集合称之为成员变量
数组一组相同元素的集合
结构体是一组不一定相同类型的元素的集合
复杂的对象不能通过简单的内置类型直接描述和表示 此时就有了结构体 来描述复杂类型
结构体的声明
但是需要知道的是,在函数体里面写的名字 ,如果要这个在其他函数进行使用,要么使用函数声明,要么把结构体放到最上面。
因为C语言的运行程序是从上往下进行运行的,但是进行函数的声明之后,就会先进行一次程序走一遍,再继续运行。
struct tag是名字//tag是名字 结构体是本身是不需要进行头文件的
{
}
大括号里面是成员 可以是多个 也可以是0个
最后是变量列表
举例 描述一个学生
一个汉字两个字符串
这个就是结构体类型
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
结构体的初始化
有了类型 才可以初始化
此时也就完成了结构体的初始化
这个是在main函数里面的初始化
当然这个不仅可以在main函数里面进行初始化 ,也可以在结构体下面的函数连进行初始话
初始化的时候就是 struct + 函数体名字(Stu) 在主函数里面 这个时候就再加上一个名字 arr等任意名字 就按照数组的方式可以进行初始化
也就是 可以是struct + Stu+si;
也可以是struct + Stu+arr;在主函数里面
在C语言中,结构体的初始化可以通过几种方式来完成,包括逐字段初始化、使用结构体数组、使用`malloc`分配内存后初始化,以及使用`memset`或`bzero`对内存块进行初始化。
1.
逐字段初始化
逐字段初始化是最直接的方法,直接为结构体的每个字段赋值例如:
struct Person {
char name[50];
int age;
float height;
};
struct Person p1 = {"张三", 30, 165.5f};//按照顺序 也就是 name=张三,age=年龄,heihgt=身高
2.
使用结构体数组
如果你有一系列结构体实例,你可以使用结构体数组来初始化它们:
struct Person students[3] = {
{"Bob", 22, 175.0f},
{"Charlie", 24, 180.0f},
{"David", 23, 172.0f}
};
3.
使用`malloc`分配内存后初始化
如果你需要在程序运行时动态分配结构体的内存,你可以使用`malloc`函数,然后手动为每个字段赋值:
struct Person *p2 = (struct Person *)malloc(sizeof(struct Person));
if (p2 != NULL) {
strcpy(p2->name, "Bob");
p2->age = 22;
p2->height = 175.0f;
}
4.
使用`memset`或`bzero`初始化
在某些情况下,你可能需要初始化整个结构体或结构体数组的全部字段,这时可以使用`memset`或`bzero`函数。`memset`将内存中的字节设置为指定的值,而`bzero`只是将内存中的字节设置为0(清零)。
struct Person p3;
memset(&p3, 0, sizeof(struct Person)); // 将p3的字段全部初始化为0
// 或者使用bzero
bzero(&p3, sizeof(struct Person)); // 将p3的字段全部初始化为0
请注意,使用`memset`或`bzero`时,要确保传递的地址是指向结构体的指针,而不是结构体本身。
这些是结构体初始化的常见方法。根据具体的需求和场景,你可以选择最适合你的初始化方式。
结构体的类型和变量
比喻
这个是图纸和房子的关系
结构体就是图示 主函数里面的初始化和框架也就是开始建立房子
类型和变量的关系(局部变量和全局变量)
这里也就是char name[100]所以占用的空间大一点
int age是整形 占据四个字节或者八个字节
char sex[5]又比int大一点
有了类型之后在主函数里面创建s1
这里的s2 s3 s4 就是结构体变量 这三个是函数外面创建的 也就是全局变量 和s1一样 但是s1 是局部变量 但是s2,s3,s4是结构体的全局变量
代码举例
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
char name[100];//这里看理解为名字 一个汉字占两个字符
int age;//这里可以设置成整形 因为这里是名字的意思
};
struct Stu s3[4] = { {"张三",12} ,{"lisi",23}, {"王五",2}, {"二狗",1} };
struct Stu s4[4] = { {"张三",12} ,{"lisi",23}, {"王五",2}, {"二狗",1} };
void test()
{
struct Stu s2[4] = { {"张三",12 } ,{"lisi",23}, {"王五",2}, {"二狗",1} };
}
int main()
{
//此时如果是在main函数里面 或者在test();函数里面创建的结构体变量,此时是局部变量 也就是cs1 s2
//如果是在结构体外部创建的变量 此时是全局变量 也就是 s3 s4
struct Stu s1[4] = { {"张三",12 } ,{"lisi",23}, {"王五",2}, {"二狗",1} };
return 0;
}
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
typedef
typedef的使用
在编程中,`typedef` 是一个关键字,用于为已存在的数据类型创建一个别名。这样做可以让代码更易于阅读和维护,特别是在处理复杂或者冗长的类型名称时。
例如,在 C 语言中,您可以使用 `typedef` 为标准数据类型如 `int` 创建别名:
typedef int INT32; // 将 int 类型重命名为 INT32
之后,您就可以使用 `INT32` 代替 `int` 来声明变量:
INT32 a, b; // 这里实际上是指 int 类型的变量
在不同的编程语言中,`typedef` 的作用和用法可能会有所不同,但核心概念是类似的,都是为了简化代码中对数据类型的引用。
typedef对变量重命名 但是需要知道 C语言里面 如果没有对结构体类型进行typedef,struct是不能省略的
也就是如果存在typedef 结构体后面的stu是一个类型
例如,如果我们有一个结构体:
struct Student {
char name[50];
int age;
float score;
};
我们可以使用 `typedef` 为这个结构体定义一个新的类型名称:
typedef struct Student stu;
这样,`stu` 就成为了 `struct Student` 的一个别名。之后,你就可以使用 `stu` 来声明这个结构体的变量:
stu s1;
这里,`s1` 是一个 `struct Student` 类型的变量,但使用了 `stu` 作为它的类型名称。
所以,如果你看到代码中有 `typedef struct Student stu;`,那么 `stu` 就是一个代表 `struct Student` 类型的别名。
或者
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
结构体的成员访问
结构体是可以互相包含的
可以在上一个 结构体里面 定义一个新的 结构体类型
上面我们知道在结构体外面创建的结构变量的全局变量
但是其实在这个外面创建的全局变量也是可以直接进行初始化的
在全局变量和局部变量里面的代码举例里面进行了举例
举例
在s1里面进行修改
初始化 并且给他一些数值
初始化 逐步初始化,
100 字符 空指针
struct S +名字s2 然后初始化 括号{} 和 数组的初始化有些类似
这里依旧是结构体的初始化的举例
选择初始化
.选择 中间 , 隔开
.的方式找到成员
这里需要记住
1 . 是 结构成员访问操作符 .
2 ->是结构成员访问操作符 .
重复一下
1 . 是 结构成员访问操作符 .
2 ->是结构成员访问操作符 .
这里打印的是这三个
如果是需要进行这个多个结构体的访问和打印 需要用上循环
struct的B sb进行初始化和成员访问
怎么放里面怎么拿出来
也就是如何打印出来 这里需要一一对应的方式打印出来,哪怕是进行循环打印,这个结构体里面的数值也要进行一一对应的方式进行打印
下面打印的是结构体B 初始化函数名sb
打印的时候就是sb.ch//意思就是sb下的struct ch
同理打印结构体第二个数值 也就是sb.s.a//意思就是struct B 结构体创建的sb下的,struct B里面的struct S s。
数值的传递
这里是直接把结构体传参过去了 传到set_stu函数里面 在后期会用得上
但是记得,在传参的时候需要带上struct+名字 +初始化的名字
进行拷贝
把张三拷贝到name里面去
strcpy是拷贝函数(记着就行)
包含头文件string.h
此时set_stu就成功的把函数拷贝过来了
语法形式记着就可以
strcmp的解释
在C语言中,`strcpy` 函数用于将一个字符串复制到另一个字符串中。它的原型定义在 `string.h` 头文件中。`strcpy` 函数的语法格式如下:
char *strcpy(char *dest, const char *source);
参数说明:
- `dest`:指向目标字符串的指针,即要复制字符串到的位置。
- `source`:指向源字符串的指针,即要复制的字符串。
`strcpy` 函数会复制 `source` 指向的字符串到 `dest` 指向的空间中,包括字符串结束符 `\0`。注意,目标字符串数组必须有足够的空间来容纳源字符串,否则可能会导致缓冲区溢出。
示例用法:
#include <stdio.h>
#include <string.h>
int main() {
char dest[20];
const char *source = "Hello, World!";
// 将源字符串复制到目标字符串中
strcpy(dest, source);
printf("复制后的目标字符串: %s\n", dest);
return 0;
}
在这个例子中,`strcpy` 函数将 "Hello, World!" 复制到 `dest` 数组中,然后程序打印出复制后的字符串。
回归主体
打印结构体s
t.什么什么 ,这个t其实就是创建的形参可以理解为
但是此时是错误
因为结构体是需要明确指向哪个地址的
画图解析原因
此时需要解决需要传址
在指针(1)指针篇章-(1)-CSDN博客里面解释了什么是传址 什么是传值
&s
这样就是正确的
这里指向的不是打印是age和name而是struct stu里面的name和age
这里也需要取地址名字s 然后指向这个位置 打印出来 这样打印的才是正确
升级版本 箭头 结构体成员
打印结构体的代码以及结构体传参
不包含循环的打印
此时也就是指针指向的是首元素的地址 所以打印的时候也就是打印的首元素的地址
要是想循环打印出内容的情况下 只需要在外部加个for循环
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
char name[100];//这里看理解为名字 一个汉字占两个字符
int age;//这里可以设置成整形 因为这里是名字的意思
};
struct Stu s3[4] = { {"张三",12} ,{"lisi",23}, {"王五",2}, {"二狗",1} };
struct Stu s4[4] = { {"张三",12} ,{"lisi",23}, {"王五",2}, {"二狗",1} };
void test(struct Stu *ps)//这里调用的是main函数里面的结构体进行打印
{
printf("%s %d\n", ps->name, ps->age);
}
int main()
{
//此时如果是在main函数里面 或者在test();函数里面创建的结构体变量,此时是局部变量 也就是cs1 s2
//如果是在结构体外部创建的变量 此时是全局变量 也就是 s3 s4
struct Stu s1[4] = { {"张三",12 } ,{"lisi",23}, {"王五",2}, {"二狗",1} };
test(&s1);
struct Stu s2[4] = { {"zhangsan",12 } ,{"lisi",23}, {"王五",2}, {"二狗",1} };
test(&s2);
return 0;
}
循环打印出结构体
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
char name[100];//这里看理解为名字 一个汉字占两个字符
int age;//这里可以设置成整形 因为这里是名字的意思
};
struct Stu s3[4] = { {"张三",12} ,{"lisi",23}, {"王五",2}, {"二狗",1} };
struct Stu s4[4] = { {"张三",12} ,{"lisi",23}, {"王五",2}, {"二狗",1} };
void test(struct Stu *ps)//这里调用的是main函数里面的结构体进行打印
{
printf("%s %d\n", ps->name, ps->age);
}
int main()
{
//此时如果是在main函数里面 或者在test();函数里面创建的结构体变量,此时是局部变量 也就是cs1 s2
//如果是在结构体外部创建的变量 此时是全局变量 也就是 s3 s4
struct Stu s1[4] = { {"张三",12 } ,{"lisi",23}, {"王五",2}, {"二狗",1} };
int sz1 = sizeof(s1) / sizeof(s1[0]);
for (int i = 0; i < sz1; i++)
{
test(&s1[i]);
}
printf("\n");
struct Stu s2[4] = { {"zhangsan",12 } ,{"lisi",23}, {"王五",2}, {"二狗",1} };
int sz2 = sizeof(s2) / sizeof(s2[0]);
for (int i = 0; i < sz2; i++)
{
test(&s2[i]);
}
return 0;
}
————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
结构体传参
传值
当结构体作为函数参数时,如果使用传值方式,那么函数将接收一个结构体变量的副本。这意味着在函数内部对结构体的修改不会影响到原始的结构体变量。因为在函数调用时,会创建一个结构体的副本并传递给函数,函数操作的是这个副本,而不是原始数据。
示例:
struct Student {
char name[50];
int age;
};
void printStudent(struct Student s) {
printf("Name: %s, Age: %d\n", s.name, s.age);
}
int main() {
struct Student student = {"Alice", 20};
printStudent(student); // 调用时传递的是student的副本
return 0;
}
传址
当使用传址方式时,函数接收的是结构体变量的地址。这意味着在函数内部对结构体的修改会影响到原始的结构体变量,因为函数操作的是存储在原始地址中的数据。
示例:
struct Student {
char name[50];
int age;
};
void modifyStudent(struct Student *s) {
strcpy(s->name, "Bob"); // 修改的是传入的结构体变量
s->age = 21;
}
int main() {
struct Student student = {"Alice", 20};
modifyStudent(&student); // 传递的是student的地址
printf("Name: %s, Age: %d\n", student.name, student.age); // 输出已修改的值
return 0;
}
总结
在实际编程中,通常根据是否需要修改原始数据来选择使用传值还是传址。如果函数需要修改结构体数据,或者结构体较大,为了节省内存和提高效率,通常使用传址方式。如果函数只是读取结构体数据,而不进行修改,使用传值方式即可。
原因函数传参的时候 参数压栈 参数过大的时候 压栈就过大 不仅浪费空间 而且浪费时间
如果直接传递地址过去 无非就是四个或者八个字节 只要有一个指针大小的空间就够了