c语言基础知识————变量

零.前言

这两天帮忙给实验室公众号写一篇推文,主要面向大一的萌新。所以重新回顾了一下c语言中关于变量的知识。
主要内容
1. 变量的三要素
2. 进制转换,原码,补码
3. 整形的溢出,浮点型的精度丢失问题
4. 初识指针
5. 自动类型转换和强制类型转换

一.引入

a=3;

  • 什么是常量,什么是变量,什么是运算符,什么表达式。

    • 上面这个语句中,‘3’是常量。 ‘=’是运算符。‘a’是变量。而’a=3’这个完整的就是一个表达式。
    • 上面整个表达式的含义:就是将常量‘3’赋值给‘a’这个变量。
  • 我们今天主要来谈谈变量,就是‘a’

二.变量

1. 变量是什么

  • 那么变量究竟是什么呢?一个字母‘a’为什么就成为了变量呢?
    • 变量就是内存设备里一段连续的存储空间(类似于一个盒子,用来存储数据)。
    • 目前,严格来说,‘a’目前还算不是一个变量。因为它没有经过变量三要素的洗礼。

2. 变量的三要素

int a;

  • 上面这个语句,就包含变量的三要素。
    • int 变量类型。
    • a 变量名 。
    • int a;变量的定义。
  • 让我们一个一个来解释

    1. 变量的类型

      • 简单来说,变量是类型就是盒子的种类。
      • 为什么变量要有不同的类型呢?
      • 在计算机里,所有的存储数据都是0/1。而且存储的资源也是有限的。为了存储不同种类(整数,实数,字符),不同大小的数据,人们将数据人为分为不同的数据类型。并且对每种类型规定了所占用内存空间的大小,以及数据和0/1之间的转换方式。
      • 那么对于变量而言,它的类型到底意味着什么呢
      • 变量的类型两个作用
        1. 说明变量所要占用的内存空间的大小(申请多大的空间),
        2. 数据和0/1之间的转换方式(如何存储数据和解释数据的标准)
      • 那我们来看看,c语言中它的常用变量类型都有那些?
        这里写图片描述
    2. 变量名

      • 就是‘a’。a其实是对刚申请的内存空间起的一个名字。为了和其他变量区分的标志。
      • 但是呢,起名也有一定的规定,不可以随意起名

        1.只能以英文字母、下划线( _ )、美元符号( )线( ) 开 头 。 后 面 可 以 接 数 字 、 英 文 字 母 、 下 划 线 和 美 元 符 号 ( )
        2.不能是C语言中的关键字

      • 上面的是必须要做到,下面的则是一些建议

        1.变量名最好做到见名知义的程度
        2.变量名不要和函数名一样

    3. 变量的定义。

      • int a;这就是一个完整的变量定义语句。
      • 那么,它代表什么含义呢呢?
      • 它表示:申请一段内存大小为4字节的存储空间,其中数据的存储方式和解释为补码,并将段内存空间起名为a

3. 一点思考

  1. 为什么整形变量会存在溢出现象呢?

    溢出:
    int a= 2147483647
    int b = a+1;
    
    最终 b = -2147483648
  2. 为什么浮点型变量会有精度的丢失现象呢?

    float a = 12.0f;
    float b = 11.9;
    float c = a - b;
    
    最终c=0.1000003815

三.深入了解变量

