自定义类型的学习总结

在C语言中,自定义类型是一种应用非常广泛的,典型的以结构体为例,比如你要描述一个学生,这个学生具有:姓名+年龄+性别+学号 这么几项特征,而通常我们有可能要把学生来包装成一个类型,这样就可以重复性的用这个类型来定义出不同的每个学生。而结构体的出现就可以使得C语言具有了这样能描述复杂类型的能力。

一般的自定义类型我们主要学习这么几种:结构体,枚举,联合体

知识点: 
>结构体类型创建 
>结构体初始化 
>结构体内存对齐 
>位段,位段计算机大小。 
>枚举+联合。 

首先是看看结构体,关于结构体,其实我们可以联想到数组,因为结构体于数组是有一定程度的相似之处的,都是属于聚合性结构,可以用来存放多个元素,不同点在于数组只能存放不同类型的元素,而结构体则可以存放相同或不同类型的元素。

结构体类型创建

首先要声明一个结构体类型,我们需要用到关键字struct ,这里给出声明一个结构体的一般格式:

1 struct tag
2 {
3      member-list;
4 }variable-list;

在这里要说明的是,这个tag被称为结构体的类型,也就是我们自己定义的这个结构体的名字,这个是可以省略的,当它被省略时,我们定义的结构体就被叫做匿名结构体。

而variable-list,也就是结构体的变量,这是代表我们实际上定义的结构体类型的这样的变量,可以在这里直接同时声明出多个同类型的变量。当然,也允许省略,省略的代价无非是我们只是在抽象出了一个结构体类型而已,等到在程序中要实际定义出这个结构体变量时,直接用这个类型来定义就可以。结构体的变量,可以被定义成一个指针,那么这个指针,也就是结构体类型的指针了。

但是,这里要强调的是,这个member-list,也就是结构体的内部成员,就是比如当我们定义一个学生时,用来描述这个学生的,姓名,学号,性别等这些属性信息。那他可以省略吗。那么我们要说的是,当我们省略结构体成员时,这就是一个空结构体了。注意:在C语言中,空结构体的情况是不被允许的,你必须至少包含一个成员。(PS:在C++里,其实结构体就是类这么一个概念,而类是允许有空类存在的)

这里的三点:结构体的类型,结构体变量以及结构体的内部成员,我们统称为结构体的三要素。这三要素,是组成一个结构体类型的重要组成部分。

结构体初始化

结构体的初始化规则,与数组是一样的,当我们需要对结构体变量初始化,也就是定义它的同时直接赋给它初值,是可以进行整体赋值的,将一组要赋的初值用{ }包起来,整体赋值就行了。比如:

1 struct Point
2 {
3     int x;
4     int y;
5 }p1;   //声明类型的同时定义变量p1
6 struct Point p2; //定义结构体变量p2
7
8 struct Point p3={x,y};  //初始化:就是定义变量的同时赋初值

再比如定义一个学生:

1 struct student
2 {
3     char name[10];
4     int age;
5 };
6 struct student s={"zhangsan",20};//直接将学生初始化成某个具体的学生

结构体体的内存对齐

谈到结构体的内存对齐,结构体的内存对齐是作为计算结构体大小的时候,需要用到的一个重要概念,总体来说,属于一种依靠浪费空间来换性能的方法。这里我先给出一个例子,来简单说明。我们都知道,cpu访问内存时,我们以为它是可以在内存任意位置读取的,但实际上,某些平台可能只能在特定位置访问。比如,cpu读取内存数据,假设起始地址必须从偏移量为4的整数部分读取。那么,现在定义这么一个结构体,我们分别画图分析内存对齐和不进行对齐时,它是如何访问内存的。

1 struct {
2     char x;
3     int y;
4 };

可以看见,当我们在内存中读取结构体当中的成员时,由于char 型的A占一个字节,而int型的B占四个字节,

