本期介绍🍖
主要介绍:C语言中一些大家熟知知识点中的盲区,这是第一期。
文章目录
1. ASCII码
在计算机中所有数据都是以二进制的形式进行存放的,字符也不例外。可字符怎么转换成二进制序列呢?通过给每一个字符编号,且一一对应,这样只要记住字符对应的编号就相当于存储字符,这种方法被称为编码。
为了方便大家互相通信,不造成混乱,于是美国国家标准协会(ANSI)规定字符编号标准:ASCII码。如下图所示。
- ASCII码 48~57 表示字符 0~9
- ASCII码 65~90 表示字符 A~Z
- ASCII码 97~122 表示字符 a~z
- ASCII码 0~31这32个字符是不可打印字符,无法打印在屏幕上观察。
2. 转义字符
转义字符是C语言中表示字符的一种特殊形式,通常用于表示那些无法打印字符和特定功能字符,就譬如:\n表示换行的意思,是\
让n
的意思发生了转变。常用的转移字符如下图所示。
例题:计算字符串"c:\test\121"
中字符的个数。
#include<stdio.h>
#include<string.h>
int main()
{
printf("%zd\n", strlen("c:\test\121"));
return 0;
}
3. 字符串结束标志
在C语言中,字符串末尾其实隐藏着一个\0
,作为字符的结束标志。如下图所示,字符串“abcdef”的末尾其实存放着‘\0’。
\0
是字符串的结束标志,printf函数打印字符串和strlen函数计算字符串长度,都是通过\0
来判断字符串是否结束,遇到\0
的时候就自动停止了。举个例子:如果字符串后面没有\0
,printf在打印该字符串时会发生什么?代码如下。
#include<stdio.h>
int main()
{
char arr1[] = "abc";
char arr2[] = { 'a','b','c' };
printf("arr1 = %s\n", arr1);
printf("arr2 = %s\n", arr2);
return 0;
}
如上图所示,arr1存放的是带\0
的字符串,arr2存放的是没有带\0
的字符串。可以看见,arr2字符串在打印的时候,除了打印 "abc"
外还打印了"烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫贴-?近"
这样的随机值,为什么会这样?因为当printf函数打印完'c'
后没有遇到结束标志,会继续往后打印直到遇见第一个\0
才停下来。
4. 全局变量和局部变量
所谓局部变量就是在括号内创建的变量,全局变量就是在括号外创建的变量。如下图所示,其中a、c是局部变量,b为全局变量。
4.1 作用域
作用域就是限定变量能够使用的范围,出了这个范围就无法使用了。
全局变量的作用域是整个工程,也就是说全局变量可以在程序的任意地方使用,甚至不同的源文件都可以用同一个全局变量(但需要是用函数extern声明一下这个全局变量是外部引用)。局部变量的作用域就是变量所在的局部范围,通俗来说就是其所在那个括号的范围。
如下图所示,printf在变量a和b作用域的范围内,所以可以正常使用。但变量c的作用域只在if语句中,printf在if语句外部不在作用域中,故无法正常使用。
4.2 生命周期
生命周期在编程中指变量、函数或者其他从创建到销毁的这个时间段。
全局变量的生命周期为整个程序,随程序的创建而创建,随程序的销毁而销毁。局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期就结束。
4.3 内存存储方式
在C语言中内存可以被简单划分几块不同的区域:栈区、堆区、静态区…,如下图所示。其中局部变量是放置在栈区中,全局变量是放置在静态区中,堆区用于动态内存开辟。
4.4 局部变量与全局变量重名
若全局变量与局部变量重命名,在局部变量作用范围内,优先使用局部变量,全局变量会被屏蔽。如下图所示。
5. sizeof操作符
每一种数据类型都有自己的长度,使用不同的数据类型就能创建出不同长度的变量,变量长度不同数据的存储范围就有所差异。怎样能够直观的看到类型和存储空间呢?有这么一个操作符sizeof,同样也是一个关键字,sizeof是专门用来计算类型或变量表达式所占内存空间的大小,类型是字节。
注意:sizeof操作符的操作数如果是类型,必须使用括号。sizeof操作符的操作数如果是表达式,则可是省略括号。
1. sizeof (类型)
2. sizeof 表达式
5.1 sizeof返回值类型
sizeof操作符的返回值C语言只规定是无符号整数,并没有规定具体的类型,而是由编译器自己决定。在不同的编译器上sizeof的返回类型有可能是unsigned int
、unsigned long
、unsigned long long
,这就导致使用printf打印sizeof的返回值,需要根据不同的编译器使用不同的占位符,不利于程序的可移植性。
于是C语言提供了一个解决办法,创造了一个类型别名size_t
,用以统一sizeof的返回值类型。size_t
类型在printf中的占位符%zd
。
5.2 sizeof中表达式不进行计算
sizeof操作符的操作数如果是表达式,sizeof只计算表达式的返回类型,不会执行表达式的。如下图所示,num并没有被赋值。
6. printf函数
6.1 输出格式
printf是格式化的标准输出函数,所谓标准输出就是输出到屏幕上,格式化就是可以制定输出文本的格式。下面介绍一些printf函数的使用小技巧:
-
限定输出宽度
在占位符%d
中间加一个数字5,%5d
就表述这个占位符宽度至少是5位。如果不满5位就会添加空格。
-
输出向左、右对齐
%5d
输出的值默认是向右对齐的,想要向左对齐,只要在占位符的%
后面添加-
号就可以%-5d
。
-
显示+/-号
默认情况下printf是只会在输出负数时才带符号,正数是省略符号。如果想让输出正数时也带符号,只要在%5d
占位符中间加个+
号就可以%+5d
。
-
限定小数位
printf输出小数默认是保留小数点后6位,如果想保留2位小数,占位符可以写成%.2f
。最小宽度和小数位数这两个限定值,都可以*
代替,通过printf的参数传入。 -
输出部分字符串
printf在输出字符串,默认是全部输出。如果想要只输出字符串开头5个字符,占位符可以写成%.5s
。
6.2 printf函数返回值
printf
函数是有返回值的,返回值类型为整形int
。如果printf
输出成功,则返回在屏幕上打印字符的个数,举个例子:printf
在屏幕上打印“abc”
,返回值为3
。若printf输出时发生错误,则返回负数。
下面的题目的返回值是多少?返回值是:4321。这是嵌套使用printf,把printf的返回值作为另一个printf函数的参数。
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
7. scanf函数
scanf是格式化的标准输入函数,所谓标准输入是指从键盘上输入,格式化就是可以制定输入文本的格式。注意:scanf参数传递的不是值,而是地址,所以变量前面必须加&
取地址符号。
7.1 scanf读取用户输入原理
scanf会把用户的输入先放入缓存中,直到用户按下回车键后,按照占位符对缓存进行解读。解读用户输入时,会从上一次解读遗留下来的第一个字符开始,直到读完缓存,或者遇到一个不符合条件的字符为止停下来。
举个例子:若用户输入:-13.45e12# 0
,下面的代码会怎么执行?当用户输入后,第一个scanf是按照整形占位符%d
进行解读,从'-'
开始往后读,直到遇见'.'
,小数点不可能出现在整数中,不符合条件停止往后读取,所以第一个scanf读到了整数-13
。第二个scanf会从上一次解读遗留下拉的‘.’
开始解读,由于是浮点型占位符'%f'
,小数点是合理的继续往后读,直到遇见'#'
停下来,因为浮点数中不可能存在#号不合理,所以第二个scanf读到的是'.45e12'
,这里的e是代表科学计数法,即:0.45×1012。输出结果如下图所示。
#include<stdio.h>
int main()
{
int x = 0;
float y = 0;
scanf("%d", &x);
printf("%d\n", x);
scanf("%f", &y);
printf("%f\n", y);
return 0;
}
7.2 占位符%c与%s的使用特性
所有占位符中除了%c
以外,都会自动忽略起首的空白字符。%c
不忽略空白字符,总是返回当前第⼀个字符,无论该字符是否为空格。如果要强制跳过字符前的空白字符,可以在%c
前加上⼀个空格,表示跳过零个或多个空白字符,举个例子:scanf(" %c", &ch)
(所谓的空白字符就是指:空格、制表符、换行符等等)。
占位符%s
会从第一个非空白字符开始读起,直到遇见空白字符才停下来。也就是说占位符%s
无法读取带空格的字符串,除非多个%s
连用。还有scanf函数在使用%s
输入字符串,会在字符串末尾存储一个空字符‘\0’
。
7.3 scanf读取时的越界访问
scanf函数在读取字符串时,是不会考虑存储空间是否能放得下用户输入的字符串,很有可能就会导致越界访问。如图所示,arr数组只能存放5个字符,可用户却输入abcdef
加上字符串末尾的结束标志\0
,总共需要7个字符的空间,scanf越界访问,系统崩溃。
为了防止这种情况发生,在使用%s
占位符时应该指定读取字符串的最长长度,即:%5s
,表示读取字符串的最大长度为5,后面的字符将被丢弃。举个例子,如下图所示。
7.4 赋值忽略符
由于用户的输入可能会不符合预定的格式,举个例子:用户需要输入年、月、
日,可能会输成2024-3-3
,或者2023/3/3
,这种情况下scanf就必然会解析失败。为了避免这种情况,scanf提供了一个赋值忽略符*
,只要把*
加在任何占位符的百分号后面,该占位符就只会解析,解析完后不会返回值。如下图所示。
7.5 scanf的返回值
scanf函数的返回值是一个整数,表示正确读取到变量的个数。如果没有读取到任何项,或匹配失败,则返回0
。如果在成功读取任何数据之前,发生了读取错误或者遇到读取到文件结尾,则返回常量EOF
(end of file文件结束标志)。
scanf的返回值可以用于多组输入的循环条件判断,举个例子:输入两个数,比较这两个数的大小,输出较大的那个,要实现多组输入。如下图所示,只有输入ctrl+z
终止符时才停下来。
8. 悬空else问题
下面来看一段非常简单的代码,请问最后输出结果是hehe还是haha?
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
if (a == 5)
if (b == 2)
printf("hehe\n");
else
printf("haha\n");
return 0;
}
答案是什么也没有输出,为什么会这样呢?这是因为else
会和离他最近的if
进行匹配,所以上题的else
是与if (b == 2)
匹配的,而不是if (a == 5)
,尽管else
看上去是与第一个if
对齐的。造成这种情况的本质问题是,不良的代码风格,你在读这段代码时是按照你的代码风格来看待,所以习惯的以为else
是与对齐的那个if
匹配的。如果养成良好的代码风格,将上题写成如下所示,别人想理解错都难。
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
if (a == 5)
{
if (b == 2)
printf("hehe\n");
else
printf("haha\n");
}
return 0;
}
9. 逻辑运算符的短路求值
在C语言中提供了3个用于逻辑运算的操作符,逻辑与&&
、逻辑或||
、逻辑取反!
。其中&&
运算符和||
运算符还有一个特点,总是先对操作符左侧的表达式求值,再对右侧的表达式求值,这个顺序是保证的。如果左侧的表达式已经满足逻辑判断条件,就不再对右侧的表达式求值,这种情况被称为:逻辑短路。
举个例子如下图所示,逻辑与运算在判断完j++
表达式结果为0后,不管&&
运算符右侧的结果是什么,结果都为假,所以右侧表达式i++
没有必要求值就忽略了,i
的值始终保持为5。
10. 循环语句中的break和continue
break
用于直接终止循环,跳出循环继续执行后面的代码。continue
用于跳过本次循环continue后面的代码,继续进行下一次循环。
10.1 while循环
10.2 do while循环
10.3 for循环
这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。