1. 预先了解,十进制转换为二进制,原码,补码。

  1. 十进制正整数如果转换为二进制

    • 规则

      除二取余,倒序排列
      解释:将一个十进制数除以二,得到的商再除以二,依此类推直到商等于一或零时为止,倒取将除得的余数,即换算为二进制数的结果

    • 举例
      c语言基础知识————变量_第1张图片

    52(10)=110100(2进制)

  2. 十进制小数如果转换为二进制

    • 规则

      乘二取整,正序排列
      解释:对被转换的小数乘以2,取其整数部分(0或1)作为二进制小数部分,取其小数部分,再乘以2,又取其整数部分作为二进制小数部分,然后取小数部分,再乘以2,直到小数部分为0或者已经去到了足够位数。每次取的整数部分,按先后次序排列,就构成了二进制小数的序列

    • 举例
      c语言基础知识————变量_第2张图片

  3. 原码

    • 规则

      当数据为正数时:最高位为0,表示正数,其余位表示数据
      当数据为负数时:最高为位1,表示负数,其余位表示数据

    • 举例

      用8字节表示52和-52的原码
      52:00110100(原)
      -52:10110100(原)

    • 说明

      • 虽然原码简单易懂,但是它存在一些问题,所以计算机中的数据存储并没有采用原码来表示
      • 第一个问题:正0 负0,按照原码的规则既是正数也是负数的0存在两种书写方式即:00000000和10000000
      • 第二个问题,无法进行正负数之间的运算,将-52和52之间转换后的原码进行加法运算,结果并不等于0
  4. 补码

    • 规则

      当数据为正数时:补码 = 原码
      当数据为负数时:原码最高位不变(1),其余位按位取反,末尾加1

    • 举例

      用8字节表示52和-52的原码
      52:00110100(原) ~ 00110100(补)
      -52:10110100(原) ~ 11001011(取反)~ 11001100(补)

    • 说明

      • 补码解决了原码的两个问题。
      • 补码就是c语言中的整形在内存中的数据的储存方式

2. 了解为什么整形变量的表示会发生溢出

  1. 先计算int的表示范围
    • int占用4b的内存,就是32位。
    • 那么32位表示正数最大值为:01111111 11111111 11111111 11111111() = 2147483647
    • 32表示负数最大值为:10000000 00000000 00000000 00000000 = -2147483648
  2. 为什么会溢出呢
    • 2147483647 + 1 = 01111111 11111111 11111111 11111111+1=10000000 00000000 00000000 00000000 = -2147483648

3. 了解为什么浮点型变量会有精度丢失

12-11.9=?
* 将11.9化为二进制为“1011. 1110011001100110011001100…”,可以看出11.9转换为二进制是一个无穷的小数,按照浮点数的存储形式(IEEE754)的时候需要舍弃一些二进制。所以在计算机中永远无法准确存储11.9这个数字。所以计算时会产生误差。
* 扩展,如果有兴趣,可以具体了解一下IEEE754是如何规定浮点数的存储的。

四.初探指针。

1.重新看一遍变量究竟是什么?

  • 之前说过变量是内存设备里一段连续的存储空间,对于内存设备中存储空间我们并没有做过多的介绍。那么我们现在简单说一下它的两个特性
    1. 存储空间是连续的,一维线性的
    2. 存储空间出厂时,每一个存储单位都有一个编号(0~2^32),这个编号就是这个存储单位的地址。

2.指针究竟是什么呢

int *p;

  • p就是一个int型指针变量。
  • 看指针和变量的定义很像,那么指针和变量有什么关系呢?
    • 指针就是一个变量,不过不同于一般的变量,指针变量所占用的空间都是4字节,所存储的是变量的地址。
  • 那么指针前面的类型有什么作用呢?
    • 指针前面的作用如下
      • p+1时指针所跳跃的字节大小=指针类型的大小
      • 读取p所指向的变量的数据时,所读取的内存大小和数据的解释方式都要依靠指针的类型。

五.不同数据类型间的类型转换

  • 数据类型之间的转换分为自动类型转换和强制类型转换。顾名思义,前者是编译器自动完成的,后者是程序员强制将数据类型进行转换。

