1.数据类型介绍

1.1整型int(integer

//短整型
short [int]
[signed] short [int]
unsigned short [int]
//整型
int
[signed] int
unsigned int
//⻓整型
long [int]
[signed] long [int]
unsigned long [int]
//更⻓的整型
//C99中引⼊
long long [int]
[signed] long long [int]
unsigned long long [int]

signed为有符号的,即存在负数;unsigned为无符号的,即都是非负数,有符号整型,无符号整型的数据类型取值范围也不同,关于数据范围取值类型放在后面一起介绍

1.2实型

float  //单精度
double  //双精度
long double  //长双精度,精度更高

1.3字符型char(character)

char //character
[signed] char //有符号的
unsigned char //⽆符号

单引号是字符,'A',双引号"A"是字符串(不是字符数组,字符串≠字符数组)

在键盘上能敲出的各种字符,如a、q、@等等,都是字符。

我们知道计算机的所有数据都是二进制的形式存储的,那么这些字符在内存中是以什么样的二进制存储的呢?

如果给这些字符中的每个字符编一个二进制序列,这个叫做编码,为了统一这个编码,后来美国国家标准学会(ANSI)出台了一个标准ASCII编码,C语言中的字符就遵循了ASCII编码的方式。

参考:ASCII 码表 - cppreference.com

需要记住的几个:

①字符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个字符是不可打印字符,无法在屏幕上观察。

1.4布尔类型

C 语言原来并没有为布尔值单独设置⼀个类型,而是使用整数 0 表示假,非零值表示真。
C99 中引入了 布尔类型 ,是专门表示真假的。
_Bool   //布尔类型


//stdbool.h头文件的内容:
//
// stdbool.h
//
//      Copyright (c) Microsoft Corporation. All rights reserved.
//
// The C Standard Library <stdbool.h> header.
//
#ifndef _STDBOOL
#define _STDBOOL

#define __bool_true_false_are_defined 1

#ifndef __cplusplus

#define bool  _Bool    //看这三行
#define false 0
#define true  1

#endif /* __cplusplus */

#endif /* _STDBOOL */
布尔类型的使用要包含头文件<stdbool.h>,布尔变量的取值是true或false
代码示例:
_Bool flag = true;
if (flag)
printf("i like C\n");

1.5各种数据类型的长度

每一种数据类型都有自己的长度,使用不同的数据类型,能够创造出不同的变量,变量长度不同,存储的数据范围就有所差异。
1.5.1sizeof()操作符

sizeof是一个关键字(上一个博客里写了),也是一个操作符,是专门用来介绍sizeof的操作数的类型长度的,操作数可以是类型名或表达式,单位是字节(byte)

例如

sizeof(类型)
sizeof 表达式  
//表达式不真实参与运算的,根据表达式的类型得出大小
//操作数如果不是类型,可以省略后面的括号

sizeof(int)
sizeof 3
sizeof a

sizeof的返回值是size_t类型的,C语言没有规定具体的类型,只规定是无符号整数,具体的类型由系统决定。不同的系统中,返回值的类型有可能是unsigned int,也有可能是unsigned long,甚至是unsigned long long,对应的占位符分别为%u、%lu、%llu,这样不利于程序的可移植性。于是C语言提供了一个解决方法——创造一个类型别名size_t,用来统一表示sizeof的返回值类型,对应的占位符为%zu

例如

#include <stdio.h>
int main()
{
int a = 10;
printf("%zd\n", sizeof(a));
//4
printf("%zd\n", sizeof a);//a是变量的名字,可以省略掉sizeof后边的()
//4
printf("%zd\n", sizeof(int));
//4
printf("%zd\n", sizeof(3 + 3.5));
//8
return 0;
}

注意:sizeof中表达式不计算!!!

#include <stdio.h>
int main()
{
short s = 2;
int b = 10;
printf("%d\n", sizeof(s = b+1));
//2
printf("s = %d\n", s);
//s = 2
return 0;
}

sizeof在代码进行编译的时候,就根据表达式的结果的类型,推到并确定了类型的长度,而表达式要真的执行起来是在程序运行期间才会发生,但是编译前已经将sizeof处理掉了,所以在运行期间就不会执行表达式了

1.5..2数据类型长度
#include <stdio.h>
int main()
{
printf("%zd\n", sizeof(char));
//1
printf("%zd\n", sizeof(_Bool));
//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));//在vs上是8个字节,在gcc上测试是16
//8
return 0;
}

2.signed和unsigned

C语言使用signed和unsigned修饰字符型和整型类型。

signed关键字,表示一个类型带有正负号,包含负值。

unsigned关键字,表示该类型不带有正负号,只能表示零和正整数。

对于int类型,默认是带有正负号的,也就是int等同于signed int。

无符号整型必须用关键字unsigned声明变量。

unsigned int a;

整数变量声明为unsigned的好处是,同样长度的内存能够表示的最大整数值,增大了一倍。

比如16位的signed short int 的取值范围是-32768~32767(2的15次方-1),,而unsigned short int的取值范围为0~65535,显然最大值几乎增大了一倍。

32位的signed int的取值范围可以看limits.h中给出的定义(如下是VS2022环境中的)

#define SHRT_MIN (-32768) //有符号16位整型的最⼩值
#define SHRT_MAX 32767 //有符号16位整型的最⼤值
#define USHRT_MAX 0xffff //⽆符号16位整型的最⼤值
#define INT_MIN (-2147483647 - 1) //有符号整型的最⼩值
#define INT_MAX 2147483647 //有符号整型的最⼤值

unsigned int里面的int可以省略,所以可以写成

unsigned a;

字符类型也可以设置signed和unsigned。

signed char c; // 范围为 -128 到 127
unsigned char c; // 范围为 0 到 255

注意:C语言规定char类型是否带有正负号,由当前系统决定

这就是说,char不等同于signed char,它有可能是signed char,也有可能是unsigned char。

这一点与int不同,int默认为就是signed int。

3.数据类型的取值范围

每一种数据类型都有自己的取值范围,也就是存储的数值的最大值和最小值所划出的区间。

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 的最大值。

4.变量

4.1变量的创建

变量创建的语法形式:

data_type name;
    |       |
    |       |
 数据类型  变量名

变量命名的一般规则:

①只能由字母(包括大写和小写),数字和下划线(‘_’)组成。

②不能以数字开头

③长度不能超过63个字符。

④变量名中区分大小写的(大小写敏感)。

⑤变量名不能使用关键字(也尽量不要和函数重名)。

变量在创建的时候就赋一个值,叫做初始化。
 

int age = 18;
char ch = 'w';
double weight = 48.0;
unsigned int height = 100;

4.2变量的分类

全局变量:在大括号外部定义的变量;在整个工程中都能使用

局部变量:在大括号内部定义的变量;只能在自己所在的局部范围内使用。

!!!当全局变量和局部变量重名时,局部变量优先!!!

#include <stdio.h>
int n = 1000;
int main()
{
int n = 10;
printf("%d\n", n);//打印的结果是多少呢?
//10
return 0;
}

内存中有三个区:栈区,堆区和静态区

(1)局部变量是放在内存的栈区

(2)全局变量放在内存的静态区(静态变量也在这里)

(3)堆区是用来动态内存管理的

5.算术操作符

有 +(加)、-(减)、*(乘法)、/(除法)、%(取余)。

这些都是双目运算符。

操作符又称为运算符,是一个意思。

5.1+和-

#include <stdio.h>
int main()
{
int x = 4 + 22;
int y = 61 - 23;
printf("%d\n", x);
printf("%d\n", y);
return 0;
}

5.2 *

#include <stdio.h>
int main()
{
int num = 5;
printf("%d\n", num * num); // 输出 25
return 0;
}

5.3 /

#include <stdio.h>
int main()
{
float x = 6 / 4;
int y = 6 / 4;
printf("%f\n", x); // 输出 1.000000
printf("%d\n", y); // 输出 1
return 0;
}

“/”两端如果是整型——两个操作数都是整数,则执行的是整数除法,得到结果的也是整数。

如果至少有一个操作数为浮点型,则是小数除法,如6/2.0

举例:

#include <stdio.h>
int main()
{
int score = 5;
score = (score / 20) * 100;
return 0;
}

上面的代码,常规运算,score会等于25,但是实际上score等于0。

这是因为score/20是整除,括号内的结果为0,所以乘上100后也还是零,想要得到25需要把20改为20.0。

#include <stdio.h>
int main()
{
int score = 5;
score = (score / 20.0) * 100;
return 0;
}

5.4 %

%用于取余(求模)运算,即返回整数除法的余数,不能用于浮点数

#include <stdio.h>
int main()
{
int x = 6 % 4; // 2
return 0;
}

负数求模的规则是,结果的正负号由第一个运算数(被除数)的正负号决定。

#include <stdio.h>
int main()
{
printf("%d\n", 11 % -5); // 1
printf("%d\n",-11 % -5); // -1
printf("%d\n",-11 % 5); // -1
return 0;
}

上面所示,第一个运算数(被除数)的正负号决定了结果的正负号。

6.赋值操作符

上面讲到了初始化:在变量创建的时候给一个初始值叫初始化。

变量创建好后再给一个值,叫赋值。

int a = 100;//初始化
a = 200;//赋值,这⾥使⽤的就是赋值操作符

赋值操作符为“=”。

6.1连续赋值

C语言支持连续赋值,形式上像数学中的连等,但是写出来的代码不容易被理解,建议拆开写。

需要注意的是,连续赋值是从右向左依次赋值的,因为赋值操作符“=”的结合性为右结合。

int a = 3;
int b = 5;
int c = 0;
c = b = a+3;//连续赋值,从右向左依次赋值的。


int a = 3;
int b = 5;
int c = 0;
b = a + 3;
c = b;

6.2复合赋值

复合赋值符有:

+= -=
*= /= %=
//下⾯的操作符后期讲解
>>= <<=
&= |= ^=

复合赋值是对变量自身进行操作,

int a = 10;
a = a+3;  //可以写为a += 3;
a = a-2;  //可以写为a -= 2;

7.单目操作符:++、--、+(正号)、-(负号)

7.1.1++和--

++是一种自增1的符号,就是把原变量的值加1再赋值给该变量,++分为前++和后++。

--是一种自减1的符号,就是把原变量的值减1再赋值给该变量,--分为前--和后--。

7.1.2前置++,前置--

前置++就是先加1后使用:先完成自增1的运算,再使用该变量

#include <stdio.h>
int main()
{
int a = 10;
int b = ++a; 
printf("a=%d b=%d\n",a , b);
//a=11,b=11
}

前置--就是先减1后使用:先完成自减1的运算,再使用该变量。

#include <stdio.h>
int main()
{
int a = 10;
int b = --a; 
printf("a=%d b=%d\n",a , b);
//a=9 b=9
}
7.1.3后置++,后置--

后置++就是先使用后加1:先使用该变量再进行自增1的运算。

#include <stdio.h>
int main()
{
int a = 10;
int b = a++; 
printf("a=%d b=%d\n",a , b);
//a=11 b=10
}

后置--就是先使用后减1:先使用该变量再进行自减1的运算。

#include <stdio.h>
int main()
{
int a = 10;
int b = a--; 
printf("a=%d b=%d\n",a , b);
//a=9 b=10
}

7.2  +(正号)和-(负号)

这里的+和-分别是正号和负号,不是加号和减号。二者都是单目运算符。

运算符+对正负值没有影响(和数学中一样),是一个完全可以省略的运算符,写上了也不会报错。

int a = +10; 等价于 int a = 10;

运算符-用来改变一个值得正负性,(和数学中一样),负数前面加上-会得到正数,正数前面加上-会得到负数。

int a = 10;
int b = -a;
int c = -10;
printf("b=%d c=%d\n", b, c);//这⾥的b和c都是-10
int a = -10;
int b = -a;
printf("b=%d\n", b); //这⾥的b是10

8.强制类型转换

在操作符中还有一种特殊的操作符是强制类型转换,语法形式很简单,如下:

(类型名)表达式

示例:

int a = 3.14;
//a的是int类型, 3.14是double类型,两边的类型不⼀致,编译器会报警告

int a = (int)3.14;
//意思是将3.14强制类型转换为int类型,这种强制类型转换只取整数部分

C语言中的类型转换(Type Conversion),包括自动类型转换(Implicit Type Conversion)和强制类型转换(Explicit Type Conversion)。

前者是C编译系统自动完成,后者是由程序中的类型转换语句指定的(如上)。

自动类型转换的原则是将参与运算的操作数转化成其中占用内存字节数最大的操作数的类型(这个在上面介绍了,可以通过sizeof操作符来得到),如下:

简称的类型名 完整类型名 长度(字节byte) 取值范围
char signed char 1 -
unsigned char unsigned char 1 -
short signed short int 2 -
unsigned short unsigned short int 2 -
int signed int  4 -
unsigned int  unsigned int 4 -
long signed long int 4 -
unsigned long unsigned long int 4 -
float float 4 -
double double 8 -
long double long double 8

一字节等于8比特,即1byte=8bit。

数据类型的取值范围计算方式为,

如果是unsigned,则是从零开始到2的(数据类型长度*8)次方-1

如果是signed,则是从负的2的(数据类型长度*8-1)次方到正的2的(数据类型长度*8-1)次方-1。

自动数据类型转换:数据类型的长度由低向高进行转换,以防损失计算精度。

首先,所有的char和short类型的操作数都转化为int类型的,然后,不同类型的操作数按照上图垂直箭头方向从低到高进行类型转换。

例如:

①'A'+32的计算过程是,将字符'A'转成整型65,再与整型32相加,结果为整型97。

注意:字符型数据与ASCII码表相对应,本质上也是整型。

②1.0/2*3.0的计算过程是,将1.0/2中的整型2转换成double类型,计算1.0/2.0得到0.5,再乘以3.0,最后的结果为double型1.5。

③1/2*3.0的计算结果为0.0,这是因为1/2为整数除,结果为0。

还有 赋值中的自动类型转换

转换的规则是:将右边表达式的值转成左边变量的类型 。

具体规则如下:

变量类型 表达式类型 赋值结果及可能出现的问题
char short或int 取其低1字节赋值,高字节舍去。当表达式的值大于127时,会出现数据溢出问题。如:char c=200会出现数据溢出,c的值为负数
short int或long 取其低2字节赋值,高字节舍去。当表达式的值大于short取值范围的上限32767时,会出现数据溢出问题。
int float或double 直接取整,截断小数部分。如:int a=3.14的结果是a=3
float double 只能保留7位有效位数,其余舍去

①当表达式的数据类型内存的字节数小于变量类型占用内存的字节数时,即由低长度类型向高长度类型赋值时,数据类型可以转换吧,不会出现数据信息丢失。

②但是反之,由高长度类型向低长度类型赋值时,就有可能出现数据精度下降、甚至数据溢出等问题。

9.scanf和printf介绍

9.1 printf

9.1.1基本用法

printf()的作用是将参数文本输出到屏幕,它名字里面的f代表了format(格式化),表示可以点痣输出文本的格式。

#include <stdio.h>
int main()
{
printf("Hello World");
return 0;
}

printf()不会再行尾自动添加换行符,运行结束后,光标就停留在输出结束的地方,不会自动换行。想要让光标移到下一行的开头,可以在输出文本的结尾,添加一个换行符 \n

#include <stdio.h>
int main()
{
    printf("Hello World\n");
    return 0;
}

如果文本内部也有换行,也是通过插入换行符来实现,如下;

#include <stdio.h>
int main()
{
printf("Hello\nWorld\n");
printf("Hello\n");
printf("World\n");
return 0;
}

printf()是在标准库的头文件stdio.h定义的,使用这个函数前,必须在源文件头部引入这个头文件,scanf()函数也是。

9.1.2占位符(格式说明符)

printf()可以在输出文本中指定占位符。

所谓占位符,就是这个位置可以用其他值代入。

上例中,%d就是占位

// 输出 There are 3 apples
#include <stdio.h>
int main()
{
printf("There are %d apples\n", 3);
return 0;
}

符,标志这个位置要其他值来替代。

占位符的第一个字符一律为百分号%,第二个字符表示占位符的类型。%d表示这里代入的值必须是一个整数。

输出文本中可以使用多个占位符。占位符和表达式的顺序是一一对应的。

除了%d外,还有其他的占位符,

%a 十六进制浮点数,字母输出为小写
%A 十六进制浮点数,字幕输出为大写
%c 字符(char)
%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
⼗进制 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 类型
%%
输出⼀个百分号

9.1.3格式修饰符

模板如下:

%[标志][宽度][.精度][长度]类型

(1)限定宽度和精度

printf()允许限定占位符的最小宽度。

最小宽度的意思是,最小输出的是这个宽度,如果超过这个宽度则对宽度的限定不起作用

#include <stdio.h>
int main()
{
printf("%5d\n", 123); // 输出为 " 123"
return 0;
}

上面示例中,%5d表示这个占位符的宽度至少为5位,如果不满足五位,会添加空格。

这里输出默认是右对齐。

对于小数,这个限定符会限制所有数字的最小显示宽度。和上面整数的一样,但是这里注意,在计算小数的宽度时,小数点也要计入。当宽度不够时,也是添加空格,但是由于小数的默认显示精度是小数点后6位,所以添加的空格数一般会比看上去要少。

// 输出 " 123.450000"
#include <stdio.h>
int main()
{
printf("%12f\n", 123.45);
return 0;
}

输出小数时,有时希望限定小数的位数,即限定精度,限定精度的数值放在小数点“.”后,如,希望小数点后保留两位,可以写成%.2f。如果小数位数不够则补零,如果超过了则截断,最后一位四舍五入。

// 杈撳嚭 Number is 0.50
#include <stdio.h>
int main()
{
printf("Number is %.2f\n", 0.5);
//Number is 0.50
printf("Number is %.2f\n", 0.548);
//Number is 0.55
return 0;
}

限定宽度和精度库结合使用,形式就是“%m.nf”

这注意区分printf()和scanf(),对于scanf函数,接受的数值赋给变量时,如果变量定义的时候是float,那格式说明符需要使用%f,但是如果定义的时候是double,格式说明符需要使用%lf。变量定义的时候,如果是浮点数,没有特殊说明,会默认为小数都是双精度double型,想要定义单精度float型,需要在小数后面加上“f”

例如

double pi = 3.14;
float a = 3.114f;

对于printf函数,只要输出的是小数,是浮点型的,占位符(格式说明符)都是%f。

如果使用的是%m.nf形式的,可以看作先限定精度,待精度确定后再算上小数点和整数部分的数位看宽度大小。

// 输出为 "  0.50"
#include <stdio.h>
int main()
{
printf("%6.2f\n", 0.5);
return 0;
}

上面示例中,%6.2f表示输出字符串最小宽度是6,小数位数为2,所以,输出字符串的头部有两个空格。

最小宽度和小数位数这两个限定值,都可以用 * 代替,通过printf()的参数传入。

#include <stdio.h>
int main()
{
printf("%*.*f\n", 6, 2, 0.5);
return 0;
}
// 等同于printf("%6.2f\n", 0.5);

上面示例中,%*.*f的两个星号通过printf()的两个参数6和2传入。

(2)+ 和 -

默认情况下,printf()对正数不显示+号,只对负数显示-好。如果想让正数前也输出+号,可以在占位符的%后面加一个+。(总是显示正负号)

#include <stdio.h>
int main()
{
printf("%+d\n", 12); // 输出 +12
printf("%+d\n", -12); // 输出 -12
return 0;
}

上面示例中,%+d可以确保输出的数值,总是带有正负号。

“-”和“+”的作用完全不同,输出的文本中,默认是右对齐,如果想要左对齐,就要在百分号%后面,也就是“标志”的位置,加上“-”。

#include <stdio.h>
int main()
{
printf("%5d\n", 123); // 输出为 " 123"
return 0;
}
#include <stdio.h>
int main()
{
printf("%-5d\n", 123); // 输出为 "123 "
return 0;
}

(3)输出部分字符串

格式说明符%s用来输出字符串,默认是全部输出,如果只想输出开头的部分,可以用%.[m]s指定输出的长度,其中[m]代表一个数字,表示所要输出的长度(空白字符也在内)。

// 输出 hello
#include <stdio.h>
int main()
{
printf("%.5s\n", "hello world");//注意百分号后先有小数点“.”再是表示输出长度的数字
return 0;
}

上面示例中,占位符“%.5s”表示只输出字符串“hello world”的前5个字符,即“hello”。

❌ printf没有类似 %-4s 这种直接取末尾 4 个的语法,标准格式说明不支持反向截取。

(4)其他,举例说明

#include <stdio.h>
int main(void)
{
    int a = 12;
    int b = -12;

    // ========= 1、- 左对齐,默认右对齐 =========
    printf("|%5d|\n", a);   // 默认右对齐,占5位:|   12|
    printf("|%-5d|\n", a);  // -左对齐:|12   |

    // ========= 2、+ 正数显示+号 =========
    printf("%+d\n", a);  // +12  正数前面输出加号
    printf("%+d\n", b);  // -12  负数依旧输出负号

    // =========3、空格标志:正数前面留空格 =========
    printf("% d\n", a); //  12(数字前面有一个空格)
    printf("% d\n", b); // -12(负数正常输出负号,不再额外空格)

    // =========4、# 辅助格式,八进制、十六进制 =========
    int num = 31;
    printf("%o\n", num);   // 八进制:37
    printf("%#o\n", num); // #给八进制加前缀0 → 037

    printf("%x\n", num);   //十六进制:1f
    printf("%#x\n", num); // #给十六进制加0x →0x1f
    printf("%#X\n", num); // 0X1F 大写X输出大写

    //注意:#对十进制 %d 没有效果!

    // =========5、0 用0填充补齐宽度 =========
    printf("%05d\n", a); //00012,宽度5,空位补0
    printf("%05d\n", b); //-0012,负号占一位

    //⚠️重点坑:0和-不能一起用!左对齐的时候0标志失效
    printf("%-05d\n",a); //输出|12   |,0无效,左对齐只能补空格

    return 0;
}

9.2 scanf

9.2.1基础用法

scanf()函数用于读取用户的键盘输入。程序运行到这个语句时,会停下来,等待用户从键盘输入。

用户输入数据、按下回车键后,scanf()就会处理用户的输入,将其存入变量。

scanf()的原型定义在头文件stdio.h中,scanf()的语法和printf()相似,但是scanf至少有接受两个参数,一个是格式字符串,一个是变量的地址(变量要提前定义)。

int i = 0;
scanf("%d", &i);

第二个参数&i表示用户输入的应该是一个整数,将用户从键盘输入的整数存入变量i。

注意:变量前面必须加上取地址运算符&,因为scanf()传递的不是值,而是地址,即将变量i的地址指向用户输入的值。

如果这里的变量是指针变量(比如字符串变量),就不用加&运算符。

scanf也可以一次性接受多个用户从键盘输入的值。但是要注意格式说明符和变量类型的对应。

scanf("%d%d%f%f", &i, &j, &x, &y);

scanf()处理格式说明符的时候,会自动过滤空白字符,包括空格,制表符,换行符等。

所以用户输入的数据质检,有一个或多个空格不影响scanf()解读数据。另外,用户使用回车键,将输入分成几行,也不影响解读。

但是%s遇到空白字符会自动结束,这点需要注意。

scanf()处理用户输入的原理是,用户的输入先放入缓存,等到按下回车键后,按照格式说明符对缓存进行解读。

解读用户输入时,会从上一次解读遗留的第一个字符开始,直到读完缓存,或者遇到第一个不符合条件的字符为止。

#include <stdio.h>
int main()
{
int x;
float y;
// ⽤⼾输⼊ " -13.45e12# 0"
scanf("%d", &x);
printf("%d\n", x);
scanf("%f", &y);
printf("%f\n", y);
return 0;
}

上面示例中,scanf()读取用户输入时,%d占位符会忽略起首的空格,从负号“-”处开始获取数据,读取到-13停下来,因为后面的“.”不属于整数的有效字符。这就是说,占位符%d会读到-13。

第二次调用scanf()时,就会从上一次停止解读的地方,继续往下读取,这一次读取的首字符是“.”,由于对应的占位符是%f,会读取到.45e12,这里采用科学计数法的浮点数格式。后面的#不属于浮点数的有效数字,所以会停留在这里。

由于scanf()可以连续处理多个占位符,所以上面的例子也可以写成下面这样。

#include <stdio.h>
int main()
{
int x;
float y;
// ⽤⼾输⼊ " -13.45e12# 0"
scanf("%d%f", &x, &y);
return 0;
}

9.2.2格式说明符

scanf()和printf()占位符基本一致,但是没有printf函数的占位符用法丰富。

%c 字符
%d 整数
%f float类型浮点数
%lf double类型浮点数
%Lf long double类型浮点数
%s 字符串
%[] 在括号内指定一直匹配的字符(比如%[0-9]、%[a-z]等等),遇到不在集合中的字符,匹配将会停止。

上述占位符中,除了%c以外,都会自动忽略起首的空白字符,%c不会忽略空白字符,总是返回当前第一个字符,无论该字符是否为空格。

如果要强制跳过字符前的空白字符,可以写成scanf(" %c",&ch);

即%c前面加上一个空格,表示跳过零个或多个空白字符。

下面对%s进行一下说明:它其实不能简单等同于字符串,它的规则是,从当前第一个非空白字符开始读起,直到遇到空白字符(即空格、换行符、制表符等)为止。

因为%s不会包含空白字符,所以无法用来读取多个单词,除非多个%s一起使用,这也意味着,scanf()不适合读取可能包含空格的字符串,比如书名或歌曲名。另外,scanf()遇到%s占位符,会在字符串变量末尾存储一个空字符\0。scanf()将字符串读入字符数组时,不会检测字符串是否超过了数组长度。所以储存字符串是,可能会超过数组的边界,导致意想不到的后果。为了防止这种情况,使用%s占位符时,应该指定读入字符串的最长长度,即写成%[m]s,其中[m]是一个整数,表示读取字符串的最大长度,后面的字符将被丢弃。

#include <stdio.h>
int main()
{
char name[11];
scanf("%10s", name);
return 0;
}
9.2.3scanf的返回值

scanf()的返回值是一个整数,表示成功读取的变量个数

如果没有读取任何项,发生了读取错误或者遇到读取文件结尾,则返回常量EOF(-1)。

EOF——end of file 文件结束标志

#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
float f = 0.0f;
int r = scanf("%d %d %f", &a, &b, &f);
printf("a=%d b=%d f=%f\n", a, b, f);
printf("r = %d\n", r);
return 0;
}

