零.前言
这两天帮忙给实验室公众号写一篇推文,主要面向大一的萌新。所以重新回顾了一下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;变量的定义。
让我们一个一个来解释
变量的类型
- 简单来说,变量是类型就是盒子的种类。
- 为什么变量要有不同的类型呢?
- 在计算机里,所有的存储数据都是0/1。而且存储的资源也是有限的。为了存储不同种类(整数,实数,字符),不同大小的数据,人们将数据人为分为不同的数据类型。并且对每种类型规定了所占用内存空间的大小,以及数据和0/1之间的转换方式。
- 那么对于变量而言,它的类型到底意味着什么呢
- 变量的类型两个作用
- 说明变量所要占用的内存空间的大小(申请多大的空间),
- 数据和0/1之间的转换方式(如何存储数据和解释数据的标准)
- 那我们来看看,c语言中它的常用变量类型都有那些?
变量名
- 就是‘a’。a其实是对刚申请的内存空间起的一个名字。为了和其他变量区分的标志。
但是呢,起名也有一定的规定,不可以随意起名
1.只能以英文字母、下划线( _ )、美元符号( )开头。后面可以接数字、英文字母、下划线和美元符号( ) 开 头 。 后 面 可 以 接 数 字 、 英 文 字 母 、 下 划 线 和 美 元 符 号 ( )
2.不能是C语言中的关键字上面的是必须要做到,下面的则是一些建议
1.变量名最好做到见名知义的程度
2.变量名不要和函数名一样
变量的定义。
- int a;这就是一个完整的变量定义语句。
- 那么,它代表什么含义呢呢?
- 它表示:申请一段内存大小为4字节的存储空间,其中数据的存储方式和解释为补码,并将段内存空间起名为a
3. 一点思考
为什么整形变量会存在溢出现象呢?
溢出: int a= 2147483647 int b = a+1; 最终 b = -2147483648
为什么浮点型变量会有精度的丢失现象呢?
float a = 12.0f; float b = 11.9; float c = a - b; 最终c=0.1000003815
三.深入了解变量
1. 预先了解,十进制转换为二进制,原码,补码。
十进制正整数如果转换为二进制
规则
除二取余,倒序排列
解释:将一个十进制数除以二,得到的商再除以二,依此类推直到商等于一或零时为止,倒取将除得的余数,即换算为二进制数的结果举例
52(10)=110100(2进制)
十进制小数如果转换为二进制
规则
乘二取整,正序排列
解释:对被转换的小数乘以2,取其整数部分(0或1)作为二进制小数部分,取其小数部分,再乘以2,又取其整数部分作为二进制小数部分,然后取小数部分,再乘以2,直到小数部分为0或者已经去到了足够位数。每次取的整数部分,按先后次序排列,就构成了二进制小数的序列举例
原码
规则
当数据为正数时:最高位为0,表示正数,其余位表示数据
当数据为负数时:最高为位1,表示负数,其余位表示数据举例
用8字节表示52和-52的原码
52:00110100(原)
-52:10110100(原)说明
- 虽然原码简单易懂,但是它存在一些问题,所以计算机中的数据存储并没有采用原码来表示
- 第一个问题:正0 负0,按照原码的规则既是正数也是负数的0存在两种书写方式即:00000000和10000000
- 第二个问题,无法进行正负数之间的运算,将-52和52之间转换后的原码进行加法运算,结果并不等于0
补码
规则
当数据为正数时:补码 = 原码
当数据为负数时:原码最高位不变(1),其余位按位取反,末尾加1举例
用8字节表示52和-52的原码
52:00110100(原) ~ 00110100(补)
-52:10110100(原) ~ 11001011(取反)~ 11001100(补)说明
- 补码解决了原码的两个问题。
- 补码就是c语言中的整形在内存中的数据的储存方式
2. 了解为什么整形变量的表示会发生溢出
- 先计算int的表示范围
- int占用4b的内存,就是32位。
- 那么32位表示正数最大值为:01111111 11111111 11111111 11111111() = 2147483647
- 32表示负数最大值为:10000000 00000000 00000000 00000000 = -2147483648
- 为什么会溢出呢
- 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.重新看一遍变量究竟是什么?
- 之前说过变量是内存设备里一段连续的存储空间,对于内存设备中存储空间我们并没有做过多的介绍。那么我们现在简单说一下它的两个特性
- 存储空间是连续的,一维线性的
- 存储空间出厂时,每一个存储单位都有一个编号(0~2^32),这个编号就是这个存储单位的地址。
2.指针究竟是什么呢
int *p;
- p就是一个int型指针变量。
- 看指针和变量的定义很像,那么指针和变量有什么关系呢?
- 指针就是一个变量,不过不同于一般的变量,指针变量所占用的空间都是4字节,所存储的是变量的地址。
- 那么指针前面的类型有什么作用呢?
- 指针前面的作用如下
- p+1时指针所跳跃的字节大小=指针类型的大小
- 读取p所指向的变量的数据时,所读取的内存大小和数据的解释方式都要依靠指针的类型。
- 指针前面的作用如下
五.不同数据类型间的类型转换
- 数据类型之间的转换分为自动类型转换和强制类型转换。顾名思义,前者是编译器自动完成的,后者是程序员强制将数据类型进行转换。
1.自动类型转换。
- 出现场景
同一句语句或表达式如果使用了多种类型的变量和常量(类型混用) - 规则
double ←── float 高
↑
long
↑
unsigned
↑
int ←── char,short 低
1.图中横向箭头表示必须的转换,如两个float型数参加运算,虽然它们类型相同,但仍要先转成double型再进行运算,结果亦为double型。纵向箭头表示当运算符两边的运
2.算数为不同类型时的转换,如一个long 型数据与一个int型数据一起运算,需要先将int型数据转换为long型, 然后两者再进行运算,结果为long型。所有这些转换都是由系统自动
进行的, 使用时你只需从中了解结果的类型即可。这些转换可以说是自动的。 其他场景中出现自动类型转换
1.在程序中将数据用printf函数以指定格式输出时,当要输出的盐据类型与输出格式不符时,便自动进行类型转换
2.赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给他。
3.函数调用中参数传递时,系统隐式地将实参转换为形参的类型后,赋给形参。
4.函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。举个栗子
1.例子1
#include <stdio.h>
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.强制类型转换
- 出现场景:
需要将一个表达式转换成指定类型。 - 规则
强制类型转换是通过类型转换运算来实现的。
其一般形式为: (类型说明符) (表达式)
其功能是把表达式的运算结果强制转换成类型说明符所表示的类型 - 举个栗子
#include<stdio.h>
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的类型并没有被改变。