C语言再认识(基于C Primer Plus的重新整理)

从printf()看输出

  • 1、 请求printf()函数打印数据的指令要与待打印数据的类型相匹配,这是我们刚开始接触到printf()函数时就学习到的,即一个待打印项对应一个打印说明。
  • 2、printf()函数的返回值
    我们大部分时间都将printf()函数当做一个输出指令来调用,但它其实同样是C中的一个内置函数,同样有返回值。printf()返回打印字符的个数(通俗来讲就是打印输出中的字符串长度)
  • 嵌套printf()函数的应用
    理解了第二点之后,这个就很好理解了。我们来举一个例子:
printf("%d\n",printf("hello!\n"));
首先我们理解的是先将printf中的内容带入到%d中,
然后再输出printf中的内容。但是其中带有嵌套,
所以%d接收到的值其实是内层printf函数的返回值。

从scanf()角度来看待输入

我们在刚接触C语言时,最常用的就是printf()函数和scanf()函数,但是我们在刚使用的时候,知其然不知其所以然。所以我将在这里对scanf()进行更深层次的讲解。

  • 我们假设scanf()根据一个%d转换说明读取一个整数,scanf()每次读取一个字符,跳过所有的空白字符,知道遇到第一个非空白字符才开始读取。因为要读取整数,所以scanf()希望发现一个数字字符或者是一个(+,-)符号。如果找到一个字符或者符号,它便保存该字符,并读取下一个字符。如果下一个字符依旧是数字字符的话,则保存该数字并继续读取下一个字符。一次遍历读取和保存数字类字符,直到遇到非数字类字符。当遇到非数字类字符时,它便认为读到了数字的末尾,即这次读取结束。然后,scanf()把非数字类型的字符放回输入。意味着程序在下一次读取输入时,首先读到的是上一次读取丢弃的非数字类字符。
  • ===>这里非常重要,我们在编程的时候经常会遇到在遇到多次输入的时候回跳过某些scanf()函数,这正是因为“\n或者\t”同样算是一个字符,若是与“\n或者\t”类型不同的输入类型,则scanf()会将它存入缓冲区,下一个scanf()接收到的值则是缓冲区中已经存在的“\n或者\t”而不是程序暂停等待你的输入。
  • 2、为什么scanf()中读取基本变量类型的值时要加&?
    变量名不代表地址,除非数组。
    输入是要对 变量所分配的内存区域赋值,便要找到相应的内存位置,便要地址(类似地址传递)。
    输出只要变量的值,进行数据(类似值传递)就行。

如何终止while循环

1、这种方法最为简单:while循环一直执行,知道判断条件为假,自然就会终止
2、使用break语句跳出或者continue语句加速while的结束
3、这里解释一个概念:整数溢出:
首先我们先看一个例子

#include <stdio.h>
int main() {
	int i = 2147483647;
	unsigned int j = 4294967295;
	printf("%d %d %d \n", i, i + 1, i + 2);
	printf("%u %u %u\n", j, j + 1, j + 2);
	return 0;
}

在这里插入图片描述
在这里我们可以把无符号整数看成汽车的里程表。当达到它能表示的最大值时,会重新从起点开始计数。int类型的整数也是如此:它会从-2147483648开始表示。(当整数发生溢出时,程序并不会报错来阻止运行,但是依然会有警告,所以不要忽视warning)

预处理宏和函数的区别(在合适的地方使用合适的东西)

首先我们比较一下二者:
函数:

  • 宏比函数在程序的规模和速度方面更胜一筹
  • 函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用;但是宏可以用来比较整型、长整型、浮点型等的类型。而宏是类型无关的。
    函数:
  • 每次使用宏的时候,一份宏定义的代码将插入到代码中,会造成代码量大幅度增加。除非宏比较短。
  • 宏无法进行调试。
  • 宏与类型无关,是优点也是缺点,不够严谨
  • 宏稍不注意就会带来运算符优先级的问题,容易导致程序出错。所以在宏比较长的时候常整体加括号避免后面出现的运算符优先级的问题。
    命名约定:
    宏名全部使用大写,函数名不要全部大写(可以使用首字母大写或者公司名进行大写)

getchar()和putchar()

定义:
首先,这是两个字符输入函数,并且这两个函数不需要转换说明,因为它们值处理字符。getchar()不带任何参数,从输入队列中(其实就是缓冲区内)返回下一个字符。也就是getchar()函数从键盘上读入一个字符,并并且显示该字符(回显),但是只有第一个字符作为函数的返回值。按照显示内容来说就是该函数的输入一直到回车才结束。回车前输入的字符都会逐个显示到屏幕上,但只有第一个字符作为返回值。
用getchar()清除缓冲区的介绍

