伊吖学C笔记(3、字符、分支结构)
一、字符(串)
如果说“加减乘除余”是C语言中的数学,那字符(或字符串)就是C语言中的语文。
1.定义
用“char ch;”来定义单个字符变量,用“char str[20];”定义字符串,用“char *str;”也可定义字符串。
字符的双重属性,即:既表字符又表数字。为了弄清这个问题,我们先看看ASCII表:
表中,一个8位二进制对应特殊的功能或字符,比如00001010(10)代表换行(\n),00001101(13)代表回车(\r),00110000(48)代表字符0,00110001(49)代表字符1,010000001(65)代表A,01100001(97)代表a,等等。
一个字符默认的数值就是它ASCII的十进制数,用%c输出是字符,用%d输出是数字。
当然,ASCII码还有扩展部分:
输出扩展字符需要加上:
#include <windows.h>和SetConsoleOutputCP(437);
比如实线表格线,就要用到这些字符:
但是要与下面这个中文表格的区别:
这是中文双字节制表符,在word或excel里通过插入符号实现。
单个字符的处理要用单引号,一个个处理太麻烦,通常我们用字符串来处理文字。字符串定义常用到字符数组和指针。
我们发现定义字符串数组,输出时出了问题,数组st、s都出现不必要的尾巴。那是因为:集中定义时,用双引号时,字符串会自动加上结束标志“\0”,本例str[]、*p就是。而单个定义时,没有“\0”,输出时循环遍历输出。如要把它当作串输出,需多定义一位长度。
多行文本定义:
2.字符输入
2.1、getchar()
getchar()支持一次输入字符串,多次调用,每次一个字符。
与getchar()相对应的是putchar(ch),输出字符。
2.2、getch()
对比getchar(),getch()无缓冲无回显立即响应;有时在程序执行过程中需要暂停,可加一句getch();意思是等待键盘输入一个字符继续。getch()也可用于密码保护:
也有getche(),类似getch(),但有回显。
2.3、gets()
字符串输入函数,空格一起计算字符。
对应的put(a)为输出字符串函数。
另外还有fgets()、fgetc(),也可以读串,后说。
2.4、scanf()
格式:scanf("格式控制",变量地址列表)
scanf()有点眼盲,空格后面就不认了。看样子字符串,还是gets()好用。
scanf()运行完成一次后,键盘缓冲区还留了一个结束标志,对后续输入造成了影响。
当输入单个字符时,前面有没有输入,如果有则要空一格,抵消结束符。另外,scanf()参数后半截是变量地址列表,不是变量列表,如果是变量,一定要作地址引用,加&。数组、指针不用加&,默认就是地址的意思。
3.字符操作
3.1、字符串连接strcat(a,b)
3.2、字符串复制strcpy(a,b)
无论长短,皆是复制品。
也可以通过程序实现:
3.3、字符串比较strcmp(a,b)
相等才是0,要注意,第一个长:1,第一个短:-1。
3.4、字符串截取strncpy(a,b+n,m)
3.5、其他
strlen(a)字符串的长度
strlwr(a)大写字母变小写
strupr(a)小写变大写
二、分支
我们设计的程序在执行过程中,不可能是一帆风顺,遇到一些十字路口,要进行选择,走哪条路?这就是程序的分支结构。
1.单分支
if(条件)操作语句;
如果只关心一个条件(比如,班里叫张三的同学),或一类条件(比如,考试成绩在90分以上),并且只对满足条件的对象进行操作或运算,其他不管,就用if(条件)操作语句;,最简单的if,满足条件即运行,根据条件选择执行。
题目:输入一个字符串,统计有多少数字?
2.双分支
if(条件)操作语句1;
else操作语句2;
如果同时关心满足条件的和不满足条件的两类情况(比如:班上及格的同学和不及格的同学),要进行两种不同的操作或运算,就用双分支。
题目:输入字符串,分别输出是数字还是不是数字,并分别统计个数?
3.多分支
if(条件1)操作语句1;
else if(条件2)操作语句2;
...
else if(条件n)操作语句n;
else操作语句n+1;
如果关心的条件有多个(比如考核结果分为:优秀、称职、基本称职、不称职),并且进行不同的操作,这时就可以用到多分支。看个一个打折的例子:
题目:输入字符,如果输入是大写字母直接输出;如果是小写,转换为大写输出;如果是其他,输出不是字母。
字母大小写转化的问题,先看ASCII字符表,如下:
从表中可以看出:大写字母A-Z的ascii编号从65到90,小写字母的编号从97到122,这是固定的。
大写变小写,只需要把编号增加32就行。有些编程用的是:+'a'-'A',也就是+97-65,也是加32,一个意思。小写变大写,减32就行,总之,小写的编号比大写的大。
题目:由键盘输入一个3位的整数,判断该数是否为升序数。若输入的不是3位数,输出“Enter error”。升序数是指高位数依次小于其低位数的数。如,359为升序数。
设计思路:首先是判断是不是三位数?这是一层意思,然后再在三位数里判断是升序还是不是升序,这是第二层意思。用if...else if...else...这个语句可以解决。
if(a<100||a>999)
//判断非三位数:如果小于100或大于999,那就一定不是三位数了。
else if(a/100<a/10%10&&a/10%10<a%10)
//判断是否升序:a是三位整数,a/100的结果取整,即为百位数字。比如358/100=3,865/100=8,整数除整数,结果只取整数。个位数,a%10,即除以10的余数,比如358%10=8、865%10=5。中间的十位数还是有点麻烦,先除以10取出前两位,再除以10取余数,即a/10%10。比如:358/10%10=5、865/10%10=6。
升序要同时满足:百位数<十位数<个位数,所以中间要用逻辑与&&。如果同时满足条件,即百位数<十位数成立,并且十位数<个位数也成立。
题目:输入三个整数x,y,z,请把这三个数由小到大输出。
思考:三个数的排序,当然先比较2个,分出大小。再用第三个数去分别比较,比小的小,放在最前面;比大的大,放在最后面;否则放中间。这是最常规的思路(方法1)。
键盘输入(三板斧):
int x,y,z;
printf("请输入三个整数,中间用空格格开:");
scanf("%d %d %d",&x,&y,&z);
排序如下:
if(x>y) //先排出x>y的可能排列组合:3种
if(y>z)printf("%d %d %d",z,y,x);//小于小的
else if (z>x)printf("%d %d %d", y,x,z);
else printf("%d %d %d", y,z,x);
else //再排出x<y的可能,也是3种
if(x>z)printf("%d %d %d",z,x,y);
else if (z>y)printf("%d %d %d", x,y,z);
else printf("%d %d %d",x,z,y);
这种思路好理解,不改变xyz原来的赋值,就是编程有些烦琐,用到了5个if、5个else、6个printf(因为有6种可能的排列组合)。
有没有更好更简洁的方法呢?有。交换法(方法2),xyz的顺序始终不变,小的值往前换,大的往后换,换3次。
if(x>y){t=x;x=y;y=t;} //保持x<y,如果x>y则互换
if(y>z){t=y;y=z;z=t;} //用z比较y,看谁最大,如果y>z则互换,保持y<z,此时z最大
if(x>y){t=x;x=y;y=t;} //再比较xy,再次确保x<y
我们发现第一个if与第三个if一模一样,没搞错吧?我们这样理解排序过程:
| x | y | z | 举例 | ||
开始 | ? | ? | ? | 852 | 528 | 582 |
if(x>y){t=x;x=y;y=t;} | 小 | 大 | ? | 582 | 258 | 582 |
if(y>z){t=y;y=z;z=t;} | 小1 | 小2 | 大 | 528 | 258 | 528 |
if(x>y){t=x;x=y;y=t;} | 小 | 中 | 大 | 258 | 258 | 258 |
这个比较后交换的顺序是:先是1、2位,再是2、3位(确定最大值),最后是1、2位。
有些书上的答案稍有区别:先是1、2位,再是1、3位(确定最小值),最后是2、3位,如下:
| x | y | z | 举例 | ||
开始 | ? | ? | ? | 852 | 528 | 582 |
if(x>y){t=x;x=y;y=t;} | 小 | 大 | ? | 582 | 258 | 582 |
if(x>z){t=x;x=z;z=t;} | 小 | 大1 | 大2 | 285 | 258 | 285 |
if(y>z){t=y;y=z;z=t;} | 小 | 中 | 大 | 258 | 258 | 258 |
程序运行结果如下:
上面就是if的基本框架及应用举例。if框架都可以嵌套,建议嵌套层级3-5层。
4.开关语句switch
最简单的理解:对号入座。
switch(开关){
case 1:...;
case 2:...;
...;
case n:...;
default:...;}
根据开关的值,决定到哪运行,就相当于,你是几班,就进几班的教室,case后面的值就是班号,如果你还没分班,前面都不是,就到default(缺省)报到。
case后面最后一般都会加上break,跳出switch,否则会顺序执行下面的case。
default:...也可以没有。
运行流程图如下:
下面看个例子:
首先 x=1,执行 case 1:a++;这句,结果a=1了;由于后面没有break;继续执行case 2:a++;b++;break;这句,结果a=2,b=1了。执行完这句后,后面有break;跳出,结束switch。
再看个例子:
题目:输入某年某月某日,判断这一天是这一年的第几天?
思考:年有平年365、闰年366之分;月有31、30、29、28天的区别。第几天怎么确定?先从第一个月捋一捋,一月无论什么时候都是31天,那一月日期数就是这一年的第几天。比如1月5日,就是全年第5天;二月呢:就是二月的日期数加上31,就是这一年的第几天。比如2月3日就是全年第34天;三月呢:平年是三月的日期数+31+28,闰年是日期数+31+29...依此类推,后面的月份都是两种可能,但有同一规律:闰年比平年多一天。是不是我们就可以:先只管平年,完成平年的计算;最后加上一个if判断,如果闰年,且月份数大于2,则平年的结果+1。
如何完成平年的第几天计算呢?我们先找找规律看:1-12月的月长分别为:31、28、31、30、31、30、31、31、30、31、30、31。虽然主要为30、31两个数,但规律并不明显,7月、8月连续两个31天,奇偶关系也变了。那我们就用比较老实的办法了,用switch()语句,设计12种可能性来完成。
闰年的判断:如果是世纪年,除以400等于0就是闰年即y%400==0;如果是非世纪年(y%100!=0),除以4等于0才是闰年,即y%4==0。综合起来就是
y%400==0||y%4==0&&y%100!=0
因为“&&”(逻辑与)的优先级高于“||”(逻辑或),比如:A&&B||C&&D,等价于(A&&B)||(C&&D)。
如果是分步判断,流程如下: