C语言初阶-初识结构体

简介: C语言初阶-初识结构体

1.结构体的声明

1.1结构体的基础知识

结构是一些值的集合,这些值称为成员变量,结构的每个成员可以使不同类型的变量

生活中描述:

人:名字+性别+年龄+身高+身份证号码+地址...

书:书名+作者+出版社+定价+书号...

这些复杂对象不能通过内置类型直接描述和表示,于是就有了结构体。

1.2结构体的声明

例如描述一个学生:

struct Stu
{
  char name[20];//姓名
  int age;//年龄
  char sex[5];//性别
  char id[20];//学号
};//注意分号不能丢

其中name,age,sex,id是结构体的成员变量,用来描述结构体对象的相关属性。

上述的strcut Stu是我们的创建的结构体类型,那在主函数中我们就可以通过这个类型创建结构体变量。

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
  char name[20];//姓名
  int age;//年龄
  char sex[5];//性别
  char id[20];//学号
};//注意分号不能丢
int main()
{
  struct Stu s1;
  return 0;
}

上述代码中的s1就是我们创建的结构体变量,当然我们也有另外一种方式,就是在创建结构体类型struct Stu的时候,直接在它的大括号外面捎带着创建结构体变量s2,s3和s4。(如下)

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
  char name[20];//姓名
  int age;//年龄
  char sex[5];//性别
  char id[20];//学号
}s2, s3, s4;//s2,s3,s4就是结构体变量 - 全局变量
int main()
{
  struct Stu s1;//局部变量
  return 0;
}

代码中的s1,s2,s3,s4都是结构体变量,只不过s1是局部变量,s2,s3和s4是全局变量。

我们也可以在创建结构体类型时,用 typedef 对其进行类型重命名,

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
typedef struct Stu
{
  char name[20];//姓名
  int age;//年龄
  char sex[5];//性别
  char id[20];//学号
}Stu;//类型重命名为Stu
int main()
{
  struct Stu s1;//局部变量
  Stu s2;
  return 0;
}

上述代码用 typedef struct Stu重命名为Stu,那在主函数中创建结构体变量时就可以直接用Stu来代替struct Stu,代码中的s2就是这样创建的。

注意:如果使用 typedef 的时候,结构体类型的大括号外面不是结构体变量,而是类型的重命名

1.3结构体成员的类型

结构体的成员可以是标量、数组、指针,甚至是其他结构体

struct S
{
  int a;
  char arr[5];
  int* p;
};
struct B
{
  char ch[10];
  struct S s;
  double d;
};
int main()
{
  return 0;
}

1.4结构体变量的定义和初始化

struct S
{
  int a;
  char arr[5];
  int* p;
}s1 = {100,"bit",NULL};
struct S s2 = { 99,"hello",NULL };
int main()
{
  struct S s3 = { 1,"abc",NULL };
  return 0;
}

上述代码的s1,s2和s3都是结构体变量,{}中是对它们的初始化。

有人这时候有问题了,如果不想按照结构体类型中规定的顺序初始化怎么办?

那就要换一种写法了:

struct S
{
  int a;
  char arr[5];
  int* p;
}s1 = { .p = NULL,.a = 100,.arr = "bit" };
struct S s2 = { .arr = "hello",.p = NULL,.a = 99 };
int main()
{
  struct S s3 = { .a = 1,.p = NULL,.arr = "abc" };
  return 0;
}

这两段代码的作用是一样的,我们可以将它们打印一下,不过这就涉及到结构体成员的访问了。

2.结构体成员的访问

前面我们讲过两个结构体成员访问操作符,即 .  ->

下面我们来打印一下试试:

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct S
{
  int a;
  char arr[5];
  int* p;
}s1 = { .p = NULL,.a = 100,.arr = "bit" };
struct S s2 = { .arr = "hello",.p = NULL,.a = 99 };
int main()
{
  struct S s3 = { .a = 1,.p = NULL,.arr = "abc" };
  printf("%d %s %p\n", s1.a, s1.arr, s1.p);
  printf("%d %s %p\n", s2.a, s2.arr, s2.p);
  printf("%d %s %p\n", s3.a, s3.arr, s3.p);
  return 0;
}

运行结果:

想要访问结构体成员,用结构体变量 . 结构体成员名就可以了。

我们也可来尝试对结构体类型 B 创建变量 sb ,初始化并打印一下:

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct S
{
  int a;
  char arr[5];
  int* p;
}s1 = { .p = NULL,.a = 100,.arr = "bit" };
struct S s2 = { .arr = "hello",.p = NULL,.a = 99 };
struct B
{
  char ch[10];
  struct S s;
  double d;
};
int main()
{
  /*struct S s3 = { .a = 1,.p = NULL,.arr = "abc" };
  printf("%d %s %p\n", s1.a, s1.arr, s1.p);*/
  struct B sb = { "thanks",{20,"abcd",NULL},3.14 };
  printf("%s %d %s %p %lf", sb.ch, sb.s.a, sb.s.arr, sb.s.p, sb.d);
  return 0;
}

运行结果:

上述代码,因为在struct B 中嵌套了struct S s;所以我们在{}中初始化时要将对struct S s初始化的内容再用个{}包起来。在打印时用两次结构体成员访问操作符(例如:sb.s.a)

以上是点操作符的使用,还有->操作符是在接受一个地址(指针)时使用,

下面看一段代码:

struct Stu
{
  char name[20];
  int age;
};
void set_stu(struct Stu t)
{
  t.age = 20;
  t.name = "王小明";
}
int main()
{
  struct Stu s = { 0 };
  set_stu(s);
  return 0;
}

我们写一个函数set_stu(),对结构体变量s赋值,那上面的代码对吗?

当然不对。

注意这句赋值语句:t.name = "王小明";出现了错误,我们说name是数组名,那你能直接把“王小明”赋给数组名吗?不能,应该是赋给数组名name所在的空间才对!

以前我们学过字符串拷贝strcpy这个函数,这里就可以用了。

struct Stu
{
  char name[20];
  int age;
};
void set_stu(struct Stu t)
{
  t.age = 20;
  strcpy(t.name,"王小明");//字符串拷贝
}
int main()
{
  struct Stu s = { 0 };
  set_stu(s);
    printf("%s %d", s.name, s.age);
  return 0;
}

问题又来了,这样写就对了吗?

运行一下:

结果是0,为什么呢?

那是因为我们在上述代码中使用的是传值调用,前面我们讲过形参是实参的一份临时拷贝,对形参的改变不影响实参的值。

如果用传址调用就可以了:

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
  char name[20];
  int age;
};
void set_stu(struct Stu* t)
{
  t->age = 20;
  strcpy(t->name,"王小明");
}
int main()
{
  struct Stu s = { 0 };
  set_stu(&s);
  printf("%s %d", s.name, s.age);
  return 0;
}

因为传址调用,传给形参的是地址,我们在访问结构体变量时就用到了->操作符

下面我们在看一段->操作符的使用示例:

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
struct Stu
{
  char name[20];
  int age;
  char sex[5];
};
void print(struct Stu* t)
{
  printf("%s %d %s\n", t->name, t->age, t->sex);
  printf("%s %d %s\n", (*t).name, (*t).age, (*t).sex);
}
int main()
{
  struct Stu s = { "翠花",25,"女" };
  print(&s);
  return 0;
}

注意:代码中,t->name (*t).name 的作用是相同的。

3.结构体传参

我们在上面的讲解中已经包含了结构体传参,实际上,结构体传参有两种方式,一种是传结构体,一种是传地址。

那我们直接上代码:

struct S
{
  int data[1000];
  int num;
};
struct S s = { {1,2,3,4},1000 };
//结构体传参
void print1(struct S s)
{
  printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
  printf("%d\n", ps->num);
}
int main()
{
  print1(s);//传结构体
  print2(&s);//传地址
  return 0;
}

按道理这两种方式都可以实现传参,但是print1和print2那个更好些呢?

答案是:首选print2函数。

大家想一下,我们在传参时,形参是实参的一份临时拷贝,而如果传过去的结构体特别大,我们需要开辟同样大小的空间,这是不是对空间的一种浪费?而拷贝也是需要时间的,对时间也是一种浪费。而传地址的话,我们只需要传递4/8个字节大小的地址,然后根据地址找到实参的空间就行。

所以结构体传参时,传结构体的地址是一种比较好的选择。

原因:

函数传参的时候,参数是需要压栈的。

如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。

今天就学到这里,未完待续。。。

目录
相关文章
|
26天前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
122 14
|
1月前
|
存储 编译器 C语言
【C语言】结构体详解 -《探索C语言的 “小宇宙” 》
结构体通过`struct`关键字定义。定义结构体时,需要指定结构体的名称以及结构体内部的成员变量。
140 10
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
148 13
|
2月前
|
存储 数据建模 程序员
C 语言结构体 —— 数据封装的利器
C语言结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起,形成一个整体。它支持数据封装,便于管理和传递复杂数据,是程序设计中的重要工具。
|
2月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
69 11
|
2月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
60 4
|
3月前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
91 10
|
3月前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
3月前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
3月前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。