ctype.h头文件的使用

在C中,有一套专门处理字符的函数,ctype.h头文件包含了这些函数的原型。这些函数接受一个字符作为参数,如果该字符属于该类别,就返回一个非零值;否则,返回0.如果一个参数是一个字母,则返回一个非零值。
字符测试函数(是该类别则返回非零值):

  • isalnum(): 判断字母数字(字母或者数字)
  • isalpha():判断字母
  • isdight():判断数字
    字符映射函数:
  • tolower():如果参数是大写字符,该函数返回小写字符;否则,返回原始参数
  • toupper():如果参数是小写字符,该函数返回大写字符;否则,返回原始参数。
    eg:
tolower(ch);//不影响ch的值
同时将ch中的大写字符转换为小写字符。
isalsum(ch);
判断是否为字母和数字
//ctype.h头文件的使用
 
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main() {
	int len;
	char name[20];
	char new_name[20];
	scanf("%s", name); 
	len = strlen(name);
	printf("%d\n", len);
	for(int i = 0; i < len; i++) {
		if(isalnum(name[i])) {
			printf("第%d个位置是规范的!\n", i);
		}	
	}
	for (int i = 0; i < len; i++) {
		printf("%c",tolower(name[i]));
		new_name[i] = tolower(name[i]);
	}
	printf("得到的新字符数组为:%s", new_name);
	return 0;
} 

注意:在传入参数的时候,函数并不影响原参数,要想得到转换后的字符串,则需要将转换的结果赋给新的字符数组!

if else的匹配问题

这个没什么好说的,else会和离自己最近的if进行匹配
其实避免匹配错误的方式很简单,即做到自己的代码规范。因为每个if和else语句中只能容纳一个简单语句或者一个复合语句,所以尽量保证自己的if和else语句下面都是一个语句块!

iso646.h头文件的使用

C是在美国用标准美式键盘开发的语言。但是在世界各地有不同符号、不同格式的键盘类型,所以C99标准在iso646.h头文件中新增了可代替逻辑运算符的拼写。常用的可替代符为:

  • 使用and替代&&
  • 使用or替代||
  • 使用not替代!

具体代码如下:

#include <stdio.h>
#include <iso646.h>
int main() {
	int a;
	scanf("%d", &a);
	if(a > 5 and a > 3) {
		printf("表达成功!");
	}
} 

这个写法虽然是习惯问题,但是这种书写细节的更改还是很让你暖心的,让python玩家感觉到很友好嘻嘻

数学逻辑与代码逻辑的差距

  1. 在代码中尽量大于等于和小于等于在语句中书写为>=或者<=
  2. 在代码中“=”是赋值的意思,即将等式右面的值给到左式,而相等则是用两个等号来代替。

  3. 我们先抛出一个问题:
if (90 <= range <= 100) {
	printf("Good show!\n");
}

请问这样的语句编译器会抛出异常吗?
答案是不会的,这样写的问题是代码具有语义错误,而不是语法错误,所以编译器不会捕获这样的问题(在Dev上甚至都不会出现警告)。
由于<=运算符的求值顺序是从左往右,所以编译器尝试将这个表达式理解为:
(90 <= range )<= 100
因为子表达式“90 <= range ”的值要么是1(非零),要么是0,但是这两个值都是小于100的,所以这个表达式恒为真。因此,这一点我觉得这点更加致命,或者说很多人一开始学习编程语言时有这样的书写习惯,但是却不知道为什么这样书写,所以我把它加了小星星单拎出来,希望大家多多注意。

字符输入/字符输出和字符验证

