C语言自定义数据类型详解(一)——结构体类型(上)
什么是自定义数据类型呢?顾名思义,就是我们用户自己定义和设置的类型。
在C语言中,我们的自定义数据类型一共有三种,它们分别是:结构体(struct),枚举(enum),联合(union)。接下来,我们将对这三者中的结构体进行系统和深入的学习。
目录
一、结构体的声明与创建:
(1)如何声明和创建结构体类型:
(2)函数内声明和函数外声明:
(3)匿名结构体的声明与创建:
二、结构体的自引用:
问题一:自引用的成员变量不是以指针的形式出现的:
问题二:使用不完整的数据类型去定义自引用成员变量:
三、结构体变量定义,初始化和赋值:
(1)关于结构体变量的定义:
(2)关于结构体变量的初始化:
i. 按照顺序进行初始化(可以进行不完全的初始化):
ii. 指定成员进行初始化:
(3)区分赋值和初始化:
四、和结构体有关的计算:
一、结构体的声明与创建:
(1)如何声明和创建结构体类型:
在使用C语言来解决一些实际问题的过程当中,我们难免会发现,对于生活中的有一些数据实体,我们难以直接使用某一种单一的基本数据类型来阐述它。
比如说我现在想要表示一个学生的实体,那对于一个学生这样的实体而言,我们需要关注的信息可能有他的名字name,他的年龄gae,他考试得了多少分score等等诸如此类的要素。显然,站在我们程序设计者的角度,在这些要素里面既有字符类型name,又有int类型age,还有double类型的score。
没法用一个单一的基本数据类型来阐述它,而我们又不希望把这些诸多要素分散开来,更希望把它们整合起来表达一个具体的实体。这个时候结构体struct应运而生,结构体类型允许用户使用struct关键字将各种不同的数据类型组合在一起,形成新的数据类型用以表示复杂的数据对象。
比如下面就是一个典型的,用以表示学生的结构体类型,它的声明代码:
struct Student
{char name[20];//名字int age; //年龄char sex[5]; //性别double score; //成绩
};
其中struct是关键字,Student是结构体的名字(显然这个可以由用户自己来设计),{}里面的内容诸如name,age……这些我们称之为结构体的成员变量(这个也是用户根据自己的业务需求来设计)。
另外,这里唯一值得大家注意和说明的一点就是:Student它不是一个完整的数据类型,struct Student它才是一个完整的数据类型。即:
struct Student
{char name[20];//名字int age; //年龄char sex[5]; //性别double score; //成绩
};int main()
{Student data1 //error,这种定义结构体的方式是不正确的!struct Student data2 //right,这种定义结构体的方式才正确!return 0;
}
(2)函数内声明和函数外声明:
结构体声明啥的有了,那带来的第一个问题便是声明位置的问题。
我们很多时候声明结构体,都习惯于将其放在函数外声明。但是并不排除有小伙伴会把结构体的声明放在函数内,如图所示:
#include<stdio.h>int main()
{struct Book{char name[20];char author[12];float price;};struct Book data = { "Childhood","Gorky",5 };printf("%s's %s costs $%.2f", data.name, data.author, data.price);return 0;
}
这样写有没有问题呢,OK,没有问题。这里面可能还涉及有一些东西,诸如结构体变量的初始化这些你可能还不太会。但是这些都不是重点,我希望你注意到的是:
结构体类型和本地变量一样,在函数内声明的结构体类型往往只能在函数内部被使用。
所以,大部分的开发者,都习惯于将一个结构体的声明放在函数的外面,这样这个结构体类型就能在多个函数里面被使用啦。
(3)匿名结构体的声明与创建:
关于C语言结构体的声明和创建,在有些地方,你可能会发现有人会把代码写成下面这样的形式:
struct
{int x;int y;
}p1, p2;
这段声明,和我们前面所提到的结构体声明,它最大的特点就是在struct后面,没有那个用户所指定的名字了。然后后面紧接着的p1,p2,它们是这个类型的两个变量(注意它不是这个类型的名字)。这种定义结构体变量的方式在C语言里面是被允许的。
我们把这种没有名字的结构体类型,统一地叫做匿名结构体。这种结构体类型的最大特点是,它没有名字,无法被用户长期地使用。
使用这种结构体的开发者,他们的需求,仅仅只是暂时性地需要一个或多个这种类型的变量罢了。然后这几个变量里面呢,有一些明确的成员比如x和y。至于这个类型,它是什么名字的,我不关心,因为这只是我暂时性的需求,我并不打算在很远的将来继续使用这种类型。这个时候我就可以去使用这种匿名结构体。
关于匿名结构体另外一个好玩的事情是,我们不妨先来一起来看一下下面这段代码:
#include<stdio.h>
//匿名结构体类型
struct
{int a;char b;float c;
}x;
struct
{int a;char b;float c;
}*p;
然后提出的问题是:在上面代码的基础上,下面这段代码是否合法:
int main()
{p = &x;return 0;
}
有很多人会简单认为,说,这两个匿名结构体成员变量都是一模一样的,因此他们是两个相同的结构体类型。实际上,在编译器看来,虽然这两个匿名结构体内部的成员都是一模一样的,但仍然是两个不同的结构体类型。 因此用户如果在前面代码的基础上执行p = &x操作,这样的行为将会认为是不合法的。
但是现在绝大多数的编译器,它大概率只会给你一个警告,但是我仍然希望广大读者朋友们,不要这样草率地去使用匿名结构体。
二、结构体的自引用:
结构体的自引用是指:在结构体中包含一个类型为该结构体本身的成员,这个成员变量总是以指针的形式出现。
这种结构体的应用场景常常出现在数据结构中。如图所示:示范如何用结构体声明一个链表结构。
//数据结构————链表的声明
struct ListNode
{int val;struct ListNode* next;
};
这里有两个初学者容易出问题的地方。
问题一:自引用的成员变量不是以指针的形式出现的:
//数据结构————链表的声明
struct ListNode
{int val;struct ListNode next;
};
这样做,在C语言里面是不被允许的。你想,如果这样做的行为被允许了,那带来的其中一个问题就是,结构体大小将变得无法计算。
即最开始的那种定义的链表,它结构体的大小,如果要计算的话,我只要知道当前这台机器它的系统架构是x86(C语言的指针在x86的系统架构下大小是4Byte),还是x64(C语言的指针在x64的系统架构下大小是8Byte).我就能大概判断出
sizeof(struct ListNode) = sizeof(int) + 4/8(实际的大小计算会更复杂,我们将在后续的篇章给大家介绍)。
但是如果说自引用的成员变量不是以指针的形式出现的。那
sizeof(struct ListNode) = sizeof(int) + sizeof(struct ListNode)
你会发现这将是一个无法被计算的表达式。
问题二:使用不完整的数据类型去定义自引用成员变量:
在正式阐述这个问题之前,我们先来认识一个C语言里面的关键字typrdef,我们说typedef是C语言里面的一个关键字,它的作用就是用来给一个数据类型起别名的。
注意这里起别名的含义,起了别名并不代表你前面那个类型的名字你用不了了。eg:
typedef int DataType;int main()
{//以下都是在定义int类型的变量:int x = 0;DataType y = 0;return 0;
}
有了这个语法做铺垫,小伙伴们以后在定义链表的时候就可以这样声明和创建它了:
//数据结构————链表的声明
typedef struct ListNode
{int val;struct ListNode* next;
}ListNode;int mian()
{//ListNode就是struct ListNode的别名,因此下面两种写法都是正确的:struct ListNode node1;ListNode node2;return 0;
}
而带来便捷的同时也可能带来一些潜在的隐患:
//数据结构————链表的声明
typedef struct ListNode
{int val;ListNode* next;//error!!!不要使用不完整的数据类型
}ListNode;
注意,这样去写就又不对了,这是因为我的typedef这个动作做完之后ListNode才是struct ListNode,换句话说,在这段代码语句中,ListNode仍然还只是一个不完整的,不可被使用的数据类型。
对于结构体的自引用大家平时注意一下这两个问题,那基本的使用就问题不大了。因此这一块的内容我们给大家介绍到这里。
三、结构体变量定义,初始化和赋值:
有了类型,接下来我们还需要学会如何去使用这个我们自定义出来的数据类型。首先第一点当然就是,如何使用这个数据类型去定义一个变量,并对这个变量进行初始化的操作。
(1)关于结构体变量的定义:
我们这里主要和大家介绍两种不同的定义结构体变量的方式方法。
第一种就是一个结构体类型被声明出来时,这个时候我们是可以去定义一些结构体变量的。如图所示:
//s1,s2都是struct Student类型的结构体变量:
struct Student
{char name[20];int age;
}s1,s2;
第二种就是声明时我不定义变量,在函数内部,当我要用到这种类型的变量时,我再去定义它。如图所示:
#include<stdio.h>
struct Student
{char name[20];int age;
};int main()
{//当我需要这种类型变量的时候我再去定义它:struct Student s1;return 0;
}
(2)关于结构体变量的初始化:
关于结构体变量的初始化,一般而言有两种方式:
- 按照顺序对它的各个成员进行初始化;
- 指定其成员变量进行变量。
接下来,我们将就一个表示学生的struct Student来进行逐个说明:
//一个表示学生的结构体:
struct Student
{char name[20];int age;double score;
};
i. 按照顺序进行初始化(可以进行不完全的初始化):
如图所示,即为按照顺序对结构体的各个成员进行初始化示例:
#include<stdio.h>struct Student
{char name[20];int age;double score;
};int main()
{//一、在结构体变量定义时,按顺序进行初始化(可以只初始化一部分,只要按顺序即可):struct Student s1 = { "Lisi",20,100 };struct Student s2 = { "Wangwu",19};printf("name:%s\tage:%d \tscore:%.2f\n", s1.name, s1.age, s1.score);printf("name:%s\tage:%d \tscore:%.2f\n", s2.name, s2.age, s2.score);return 0;
}
ii. 指定成员进行初始化:
如图所示,即为在定义结构体变量时指定成员进行初始化示例:
#include<stdio.h>struct Student
{char name[20];int age;double score;
};int main()
{//一、在结构体变量定义时,按顺序进行初始化(可以只初始化一部分,只要按顺序即可):struct Student s1 = { "Lisi",20,100 };struct Student s2 = { "Wangwu",19};printf("name:%s\tage:%d \tscore:%.2f\n", s1.name, s1.age, s1.score);printf("name:%s\tage:%d \tscore:%.2f\n", s2.name, s2.age, s2.score);//二、在结构体变量定义时,指定成员进行初始化:struct Student s3 = { .name = "Zhangsan", .score = 80 };printf("name:%s\tage:%d \tscore:%.2f\n", s3.name, s3.age, s3.score);return 0;
}
(3)区分赋值和初始化:
大家在对变量进行操作时,一定要区分好赋值和初始化。在C语言里面,它们是两个截然不同的概念。如图所示:
#include<stdio.h>int main()
{//变量的初始化:int x = 10;//变量的赋值:x = 20;return 0;
}
简单来说:
- 初始化是指在变量声明时就为其赋予的初值,变量一开始是没有值的或者说只有未知值,初始化的目的在于使变量一开始就处于一个已知的状态,这一点对于避免未定义的行为非常重要。
- 赋值是指在变量已经有一个确定的值的前提(即初始化)下,改变其当前值的行为。
如果有了上面的基础,你大概就能很好地掌握,我接下来要阐述的关于结构体赋值的几个要点。关于结构体的赋值,在C语言里面我们要注意,不能使用列表(列表也就是形如{......}的这种形式)。即下面这些关于结构体赋值的操作在C语言里面都是错误的行为(如果你的这些行为被允许了,请考虑将你文件改为.c,因为这种行为在C++里面是被允许的,但是纯C当然就不行):
#include<stdio.h>struct Book {char name[20];double price;
}book1;int main()
{//错误的结构体赋值操作:book1 = { "平凡的世界", 8 };book1 = { .name = "查理九世", .price = 5 };return 0;
}
那正确的赋值操作,应该怎么做?正确的赋值操作应该是下面这种的:
#include<stdio.h>
#include<stdlib.h>struct Book {char name[20];double price;
}book1;int main()
{//正确的结构体赋值操作:strcpy(book1.name, "查理九世");book1.price = 5;return 0;
}
既然用不了列表,我们就不要去用列表嘛,OK,就是这么简单。另外,小伙伴可能会有下面这种行为:
book.name = "查理九世";
注意,name是个数组名,数组名是数组首元素的地址,那是一个常量的地址。即name的地址是改不了了的,但是你上面的代码语句的操作:是将一个常量字符串的地址交给name,这种行为已经对name的值进行改变了,因此是不被允许的。
你只能改的是name数组里面存储的内容。这里用我们前面学过的字符串内容拷贝函数——strcpy,是个不错的选择。
四、和结构体有关的计算:
以下面这个结构体为例子,我们来快速过一下和结构体有关的一些运算:
//p1,p2是两个struct Point的结构体变量:
struct Point
{int x;int y;
}point1 = {1, 2}, point2 = {3, 4};
首先和一般的变量一样,你可以用" & "操作符,拿到结构体变量的地址,并把它交给一个同类型的结构体指针变量进行保存:
struct Point* p1 = &point1;
再者,两个不同的结构体变量之间可以进行诸如point1 = point2,也就是赋值的操作:
point1 = point2; //相当于point1.x = point2.x,point1.y = point2.y。
point1 = (struct Point){ 5,6 }; //相当于point1.x = 5,point1.y = 6。
当然啦,结构体也可以作为我们函数的参数:比方说我现在想要设计一个函数PrintStruct,这个函数可以打印出给定结构体的各个成员,我大致可以这样来设计它的参数类型(一般情况下,我们更加推介大家使用第二种写法(即:传址优于传值),具体原因我们会在之后的篇章给大家进行说明):
//写法一:形参写的是结构体变量:
void PrintStruct(struct Point point);
//写法二:形参写的是结构体指针:
void PrintStruct(struct Point* p);
其次对于结构体变量来说,我们可以使用" . "结构体变量访问成员操作符。来访问它的内部成员。
printf("point1.x = %d, point1.y = %d", point1.x, point1.y);
最后对于结构体指针变量,可以使用" -> "结构体指针访问成员操作符。来访问它的内部成员。
printf("point1.x = %d, point1.y = %d", p1 -> x, p1 -> y);
相关文章:

