【C语言】自定义类型:结构体

简介: 【C语言】自定义类型:结构体

前言

今天这篇文章,我们来学习自定义类型中的结构体类型

之前我们就初步了解过结构体类型,知道他是用来描述复杂类型的

像之前的short、int、long之类的称为C语言的内置类型

而如结构体、枚举、联合类型称为自定义类型

初识结构体

在正式学习前,我们先来回忆一下之前的知识

定义

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

结构体类型的声明

struct tag { //struct是关键字,tag是自定义的标签
  member - list //成员列表
}variable - list;//变量列表

例子

struct Stu { 
  char name[20];
  int age;
};//;不可以省略

结构体变量的创建

方式一

struct Stu { 
  char name[20];
  int age;
}s3, s4, s5;
struct Stu s6;
int main()
{
  struct Stu s1;
  struct Stu s2;
  return 0;
}
注意

s3, s4, s5,s6都是全局结构体变量

方式二:使用typedef简化

使用typedef将node重新命名为node

typedef struct node
{
  char arr[20];
  struct node* next;
}node;

创建变量的方式就有两种了

int main()
{
  struct node s1;
  node s2;
  return 0;
}

特殊的创建方式

匿名结构体类型(不建议这么使用)
struct
{
  int a;
  char b;
  float c;
}x;
struct
{
  int a;
  char b;
  float c;
}a[20], * p;

这种创建方式,只能创建全局变量,不能创建局部变量,因为这个结构体没有标签(名字)

注意1

当尝试使用p = &a进行赋值时,程序会报错

因为二者的类型不同,即他们虽然都隐去了标签,但编译器认为他们是属于不同的类型

注意2

当使用匿名结构体类型时,是无法使用typedef进行简化的

结构的自引用

引用自己的变量(错误)

不可以在结构体的成员列表处创建自己的结构体变量,如下

struct node
{
  char arr[20];
  struct node n;
};

这跟函数递归一点关系都没有,这么写是错误的

引用指针(正确)

正确的方式是引用一个地址,方便找到下一个元素存储在哪里

(就是链表的实现方式)

struct node
{
  char arr[20];
  struct node* next;
};

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

示例

struct S
{
  char c;
  int a;
  double d;
  char arr[20];
};
int main()
{
  struct S s = { 'c', 100, 3.14,"hello world"};
  printf("%c %d %lf %s\n", s.c, s.a, s.d, s.arr);
  return 0;
}

结构体嵌套初始化

结构体嵌套(结构体包含结构体的)初始化方式如下

要使用{}

struct T
{
  int age;
  double weight;
};
struct S
{
  char c;
  int a;
  double d;
  char arr[20];
  struct T st;
};
int main()
{
  struct S s = { 'c', 100, 3.14,"hello world", {20, 80.0} };
  printf("%d\n", s.st.age);
  return 0;
}

结构体内存对齐

计算结构体内存大小

引入

下面代码的结果是什么

struct S1
{
  char c1;
  int a;
  char c2;
};
struct S2
{
  int a;
  char c1;
  char c2;
};
int main()
{
  struct S1 s1 = { 0 };
  struct S2 s2 = { 0 };
  printf("%d\n", sizeof(s1));
  printf("%d\n", sizeof(s2));
  return 0;
}

运行结果

这是为什么?下面我们就来讲解它

对齐规则

代码就是上面“引入”中的代码

1

第一个成员在与结构体变量偏移量为0的地址处。

意思就是:结构体的第一个成员就存储在结构体变量所处的地址处

偏移量为0,就是重合

2

其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。

VS中默认的值为8

gcc中无明确默认值

先解释对齐数

以a作为例子,a为整型,大小是4,小于8,所以对齐数就是4

再解释整数倍

对齐数是4,那a这个变量就应该存储在4的倍数的地址处,

意思就是:从4的倍数的地址处开始存储,前面的就空着,不存储

那么根据前两条规则,结构体S1的大小是9,输出结果是12,那这是为什么呢

3

结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍

以结构体变量s1为例,最大对齐数是4,所以就再浪费三个字节的空间,将大小扩展到12个字节

用前三条规则也可以得出s2的大小是8个字节

4

如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

在“例题2”处,会做详细解释

例题1

下面结构体变量的大小是多少

struct S3
{
 double d;
 char c;
 int i;
};

根据前三条规则可以轻易得出:

