Bootstrap

C语言入门 :《第一章:初识C语言之从打印 Hello World到被指针追杀の几个月》

C语言入门 :《第一章:初识C语言之从打印 Hello World到被指针追杀の几个月》

目录

一、什么是c语言(来到了经典的搬定义环节)

C语言是一门通用计算机语言,广泛应用于底层开发。C语言的设计目标是提供一种能简易的方式编译,处理低级存储器,产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。
尽管C语言提供了许多低级处理的功能,但仍然保持着良好跨平台的特性,以一个标准规格写出的C语言程序可在许多电脑平台上编译,甚至包含一些嵌入式处理器,以及超级电脑等作业平台。
二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言制定了一套完整的美国国家标准语法,称为ANSI C,作为C语言最初的标准。最新的标准是C23。

二、环境搭建:安装你的编译器

众所周知,C语⾔是⼀⻔编译型计算机语⾔,C语⾔源代码都是⽂本⽂件,⽂本⽂件本⾝⽆法执⾏,必须通过编译
器翻译和链接器的链接,⽣成⼆进制的可执⾏⽂件,可执⾏⽂件才能执⾏。 
C语⾔代码是放在 .c 为后缀的⽂件中的,要得到最终运⾏的可执⾏程序,中间要经过编译和链接2个过程。
  • VS Vode + CLang : 年轻人的第一套赛博炼丹炉
  • Dev-C++:祖传IDE,图标长得像Win98回收站
  • VS2022 :主角登场,本人最喜欢用的

以上都为集成开发环境,同时担任编辑器,编译器,链接器,调试器的工作

		温馨提醒 :创建项目时,名称要有一定的意义,不要乱写,尽量不要用中文,兼容性不太好。


		创建源文件
      	写C语言代码的时候 我们代码放在文件中
		.h —— 头文件
		.c —— 源文件

三、Hello World : 程序员の神秘仪式

	打开编辑器,颤抖着敲下这段祖传代码:
#include<stdio.h>	//头文件 :相当于找隔壁stdio家借打印机

int main()			//main为主函数,程序的入口,比霍格沃兹入口还重要
{
	printf("Hello World \n");
	return 0;
}
接下来 按Ctrl + F5 即可运行 编译链接代码,禁忌的魔法阵将会启动。

同时我们也要了解刚才写代码时一些特性:助你理解刚刚代码的逻辑

  1. main叫主函数
  2. 主函数是程序的入口
  3. C语言代码中必须要有主函数
  4. 但是有且仅有一个主函数
  5. 一个工程(项目)中可以有多个.c文件,但是多个.c文件中也只能有一个main函数
  6. printf —— 是c语言标准库中提供的一个库函数,是用来在屏幕上打印数据的
  7. 那么include<stdio.h>就是用来提供对应库函数中的一个类别
    stdio — standard input output 标准输入输出头文件

四、数据类型:操纵现实的禁忌法则 ( ̄▽ ̄)~*

	可能你有想过,我们为什么要写代码?写代码是解决未来生活的问题,尤其是一些重复的问题,
	C语言必须有能力描述我们的生活。所以数据类型设定随之而来。

																				所占字节数空间
 	char   	   							字符数据类型									1
 	short  								短整型										2
 	int									整型										4
 	long								长整型										4
 	long long							更长的整型									8
 	float								单精度浮点数									4
 	double								双精度浮点数									8
 	_Bool								布尔类型(表示真假)							1

其中在vs2022中创建float变量最好加上f,否则会默认为double型。
在这里插入图片描述在这里插入图片描述

且浮点型数据在内存中无法精确保存。(和二进制的因素有关)


我们可以写一段代码验证其所占内存空间:

