- (꒪ꇴ꒪(꒪ꇴ꒪ ),hello我是祐言
- 博客主页:C语言基础,Linux基础,软件配置领域博主🌍
- 快上🚘,一起学习!
- 送给读者的一句鸡汤🤔:
- 集中起来的意志可以击穿顽石!
- 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
在上一个博客中我们简单了解和编写了一些关于结构体的知识点和代码,那么今天呢我们来学习一下如何使用好结构体,去深入了解结构体的应用,争取在以后的工作中不会被难倒。
一、结构体中可存放类型
首先我们来了解一下结构体到底可以存放哪些类型的数据呢,大致概括为基本数据类型和自定义的数据类型,下面就来归纳一下常见数据类型。
1.基本数据类型
- 整型:int、short、long、char
- 浮点型:float、double
- 布尔型:bool
struct student {
int age;
float gpa;
char name[20];
bool isEnrolled;
};
2.指针类型
- 指向基本数据类型的指针
- 指向其他结构体的指针
struct student {
int *pi;
char *pc;
float *pf;
int **ppi; //二级指针
int (*ap)[3]; //数组指针
struct A *ps; //结构体指针
int (*pfunc)(int, int); //函数指针。 注意:结构体成员中不可以定义函数,但是可以定义函数指针
};
3.枚举类型
- 定义一组可能的取值
enum color {
RED,
GREEN,
BLUE
};
struct car {
char brand[20];
enum color paintColor;
};
4.数组类型
- 存储多个相同或不同类型的元素
- 多维数组
struct point {
int coordinates[2];
int array[5];
int array2[2][5]; //二维数组
int *pia[3]; //指针数组
struct A sa[3]; //结构体数组
};
5.结构体类型
- 嵌套结构体
struct address {
char street[20];
char city[20];
char state[20];
};
struct person {
char name[20];
int age;
struct address homeAddress;
};
以上是一些常用的结构体定义的实例举例,其实除了这些结构体还可以自定义很多,留给往后继续学习掌握喔,这里就不过多介绍了,毕竟多了我也不会hhh。
二、值传递和地址传递
1.值传递(Pass by Value)
值传递是指将实参的值复制给形参,函数内部对形参的修改不会影响到实参本身的值。在值传递中,函数操作的是形参的副本,而不是实参本身。 特点:
(1)形参和实参是两个独立的变量,在内存中占用不同的空间。
(2)函数对形参的修改不会影响到实参的值。
(3)值传递适用于传递简单数据类型(如整数、浮点数、字符)或较小的结构体,可以保护实参的值不被函数修改。
2.地址传递(Pass by Address)
地址传递是指将实参的地址(指针)传递给形参,函数内部通过指针访问实参的值。在地址传递中,函数操作的是实参的内存地址,可以直接修改实参的值。 特点:
(1)形参和实参共享同一块内存空间,指向相同的数据;
(2) 函数对形参的修改会影响到实参的值;
(3)地址传递适用于传递较大的结构体或数组,可以避免复制大量的数据。
例程:
#include <stdio.h>
// 描述一个学生的基本信息
struct student
{
char name[20];
short int age;
int number;
};
// 值传递
void show_student(struct student stu)
{
stu.age = 30; // 不会影响实参的值
printf("%s, %hd, %d\n", stu.name, stu.age, stu.number);
}
// 地址传递, 更高效
// void show_student(struct student *stu)
// {
// stu->age = 30; // 会影响实参的值
// printf("%s, %hd, %d\n", stu->name, stu->age, stu->number);
// }
int main(int argc, char *argv[])
{
struct student zs = {.name = "zs", .age = 22, .number = 20001};
show_student(zs); // 传递的是zs这个变量的值{.name = "zs", .age = 22, .number = 20001};
// show_student(&zs); // 传递的是&zs, 把zs变量的地址传递到函数
printf("%s, %hd, %d\n", zs.name, zs.age, zs.number);
return 0;
}
选择值传递还是地址传递取决于所需的功能和效率。如果函数需要修改实参的值或者传递大量的数据,通常使用地址传递;如果函数仅需要读取实参的值或者传递简单的数据类型,可以使用值传递。
三、结构体访问
1.结构体数组之间不能直接进行赋值操作
话不多说直接上代码:
#include <stdio.h>
struct arr
{
int array[3];
};
void show_array(int *array, int n)
{
for (int i=0; i<n; i++)
{
printf("%d ", array[i]);
}
printf("\n");
}
int main(int argc, char *argv[])
{
// 错误的写法,被注释的
// int a[3] = {1, 2, 3};
// int b[3];
// b = a; // 错误
//正确的写法如下:
struct arr a;
a.array[0] = 1;
a.array[1] = 2;
a.array[2] = 3;
struct arr b;
b = a;
show_array(b.array, 3);
return 0;
}
当我们尝试将结构体变量 a
赋值给结构体变量 b
时,我们会遇到编译错误。这是因为结构体数组之间是不能直接进行赋值的。即使结构体中只包含一个数组成员,也不允许整体进行赋值操作。
如果我们想要复制一个结构体数组的值到另一个结构体数组,需要使用循环或其他方式逐个复制数组元素。
因此在这个例子中,我们可以通过编写一个函数 show_array
来展示如何遍历并打印结构体数组中的元素。这样我们可以看到通过逐个复制数组元素的方式,将结构体数组 a
的值复制给结构体数组 b
,并成功打印出复制后的结果。
这就不得不引出了结构体变量之间的赋值,但要满足两个条件:
- 类型一致:被赋值的结构体变量和赋值操作的结构体变量类型必须完全相同。
- 同一结构体定义:被赋值的结构体变量和赋值操作的结构体变量必须是同一个结构体定义中声明的变量。
例如:
#include <stdio.h>
struct
{
int a;
char b;
}s1, s2; //s1 s2是对于这个结构体访问的两个变量
struct
{
int a;
char b;
}s3, s4; //s3 s4是对于这个结构体访问的两个变量
int main(int argc, char *argv[])
{
s1.a = 100;
s1.b = 'A';
s2 = s1; //类型一致,是同一个结构体定义的变量,可以赋值
printf("%d, %c\n", s1.a, s1.b);
printf("%d, %c\n", s2.a, s2.b);
// s3 = s1; 类型不一致,赋值错误,是两个不同结构体定义的变量,当然也无法打印其值,就不展示了。
return 0;
}
2.动态内存分配和释放
动态内存分配和释放是在程序运行时动态地申请和释放内存空间的过程。它们允许程序在运行时根据需要动态地分配内存,提高了程序的灵活性和效率。
动态内存分配和释放的典型应用场景有:
-
当程序需要在运行时根据具体情况动态调整内存空间的大小时,可以使用动态内存分配来满足需求。
-
当程序需要存储动态生成的数据、临时数据或大量数据时,可以使用动态内存分配来避免静态内存空间不足的问题。
-
当程序需要在函数之间传递数据或在函数执行结束后保持数据的有效性时,可以使用动态内存分配来分配内存并将指针传递给其他函数。
需要注意的是,动态分配的内存需要在使用完毕后及时释放,以防止内存泄漏。如果不释放动态分配的内存,将导致内存泄漏问题,使得系统的可用内存逐渐减少,最终可能导致程序崩溃或系统变慢。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 描述一个学生的基本信息
struct student
{
char *name;
short int age;
int number;
};
int main(int argc, char *argv[])
{
struct student s1 = {.name = "zhangsan", .age = 22, .number = 2001};
s1.name = malloc(20); //分配20字节的堆内存,把这块内存的基地址赋值给s1的成员name
printf("%s, %hd, %d\n", s1.name, s1.age, s1.number);
strcpy(s1.name, "zhangsi");
printf("%s, %hd, %d\n", s1.name, s1.age, s1.number);
// 使用完毕就及时释放
free(s1.name);
return 0;
3.结构体内存访问
结构体的内存布局是按照成员变量的顺序存储的,成员变量在内存中的地址是连续的。同时,我们可以使用 . 和->
操作符来访问结构体变量和结构体指针的成员变量。
总结起来,结构体的内存布局和访问方式使我们可以灵活地操作和管理结构化的数据,同时结构体的嵌套特性可以更好地组织复杂的数据结构。
代码如下,一一认真看o~
#include <stdio.h>
struct A
{
int a;
char c;
};
// 描述一个学生的基本信息
struct student
{
char name[20];
short int age;
int number;
struct A sa;
};
int main(int argc, char *argv[])
{
struct student a = {"lisi", 22, 10001};
printf("%lu byte\n", sizeof(a)); // 36byte
printf("struct student address: %p\n", &a); //整个结构体的地址
printf("name address: %p\n", &a.name);
printf("age address: %p\n", &a.age);
printf("number address: %p\n", &a.number);
struct student *pa = &a;
printf("%s\n", pa->name);
printf("%hd\n", (&a)->age);
printf("%d\n", (&a)->number);
printf("struct student address: %p\n", pa); //整个结构体的地址
printf("name address: %p\n", &pa->name);
printf("age address: %p\n", &pa->age);
printf("number address: %p\n", &pa->number);
printf("sa address: %p\n", &a.sa); //取sa地址
printf("struct A 中的a address: %p\n", &a.sa.a); //取struct A中的a的地址
printf("sa address: %p\n", &a.sa); //取sa地址
printf("struct A 中的a address: %p\n", &(&a.sa)->a); //取struct A中的a的地址
return 0;
}
四、总结
结构体在C语言的学习中占有举足轻重的地位,今后也会常常用到,通过结构体,我们可以创建具有多个成员变量的数据结构,并灵活地使用和操作这些数据。操作的方式主要有一下这些方面:
- 定义结构体类型:通过
struct
关键字定义结构体类型,包含多个成员变量。 - 创建结构体变量:使用定义的结构体类型创建结构体变量。
- 访问结构体成员:使用点操作符 . 或者 -> 访问结构体变量的成员变量。
- 结构体赋值:可以将一个结构体变量的值赋给另一个结构体变量。
- 结构体数组:可以创建结构体类型的数组来存储多个结构体变量。
- 结构体指针:可以使用指针来引用和操作结构体变量。
上述没写到的知识点详见另一个博客,本篇主要是一些用法与心得。
【手撕C语言基础】结构体_祐言的博客-CSDN博客
📢写在最后
- 今天的分享就到这啦~
- 觉得博主写的还不错的烦劳
一键三连喔
~ - 🎉感谢关注🎉