01.1.1 数据类型-常量-变量(整型-浮点-字符)
1 数据类型
2 常量
常量是指在程序运行过程中,其值不发生变化的量。常量又可分为整型、 实型(浮点型)、字符型和字符串型。
3 变量
变量代表内存中具有特定属性的一个存储单元,它用来存放数据,即变量的值。这些值在程序的执行过程中是可以改变的。
变量名实际上以一个名字代表一个对应的存储单元地址,编译、链接程序时,由编译系统为每个变量名分配对应的内存地址(就是空间)。从变量中取值实际上是通过变量名找到内存中存储单元的地址,并从该存储单元中读取数据,如下图所示。
变量的命名规定如下:C语言规定标识符只能由字母、数字和下画线三种字符组成,并且第一个字符必须为字母或下画线。
注意:
(1)编译系统认为大写字母和小写字母是不同的字符,因此C语言要求对所有用到的变量做强制定义,即“先定义,后使用”。
(2)在选择变量名和其他标识符时,应尽量做到“见名知意”,即选择具有含义的英文单词(或其缩写)作为标识符。
(3)变量名不能与关键字同名!
4 整型数据
4.1 符号常量
符号常量直接替换
4.2 符号变量
注意:int i,变量i是4个字节。
5 浮点型数据
5.1 浮点型常量
5.2 浮点型变量
注意:float f,变量f是4个字节。
6 字符型数据
6.1 字符型常量
用单引号括起来的一个字符是字符型常量,且只能包含一个字符!
例如,'a'、'A'、'1'、' '是正确的字符型常量,而'abc’、"a"、" "是错误的字符型常量。下表中给出了各种转义字符及其作用。以"\"开头的特殊字符称为转义字符,转义字符用来表示回车、退格等功能键。
6.2 字符数据在内存中的存储形式及其使用方法
字符型变量使用关键字 char进行定义,一个字符型变量占用1字节大小的空间。一个字符常量存放到一个字符型变量中时,实际上并不是把该字符的字型放到内存中,而是把该字符的ASCII码值放到存储单元中。
打印(printf)字符型变量时,如果以字符形式("%c")打印,那么计算机会到 ASCII码表中查找字符型变量的ASCII码值,查到对应的字符后会显示对应的字符,如下图所示。
这样,字符型数据和整型数据之间就可以通用。字符型数据既可以以字符形式输出,又可以以整数形式输出,还可以通过运算获取想要的各种字符,请看下面例子。
对于字符型变量,无论是赋 ASCII码值还是赋字符,使用%c打印输出时得到的都是字符,使用%d打印输出时得到的都是 ASCI码值,将小写字母转换为大写字母时,由ASCII码表发现小写字母与大写字母的差值为32,因此将c减去32就可以得到大写字母A。
7 字符串型常量
字符串型常量是由一对双引号括起来的字符序列。例如,"How do you do."、"CHINA"、"a"和"$123.45"是合法的字符串型常量,我们可用语句 printf("How do you do.")输出一个字符串,但要注意的是,'a'是字符型常量,而"a"是字符串型常量,二者是不同的。
例如,如果先用语句char c定义字符型变量c,后令 c="a"或 c="CHINA",那么这样的赋值都是非法的,原因是不可以将字符串型常量赋值给字符型变量。C语言中没有定义字符串型变量的关键字,介绍字符数组时我们将详细讲解如何存放字符串。
C语言规定,在每个字符串型常量的结尾加一个字符串结束标志,以便系统据此判断字符串是否结束。C语言规定以字符'/0'作为字符串结束标志。
例如,字符串型常量"CHINA"在内存中的存储结果如下图所示,它占用的内存单元不是5个字符,而是6个字符,即大小为6字节,最后一个字符为'/0'。然而,在输出时不输出'/0',因为'/0'无法显示。
01.1.2 混合运算-printf
1 混合运算
类型强制转换。整型数进行除法运算时,如果运算结果为小数,那么存储浮点数时一定要进行强制类型转换。
2 print函数
printf 函数可以输出各种类型的数据,包括整型、浮点型、字符型、字符串型等,实际原理是 printf函数将这些类型的数据格式化为字符串后,放入标准输出缓冲区,然后将结果显示到屏幕上。
位于%和格式化命令之间的一个整数被称为最小字段宽度说明符,通常会加上空格来控制格式。
- 用%f精度修饰符指定想要的小数位数。例如,%5.2f会至少显示5位数字并带有2位小数的浮点数。
- 用%s精度修饰符简单地表示一个最大的长度,以补充句点前的最小字段长度。
printf函数的所有输出默认是右对齐的,在%符号后放一个负号代表左对齐。例如,%-5.2f会显示5位字符、2位小数位的浮点数并且左对齐。
01.1.3 整型进制转换
1 整型常量的不同进制表示
计算机中只能存储二进制数,即0和1,而在对应的物理硬件上则是高、低电平,为了更方便地观察内存中的二进制数情况,除我们正常使用的十进制数外,计算机还提供了十六进制数和八进制数。
下面介绍不同进制数的对应关系。
首先,在计算机中,1字节为8位,1位即二进制的1位,它存储0或1。int型常量的大小为4字节,即32位。
设有二进制数0100 1100 0011 0001 0101 0110 1111 1110,其最低位是2的零次方,代表数值的最高位是2的30次方,最高位为符号位,符号位为1时是补码。
上面的二进制数对应的八进制数是 011414253376,它以0开头标示,数位的变化范围是0~7。二进数转换为八进制数的方式是,对应的二进制数每3位转换为1位八进制数,首先将上面的二进制数按每3位隔开,得到01 001 100 001 100 010 101 011 011 111 110,然后每3位对应0~7范围内的数进行对应转换,得到八进制数011414253376。由于实际编程时,识别八进制数时前面需要加0,所以在前面加了一个0。
上面的二进制数对应的十六进制数是0x4C3156FE,它以0x开头标示,数位的变化范围是0~9和A~F,其中A代表10,F代表15,对应的二进制数每4位转换为1位十六进制数,十六进
制在观察内存时需要频繁使用。
上面的二进制数对应的十进制数是1278301950,具体计算需要以2的幂次相加依次来计算是来实现(为1的位置就需要2的幂次,为零不需要),最好通过计算器来进
行。
二进制:0和1
十进制:0-9
八进制:0-7
十六进制:0-9 a-f
目前我们执行到语句 int i = 123,变量i会在内存上被分配空间,大小为4字节,会看到如下图所示,其中 i 的值变为7b,其十进制值为 7x16+11=123。i 的值是 0x0000007b。
#include <stdio.h>
//进制转换
int main() {
int i = 123;
printf("%d\n",i); //十进制输出
printf("%o\n",i); //八进制输出
printf("%x\n",i); //十六进制输出
return 0;
}
输出结果
我们以十六进制方式查看内存, 为什么显示结果为7b 00 00 00?
原因是英特尔的CPU采用了小端方式进行数据存储,因此低位在前、高位在后。
十进制数转换为二进制数
Eg:十进制数 123 转换为二进制数
方法:让123不断地除以2,并把余数写在右边,把商写在下方,直到商为0,然后逆序写出所有余数,即可得到转换后的二进制数1111011。详细过程如下图所示。
对应的十六进制数为76,十进制数转换为十六进制数的方式是除以16,十进制数转换为八进制数的方式是除以8。
小技巧
手动转换一个数的进制后,若不知道转换后的进制数是否正确,则可在视窗操作系统下选择"开始"一>"计算器"(或win+r打开运行,输入calc),打开"计算器",然后选择菜单项"查看"一>"程序员",得到如下图所示的计算器。输入一个十进制数后,单击"十六进制""八进制"或"二进制",即可得到对应进制的转换结果。
01.1.4 scanf读取标准输入
常用的数据输入/输出函数
如下图所示,程序员可以给程序输人数据,程序处理后会返回一个输出,C语言通过函数库读取标准输入,然后通过对应函数处理将结果打印到屏幕上,前面我们学习了printf函数,理解了通过 printf函数可以将结果输出到屏幕上。下面详细讲解标准输人函数 scanf。
1 scanf函数的原理
C语言未提供输入/输出关键字,其输入和输出是通过标准函数库来实现的。C语言通过scanf 函数读取键盘输入,键盘输入又被称为标准输入。当scanf 函数读取标准输入时,如果还没有输入任何内容,那么scanf函数会被卡住(专业用语为阻塞),下面来看一个例子。
【例】scanf读取标准输入
#include <stdio.h>
//scanf时,%d %f发现里面有空格或\n --忽略
//scanf时,%c --不忽略
int main() {
int i = 10;
char c;
scanf("%d",&i); //注意一定要取地址
//scanf用来读取标准输入,scanf把标准输入内的内容,需要放到某个变量空间里
//因此变量必须取地址
printf("%d\n",i);
// fflush(stdin); //清空标准输入缓冲区
scanf("%c",&c);
printf("c=%c\n",c);
return 0;
}
执行时输入 20,然后回车,显示结果如下图所示。为什么第二个scanf函数不会被阻塞呢?其实是因为第二个scanf函数读取了缓冲区中的'\n',即 scanf("%c",&c)实现了读取,打印其实输出了换行,所以不会阻塞。
程序执行结果
但是如果我们将上面例子中注释的标准输入缓冲区fflush(stdin)打开,就会发现第二个scanf("%c",&c)会阻塞,这是什么原因呢?下面介绍缓冲区原理。
行缓冲:在这种情况下,当在输入和输出中遇到换行符时,将执行真正的I/O处理操作。这时,我们输入的字符先存放到缓冲区中,等按下回车键换行时才进行实际的IO操作,典型代表是标准输入缓冲区(stdin)和标准输出缓冲区(stdout),printf使用的是stdout。
在使用scanf进行输入时,输入的其实为一串字符串,而字符串里为'键盘输入内容\n'。
如上面的例子所示,我们向标准输入缓冲区中放入的字符为'20\n',输入'\n'(回车)后,scanf函数才开始匹配,scanf 函数中的%d 匹配整型数 20,然后放入变量i中,接着进行打印输出,这时'\n'仍然在标准输入缓冲区(stdin)内,如果第二个scanf函数为scanf("%d",&i),那么依然会发生阻塞,因为scanf函数在读取整型数、浮点数、字符串时,会忽略'\n'(回车符)、空格符等字符(忽略是指scanf函数执行时会首先删除这些字符,然后再阻塞)。scanf函数匹配一个字符时,会在缓冲区删除对应的字符,因为在执行scanf("%c",&c)语句时,不会忽略任何字符,所以scanf("%c",&c)读取了还在缓冲区中残留的'\n'。
2 多种函数类型混合输入
当我们让scanf函数一次读取多种类型的数据时,对于字符型数据要格外小心,因为当一行数据中存在字符型数据读取时,读取的字符并不会忽略空格和'\n'(回车符),所以使用方法如下例所示。编写代码时,我们需要在%d与%c之间加入一个空格。输入格式和输出效果如下图所示,scanf 函数匹配成功了4个成员,所以返回值为4,我们可以通过返回值来判断scanf函数匹配成功了几个成员,中间任何有一个成员匹配出错,后面的成员都会匹配出错。
【例】一次读取多种类型的数据
#include<stdio.h>
int main(){
int i,ret; //ret为scanf返回值,通过返回值来判断scanf函数匹配成功了几个成员
char c;
float f;
ret = scanf("%d %c%f",&i,&c,&f); //要在%c前加一个空格
printf("i=%d,c=%c,f=%5.2f\n",i,c,f);
return 0;
}
输出结果
练习题
1.printf的输出默认是左对齐
A.正确 B.错误
答案:B
解释:printf的输出默认是右对齐,不是左对齐。如果需要左对齐,那么加人负号。
2.整型数124对应的十六进制值是0x7c
A.正确 B.错误
答案:B
解释:把一个10进制数转为16进制,只要不断除16即可,124除16,商是7,余数是12,而12就是c,因此是 0x7c。