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个字节大小的地址,然后根据地址找到实参的空间就行。

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

原因:

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

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

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

目录
相关文章
|
22天前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
30 10
|
21天前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
26天前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
26天前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。
|
30天前
|
编译器 C语言 C++
C语言结构体
C语言结构体
25 5
|
1月前
|
编译器 Linux C语言
C语言 之 结构体超详细总结
C语言 之 结构体超详细总结
18 0
|
1月前
|
存储 编译器 Linux
深入C语言:探索结构体的奥秘
深入C语言:探索结构体的奥秘
|
1月前
|
存储 编译器 C语言
c语言回顾-结构体(2)(下)
c语言回顾-结构体(2)(下)
28 0
|
1月前
|
存储 编译器 程序员
c语言回顾-结构体(2)(上)
c语言回顾-结构体(2)(上)
28 0
|
1月前
|
存储 C语言
c语言回顾-结构体
c语言回顾-结构体
16 0