#include<stdio.h>
int main()
{
	printf("%d\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(_Bool));
	return 0;
}

在vs2022 X64下运行的结果如下:
在这里插入图片描述

其中%zd	意思是unsight int(无符号整型)类数据返回类型的占位符
sizeof是一个计算符,是用来计算变量大小的,单位是字节
计算完后的值即为返回值,返回到占位符处

既然出现了字节那顺便将计算机中常见的单位给出:

  • bit — 比特位

  • byte — 字节

  • KB — 千字节

  • MB — 兆字节

  • GB — 吉字节

  • TB — 太字节

  • PB — 拍字节

      换算:
      1B(字节)=8b(位)( 1 Byte(字节)=8 bit(位))
      1 KB = 1024 B
      1 MB = 1024 KB
      1 GB = 1024 MB
      1TB = 1024GB
    

sizeof操作符の基本操作

sizeof 的语法简单到令人发指:

sizeof(对象或类型)

示例代码

#include<stdio.h>
int main()
{
	int age = 25;
	printf("%zd\n",sizeof(int));
	printf("%zd\n",sizeof(age));
}

灵魂总结

  • sizeof是编译时操作符,不是函数!运行时不会计算括号内的表达式
  • %zd 或 %zu时标准输出的占位符,专门接应sizeof的结果(z表示size_t类型)。

sizeofの进阶操作

  • 测表达式:
printf("zd\n",sizeof(3 + 5));

注意sizeof(3 + 5)不会真的计算3 + 5,它只看类型!

  • 测虚空(void)
printf("%zd\n",sizeof(void));

五、变量、常量

生活中有些值是不变的(比如性别,身份证号,名字,血型·····)
有些值是可变的(比如:年龄,体重,身高)
不变的值,C语言中用常量的概念来表示,变得值C语言中用变量来表示。

变量的声明与定义

  • 声明:当变量已定义,存在时才可声明。(一般要用变量时超出作用域需用到声明)
    相当于在房东(编译器)那里挂个号:“我要租个房,户型是int,名字叫age!”
    例如
extern int age
  • 定义:类型 + 变量名 即可定义一个变量
    相当于真金白银签合同,房东给你钥匙(分配内存)。
    例如:
int age = 18; 			//定义完成即被分配对应字节的内存

同时要养成一个好的习惯:定义变量的同时给一个初始值(初始化变量)。

变量 & 命名の艺术

给变量命名就像给猫起名——越直白越好:
以下时命名时的注意事项:

  • 只能由字母(包括大写小写)、数字下划线组成。(千万别用中文变量名,虽然C99支持,但你未来的同事可能提着40米的大刀正在路上)
  • 不能以数字开头。
  • 长度不能超过63个字符。
  • 变量名中是区分大小写的。
  • 变量名不能使用关键字

变量的分类

全局变量:在⼤括号外部定义的变量就是全局变量
全局变量的使⽤范围更⼴,整个⼯程中想使⽤,都是有办法使⽤的。
局部变量:在⼤括号内部定义的变量就是局部变量
局部变量的使⽤范围是⽐较局限,只能在⾃⼰所在的局部范围内使⽤的。

#include <stdio.h>
int global = 2023;//全局变量 
int main()
{
 int local = 2018;//局部变量 
 printf("%d\n", local);
 printf("%d\n", global);
 return 0;
}

当局部变量和全局变量同名的时候,局部变量优先使⽤。

变量的作用域和生命周期

作用域

作用域(scope)是程序设计概念,通常来说,一段程序代码中所用到的		名字
并不总是有效/可用的。而限定这个名字的可用性的蒂埃玛范围就是这个名字
的作用域

1、局部变量的作用域是变量所在的局部范围
2、全局变量的作用域是整个工程
生命周期
变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段
1、局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
2、全局变量的生命周期是:整个程序的生命周期。


常量

 C语言中的常量和变量的定义的形式有所差异

C语言中的常量分为以下几种:

  • 字面常量
  • const 修饰的常变量(虽然不能被修改,但是还是有变量属性)
  • #define 定义的标识符常量
  • 枚举常量

六、字符串

“hello kiotao.\n”

这种由双引号括起来的一串字符称为字符串字面值,或者简称字符串
本质是字符数组的cosplay:

char str[] = "hello";		//	实际是h e l l o \0
  • 字符串的结束标志是一个\0的转义字符。在计算字符长度的时候\0是结束标志,不算字符串内容

字符串的两种初始化

  • 作死初始化法(容易手动踩雷):
char ch1[] = {'a','b','c'};		//没有'\0',等着printf表演乱码吧

*老实人初始化法:(自动补‘0’)

char ch2[] = "abc";				//长度4:a b c \0

当打印字符串时,从开始直到扫描到\0为止,所以打印第一种作死初始化法没有手动补\0的情况会一直打印超出规定内存的内容,直到遇到\0。
在这里插入图片描述

strlen vs sizeof :量身高还是量体重?

C 语言提供了一个库函数,可以计算字符串长度:strlen
需要包含头文件 string 时才能使用

#include<stdio.h>
#include<string.h>
int main()
{
	char str[] = "hello";
	printf("strlen:%zd\n",strlen(str));		//输出5(不计算'\0')
	printf("sizEof:%zd\0",sizeof(str));		//输出6(计算'\0')
	return 0;
}

重要结论:

  • strlen是测实际长度(\0之前的)
  • sizeof是测内存占用
  • 两者混淆的后果:轻则内存浪费,重则缓冲区溢出。

七、转义字符

也许在前⾯的代码中你看到 \n , \0 很纳闷是啥。其实在字符中有⼀组特殊的字符是转义字符
转义字符顾名思义:转变原来的意思的字符,\n转义后表示为换行

C语⾔中像这样的转义字符还有⼀些,具体如下:

  • \? :在书写连续多个问号时使⽤,防⽌他们被解析成三字⺟词,在新的编译器上没法验证了。
  • \’ :⽤于表⽰字符常量’
  • \" :⽤于表⽰⼀个字符串内部的双引号
  • \\ :⽤于表⽰⼀个反斜杠,防⽌它被解释为⼀个转义序列符。
  • \a :警报,这会使得终端发出警报声或出现闪烁,或者两者同时发⽣。
  • \b :退格键,光标回退⼀个字符,但不删除字符。
  • \f :换⻚符,光标移到下⼀⻚。在现代系统上,这已经反映不出来了,⾏为改成类似于 \v 。
  • \n :换⾏符。
  • \r :回⻋符,光标移到同⼀⾏的开头。
  • \t :制表符,光标移到下⼀个⽔平制表位,通常是下⼀个4/8的倍数。
  • \v :垂直分隔符,光标移到下⼀个垂直制表位,通常是下⼀⾏的同⼀列。

下⾯2种转义字符可以理解为:字符的8进制或者16进制表⽰形式

  • \ddd :ddd表⽰1~3个⼋进制的数字。如: \130表⽰字符X
  • \xdd :dd表⽰2个⼗六进制数字。如: \x30表⽰字符0
    \0 :null字符,代表没有内容, \0 就是 \ddd 这类转义字符的⼀种,⽤于字符串的结束标志,其
    ASCII码值是0.

八、注释

注释有两种风格:

  • C语言的注释风格 /XXXXXX*/

      	缺陷:不能嵌套注释
    
  • C++风格的注释 //XXXXXXXX

    可以注释一行也可以注释多行
    

九、算术操作符:+、-、*、/、%

在写代码时候,⼀定会涉及到计算。 C语⾔中为了⽅便运算,提供了⼀系列操作符,其中有⼀组操作符叫:算术操作符。分别是: + - * / %
,这些操作符都是双⽬操作符。 注:操作符也被叫做:运算符,是不同的翻译,意思是⼀样的。

+ 和 -

*+ 和 - ⽤来完成加法和减法。
*+ 和 - 都是有2个操作数的,位于操作符两端的就是它们的操作数,这种操作符也叫双⽬操作符。

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

*

运算符 * ⽤来完成乘法。

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

/

运算符 / ⽤来完成除法。 除号的两端如果是整数,执⾏的是整数除法,得到的结果也是整数。

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

上⾯⽰例中,尽管变量 x 的类型是 float (浮点数),但是 6 / 4 得到的结果是 1.0 ,⽽不是
1.5 。原因就在于C语⾔⾥⾯的整数除法是整除,只会返回整数部分,丢弃⼩数部分。
如果希望得到浮点数的结果,两个运算数必须⾄少有⼀个浮点数,这时C语⾔就会进⾏浮点数除法。

#include <stdio.h>
int main()
{
 float x = 6.0 / 4; // 或者写成 6 / 4.0 
 printf("%f\n", x); // 输出 1.500000 
 return 0;
}

上⾯⽰例中, 6.0 / 4 表⽰进⾏浮点数除法,得到的结果就是 1.5 。

%

运算符 % 表⽰求模(余)运算,会返回两个整数相除的余值。且这个运算符只能⽤于整数,不能⽤于浮点数。

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

十、赋值操作符 =

赋值:在变量创建的时候给⼀个初始值叫初始化,在变量创建好后,再给⼀个值。

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

连续赋值

赋值操作符也可以连续赋值,如:

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

虽然C语言可以连续赋值,但是可读性不强,不方便理解,建议还是拆开来写

复合赋值符

在写代码时,我们经常可能对⼀个数进⾏⾃增、⾃减的操作,如下代码:

int a = 10;
a = a+3;
a = a-2;

这样代码C语⾔给提供了更加⽅便的写法:

int a = 10;
a += 3;
a -= 2;

其他的复合赋值符:

+= -=
*= /= %=
>>= <<=
&= |= ^=

十一、单目操作符:++、- -、+、-

单目操作符顾名思义 只需要一个操作数的操作符。

++和- -

  • ++是⼀种⾃增的操作符,⼜分为前置++和后置++,- -是⼀种⾃减的操作符,也分为前置- -和后置- -.
前置++
int a = 10;
int b = ++a;//++的操作数是a,是放在a的前⾯的,就是前置++ 
printf("a=%d b=%d\n",a , b);
  • 运算逻辑: 先+1,后使用。
后置++
int a = 10;
int b = a++;//++的操作数是a,是放在a的后⾯的,就是后置++ 
printf("a=%d b=%d\n",a , b);
  • 运算逻辑:先使⽤,后+1
前置- - 后置- -
  • 同理只是把加⼀换成了减⼀

+ 和 -

这⾥的+是正号,-是负号,都是单⽬操作符。
运算符 + 对正负值没有影响,是⼀个完全可以省略的运算符,但是写了也不会报错。
运算符 - ⽤来改变⼀个值的正负号,负数的前⾯加上 - 就会得到正数,正数的前⾯加上 - 会得到负数。

十二、强制类型转换

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

(类型)XXXX

代码如下:

int a = 3.14;
//a的是int类型, 3.14是double类型,两边的类型不⼀致,编译器会报警告 
//为了消除这个警告,我们可以使⽤强制类型转换:
int a = (int)3.14;//意思是将3.14强制类型转换为int类型,这种强制类型转换只取整数部分 
  • 强制类型转化一般是万不得已的时候用的,如果不需要就能实现代码,尽量不要用。

十三、scanf和printf

printfの输出手段

  • 基本用法:printf() 的作⽤是将参数⽂本输出到屏幕。它名字⾥⾯的 f 代表 format (格式化),表⽰可以定制输出⽂本的格式。
#include <stdio.h>
int main(void)
{
 printf("Hello World");
 return 0;
}

上⾯命令会在屏幕上输出⼀⾏⽂字“HelloWorld”。
printf() 不会在⾏尾⾃动添加换⾏符,运⾏结束后,光标就停留在输出结束的地⽅,不会⾃动换
⾏。
为了让光标移到下⼀⾏的开头,可以在输出⽂本的结尾,添加⼀个换⾏符 \n 。
如果⽂本内部有换⾏,也是通过插⼊换⾏符来实现,如下⽅代码:

#include <stdio.h>
int main(void) 
{
 printf("Hello\nWorld\n");
 
 printf("Hello\n");
 printf("World\n");
 return 0;
}
  • printf() 是在标准库的头⽂件 stdio.h 定义的。使⽤这个函数之前,必须在源码⽂件头部引⼊这个头⽂件。
占位符

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

所谓“占位符”,就是这个位置可以⽤其他值代⼊。

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

上⾯代码中, There are %d apples\n 是输出⽂本,⾥⾯的 %d 就是占位符,表⽰这个位置要⽤其他值来替换。占位符的第⼀个字符⼀律为百分号 % ,第⼆个字符表⽰占位符的类型, %d 表⽰这⾥代⼊的值必须是⼀个整数。
printf() 的第⼆个参数就是替换占位符的值,上⾯的例⼦是整数 3 替换 %d 。执⾏后的输出结果就是 There are 3 apples 。

  • 输出⽂本⾥⾯可以使⽤多个占位符,printf() 参数与占位符是⼀⼀对应关系,如果有 n 个占位符, printf() 的参数就应该有 n + 1 个。如果参数个数少于对应的占位符, printf() 可能会输出内存中的任意值。
占位符列举
  • %a :⼗六进制浮点数,字⺟输出为⼩写。
  • %A :⼗六进制浮点数,字⺟输出为⼤写。
  • %c :字符。
  • %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 类型。
  • %% :输出⼀个百分号。
输出格式

printf() 可以定制占位符的输出格式。

限定宽度

printf 可以限定占位符的最小宽度

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

上⾯⽰例中, %5d 表⽰这个占位符的宽度⾄少为5位。如果不满5位,对应的值的前⾯会添加空格
输出的值默认是右对⻬,即输出内容前⾯会有空格;如果希望改成左对⻬,在输出内容后⾯添加空格,可以在占位符的 % 的后⾯插⼊⼀个 - 号

#include <stdio.h>
int main()
{
 printf("%-5d\n", 123); // 输出为 "123 " 
 return 0;
}
  • 上⾯⽰例中,输出内容 123 的后⾯添加了空格。

对于⼩数,这个限定符会限制所有数字的最⼩显⽰宽度

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

%12f 表⽰输出的浮点数最少要占据12位。由于⼩数的默认显⽰精度是⼩数点后6位,小数点也算一位!!!
所以 123.45 输出结果的头部会添加2个空格。

总是显⽰正负号
  • 默认情况下, printf() 不对正数显⽰ + 号,只对负数显⽰ - 号。如果想让正数也输出 + 号,可以在占位符的 % 后⾯加⼀个 +
#include <stdio.h>
int main()
{
 printf("%+d\n", 12); // 输出 +12 
 printf("%+d\n", -12); // 输出 -12 
 return 0;
}
限定⼩数位数
  • 输出⼩数时,有时希望限定⼩数的位数。举例来说,希望⼩数点后⾯只保留两位,占位符可以写成 %.2f
// 输出 Number is 0.50 
#include <stdio.h>
int main()
{
 printf("Number is %.2f\n", 0.5);
 return 0;
}
  • 上⾯⽰例中,如果希望⼩数点后⾯输出3位( 0.500 ),占位符就要写成 %.3f

我们也可以当个缝合怪,可以与限定宽度占位符结合使⽤:

// 输出为 " 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 传⼊。

输出部分字符串
  • %s 占位符⽤来输出字符串,默认是全部输出。如果只想输出开头的部分,可以⽤ %.[m]s 指定输出 的⻓度,其中 [m] 代表⼀个数字,表⽰所要输出的⻓度。
// 输出 hello 
#include <stdio.h>
int main()
{
 printf("%.5s\n", "hello world");
 return 0;
}

上⾯⽰例中,占位符 %.5s 表⽰只输出字符串“helloworld”的前5个字符

scanf

当我们有了变量,我们需要给变量输⼊值就可以使⽤ scanf 函数,如果需要将变量的值输出在
屏幕上的时候可以使⽤ prinf 函数,下⾯看⼀个例⼦:
#include <stdio.h>
int main()
{
 int score = 0;
 printf("请输⼊成绩:");
 scanf("%d", &score);
 printf("成绩是:%d\n", score);
 return 0;
}
scanf的基本用法

scanf() 函数⽤于读取⽤⼾的键盘输⼊。
程序运⾏到这个语句时,会停下来,等待⽤⼾从键盘输⼊。
⽤⼾输⼊数据、按下回⻋键后, scanf() 就会处理⽤⼾的输⼊,将其存⼊变量。
它的原型定义在头⽂件 stdio.h 。
scanf() 的语法跟 printf() 类似。

scanf(“%d”, &i);

它的第⼀个参数是⼀个格式字符串,⾥⾯会放置占位符(与 printf() 的占位符基本⼀致),告诉编译器如何解读⽤⼾的输⼊,需要提取的数据是什么类型。

这是因为C语⾔的数据都是有类型的, scanf() 必须提前知道⽤⼾输⼊的数据类型,才能处理数据。

它的其余参数就是存放⽤⼾输⼊的变量,格式字符串⾥⾯有多少个占位符,就有多少个变量
上⾯⽰例中, scanf() 的第⼀个参数 %d ,表⽰⽤⼾输⼊的应该是⼀个整数。 %d 就是⼀个占位
符, % 是占位符的标志, d 表⽰整数。第⼆个参数 &i 表⽰,将⽤⼾从键盘输⼊的整数存⼊变量
i 。
注意:变量前⾯必须加上 & 运算符(指针变量除外),因为 scanf() 传递的不是值,⽽是地址,就是将变量 i 的地址指向⽤⼾输⼊的值。

如果这⾥的变量是指针变量(⽐如字符串变量),那就不⽤加 & 运算符。

下⾯是⼀次将键盘输⼊读⼊多个变量的例⼦。

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

上⾯⽰例中,格式字符串 %d%d%f%f ,表⽰⽤⼾输⼊的前两个是整数,后两个是浮点数,⽐如 1 -20 3.4 -4.0e3 。这四个值依次放⼊ i 、 j 、 x 、 y 四个变量。

scanf() 处理数值占位符时,会⾃动过滤空⽩字符,包括空格、制表符、换⾏符等。
所以,用户输⼊的数据之间,有⼀个或多个空格不影响 scanf() 解读数据。另外,⽤⼾使⽤回⻋键,将输⼊分成⼏⾏,也不影响解读


1
-20
3.4
-4.0e3

上⾯⽰例中,⽤⼾分成四⾏输⼊,得到的结果与⼀⾏输⼊是完全⼀样的。每次按下回⻋键以后,scanf() 就会开始解读,如果第⼀⾏匹配第⼀个占位符,那么下次按下回⻋键时,就会从第⼆个占位符开始解读

  • scanf() 处理⽤⼾输⼊的原理是,⽤⼾的输⼊先放⼊缓存,等到按下回⻋键 进⾏解读
  • 解读⽤⼾输⼊时,会从上⼀次解读遗留的第⼀个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为⽌
#include <stdio.h>
int main()
{
 int x;
 float y;
 
 // ⽤⼾输⼊ " -15.65e12# 0" 
 scanf("%d", &x);
 printf("%d\n", x);
 scanf("%f", &y);
 printf("%f\n", y);
 return 0;
}

上面的代码中,当用户输入该数据后,%d占位符会忽略掉你前面输的一堆空格,从 – 处开始获取数据,读到–15停下来,因为后面的不属于整数的有效字符。
当第二次调用scanf 时,就会从上一次停止解读的地方,继续往下读取。(因为你输入的值还没有被读取完)这一次读取的首字符时. 由于对应的占位符是%f,会读取到.65e12,这是采用科学计数法的浮点数格式。后面的#不是浮点数的有效格式,所以不读取。

由于 scanf() 可以连续处理多个占位符,所以上⾯的例⼦也可以写成下⾯这样。
#include <stdio.h>
int main()
1
2
3

 int x;
 float y;
 
 // ⽤⼾输⼊ " -15.65e12# 0" 
 scanf("%d%f", &x, &y);
 return 0;
}
scanf的返回值

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

  • 如果没有读取任何项,或者匹配失败,则返回 0
  • 如果在成功读取任何数据之前,发⽣了读取错误或者遇到读取到⽂件结尾,则返回常量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;
}