C语言自定义数据类型详解(一)——结构体类型(上)
什么是自定义数据类型呢?顾名思义,就是我们用户自己定义和设置的类型。 在C语言中,我们的自定义数据类型一共有三种,它们分别是:结构体(struct),枚举(enum),联合(union)。接下来,我…...
使用 Tailwind CSS + PostCSS 实现响应式和可定制化的前端设计
随着前端开发框架和工具的不断更新,设计和样式的管理已经成为前端开发中的一项核心任务。传统的 CSS 编写方式往往让样式的复用和可维护性变得困难,而 Tailwind CSS 和 PostCSS 作为当下流行的工具,提供了强大的功能来简化开发过程࿰…...

巧用多目标识别能力,帮助应用实现智能化图片解析
为了提升用户体验,各类应用正通过融合人工智能技术,致力于提供更智能、更高效的服务。应用不仅能通过文字和语音的方式与用户互动,还能深入分析图片内容,为用户提供精准的解决方案。 在解析图片之前,应用首先需要准确识…...

算法中的移动窗帘——C++滑动窗口算法详解
1. 滑动窗口简介 滑动窗口是一种在算法中常用的技巧,主要用来处理具有连续性的子数组或子序列问题。通过滑动窗口,可以在一维数组或字符串上维护一个固定或可变长度的窗口,逐步移动窗口,避免重复计算,从而提升效率。常…...
AcWing 3585:三角形的边 ← sort() 函数
【题目来源】 给定三个已知长度的边,确定是否能够构成一个三角形,这是一个简单的几何问题。 我们都知道,这要求两边之和大于第三边。 实际上,并不需要检验所有三种可能,只需要计算最短的两个边长之和是否大于最大那个就…...

