0.展示PTA总分(0----2)

1.本章学习总结(2分)

1.1 学习内容总结

常用的知识点或是需要注意的地方

  • 关于输入(字符串)
    • 用scanf输入的话,它无法读取空格,它遇到tab、空格、换行符就结束输入,当然,换行符什么的是不会被读入字符串的,它会默认给字符串末尾加上结束符'\0'。格式为scanf("%s",str)
    • 用gets输入,它可以读入空格,遇到换行符结束输入,也会给字符串加上结束符'\0'。格式为:gets(str)
    • 用fgets输入的话,它遇到换行符、文件尾、或者读完n-1个字符会结束,它不会自动补上换行符,而且,它会读入换行符'\n'。格式为:fgets(str,n,stdin)
  • 常用的函数:
    • 字符串复制:strcpy(str1,str2)把str2赋给str1。(str1的末尾会有结束符'\0')
    • 字符串连接:strcat(sre1,str2)把str2接在str1并存入str1。
    • 字符串比较:strcmp(str1,str2)比较str1和str2,如果相等返回0,如果str1大,返回一个正数,如果str1小,返回一个负数。
    • 字符串长度:strlen(str)计算str的有效长度,不包括'\0',但是包括'\n'
  • 关于使用:
    • 指针必须先赋值后使用。
    • *p是直接对内存单元操作,改变变量的数据。
    • &表示取地址,*表示取内容
    • 在改变指针的时候,要注意不能让它越界。

指针做循环变量做法

字符指针如何表示字符串

  • 对于字符串的表示,我们原先是用字符型数组表示的,比如char a[]="heihei";现在,我们可以用指针写成这样:const char *a="heihei"
  • 对于上面的例子,这里有几个需要注意的地方:
    • 指针只能,不可以直接赋值,比如写成a="heihei"就是错误的
    • 再有,为什么加上const就可以了呢?因为const是constant的缩写,为“恒定的、不变的”的意思,定义之后,就不允许对它进行修改或是赋值,即便是赋给它相同的值也是不允许的,所以,它只能在定义的时候就赋值完。

动态内存分配

  • 为什么需要动态内存的分配呢?
    • 我们之前写代码的时候,对于数组问题,我们都是直接给它设定好了空间,将它直接放入栈区;而动态内存分配,顾名思义,就是根据情况需要,动态地分配内存用于存放相应的对象。动态内存分配,便于储存内存较大的对象,因为栈区容不下过大的对象,而且指针的传递比整个对象的传递更快、更方便。
    • 动态内存分配函数malloc()
      • 原型:```void *malloc(unsigned size)
      • 注意:在调用时,应该用sizeof计算存储块大小,不要直接写数值。虽然它是动态分配的,但它的大小在分配后也是确定的,也需要注意不能越界使用,特别是不能越界赋值。
    • 计数动态分配函数calloc()
      • 原型:void *calloc(unsigned n,unsigned size)
      • 注:malloc()对所分配的存储块不做任何处理,而calloc()对整个区域进行了初始化。
    • 动态存储释放free()
      • 原型是:void free(void *ptr)
      • 注:为了保证动态存储区的有效利用,在知道某个动态分配的存储块不再用的时候,就应该及时将它释放。但是释放后不允许再通过指针去访问已经释放的块。

指针数组及其应用

  • 比如说,我定义了一个整型数组a[100],然后定义一个指针p。数组名a代表的就是这个数组的首地址,我可以让p=a,就是让p指向数组a的首地址,也可以写成p=&a[0]
  • 应用:
    • 比如做循环的时候,可以用指针做循环变量,如上面提到的那个例子:6-1 用指针将数组中的元素逆序存放

二级指针、行指针

  • 指向指针的指针,称为二级指针。
  • 一般定义为:类型名 **变量名
  • 对二维数组a[6][6]
    • a[i]表示一维数组,每行地址第一个元素地址,而数组名a表示指向第一行地址的地址,所以是二级地址。
    • a+i指向a[i],是第i行的地址,是二级指针;
    • *a是首行第一个元素的地址,又称为列指针,是一级指针
    • *(a+i)即a[i],表示第i行首元素地址,但是,它是一级指针;
    • *(a+i)+j => a[i]+j => &a[i][j],即第i行第j个元素的地址,是一级地址;
    • 注:
      • a+i ≠ a[i],前者是二级地址,后者是一级地址;
      • *(a+i)=a[i],是一级地址;
      • 二级地址做运算后还是地址,而一级地址运算后就是内容。
  • 行指针的形式为:```int (*p)[n],其中,p为指向含有n个元素的一维数组的指针变量,是二级指针。
    • 注:
      • p+i=a+i ≠ a[i],这是二级指针;
      • (p+i)=(a+i) = a[i],这个是一级指针。

