C primer plus(学习笔记)—— 第四章 字符串和格式化输入/输出

目录

4.1 前导程序(略)

4.2 字符串简介

1、字符串常见陷阱

1)\0结束符可以不写,但是必须给它预留空间

2)手动加入的\0也会被识别并且作为结尾

3)%s在scanf中无法存储空格

4.2.1 char类型数组和null字符

4.2.2 使用字符串

4.2.3 strlen()函数

strlen()与 sizeof()的区别

4.3 常量和C预处理器

4.3.1 const限定符

4.3.2 明示常量

4.4 printf()和scanf()

宽度与精度

​ 1.基础说明

scanf()输入原理

4.4.1 printf()函数


4.1 前导程序(略)
 

4.2 字符串简介

C语言中没有专门用于储存字符串的变量类型,字符串都被储存在char类型的数组中,数组是由连续的存储单元组成,字符串的字符储存在相邻的存储单元中,每个单元存储一个字符。

1、字符串常见陷阱

1)\0结束符可以不写,但是必须给它预留空间
#include<stdio.h>
#pragma warning (disable:4996)
int main()
{
	char i[3] = "123";
	printf("%s", i);
}
//输出结果:123烫烫虁虐乴

从输出看,很明显发生了溢出,这说明C风格的字符串必须遵守:字符串储存数要始终大于储存字符量至少1个字符(字节),否则会发生溢出,原因是因为字符串需要存储一个字节的\0字符来表明字符串结束。

2)手动加入的\0也会被识别并且作为结尾
#include<stdio.h>
#include<string.h>
#pragma warning (disable:4996)
int main()
{
	char a[40] = "1\0ABC";
	printf("%s,%c",a,a[2]);
}
//输出结果为1,A

虽然在准备字符串时,给定了"1\0ABC",但是由于加入了\0字符串结束识别符,字符串的实际长度只有1,即只将1存储了进去。

​ 但是后面存储的字符是依旧存在的,但是并不算做字符串范围内,这里使用了a[2]强行越界读取,编译器报告了一个警告,但是依旧读取出了结果。

3)%s在scanf中无法存储空格
#include<stdio.h>
#pragma warning (disable:4996)
int main()
{
	char i[40];
	scanf("%s",i);
	printf("%s", i);
}
//输入:fasdaw a7221
//输出结果:fasdaw

scanf()读取到空格时字符串发生了截断,只把fasdaw读取了,后面的a7221并没有读取。这是因为scanf的读取规则是:遇到第一个空白(空格、制表符或者换行符)时不再读取输入。这是因为 scanf() 默认使用空白字符作为分隔符来区分不同的输入项。如果你想同时读取 fasdawa7221,你可以使用 %[^\n] 格式说明符,它告诉 scanf() 读取直到遇到换行符为止的所有字符;

char str[100];
scanf("%[^\n]", str);

另外,为了安全起见,最好总是使用 scanf() 的宽度限定符来避免缓冲区溢出:

scanf("%99s", str); // 假设 str 的大小是 100

​这样也有坏处或者说是限制:

  1. 截断输入:如果你的输入字符串长度超过了99个字符,那么 %99s 只会读取前99个字符,剩余的字符会被忽略。这可能会导致数据丢失,特别是当你需要处理长字符串时。

  2. 换行符问题:使用 %s 格式说明符时,scanf() 会在遇到空白字符(如空格、制表符或换行符)时停止读取。这意味着如果输入中包含空白字符,scanf() 将不会读取整个行。而使用 %99s 并不会改变这一行为,它只是限制了最大读取字符数。

  3. 不处理特殊字符%s%99s 都不会处理特殊字符,如引号或逗号。如果你的输入数据中包含这些字符,它们可能会被错误地解释或导致读取失败。

  4. 不灵活:使用 %99s 意味着你每次都需要根据你的字符串数组的大小来调整格式字符串。如果你的程序需要处理不同大小的字符串,这可能会变得笨拙。

  5. 可能的混淆:对于不熟悉 scanf() 的程序员来说,%99s 可能会造成混淆,因为它看起来像是在指定一个特定的数字,而不是一个限制。

  6. 效率问题:在某些情况下,如果输入的字符串长度远小于99,那么使用 %99s 可能会稍微降低效率,因为 scanf() 需要检查更多的字符来确定何时停止读取。

4.2.1 char类型数组和null字符

C语言没有专门存储字符串的变量类型,字符串存储在char类型数组中。

在数组末尾有一个空字符(null character)  \0 标记字符串结束。

4.2.2 使用字符串

char name[40];

scanf("%s", name);

printf("Hello, %s\n", name);

tip(只有一个字符的字符串和字符不同): ‘x' 和"x“ 是不同的,单引号的是字符;双引号的是字符串,实际上是两个字符x和\0组成的。

‘x’        是一个字符(char基本类型)        “x”        是一个字符串 (本质上:char数组)

4.2.3 strlen()函数

strlen()与 sizeof()的区别

strlen()是用于计算字符串实际长度的,它会以\0为结束符来计算到底有几个实际字符存入了该字符串。(如果没有遇到 \0 导致该字符串没有结束符。字符串前后的存储空间并不是空的,只不过并不在字符串的存储范围内,他们通常是一些垃圾数据。此时strlen()在超过字符串限制后会继续往后读取垃圾数据,直到它遇到\0才会结束读取,所以出现了字符量远大于字符串容量的情况。)

​ sizeof()是用来计算字符串容量的,无论存储了几个字符进去,他只会显示该字符串的最大容量。

4.3 常量和C预处理器

定义常量

 #define NAME value

//注意 后面没;

例如:define PI 3.1415

这里涉及到了宏替换的定义规则

4.3.1 const限定符

const关键字 限定一个变量为只读,

const int MONTHS = 12;

PS:在C语言里,const类型限定符声明的是变量,而不是常量

const 和 #define的区别


#define在编译的预处理阶段起作用,只是进行字符串的替换,并没有类型检查等操作,在程序运行时,程序每使用一次#define定义的值就会开辟一块内存,容易浪费内存。程序中使用#define可以使程序简单明了,通俗易懂。并且代码的维护相对容易,在修改定义的值时只需修改定义处即可。
const常量有数据类型,在运行时会对其进行类型安全检查,const定义的只读变量在程序运行过程中只备份一次,比较节省空间。

4.3.2 明示常量

C头文件limits.h和float.h 分别提供了与整数类型和浮点数类型大小限制相关的信息。

4.4 printf()和scanf()

输出 和 输入函数,简称I/O函数。

宽度与精度


​ 1.基础说明

​ printf与scanf在输入输出时可以在占位符上添加修饰符,其中使用最多的就是宽度与精度。

​ %d加入宽度后:%数字d

​ %d加入精度后:%.数字d,请注意scanf是不可以描述精度的,scanf中只允许使用宽度。

​ printf中一个占位符可以同时存在宽度与精度:%数字.数字d

scanf()输入原理

scanf的输入和赋值是两部分组成,输入时通过一个输入池进行管理。运行到scanf位置时,输入池打开时,可以向里面输入任何字符都会被保存在输入池中(包括enter键的换行符),识别到换行符时关闭输入池,scanf按顺序从输入池中取出相应的字符进行赋值。

​ 如果在第一次打开输入池时,输入的值满足了后续赋值所需要的数据个数,同样运行到该scanf位置上时,会直接从输入池中取出所需的值进行赋值,而不再开放输入池。

但是有char的情况,这种却可以运行的。scanf并没有将池子里多出的[enter]赋值给b,而是在遇到第二个scanf时重新开启了输入池,在输入结束后,将检测到的数字赋值给b。

​ 原因有两个:

​ 1.scanf自身有一定的输入池检测能力,它能清楚被赋值的数到底需要什么类型的值,如果类型不匹配,则会选择一种方式输出:输出乱码或者输出换行符之后的东西。

​ 2.接收值有char变量时,降低了scanf对于字符类型的灵敏度,这时候换行符将不再会被检测为无关数据,而是会以可能存在的输入值保存在输入池中,所以在最初的例子中,scanf实际在输入池中识别到的字符是3和换行符两个,满足了后续输入,所以第二个输入池不再开放,将换行符赋给了c。

搬用的【速过C primer plus】第四章_c primer plus 第四章溢出-CSDN博客

printf()函数

在使用printf()函数打印数据时指令要和待打印的类型相匹配,例如在打印整形数据时,不能使用字符的转换字符,C语言有标准的转换说明要求,如下表所示:

scanf()函数

scanf()和printf()类似,也是用格式字符串和参数列表。两个函数主要区别在参数列表,printf()函数使用变量、常量和表达式,而scanf()函数使用只指向变量的指针。这里有两条规则:
如果scanf()读取基本变量类型的值,在变量名前加&;
如果scanf()把字符串读入字符数组中,不需要使用&。

对于scanf()函数中的转换说明具体含义如下表所示:

4.4.1 printf()函数

printf()的格式是        printf(格式字符串,待打印项1,待打印项2,...);

例子:printf("My age is %d, my weight is %g kg", 18,66.5);

Tip:printf()函数也有返回值,它返回打印的字符数,如果输出错误,则返回负数。

输出长字符串的3种方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值