在一切的开始,内存只是一片荒芜,后修真者编译天地,便有了今天的锦绣山河。


一块没有使用的内存就像是一片荒凉的大地,为了更方便管理,人们进行区域划分,便有了良田千顷,房屋万座,为了更方便的管理内存,几乎在每一门编程语言中都有类型这个概念,每一种类型都有特定的大小,内存在被使用时就被不同的类型分割成了大大小小的不同的块。C语言中基本数据类型有int、char等,在基本数据类型的基础上又可以任意组合生成结构体类型。很多人在第一次接触编程时,往往很难理解类型是一个怎样的概念,我个人觉得称之为类型不是很合适,我更喜欢称之为格式,本文会从格式的角度尝试理解类型,看看会发生怎样的结果。

在不同的平台上,同一种类型也会有不同的大小,所以抛开平台谈类型无异于耍流氓,本文使用的环境为windows 64位。int格式占用空间为4个字节,char格式占用空间为1个字节,所以int格式和char格式不是同一种格式,float格式占用空间为4个字节,那int格式和float格式就是同一种格式吗?显然这是不合逻辑的,格式的定义还是太过于模糊了,那就细分为存储格式读取格式,存储格式描述了数据存储所占用的空间,读取格式描述了存储空间中的数据已何种方式组装,这两种格式决定了一种“类型“,int和float有用相同的存储格式,但是没有相同的读取格式,所以它们不是一种格式。

当我们定义一个变量的时候都发生了什么呢?先看一个很复杂的表达式:

int 

之所以说这个表达式很复杂,是因为编译器帮我们处理了太多的事,我们尝试用格式的概念分析下这个表达式背后故事,语法分词后编译器首先接收到int关键字,int格式在编译器中对应着类似这种结构:

