目录
一、C 语言概述
1. 编译与链接
C 语言是一门编译型计算机语言,C 语言源代码都是文本文件,文本文件本身无法执行,必须通过编译器翻译和链接器链接后,生成二进制形式的可执行文件,可执行文件才能执行。
C 语言代码是放在后缀为 .c 的文件中的,要得到最终运行的可执行程序,中间要经过编译和链接 2 个过程。
1. 每个源文件(.c)单独经过编译器处理生成对应的目标文件(.obj为后缀的文件)。
2. 多个目标文件和库文件经过链接器处理生成对应的可执行程序(.exe文件)。
编译和链接过程:
2. 第一个C 语言程序
#include <stdio.h>
int main()
{
printf("hello C\n");
return 0;
}
3. main() 函数
int main()
{
// ......代码
return 0;
}
main() 函数是程序的入口,是必不可少的。即使一个项目中有多个 .c 文件,也有且只能有一个main() 函数(因为程序的入口只能有一个),main() 函数也被叫做主函数。main 前⾯的 int 表示 main() 函数执行结束的时候返回一个整型类型的值。所以在 main() 函数的最后写 return 0; 正好前后呼应。main() 函数的写法是固定的,不能随意更改。
4. printf() 函数和库函数
4.1 printf() 函数
printf("hello C\n"); // 用printf() 函数打印 -- hello C
printf() 函数是标准输出库函数,需要引用头文件 <stdio.h> -- 标准输入输出头文件。
#include <stdio.h> // 引用 <stdio.h> 头文件
4.2 库函数
为了不浪费时间重复编写常见的代码,让程序员提升开发效率,C 语言标准规定了一组函数,这些函数再由不同的编译器厂商根据标准进行实现,提供给程序员使用。这些函数就共同组成了一个函数库,被称为标准库,这些函数也被称为库函数。在这个基础上一些编译器厂商可能会额外扩展提供部分函数(需要注意的是,这些函数其他编译器不一定支持)。一个系列的库函数一般会声明在同一个头文件中,所以库函数的使用,要引用对应的头文件。完整库函数的链接 : https://cplusplus.com/reference/clibrary/
5. 关键字介绍
C 语言中,如 int 、if 、return 等,这些符号被称为保留字或者关键字。
- 关键字都有特殊的意义,是保留给C 语言使用的
- 程序员自己在创建标识符的时候不能和关键字重复
- 关键字不能自己创建。
有关C 语言关键字的全部介绍:https://zh.cppreference.com/w/c/keyword
6. 字符和 ASCII 编码
在键盘上能够敲出的如:a,q,@,#等,这些符号都被称为字符,C 语言中字符是用单引号括起来的,如:'a','b','@'。在计算机中所有的数据都是以二进制的形式存储的,字符也不例外。如果我们每个人都给这些字符中的每个字符编一个二进制序列,这个叫做编码,很显然,每个人的编码结果不尽相同,那么每个人电脑上字符的存储就不同,不利于通信。为了方便通信,不造成混乱,后来美国国家标准学会(ANSI)出台了一个标准 ASCII 编码,C 语言中的字符就遵循了 ASCII 编码的方式。ASCII 编码表的具体内容参考 : https://zh.cppreference.com/w/cpp/language/ascii
经常看到或使用的编码数字有以下几种:
- 字符 A~Z 的 ASCII 码值从 65~90
- 字符 a~z 的 ASCII 码值从 97~122
- 对应的大小写字符( a 和 A )的 ASCII 码值的差值是 32
- 数字字符 0~9 的ASCII码值从 48~57
- 换行符 \n 的 ASCII 值是 10
ASCII 码值从 0~31 这 32 个字符是不可打印字符,无法打印在屏幕上观察。
#include <stdio.h>
int main(){
printf("%c\n", 97); // 打印ascii码值97对应的字符
printf("%d\n", '0'); // 打印字符0对应的ascii码值
// 用循环打印出ascii码值32到127对应的字符
int i;
for (i = 32; i < 127; i++) {
printf("%c ", i);
if (i % 16 == 0) { // 控制在每行打印16个字符
printf("\n");
}
}
return 0;
}
7. 字符串和结束符 \0
使用双引号括起来的一串字符就被称为字符串,如:"abcdef"。字符串的打印格式可以在 printf() 函数使用 %s 来指定,也可以直接打印,如下:
#include <stdio.h>
int main()
{
printf("%s\n", "hello C"); // 使用占位符%s指定打印字符串
printf("hello c"); // 直接打印字符串内容
return 0;
}
C 语言会在字符串的末尾隐藏放着⼀个 \0 字符,这个 \0 字符是字符串的结束标志。使用 printf() 函数打印字符串时,遇到 \0 停止打印。
#include<stdio.h>
int main() {
printf("%s\n","abcdef");
printf("%s\n","abc\0def"); // 输出:abc
return 0;
}