阿里云-银行核心系统转型之业务建模与技术建模
业务领域建模包括业务建模和技术建模,整体建模流程图如下: 业务建模包括业务流程建模和业务对象建模 业务流程建模:通过对业务流程现状分析,结合目标核心系统建设能力要求,参考行业建 模成果,形成结构化的…...
MySQL核心知识:春招面试数据库要点
在前文中,我们深入剖析了MyBatis这一优秀的持久层框架,了解了它如何实现SQL语句与Java对象的映射,以及其缓存机制等重要内容。而作为数据持久化的核心支撑,数据库的相关知识在Java开发中同样至关重要。MySQL作为最流行的开源关系型…...

Hive之加载csv格式数据到hive
场景: 今天接了一个需求,将测试环境的hive数据导入到正式环境中。但是不需要整个流程的迁移,只需要迁移ads表 解决方案: 拿到这个需求首先想到两个方案: 1、将数据通过insert into语句导出,然后运行脚本 …...
Java web与Java中的Servlet
一。前言 Java语言大多用于开发web系统的后端,也就是我们是的B/S架构。通过浏览器一个URL去访问系统的后端资源和逻辑。 当我在代码里看到这个类HttpServletRequest 时 让我想到了Servlet,Servlet看上去多么像是Java的一个普通类,但是它确实…...