函数返回值为指针

  • 函数返回值为指针时,它返回的应该是一个地址。
  • 注:不能返回在函数内部定义的局部数据对象的地址,这是因为所有的局部数据对象在函数返回时就会消亡,它的值不再有效。所以,返回指针的函数一般都会返回全局数据对象或是主调函数中数据对象的地址。
  • 举个例子:6-7 输出月份英文名

1.2 本章学习体会

  • 学到指针这一块的时候,我很多时候其实有点晕,特别是在自定义函数需要返回值的时候,我会因为返回值的错误导致无法输出或是输出错误。a我得承认,我对知识点还不是特别熟悉,掌握的还不是很好。(当然,我会再看看那些知识点,把它补回来的。)也许是因为不熟练,在找错的时候它使我很困扰,很多时候我会有那种,我明明是对的,但是它咋就是不给我过的气恼和无奈🙃。能怎么办呢,自己敲的代码,哭着也得自己改对啊。改对的话,那一刻,心里还是很舒服的😉
  • 代码量:指针题集:486(果然还是太少了orz,但是它,好难😭)

2.PTA实验作业(7分)

从PTA题目集中选3题你最满意的题目,题目选难度越大,分值越高。每题2分,做如下内容:

2.1 6-6 查找子串

2.1.1 伪代码

定义字符型指针变量firstAddress来存放子串首个元素的地址,flag用于标记是否找到了子串,i用于循环计数,number用于记录在比较过程中子串元素在首地址后移动的次数。

初始化firstAddress=NULL;
flag = 0;
number = 1;

for  to *s != '\0'
   初始化j=0
   if 字符串元素与子串首个元素相同
     flag = 1;
     s++;
     j++;
     number = 1;
     while 子串未结束
       if 字符串已经结束
          flag = 0;
          跳出循环
       end if
       if 元素相等
          j++;
	  s++;
	  number++;
       end if
       else
          flag = 0;
	  跳出循环
       end else
     end while
     if flag
       firstAddress = s - number;
       跳出循环
   end if
end for
return 首地址

2.1.2 代码截图


2.1.3 总结本题的知识点

  • 指针做循环查找,需要注意的是,子串首个元素可能与字符串任意位置的元素相等,因此不能仅考虑子串结束了没有,还需要考虑字符串是否依然存在,没有考虑到这个可能会导致栈溢出。
  • 如果字符串与子串比较到一半时,发现与出现了于子串不同的元素,则需要重新寻找子串,这时候,子串需要重新从首个元素开始比较,例如我的代码中,子串元素是用t[j]表示的,我就需要重新将j的值初始化,即j=0;
  • 题目要求我们返回的是子串在字符串中首个元素的地址,因此我们需要得到那个首地址,可以在找到首个相同元素时,定义一个变量来存放这个首地址,也可以像我一样,记录从首地址开始,元素下标移动的次数,移了多少次,就减多少次。

2.1.4 PTA提交列表及说明

提交列表说明:
1.部分正确:最开始我是用*t来表示子串的元素,但是我没有考虑到找一半发现这不是子串的时候,应该让t回到首个元素的位置,而且我并未考虑到字符串可能比子串先结束的情况。
2.多种错误:有答案错误和段错误。我在写字符串结束条件时,用的是*s!='\n',但是,题目的输入,结尾应该是有结束符'\0'的。同样,我还未意识到字符串有先结束的可能性。
3.多种错误:同样有答案错误和多个段错误。这次我意识到了字符串可能比子串先结束的问题,但我写的字符串的结束条件没有改。
4.部分正确:对后续比较时用到的number,我没有重新做初始化。
5.部分正确:我猜测,是我return的方式不对,然后我修改了一下,其实并无意义,所以还是存在错误。
6.部分正确:我之前考虑到字符串提前结束的问题,所以我有跳出循环,但是我忘记了,需要令标识flag=0,在这个时候,我发现了这个错误,修改了一下,就又过了一个测试点。
7.部分正确:这个地方,我还是在return的方式上做了修改,把两个return整合成了一个,然而并没有什么用。
8.答案正确:最后,我终于发现,需要初始化number,修改了之后,我就正确通过了。