下面进行输出输入测试:
在这里插入图片描述

正常输入三个值,返回值r为3,表示成功读取三个值。

在这里插入图片描述

当输入两个数后,按ctrl + z,将会提前结束输入一个值
可以看到r = 2成功读取两个变量

在这里插入图片描述

在vs2022的环境中连续ctrl + z 回车三次才能结束输入(虽然我也不知道为啥),我们可以看到r = -1也就是EOF

占位符

scanf 和 printf 的占位符基本一致
这里再补充一个:
%[ ] :在⽅括号中指定⼀组匹配的字符(⽐如 %[0-9] ),遇到不在集合之中的字符,匹配将会停⽌。

  • 所有占位符之中,除了 %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;
}

上⾯代码中, name 是⼀个⻓度为11的字符数组, scanf() 的占位符 %10s 表⽰最多读取⽤⼾输⼊的10个字符,后⾯的字符将被丢弃,这样就不会有数组溢出的⻛险了。

赋值忽略符

有些时候,需要的输⼊可能不符合预定的格式

#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-01-01 ,就会正确解读出年、⽉、⽇。问题是⽤⼾可能输⼊其他格式,⽐如 2050/01/01 ,这种情况下, scanf() 解析数据就会失败。为了避免这种情况, scanf() 提供了⼀个赋值忽略符 * 。
只要把 * 加在任何占位符的百分号后⾯,该占位符就不会返回值解析后将被丢弃

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

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

scanf在VS环境中的使用

在我们常用的集成开发环境vs中,如果我们使用scanf函数则会遇到这种情况:

在这里插入图片描述
如你所见,会给无情的报错,这是因为vs环境认为scanf是不安全的函数。
如果我们要正常使用输入函数,则有以下两种办法:

  • 1、在代码的最上端下一个宏定义,保证scanf继续使用
    在这里插入图片描述

这个宏定义中间的部分到下面输出列表里复制即可

在这里插入图片描述

  • 2、使用vs专门提供的一个函数scanf_s即可,其余使用方式不变。但只在vs环境下才可使用,不符合C语言标准,跨平台兼容性差

在这里插入图片描述

;