1、更详细的介绍输入、输出

  • 常见的I/O函数:printf(),scanf(),getchar(), putchar()

  • 单字符I/O:getchar()和putchar()
    getchar()和putchar()每次只处理一个字符。虽然这种方法可能有些笨拙,但是这种方法很适合计算机。而且,这是大多数文本处理程序所用的核心方法。其实,自从ANSI C标准发布之后,C就把stdio.h头文件与使用getchar(),putchar()相关联。(其实这两个并不是真正的函数,他们被定义为供预处理使用的宏。)

    缓冲区

    首先抛出一个问题:为什么要有缓冲区?
    首先,我们将若干个字符作为一个块进行传输比逐个发送这些字符节约时间。其次,如果用户在输入过程中如果打错字符,可以直接通过键盘进行修改,而不是通过回退操作来进行修改。保证最后按下Enter键时,传输的是正确的输入。
    下来我们再来介绍一下缓冲区到底是什么?
    缓冲区又称为缓冲,它是内存空间的一部分。
     也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。
      C语言缓冲区分为三种类型:1、全缓冲2、行缓冲3、不带缓冲

  1. 全缓冲
    当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。

  2. 行缓冲
    当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是标准输入(stdin)和标准输出(stdout)。

  3. 不带缓冲
    也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
    通常来说,输入的一些数据都会存入缓冲区中,知道程序需要某一类型的数据时,才会从缓冲区中去取。
    我们现阶段比较熟悉的输入函数有:getchar()和scanf()
      getchar()读取每个字符,也就是说空格、制表符和回车符都会被读取。
      而scanf()函数在读取数字时则会跳过空格、制表符和回车符(p228)

    我们先来讲讲scanf()函数:
     基本的语法格式相信大多数人都知道。当要读取%c时,则是每个字符都会被选中。然而读取数字时,问题就来了。
    要知道,当我们要结束数字的输入时,在数字输入完成后会再次输入回车符表示已经结束输入了,但是我们的回车符依然会进入缓冲区。所以在scanf()语句被执行时,实际上缓冲区内存入的有你从键盘上输入的数字和一个“\n”。若是下一个scanf()中接受的是数字的话,则会跳过这个回车符。但如果下一个输入语句是getchar()或者scanf()中需要的是%c,那么,缓冲区会将缓冲区内存在的下一个符合要求的字符给到输入语句。这样也就意味着按输入逻辑来说跳过了本次输入。
       我们来举几个例子来解释上面这一串又长又臭的话:

    eg1:

	eg1:
	   char a, b;
       scanf("%c", &a); /*此时输入一个字符,并按下回车,那么变量a将会正确接收输入的字符*/
       //此时a被填入语句中,但是缓冲区内还剩下了“\n”;
       scanf("%c", &b); /*所以这时变量b将会接收到回车符,其值为10,或者说ASCII码为10*/

eg2:

       eg2:
        scanf("%d",&x); /*此时输入一串数字并回车,变量x将正常接收到输入的整数*/
        scanf("%d",&y); /*变量y不会接收到回车符,需要用户继续输入数字,并回车,正常接收到整数*/

此时我们再来举一个极端的例子:

//	scanf("%c", &a);
//	scanf("%c", &b);
//	scanf("%c", &c);
	scanf("%c%c%c", &a, &b, &c);//与前三条语句效果一样
	printf("%c   %c	  %c", a, b, c);
	printf("%d   %d   %d", a, b, c);

此时我们试着依次输入:a,空格符和回车
(第一行是输入,第二行和第三行是输入结果)
在这里插入图片描述
这样就可以很生动的体现出缓冲区内字符调用的逻辑。

很多时候,我们在写代码的时候可能不会考虑到太多,在运行的时候就会出现跳过输入语句的现象。所以在发现错误时,我们要将缓冲区内的符号都清空,然后让程序接收此时从键盘或者文件中读入的数据。

  • 使用fflush(stdin);函数
  • 该函数包含在stdio.h头文件中。

输入验证

 在实际应用中,用户不一定会按照程序的指令形式。用户的输入和程序期望的输入不匹配时常发生。这会导致程序执行失败或者崩溃。所以我们需要在某些选择权在用户手中的语句中添加上检测并处理输入错误的方法。
eg1:
 假设你编写了一个处理非负数的循环,当用户输入一个负数时,该循环就会被跳过不执行。我们可以使用判断条件来排除这种情况

int n;
scanf("%d", &n);
while (n >= 0) {//检测是否在范围内
	scanf("%d", &n);  //重新获取下一个值
}

那如果用户手一飘,输入的根本就不是数字,而是字母甚至于字符,那该怎么处理呢?

  • 排除这种情况的方法是:检查scanf()的返回值。回忆一下,scanf()的返回值是成功读取项的个数,因此,请看下面的例子:
当程序出现小于零的数字或者不属于数字的字符时就会跳出循环结束。
int n;
int flag;
flag = scanf("%d", &n);
while (flag == 1 && n >= 0) {
	flag = scanf("%d", &n);
}

当然,想要更方便的话,调用之前提过的ctype.h头文件中的一些函数就好。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值