8. 转义字符
字符中有一组能够转变字符本身含有的字符,就是转义字符。如上文提到的 \0 就是一个转义字符。0 的前面加了一个反斜杠 \ 就改变了 0 原本的意思,变成了空字符,用作字符串的结束标志,其 ASCII 码值是 0。
常见的转义字符有:
- \n:换行符
- \t:制表符
- \':表示字符常量 '
- \\:表示一个反斜杠
- ......
转义字符参考:https://zh.cppreference.com/w/c/language/escape
9. 语句和语句分类
C 语言的代码是由一条一条的语句构成的,用分号结束一条语句,C 语言中的语句可为以下五类:
- 空语句
表达式语句 函数调用语句 复合语句 控制语句
9.1 空语句
空语句就是只有一个分号的语句。一般出现在:需要⼀条语句,但是这个语句不需要做任何事的地方,可以理解为只是占了一个位置,但不放任何东西(个人理解)。
#include <stdio.h>
int main()
{
; // 空语句 -- 一个分号充当一个语句
return 0;
}
9.2 表达式语句
表达式语句就是用表达式和分号组合充当的语句。
#include<stdio.h>
int main()
{
int a = 10; // 初始化
int b = 0;
b = a + 10; // 表达式
return 0;
}
9.3 函数调用语句
包含一个函数调用和分号的语句就是函数调用语句。
#include<stdio.h>
// 两个数字相加的函数
int add(int a,int b)
{
return a + b;
}
int main()
{
int i = 10;
int j = 5;
int k = add(i, j); // 调用函数语句
return 0;
}
9.4 复合语句
复合语句其实就是代码块,成对大括号中的代码就构成一个代码块,也被称为复合语句。
#include<stdio.h>
int main()
{
int a = 10;
int b = 5;
if(a > b) // if 语句后大括号中的语句就是复合语句
{
b = b + 1;
printf("a > b", a, b);
}
printf("%d %d\n", a, b); // %d 是占位符,表示输出一个整数
return 0;
}
9.5 控制语句
控制语句用于控制程序的执行流程,实现程序的各种结构方式(顺序结构、选择结构、循环结构)。C 语言有九种控制语句,分为以下三类:
- 条件判断语句也叫分支语句:if 语句、switch 语句;
- 循环执行语句:do while 语句、while 语句、for 语句;
- 转向语句:break 语句、goto 语句、continue 语句、return 语句。
后续会详细介绍以上列出的控制语句。
10. 注释是什么?为什么写注释?
注释是对代码的说明,编译器在编译代码时会忽略注释,注释是给自己或者他人看的,方便理解代码。好的注释可以帮我们更好的理解代码,但是也不要过度注释。
注释的两种形式:
- /* */ 的形式 --- 多行注释,不支持嵌套
- // 的形式 --- 单行注释
不管是哪种注释,都不能放到双引号里面。双引号里面的注释符号,会成为字符串的⼀部分,被解释为普通符号,失去注释作用。
/*
多行注释
%d -- 表示打印整数
%c -- 打印字符;%s -- 打印字符串;
/*
printf("%s\n", "abcdef");
*/
printf("%s\n", "abcdef");
*/
/* 注释 */
int fopen(char* s /* file name */, int mode);
// 多行注释 /* file name */ ⽤来对函数参数进行说明,跟在它后⾯的代码依然会有效执⾏。
// 单行注释
// 这是注释
int x = 1; // 这也是注释
二、数据类型和变量
1. 数据类型介绍
C 语言提供了丰富的数据类型来描述生活中的各种数据。
所谓“类型”,就是相似的数据所拥有的共同特征,编译器只有知道了数据的类型,才知道怎么操作数据。这里只介绍C 语言的内置数据类型:字符型、整型、浮点型、布尔类型。
1.1 字符型
char //character
[signed] char //有符号的
unsigned char //⽆符号的
1.2 整型
// 整数 - integer
// 短整型
short [int]
[signed] short [int]
unsigned short [int]
// 整型
int
[signed] int
unsigned int
// ⻓整型
long [int]
[signed] long [int]
unsigned long [int]
// 更⻓的整型
long long [int]
[signed] long long [int]
unsigned long long [int]
1.3 signed 和 unsigned
C 语言使用 signed 和 unsigned 关键字修饰字符型和整型类型,浮点型和布尔类型不能用 signed 和 unsigned 关键字。
signed 关键字,表示修饰的类型带有正负号,包含负值,比如温度;
unsigned 关键字,表示修饰的类型不带有正负号,只能表示零和正整数,比如年龄。只表示非负整数时必须要用 unsigned 修饰变量,但 unsigned int 中的 int 可以省略。整数变量用 unsigned 修饰的好处是,同样长度的内存能够表示的最大整数值,比用signed 修饰的增大了一倍,因为 unsigned 修饰的整型不包括负值,而 signed 修饰的整型要包括负值。对于 int 类型,默认是带有正负号的,也就是说 int 等同于 signed int。对于 char 类型,则是不确定的。char 不等同于 signed char,它有可能是 signed char,也有可能是 unsigned char,由使用的系统决定。
// 整型
signed int a; // 等同于int a;
unsigned int a; // unsigned int ⾥⾯的 int 可以省略
unsigned a;
// 字符
signed char ch;
unsigned char ch;
1.4 浮点型
float // 单精度浮点数
double // 双精度浮点数
long double
1.5 布尔型
C 语言原来并没有为布尔值单独设置一个类型,而是使用整数 0 表示假,非零值表示真。在 C99 中才引入布尔类型 ,专门用来表示真假。布尔类型的使用得包含头文件 <stdbool.h>。布尔类型变量的取值是: true 或者 false。
_Bool flag = true;
1.6 各种数据类型的长度
每一种数据类型都有自己的长度,使用不同的数据类型,创建的变量长度不同,存储的数据范围就会有所差异。
数据类型的长度用 sizeof 操作符进行计算。
sizeof 既是一个关键字,也是一个操作符,是用来计算类型长度的,单位是字节。
sizeof 操作符的操作数可以是类型,也可是变量或者表达式。
- sizeof(类型)
- sizeof 表达式
sizeof 后边的表达式是不真实参与运算的,根据表达式的类型得出结果。sizeof 的计算结果是 size_t 类型的 -- 打印的时候需要用占位符 %zd -- %zd 等同于 %zusizeof 运算符的返回值,C 语言只规定了是无符号整数,但没有规定具体的类型,不同的系统,返回值可能不同。而无符号整型又有 unsigned int 、unsigned long int 、unsigned long long int 几种,printf() 函数中对应的占位符分别是 %u、%lu、%llu。
因此,C语言创造了一个别名 size_t ,用来统一表示 sizeof 的返回值,对应当前系统的返回值。这样,我们就不用关心 sizeof 的返回值了,统一用占位符 %zd 打印。
#include<stdio.h>
int main()
{
printf("%zd\n", sizeof(_Bool)); // 1
printf("%zd\n", sizeof(char)); // 1
printf("%zd\n", sizeof(short)); // 2
printf("%zd\n", sizeof(int)); // 4
printf("%zd\n", sizeof(long)); // 4
printf("%zd\n", sizeof(long long)); // 8
printf("%zd\n", sizeof(float)); // 4
printf("%zd\n", sizeof(double)); // 8
printf("%zd\n", sizeof(long double)); // 8
int a = 10;
short b = 5;
printf("%zd\n", sizeof (b = a + 2)); // 2 -- b是短整型
printf("%d\n", b); // 5 -- sizeof中表达式不参与真实计算
return 0;
}
2. 数据类型的取值范围
每一种数据类型有自己的取值范围,也就是存储的数值的最大值和最小值的区间,以便适用于不同场景。
limits.h 文件中说明了整型类型的取值范围。
float.h 文件中说明浮点型类型的取值范围。
为了代码的可移植性,需要知道某种整数类型的极限值时,应该尽量使用 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 的最小值和最大值
3. 变量
C 语言中把经常变化的值称为变量,不变的值称为常量。
变量的创建语法:
data_type name; 即 变量类型 变量名;
在创建变量的同时赋给一个初始值,就叫初始化。
变量名需要注意:
1. 有意义,能够见名知意;
2. 只能由字母、数字和下划线组成;
3. 不能由数字开头
#include<stdio.h>
int main()
{
int i; // 创建一个整型变量i
char ch = 'w'; // 创建一个字符变量ch,并初始化为w
return 0;
}
变量的分类:全局变量、局部变量
全局变量:在大括号外定义的变量,使用范围更广,整个项目内都能使用。
局部变量:在大括号内定义的变量,使用范围局限,只能在变量相邻的大括号内使用。
全局变量和局部变量的存储位置?
全局变量 -- 存在内存的静态区
局部变量 -- 存在内存的栈区
#include<stdio.h>
int age=18; // 全局变量
void test()
{
printf("%d\n", age);
}
int main() {
{
double price=100; // 局部变量
}
printf("%lf\n", price); // 报错 -- E0020:未定义标识符"price"
printf("%d\n", age);
return 0;
}
全局变量和局部变量名字相同且在同一范围内都可以使用时,局部优先。
#include<stdio.h>
int n = 10;
int main() {
int n = 100;
printf("%d\n", n); // 100
return 0;
}
4. 算术操作符:+、-、*、/、%
C 语言为了方便运算,提供了一系列操作符(操作符也叫运算符),其中有一组操作符是算术运算符,即 +、-、*、/、%,这些运算符也叫双目运算符。双目操作符就是有两个操作数的运算符。
除号 /
除号 / 两端如果都是整数,则执行的是整数除法,运算结果也是整数,舍去小数。
除号 / 两端如果出现浮点数(两端至少有一端出现浮点数),则执行的是浮点数数除法,运算结果也是浮点数,保留小数。
#include<stdio.h>
int main() {
printf("%d\n", 6 / 4); // 1
printf("%f\n", 6 / 4.0); // 1.500000
int score = 5;
/*
score = (score / 20) * 100;
printf("%d\n", score); // 0
*/
score = (score / 20.0) * 100;
printf("%d\n", score); // 25
return 0;
}
取模(余) -- 只能用于整数,不能用于浮点数
取模(余):返回两个整数相除的余数。
负数求模的规则:结果的正负号由第一个运算数的正负号决定。
#include<stdio.h>
int main() {
printf("%d\n", 6 / 4); // 1
printf("%d\n", 6 % 4); // 2
//printf("%d\n", 10 % 3.0); // 报错 -- E0031 表达式必须包含整型
printf("%d\n", -11 % 3); // -2
printf("%d\n", -11 % -3); // -2
printf("%d\n", 11 % -3); // 2
return 0;
}
加(+)、减(-) 、乘(*)用法和平常一样,这里不加以赘述。
5. 赋值操作符:= 和复合赋值
在变量创建好后,再给变量一个值,这叫赋值。赋值操作符 = 是一个随时可以给变量赋值的操作符。
赋值操作符可以连续赋值,但这种赋值方式不容易理解,不推荐使用。
#include<stdio.h>
int main() {
int x = 10; // 初始化
x = 100; // 赋值
// 连续赋值
int a = 3;
int b = 4;
int c = 0;
c = a = b + 3; // 从右到左按优先级依次计算
printf("%d\n", c); // 7
printf("%d\n", a); // 7
return 0;
}
复合赋值 -- 既运算又赋值,先计算再赋值
符合赋值符:+=、-=、*=、/=、%=
#include<stdio.h>
int main()
{
int a = 10; // 初始化
// 复合赋值
a += 5; // 等同于 a = a + 5;
/*
a -= 5; // 等同于 a = a - 5;
a *= 5; // 等同于 a = a * 5;
a /= 5; // 等同于 a = a / 5;
a %= 5; // 等同于 a = a % 5;
*/
printf("%d\n", a);
return 0;
}
6. 单目操作符:++、--、+、-
单目操作符,即只有一个操作数的操作符。
++ 是⼀种自增的操作符,使用一次加 1,分为前置++(++a) 和后置++(a++)。
a++ 和 ++a 的区别:
a++:后置++:先使用后加1
++a:前置++:先加1后使用
#include<stdio.h>
// 前置加加
int main() {
int a = 10;
// a++、++a都是让a加1
/*
a++; // a = a + 1; a += 1; ++a ;
printf("%d\n", a); // 11
*/
int b = a++; // 先把a赋给b,再加1 --- b = a; a = a + 1;
printf("%d\n", a); // 11
printf("%d\n", b); // 10
return 0;
}
#include<stdio.h>
// 后置加加
int main() {
int a = 10;
int b = ++a; // 先加1,再赋给c --- a = a + 1; c = a;
printf("%d\n", a); // 11
printf("%d\n", b); // 11
return 0;
}
-- 是⼀种自减的操作符,使用一次减 1,分为前置--(--a) 和后置--(a--)。用法与 ++ 类似。
#include<stdio.h>
// 后置减减
int main() {
int a = 10;
int b = a--; // 先算 b = a; 再算 a = a - 1;
printf("%d\n", b); // 10
return 0;
}
#include<stdio.h>
// 前置减减
int main() {
int a = 10;
int b = --a; // 先算 a = a - 1; 再算 b = a;
printf("%d\n", b); // 9
return 0;
}
+(正号)和-(负号)是单目运算符。
运算符 + 对正负值没有影响,可以省略。
运算符 - 用来改变一个值的正负号,负数前加 - 变为正数,正数前加 - 变为负数。
#include<stdio.h>
int main() {
int a = -10;
printf("%d\n", a); // -10
printf("%d\n", -a); // 10
printf("%d\n", +a); // -10
return 0;
}
若想要在输出正数时显示 + (正号),则需要在占位符的 % 后加上正号 +。
#include<stdio.h>
int main() {
int a = +10; // 即使在数值前加正号+,输出结果也不会带+
// %+d -- 使输出正数时带+,同时也不影响负数带-
printf("%+d\n", a); // +10
printf("%+d\n", -a); // -10
printf("%+d\n", +a); // +10
return 0;
}
7. 强制类型转换
语法:(类型) --- 括号中是转换的目标类型。
#include<stdio.h>
int main() {
// a是int类型,3.14是double类型(编译器默认小数为double类型)
// 在小数后加上f就变成float类型了,如3.14f
int a = (int)3.14; // 将double类型强制转换为int类型,舍去小数部分,只保留整数
printf("%d\n", a); // 3
return 0;
}
8. printf() 和scanf() 介绍
printf()
printf = print + format --- 按照格式打印数据,不会主动换行。
printf() 可以输出文本中指定的占位符,即输出代入的值。
多个占位符 -- printf() 中的参数和占位符一一对应,如果有 n 个占位符,则printf() 中一共有 n+1 个参数,包含占位符的字符串算一个参数。如果参数少于占位符,则printf()可能输出内存中的任意值。
所谓 “占位符”,就是这个位置可以用其他值代入。
常用占位符:
%d -- int -- 十进制整数(带符号)
%c -- char -- 字符
%f -- float -- 单精度浮点数
%lf -- double -- 双精度浮点数
%s -- char[] -- 字符串
%hd -- short int -- 带符号十进制短整型
%hu -- unsigned short int -- 无符号十进制短整型
%ld -- long int -- 带符号十进制长整型
%lu -- unsigned long int -- 无符号十进制长整型
%lld -- long long int -- 带符号十进制长长整型
%Lf -- long double -- 长双精度浮点数
%p -- 指针(打印地址)
%u -- 打印无符号整数(unsigned int)
%x -- 打印十六进制整数
%o -- 打印八进制整数
%zd -- 打印size_t类型
%% -- 打印百分号,写在占位符前,有转义的作用,即将占位符变为普通字符
#include<stdio.h>
int main()
{
printf("There are %d apples.\n",3); // There are 3 apples.
printf("%s says:it is %d o'clock.\n", "lisi", 10); // lisi says:it is 10 o'clock.
printf("%d\n", 10); // 10
printf("%o\n", 10); // 12
printf("%x\n", 10); // a
return 0;
}
限定输出格式
printf() 允许限定占位符的最小宽度,默认右对齐,并用空格补齐宽度。
当输出数据宽带大于限定宽度时,按原数据宽度输出。
若想要改成左对齐,可以在占位符的 % 后加一个 - (负号)即可。当输出数据宽带大于限定宽度时,按原数据宽度输出。
#include<stdio.h>
// 对整数
int main() {
printf("%d\n", 123); //123
printf("%5d\n", 123); // 123
printf("%5d\n", 1234567); //1234567
printf("%-5d---\n", 123); //123 ---
return 0;
}
对小数输出格式的限定 -- 小数打印时默认保留小数点后6位
还可以具体限制保留小数点后几位,如 %12.3 表示最小宽度12位,小数点后保留3位。
若不设置最小宽度,只限制小数点后几位,则可以省略最小宽度的值,如写成 %.3f。
#include<stdio.h>
int main() {
printf("%f\n", 123.456);//123.456000
printf("%12f\n", 123.456);// 123.456000
printf("%12.3f\n", 123.456);// 123.456
printf("%.3f\n",123.456);//123.456
return 0;
}
最小宽度和小数位数这两个限定值,都可以用 * 代替,通过 printf() 的参数传入。
#include<stdio.h>
int main()
{
printf("%*.*f\n", 12, 3, 123.456);// 123.456
return 0;
}
对字符串 -- 默认输出完整的字符串内容
如果只想输出前面指定个数的字符,可以用 %.[m]s 指定输出的长度,其中[m]代表一个数字,表示要输出的字符串长度(字符个数)。
#include<stdio.h>
int main()
{
printf("%s\n", "abcdef"); //abcdef
printf("%.3s\n", "abcdef"); //abc
// 科学计数法 -- 5.0e5 表示5.0乘以10的5次方
printf("%e\n", 123.456); //1.234560e+02
return 0;
}
scanf()
scanf() -- 用于读取键盘输入,输入时不要使用换行符
scanf() 处理 -- 程序运行到 scanf() 时会停下来,等待用户从键盘输入,用户从键盘输入并按下回车后,scanf() 就会处理该输入,将其存入变量。
scanf() 中的占位符:告诉编译器读取什么类型的数据,scanf必须提前知道用户输入的类型,才能处理数据。
scanf() 中占位符和变量一一对应,变量需要取地址,即必须要有用 & 符号(指针变量除外)。因为 scanf() 传递的不是值,而是地址,将变量的地址指向输入的值。
& -- 取地址符号,表示将用户输入的值放入变量中去。scanf() 函数处理数值时会自动过滤空白字符,包括空格、制表符、换行符等。所以,用户输入的数据之间,有一个或多个空格不影响 scanf() 读取数据,即时用回车将输入分成几行同样不影响 scanf() 读取数据。
#include<stdio.h>
int main() {
int a=0;
int b=0;
float x = 0.0;
float y = 0.0;
scanf("%d%d%f%f", &a, &b, &x, &y); // scanf函数中不要使用\n换行
printf("%d %d %f %f\n", a, b, x, y);
return 0;
}
scanf() 处理用户输入的原理 -- 先把用户输入放入缓存,等到用户按下回车键后,按照占位符对缓存进行解读。解读用户输入时,会从上一个解读遗留的第一个字符开始,直到读完缓存,或者遇到第一个不符合条件的字符为止。
#include<stdio.h>
int main() {
int x = 0;
float b = 0.0;
// 输入:-12.34e12# 0
scanf("%d", &x);
scanf("%f", &b);
printf("%d\n", x); // -12
printf("%f\n", b); // 340000014336.000000 -- 浮点数在内存中可能存储不精确!!!
return 0;
}
scanf() 的返回值 -- 是一个整数,表示成功读取数据的变量个数。
没有变量接收 scanf() 的返回值时,有时会出现警告,提示"C6031:返回值被忽略。"如果没有读取任何项,或者匹配失败,则返回0。如果在成功读取任何数据之前,发生了读取错误或者遇到读取到文件结尾,则返回常量 EOF(-1)。EOF -- end of file 文件结束标志。
按 ctrl + z (VS需要按三次,其他编译器只需按一次)可使 scanf() 读取失败,即可使得scanf() 返回值为 -1。
#include<stdio.h>
int main() {
int a = 0;
int b = 0;
int c = 0;
int ret = scanf("%d%d%d", &a, &b, &c); // 接收成功读取数据的变量个数
printf("a = %d, b = %d, c = %d\nret = %d", a, b, c, ret);
return 0;
}
关于 scanf() 返回值在什么场景下使用 -- 在竞赛题目中,有多组输入数据的题目,需要scanf() 的返回值进行测试。
#include<stdio.h>
int main() {
int a = 0;
int b = 0;
while (scanf("%d %d", &a, &b) != EOF) { // 当有多组输入时,循环判断每组输入是否满足条件
printf("%d\n", a + b);
}
return 0;
}
scanf() 中,占位符 %c 不忽略空白字符,总是返回当前第一个字符,无论该字符是否为空格。
如果要强制跳过字符前的空格,可以写成 scanf(" %c", &ch); 即在 %c 前面加上一个空格,表示跳过 0 个或多个空白字符。
#include<stdio.h>
int main() {
char ch;
/*
scanf("%c", &ch); // 输入: x
printf("===%c===\n", ch); // 输出:=== ===
*/
scanf(" %c", &ch); // 输入: x
printf("===%c===\n", ch); // 输出:===x===
return 0;
}
注:
printf() 函数中,float、double 类型的数据打印时都可以使用占位符 %f,double 类型数据打印时也可以使用占位符 %lf。
scanf() 函数中,读取 float 类型的数据要使用占位符 %f,读取 double 类型的数据要使用占位符 %lf,读取 long double 类型的数据要使用占位符 %Lf。
%s 不能简单等同于字符串,%s 是从当前第一个非空白字符开始读起,直到遇到空白字符(即空格、换行符、制表符等)为止。
#include<stdio.h>
int main() {
char arr[20]; // 数组 -- 存放一组相同类型的数据,[]中的数字代表数组长度
// 这里不需要用取地址符号 & ,因为数组名本来就是地址
scanf("%s", arr); // 输入:hello world
printf("%s\n", arr); // 输出:hello
return 0;
}
因为 %s 不能包含空白字符,所以,无法用来读取多个单词,除非多个 %s 一起使用。
这意味着 scanf() 不适合读取可能包含空白字符的字符串。
scanf() 遇到 %s 占位符时,会在字符串变量末尾存储一个空字符 \0,因此 scanf() 中 [m] 的取值应该小于数组定义数组时指定的长度。scanf() 不安全的原因 -- scanf() 将字符串读入字符数组时,不会检测字符串长度,是否超过了字符数组的长度。
所以,在存储字符串时,很可能会超过字符数组的边界,导致预想不到的结果。
为了防止这种情况的发生,使用 %s 占位符时,应该指定读入的字符串的最长长度,即写成 %[m]s 的形式,[m]是一个整数,代表读取字符串的最大长度。后面的字符将会被丢弃。
#include<stdio.h>
int main() {
char arr[5] = { 0 }; // 初始化数组,使数组中的值全为0
// 这里字符串输出的最大长度应该小于定义数组时指定的长度5
scanf("%4s", arr); // 输入:helloworld
printf("%s\n", arr); // 输出:hel
return 0;
}
赋值忽略符 -- 避免出现用户在键盘输入时与 scanf() 中的格式不同而造成错误输出。
赋值忽略符 * -- 只要把 * 加到任意占位符 % 后,该占位符就不会有返回值,解析后直接被丢弃。
#include<stdio.h>
int main() {
int year = 0;
int month = 0;
int day = 0;
/*
scanf("%d%d%d", &year, &month, &day); // 输入:2025/5/30
printf("%d %d %d\n", year, month, day); // 输出:2025 0 0
scanf("%d%d%d", &year, &month, &day); // 输入:2025.5.30
printf("%d %d %d\n", year, month, day); // 输出:2025 0 0
scanf("%d%d%d", &year, &month, &day); // 输入:2025-5-30
printf("%d %d %d\n", year, month, day); // 输出:2025 -5 -30
*/
scanf("%d%*c%d%*c%d", &year, &month, &day); // 输入:2025.5.30
printf("%d %d %d\n", year, month, day); // 输出:2025 5 30
return 0;
}

被折叠的 条评论
为什么被折叠?