{

然后编译器接收到x,根据前面获取到的存储格式分配存储空间,绑定x和这块存储空间,接着将5存入该空间中,此时编译器中应该有了这样一个结构:

{

程序中使用x时,会根据变量名调用读取格式对应的读取器,去处理存储空间指针指向的区域,读取大小为存储格式规定的。截止到现在,格式的基本概念已经基本清楚了,后文会继续使用格式去理解几个特定的问题。

为了行文方便,伪造了一些编译器的概念,真实的编译过程并不是上面说的那样,感兴趣的可以学一学编译器原理,真的很浪漫。

1. 类型转换

C语言中有两种类型转换:自动(隐式)类型转换强制(显式)类型转换,自动类型转换由编译器完成,例如:

char 

自动类型转换一般发生在较低类型转换为较高类型,此处的高低指存储空间占用的大小,这样可以保证数据完整性,如果从较高类型向较低类型转换,多出的数据将会丢弃,这样会导致数据不完整,所以需要强制类型转换(显示的调用,要清楚自己在做什么)。

而这一切如果用格式的概念理解,就变得简单起来,上文代码中的c被转换为int格式,对应的操作为:先复制一份c,保证原有的数据不受影响,然后将复制出的c存储格式改为int的存储格式,读取格式变成int的读取器,变量名变为x,存储空间指针不变(此处的指针并不指向c,而是指向c的复制体),这样就完成了一次类型转换。强制类型转换发生的数据丢失该怎么理解呢?注意到一点,读取器读取的空间大小是存储格式指定的,类型转换的时候存储格式已经被改变了,所以读取器读取的时候会读取新的大小。

还有一个有趣的问题,假设有如下代码:

float 

int和float存储空间都为4个字节,x是类型转换后的f,此时并没有发生数据丢失,那f1在赋值类型转换后的x,也没有发生数据丢失,是不是f1就是1000.1呢?然而f1实际的结果是1000.0,看着好像发生了数据丢失,也确实是这样,只不过并不是因为类型转换导致的,编译器在处理x的赋值的时候,自动舍弃了小数点之后的数据取整,所以再转换为float的时候有部分数据就丢失了。

2. 指针

指针作为C语言的灵魂,有着很多无法替代的作用。指针本身有两种类型,一种是指针类型,一种是指针指向的类型,很难理解?试试格式的概念,指针的格式如下:

{

指针都有一个共同的特点,就是它们的存储格式都保持一致,均为地址存储格式,读取格式也保持一致,决定它们不同的是目标格式,比如下面这段代码:

int 

变量x的定义不在赘述了,后面的指针初始化流程为:存储格式为int *,读取格式为int *的读取器,变量名为p,分配8个字节的内存空间,存储空间指针指向该块内存,目标格式为int,取出x的存储空间指针,赋值到p的存储空间中,然后以p的目标格式的读取器(int的读取器)读取p存储空间中存储的x的存储空间指针指向的空间,其中涉及到了两个操作符:& 返回变量的存储空间指针,* 表示调用目标格式的读取器读取目标的地址。伪代码表示如下:

x

是不是发现访问*p和访问x是完全一致的呢?

3. 结构体和结构体指针

结构体是一种复合类型,通过它可以衍生出无数的类型,格式的概念用来解释结构体再合适不过了。先来看一个简单的结构体:

typedef 

*注: 编译器在处理结构体时,为了加快访问速度会执行内存对齐,例如上面的结构体实际所占内存为 4 + 1 + 3 + 8 = 16。

这个结构体格式在编译器中对应着类似这种结构(数字表示相对于结构体首地址的偏移量):

Example 

初始化一个结构体变量:

Example 

对属性进行赋值时经过的过程如下:

example

接下来看一下结构体指针怎么理解,这里是重中之重

Example 

先来说一下malloc,这是C语言中在堆中分配内存使用的API,栈内存是由系统进行统一分配回收的,而堆内存是由开发者自主维护,malloc有唯一参数表示要分配内存的大小,单位字节,返回值类型为void *,void *可以指向任意类型,也可以转换为任意类型,后文会提到。上面的代码将一块内存初始化为了Example格式,即将这块内存按照Example的存储格式进行分块,分块的格式为: [0 - 3][4 - 4][5 - 7][8 - 15]。这里就是把结构体的指针类型看成格式刷对目标内存进行格式化。下面接着看一个有趣的问题:

Example 

第六行将p指针强制转换成了int *格式,此时发生了什么呢?

// 创建int *指针

那从Example *转换为int *存在数据的丢失吗?回想上节说明的,所有的指针存储格式和读取格式保持一致,唯一的区分在于目标格式,类型转换时,相当于改变存储格式和读取格式,从一个指针类型转换为另一个指针类型,它的存储格式和读取格式改变后还是原来的样子,所以就不存在一种指针转换成另一种指针发生数据丢失的情况,但是在指针的类型转换中,目标格式发生了变化,所以在读取的时候会调用不同的读取器,例如:

printf

应用这个特点,可以实现结构体的继承,前提条件是子结构体要将继承的父结构体放在属性的第一位。

4. 函数指针

很多教材将变量和函数分开来讲,以至于在后面遇到函数指针和回调的时候,往往一时间不能接受,所以我个人认为学会使用函数指针和回调的关键是要打破固有思维,将函数看成一种特殊的变量,对于这一点我更喜欢JavaScript对待函数的态度:

function 

function作为js的一等公民,可以出现在任何变量可以出现的地方,变量可以当做函数参数,那函数也可以当做参数使用,这就是回调

使用类型的概念我们很难去描述一个函数,但是用格式的概念就很简单了,试想一下区分两个函数的标准是什么?

1. 返回值
2. 参数表
3. 函数名

所以可以把函数类型看成一个结构体:

{返回格式;参数表;
}

定义一个函数的时候,实际上是:

int 

看到这你也许会发现,add和sub的返回格式和参数表相同,所以add和sub是同一种函数格式。可以尝试定义一个这种格式的函数指针:

// 定义Func类型

从格式的概念看,函数和普通的变量没有任何的区别,普通的变量可以进行类型转换,那函数类型肯定也可以类型转换:

int 

可以看到,当函数指针强制转换(回顾前文指针类型转换一定不会发生数据丢失)时,参数表和返回值发生了变化,导致最终的结果不可预知,所以在日常开发中要尽量避免函数指针发生类型转换。


文中定义了格式的概念只是为了帮助理解,真实的编译过程并不是文中所述。

c语言float转换为int_关于C语言的类型相关推荐

  1. c语言float转换为int_C语言的隐式类型转换和显示类型转换

    C语言是一种强类型语言,当使用一种类型的对象代替另外一种类型的对象进行操作时,必须首先进行类型转换. 类型转换的方式,一般可分为隐式类型转换和显示类型转换(也称为强制类型转换). 1.隐式类型转换 隐 ...

  2. c语言float二进制输出代码_C语言学习笔记——学前知识概述

    将我大一学习C语言时做的笔记拿来与大家分享,内容比较浅显,比较适合初学者,如有错误还请见谅,提出改正,谢谢! 前言:此C语言笔记是本人在自学时记录的一些重点或初学者常犯的错误,希望我的这本笔记能够对大 ...

  3. c语言强制转换为结构体,C语言结构体的强制类型转换

    陈浩师兄03年的一篇博客<用C写有面向对象特色的程序>描述了用C语言来实现相似C++类继承的方法,这样方法的核心要点就是结构体的强制类型转换,让我来简单分析分析C语言中的结构体强制类型转换 ...

  4. c语言 float定义nan,nan - [ C语言中文开发手册 ] - 在线原生手册 - php中文网

    在头文件中定义float       nanf( const char* arg );(since C99) double      nan( const char* arg );(since C99 ...

  5. c语言float型存储方式,C语言float型数据在内存中的储存方式

    WPF 自定义IconButton 自定义一个按钮控件 按钮控件很简单,我们在项目中有时把样式封装起来,添加依赖属性,也是为了统一. 这里举例,单纯的图标控件怎么设置 1.UserControl界面样 ...

  6. c语言 字符串转换为int或float

    在c语言编程中,经常会遇到将字符串或者字符数组内的数据转换为int型数据或者float型数据,网上找了好多方法,结果都不可行,可能是C++的函数吧.在经过多方询问后,发现可以用atoi和atof函数来 ...

  7. c语言float的使用,C语言double和float实例分析用法

    C语言double和float 实例分析用法 C语言double和float 实例分析用法 小数也称实数或浮点数.例如,0.0.75.0.4.023.0.27.-937.198 都是合法的小数.这是常 ...

  8. C语言十六进制转换为八进制(附完整源码)

    十六进制转换为八进制 C语言十六进制转换为八进制完整源码 C语言十六进制转换为八进制完整源码 #include <stdio.h>int main() {#define MAX_STR_L ...

  9. C语言字符串转换为整数(附完整源码)

    C语言字符串转换为整数 C语言字符串转换为整数完整源码 C语言字符串转换为整数完整源码 #include <assert.h> #include <stdio.h> #incl ...

最新文章

  1. keras 的 example 文件 variational_autoencoder.py 解析
  2. 8年面试官问到:数据库自增 ID 用完了会咋样?
  3. 使用etcd+confd管理nginx配置
  4. python要学多久-python需要学多久?自学两年也很难达到企业标准!
  5. 5.2 matlab多项式计算(多项式的四则运算、求导、求值、求根)
  6. 107条javascript常用小技巧
  7. yuv420(planer) to bgr24 to bmp
  8. STM32-通用定时器-PWM输出
  9. 中国人工智能学会通讯——深度学习与推荐系统 1.2 基于特征的推荐 (Feature-based Recommendation)...
  10. android+1m的大小,android raw读取超过1M文件的方法
  11. 【分享】自身经历谈一谈自然语言处理领域如何学习?
  12. Playing with OS(操作系统)
  13. spark记录(7)SparkCore的调优之数据倾斜调优
  14. Linux下PHP+MySQL+CoreSeek中文检索引擎配置
  15. 《keras中文文档》资料分享
  16. ModelAndView 详解
  17. 3Q双向可控硅与4Q双向可控硅对比好处
  18. VS2003 搜索直接导致卡死问题
  19. python爬取电子病历_利用 BERT 模型解析电子病历
  20. 用python在大麦网抢票_大麦网抢票python+selenium实现

热门文章

  1. 自动化创建tornado项目
  2. 数据结构与算法之--高级排序:shell排序和快速排序
  3. 允许远程用户登录访问mysql的方法
  4. TestNg的IReporter接口的使用
  5. JAVA 字符串驻留池
  6. 51 -算法 -斐波拉奇数列 -LeetCode 70 -递推
  7. linux求生之路字体乱码,Linux中文字符出现乱码怎么办
  8. Java面向对象之静态属性静态方法、访问权限、getter与setter
  9. C++类型转换实现不同类型相加【复数与double类型相加】
  10. idea运行报错Parameter ‘name‘ not found. Available parameters are [arg1, arg0, param1, param2]