当前课程知识点:基于Linux的C++ > 第六讲 复合数据类型 > 6.5 结构体 > LinuxCPP0605
接下来这个主题是结构体
它的性质和我们的数组是不一样的
数组的所有的元素
它们的元素类型都是相同的
可是我们的结构体可以相同
也可以不同
主要的内容分成了这四个部分:
一个就是结构体的意义和性质
第二个就是结构体的存储表示
第三个就是结构体定义的数据对象
该如何进行访问
第四个就是结构体和函数之间的关系
我们首先来看结构体的意义与性质
从结构体定义的这个角度来讲
和数组的最大差别
它事实上是个不同类型的数据对象的集合
这一点和数组有最大的不同
数组中所有的元素
它的元素类型都是一样的
可是对于我们这个结构体来讲呢
这就不一样
我们来看像这样一个基本的定义
前面使用一个struct
后面跟着一个结构体类型的名称
一个花括号对
里面就是成员类型1
成员名称1 成员类型2 成员类型2……
就按照这样一个方式定义结构体的类型
注意花括号后边
最后是要有一个分号
来表示这个结构体类型的结尾的
那么在定义结构体类型的时候
你要记得 在这里面
它事实上是一系列数据元素
我们称之为字段、域、成员
每个元素的类型
(每个字段的名称)都可以是相同的
也可以是不同的
对于数组来讲
它的每一个成员元素类型
必须是一模一样的
所以在定义的时候
我们只需要指出它的元素类型就够了
可是对于结构体呢 这里面成员的类型
它的类型的名字可以是一样的
可以是不一样的
所以 我们在定义的时候
就不能够用一个就完成
有了这个结构体类型的定义
你当然就可以定义这个结构体类型的变量
我们在讲怎么定义这个变量之前
我们来看几个结构体的例子
你比如讲日期结构体
它实际上包括了年 包括了月 包括了日
每一个我都可以用结构体的成员来表达
三者都定义成一个int
所以你看我会写
struct DATE{ int year; int month; int day}
花括号对结束之后有一个分号
这个就是我们的日期这个结构体
第二个例子就是负数
struct COMPLEX做了一个负数的结构体
这里边有一个实部 有一个虚部
这个实部和虚部
实际上都是double类型的量
这些都叫结构体类型的定义
这里面每个字段都需要描绘得清楚
就像量一样去定义它
但事实上它不是量
它事实上是这个结构体的一个成员
或者一个成分
我们可以说
这个结构体里边有好几个成分
每个成分都是结构体信息的一部分
就相当于它的信息的一个切片
所有这些切片合在一起
才构成了整个完整的结构体类型的描述
还有一种很特殊的情况
在C++代码里边
可以按照“struct COMPLEX;”
这种格式来声明一个结构体类型
注意这里面写出来的struct COMPLEX
它是一个结构体类型的声明
它不是定义
它就没有给出这个结构体的完整的描述
也就是说你缺了花括号对
你没有跟我说明这个成分是什么样子
那么就不叫定义 只能叫声明
仅仅引入这个结构体的名字
以后有些地方
我们只使用名字就可以了的
而有的地方仅仅使用声明是不够的
那么那个时候你就不能够去使用它
我们并没有产生或者创造
新结构体类型出来
我们仅仅是引入它
我们知道
有这样一个结构体类型名字的存在
而不知道它具体的内容是什么
所以你不可以去使用
这样一个格式去定义一个变量
我们来看看实际的例子
我如何来表示
我们一个同学的学生信息呢
我们就来分析
我们解决一个实际问题的时候
结构体是非常非常常用的数据结构
那么针对于学生信息这道题
那我们就要想
我要表达的学生的信息
包括了哪些细节
第一个 我要表达学号
我用什么类型来表达呢 整数
那我可不可以使用一个字符串类型呢
其实都是可以的
那还有一个 名字
其实应该使用字符串类型
还有呢 性别
性别 我们就使用一个枚举型
这是很容易的
年龄 我们可以使用一个整数
你可能说他可能还有电话号码
你可以使用一个字符串
可能有的同学不是一个电话号码
他有固定电话啊 他有手机啊
手机号码可能有两个
碰到这种情况怎么办
一个串型可能就不够了
对于电话来说
可能我们还需要一个数组
但是我们现在呢
实际上并没有真正去讨论
字符串该如何去实现
所以我们假设这里边
已经产生了一个大写的STRING
字符串类型的定义
假设你已经知道了
我们用它来表达一个字符串
那么表达这个结构体类型的时候
就可以按照这样一个模式来去写
第二点 就是结构体的存储表示
结构体类型的变量
将按照它的结构体成员的定义
一个接着一个地顺序去放
这个空间和数组不一样
数组元素的存放是紧挨着放的
结构体这些成员呢
要求一个挨着一个地往下放
但是并不要求它们必须是完全紧密的
为啥呢
就是因为结构体的元素类型
性质是不一样的
可能第一个元素类型是个int
第二个元素类型就变成了STRING
第三个类型可能变成枚举型GENDER
它们的类型是不一致的
所以没有办法完全紧挨着放
还能保证整个程序的运行效率
这个就是我们整个
硬件基础架构所导致的
一个硬件基础架构是32位的
理论上我数据的存放
应该是4的倍数往上放
它的存储效率是高的
所以在这种情况下面
事实上就是为了保证整个程序的效率
它可能在按照顺序
摆放结构体成员的时候
中间可能会出现空洞
这一点是和数组有最大的不同
你要想知道真正结构体存储空间的大小
不要想当然认为它将等于
这些字段的存储空间大小之和
用sizeof操作符获取它
有了这个结构体的类型
我们就可以定义这个结构体类型的变量
你比如说
我定义了DATE类型的一个变量 date
我定义了STUDENT类型的一个变量Zhang_San
还有一种情况呢
比如说我有很多位同学
我就可以用一个数组来表达它
这个数组每一个元素
就是我们结构体类型
STUDENT students[8] 我有8个元素
每个元素都是STUDENT这样的一个结构体
你可以在定义的时候对它进行初始化
不管是常量还是变量
都可以对它进行初始化
你用初始化记号后面跟着花括号对
一个成员、一个成员
对它进行初始化就可以了
中间用逗号来分割开
如果这个成员是个整数
你就按整数的格式对它进行初始化
如果它是字符串
就按照字符串的格式对它进行初始化
有了结构体类型的变量
就可以对这个结构体类型的变量进行赋值
注意 这一点又是和数组有最大的不同
数组是不能够整体赋值的
哪怕这两个数组的元素的个数
是一模一样多的
元素的类型也是一模一样的
要想完成这个数组的赋值
只有一个方案 写一个循环
一个元素 一个元素地拷贝
否则是做不到的
可是我们结构体不一样
你定义了两个结构体类型的变量
只要这两个结构体的类型是一样的
就可以把这个结构体date
赋值给另外一个结构体new_date
它就一个字段 一个字段给你拷贝过去
year拷贝给year
month拷贝给month
day拷贝给day
注意拷贝的这个动作
它只拷贝那个字段的内容
大部分时候这个就是我们所要的结果
但是还有一些很特殊的场合
这种情况可能是不够的
它是一种浅拷贝 层次很浅
如果这个拷贝的深度不够
我们可能需要写一个深拷贝
那个时候就必须你自己写一个函数
来完成这个结构体的赋值的动作
我有了这个结构体类型的变量
那我们怎么访问这个
结构体变量中某一个特定的成员呢
我当然可以整体对它进行访问
就像我刚才讲的
你可以整体对结构体变量进行赋值啊
有的时候我只想访问
它的某一个特定的字段
你比如说这个date
我只想知道它的year信息(年份信息)
是不是2015年
我就要能够访问它的单一的某一个成员
这个时候就需要使用到这个点号操作符
其实称之为成员选择操作符
我选择一个成员year
我就date.year 赋值为2008
这就是一个点号操作符
我们就可以解析出
这个结构体类型变量的
某一个特定的成员
year是这么做 month也这么做
day同样还是这么做
还有一种情况
如果这样一个结构体类型定义的时候
它是嵌套的
你比如讲 我这里有一个类型叫FRIEND
我有一个朋友类型
这个朋友里边有一个字段叫id
有一个字段叫name
还有一个字段birthday
生日是什么类型 年 月 日
这就是一个DATE结构体类型
所以我们有了这个
FRIEND结构体类型的这样一个变量
那我就访问它的
特定的生日这个字段的时候
我就friend.birthday
解析 然后“.year”解析它的年份
也就是说如果这个结构体是个嵌套的
结构体某一个成员还是一个结构体类型
我想访问内部结构体某个成员的时候
就用连续的点号操作符去解析它
还有一种情况
我的朋友可能还不是一个
所以有friends这样一个数组
假设我这里面记录4个朋友
我想访问0号元
那就friends[0].birthday.year
如果是1号元friends[1].birthday.year
按照这个方式进行访问
你看着就觉得好怪
中括号后面就跟着打点
没问题 它就按照这个格式
friend[0]、friends[1]
它得到的就是那个数组元素
那个类型是什么 是个结构体
只要是结构体名字
就可以后面直接打点号解析
一旦解析出来
它就可以像一个普通量一样使用
最后一个主题
关于结构体和函数之间的关系
我们来看这样一个例子
写一个函数 使用一个结构体类型
来存储我们的日期
我们来计算这一天它是今年的第几天
具体天数从1开始计算
你比如2016年1月20号
它是这一年的第几天呢
它就是第20天
那2月1号多少天 32天
我用那个函数来展示什么呢
来展示的就是我们这个结构体
怎么样来作为我们整个函数的参数
GetDateCount这个函数
传一个date作为它的参数
返回值就是它的天数
这个程序该怎么做才能快
我就想一招
1月份 我就直接看它的日期是多少天
如果是2月份呢 它日期是几号
那是它2月是多少天
此前我必须把这个1月全部的天数给加上
如果是3月呢
那个几号就要加上第一月
和第二个月全部的天数
还有一个重要地方
如果是个闰年呢 3月以后
你得要把闰年闰的那一天给加上
如果是非闰年 那一天肯定是不存在的
如果是闰年 月份又在2月份之前
在这种情况下你又不用加
哪怕是2月28号 2月29号它都不用加
因为传过来天数就是那一天
也就是说 我要写一个for循环
我从1月开始去计算
然后这个month天数
累加到month-1那一月份
把这个以前的月份的天数全部加在一起
然后我就要去判断它是闰年不是
如果是闰年
而且这个月份是3月以后(包含3月)
我就把闰年那一天给加上
否则就不用管
我怎么样才能让我这个程序做最快
我们这里面能够处理的最重要一个地方
就是月份这个天数
我1月份31天
2月份多少天
非闰年28天 闰年29天
3月份31天
我们直接做一个数组存起来
我这个数组定义好
days_of_months
定义像这样一个数组
把某一个月所有天数全都存起来
我这个数组定义很特殊
定义13个元素 我们其实就12个月
但我定义13个元素
1月份就在1号元上
2月份就在2号元上
不需要每次做一个减法运算
按照这个方式我们程序就快
这样一个数组 我定义成一个static
我不用每次进入这个函数都创建一遍
然后你就可以用for循环累加
这个月之前的全部月份的日期
然后累加这一月的天数
然后判定它的月份是不是大于2
如果是月份大于2的
然后它是个闰年的
那我就累加 加一天
最后返回日期就OK了
这是第一个例子
第二个例子 就是结构体
还可以作为函数的返回值
这一点还是和数组不一样的
我们前面讲数组是不能整体赋值的
所以数组本身
是不能够作为函数返回值的
可是我们这个结构体是可以整体赋值的
所以它就可以作为函数的返回值
你比如讲我可以生成一个
二维平面上的点坐标
像二维点 有x轴、y轴
我定义了一个结构体类型叫struct POINT
然后我就可以生成像这样二维的点
使用前面的随机数库随机生成
这样的点范围在0~1920
0~1200
GenerateRandomNumber生成随机数
给它定义POINT类型的变量t
然后t.x、t.y 生成出来
生成完了以后 我们return
这就是结构体
作为函数的返回值的时候该怎么用
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口