目录
[一.%[ ] 格式说明符](#一.%[ ] 格式说明符)
[3.^ 取反操作](#3.^ 取反操作)
[(1). 字符范围的正确表示](#(1). 字符范围的正确表示)
[(2). 避免字符集中的特殊字符冲突](#(2). 避免字符集中的特殊字符冲突)
[(4). 输入长度的控制](#(4). 输入长度的控制)
[(5). 换行符和空格的处理](#(5). 换行符和空格的处理)
[(6). 字符集的顺序和位置](#(6). 字符集的顺序和位置)
[(7). 检查返回值](#(7). 检查返回值)
[三.%n 格式说明符](#三.%n 格式说明符)
一.%[ ]
格式说明符
在C语言中,
scanf
函数 中的**%[ ]
格式** 说明符是一种非常灵活的方式,可以用来读取满足特定条件的一系列字符。%[ ]
格式说明符允许程序员定义一个字符集 ,scanf
会连续读取输入流中的字符 ,直到遇到不属于该字符集的字符为止。
ps:通常情况下,scanf
会自动跳过空白字符(包括换行符),但是%[ ]
这种格式说明符是一个例外。
1.基本用法
scanf("%[character_set]", string);
character_set
: 这是一个字符集,它可以是特定字符或字符范围。string
: 这是一个字符数组(字符串),用来存储读取到的字符。
举例说明:
(1)读取字母字符:
cpp
char str[100];
scanf("%[a-zA-Z]", str); // 只读取字母字符 (大小写)
printf("读取到的字母是: %s\n", str);
输入 : abc123
输出 : 读取到的字母是: abc
解释 : scanf
会从输入流中读取所有属于 a-zA-Z
的字符,一旦遇到不属于该范围的字符(如数字 1
),它将停止读取。
(2)读取数字字符:
cpp
char numStr[100];
scanf("%[0-9]", numStr); // 只读取数字字符
printf("读取到的数字是: %s\n", numStr);
输入 : 123abc
输出 : 读取到的数字是: 123
解释 : 这里 scanf
只会读取数字字符(0到9),直到遇到非数字字符。
(3)读取所有字符直到遇到空格:
cpp
char str[100];
scanf("%[^ ]", str); // 读取所有字符直到遇到空格
printf("读取到的内容是: %s\n", str);
输入 : hello world
输出 : 读取到的内容是: hello
解释 : scanf
会读取所有字符,直到遇到空格(
)为止。^
表示"除指定字符之外的所有字符"。
(4)读取直到换行符:
cpp
char line[100];
scanf("%[^\n]", line); // 读取一整行,直到换行符
printf("读取到的行是: %s\n", line);
输入 : Hello, world!
输出 : 读取到的行是: Hello, world!
解释 : scanf
会读取所有字符,直到遇到换行符 \n
为止。即回车
2.使用范围和组合:
- 范围 : 可以通过
-
来表示一个字符范围,例如[a-z]
表示所有小写字母。- 多个范围和字符 : 你可以结合多个字符集或字符范围,例如
"[a-zA-Z0-9]"
会匹配所有字母和数字。
cpp
char str[100];
scanf("%[a-zA-Z0-9]", str); // 读取字母和数字
printf("读取到的内容是: %s\n", str);
输入 : abc123!@#
输出 : 读取到的内容是: abc123
解释 : 这里 scanf
读取了字母和数字,直到遇到非字母非数字字符。
3.^
取反操作
取反 : 在字符集的开头加上
^
符号表示取反,即读取不属于该字符集的字符。
cpp
char str[100];
scanf("%[^,]", str); // 读取直到遇到逗号
printf("读取到的内容是: %s\n", str);
输入 : Hello,world
输出 : 读取到的内容是: Hello
解释 : 这里 scanf
读取了所有字符,直到遇到逗号 ,
为止,因为 [^,]
表示"除逗号之外的所有字符"。
4.注意事项
(1). 字符范围的正确表示
范围表示法 :
a-z
、A-Z
、0-9
这些范围表示法必须是有效的字符序列。a-z
表示小写字母从a
到z
的所有字符 ,A-Z
表示大写字母从A
到Z
的所有字符 ,0-9
表示所有数字字符。错误的表示 :
z-a
、9-0
这样的表示是无效的,会导致意外的行为。
cpp
char str[100];
scanf("%[z-a]", str); // 无效:z-a不是有效范围
(2). 避免字符集中的特殊字符冲突
连字符
-
的使用 :连字符-
用于指定范围,但如果它被放置在错误的位置,可能会引起解析错误或未定义行为。
- 正确 :
[a-z]
,[0-9A-F]
。- 错误 :
[-z]
(这里连字符和z
之间没有起始字符,这种用法可能会导致未定义行为)。特殊用法:
- 开始位置 : 如果要包含
-
本身,可以把它放在范围的起始位置,如[-a-z]
,表示-
和a
到z
的所有字符。- 结束位置 : 如果连字符在字符集的末尾,如
[a-z-]
,它表示a
到z
的所有字符和-
字符。
cpp
char str[100];
scanf("%[-a-zA-Z]", str); // 读取`-`或字母
(3).避免空字符集
空字符集 :
scanf
的%[ ]
如果是空字符集,scanf
会直接返回而不做任何操作,因此要确保字符集内包含有效内容。错误:
cpp
char str[100];
scanf("%[]", str); // 空字符集,`scanf`什么都不会读取
(4). 输入长度的控制
缓冲区溢出 :
scanf
使用%[ ]
时没有自动限制读取的字符数。如果输入的字符超过了数组的容量,可能会导致缓冲区溢出。因此,建议使用宽度限定符来限制读取的最大字符数。
cpp
char str[100];
scanf("%99[a-zA-Z0-9]", str); // 最多读取99个字符,保留1个字符给结束符`'\0'`
(5). 换行符和空格的处理
换行符问题 :
%[ ]
读取字符 时不会 消耗换行符\n
。这可能会导致在之后的scanf
或getchar
调用中,直接读取到换行符。如果需要处理换行符,可以在之后使用getchar()
来消耗这个换行符。
可能大伙还是不懂,继续往下看:
假设你有以下代码:
cpp
char str[100];
scanf("%[a-zA-Z]", str);
- 输入 :
hello\nworld
scanf("%[a-zA-Z]", str)
会读取并存储字符串"hello"
,但它不会读取或消耗换行符\n
。- 换行符
\n
仍然留在输入流中,等待下一次scanf
或其他输入函数处理。
如果你随后调用scanf
来读取另一个输入:
cpp
scanf("%d", &num);
- 因为之前的换行符
\n
还在输入流中,scanf("%d", &num)
会遇到这个换行符,并立即返回,通常会导致输入错误或跳过输入。
解决方法
为了避免这个问题,可以在调用scanf("%[ ]", ...)
后手动读取并消耗掉换行符,通常通过getchar()
来实现。
修改后的代码
cpp
char str[100];
scanf("%[a-zA-Z]", str);
getchar(); // 手动读取并消耗掉换行符
解释 : 在读取完字符串后,getchar()
将读取并消耗掉输入流中的换行符,使得后续的scanf
调用不会受到影响。
空白字符的忽略 :
scanf
的%[ ]
不会自动跳过空白字符(如空格、制表符、换行符等),除非在字符集中明确包含这些字符
cpp
char str[100];
scanf("%[a-zA-Z0-9 ]", str); // 允许读取空格字符
(6). 字符集的顺序和位置
顺序影响 : 字符集的顺序不会影响
scanf
的行为,但是清晰的字符集顺序更易于理解和维护代码。例如[a-zA-Z0-9]
比[z-aZ-A9-0]
更易读。排除特殊字符 : 如果你想排除特定字符集中的某些字符,可以使用
[^ ]
,如[^a-zA-Z0-9]
来读取非字母、数字的字符。
(7). 检查返回值
scanf
的返回值 :scanf
返回成功读取的项目数。可以通过检查返回值来判断输入是否成功匹配。
cpp
char str[100];
int n = scanf("%99[a-zA-Z0-9]", str);
if (n == 1) {
printf("读取成功: %s\n", str);
} else {
printf("输入格式不匹配或读取失败\n");
}
ps:在scanf
的语境中,"项目"通常指的是一个成功读取并存储到对应变量中的数据单元。因此,str
在这个上下文中确实被视为一个项目。
二.%*抑制符
在C语言的
scanf
函数中,%*
被称为**"抑制符"** (或者**"跳过符"** )。它的作用是告诉scanf
函数读取数据 但不存储它 。换句话说,scanf
会解析输入数据并跳过该数据,而不将其赋值给任何变量。
1.基本用法
scanf("%*d");// 读取并跳过一个整数
在这个例子中,scanf
会从输入中读取一个整数,但不会将其存储在任何变量中。%*
与任何有效的格式说明符结合使用,都会导致该数据被读取但不会存储。
用途
-
跳过不需要的数据 :有时候你可能只需要读取输入中的部分数据,而对其他部分的数据不感兴趣。这时候可以使用
%*
来跳过不需要的数据。 -
处理复杂的输入 :如果输入数据格式比较复杂,你可以通过
%*
来跳过一些无关的部分,只提取你感兴趣的数据。
示例
(1).跳过某些数据
cpp
#include <stdio.h>
int main() {
int a, c;
scanf("%d %*d %d", &a, &c);
printf("a = %d, c = %d\n", a, c);
return 0;
}
输入 : 1 2 3
输出 : a = 1, c = 3
解释 : 这个程序通过%d
读取了第一个整数1
并将其存储在a
中,然后通过%*d
读取了第二个整数2
但没有存储它,最后通过%d
读取了第三个整数3
并将其存储在c
中。
(2).跳过特定格式的数据
cpp
#include <stdio.h>
int main() {
char name[50];
int age;
scanf("%*s %d", &age);
printf("Age: %d\n", age);
return 0;
}
输入 : John 25
输出 : Age: 25
解释 : 这里%*s
会跳过输入的第一个字符串(John
),然后%d
读取第二个整数(25
)并将其存储在age
中。
*
的多种组合使用
*
可以与任何scanf
的格式说明符结合使用,例如:
%*c
: 读取并跳过一个字符。%*f
: 读取并跳过一个浮点数。%*s
: 读取并跳过一个字符串。%*[]
: 读取并跳过一组符合特定字符集的字符。
(3).跳过特定字符
cpp
#include <stdio.h>
int main() {
int year, month, day;
scanf("%d-%*d-%d", &year, &day);
printf("Year: %d, Day: %d\n", year, day);
return 0;
}
输入 : 2024-08-13
输出 : Year: 2024, Day: 13
解释 : 这里%d
读取年份2024
,%*d
跳过月份08
,然后%d
读取日期13
。
2.注意事项
不会增加返回的项目数 : 被
%*
抑制符忽略的数据不会被计入scanf
返回的成功读取项目数 。例如,如果scanf
成功读取两个变量而跳过一个数据,它的返回值是2
,而不是3
。格式匹配问题 :
scanf
依然会验证被跳过的数据是否符合指定格式,如果输入数据不符合指定的格式,scanf
会停止读取。输入缓冲区影响 : 即使数据被跳过,输入缓冲区中的数据仍然会被消耗掉,因此后续的
scanf
调用不会再看到这些数据。
三.%n 格式说明符
%n
是C语言中scanf
函数的一种特殊格式说明符。它用于将到目前为止已经读取的字符数 存储到一个整数变量 中。与其他格式说明符不同,%n
并不会从输入中读取数据并与其对应的数据类型匹配,而是直接记录scanf
已经成功处理的字符数量。
1.基本用法
scanf("%d%n", &value, &num_chars);
在这个例子中,scanf
会读取一个整数并将其存储在value
中,接着,它会把读取到该整数为止所消耗的字符数存储在num_chars
中。
scanf
中的%n
格式说明符
-
不消耗输入 :
%n
本身不消耗输入字符。它只是在内部计算并存储从输入流中已经读取的字符数。 -
存储读取字符的数量 :
%n
会将到达它的位置为止读取的总字符数存储在其对应的参数中。这个参数必须是指向int
类型的指针。 -
多个
%n
: 如果在一个scanf
调用中出现多个%n
,每个%n
都会记录到目前为止从输入流中读取的字符数。因此可以用它来跟踪输入过程中的不同点。
示例
(1)简单用法
cpp
#include <stdio.h>
int main() {
int value, num_chars;
scanf("%d%n", &value, &num_chars);
printf("Value: %d, Characters read: %d\n", value, num_chars);
return 0;
}
输入 : 12345
输出 : Value: 12345, Characters read: 5
解释 : 在输入12345
之后,scanf
将12345
存储在value
中,而%n
记录并存储了读取字符的数量(5个字符)。
(2)跟踪多个输入
cpp
#include <stdio.h>
int main() {
int a, b;
int num_chars1, num_chars2;
scanf("%d%n %d%n", &a, &num_chars1, &b, &num_chars2);
printf("a = %d, b = %d\n", a, b);
printf("Characters read for a: %d, total characters read: %d\n", num_chars1, num_chars2);
return 0;
}
输入 : 12 34
输出 :a = 12, b = 34
Characters read for a: 2, total characters read: 5
解释:
- 在读取完第一个整数
12
后,num_chars1
存储了已经读取的字符数(2个字符)。 - 在读取完第二个整数
34
后,num_chars2
存储了总的字符数(5个字符,包括中间的空格)。
(3)复杂输入的验证
cpp
#include <stdio.h>
int main() {
int day, month, year;
int chars_read;
scanf("%2d/%2d/%4d%n", &day, &month, &year, &chars_read);
if (chars_read == 10) {
printf("Valid date: %02d/%02d/%04d\n", day, month, year);
} else {
printf("Invalid date format.\n");
}
return 0;
}
输入 : 15/08/2024
输出 : Valid date: 15/08/2024
解释:
- 这个程序读取日期并确保输入格式为
dd/mm/yyyy
(总共10个字符)。 - 如果读取的字符数正好是10个字符,那么输入是有效的,否则输出无效格式。
2.注意事项
安全性问题 : 使用
%n
时,确保对应的参数是有效的int*
指针,否则会导致未定义行为。这也是它不常用于现代C编程的原因之一,因为如果误用会导致潜在的安全漏洞。
scanf
返回值不包含%n
:scanf
返回的成功读取项目数不包括%n
。所以,即使%n
在scanf
中使用,它也不会影响scanf
的返回值。与其他格式说明符的组合 :
scanf
会按照顺序解析格式说明符,如果在使用%n
之前的格式说明符解析失败,%n
就不会被执行。可能的用途:
- 输入格式检查: 可以通过检查读取的字符数来确保输入符合预期格式。
- 精确输入解析 : 当你需要知道输入的精确位置(如进行复杂的文本处理)时,可以使用
%n
。
完