第2讲:C语言数据类型和变量
第2讲:C语言数据类型和变量
XingJiのBlog⽬录
数据类型介绍
signed和unsigned
数据类型的取值范围
变量
算术操作符:+、-、*、/、%
赋值操作符:=和复合赋值
单⽬操作符:++、–、+、-
强制类型转换
scanf和printf介绍
正文开始
数据类型介绍
C语言提供了丰富的数据类型来描述生活中的各种数据。
使用整型类型 来描述整数,使用 字符类型 来描述字符,使用 浮点型类型 来描述小数。
所谓 “类型” ,就是相似的数据所拥有的共同特征,编译器只有知道了数据的类型,才知道怎么操作数据。
下面盘点一下C语言提供的各种数据类型,本章节主要探讨内置数据类型。
列出C语言中的基本数据类型,并简述它们的用途及输出格式的调用。
字符型
1 | char //character |
整型
1 | //短整型 |
浮点型
1 | float //单精度浮点型 |
布尔类型
表示真假的变量,只有两个取值:true 和 false。整数 0 表示假(false),非零值(包括负数)表示真(true)。
注:C89中没有布尔类型,在C99中引入了布尔类型,是专⻔表示真假的。
1 | _Bool |
布尔类型的使用得包含头文件 <stdbool.h> .
布尔类型变量的取值是:true 或者 false .
1 |
代码演示
:
1 |
|
运行结果
:
1 | i like C |
各种数据类型的⻓度
每一种数据类型都有自己的⻓度,使用不同的数据类型,能够创建出⻓度不同的变量,变量⻓度的不
同,存储的数据范围就有所差异。
sizeof运算符
sizeof是一个关键字,也是操作符,专⻔是用来计算sizeof的操作符数的类型⻓度的,单位是字节。
sizeof操作符的操作数可以是类型,也可是变量或者表达式。
1 | sizeof( 类型名 ) |
sizeof
的操作数如果不是类型,是表达式的时候,可以省略掉后边的括号的。
sizeof
后边的表达式是不真实参与运算的,根据表达式的类型来得出大小。
sizeof
的计算结果是size_t
类型的。
📌
sizeof
运算符的返回值,C语言只规定是无符号整数,并没有规定具体的类型,而是留给
系统自己去决定,sizeof
到底返回什么类型。不同的系统中,返回值的类型有可能是unsigned int
,也有可能是unsigned long
,甚至是unsigned long long
,
对应的printf()
占位符分别是%u
、%lu
和%llu
。这样不利于程序的可移植性。
C语言提供了一个解决方法,创造了一个类型别名size_t
,用来统一表示sizeof
的返
回值类型。对应当前系统的sizeof
的返回值类型,可能是unsigned int
,也可能是unsigned long long
。
比如:
1 |
|
数据类型⻓度
1 |
|
在VS2022X64配置下的输出:
1 | 1 |
sizeof中表达式不计算
1 | //测试:sizeof中表达式不计算 |
sizeof
的表达式不计算,sizeof在代码进行编译的时候,就根据表达式的类型确定了,类型的常用,而表达式的执行却要在
程序运行期间才能执行,在编译期间已经将sizeof处理掉了,所以在运行期间就不会执行表达式了。
signed 和 unsigned
C语言使用signed和unsigned关键字修饰字符型和整型 类型的。
signed关键字,表示一个类型带有正负号,包含负值;
unsigned关键字,表示该类型不带有正负号,只能表示零和正整数。
对于int类型,默认是带有正负号的,也就是说int等同于signed int。
由于这是默认情况,关键字signed一般都省略不写,但是写了也不算错。
1 | signed int a; |
对于int类型也可以不带正负号,只表示非负整数。这时就必须使用关键字unsigned声明变量。
1 | unsigned int a; |
整数变量声明为unsigned的好处是,同样⻓度的内存能够表示的最大整数值,增大了一倍。
比如, 16 位的signed short int的取值范围是:-3276832767,最大是 32767 ;而65535,最大值增大到了65,535。 32 位的signed
unsigned short int的取值范围是:0
int的取值范围可以参看limits.h中给出的定义。
下面的定义是VS2022环境中,limits.h中相关定义。
1 |
1 | unsigned int里面的int可以省略,所以上面的变量声明也可以写成下面这样。 |
1 | unsigned a; |
字符类型char也可以设置signed和unsigned。
1 | signed char c; // 范围为 -128 到 127 |
注意,C语言规定char类型默认是否带有正负号,由当前系统决定。
这就是说,char不等同于signed char,它有可能是signed char,也有可能是
unsigned char。
这一点与int不同,int就是等同于signed int。
数据类型的取值范围
上述的数据类型很多,尤其数整型类型就有short、int、long、longlong四种,为什么呢?
其实每一种数据类型有自己的取值范围,也就是存储的数值的最大值和最小值的区间,有了丰富的类
型,我们就可以在适当的场景下去选择适合的类型。如果要查看当前系统上不同数据类型的极限值:
- 整型:limits.h头文件中定义了整型的极限值。
- 浮点型:float.h头文件中定义了浮点型的极限值。
为了代码的可移植性,需要知道某种整数类型的极限值时,应该尽量使用这些常量。
SCHAR_MIN
,SCHAR_MAX
:signed char的最小值和最大值。SHRT_MIN
,SHRT_MAX
:short的最小值和最大值。INT_MIN
,INT_MAX
:int的最小值和最大值。LONG_MIN
,LONG_MAX
:long的最小值和最大值。LLONG_MIN
,LLONG_MAX
:longlong的最小值和最大值。UCHAR_MAX
:`unsigned char的最大值。USHRT_MAX
:unsigned short的最大值。UINT_MAX
:unsigned int的最大值。ULONG_MAX
:unsigned long的最大值。ULLONG_MAX
:unsigned long long的最大值。
变量
变量的创建
了解清楚了类型,我们使用类型做什么呢?
类型是用来创建变量
的。
什么是变量呢?
C语言中把经常变化的值称为变量
,不变的值称为常量
。
变量创建的语法形式是这样的:
1 | 数据类型 变量名 = 初始值 ; |
1 | int age; //整型变量 |
变量在创建的时候就给一个初始值,就叫初始化。
1 | int age = 18 ; |
变量的分类
全局变量: 在大括号外部定义的变量就是全局变量
全局变量的使用范围更广,整个工程中想使用,都是有办法使用的。局部变量: 在大括号内部定义的变量就是局部变量
局部变量的使用范围是比较局限,只能在自己所在的局部范围内使用的。静态变量: 在函数体外定义的变量就是静态变量
静态变量的作用域是整个函数体,在函数体内使用,只能在函数体内使用。
1 |
|
如果局部和全局变量,名字相同呢?
1 |
|
其实当局部变量和全局变量同名的时候,
局部变量优先使用
。
全局变量和局部变量在内存中存储在哪里呢?
一般我们在学习C/C++语言的时候,我们会关注
内存中的三个区域:栈区
、堆区
、静态区
。
局部变量是放在内存的 ——> 栈区
全局变量是放在内存的——> 静态区
堆区是用来动态内存管理的(后期会介绍)
算术操作符:+、-、*、/、%
在写代码时候,一定会涉及到计算。
C语言中为了方便运算,提供了一系列操作符,其中有一组操作符叫: 算术操作符 。分别是:**+
,-
, *
,/
, %
**,这些操作符都是 双目操作符 。
注:操作符也被叫做: 运算符 ,是不同的翻译,意思是一样的。
+
和 -
+和-用来完成加法和减法。
+和-都是有 2 个操作数的,位于操作符两端的就是它们的操作数,这种操作符也叫 双目操作符 。
1 |
|
*
运算符*用来完成乘法。
1 |
|
/
运算符/用来完成除法。
除号的两端如果是整数,执行的是整数除法,得到的结果也是整数。
1 |
|
上面示例中,尽管变量
x
的类型是float
(浮点数),但是6 / 4
得到的结果是1.0
,而不是1.5
。原因就在于C语言里面的整数除法是整除,只会返回整数部分,丢弃小数部分。
如果希望得到浮点数的结果,两个运算数必须至少有一个浮点数
,这时C语言就会进行浮点数除法。
1 |
|
上面示例中,6.0 / 4
表示进行浮点数除法,得到的结果就是1.5。
再看一个例子:
1 |
|
上面的代码,你可能觉得经过运算,
score
会等于25
,但是实际上score
等于0
。这是因为score / 20
是整除,会得到一个整数值0
,所以乘以100
后得到的也是0
。
为了得到预想的结果,可以将除数 20 改成20.0,让整除变成浮点数除法。
1 |
|
%
运算符%
表示求模(余)运算,即返回两个整数相除的余值。这个运算符只能用于整数,不能用于浮点数。
1 |
|
负数求模的规则是,结果的正负号由第一个运算数的正负号决定。
1 |
|
上面示例中,第一个运算数的正负号(
11
或-11
)决定了结果的正负号。
赋值操作符:=和复合赋值
在变量创建的时候给一个初始值叫初始化,在变量创建好后,再给一个值,这叫赋值。
1 | int a = 100 ;//初始化 |
1 | a = 200 ;//赋值,这里使用的就是赋值操作符 |
赋值操作符=是一个随时可以给变量赋值的操作符。
连续赋值
赋值操作符也可以连续赋值,如:
1 | int a = 3 ; |
C语言虽然支持这种连续赋值,但是写出的代码不容易理解,建议还是拆开来写,这样方便观察代码的执行细节。
1 | int a = 3 ; |
这样写,在调试的是,每一次赋值的细节都是可以很方便的观察的。
复合赋值符
在写代码时,我们经常可能对一个数进行自增、自减的操作
,如下代码:
1 | int a = 10 ; |
这样代码C语言给提供了更加方便的写法:
1 | int a = 10 ; |
C语言中提供了复合赋值符,方便我们编写代码,这些赋值符有:
1 | += -= |
单目操作符:++、–、+、-
前面介绍的操作符都是双目操作符,有 2 个操作数的。C语言中还有一些操作符只有一个操作数,被称
为单目操作符。**++、--、+(正)、-(负)
**就是单目操作符的。
++和–
++是一种自增的操作符,又分为前置++和后置++,–是一种自减的操作符,也分为前置–和后置–
前置++
1 | int a = 10 ; |
计算口诀
:先+1,后使用;
a原来是 10 ,先+1,后a变成了 11 ,再使用就是赋值给b,b得到的也是 11 ,所以计算技术后,a和b都是 11 ,相当于这样的代码:
1 | int a = 10 ; |
后置++
1 | int a = 10 ; |
计算口诀
:先使用,后+1
a原来是 10 ,先使用,就是先赋值给b,b得到了 10 ,然后再+1,然后a变成了1 1,所以直接结束后a是11 ,b是 10 ,相当于这样的代码:
1 | int a = 10 ; |
前置–
如果你听懂了前置++,那前置–是同理的,只是把加 1 ,换成了减1 ;
计算口诀
: 先-1,后使用
1 | int a = 10 ; |
后置–
同理后置–类似于后置++,只是把加一换成了减一
计算口诀
: 先使用,后-1
1 | int a = 10 ; |
+和-
这里的+是正号,-是负号,都是单目操作符
。
运算符
+
对正负值没有影响,是一个完全可以省略的运算符,但是写了也不会报错。
1 | 1 int a = + 10 ; 等价于 int a = 10 ; |
运算符
-
用来改变一个值的正负号,负数的前面加上-
就会得到正数,正数的前面加上-
会得到负数。
1 | int a = 10 ; |
强制类型转换
在操作符中还有一种特殊的操作符是强制类型转换,语法形式很简单,形式如下:
1 | (类型) |
请看代码
:
1 | int a = 3.14; |
为了消除这个警告,我们可以使用强制类型转换
:
1 | int a = (int)3.14;//意思是将3.14强制类型转换为int类型,这种强制类型转换只取整数部分 |
俗话说,强扭的瓜不甜,我们使用强制类型转换都是万不得已的时候使用,如果不需要强制类型转化就能实现代码,这样自然更好的。
scanf 和 printf介绍
printf
基本用法
printf()
的作用是将参数文本输出到屏幕。它名字里面的f
代表format(格式化
,表示可以定制输出文本的格式。
1 |
|
上面命令会在屏幕上输出一行文字“Hello World”。
printf()
不会在行尾自动添加换行符,运行结束后,光标就停留在输出结束的地方,不会自动换行。
为了让光标移到下一行的开头,可以在输出文本的结尾,添加一个换行符\n
。
1 |
|
如果文本内部有换行,也是通过插入换行符来实现,如下方代码:
1 |
|
printf()
是在标准库的头文件stdio.h定义的。使用这个函数之前,必须在源码文件头部引入这个头文件。
占位符
printf()
可以在输出文本中指定占位符。
所谓“ 占位符 ”,就是这个位置可以用其他值代入。
1 | // 输出 There are 3 apples |
上面示例中,
There are %d apples\n
是输出文本,里面的%d
就是占位符,表示这个位置要用其他值来替换。 占位符的第一个字符一律为百分号 %,第二个字符表示占位符的类型,%d
表示这里代入的值必须是一个整数 。
printf()
的第二个参数就是替换占位符的值,上面的例子是整数3
替换%d
。执行后的输出结果
就是There are 3 apples
。
常用的占位符除了%d
,还有%s
表示代入的是字符串。
1 |
|
上面示例中,
%s
表示代入的是一个字符串 ,所以printf()
的第二个参数就必须是字符串,这个例子是zhangsan
。执行后的输出就是zhangsan will come tonight
。
输出文本里面可以使用 多个占位符。
1 |
|
上面示例中,输出文本
%s says it is %d o'clock
有两个占位符,**第一个
是字符串占位符%s
,第二个
是整数占位符%d
**,分别对应printf()
的第二个参数(lisi
)和第三个参数(21
)。执行后的输出就是lisi says it is 21 o'clock
。
printf()
参数与占位符是一一对应关系,如果有n
个占位符,printf()的参数就应该有n+1
个。如果参数个数少于对应的占位符,printf()
可能会输出内存中的任意值。
占位符列举
printf()
的占位符有许多种类,与C语言的数据类型相对应。下面按照字母顺序,列出常用的占位符,方便查找,具体含义在后面章节介绍。
%a
:十六进制浮点数,字母输出为小写。%A
:十六进制浮点数,字母输出为大写。%c
:字符。%d
:十进制整数。//int%e
:使用科学计数法的浮点数,指数部分的e为小写。%E
:使用科学计数法的浮点数,指数部分的E为大写。%i
:整数,基本等同于%d。%f
:小数(包含 float 类型和double 类型)。//float %f double-%lf%g
:^6 个有效数字的浮点数。整数部分一旦超过^6 位,就会自动转为科学计数法,指数部分的e为小写。%G
:等同于%g,唯一的区别是指数部分的E为大写。%hd
:十进制 short int类型。%ho
:八进制short int类型。%hx
:十六进制 short int类型。%hu
:unsigned short int类型。%ld
:十进制longint类型。%lo
:八进制longint类型。%lx
:十六进制longint类型。%lu
:unsigned long int类型。%lld
:十进制 long long int类型。%llo
:八进制 long long int类型。%llx
:十六进制 long long int类型。%llu
:unsigned long long int类型。%Le
:科学计数法表示的 long double类型浮点数。%Lf
:long double 类型浮点数。%n
:已输出的字符串数量。该占位符本身不输出,只将值存储在指定变量之中。%o
:八进制整数。%p
:指针(用来打印地址)。%s
:字符串。%u
:无符号整数(unsigned int) 。- **
%x
:十六进制整数。 %zd
:size_t 类型。%%
:输出一个百分号。
输出格式
printf()
可以定制占位符的输出格式。
限定宽度
printf()
允许限定占位符的**最小宽度
**。
1 |
|
上面示例中,
%5d
表示这个占位符的宽度至少为 5 位。如果不满 5 位,对应的值的前面会添加空格。
输出的值 默认是右对⻬ ,即输出内容前面会有空格;如果希望改成左对⻬,在输出内容后面添加空
格,可以在占位符的%
的后面插入一个-
号。
1 |
|
输出结果
:
1 | 123 |
上面示例中,输出内容
123
的后面添加了空格。
对于小数,这个限定符会限制所有数字的最小显示宽度。
1 | // 输出 " 123.450000" |
输出结果
:
1 | __123.450000 |
上面示例中,
%12f
表示输出的浮点数最少要占据 12 位。由于小数的默认显示精度是小数点后 6 位,所以123.45
输出结果的头部会添加 2个空格。
总是显示正负号
默认情况下,printf()不对正数显示+号,只对负数显示-号。如果想让正数也输出+号,可以在占位符的%后面加一个+。
1 |
|
输出结果
:
1 | +12 |
上面示例中,%+d可以确保输出的数值,总是带有正负号。
限定小数位数
输出小数时,有时希望限定小数的位数。举例来说,希望小数点后面只保留两位,占位符可以写成%.2f。
1 | // 输出 Number is 0.50 |
输出结果
:
1 | Number is 0.500 |
上面示例中,如果希望小数点后面
输出3位(0.500
,占位符就要写成%.3f
。
这种写法可以与限定宽度占位符,结合使用。
1 | // 输出为 " 0.50" |
输出结果
:
1 | __0.50 |
上面示例中,
%6.2f
表示输出字符串最小宽度为 6
,小数位数为 2
。所以,输出字符串的头部有两个空格
。
最小宽度和小数位数这两个限定值,都可以用
*
代替,通过printf()
的参数传入。
1 |
|
输出结果
:
1 | __0.50 |
上面示例中,
%*.*f
表示输出字符串,其中*
表示第一个参数是最小宽度,第二个参数是小数位数。及%*.*f
的两个星号通过printf()的两个参数6
和2
传入。
输出部分字符串
%s
占位符用来输出字符串,默认是全部输出。如果只想输出开头的部分,可以用%.[m]s
指定输出的⻓度,其中[m]
代表一个数字,表示所要输出的⻓度。
1 | // 输出 hello |
输出结果
:
1 | hello |
上面示例中,占位符%.5s
表示只输出字符串“hello world”
的前5 个字符
,即“hello”
。
scanf
当我们有了变量,我们需要给变量输入值就可以使用scanf
函数,如果需要将变量的值输出在屏幕上的时候可以使用printf
函数,下面看一个例子:
1 |
|
运行截图
:
画图演示
:
注
:标准输入一般指的就是键盘,标准输出一般指的就是屏幕
那接下来我们介绍一下
scanf
函数。
基本用法
scanf()
函数用于读取用戶的键盘输入。
程序运行到这个语句时,会停下来,等待用戶从键盘输入。
用戶输入数据、按下回⻋键后,scanf()
就会处理用戶的输入,将其存入变量。
它的原型定义在头文件stdio.h
。scanf()
的语法跟printf()
类似。
1 | scanf("%d", &i); |
它的第一个参数是一个格式字符串,里面会放置占位符(与printf()的占位符基本一致),告诉编译器如何解读用戶的输入,需要提取的数据是什么类型。
这是因为C语言的数据都是有类型的,scanf()必须提前知道用戶输入的数据类型,才能处理数
据。
它的其余参数就是存放用戶输入的变量,格式字符串里面有多少个占位符,就有多少个变量。
上面示例中,scanf()的第一个参数%d,表示用戶输入的应该是一个整数。%d就是一个占位符,%是占位符的标志,d表示整数。第二个参数&i表示,将用戶从键盘输入的整数存入变量i。
注意:变量前面必须加上&运算符(指针变量除外),因为scanf()传递的不是值,而是地址,即将变量i的地址指向用戶输入的值。
如果这里的变量是指针变量(比如字符串变量),那就不用加&运算符。
下面是一次将键盘输入读入多个变量的例子
。
1 | scanf("%d%d%f%f", &i, &j, &x, &y); |
上面示例中,格式字符串%d%d%f%f,表示用戶输入的前两个是整数,后两个是浮点数,比如 1-20 3.4 -4.0e3。这四个值依次放入i、j、x、y四个变量。
scanf()处理数值占位符时,会 自动过滤空白字符,包括空格、制表符、换行符 等。
所以,用戶输入的数据之间,有一个或多个空格不影响scanf()解读数据。另外,用戶使用回⻋
键,将输入分成几行,也不影响解读。
1 | 1 |
上面示例中,用戶分成四行输入,得到的结果与一行输入是完全一样的。每次按下回⻋键以后,scanf()
就会开始解读,如果第一行匹配第一个占位符,那么下次按下回⻋键时,就会从第二个占位符开始解读。
scanf()
处理用戶输入的原理是,用戶的输入先放入缓存,等到按下回⻋键后,按照占位符对缓存进行解读。
解读用戶输入时,会从上一次解读遗留的第一个字符开始,直到读完缓存,或者遇到第一个不符合条件的字符为止。
1 |
|
上面示例中,scanf()
读取用戶输入时,%d
占位符会忽略起首的空格,从-
处开始获取数据,读取到-13
停下来,因为后面的.
不属于整数的有效字符。这就是说,占位符%d
会读到-13
。
第二次调用scanf()
时,就会从上一次停止解读的地方,继续往下读取。这一次读取的首字符
是.
,由于对应的占位符是%f
,会读取到.45e12
,这是采用科学计数法的浮点数格式。后面的#
不属于浮点数的有效字符,所以会停在这里。
由于scanf()
可以连续处理多个占位符,所以上面的例子也可以写成下面这样。
1 |
|
scanf的返回值
scanf()
的返回值是一个整数,表示成功读取的变量个数。
如果没有读取任何项,或者匹配失败,则返回0
。
如果在成功读取任何数据之前,发生了读取错误或者遇到读取到文件结尾,则返回常量EOF(-1)
。EOF -end off ile文件结束标志
1 |
|
输入输出测试
:
如果输入 2 个数后,按ctrl+z,提前结束输入
:
在VS环境中按 3 次ctrl+z
,才结束了输入,我们可以看到r是 2 ,表示正确读取了 2 个数值。
如果一个数字都不输入,直接按 3 次ctrl+z,输出的r是-1,也就是EOF
占位符
scanf()
常用的占位符如下,与printf()
的占位符基本一致。
%c
:字符。%d
:整数。%f
:float类型浮点数。%lf
:double类型浮点数。%Lf
:long double类型浮点数。%s
:字符串。%[]
:在方括号中指定一组匹配的字符(比如%[0-9]),遇到不在集合之中的字符,匹配将会停止。
上面所有占位符之中, 除了%c
以外,都会自动忽略起首的空白字符 。%c
不忽略空白字符,总是返回当前第一个字符,无论该字符是否为空格。
如果要强制跳过字符前的空白字符 ,可以写成scanf(" %c", &ch)
,即%c前加上一个空格,表示跳过零个或多个空白字符。
下面要 特别说一下占位符 %s
,它其实不能简单地等同于字符串。它的规则是,从当前第一个非空白字符开始读起,直到遇到空白字符(即空格、换行符、制表符等)为止。
因为%s
不会包含空白字符,所以无法用来读取多个单词,除非多个%s一起使用。这也意味着,scanf()
不适合读取可能包含空格的字符串,比如书名或歌曲名。另外,scanf()
遇到%s
占位符,会在字符串变量末尾存储一个空字符\0
。
scanf()
将字符串读入字符数组时,不会检测字符串是否超过了数组⻓度。所以,储存字符串时,很可能会超过数组的边界,导致预想不到的结果。为了防止这种情况,使用%s
占位符时,应该指定读入字符串的最⻓⻓度,即写成%[m]s
,其中的[m]
是一个整数,表示读取字符串的最大⻓度,后面的字符将被丢弃。
1 |
|
上面示例中,name
是一个⻓度为 11 的字符数组,scanf()
的占位符%10s表示最多读取用戶输入
的 10 个字符,后面的字符将被丢弃,这样就不会有数组溢出的⻛险了。
赋值忽略符
有时,用戶的输入可能不符合预定的格式。
1 |
|
上面示例中,如果用戶输入2020-01-01
,就会正确解读出年、月、日。问题是用戶可能输入其他格式,比如2020/01/01,这种情况下,scanf()解析数据就会失败。
为了避免这种情况,scanf()提供了一个 赋值忽略符 (assignment suppression character)*
。只要把*加在任何占位符的百分号后面,该占位符就不会返回值,解析后将被丢弃。
1 |
|
上面示例中,%*c
就是在占位符的百分号后面,加入了赋值忽略符*
,表示这个占位符没有对应的变量,解读后不必返回。
完