一、PTA实验作业
题目1:7-8 判断合法标识符
1. 本题PTA提交列表
2. 设计思路
- 1.定义整数型变量repeat和i来存放判断字符串是否为合法标识符的次数,定义字符型ch来存放读取的字符
- 2.输入repeat的值, getchar() //吃掉输入repeat后的回车
- 3.for (i = 0; i < repeat; i++) { //循环repeat次
ch = getchar(); //读取一个字符存放在ch
if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '')) { //对开头字符不合法的判断
输出 no
while (getchar() != ' '); //吃掉后续无用的字符
continue; //跳过这次循环,进入下次循环
}
while ((ch = getchar()) != ' ') { //循环读取字符直到读取回车
if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '' || (ch >= '0' && ch <= '9'))) {
输出no
while (getchar() != ' '); //已经判断出不合法,吃掉后续无用的字符
break; //结束这次循环
}
}
if (ch == ' ') { //所有字符都判断结束没有不合法
输出yes
}
}
3.代码截图
4.本题调试过程碰到问题及PTA提交列表情况说明。
-
1.输出一个no以后不能继续输入了
解决方法:通过调试发现没有让我们再输入数的原因是它会一直读入字符直到读到最后的换行符了后就停止输入了
-
2.发现以上问题后进行修改后出现了很多其他的错误,eg
于是重新整理思路重新编辑一段代码,废掉flag和重新定义新函数等,简单和直接地去考虑问题。然后解决了只能输出一个结果不能重复输入的问题后出现了新的问题:不管输入什么都输出no yes 或者是yes no等 -
解决方法:通过检测变量和调试发现:主要的问题是没有清空后续字符:比如1num,先判断1是否为合法标识符,之后要把num以及末尾的回车清除掉,但是我的代码并没有这么做,于是接着判断num是否为合法标识符,所以输出了no yes。在一位大神的建议下引入了 while((getchar())!=' ') 利用循环+getchar()来不断地吃字符,但是没有保存,相当于清除了后续无用的字符,连回车也清除掉
同时被普及了知识: -
(1):getchar()的意思是get a character,就是获取一个字符,读15的时候,getchar()只读取1,这个1是作为字符1读取的,再次getchar()就是读取字符5,而scanf%d的时候是把15作为数字一起读取的
-
(2):输入缓冲区的概念:比如输入11num,还没有按回车,这时11num是放在输入缓冲区的,并没有到程序里去,程序通过getchar(),scanf,gets等,从这个输入缓冲区读取
题目2: 切分表达式——写个tokenizer吧
1. 本题PTA提交列表
2. 设计思路
1.定义字符型变量pre和now,pre存放读取的上一个字符 now存放现在读取的字符
2.读取一个字符存放在pre
3.输出第一个字符
4.while((now=getchar())!='
'){ //一直读取字符直到读到回车
(1)如果现字符是数字,直接输出不加任何换行
(2)如果现字符是+-:进行以下判断
如果前字符是数字,输出换行现字符换行
如果前字符是')'输出现字符换行
如果前字符是'('输出现字符
如果不是以上三种情况,其余都输出现字符换行
(3)如果现字符是小数点,直接输出现字符
(4)如果现字符是'*'或'/':
如果前字符是数字,输出换行现字符换行
如果前字符不是数字,则输出现字符换行
(5)如果现字符是'(',则输出现字符换行
(6)如果现字符是')'
如果前字符是数字,则输出换行现字符换行
如果前字符不是数字,则输出现字符换行
5.pre=now; //把现字符存放在前字符,进行下一轮的判断
3.代码截图
4.本题调试过程碰到问题及PTA提交列表情况说明。
-
1.思路受阻:
一开始对这题思路不清晰,困惑点:
1,既然字符串那只能利用循环+getchar来读取,那读到3怎么根据下个字符来判断输出3后是否应该回车,因为我们还没有读到下一个字符啊
2.怎么判断+-是正号还是负号呢
3.到底哪几种情况下应该输回车呢 -
解决方法:重新整理思路,分析了加回车的情况,得出了解题关键点:所谓的切片实际上就是在原来的字符串基础上加上一些回车 ,整个字符串是一个个字符读取,切片的时候要考虑现在读取的字符,也要考虑之前的一个字符。因此要把所有可能情况:数字,正负号,小数点,+-*/()每个都分析一下,如果当前字符是这个,结合前一字符,那要不要输出回车,要输出几个回车?列出具体的表格如下:
-
2.根据列出的表格敲出相对应的代码后在DevC++运行后发现:多了一个空格和负号被判断成减号问题(左下)
解决方法:空格问题:发现在代码中")"前后都输出
而作为加号的+也前后都输出
于是多了一个
,于是就再加入这种情况的判断来少输出一个
后得以解决; 判断负号错误:同上也是引入这个情况的判断来决定+-后是否应该有
解决后的运行结果:(右上)
- 3.发现运行结果仍然不对(哭唧唧):又重复输出了-和+
这回只能老老实实一步步调试:
发现它竟然迷之进入了else的分支,这不是我们想要的结果,可是为什么会进入else的分支的,明明已经进入了上面if的分支为啥还会进入else,哦哦哦突然想起 else只跟离它最近的if相匹配,于是老老实实所有语句都用 if elseif else的格式来写,避免出错..... 修改好后运行呼终于得到正确结果,忐忑不安去pta提交:哈哈哈哈竟然通过了所有的测试点~~
题目3:7-10 简单计算器
1. 本题PTA提交列表
2. 设计思路
1.定义整数型变量n1 n2分别用来存放表达式的前一个数和后一个数,定义整数型变量result来存放运算结果
2.定义flag,condition来存放情况,定义字符型变量op来存放运算符
3.输入n1
4.while((op=getchar())!='='){ //读取字符存放在op直到读取到‘=’
输入n2
switch(op){ //判断op,对应不同的表达式
case '+':result=n1+n2;break;
case '-':result=n1-n2;break;
case '':result=n1n2;break;
case '/':
if(n2==0){ //除数为0的情况,令condition=0来记录
condition=0;
}
else{
condition=1; //除数不为0的情况,令condition=1来记录
result=n1/n2;
break;
}
default :condition=0;break; //有非法运算符的情况,令condition=0来记录
}
5.n1=result //表达式的结果存放在n1以便进入下一次循环
6.flag=1; //flag=1来记录有超过一个运算符的情况
}
7.if(condition){ //如果除数不为0或者没有非法运算符
if(flag) //如果有超过一个运算符
输出结果
else{
只有一个运算符的情况,直接输出n1
}
}
else{ //如果除数为0或者有非法运算符
输出输出错误信息“ERROR”
}
3.代码截图
4.本题调试过程碰到问题及PTA提交列表情况说明。
- 1.相比7-9这题明显熟悉和简单许多,因为没有运算符的优先级就更降低了这题的难度系数,但是并不意味着可以一次性通过,仍然还是会出现一些小问题
根据pta给我们的提示,先是最小表达式错误:
- 解决方法:引入一个情况变量flag来记录这种情况,解决最小公式的问题
- 2.讨论分母为0的情况:
- 解决方法: 在case '':中加入分母为0的判断情况后输出error
发现多了后面的输出数字,于是再次引入变量condition
来记录这种分母为0的情况后输出正确结果
小体会:必要时候引入情况变量flag和condition真的很好用
二、截图本周题目集的PTA最后排名。
三、本周学习总结
1.你学会了什么?
1.1 一维数组如何定义、初始化?
首先声明数组的类型,然后声明数组元素的个数(也就是需要多少存储空间)
格式: 元素类型 数组名[元素个数]; eg: int[3];
数组元素的初始化:
- int a[3] = {10, 9, 6};
- int a[3] = {10,9};
- int a[] = {11, 7, 6};
- int a[4] = {[1]=11,[0] = 7};
注:只能在定义数组的时候进行初始化
如果想在定义数组的同时进行初始化,数组元素个数必须是常量或者不写
int ages[count] = {12,19,14,26,18};
注意:[ ]里面的个数必须是一个固定值,可以是常量(比如6、8)、常量表达式(比如3+4、5*7)。绝对不能使用变量或者变量表达式来表示元素个数,大多数情况下不要省略元素个数
注意:数组下标是从0开始,下标不能越界
补充:区别:
int a[x] 若未赋初值,则系统随机赋值
static int a[x] 若未赋初值,则系统赋0
1.2 一维数组在内存中结构?可画图说明。数组名表示什么?
数组名表示数组在内存的起始地址
1.3 为什么用数组?
关于数组我们可以把它看作是一个类型的所有数据的一个集合,并用一个数组下标来区分或指定每一个数,例如一个足球队通常会有几十个人,但是我们来认识他们的时候首先会把他们看作是某某对的成员,然后再利用他们的号码来区分每一个队员,这时候,球队就是一个数组,而号码就是数组的下标,当我们指明是几号队员的时候就找到了这个队员。 同样在编程中,如果我们有一组相同数据类型的数据,例如有10个数字,这时候如果我们要用变量来存放它们的话,就要分别使用10个变量,而且要记住这10个变量的名字,这会十分的麻烦,这时候我们就可以用一个数组变量来存放他们,eg:a[10] , 使用数组会让程序变的简单,而且避免了定义多个变量的麻烦。
1.4 介绍选择法、冒泡法、直接插入排序如何排序?伪代码展示.
(1)选择法排序
- 设计排序函数,形参为数组a,数组长度为n
2.定义循环变量i,j ,定义最小值为a[index]
3.for i=0 to n--1
利用循环输入数组a,最小值a[index]初始化为第一个元素
for k=0 to n-2
找出每趟的最小值,存放在a[index]
4.输出数组a
(2)直接插入排序。①算法思想。直接插入排序把序列分成有序序列 (前)和无序序列(后)两个部分,其实质是把无序序列中的第一个元素插入到有序序列的对应位置。如果序列中的元素为n,则需要进行n-1次插入,每次插入需要做若干次比较。②C程序实现过程。
int a[N],i,j,t; //i,j分别用来做插入和比较的循环计数变量
//此外,i还用来表示无序序列中第一个元素的下标
//从键盘中输入数给数组a[N]中的每个元素
for(i=0;i<N;i++)
scanf("%d",&a[i]);
for(i=1;i<N;i++)
if(a[i]<a[i-1]) //如果无序序列中的第一个元素比有序序列中
{ //的最后一个元素小,则需插入
t=a[i];
a[i]=a[i-1];//有序序列中的最后一个元素后移
for(j=i-2;j>=0;j--)//从有序序列的倒数第二个元素开始比较
if(a[j]>t)a[j+1]=a[j];
else break;
a[j+1]=t;
(3)冒泡排序。①算法思想。冒泡排序把序列分成无序(前)和有序 (后)两个序列,其实质是把无序序列中相邻两个元素依次比较,大者下沉 (后移),移动到最后的元素即为有序序列的第一个元素,多次冒泡以后直至序列有序。如果序列中的元素为n,则需要进行n-1次冒泡,每次冒泡需要做若干次比较。②C程序实现过程。
int a[N],i,j,t;//i,j分别用来做冒泡和比较的循环计数变量,
//此外,i还用来表示无序序列中倒数第二个数
//从键盘中输入数给数组a[N]中的每个元素
for(i=0;i<N;i++)
scanf("%d",&a[i]);
for(i=N-2;i>=0;i--)
for(j=0;j<=i;j++)
if(a[j]>a[j+1])//无序序列中的相邻两个元素两两相互比较
{
t=a[j+1];
a[j+1]=a[j];
a[j]=t;
}
1.5 介绍什么是二分查找法?它和顺序查找法区别?
二分查找顾名思义,我们找到数组的最大值max,最小值min求出中间值mid,然后用mid作为数组下标得到对应的元素,用这个元素和目标值key进行比较:
如果numbers[mid] > key,那么说明key在min和mid之间,那么就设置max为mid - 1,min不变,然后重新计算mid,重复上述步骤,最后找出key。
如果numbers[mid] < key,那么说明key在mid和max之间,那么就设置min为mid + 1,max不变,然后重新计算mid,重复上述步骤,最后找出key。
注意这里的结束条件,有可能数组中有这个key,也有可能没有,那么当min > max时,说明数组中并没有这个key,要小心这种情况。
区别:
条件不一样:二分查找要求数组必须是有序的。(有序表)
顺序查找有序无序都可以,运用范围更广。
1.6 二维数组如何定义、初始化?
二维数组是一个特殊的一维数组:它的元素是一维数组。
例如int a[2][3]可以看作由一维数组a[0]和一维数组a[1]组成,这两个一维数组都包含了3个int类型的元素
定义形式: 类型 数组名[ 行数] [列数] eg:int a[2][3]; //2行3列的二维数组
二维数组的初始化:
int a[3][4] = {1,2,3,4,5,6};
int a[3][4] = {{},{},{}};
补充:
二维数组的存放顺序是按行存放的,先存放第一行的元素,再存放第2行的元素。
例如int a[2][3]的存放顺序是:a[0][0] → a[0][1] → a[0][2] → a[1][0] → a[1][1] → a[1][2]
(注意:a[0]、a[1]也是数组,是一维数组,而且a[0]、a[1]就是数组名,因此a[0]、a[1]就代表着这个一维数组的地址)
1> 数组a的地址是ffc1,数组a[0]的地址也是ffc1,即a = a[0];
2> 元素a[0][0]的地址是ffc1,所以数组a[0]的地址和元素a[0][0]的地址相同,即a[0] = &a[0][0];
3> 最终可以得出结论:a = a[0] = &a[0][0],以此类推,可以得出a[1] = &a[1][0]
1.7 矩阵转置怎么实现?方阵中:下三角、上三角、对称矩阵的行标i列标j的关系?请说明。
矩阵转置实际上就把该二维数组的行标变为转置后元素的列标,列标变成转置后元素的行标,然后形成一个新的矩阵就是我们所求的转置矩阵
eg:a[i][j]=b[j][i] 得到的b[][]数组就是a[][]数组的转置矩阵
下三角:j<=i 上三角:j>=i ,其中i代表行数,j代表列数
对称矩阵:在一个n 阶方阵中,a[i][j]=a[j][i] ,其中1≤i , j≤n,
1.8 二维数组一般应用在哪里?
1.主要运用于矩阵的计算,eg矩阵的乘法
2.生活中实际问题的应用,eg比如一个班所有学生的名字,则需要二维字符数组,char names[15][20]可以存放15个学生的姓名(假设姓名不超过20字符)
3.二维数组与函数
2.本周的内容,你还不会什么?
1.从本周的期中考检测中发现自己存在很多问题:
(1)阅读代码的能力不强:对一题代码需要重复捋好几遍思路才会很清楚代码想要实现的功能
解决方法:老师给的建议是多看别人的代码多思考同时可以代入具体的数值进去更容易知道某语句想要实现的功能
(2)对很多犯过的错题没有好好去重新复习和总结
(3)考试时候对时间的把握不够好,而且发现自己手写代码的能力非常差
(4)对很多不常用的词概念模糊不清,比如本题考试中出现 auto 还有static 的作用范围阿之类的 p113
2.从本次的pta完成的过程中:
虽然多次想尝试利用数组加函数,可惜都因为错误太多放弃了这种做法:也就是数组加上函数的传参什么时候应该传数什么时候传地址,还有后续将要学到的二维数组加函数的传参啥的目前还是一知半解
3.对二维数组的认识还很浅,需要继续深入学习