C语言 - 结构体、结构体数组、结构体指针和结构体嵌套

结构体的意义

问题:学籍管理需要每个学生的下列数据:学号、姓名、性别、年龄、分数,请用 C 语言程序存储并处理一组学生的学籍。

单个学生学籍的数据结构:

  • 学号(num): int 型
  • 姓名(name) :char
  • 性别(sex):char 型
  • 年龄(age):int 型
  • 分数(score):float 型
    思考:如果有多个学生,该怎么定义,已学数据类型无法解决(已学的数据类型需要定义好多变量,不友好)。

概述

  • 正式:
    结构体是由一批数据组合而成的结构型数据。组成结构型数据的每个数据被称为结构型数据的 "成员" ,其描述了一块内存区间的大小及解释意义。
  • 通俗:
    结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。

在C语言中,定义结构体的语法格式如下:

c 复制代码
struct 结构体名 {
    类型 成员1;
    类型 成员2;
    // ...
};

其中,结构体名是您自定义的结构体类型名称,可以根据需求进行命名。成员1成员2等表示结构体的成员变量,每个成员都有自己的类型和名称。

定义结构体后,可以使用该结构体类型创建结构体变量,并访问结构体的成员。访问结构体成员的语法是使用结构体变量名后跟成员名,中间使用点.进行连接。

结构体的使用

  • struct 结构体名 变量名

  • struct 结构体名 变量名 = {成员1值,成员2值...}

  • 定义结构体时顺便创建变量(这时候创建几个变量都可以,中间用逗号隔开,直接在创建的时候赋值也可以,例如:)

    c 复制代码
    struct student
    {
      int num; //学号
      char name[16];  //姓名
      float score;  //成绩
    }stu5 = {1002,"lihua",89},stu6;
  • 如果只想给一部分数据赋值的话:

    c 复制代码
    struct 结构体名 变量名 = {
    .name = "cuiyi",
    .num = 111,
    };

下面是一个更完整的示例:

c 复制代码
#include <stdio.h>

// 定义一个结构体
struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    // 创建一个结构体变量
    struct Person person1;

    // 访问结构体的成员
    strcpy(person1.name, "John");
    person1.age = 25;
    person1.height = 1.75;

    // 输出结构体的成员
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    printf("Height: %.2f\n", person1.height);

    return 0;
}

在上述示例中,我们定义了一个名为Person的结构体,它包含了姓名、年龄和身高三个成员变量。然后,我们创建了一个名为person1的结构体变量,并给它的成员赋值。最后,使用printf函数输出结构体的成员值。

结构体数组

  • 作用:将自定义的结构体放入数组中方便维护
  • 语法:
    struct 结构体名 数组名元素个数 = {{}, {}, ...{}}
    示例:
c 复制代码
#include<stdio.h>

struct stu
{
	char name[16];
	int age;
	float score;
}s[3];

int main()
{
	struct stu s[3] = {{"zhangsan",18,500},{"lisi",18,530},{"wangwu",18,550}};
	int i;

	for (i = 0; i < 3; i++)
	{
		printf("name=%s, age=%d, score=%f\n",s[i].name,s[i].age,s[i].score);
	}

	return 0;
}

结构体指针

  • 作用:通过指针访问结构体的成员
  • 语法:struct 结构体名 *指针名;
  • 利用操作符->可 以通过结构体指针访问结构体属性(比如s.name 有一个指针 ps 指向 s ,那么可以用 ps->name 代替 s.name

示例:

结构体数组和结构体指针

c 复制代码
#include<stdio.h>

struct stu
{
	char name[16];
	int age;
	float score;
}s[3];

int main()
{
	struct stu s[3] = {{"zhangsan",18,500},{"lisi",18,530},{"wangwu",18,550}};
	struct stu *ps = s;//定义一个指针指向结构体数组
	int i;

	for (i = 0; i < 3; i++)
	{
		printf("name=%s, age=%d, score=%f\n",(*(ps+i)).name,(*(ps+i)).age,(*(ps+i)).score);
	}

	return 0;
}

在上面的代码中,一定要记得 (*(ps+i)) 才是一个大括号里面的值,这样才能 .name .age .score 。

结构体嵌套结构体

  • 含义
    结构体中的成员可以是另一个结构体
  • 语法
c 复制代码
struct 结构体名 
{
	struct 结构体名 成员名;
};

示例:

c 复制代码
#include <stdio.h>
#include <string.h>
 
struct person
{
    char name[16];
    int age;
    char sex;
};
 
struct  student
{
    struct person stu; 
    float score;
};
 
struct teacher
{
    struct person tea;
    char phone[12];
};
 
int main(int argc, const char *argv[])
{
    struct student s;
 
    strcpy (s.stu.name,"zhangsan");
    s.stu.age = 12;
    s.stu.sex = 'm';
    s.score = 98;
 
    printf("name = %s,age = %d, sex = %c, score = %f\n", s.stu.name, s.stu.age, s.stu.sex, s.score);
 
    struct teacher t;
    struct teacher *p = &t;
 
    strcpy (p->tea.name, "lisi");
    p->tea.age = 54; //注意这里操作符 -> 的用法
    p->tea.sex = 'w';
    strcpy (p->phone, "13112341234");
 
    printf("name = %s,age = %d, sex = %c, score = %s\n", t.tea.name, t.tea.age, t.tea.sex, t.phone);
     
    return 0;
}

上述代码中,有一个部分用到了 p->tea.age 这种形式,这是因为p是指针,而 tea 只是一个普通变量,所以从 tea 出发不能用 ->,只有指针才可以使用这个操作符。

结构体大小

字节对齐

  • 含义
    字节对齐主要是针对结构体而言的,通常编译器会自动对其成员变量进行对齐,以提高数据存取的效率。(因为如果按照类型实际的大小来判断的话,那么需要判断很多次,这样对齐了以后有规律就不用判断了)
  • 作用
    • 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
    • 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

计算方法

  • 自身对齐 (这个数据类型大小是多少就是多少)
  • 默认对齐 (4字节)
  • 有效对齐 (在自身对齐和默认对齐之间选最小)

规则(地址 / 有效地址) 必须是整数。

计算过程:

  1. 把结构体里每个变量的类型的自身对齐,默认对齐和有效对齐分别写出来;
  2. 以有效对齐为准写每个变量的地址,最开始那个变量的地址肯定是0,然后后面叠加,注意在这个过程中要遵从**<规则>**,比如图中的变量 b ,本来地址应该是 1 ,但是因为 1 / 4 不是整数,所以要扩充到 4 凑整,那么这个时候 变量 a 的地址浪费了 1 2 3 这三个地址,又因为 b 本身就是 4 个字节,所以它的地址是 4 5 6 7。 c 和 d 因为都可以整除有效对齐,所以每个都加 1 个字节就行;
  3. 最终看一下,有效对齐最大的是 4 ,所以每个都要以 4字节 对齐,则要在变量 d 的后面再补 2 个地址:10 和 11 (因为前面的 8 和 9 已经占了 2 个地址了,还差 2 个地址凑够 4 个地址)。
  4. 得出结果:结构体 A 的地址是 0~11 ,所以大小是 12 。

上述过程要注意:能不能整除只能决定每个变量开头的地址,具体要每一行的地址从开头的地址要写到几要看变量类型的 sizeof 是多少。比如有 double c,c的开头地址是 8 ,那么这个变量占的字节就是 8 9 10 11 12 13 14 15 这八个字节。

相关推荐
Scott9999HH19 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社20 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海21 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖21 小时前
JDK 26 新特性详解
java·开发语言
马优晨1 天前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区1 天前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大1 天前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai1 天前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜1 天前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa1 天前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio