数据类型和变量

247 阅读25分钟

2.1 数据类型介绍

C语言中提供了丰富的数据类型以方便来解决现实生活中的各种数据。所谓**“类型”**,就是相似的数据说拥有的共同特征,编译器只有知道可数据类型,才知道怎么操作数据。

image-20250627110456892.png

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可以写为3.14×1003.14 × 10^0,也可以写为0.314×1010.314 × 10^1。它的小数点位置是可以移动的,正因为如此小数也叫做浮点数。

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>为使用布尔类型时包含的头文件,_Boolbool均可以调用布尔类型。

布尔类型的返回值只有两个,即要么为真true,要么为假false

2.2 各种数据类型的长度

每一种数据类型都有自己对应的长度,使用不同的数据类型,能够创建出不同长度的变量。变量长度不同,存储的数据范围就会有所差异。

sizeof操作符

sizeof是一个专门用来计算数据类型长度的操作符,它的返回值为size_t类型的,其单位为字节(Byte),其返回值在打印时使用%zd

  • 拓展 - 计算机存储单位

image-20250627143303391.png

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 signedunsigned - 有符号和无符号

对于整型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也可以设置signedunsigned

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类型。

image-20250629195533701.png

2.5.2 变量的分类

  • 全局变量:在大括号外面的定义的变量就是全局变量

    全局变量的使用范围更广,整个工程中想使用,都是有办法使用的。

  • 局部变量:在大括号内部的定义的变量就是局部变量

    局部变量的使用范围是比较局限的,只能在自己所在的局部范围内能使用

int m = 1000;

int main()
{
	{
		int m = 100;
		printf("%d\n", m); // 局部优先
	}
	return 0;
}
// 输出
// 100

那么,全局变量和局部变量在内存中储存在哪里呢?

在C语言/C++当中,我们会关注内存的三个区域:栈区,堆区,静态区

  1. 局部变量存放在内存的栈区
  2. 全局变量存放在内存的静态区

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

    说明: 2526被称为操作数,+-被称为操作符

  • /

    除号/有两种输出结果的数据类型,即整数类型和浮点类型

    如果/的两个操作数都是整数类型的数据,那么/执行整数除法,得到的结果也是一个整数类型的

    如果/的两个操作数至少有一个是浮点数类型的数据,那么/执行浮点数除法,得到的结果也是一个浮点数类型的

    代码示例:

    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 printfscanf介绍

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类型
  • %huunsigned short int类型
  • %ld十进制long int类型
  • %lo:八进制long int类型
  • %lx:十六进制long int类型
  • %luunsigned long int类型
  • %lld:十进制long long int类型
  • %llo:八进制long long int类型
  • %llx:十六进制long long int类型
  • %lluunsigned long long int类型
  • %Le:科学计数法表示的long double类型浮点数
  • %Lflong double类型浮点数
  • %n:已输出的字符串数量。该占位符本身不输出,只能将存储在指定变量之中
  • %o:八进制整数
  • %p指针(用来打印地址)
  • %s字符串
  • %u无符号整数 (unsigned int)
  • %x十六进制整数
  • %zdsize_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

image-20250704213027240.png

我们的解决方法是这样的,

  • 由于scanfscanf_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也就是45×101245×10^{12},但是我们看到打印在屏幕上的值确是449999994880.000000这是为什么呢?

我的回答是这样的,因为浮点数内存中可能无法精确保存

如果有大佬能够读到这,能否在评论区说一下您的观点??

scanf的返回值

在上面我们写scanf函数时VS2022总是给一个警告,样例如下

image-20250705092608413.png

这是因为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类型浮点数
  • %lfdouble类型浮点数
  • %LfLONG 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,由于helloworld之间存在空格字符,因此当程序读入内容时,遇到第一个空格结束读取,并使用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也是会弹出如下窗口

image-20250705101519052.png

为了防止这种情况,使用%占位符时,应该指定读入字符申的最长长度,即写成 %[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

image-20250705104226051.png

虽然在其他的编译器上可能成功编译未初始化的变量,但是

局部变量不初始化,它每次编译的值总是随机的

而全局变量如果不初始化,默认值为-1

来源

本篇文章来自于b站鹏哥C语言教学视频的学习笔记