8+1+3+4 == 16

大小就是16个字节

例题2

下面结构体的大小是多少

struct S4
{
 char c1;
 struct S3 s3;
 double d;
};

这里就需要用到第四条规则了:结构体嵌套

s3根据上一题可知,字节大小是16,

存储开始位置:嵌套结构体(此处就是s3)自己的最大对齐数的整数倍处

对于s3来说,最大的就是double的8个字节,也就是从8的整数倍处开始存储

并且结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

所以,变量s4大小就是:

1+7+16+8 == 32

内存对齐存在的原因(了解即可)

用空间换时间

1. 平台原因(移植原因):

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2. 性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

比如:32位的机器,一次可以读取4个字节的数据,如果不对齐,那就可能分两次读取,如果对齐,读取一次就够了

改进

所以为了节省空间和满足对齐条件

我们在创建结构体变量的时候,应该像“引入”中的变量s2一样:让占用空间小的成员尽量集中在一起

修改默认对齐数

使用预处理指令

#pragma

设置默认对齐位为4

#pragma pack(4)

取消设置的默认对齐位

#pragma pack(

结构体传参

注意:

结构体传参,分为两种:传值调用、传址调用

不修改变量成员可以使用传值调用和传址调用,建议使用传址调用,因为传过去的是地址,字节大小是固定的

要修改成员(如,初始化)就不能使用传值调用,只能传址调用

传值调用

struct S
{
  int data[100];
  int num;
};
void print1(struct S tmp)
{
  printf("%d\n", tmp.num);
}
int main()
{
  struct S s = { {1,2,3}, 100 };
  print1(s);
  print2(&s);
  return 0;
}

传址调用

如果担心ps指向的对象被修改时,只需要用const修饰即可

struct S
{
  int data[100];
  int num;
};
void print2(const struct S* ps)
{
  printf("%d\n", ps->num);
}
int main()
{
  struct S s = { {1,2,3}, 100 };
  print1(s);
  print2(&s);
  return 0;
}

原因(网上搜的)

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

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

结语

结构体的初步介绍就到这里了,希望对你有帮助

下一篇文章我们会学习位段,我们下次见~

相关文章
|
5天前
|
存储 C语言
C语言程序设计核心详解 第九章 结构体与链表概要详解
本文档详细介绍了C语言中的结构体与链表。首先,讲解了结构体的定义、初始化及使用方法,并演示了如何通过不同方式定义结构体变量。接着,介绍了指向结构体的指针及其应用,包括结构体变量和结构体数组的指针操作。随后,概述了链表的概念与定义,解释了链表的基本操作如动态分配、插入和删除。最后,简述了共用体类型及其变量定义与引用方法。通过本文档,读者可以全面了解结构体与链表的基础知识及实际应用技巧。
|
10天前
|
C语言
c语言中的结构体
本文档详细介绍了C语言中结构体的使用方法,包括结构体的基本定义、变量声明与赋值、数组与指针的应用,以及结构体嵌套、与`typedef`结合使用等内容。通过示例代码展示了如何操作结构体成员,并解释了内存对齐的概念。
|
16天前
|
C语言
C语言结构体赋值的四种方式
本文总结了C语言结构体的四种赋值方式,并通过示例代码和编译运行结果展示了每种方式的特点和效果。
24 6
|
26天前
|
编译器 程序员 Linux
【C语言篇】结构体和位段详细介绍
跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
|
26天前
|
存储 编译器 C语言
【C语言篇】自定义类型:联合体和枚举详细介绍
像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型。
|
1月前
|
存储 安全 编译器
C语言自定义类型
C语言自定义类型
28 10
|
1月前
|
存储 C语言
C语言------结构体和共用体
这篇文章是关于C语言中结构体和共用体的实训,通过示例代码演示了结构体的定义、赋值、使用,以及如何使用结构体变量进行数据的组织和操作,包括输入、排序、求平均分和查找学生信息等功能。
C语言------结构体和共用体
|
3月前
|
网络协议 编译器 Linux
结构体(C语言)
结构体(C语言)
|
2月前
|
存储 编译器 定位技术
结构体数组在C语言中的应用与优化策略
结构体数组在C语言中的应用与优化策略
|
2月前
|
存储 编译器 数据库
结构体数组在C语言中的应用与优化技巧
结构体数组在C语言中的应用与优化技巧