kafka常用目录文件解析
文章目录 1、消息日志文件(.log)2、消费者偏移量文件(__consumer_offsets)3、偏移量索引文件(.index)4、时间索引文件( .timeindex)5、检查点引文件( .checkpoint&#x…...

RV1126+FFMPEG推流项目源码
源码在我的gitee上面,感兴趣的可以自行了解 nullhttps://gitee.com/x-lan/rv126-ffmpeg-streaming-projecthttps://gitee.com/x-lan/rv126-ffmpeg-streaming-project...
ANSYS SimAI
ANSYS SimAI 是 ANSYS 公司推出的一款基于人工智能(AI)的仿真解决方案,旨在通过机器学习技术加速仿真流程,降低计算资源需求,并为用户提供更高效的工程决策支持。其核心目标是简化复杂仿真过程,帮助工程师快…...

hedfs和hive数据迁移后校验脚本
先谈论校验方法,本人腾讯云大数据工程师。 1、hdfs的校验 这个通常就是distcp校验,hdfs通过distcp迁移到另一个集群,怎么校验你的对不对。 有人会说,默认会有校验CRC校验。我们关闭了,为什么关闭?全量迁…...

蓝桥杯单片机(八)定时器的基本原理与应用
模块训练: 当有长定时情况时,也就是定时长度超过65.5ms时,采用多次定时累加 一、定时器介绍 1.单片机的定时/计数器 2.定时器工作原理 3.定时器相关寄存器 二、定时器使用程序设计 1.程序设计思路 与写中断函数一样,先写一个初…...

刷题总结 回溯算法
为了方便复习并且在把算法忘掉的时候能尽量快速的捡起来 刷完回溯算法这里需要做个总结 回溯算法的适用范围 回溯算法是深度优先搜索(DFS)的一种特定应用,在DFS的基础上引入了约束检查和回退机制。 相比于普通的DFS,回溯法的优…...
C++ 静态变量static的使用方法
static概述: static关键字有三种使用方式,其中前两种只指在C语言中使用,第三种在C中使用。 静态局部变量(C) 静态全局变量/函数(C) 静态数据成员/成员函数(C) 静态局部变量 静态局部变量&…...

Langchain+文心一言调用
import osfrom langchain_community.llms import QianfanLLMEndpointos.environ["QIANFAN_AK"] "" os.environ["QIANFAN_SK"] ""llm_wenxin QianfanLLMEndpoint()res llm_wenxin.invoke("中国国庆日是哪一天?") print(…...
20250124 Flink中 窗口开始时间和結束時間
增量聚合的 ProcessWindowFunction # ProcessWindowFunction 可以与 ReduceFunction 或 AggregateFunction 搭配使用, 使其能够在数据到达窗口的时候进行增量聚合。当窗口关闭时,ProcessWindowFunction 将会得到聚合的结果。 这样它就可以增量聚合窗口的…...

Android Studio安装配置
一、注意事项 想做安卓app和开发板通信,踩了大坑,Android 开发不是下载了就能直接开发的,对于新手需要注意的如下: 1、Android Studio版本,根据自己的Android Studio版本对应决定了你所兼容的AGP(Android…...

设计模式Python版 单例模式
文章目录 前言一、单例模式二、单例模式实现方式三、单例模式示例四、单例模式在Django框架的应用 前言 GOF设计模式分三大类: 创建型模式:关注对象的创建过程,包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...