当前位置:   article > 正文

从零开始学C语言系列之第二章《C语言概述及基本知识2》

从零开始学C语言系列之第二章《C语言概述及基本知识2》

往期回顾

【第一章】《认识C语言》
【第二章】C语言概述及基本知识1


进制

  要想理解数据的存储,首先要明白最基本的二进制问题,因为,这是计算机中数据最基本的形式,首先看下面的问题:

​ 1、什么是二进制?进制的概念?

​ 2、计算机中为什么要用二进制?

​ 3、二进制和符合人类思维的十进制之间的关系?

​ 4、为什么又会出现八进制、十六进制?

​ 5、所有进制之间的转换?

进制的概念

​  进制也就是进位制,是人们规定的一种进位方法。 对于任何一种进制—X进制,就表示某一位置上的数运算时是逢X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一

​ (1)、每一种数制都有笃定的符号集。例如,十进制数制的基本符号有十个:0,1,2。。。,9。

​ 例如,二进制数制的基本符号有两个:

​                 0和1.


​ (2)、每一种数制都使用位置表示法。即处于不同位置的数符所代表的值不同,与它所在位的权值有关。

例如:十进制1234.55可表示为

​      1234.55=1×103+2×102+3×101+4×100+5×10(-1)+5×10(-2)



计算机中为什么要用二进制

  电脑使用二进制是由它的实现机理决定的。我们可以这么理解:电脑的基层部件是由集成电路组成的,这些集成电路可以看成是一个个门电路组成,(当然事实上没有这么简单的)。


  当计算机工作的时候,电路通电工作,于是每个输出端就有了电压。电压的高低通过模数转换即转换成了二进制:高电平是由1表示,低电平由0表示。也就是说将模拟电路转换成为数字电路。这里的高电平与低电平可以人为确定,一般地(简易理解),2.5伏以下即为低电平,3.2伏以上为高电平



好处:

​ (1)技术实现简单,计算机是由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开,这两种状态正好可以用“1”和“0”表示。

​ (2)简化运算规则:两个二进制数和、积运算组合各有三种,运算规则简单,有利于简化计算机内部结构,提高运算速度。

​ (3)适合逻辑运算:逻辑代数是逻辑运算的理论依据,二进制只有两个数码,正好与逻辑代数中的“真”和“假”相吻合。

​ (4)易于进行转换,二进制与十进制数易于互相转换。

​ (5)用二进制表示数据具有抗干扰能力强,可靠性高等优点。因为每位数据只有高低两个状态,当受到一定程度的干扰时,仍能可靠地分辨出它是高还是低



进制间的相互转换问题

常用的进制有二进制、十进制、八进制和十六进制

①、八进制、十六进制、二进制-------------->十进制

都是按权展开的多项式相加得到十进制的结果。

比如

​   二进制1010.1到十进制:1×2^3 + 0×2^2 + 1×2^1 + 0×2^0 + 1×2^(-1)=10.5

​   八进制13.1到十进制:1×8^1 + 3×8^0 + 1×8^(-1)=11.125

​   十六进制13.1到十进制:1×16^1 + 3×16^0 + 1×16^(-1)=19.0625



②、十进制-------------->八进制、十六进制、二进制

​   都是按照整数部分除以基数(r)取余,小数部分乘以基数(r)取整

​   十进制10.25 到二进制:整数部分除2,一步步取余。小数部分乘2,一步步取整
在这里插入图片描述
  八进制到十进制,十六进制到十进制都是和上面的一样,只不过不在是除2乘2,而是8或者16了,这是根据自己的基数来决定的。




③、二进制<------------->八进制、十六进制

  二进制转换成八进制的方法是:从小数点起,把二进制数每三位分成一组,小数点前面的不够三位的前面加0,小数点后面的不够三位的后面加0,然后写出每一组的对应的十进制数,顺序排列起来就得到所要求的八进制数了。

  依照同样的思想,将一个八进制数的每一位,按照十进制转换二进制的方法,变成用三位二进制表示的序列,然后按照顺序排列,就转换为二进制数了。

  二进制数10101111.10111转换为八进制的数:(010 101 111.101 110)= 2 5 7.5 6=257.56

  八进制数257.56转换为二进制的数:2 5 7.5 6 =(010 101 111.101 110)=10101111.101

  二进制转换到十六进制差不多:从小数点起,把二进制数每四位分成一组,小数点前面的不够四位的前面加0,小数点后面的不够四位的后面加0,然后写出每一组的对应的十进制数,然后将大于9的数写成如下的形式,10---->A,11–>B,12---->C,13----->D,14----->E,15---->F,在顺序排列起来就得到所要求的十六进制了。

  同样,将一个十六进制数的每一位,按照十进制转换二进制的方法,变成用四位二进制表示的序列,然后按照顺序排列,就转换为二进制数了。

  二进制数 10101111.10111转换为十六进制的数:(1010 1111.1011 1000)=A F.B 8=AF.B8