2.2 7-4 说反话-加强版

2.2.1 伪代码

定义str存放字符串,word存放单词,(因为是字符串的最后一个元素倒过来存的,所以输出需要逆序输出);
j用于word的计数,i用于str的计数;len用于存放字符串长度;number用于标记输出的单词个数,以解决空格问题;flag标记遇到的第一个空格;
flag = 0;j = 0;number = 1;//初始化
输入字符串str;
len = strlen(str);
for len-2 to 0
  if 第一次出现空格且有单词存入word中
     if number
        number=0;
     end if
     else 
        输出空格
     end else
     flag=1;//之后再有空格,不用管
     for j-1 to 0
         逐个输出word中的元素
     end for
  end if
  if 这个元素不是空格
     flag=0;
     将这个元素存入word
     j++;  
  end if
  if 循环到最后一个元素而这个元素不是空格
     if 还没有输出过单词
        number=0;
     end if
     else 
        输出一个空格
     end else
     for j-1 to 0
         逐个输出word中的元素
     end for
  end if
end for 

2.2.2 代码截图



2.2.3 总结本题的知识点

  • 在计算字符串长度时,fgets得到的字符串是以'\n'为结尾,这个换行符会被算进len这个长度里,所以在循环的时候,如果用len来写循环条件的时候,需要注意需不需要扣除这个换行符,而如果字符串是以'\0'为结尾,这个结束符是不记录在len里的。
  • 这一题需要注意的一个是,它的格式要求单词间只能有一个空格且首尾不能有空格,所以在对格式进行控制的时候,我用到了flag和number来做标记。
    number是类似于之前的做法,就是未输出单词前,不能输出空格,这样就确保了输出的句子前面没有空格,而flag则保证了不会出现输出多个空格的问题。
  • 还有一点需要注意的是,我们要做到在正序的句子中将各个单词倒序输出,所以输出并不是单纯的完全正序或是完全倒序。我的做法是先按倒着的顺序存入令一个字符型数组中,再把这个数组倒序输出,等于说是从后到前,把每个单词先倒过来再倒回去,然后输出的单词就是正序的,而句子是倒序的。当然,我后来发现,其实也可以不用再定义数组,只需要记录这个单词共有几个字母(记为n),然后遇到空格的时候,从这个空格后的那个字母开始,依次输出n个字母即可。

2.2.4 PTA提交列表及说明

说明:

  • 部分正确:没有考虑到句子前后有空格的情况,而且对于输出空格的条件没有控制好,我那时候写的条件等于是默认了有空格出现就输出空格。还有就是我的word循环输出结束后,那个循环计数的变量j=-1,我忘记重新初始化为0了,还有一个点是我的字符串的极限值不够大。
  • 部分正确:这里,我更改了输出空格的条件,我改为这个元素为第一个出现空格而下一个元素不是空格且不是换行符,但这样保证了末尾不会有空格,无法保证最开始没有空格。
  • 部分正确:我本来用flag来标记空格的输出的,但是我最后一个if条件句对于字符串结尾的处理的条件不小心写成了i>0,这就意味着无论怎样这个语句都会执行,所以基本上就都错了。
  • 部分正确:上面错太多,所以我就把第二个的代码重新修改了,而且修改了极限值,过了大部分的测试点,但是还有很重要的一个句子开头的空格问题未解决。
  • 部分正确:我尝试设置了一个number用来标记第一个输出的单词,但是我number的条件句放在了循环输出的后面,等于我做了输出,改了number的值,然后再判断number,语句顺序没写好,等于说我的这个number有等同于无。
  • 部分正确:这里,我犯了一个很,迷,的错误,我后面的if条件句,忘记了它的else。(emmm,据我的初步推测,是我懒的写,从上面的代码复制到下面的时候,忘记复制else了,至于为什么会断在else,我是写完if的部分,发现else和上面写过的一样,然后就。害。它是想说,我不能偷懒嘛。。😶)
  • 答案正确:补上那个神奇的else,我就正确了呢。

