自学内容网 自学内容网

结构体详解

结构体

什么是结构体

结构体是一种用户自定义的数据类型,可以组合多个相关值成为一个单一类型。它是由一批数据组合而成的结构型数据,结构体可以包含多个不同类型的字段,如基本数据类型、其他结构体、枚举类型等。在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;
   }

总结

 在实际编程中,通常根据是否需要修改原始数据来选择使用传值还是传址。如果函数需要修改结构体数据,或者结构体较大,为了节省内存和提高效率,通常使用传址方式。如果函数只是读取结构体数据,而不进行修改,使用传值方式即可。

原因函数传参的时候 参数压栈 参数过大的时候 压栈就过大 不仅浪费空间 而且浪费时间

如果直接传递地址过去 无非就是四个或者八个字节 只要有一个指针大小的空间就够了


原文地址:https://blog.csdn.net/Jason_from_China/article/details/136477956

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!