输入输出测试:

如果输入2个数后,按Ctrl+Z,提前结束输入:

上面测试使用的是DevC++,它的编译器是gcc,但是在VS环境中按三次Ctrl+Z,才结束了输入,可以看到r的值为2,表示正确读取了两个数值。

如果一个数字都不输入,直接按三次Ctrl+Z,输出的r是-1,也就是EOF。

9.2.4赋值忽略符

scanf()格式控制字符串中如果规定了格式,例如其中有“-”,空格,“_”等,scanf()对用户键盘上输入的内容格式要求严格。有时,用户的输入可能不符合预定的格式。

#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;
}

上面示例中,如果用户输入了2020-01-01,就会正确解析出年月日,问题是可能用户会输入其他格式,如2020/01/01,这种情况下,scanf()解析数据就会失败。

为了避免这种情况,scanf()提供了一个赋值忽略符(assignment suppression character)* 。

只要把“*”加在任何占位符的百分号%后面,该占位符就不会返回值,解析后将被丢弃。

示例:

#include <stdio.h>
int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d%*c%d%*c%d", &year, &month, &day);
return 0;
}

输入输出结果如下:

上面的示例中,%*c就是在占位符的百分号后面,加入了赋值忽略符*,表示这个占位符没有对应的变量,解读后不必返回。

9..2.5其他

scanf格式控制符:

% [*] [宽度] [长度] 转换字符

[ ]代表可选部分;

scanf 没有精度修饰(不能写.n),也没有 - + # 0这些 printf 的标志!

(1)转换字符就是上面的%d、%u、%f等,不再重复介绍。

重点区分:

->scanf:读double必须写%lf,不能写%f

->printf:打印double直接用%f,%lf也兼容

(2)格式控制串里面,除了%开头的格式说明,其余普通字符,输入的时候必须原样输入!

(3)宽度:数字,例如%3d

限定本次最多读取多少个字符(和printf的最小宽度不同),遇到空格提前结束

int x;
scanf("%3d",&x);
//输入12345 → x拿到123,剩下45留在缓冲区

(4)长度修饰符h\l\ll

修饰符 格式 类型
h %hd short int
l %ld long int ; 搭配%lf → double
ll %lld long long
short s;
scanf("%hd", &s);

long num;
scanf("%ld", &num);

scanf和printf的对比

printf scanf
标识符 支持 - + # 0 完全不支持
精度.n 支持 禁止
宽度 支持 支持
* 含义 动态宽度精度 赋值忽略,丢弃输入
double %f  /  %lf 必须%lf

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