2.3 7-5 删除字符串中的子串

2.3.1 伪代码

定义string存放原来的字符串,substring存子串;
指针p用于循环时的计数;指针locPtr存放找到的子串第一个元素在的地址;lenSubstring记录子串的长度
(主函数部分只做调用函数和输出删除后的字符串)
locPtr = NULL;
lenSubstring = strlen(substring)-1;
substring[lenSubstring] = '\0';//初始化
while 字符串中存在子串
     for 找到字符串的首地址 to 字符串结束
	*p = *(p + lenSubstring);
	 if 字符串结束
	    跳出循环
	 end if
	*p = '\0';//末尾要有结束符
     end for
end while

2.3.2 代码截图


2.3.3 总结本题的知识点

  • 在一个字符串中找子串,之前用的都是循环比对,先找第一个,然后按顺序比对至子串结束,如果循环中途有发现一个不一样的,就直接结束,说明这个不是子串;而在这次的代码里,有一个新的函数strstr(s1,s2)s2表示字符串,s1表示子串,如果存在子串,它得到的值就是子串在字符串中的首地址,如果不存在子串,它就会返回NULL。有了这个函数,找子串就很方便了。但是要注意的是,如果题目要求我们写的是函数部分,且头文件不包含<string.h>,就不能用这个函数,只能老老实实自己敲代码了。
  • 要注意的是,我们在对字符串做处理的时候,循环条件必须根据我们的输入的写法来写,比如fgets以换行符为结尾,scanf无法读入空格等等,这不仅是这一题需要注意的,几乎每个都得注意。
  • 在对字符串做删减的时候,要注意,给末尾加上结束符'\0'

2.3.4 PTA提交列表及说明


说明:
这个提交列表虽然只有一个,但是我,私底下还是有调的。因为我是在老师讲评后写的,然后按照那个思路,稍作修改,就对了。

  • 我的修改,错误的,有一个就是忘记了给字符串加上结束符,导致输出时候,输出了一堆奇奇怪怪的东西,(因为我最开始是有给原字符串加上结束符的,就是后期修改了之后,忘记在删减的地方加了,所以输出的会有多余的东西)
  • 再有就是,用到的那个指针忘记初始化为NULL了,当然,vs很快就提醒我了,其他的话,因为毕竟是讲评过的,也没有太大的问题。

3.阅读代码(-2--1分)

  • 题目:

  • 解答:



  • 代码解读和分析(上述代码,好像是python的写法,代码有些我看的不是很懂,结合它的注释,我整理了一下)

    • 1.题意:这道题是要求两数相乘的积,两个数以字符串的形式分别存在两个不同的字符型数组中。(这题就跟我们之前在pta上刷的那个整数相加有类似的地方,有点像是升级版)
    • 2.思路:他是将其中的一个字符串看作整体,然后以倒序的方式,让另一个字符数组的每个元素乘以这个整体,根据元素所在位置,考虑需要再加多少个0的问题,然后把相乘的结果加起来。整体思路其实就是根据我们列竖式计算的方法来的。他的倒序,其实就是从个位开始乘的意思。
    • 3.优点:
      • 这份代码,首先,他有一个做法是,保障了作为整体的字符串所代表的数,位数比较大,我们在做列竖式计算的时候其实也会有这个习惯,不是说位数小的在上就不能够得出答案,只是,这样减少了最后做加法的次数,更加方便,程序运行效率更高;
      • 其次,他的函数接口设计的比较好,不仅有传入参数,还有返回值,函数间的联系就比较强,就不是单纯地分装代码,而是真正做到了功能分配,我觉着这一点我需要向他学习;
      • 再有就是,他考虑到了越界的问题,就感觉比较细致,考虑的比较周到。我就觉着很好,因为我的代码,一不小心就会越界。