2.1 数据类型介绍
C语言中提供了丰富的数据类型以方便来解决现实生活中的各种数据。所谓**“类型”**,就是相似的数据说拥有的共同特征,编译器只有知道可数据类型,才知道怎么操作数据。
2.1.1 基本类型 - 字符型[char]
char [signed] char // 有符号的 unsigned char // 无符号的
2.1.2 基本类型 - 整型[int]
short — 短整型 — 完整的写法 short [int]
int — 整型 — 完整的写法 int
long — 长整型 — 完整的写法 long [int]
long long — 更长的整型 — 完整的写法 long long [int]
2.1.3 基本类型 - 浮点型[float]
例如,3.14可以写为,也可以写为。它的小数点位置是可以移动的,正因为如此小数也叫做浮点数。
float — 单精度浮点型
double — 双精度浮点型
long double — 更长的双精度浮点型
2.1.4 布尔类型
C语言一开始并没有为布尔值单独设置一个类型,而是使用整数0表示假,而非零的数值表示真。在C99中才引入了布尔类型,用来表示专门真和假。
代码示例:
# include <stdbool.h>
# include <stdio.h>
int main()
{
_Bool flag = true;
bool flag2 = false;
if (flag)
{
printf("hi\n");
}
return 0;
}
说明:<stdbool.h>为使用布尔类型时包含的头文件,_Bool或bool均可以调用布尔类型。
布尔类型的返回值只有两个,即要么为真true,要么为假false
2.2 各种数据类型的长度
每一种数据类型都有自己对应的长度,使用不同的数据类型,能够创建出不同长度的变量。变量长度不同,存储的数据范围就会有所差异。
sizeof操作符
sizeof是一个专门用来计算数据类型长度的操作符,它的返回值为size_t类型的,其单位为字节(Byte),其返回值在打印时使用%zd。
- 拓展 - 计算机存储单位
sizeof操作对象可以是数据类型,也可以是变量或表达式
sizeof(数据类型)
sizeof 表达式
代码举例: sizeof(数据类型)
int main()
{
printf("%zd\n", sizeof(char));
printf("%zd\n", sizeof(short));
printf("%zd\n", sizeof(int));
printf("%zd\n", sizeof(long));
printf("%zd\n", sizeof(long long));
printf("%zd\n", sizeof(float));
printf("%zd\n", sizeof(double));
printf("%zd\n", sizeof(long double));
return 0;
}
// 输出
// 1
// 2
// 4
// 4
// 8
// 4
// 8
// 8
代码举例: sizeof 表达式
int main()
{
int a = 10;
printf("%zd\n", sizeof(int));
printf("%zd\n", sizeof(a));
printf("%zd\n", sizeof a);
return 0;
}
// 输出
// 4
// 4
// 4
注意:如果sizeof的操作对象不是数据类型,是表达式的时候,可以省略掉圆括号,此外,表达式时不会真实参与计算的,它是根据表达式的数据类型推断来的。例如,
int main()
{
short s = 1;
int b = 30;
printf("%zd\n", sizeof(s = b + 1)); // 输出为 2 <== short类型数据长度
printf("s = %d\n", s); // 输出为 1
return 0;
}
sizeof在代码编译时,是根据表达式的类型确定的,而表达式的执行却要在代码运行期间才能执行,在编译期间已经将sizeof处理掉了,所以在运行期间就不会执行表达式了。
2.3 signed和unsigned - 有符号和无符号
对于整型int,C语言规定int是有符号的整型,即int == signed int。
- 对于有符号的整数(
signed int)打印应该使用%d,取值可以是正数,负数和0 - 对于无符号的整数(
unsigned int)打印应该使用%u,取值可以是正数和0
// 短整型
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]
注意:这里[]内的内容可以被省略
整数变量声明为unsigned的好处是,同样长度与的内存能够表示的最大整数值,增大了一倍。比如16位的有符号的短整型(signed short int)的取值范围为-32768~32767,其最大值为32767;而无符号的短整型(unsigned short int)的取值范围为0~65536,其最大值为65536。
int main()
{
int num = -100;
printf("%d\n", num); // -100
// signed int --- 有符号的int
signed int num1 = -200;
printf("%d\n", num1); // -200
// unsigned int --- 无符号的int
unsigned int num2 = 1000;
printf("%u\n", num2); // 1000
return 0;
}
字符类型char也可以设置signed和unsigned。
signed char a // 取值范围为 -128 ~ 127
unsigned char b // 取值范围为 0 ~ 255
但是和“int是有符号的整型,即int == signed int”不同,C语言规定char是不是带正负号,是由当前的系统决定的,也就是说char可能是有符号的字符型(signed char),也可能是无符号的字符型(unsigned char)。
2.4 数据类型的取值范围
如果要查看当前系统上不同数据类型的极限值:
-
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:long long的最小值和最大值UCHAR_MAX:unsigned char的最大值USHRT_MAX:unsigned short的最大值UINT_MAX:unsigned int的最大值ULONG_MAX:unsigned long的最大值ULLONG_MAX:unsigned long long的最大值
具体的值可以使用Everything找到limits.h头文件去寻找相应的值。
2.5 变量
常量是指在程序运行过程中,其值不发生变化的量;而变量是指在程序运行过程中,其值发生变化的量
2.5.1 变量的创建
data_type name;
| |
| |
数据类型 变量名
代码举例:
# include <stdbool.h>
int main()
{
unsigned int age = 10; // 初始化
age = 20; // 赋值
printf("%d\n", age);
char ch = 'q';
float score = 3.14f;
double wegiht = 4.5;
bool flag = true;
return 0;
}
说明:我们在初始化float类型的score的值为3.14时,其在最后要加上f,否者,3.14的默认为double类型。
2.5.2 变量的分类
-
全局变量:在大括号外面的定义的变量就是全局变量
全局变量的使用范围更广,整个工程中想使用,都是有办法使用的。
-
局部变量:在大括号内部的定义的变量就是局部变量
局部变量的使用范围是比较局限的,只能在自己所在的局部范围内能使用
int m = 1000;
int main()
{
{
int m = 100;
printf("%d\n", m); // 局部优先
}
return 0;
}
// 输出
// 100
那么,全局变量和局部变量在内存中储存在哪里呢?
在C语言/C++当中,我们会关注内存的三个区域:栈区,堆区,静态区
- 局部变量存放在内存的栈区
- 全局变量存放在内存的静态区
2.6 算术操作符:+,-,*,/,%
C语言当中为了方便运算,引入了一系列的操作符,其中一组就是算术操作符:+,-,*,/,%。它们分别对应两个操作数相加,相减,相乘,相除以及取余。由于这些算术操作符都是包括两个操作数的,因此,又被称为双目操作符
-
+,-和*int main() { printf("%d\n", 25 + 26); // 51 printf("%d\n", 25 - 26); // -1 printf("%d\n", 25 * 26); // 650 return 0; }说明:
25和26被称为操作数,+或-被称为操作符 -
/除号
/有两种输出结果的数据类型,即整数类型和浮点类型如果
/的两个操作数都是整数类型的数据,那么/执行整数除法,得到的结果也是一个整数类型的如果
/的两个操作数至少有一个是浮点数类型的数据,那么/执行浮点数除法,得到的结果也是一个浮点数类型的代码示例:
int main() { // 1. 整数除法 printf("%d\n", 9 / 3); // 3 printf("%d\n", 10 / 3); // 3 // 2. 浮点数除法 printf("%f\n", 10 / 4.0); // 2.500000 printf("%f\n", 10.0 / 4); // 2.500000 printf("%f\n", 10.0 / 4.0); //2.500000 return 0; }注意:在执行整数除法时,如果两个操作数不能够整除,例如
10 / 3它的结果为商3余数为1,那么C语言里面会直接返回商的值3,而余数1直接被舍弃。 -
%操作符
%表示取余运算或者求模运算,即返回两个整数相除的余数,即我们上面例子当中的10 / 3它的结果为商3余数为1,其返回值为余数1。注意:取余运算两个操作符都不能是浮点型数据。- 负数取余数的规则:两正或两负返回值为正数,一正一负返回值为负数
int main()
{
printf("%d\n", 10 / 3); // 3
printf("%d\n", 10 / 4); // 2
// 负数取余数
printf("%d\n", -10 / 4); // -2
printf("%d\n", -10 / -4); // 2
printf("%d\n", 10 / 4); // 2
printf("%d\n", 10 / -4); // -2
return 0;
}
2.7 赋值操作符:=,复合赋值
赋值操作符:=
在变量创建的时候会给一个初始值叫做初始化,在变量创建好后的,第二次或第n次再给一个值,叫做赋值
# include <stdbool.h>
int main()
{
unsigned int age = 10; // 初始化
age = 20; // 赋值
printf("%d\n", age);
return 0;
}
-
连续赋值
C语言中虽然支持这种连续赋值的操作,但是还是建议拆开来写,这样更方便观察代码执行的细节
int main() { int a = 10; int b = 20; int c = 0; // 连续赋值 c = b = a + 3; // 从右向左依次赋值 printf("%d\n", c); // 拆开来写的等价形式 b = a + 3; c = b; printf("%d\n", c); } -
复合赋值: 对一个数进行自加或者自减操作
int main() { // 复合赋值 int x = 0; // 初始化 x = x + 3; x += 3; // 复合赋值 x = x - 3; x -= 3; return 0; }
2.8 单目操作符:++,--,+(正号),-(负号)
++是自增操作符,又可以分为前置++和后置++
int main()
{
int a = 5;
int b = ++a;
// 前置++
// 口诀:先++,在使用
// a = a+1 , b = a
printf("%d\n", a); // a = 6
printf("%d\n", b); // b = 6
int a = 5;
int b = ++a;
// 后置++
// 口诀:先使用,再++
// b = a, a = a + 1
printf("%d\n", a); // a = 6
printf("%d\n", b); // b = 5
return 0;
}
--是自减操作符,又可以分为前置--和后置--
int main()
{
int a = 5;
int b = --a;
// 前置--
// 口诀:先--,在使用
// a = a-1 , b = a
printf("%d\n", a); // a = 4
printf("%d\n", b); // b = 4
int a = 5;
int b = a--;
// 后置--
// 口诀:先使用,再--
// b = a, a = a - 1
printf("%d\n", a); // a = 4
printf("%d\n", b); // b = 5
return 0;
}
+表示正号
-表示负号
int main()
{
int a = -10;
printf("%d\n", +10);//10
printf("%d\n", +a);//-10
printf("%d\n", 10);//10
printf("%d\n", -10);//-10
printf("%d\n", -a);//10
printf("%d\n", 10);//10
return 0;
}
2.9 强制类型转换
如果不是万不得已还是不要使用强制类型转换,顺其自然就好
语法格式:将浮点型(3.14)转换为整型
(type_name) 3.14
| |
| |
目标数据类型名 被转换的数
代码示例
int main()
{
int a = (int)3.14;
printf("%d\n", a); // 3
return 0;
}
2.10 printf和scanf介绍
2.10.1 printf
基本用法
-
作用:在屏幕上打印出参数文本输入
-
printf()函数是一个标准的库函数,在使用时必须包含相应的头文件stdio.h -
示例代码:
# include <stdio.h> int main() { printf("Hello World\n"); return 0; } -
printf()函数不会在行尾添加自动换行符,程序运行结束后,光标会停留在输出结束的地方,不会自动换行。因此,为了让光标移动到下一行的开头位置,可以在输出文本的结尾,添加一个换行符\n。当然,如果输入文本在中间需要换行,也可以添加换行符\n。
# include <stdio.h>
int main()
{
// eg1:
printf("Hello World\n");
printf("Hello VS2022\n");
// eg2:
printf("abcdef\n");
printf("abc\ndef");
return 0;
}
// 输出
// Hello World
// Hello VS2022
// abcdef
// abc
// def
占位符
printf()可以输出文本中指定的占位符
所谓的“占位符”就是可以被其他值带入的,例如%d,%s,%f,%lf,它们可以分别被整型,字符串类型,双精度浮点型(double),单精度浮点型(float)值所带入,以下是代码示例,
# include <stdio.h>
// %s - 打印字符串
// %d - 打印整数
// %c - 打印字符
// %f - 打印小数
int main()
{
printf("%s\n", "hi word");
printf("%d\n", 100);
printf("%c\n", 'q'); //单引号括起来的表示字符
printf("%lf\n", 3.14);
printf("%f\n", 3.14f);
return 0;
}
-
占位符的第一个字符一定是百分号
%,第二个字符表示占位符的类型 -
输出文本里面可以使用多个占位符,占位符是一一对应的关系,如果有
n个占位符,那么printf()的参数就应该是有n+1个的,如果参数个数少于对应的占位符,printf()会随机生成一个符合条件的值去带入其中。以下是代码举例,# include <stdio.h> int main() { printf("%s says it %d o'clook.\n", "xiaoming", 10); printf("%s says it %d o'clook.\n", "cuihua", 12); // 参数数量 < 占位符数量 + 1 printf("%s says it %d o'clook.\n", "xiaoming"); return 0; } // 输出 // xiaoming says it 10 o'clook. // cuihua says it 12 o'clook. // xiaoming says it -4 o'clook.
占位符例举
printf()的占位符有许多种类,这里我把它里出来方便后期查找,请注意这是一个随用随查的工具,不需要把它们全部记下来!!!
%a:十六进制浮点数,字母输出为小写%A:十六进制浮点数,字母输出为大写%c:字符%d:十进制整数 (int)%e:使用科学计数法的浮点数,指数部分的e为小写%E:使用科学计数法的浮点数,指数部分的E为大写%i:整数(基本等同于%d)%f:小数(包含单精度浮点型float和双精度浮点型double)// 它们分别对应%f和%lf%g:6个有效数字的浮点数,整数部分一旦超过6位,就会自动转为科学计数法,指数部分的e为小写%G:等同于%g,唯一的区别在于,指数部分的E为大写%hd:十进制short int类型%ho:八进制short int类型%hx:十六进制short int类型%hu:unsigned short int类型%ld:十进制long int类型%lo:八进制long int类型%lx:十六进制long int类型%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()允许限定占位符的最小宽度
# include <stdio.h>
int main()
{
printf("%d\n", 123);
printf("%5d\n", 123456);
// 右对齐
printf("%5d\n", 123);
//左对齐
printf("%-5dxxxx\n", 123);
return 0;
}
上面的例子,%5d表示这个占位符的宽度至少为5位,如果不满5位,对应的值的前面会添加空格。
输出的值默认是右对齐,即输出内容前面会有空格: 如果希望改成左对齐,在输出内容后面添加空格,可以在占位符的%的后面插入一个-号。
%f和%lf在打印的时候,小数点后默认是打印6位的,以下是代码示例,
# include <stdio.h>
int main()
{
printf("%lf\n", 123.456);
printf("%12lf\n", 123.456);
return 0;
}
// 输出
// 123.456000
// 123.456000
总是显示正负号
在C语言的默认场景下,printf()对有打印在屏幕上的正数是不显示+号的,而是在打印负数时显示-号。
如果想要在屏幕上打印出+号,可以在占位符的%之后加上一个+。这样做的好书是能够保持屏幕上打印出来数值总是带有正负号。
以下是代码示例
# include <stdio.h>
int main()
{
printf("%d\n", 123); // 123
printf("%d\n", -123); // -123
// 在屏幕上打印 +
printf("%+d\n", 123); // +123
printf("%+d\n", -123); // -123
return 0;
}
限定小数位数
在C语言中使用printf()打印小数时,默认打印到小数点后6位。例如,我们想要打印圆周率的值3.1415926...,那么C语言默认会进行四舍五入并输出小数点后六位的值—3.141593。
但是在实际的场景中,我们往往希望能够输出限定小数的位数的值。举一个例子,我们希望输出小数点后2位的值,那么我们在占位符%的后面加上.2,即写为%.2lf。
# include <stdio.h>
int main()
{
printf("%lf\n", 3.1415926); // 3.141593
printf("%.2lf\n", 3.1415926); // 3.14
return 0;
}
类似的,如果想要保留小数点后3位,那么就可以写为%.3lf,这里我们可以回顾一下%lf表示的是双精度浮点型(double)。
我们可以在这里做一下结合,比如既要限定小数点的位数,又要限定最终输出小数的宽度,该如何操作呢?这里提供两种方式,下面直接代码举例,
# include <stdio.h>
int main()
{
printf("%lf\n", 123.1415926);
// 方式 1 小数点前的 12 表示输出的宽度
// 小数点后的 2 表示要保留小数点后2位的数
printf("%12.2lf\n", 123.1415926);
// 方式2
// 最小宽度和小数点位数这两个限定值,都可以使用 * 代替
// 之后通过 printf() 进行传递参数
// 这里的第一个 * 值为对应的第一个参数 12
// 第二个 * 值为对应的第二个参数 2
printf("%*.*lf\n", 12,2,123.1415926);
return 0;
}
输出部分字符串
%s作为一个占位符用来输出字符串,printf()默认会把输入的字符串全部输出。如果只想输出开头部分,可以使用%.[m]s来指定输出的长度,其中[m]这个整体代表一个数值,表示要输出字符串的长度。下面是代码举例,
# include <stdio.h>
int main()
{
printf("%s\n", "abcdefgh"); // abcdefgh
printf("%.3s\n", "abcdefgh"); // abc
return 0;
}
2.10.2 scanf
基本用法
scanf()函数用于读取用户在键盘上的输入
我们在运行含有scanf()函数的程序时,当程序运行到该行代码时,会停下来等待用户在键盘上面输入内容,当用户在键盘上输入完数据后,按回车键Enter后,scanf()会处理用户在键盘上输入的数据,将其存入变量。
由于scanf()是定义在头文件名为stdio.h中,因此在调用它之前请在程序开头包含头文件stdio.h
下面是代码格式
scanf("%d", &a)
scanf的第一个参数%d表示用户输入的内容应该是一个整数,%d就是一个占位符,而%是一个占位符标志,d表示一个整数类型,第二个参数&a表示,将用户从键盘上输入的整数存入变量a,
注意,这里的
&表示取地址操作符,即变量前面必须加上&符号(指针变量除外),因为scanf()函数传递的不是值,而是地址,即变量a的地址指向用户输入的值。
# include <stdio.h>
int main()
{
// 创建并初始化变量
int score = 0;
printf("请输入成绩:");
// 用户从键盘上输入内容
scanf("%d", &score);
// scanf函数中的占位符的后面的参数需要的是地址
// & - 取地址操作符, 例如, &score - 取出变量score的地址
// printf输出
printf("成绩是 %d\n",score);
return 0;
}
我们可以看到它和printf函数是类似的,它的第一个参数是格式字符串,内部放置占位符,这个基本上与printf的占位符基本一致。它能够告诉编译器如何解读用户的输入,需要提取什么类型的数据。
它的其余参数就是存放用户输入的变量,格式字符串里面有多少各占位符,就有多少各变量。下面是一个多变量的代码例子:
# include <stdio.h>
int main()
{
// 创建并初始化变量
int a = 0;
int b = 0;
float f1 = 0.0;
float f2 = 0.0;
// 用户从键盘上输入内容 1 2 3.0 4.5
scanf("%d %d %f %f", &a, &b, &f1, &f2);
// printf输出
printf("%d %d %f %f\n", a, b, f1, f2); // 1 2 3.000000 4.500000
return 0;
}
在这个例子当中,格式字符串为%d %d %f %f,表示用户需要输入四个变量,它们的类型分别是整型,整型,浮点型,浮点型。例如在这个例子中我们输入的是1 2 3.0 4.5,那么,这四个值会依次放入a,b,f1,f2当中。
由于scanf函数在处理数值占位符时,会自动过滤掉空白字符,包括空格,制表符,换行符等。
因此,用户输入数据之间,有一个或者多个空格不影响scanf函数解读数据。另外,用户在使用回车换行也不会影响解读。大家可以在自己的VS上面尝试一下,这里我就不做演示了。。。
一个小提示:在VS2022当中,如果出现下面的报错的内容,即
VS提示:
scanf函数不安全,考虑替换为scanf_s,如果让错位信息失效,请使用_CRT_SECURE_NO_WARNINGS
我们的解决方法是这样的,
-
由于
scanf和scanf_s这两个函数使用不完全相同,我们依旧是使用scanf函数 -
关于这个
_CRT_SECURE_NO_WARNINGS,我们可以在每个.c文件的第一行加入下面这行代码即可# define _CRT_SECURE_NO_WARNINGS 1 -
另外,还有一种一劳永逸的办法,因为我们每次创建
.c文件时,VS会拷贝一份来自newc++file.cpp的文件内容,我们可以使用记事本打开newc++file.cpp,将上面的内容复制粘贴上去,即可。
scanf处理用户输入的原理是,用户的输入先放入缓存,等到按下回车键后,按照占位符对缓存进行解读。解读用户输入时,会从上一次解读遗留的第一个字符开始,直到读完缓存,或者遇到第一个不符合条件的字符为止。下面是代码举例
# include <stdio.h>
int main()
{
// 创建变量并初始化
int x = 0;
float y = 0.0;
// 用户输入 " -13.45e12# 0"
scanf("%d %f", &x, &y);
printf("%d %f\n", x, y); // -13 449999994880.000000
return 0;
}
当遇到第一个小数点时,程序会输出小数点之前符合数值为整数的内容,也就是这里的-13,而变量y是一个浮点数类型的值,因此,程序会从上一次解读遗留的第一个字符也就是小数点.开始读取,当遇到#时停止,(#不属于浮点数类型),所以变量y的值为45e12也就是,但是我们看到打印在屏幕上的值确是449999994880.000000这是为什么呢?
我的回答是这样的,因为浮点数内存中可能无法精确保存
如果有大佬能够读到这,能否在评论区说一下您的观点??
scanf的返回值
在上面我们写scanf函数时VS2022总是给一个警告,样例如下
这是因为scanf函数是有返回值的,其返回值类型是一个整数
它的含义是表示成功读取到的变量个数,下面是一个代码示例
# include <stdio.h>
int main()
{
// 创建变量并初始化
int a = 0;
int b = 0;
int c = 0;
int d = 0;
// 用户输入 "10 20 30 40"
int ret = scanf("%d %d %d %d", &a, &b, &c, &d);
printf("a=%d b=%d c=%d d=%d\n", a, b, c, d);
// a=10 b=20 c=30 d=40
printf("ret=%d\n", ret);
// ret=4
return 0;
}
如果没有读取任何项,或者匹配失败,则返回0。
我们还是拿上面的代码举例,当运行代码时,用户输入10 20之后再按三次键盘上的Ctrl+Z,此时printf函数打印结果如下
// a=10 b=20 c=0 d=0
// ret=2
这里注意在VS2022中,我们要按三次键盘上的Ctrl+Z,在其他编译器上可能只需要按两次键盘上的Ctrl+Z,大家如果感兴趣可以去不同的编译器尝试一下。
如果在成功读取任何数据之前,发生了读取错误或者遇到读取到文件结尾,则返回常量EOF (-1)
EOF - end of file文件结束标志
我们还是拿上面的代码举例,当运行代码时,用户输入按三次键盘上的Ctrl+Z,此时printf函数打印结果如下
// 键盘输入
^Z
^Z
^Z
// a=0 b=0 c=0 d=0
// ret=-1
占位符
scanf常用的占位符如下,与printf函数的占位符基本一致
%c: 字符%d:整数%f:float类型浮点数%lf:double类型浮点数%Lf:LONG double类型浮点数%S: 字符串%[]: 在方括号中指定一组匹配的字符(比如%[0-9]),遇到不在集合之中的字符,匹配将会停止
注意: 上面的所有占位符之中,除了%c之外,都会自动忽略起首的空白字符。%c不忽路空白字符,总是返回当前第一个字符,无论该字符是否为空字符。
如果要强制跳过字符前的空白字符,可以写成scanf(”%c",&ch),即% 前加上一个空格,表示跳过零个或多个空白字符。下面是一个代码示例
# include <stdio.h>
int main()
{
char ch = 0;
scanf(" %c", &ch); // A
printf("xxxx%cyyyy\n", ch); // xxxxAyyyy
return 0;
}
这里也要特别说明一下%s这个占位符,他其实不能够简单地等同于字符串。
它的规则是从当前第一个非空字符开始读起,知道遇到空白字符(即空格,换行符,制表符等)为止。下面为一个代码示例
# include <stdio.h>
int main()
{
char arr[20] = { 0 };
scanf("%s", arr); // hello world
printf("%s\n", arr); // hello
return 0;
}
这里可以看到我们hello world,由于hello和world之间存在空格字符,因此当程序读入内容时,遇到第一个空格结束读取,并使用printf函数打印空格前的内容—hello。另外,我们可以看到scanf函数的第二个参数并没有使用取地址操作符&,这是因为我们创建的arr是一个数组,数组名是一个地址。
因为%s不会包含空白字符,所以无法用来读取多个单词,除非多个%s一起使用。这也意味着,scanf()不适合读取可能包含空格的字符申,比如书名或散曲名。另外,scanf()调到%占位符,会在符串变量末存储一个空字符\0。
scanf()将字符事读入字行数组时,不会检测符事是否超过了数组长度,所以,储存字行中时,很可能会超过敌姐的边界,导致预想不到的结果。(这里就解释了为什么VS2022会提示一个警告说scanf函数不安全),例如
# include <stdio.h>
int main()
{
char arr[5] = { 0 };
scanf("%s", arr); // abcdefghijklmn
printf("%s\n", arr); // abcdefghijklmn
return 0;
}
虽然能够正常打印出来想要的字符内容,但是VS也是会弹出如下窗口
为了防止这种情况,使用%占位符时,应该指定读入字符申的最长长度,即写成 %[m]s,其中的[m]是一个整数,表示读取字符串的最大长度,后面的字符将被丢弃。
赋值忽略符
有时,用户的输入可能不符合预定的格式。请看代码示例
# include <stdio.h>
int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d-%d-%d", &year, &month, &day);
printf("%d %d %d\n", year, month, day);
return 0;
}
当用户输入2025-07-05,就会正常读出年月日。问题是当用户输入的是其他的格式时,比如2025/07/05,那么在这种情况下,scanf()就会解析数据失败
为了避免这种情况,scanf()提供了一个赋值忽略符,只需要把符号*加在任意一个占位符的百分号后面,该占位符就不会返回值,解析后将被丢弃。代码示例
# include <stdio.h>
int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d%*c%d%*c%d", &year, &month, &day);
printf("%d %d %d\n", year, month, day);
return 0;
}
最后是一个小小的建议
在创建变化量的时候,给变量一个初始值,是一种好的编程习惯,
如果不给变量初始值,有的编译器会报错,比如 VS2022
虽然在其他的编译器上可能成功编译未初始化的变量,但是
局部变量不初始化,它每次编译的值总是随机的
而全局变量如果不初始化,默认值为-1
来源
本篇文章来自于b站鹏哥C语言教学视频的学习笔记