那么假设他们都从偏移量为4的这个位置开始,只访问4的整数倍数。可以看出,不内存对齐时,访问A只要一步,读够一个字节就好,访问B时,接着A的位置继续,就要先读完A剩下的三个字节,再从8的位置起,再读一个字节,补够B所需四个字节。这样读取B就需要两次。而当我们进行内存对齐,也就是假设我A后面三个字节不要了,我直接从8起,把B放在这四个字节里。这时我读取A,只需要一步,再从8开始读取B,也只需一步就能读够所要的四个字节。速度上我就可以提高,这在某些需要性能的程序上是很重要的。

所以总的来说,内存对齐,就是一种用空间换时间的做法。至于深入的探究,这里暂时先不讨论。

位段,位段计算机大小

位段是一种与结构体很类似的自定义类型,在声明上,主要有两个特点:

  1. 位段成员必须是int ,unsigned int或signed int也或者char (即整型家族);
  2. 位段成员名后面有一个冒号和一个数字(ps:数字就是用于表示该成员占据的字节数)。

1 struct A
2 {
3     int _a:2;
4     int _b:5;
5     int _c:10;
6     int _d:10;
7 };

那么我们计算位段一个位段类型变量时,其实就是将这些数字加在一块就可以了,而位段其实就是通过比特位压缩存储的方式来存储的。

当我们存储一个变量时,计算机会先看这个变量前面的一个变量是否将为之开辟的空间用完,如果发现前一个变量实际上没有用完空间。再存储这个变量,是接着前一个变量继续存储的,而不是继续开辟新的空间。可以看出,位段是这样子,与前面结构体内存对齐的概念相反,没有对内存的浪费,而是存储的非常紧促,是以牺牲性能的方法换取内存上的节省。

位段有一个很重要的限制在于,它是不支持跨平台的,所以如果是在考虑跨平台移植的程序里,就无法使用位段了。

枚举+联合

最后简单说说枚举和联合这两个概念,所谓枚举,可以理解为一一列举。

枚举在自定义类型里,有一个很与众不同的特性,就是它的内部所有成员,都不是变量,而是常量,并且成员之间是以逗号间隔的。也就是说,所谓枚举,只是简单的将其包含的成员一一列举出来,而不作任何改变。举个例子:一个星期从周一周日,这是可以列举出来,而无法进行改变的;再比如颜色有好多种可一一列举,一年十二个月等等,这些都是可以的。枚举是以关键字enum定义的。

 1 enum Day
 2 {
 3     Mon,
 4     Tues,
 5     Wed,
 6     Thur,
 7     Fri,
 8     Sat,
 9     Sun
10 };

使用枚举是可以用来定义变量的,既然是定义变量,那可以与另一个定义变量的工具--宏进行对比,我们可以发现,枚举,可以一次性定义多个常量,而宏只能一个一个定义了。从效率上就可以分出胜负了,并且枚举作为一种类型。是有着严格的类型检查的,所以相对来说,会更加严谨一点。

最后再看看联合体,联合体我们也叫作共用体,是以关键字union来声明的。一个很重要的特征是,联合体当中所有的成员是公用的同一块空间,可以理解为其他自定义类型中,成员是一个跟在一个后面的排布,而联合体中成员是横排排列的。这样,所有的成员就都是第一个元素,联合体的地址,是所有成员的地址。每个成员的首地址,都是相同的。而整个联合体的大小,则只需要计算所有成员里面占据空间最大的那个就可以了。

1 union Un
2 {
3     int i;
4     char c;
5
6 };

原文地址:https://www.cnblogs.com/sunjiyuan/p/10051991.html

时间: 2024-08-05 08:47:26

自定义类型的学习总结的相关文章

Haskell学习笔记二:自定义类型