十六进制AF.B8转换为二进制: A F.B 8=(1010 1111.1011 1000)=10101111.10111





变量及常量数据

变量

基本概念:

​   其他数据类型在程序运行期间可能会改变或被赋值,这些称为变量(variable)。

进阶概念:

​   变量分为全局变量局部变量,但是写代码的时候尽量使用局部变量。

函数:
/*
函数:
函数的也可以叫做子程序。
这里的 function 既可称为一个函数
*/

#include "stdio.h"
#include<string.h>
#include "math.h"

void function(void)
{
	/*
	内容
	*/
}

int main()
{
	/*
	主函数程序
	*/
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
全局变量:
/*
全局变量:
在所有函数外部声明的变量,称为全局变量。
var 在这里即是全局变量
这里定义的 in var=0; var既可以在函数function中使用,也可以在主函数main中使用
*/

#include "stdio.h"
#include<string.h>
#include "math.h"

int var=0;

void function(void)
{
	/*
	内容
	*/
}

int main()
{
	/*
	主函数程序
	*/
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
局部变量:
/*
局部变量:
在函数或一个代码块内部声明的变量,称为局部变量。
因为在函数function 中定义了一个变量var,  则var在这里称为是函数function的局部变量
这里定义的 in var=0; var只能在函数function中使用,不能再在主函数main中使用
在那个函数定义的局部变量,只能在那个函数中使用
*/



void function(void)
{
    int var=0;
	/*
	内容
	*/
}

int main()
{
	/*
	主函数程序
	*/
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24



重名时优先级:

  在C语言中全局变量与局部变量命名可以相同,使用冲突时,局部变量优先被使用。

正常情况下,变量数据的访问 :

**函数中如果出现了和全局变量重名的变量 :**
  • 1

简单理解,就近原则:谁离访问代码近~优先访问谁**

初学者一般建议不要重复命名

 // 注意,这里用的是c99标准
#include "stdio.h"
int a = 1;
int main()
{
	printf("%d\n",a);   //就近原则,此处输出结果为1 用的是全局变量
	int a = 10;
	printf("%d\n",a);    //此处输出结果为10	用的是局部变量
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10





整数

和数学的概念一样,在C语言中,整数是没有小数部分的数。
例如,2、-23 和 2456 都是整数。
而3.14.0.22 和 2.000 都不是整数。
  • 1
  • 2
  • 3





浮点数

浮点数与数学中实数的概念差不多。
2.75、3.16E7、7.00 和 2-8 都是浮点数。
注意,在一个值后面加上一个小数点,该值就成为一个浮点值。
所以,7 是整数,7.00 是浮点数。显然,书写浮点数有多种形式稍后将详细介绍 e 记数法,这里先做简要介绍:3.16E7 表示3.16X10(3.16 乘以 10的7次方)。其中107=10000000,7被称为 10的指数。
  • 1
  • 2
  • 3
  • 4



区别

  • 整数没有小数部分,浮点数有小数部分
  • 浮点数可以表示的范围比整数大。
  • 对于一些算术运算(如,两个很大的数相减),浮点数损失的精度更多。
  • 因为在任何区间内(如,1.0 到 2.0之间)都存在无多个实数,所以计算机的浮点数不能表示区间内所有的值。浮点数通常只是实际值的近似值。例如,7.0 可能被储存为浮点值 6.99999。





int 类型

有符号类型整数

  int 类型是有符号(既可以表示正数或者负数)整形,int 类型的值必须是整数(正整数/ 负整数/ 零)

​   int 类型取值范围依据计算机系统而言,早期PC用 16位, 目前个人计算机用的是 64位

  ISOC规定了int的实际长度/范围最小为-32768~32767

占用空间: int 建议为一个机器字长。32位环境下机器字长为4字节,64位环境下机器字长为8字节。
符号类型: 有符号类型
能表示的范围:一般64位计算机int取值范围为-2147483648~2147483647
  • 1
  • 2
  • 3

例子:

#include "stdio.h"
int main()
{
	int a = -10;
	printf("%d\n",a);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
无符号类型整数

​   在int前 加入c语言关键字unsigned 则会使int类型从有符号类型转化为无符号类型整数

  1. 无符号数据表示数量,只有正值
  2. unsigned无符号标识不会改变数据类型的字节大小
  3. 有符号型前面一般不写signed
  4. 除了表示范围与int不同,其他用法大致与 int 相同

unsigned int 无符号整形的范围为:0~65535

#include "stdio.h"
int main()
{
	unsigned int a = 10;
	printf("%d\n",a);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
整数溢出

  简单来说,当实际给的数据超出int的范围时,会从他的上/下限开始计数

有符号类型上溢出

  int的范围是-2147483648~2147483647

  所以当数据超过上限的时候,数据则会从 -2147483648 这个下限开始算起

#include "stdio.h"
int a = 2147483648;
int main()
{
	printf("%d\n",a);    //输出结果为-2147483648
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
正数的补码与原码反码都相同

[2^31]原=011111…1111
[2^31]反=011111…1111
[2^31]补=011111…1111

​   如果疑惑为什么1000…0000表示INT_MIN? 即-2^31呢?

​   原因如下:前面提到,整型数据都是以补码的形式储存的,对于负数,补码=反码+1;

[-2^31]原=100000…0000
[-2^31]反=111111…1111 (除符号位,其余取反)
[-2^31]补=100000…0000(反码+1,本来加上去,要进一位,但是由于符号位不变,进的那一位被抛弃)

2^31 + 1 =011111…1111(补) + 000000…0001(补) = 100000…0000(补)

100000…0000(补) = [-2^31]

所以得出结论:

​   INT_MAX + 1 = INT_MIN;
​   INT_MIN - 1 = INT_MAX;

有符号类型下溢出

  所以当数据超过下限的时候,数据则会从他的上限开始计数

#include "stdio.h"
int a = -2147483649;
int main()
{
	printf("%d\n",a);    //输出结果为2147483647
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述

在这里插入图片描述

无符号数据溢出

  无符号整型,也是32位,但是与int不同的是,他没有符号位,因此只能表示非负数.

与有符号的同理
在这里插入图片描述

总结

超过上限则从下限开始计数,超过下限则从上限开始计数

#include <stdio.h>
int main(void)
{
    int i = 2147483647;
    unsigned int j = 4294967295;
    
    printf("%d %d %d\n", i, i+1, i+2);
    printf("%u %u %u\n", j, j+1, j+2);
    
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  溢出行为是未定义的行为,所以系统并不会告知用户,因此需要在编程的时候注意这类行为





其他整形类型

short int(short)

​   占用的存储空间可能比int少,常用于较小数值的场合来节省空间

占用空间: short 至少占用2个字节。在16位环境下,short 为2个字节,int 为2个字节
符号类型: 有符号类型
  • 1
  • 2
long int(long)

​ 占用的存储空间可能比int多,常用于较大数值的场合

占用空间: 在16位环境下,long 为4个字节,对于32位的 Windows中 int 为4个字节,long 也为4个字节
符号类型: 有符号类型
  • 1
  • 2
long long int

​   占用的空间可能比long int 多,适用于更大数值的场合

占用空间: 该类型至少占8个字节
符号类型: 有符号类型
  • 1
  • 2
unsigned

​   适用于非负值的场合,这种类型与有符号类型表示的范围不同,例如16位的unsigned可以表示0-65536,而16位的int表示为-32768~32767





char 类型

  char型 用来储存字符,但是计算机用数字编码储存字符,如美国用ASCII码,所以字符和证书均可以表示char型,但是从技术层面来讲,char是整数类型char实际存储的是整数而不是字符,即用特定的整数来表示特定的字符(ASCII码)

char是最小的整数类型
在这里插入图片描述
2、表示方法
  字符可以用字母表示,也可以用整数表示;如在ASCII码中,65代表大写字母A
    char grade=’A’;
    char grade=65;
  注意:char是字符型,用单引号表示,’A’;而字符串用双引号表示,”A”



3、常表示字符
用char表示的字符除了大小写字母,还有以下几种转义字符:

注意,标红为常用转义字符
在这里插入图片描述




float, double类型

浮点数的概念

  浮点数也称小数或实数。例如,0.0、75.0、4.023、0.27、-937.198 都是合法的小数。

  C语言中采用float和double关键字来定义小数,float称为单精度浮点型,double称为双精度浮点型,long double更长的双精度浮点型。

  在任何区间内(如1.0 到 2.0 之间)都存在无穷多个实数,计算机的浮点数不能表示区间内所有的值。

float

  C标准规定,float类型至少能表示6位有效数字(指的是数字的前六位),且取值范围为:
1 0 − 37 到  1 0 37 10^{-37} 到 ~10^{37} 1037 1037

  通常,系统存储一个浮点数通常要占用32位,有8位用于表示指数的值与符号,剩下24位用于表示非指数的部分

double

  C标准规定,double类型至少能表示10位有效数字(指的是数字的前十位),double类型的至少有13位有效数字

  通常,系统存储一个浮点数通常要占用64位,多出来的32位用于表示非指数的部分



浮点值得上溢或下溢
当浮点值发生上溢的时候,会给toobig赋一个表示无穷大的特定值,如果用printf打印出来则会显示为inf或者infinity

当浮点值发生下溢的时候,则会丢失精度,因为计算机会把尾数部分的位向右边移动,空出一个二进制位,并且丢弃最后一个二进制数
  • 1
  • 2
  • 3





补充/详情

使用数据类型

​   编写程序的时候,应该合理选择所需的变量及其类型,通常用int或者float类型表示数字,用char表示字符

​   如果将一个类型的数值初始化给不同类型的变量的时候,编译器会把值转化成与变量相匹配的类型,这样会导致部分数据的丢失

如:

int cost = 12.99
float pi = 3.1415926535
    
/*最后编译器得出的
cost = 12 (将后面小数部分截断)
pi = 3.14159(只有6位精度)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6





存储方式

整型数据都以补码的形式储存在内存中。

无符号整形

​   无符号整形在数据中的存储无疑是最方便的,因为没有符号位,只表示正数,所以在存储计算方面都很简单。无符号整形在就是以纯粹的二进制串存储在计算机中的。
在这里插入图片描述

带符号整形

  对于带符号数,机器数的最高位是表示正、负号的符号位,其余位则表示数值。

先看下面的例子:

假设机器字长为8的话:

​     一个十进制的带符号整形 1,表达为二进制就是 (0000 0001)

​     一个十进制的带符号整形 -1,表达为二进制就是 (1000 0001)





原码

​   数值X的原码记为[x]原,如果机器字长为n(即采用n个二进制位表示数据)。则最高位是符号位。0表示正号,1表示负号,其余的n-1位表示数值的绝对值。

​   数值零的原码表示有两种形式:[+0]原=0000 0000 ,-[0]原=1000 0000.

例子:若机器字长n等于8,则

    [+1]原=0000 00001 [-1]原=1000 00001

    [+127]原=0111 1111 [-127]原=1111 1111

    [+45]原=0010 1101 [-45]原=1010 1101





反码

​   数值X的反码记作[x]反,如果机器字长为n,则最高位是符号位,0表示正号,1表示负号,正数的反码与原码相同负数的反码则是其绝对值按位求反。

​ 数值0的反码表示有两种形式:[+0]反=0000 0000 ,-[0]反=1111 1111.

例子:若机器字长n等于8,则

    [+1]反=0000 00001 [-1]反=1111 1110

    [+127]反=0111 1111 [-127]反=1000 0000

    [+45]反=0010 1101 [-45]反=1101 0010





补码

​   数值X的补码记作[x]补,如果机器字长为n,则最高位是符号位,0表示正号,1表示负号,正数的补码与原码反码都相同负数的补码则等于其反码的末尾加1

​   数值0的补码表示有唯一的编码:[+0]补=0000 0000 ,-[0]补=0000 0000.

例子:若机器字长n等于8,则

    [+1]补=0000 00001 [-1]补=1111 1111

    [+127]补=0111 1111 [-127]补=1000 0001

    [+45]补=0010 1101 [-45]补=1101 0011

可以看到

  1+(-1)=0 | (0000 0001)补+(1111 1111)补=(0000 0000)补=(0000 0000)原=【0】 可以看到。没有问题

  1+(-2)=-1 | (0000 0001)补+(1111 1110)补=(1111 1111)补=(1000 0001)原=【-1】 可以看到,没有问题

  -1+(2)=1 | (1111 1111)补+(0000 0010)补=(0000 0001)补 =(0000 0001)原=【1】 可以看到,没有问题





补码的原理(简易理解)
为什么原码不行?

    ( 1 ) - ( 1 ) = ( 1 ) + ( -1 ) = ( 0 )

    (00000001)原 + (10000001)原 = (10000010)原 = ( -2 ) 显然不正确.

通过上面原码计算式可以看出,当正数加上负数时,结果本应是正值,得到的却是负值(当然也有可能得到的是正数,因为被减数与减数相加数值超过0111 1111,即127,就会进位,从而进位使符号位加1变为0了,这时结果就是正的了)。而且数值部分还是被减数与减数的和。
  • 1

​   并且,当负数加上负数时(这里就拿两个数值部分加起来不超过0111 1111的来说),我们可以明显看出符号位相加变为0,进位1被溢出。结果就是正数了。

​   因此原码的错误显而易见,是不能用在计算机中的。





补数

​   要谈补码,先看看补数的问题。什么是补数,举个简单的例子,100=25+75。100用数学来说就是模M,那么就可以这样概括。在M=100的情况下,25是75的补数。这就是补数。

​   25是75的补数,这是在常规世界中,在计算机上就不是这样了,因为,在计算机中,数据存在这溢出的问题。

​   假设机器字长是8的话,那么能表达的最大无符号数就是1111 11111,在加1的话,就变成1 0000 0000 ,此时因为溢出,所以1去掉,就变成0了,这个很简单,相信学计算机的人都会明白。

​   也就是说,在计算机中,补数的概念稍微不同于数学之中,25+75=100,考略计算机中的溢出问题,那么25+75就等于0了。也就是说,25和75不是互为补数了。

​   我觉得用闹钟来比喻这个问题在形象不过了,因为闹钟也存在着溢出的问题,当时间到达11:59 ,在加1分钟的话就变成0:0了,这和计算机的溢出是同一个道理。

​   那么,有一个时钟,现在是0点,我想调到5点,有两种方法,一个是正着拨5,到5点。第二种方法是倒着拨7,也可以到5点。正着拨5记作+5,倒着拨7,记作-7,而闹钟的M是12,也就是说,在考略溢出的情况下,M=12,5是-7的补数。用个数学等式可以这样表达0+5=0±7,即0+5=0-7

​   这就是计算机中的数值计算和数学中的计算不同的地方。

  明白了计算机中补数的道理,那么就明白补码的问题了。还是用例子说明:

在计算机中计算十进制 1+(-2)。

    1的原码是:0000 0001

    -2的原码是:1000 0010

  -2的补码是:1111 1110 这个二进制换做无符号的整数大小就是254,而8位二进制数的M=28=256。(很多文章中把M写成27,这根本就是不对的,根本没有解决符号位的问题)

  你发现什么了没,当换成补码后,-2和254就是补数的关系。

  也就是1+(-2) 等价于了 1+254了。

      1+(-2) = (-1)

      -1 的补码是 1111 1111

这样做,好处在什么地方,你自己都可以看得到:

①、利用补数和溢出的原理,减法变成了加法

②、符号位不在是约束计算的问题,不会存在原码中的问题了,因为变成补码后,虽然最高位依然是1,但是这个1就不在是最为符号位了,而是作为一个普通的二进制位,参与运算了。

​   所以,这就是补码的原理所在,通过补数和溢出,解决了减法和负数问题。不知道各位理解了没有,额,反正我是通过这种方法安慰自己的,不知道是不是有失偏颇。





十进制数求补码

​   如果是正数,直接求它的原码,符号位为0

​   如果是负数,比较好的方法是先求十六进制,在由十六进制求二进制,符号位为1,在除了符号位都取反,在加1,即可得到补码。

补码就十进制 :

​   根据符号位判断,如果符号位是0,表示是正数,就是原码,直接转换就十进制即可。

​   如果符号为是1,表示是负数。那么,连符号位在内都取反,在加1,将该二进制转换为十进制,该十进制数即使该负数的绝对值,加个负号-,就得到该负数。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/513862
推荐阅读
相关标签
  

闽ICP备14008679号