目录
一、数据类型
1.1、什么是数据类型
数据类型就是一类相似数据的共同特征。编程是为了解决生活中的问题,比如网络商城中的商品有名称、编号、价格等属性,那么代码就需要具备描述这些不同属性的能力:字符用字符型描述、整数用整型描述、小数用浮点型描述......
1.2、数据类型有哪些
数据类型又分为内置类型(字符型、整型、浮点型、布尔型)和自定义类型(数组、结构体-struct、枚举-enum、联合体-union),本文章只讲述内置类型。
首先补充一个知识:在计算机中所有数据都是以二进制方式存储的,每一位上要么是0要么是1,一个二进制位包含的信息量就是一比特(bit)。下面是计算机的存储容量换算关系。
1字节(Byte,简写B)= 8比特(bit)
1千字节(KB)= 1024(2的10次方)B
1兆字节(MB)= 1024 KB
1吉字节(GB)= 1024 MB
1太字节(TB)= 1024 GB
以下代码中的[...]表示可省略。
(1)字符型
//字符型长度为 1字节
char
signed char
unsigned char
在编译器中char可能默认为有符号signed char,也可能默认为无符号unsigned char,到底是哪种取决于编译器,但是一般来讲都是把char默认为signed char。
(2)整型
//短整型(2字节) 占位符
[signed] short [int] %hd
unsigned short [int] %hu
//整型(4字节)
[signed] int %d
unsigned int %u
//长整型(4字节或者8字节)
[signed] long [int] %ld
unsigned long [int] %lu
//更长的整型,在C99中加入的(8字节)
[signed] long long [int] %lld
unsigned long long [int] %llu
短整型、整型、长整型、更长的整型是根据能储存的整型的长度区分的。short、long、long long完整写法应该在之后加一个int,C语言规定也可以省略掉简化代码。C语言还规定了整型默认是有符号signed的。长整型long的存储长度是4字节还是8字节取决于系统。
(3)浮点型
//单精度浮点数
float //4字节
//双精度浮点数
double //8字节
long double //是后面加入的,是8或12或16字节,取决于系统
一个小数比如 2.1415 可以写成 214.15 x 10²,小数点可以在每一位之间浮动,所以其类型被形象地称为浮点型。双精度比单精度的精度更大,我们来看一个例子:
可以发现double类型打印的数据是正确的,float类型打印的数据却改变了的。为什么呢?这就是double的精度比float更大的缘故。在VS2019的运行输出中能看到一个关于float类型的警告:
实际上所有数据在计算机上都以二进制存储,float类型的存储长度有32位,double类型的存储长度有64位,它们的长度构成如下:
浮点类型 符号位长度 尾数长度 指数长度 总长度
float 1 23 8 4B x 8 = 32 bit
double 1 52 11 8B x 8 = 64 bit
指数长度决定了浮点数的范围,指数的绝对值越大表示的浮点数的范围就越大;尾数的长度决定了浮点数的精度,尾数越长说明保留的二进制小数位越多。我们这里讨论的是浮点数的精度,所以只需关心尾数长度,建议不知道浮点数是怎么存储的先看一下IEEE浮点数表示法。因为float的尾数长度为23位并且整数位还有一个隐藏的1,所以float只能保留24位的二进制精度,装不下的部分都会截掉,24位二进制能表示的最大十进制数为16777215,所以float能保证的十进制数精度为7~8位。同理double类型能保证的十进制数精度为15~16位。
我们再来看看十进制数365.12345:
完整的二进制:
(1)0110'1101'.0001'1111'1001'1010'0110'1011'0101'0000'1011'0000'1111'0010......
double类型存储的二进制数:
(1)0110'1101'.0001'1111'1001'1010'0110'1011'0101'0000'1011'0000'1111
>> 十进制:365.12344'99999'99991'17790'1
float类型存储的二进制数:
(1)0110'1101'.0001'1111'1001'101
>> 十进制:365.12344'36035'15625
使用printf函数打印的时候会四舍五入保留小数点后6位,使得最终结果为:float类型打印的是365.123444,而double类型打印的是365.123450。
所以如果对浮点数机制不熟练的话,建议大家尽量使用double,可以避免很多因数据遗失误差太大而造成的问题。
(4)布尔型
在原来C语言没有为布尔值设置一个布尔类型,而是把整数0作为假,非零整数作为真。后来在C99中添加了布尔类型 _Bool,它包含在头文件<stdbool.h>中,布尔值规定为false为假,true为真。在<stdbool.h>中是这样定义false和true的取值:
#define false 0
#define true 1
1.3、有符号signed和无符号unsigned
只有字符型和整型数据才有signed和unsigned的概念。像温度、海拔这样的数据有正负号就需要定义为signed;像年龄这样没有正负的数据就定义为unsigned。有人会说这不是多此一举吗,管它有符号还是无符号,全部都是signed,填数据的时候不用符号不就行了。但是这样会浪费很多的存储空间,比如char类型,对于有符号的char它的取值范围为-128~127,而对于无符号的char它的取值范围为0~255,255直接比有符号的127大了一倍。所以区分有无符号可以最大化利用存储空间得到更大的存储数据范围。
有关字符型和整型的所有数据限制范围可参考头文件<limits.h>,浮点型参考<float.h>。为了方便查找文件,可以安装一个叫Everything的软件,如图:
1.4、sizeof操作符
sizeof操作符可以计算数据类型的长度,操作数可以是数据类型,也可以是个表达式,例如:
可以得到int的字长为4字节,表达式b = a + 1的字长也为4字节。当操作数为表达式时,()可以省略掉,计算出的值是表达式的类型的字长,如上图a定义为double,1在系统中默认为int,double类型的值与int类型的值相加时,为了避免数据的丢失会将int转为double,所以右边的值类型为double。而在C语言中,赋值时会发生强制转换,等号右边的数据类型强制转换为左边的数据类型。因为b是int,右边double类型值就会截断一部分数据转为int类型,最终求得表达式的类型的长为4字节。在VS2019中也出现了这样的警告,就是因为截断造成的:
最后打印b的值时发现是5,而a+1应该等于11才对。这是因为sizeof的操作数为表达式时,不对表达式进行计算的缘故。在编译阶段编译器就将sizeof的返回值类型根据操作数中表达式的类型确定了,然后把sizeof处理掉了;而表达式的计算是要在程序运行阶段执行的,经过编译后表达式就没有了,最后当然也不会执行表达式。
在上面的测试用例图中可以看到打印sizeof的结果这两行出现了绿色的波浪下划线,同时在VS2019中产生了如下警告:
这是因为C语言只规定了sizeof的返回值类型是无符号整型,但是至于是unsigned short、还是unsigned int、还是unsigned long、还是unsigned long long,由系统决定。而在printf函数打印时,他们的占位符分别是%hu、%u、%lu、%llu,所以同一代码在不同的系统中是不利移植的。故C语言给sizeof的返回值创造了一个size_t类型,不管是哪种无符号整型,都统一使用%zd作为占位符。将图中的%d改为%zd就不会出现警告了。
二、变量
2.1、变量的创建
数据类型的存在就是为了创建变量,创建变量的实质就是向内存申请存储空间。变量就是经常变化的数据,如:身高、体重等;常量就是恒定不变的数据,如:一个人的血型、圆周率值等。创建变量的格式如下:
data_type data; //变量的类型 变量名
//举例:创建字符型变量ch
char ch;
2.2、变量的分类
变量分为全局变量和局部变量,它们的区别:
变量类型 创建位置 作用域 生命期
全局变量 大括号外 可在整个项目中 程序执行的开始到结束
局部变量 大括号内 所在的大括号内 所在大括号内的执行的开始到退出
此外,内存大概分为三个区域:栈区、静态区、堆区。全局变量存储在静态区,而局部变量存储在栈区,堆区是用来动态内存管理的(malloc、calloc、realloc、free)。
关于相同名字的变量,总结为三点:
- 在同一作用域内,不允许定义变量重名。
- 在不同作用域内,允许定义的变量重名。
- 不同作用域内定义的重名变量,访问时采取就近原则。
在这个例子中可以看到第三个a只在第二个大括号内起作用,第二个a只在第一个大括号内起作用。虽然全局变量a在整个源文件中都起作用,但是根据就近原则,当变量名称相同时,最近的局部变量a会把前面的a覆盖掉。
关于变量类型的选择,尽量用局部变量,其次再考虑全局变量。虽然全局变量在整个项目中都有办法使用,看似非常方便,但是当很多地方都对全局变量进行改动时容易造成混乱而导致错误发生,在一个就是全局变量的生命期是从程序运行的开始到结束,所以会一直占据一部分存储空间,内存是很小的通常为8G或者16G,我们要在程序运行时尽可能地节省空间。
2.3、变量的初始化
在创建变量的同时给变量一个初始值就叫做变量的初始化,如下面的例子:
char ch = 'a';
三、操作符
操作符即运算符,叫法不同而已。操作符是为了方便计算而存在的。
3.1、算术操作符
算术操作符有:+、-、*、/、%,对应加、减、乘、除、取余,它们都是双目操作符。双目操作符的意思就是有两个操作数。+、-、*没什么特别的,需要注意的是/和%。
在C语言中做除法,当操作数都为整数时,若结果为小数,则会只保留整数部分而直接舍去小数部分;若有一个操作数为浮点数,则会将结果中的小数保留下来。测试用例如下:
取余的操作数都必须是整数,结果为两个操作数相除的余数。当操作数中存在负数时,计算结果的正负号由第一个操作数的正负号决定,测试用例如下:
3.2、赋值操作符
赋值操作符是=。在创建变量的时候同时给变量一个值叫做初始化;在创建变量后,再给变量一个值才叫赋值。如下面的例子:
int a = 1; //初始化
int b;
a = 10; //赋值
b = 10; //赋值
连续赋值如下,从右到左依次赋值:
int a = 1;
int b = 2;
int c;
c = a = b + 1; //连续赋值
不建议使用连续赋值,因为不容易理解;调试的时候一行就执行完了,不利于观察细节。
复合赋值就是把赋值操作符与其它操作符结合成一个操作符,这样可以简化代码。如下:
int a = 2;
a = a + 1; //第一种写法
a += 1; //第二种写法
//所有的复合赋值符
+= -= *= /= %=
>>= <<= |= &= ^=
3.3、单目操作符
单目操作符就是只有一个操作数的操作符。
(1)++和--
++和--分别被称为自增、自减操作符,它们能使操作数自身加(减)1。这两个操作符处于操作数的位置不同,又分为前置(操作符放置在操作数前)和后置(操作符放置在操作数后),从而导致功能上有些差别。简单来说,前置++(--)就是先加1(减1),再使用操作数;后置++(--)就是先使用操作数,再加1(减1)。
如上:前置++,b先自增1得3,然后再使用b赋值给a得3。
如上:后置++,先使用b赋值给a得2,然后b再自增1得3。
(2)-和+
正负号没啥特别的,正号加不加对数字都没有影响,加了也不算错。负号就是负负得正。
(3)强制类型转换
像上面这样的会报警告:
为了消除警告就要使用强制转换,把double类型强制转换为int:
强制转换能不用就不用,毕竟强扭的瓜不甜,这样也会导致数据遗失。
四、标准输出和输入函数
4.1、标准输出函数
标准输出函数就是printf函数 = print + format(格式化),作用就是将参数中的文本按限定格式打印到屏幕。printf函数在打印完文本后,光标就停在输出结束的位置;加一个\n,光标才会停在下一行首位置。如下:
如果使用了printf函数运行后,给出了这种错误:
说明没有给出头文件。因为printf函数是在<stdio.h>头文件里面定义的,所以在使用printf函数之前必须包含<stdio.h>头文件,scanf函数同理。
(1)占位符
占位符就是先占着位置,后面可以被替换成其它的数据。占位符的第一个字符是%,表示是占位符;最后几个字符表示可替换该占位符的数据的类型(比如int类型的就是d,double类型的就是lf);中间可能会有一些限制格式的符号(在后面“限定输出格式”这一节会讲)。文本参数中的占位符与后面的参数是按顺序一一对应的,printf函数内有n个占位符就应该一共有n + 1个参数(不考虑限定输出格式的参数情况下)。如果参数个数少于n + 1,就会发生下面的结果:
你胡来,那么编译器也会随便给你搞一个数。
为什么要有占位符呢?如果没有占位符,文本就是死的,想改数据就得去找对应的printf函数在哪,当代码很多的时候是比较麻烦的。现在有了占位符,改数据只需要找到有特殊含义名字的变量,并且一般变量的初始化和赋值都放在靠前的位置,比较方便查找。还有就是有些情况是从键盘得到数据的,不能够在代码中给变量初始化或赋值,所以需要用占位符占位,后面程序运行时才能得到数据并打印。
下面是按字母表顺序排列的占位符表,自行查阅:
%a 以p计数法表示的十六进制浮点数,字母小写。
%A 以p计数法表示的十六进制浮点数,字母大写。
%c 字符。
%d 十进制 int 类型。
%e 以科学计数法表示的浮点数,指数部分为小写e。
%E 以科学计数法表示的浮点数,指数部分为大写E。
%i 整数,能区分十进制、八进制、十六进制。
%f 浮点数,包括float和double。
%g 浮点数,并且会根据浮点数的精度自动选择使用%f还是%e,指数部分e为小写。
%G 浮点数,并且会根据浮点数的精度自动选择使用%f还是%e,指数部分E为大写。
%hd 十进制 short 类型。
%ho 八进制 short 类型。
%hx 十六进制 short 类型。
%hu unsigned short 类型。
%ld 十进制 long 类型。
%lo 八进制 long 类型。
%lx 十六进制 long 类型。
%lu unsigned long 类型。
%lld 十进制 long long 类型。
%llo 八进制 long long 类型。
%llx 十六进制 long long 类型。
%llu unsigned long long 类型。
%Le 科学计数法表示的 long double 类型。
%Lf long double 类型。
%n 前面已输出的字符数量,本身不输出,而是存储在参数中的变量里。
%o 八进制 int 类型。
%p 指针。
%s 字符串。
%u unsigned int 类型。
%x 十六进制 int 类型。
%zd size_t 类型。
%% 输出一个百分号。
① 对于%a和%A的补充:
p计数法是C99编程语言中用来表示浮点数的一种计数方法。p或P表示以2为底的阶码(指数),以0x开头的十六进制表示浮点数。以102.5为例,它的二进制为1100110.1 等价于 1.1001101 x 2^6,二进制1.1001101的十六进制为1.9a。故102.5的p计数法表示为0x1.9ap+6(注意,小数部分是十六进制的,指数部分是十进制的)。
② 对于%e和%E的补充:
为了简化公式,常采用科学计数法。一个数字被写成1~10之间的数字(不含1和10),然后让这个数字乘以10的整数次幂,其中用e或E来表示10的指数。比如102.9用科学计数法表示就是1.029e+02。
③ 对于%i的补充:
它与%d在printf函数中使用时没有区别;在scanf函数中使用时有区别,%d只能匹配十进制整数,而%i能匹配十进制、以0开头的八进制、以0x或0X开头的十六进制的整数。如下图的例子:
scanf中的%d会把056匹配为十进制的56;scanf中的%d会遇到x而匹配失败,只匹配到十进制的0(在后面“scanf处理用户输入的原理”中会讲)。
在scanf中使用%i,就会把056匹配为八进制数,把0x56匹配为十六进制数。
④ 对于%f的补充:
我们容易误解一个点,严格来说printf其实是没有%lf的,只不过很多编译器支持它,用%lf打印float和double数据没有出错。但是建议在printf中尽量用%f,不要用%lf,毕竟%f才是正规的。
在scanf中严格区分%f(float)和%lf(double),如果随便乱用会出现下面的错误:
红框里面是说,变量b周围的栈被破坏了,就是变量b栈溢出了。首先,scanf从键盘输入得到的是%lf(double)类型的数据,然后存进float类型的变量b里;其次,变量b为局部变量是存放在栈里的。因为过大的数据放进申请的存储空间较小的变量里,所以发生了栈溢出。因此在scanf中向float变量存放数据必须使用%f。
scanf中向double变量a存入数据使用%f也出现了问题。
⑤ 对于%g和%G的补充:
使用%g和%G时输出的必须是浮点数,如果输出整数会出错:
会自动删除多余的无效0:
会对浮点数进行四舍五入或直接舍去零头(简化并且对当前这个数影响不大):
会对过大或过小的数使用科学计数法:
%g不是准确(会根据需要四舍五入、舍去零头)输出原浮点数的,它的目的是对原浮点数产生尽可能小的影响下,进行最简化。
⑥ 对于%n的补充:
在VS里边直接用%n会出错,想用就得先用 _set_printf_count_output() 函数,参数设置为非0整数。如下:
(2)限定输出格式
① 限定宽度:
限定占位符的最小宽度。意思就是输出字符宽度小于限定宽度则补足空格;大于或等于限定宽度则该怎样就怎样。默认右对齐,想左对齐就要加个负号。验证如下:
② 限定小数位数:
printf默认保留小数点后6位数,想改变位数就如下做:
各种限定是可以复合使用的。
③ 限定总显示正号:
printf打印的时候默认正号不显示,只会显示出负号。想输出正号可以在占位符中加个正号,如下:
④ 限定输出字符串长度:
想限定输出的字符串长度就这样做:
限定的宽度、小数位数、输出字符串的长度,还能先用*占位,再通过参数参入数据:
4.2、标准输入函数
标准输入函数就是scanf函数 = scanf + format,作用就是根据占位符的类型,读取键盘输入的数据存入变量中。有几个占位符就有几个变量参数,跟printf不同的是,变量参数是变量的地址,变量前要加一个地址符&(指针变量除外,它存储的是内存地址。如字符串变量,因为字符串在C语言中是以字符数组的形式存在的,而数组的名称可以直接表示其首地址)。
在运行上面的程序时,到scanf这句会停下来,等待用户输入,输入好后按下回车键,scanf就会处理用户输入的数据,最后放入变量中。跟printf一样,它的头文件也是<stdio.h>。
(1)scanf处理用户输入的原理
首先,用户的输入是先存放在缓冲区里的。然后,等按下回车后scanf再根据占位符在缓冲区里从上一次读取剩下数据的第一个字符开始读取。最后,每个占位符的读取在碰到不匹配的字符或者缓冲区里的数据全部读完,就停止读取。对于除了%c的占位符,在没有匹配到对应数据前会忽略空白符(空格、制表符、换行符等),在匹配到对应数据后碰到空白符也会停止。
举例1:scanf("%s%s", &a, &b);
输入:空格空格空格abcd空格回车aaa回车
首先,输入了第一个回车,%s开始读取缓冲区的 空格空格空格abcd空格回车。
对于第一个%s:前面三个空格全部忽略掉 >> 依次读取到abcd >> 碰到空格停止读取,字符串末尾加一个\0 >> 第二个%s等待读取。
然后,如果没有新的输入就等待,直到输入第二个回车。从上一次剩的开始读 空格回车aaa回车。
对于第二个%s:前面的空格回车忽略掉 >> 依次读取到aaa,字符串末尾加一个\0 >> 碰到回车停止读取 >> 没有占位符了,结束。
举例2:scanf("%d", &a);
输入:12.34回车
%d先匹配到12 >> 碰到 . 不匹配%d,停止读取 >> 没有下一个占位符,结束。
最后,缓冲区还剩下.34回车。
我要单独说一下字符占位符%c,它跟其它的占位符不同,它不会忽略掉任何字符,只要是一个字符都能跟它匹配。比如下面的例子:
执行了第9行 >> 我输入了 abcd回车 >> 执行了第10行打印了abcd >> 看起来是结束了??
跟预期完全不符,为什么?事实上在%s读取到abcd并打印了abcd后,%c因为能匹配ASCII上的所有字符,只要是一个字符就行!所以它把回车匹配了,可以看到在屏幕的最后一行出现了回车。
怎么解决这个问题呢?要么在第10行后面加一个getchar(),把不想要的字符“吃掉”;要么使用fflush(stdin)把缓冲区清空(但是在一些新编译器上不能用了,比如VS2019);要么在第9行的%s后面加一个空白符,这样就能跳过零个或多个空白字符了。
(2)scanf的返回值
在网站:cplusplus.com 可以查询scanf函数(点击首页右上角的 [Legacy version] 切换到旧版本有搜索功能),可以看到scanf是有返回值的:
scanf的返回值是一个整数,表示成功读取的变量个数。
成功读取2个的变量,匹配失败1个,返回2:
发生了读取错误(或者读取到文件结尾),返回EOF:
在上面例子中,VS 环境中按了3次 ctrl + z ,提前结束输入发生错误,scanf返回-1(EOF)。
scanf的返回值经常运用于竞赛的OJ(online judge)题目中,用来判断是否成功读取数据。比如scanf中要求3个输入,如果其返回值为3则成功读取,进行正常的程序;如果不为3说明有问题,则进行其它的处理。比如在牛客上有这样一道题:
我一开始是这样解答的:
运行结果不正确,因为题目中的输入描述是输入多组数据:
解决方案,使用scanf的返回值,如果不是EOF(读取错误或读到了文件尾)就继续读取数据:
(3)占位符
scanf的占位符基本跟printf的一样,不一样的%f和%lf在4.1(1)④中讲过了。特别的%c在4.2(1)中讲过了。还有一个%[]是printf没有而scanf有:它表示在方括号中指定一组匹配的字符集合(如:[0-9]),若碰到不在集合中的字符,则停止匹配。
然后特别讲一下%s。第一点是:因为除了%c的占位符都会遇到空白符忽略和停止,所以%s只能读取不含空格的字符串。第二点是:因为在C语言中没有单独的string类型,字符串是按照字符类型存储在char数组里的,所以使用scanf读取字符串结束后,还会额外在结尾加上一个\0。可以在VS中调试,验证这一点:
第三点是:因为scanf把字符串读入字符数组的时候,不会检查字符串是否超过了数组长度,很可能导致数组越界,像这样:
上面这个例子中str变量栈溢出了。所以使用%s的时候,要给%s指定一个字符串长度,如下:
这样就能防止数组越界了,超过的部分直接扔掉。数组里边第11个位置是给\0留的,所以限定%s长度只有10。
(4)赋值忽略符号
有时候你想用户输入这样:2024-7-20,可是用户非要输入这样:2024/7/20,这种情况下scanf读取就会失败,如下:
解决办法就是用赋值忽略符号*,只要把它加在任何占位符的百分号后面,占位符就不会返回匹配的值,读取的数据也丢掉,如下:
(5)VS中不能直接使用scanf函数的问题
如果在VS中直接使用scanf函数会报错,如下:
这个错误是说:这个scanf函数不安全(上面的占位符这部分说了,使用%s不限定长度,scanf读取输入很可能会导致越界),考虑使用scanf_s函数来替换scanf函数。或者用 _CRT_SECURE_NO_WARNINGS 使这个默认规定弃用。
新手不建议使用scanf_s,它跟scanf用法不同,除非对它深入学习并且很熟练了才用。
为了解决这个问题,我们可以直接在源代码第一行加上 :
程序正常运行了。但是每次都专门在文件开头加上这么一句话好麻烦,所以可以用第二种方法:newc++file.cpp文件是安装完VS后,安装文件内会自动包含的一个文件,之后每次在项目内新建文件(包括源文件和头文件),都会拷贝newc++file.cpp文件的内容。
先在Everting里边找到newc++file.cpp文件并打开。
然后在里边加上 #define _CRT_SECURE_NO_WARNINGS 1; 并保存。如果权限不够就复制一份到桌面再改,最后复制回原路径下覆盖原文件就行了。
再新建源文件就会发现开头自动生成了。
标签:变量,int,scanf,数据类型,long,占位,类型,C语言 From: https://blog.csdn.net/2401_86272648/article/details/140485465