1.自动类型转换。

  1. 出现场景
    同一句语句或表达式如果使用了多种类型的变量和常量(类型混用)
  2. 规则

    double ←── float 高

    long

    unsigned

    int ←── char,short 低
    1.图中横向箭头表示必须的转换,如两个float型数参加运算,虽然它们类型相同,但仍要先转成double型再进行运算,结果亦为double型。纵向箭头表示当运算符两边的运
    2.算数为不同类型时的转换,如一个long 型数据与一个int型数据一起运算,需要先将int型数据转换为long型, 然后两者再进行运算,结果为long型。所有这些转换都是由系统自动
    进行的, 使用时你只需从中了解结果的类型即可。这些转换可以说是自动的。

  3. 其他场景中出现自动类型转换

    1.在程序中将数据用printf函数以指定格式输出时,当要输出的盐据类型与输出格式不符时,便自动进行类型转换
    2.赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给他。
    3.函数调用中参数传递时,系统隐式地将实参转换为形参的类型后,赋给形参。
    4.函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。

  4. 举个栗子

1.例子1
#include 
int main(void)
{
    char a='1';
    int  b=-4;
    unsigned c=2;
    printf("%d %d %d \n",a,a+b,b+c);
    //结果为49 45 -2   
}
  • 问题:b是int(整形) c是unsigned(无符号整形)。按照算术运算自动类型转换规则 b+c结果应该是一个unsigned类型。可是输出的结果是-2,明显不是无符号unsigned啊。难道是之前的规则写错了吗?还是输出写错了呢?
  • 回答:嗯,规则没有错,输出也没有错。原因呢,是因为输出的时候又进行了一次类型转换。%d是输出整形的格式控制符。所以呢 b+c结果为无符号整形,但输出的时候因为控制输出符%d,所以又转换为整形 。如果需要转换为无符号整形,应该用%u.
2.例子2
int main(void)
{
    char a='1';
    int  b=-4;
    unsigned c=2;
    printf("%d %d %u \n",a,a+b,b+c);
    //结果为49 45 4294967294   
}
  • 问题:那么你怎么证明,是因为b+c运算结果为无符号类型。还是%u将结果转换为无符号类型。
  • 回答:真是很好的问题。如果算术运算和输出都会导致自动转换,这样子我们永远无法知道,是什么导致自动转换。所以我们要控制变量,不能用将结果输出查看自动转换的原因。那么该如果进行证明int+unsigned会转换成unsigned。继续来做下一个实验
3.例子3
int main(void)
{
    int  b=-4;
    unsigned c=2;
    if(b>c)
        printf("整形b-4大于无符号c2 \n");
    else
        printf("无符号c2大于整形b-4\n");   
    //结果:整形b-4大于无符号2
} 
  • 问题:问题:好奇怪啊,-4居然比2大,为什么呢?
  • 回答:
    • b=-4 = 10000000 00000000 00000000 00000100(原) = 11111111 11111111 11111111 11111100(补);所以b中存的是上面这堆01码
    • c=2= 00000000 00000000 00000000 00000010(原) = 00000000 00000000 00000000 00000010(补):所以c中存的上面这堆01码
    • 运算b>c时,b是整形,c是无符号整形。所以将b转换为无符号整形。无符号整形最高位不表示符号位,也表示数据。所以b中存的11111111 11111111 11111111 11111100= 4294967292 c依然是无符号整形为2。
    • 4294967292>2为真,b>c表达式结果为1,执行printf(“整形b-4大于无符号c2 \n”);语句
    • 所以实际上不是-4>2。而是4294967292>2

2.强制类型转换

  1. 出现场景:
    需要将一个表达式转换成指定类型。
  2. 规则
    强制类型转换是通过类型转换运算来实现的。
    其一般形式为: (类型说明符) (表达式)
    其功能是把表达式的运算结果强制转换成类型说明符所表示的类型
  3. 举个栗子
#include
int main (void)
{
    float b=3.1415;
    double a = (int)b;
    printf("a=%lf,b=%lf\n",a,b); 
    //结果 a=3.000000,b=3.141500
} 
  • a=(int)b;先将b强制转换为int。即:(int)3。
  • a为double类型。所以继续将(int)3自动转换为double类型(结果为:3.000000)
  • 所以输出:结果 a=3.000000,b=3.141500
  • 同时可以看出来b的类型并没有被改变。

你可能感兴趣的:(c语言)