内容提要: 代数数据类型 - Algebraic Data Types: 自定义数据类型 - data关键字:值构造器:类型变量与类型构造器: 记录(Record)语法 - 简化自定义数据类型的一种语法糖: 一个完整的例子 - PurchaseOrder定义和简单计算.单元测试: 代数数据类型(Algebraic Data Types) 为什么Haskell的数据类型会有代数数据类型这个名字?回想我们初中时代,初次学习代数的情况,印象最深刻就是x,y,z代替了具体的数字,引入方程式的概念,对 解

[原创]java WEB学习笔记67:Struts2 学习之路-- 类型转换概述, 类型转换错误修改,如何自定义类型转换器

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

JavaScript之面向对象学习七(动态原型模式和寄生构造函数模式创建自定义类型)

一.动态原型模式 在面向对象学习六中的随笔中,了解到组合构造函数模式和原型模式创建的自定义类型可能最完善的!但是人无完人,代码亦是如此! 有其他oo语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常困惑.因为对象在其他oo语言中往往是封装在一块的,而构造函数确是和原型分开的,所以并没有真正意义上的封装,所以动态原型模式正是致力与解决这一问题的一个方案! 动态原型模式将所有的信息都封装在构造函数中(包括原型和实例属性),通过在构造函数中实例化原型(仅在必要的情况下)实现封装,又保持了

MyBatis学习笔记(二):创建自定义类型处理器

MyBatis定义了一些默认处理器,可以用来设置参数或取结果集时实现自动转换,有些类型MyBatis是不支持的,例如Java 8中的日期类型,本文将介绍如何定义自定义类型处理器,来满足最新的日期类型. 一. 新建数据库表students,包含time和date类型,我用的是MySql数据库 -- 创建students示例表 create table students (     id int primary key auto_increment,     name varchar(20),   

Hadoop日记Day13---使用hadoop自定义类型处理手机上网日志

测试数据的下载地址为:http://pan.baidu.com/s/1gdgSn6r 一.文件分析 首先可以用文本编辑器打开一个HTTP_20130313143750.dat的二进制文件,这个文件的内容是我们的手机日志,文件的内容已经经过了优化,格式比较规整,便于学习研究,感兴趣的读者可以尝试一下. 我从中截取文件中的一行记录内容进行分析: 1363157985066     13726230503    00-FD-07-A4-72-B8:CMCC    120.196.100.82    i

struts2基础---->自定义类型转换器

这一章,我们开始struts2中自定义类型转换器的学习. 自定义类型转换器 一.定义一个继承于StrutsTypeConverter的转换类: package com.huhx.converter; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import o

【Spring】利用spring的JdbcTemplate查询返回结果映射到自定义类型

// org.springframework.jdbc.core.JdbcTemplate 中的查询方法基本都有支持参数RowMapper<T> rowMapper的重载方法.下面只是随便举例2个,还有很多 public <T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper) throws DataAccessException { ... }; public <T>

SQL Server -- 自定义函数(学习总结,备忘)

SQL Server自定义函数,以前只在书上看过,没有动手去敲一敲,今天刚好接触到,看了几篇博文学习了下.做好备忘很重要!! (@[email protected])Y Learn from:http://www.cnblogs.com/lideng/archive/2013/04/15/3022418.html 自定义函数分为:标量值函数或表值函数两种. 标量值函数:如果 RETURNS 子句指定一种标量数据类型,则函数为标量值函数. 表值函数:如果 RETURNS 子句指定 TABLE,则函

去除List集合中的重复元素? 如果没有Set集合,List集合是怎么去除重复元素的(字符串类型,自定义类型)?

 关键字: 如果没有Set集合,List集合是怎么去除重复元素的(字符串类型)?  *   *     思考: List就可以存储重复元素,那么需求中容器中的元素必须保证唯一性,该如何解决呢??  *      *   去除List集合中的重复元素?  * * 思路: * * 1.首先我需要另一个临时容器tempList,用来存放我认为应该保留的元素.(也就是不重复的元素) * 2.然后我们应该遍历原容器, 一个一个的取出元素, 放入tempList. * 当tempList里已经装有刚刚取出的