• C博客作业05


    第五次C博客作业


    Q0.展示PTA总分


    Q1.本章学习总结

    1.1 学习内容总结

    • 指针做循环变量做法
      • 指针作为一个循环变量时,经常伴随着一个数组,指针又指向该数组的首地址,我们常常采用的是地址法
      • 有时候也会直接根据对指针里的内容进行判断决定是否继续循环,通常会出现在指向字符串的指针中(且常与fgets()函数一同出现)
         int a[10];
         int* p = a;   //使指针p指向数组a的首地址
         
         for(; p < a + 10; p++)   //这里的a其实也是地址,p的地址不能越出a
         {
          ...
         }
    
     
         char str[80];
         fgets(str,80,stdin);
         char* s = str;
    
         while(*p && *p != '
    ')   //循环条件直接使用p指针的内容
         {
          ...
          p++;
         }
    
    • 可以看出,指针作为循环变量时,不论循环条件是什么,通常都需要地址的自增

    • 有时候,我们会遇到函数中传入的是指针,而函数中又要根据指针写循环,这时候我们常常会再定义一个新的指针等于传入的指针,再用这个新的指针作为循环变量,而不是直接使用传入的指针,这样子可以防止需要对传入的指针的内容进行处理时导致意外错误

    • 字符指针表示字符串、指针数组及其应用

      • 字符指针表示一维的字符串较为简单,不再赘述
      • 字符指针数组(如char* pstr[10])对应着二维的字符数组,常在输入多个字符串、单个字符串长度未知的情况下使用它
      • 要注意的是,在VS2017之后的版本,已经不能像课本中那样直接对字符指针进行赋值了(如char* p[5]={"a","bb",...,"sss"}),如果一定想这么写,只能将他的类型改为const char*,但这也意味着它不能再被修改了
      • 在有字符串的编程中,我们更倾向使用字符指针,举个例子,我们用二维数组来存放字符串,但有一个字符串长度为1,又有一个字符串长度为10000,那我们在定义这个二维数组时就得设置它的列长为10000,但这样的设置对长度为1的字符串在空间上造成了极大的浪费
      • 用一张图看一下字符指针数组和二维数组表示字符串,指针数组最常见的应用也是在字符串方面了
      • 同样的,普通的数字数组也可以用指针数组进行替换,这部分与行指针较为类似
    • 动态内存分配

      • 这些函数都需要头文件stdlib.h
      • 同时,由于这些函数都是void类型,但我们的变量没有这种类型啊!所以在使用时需要进行强制类型转换
      • 举个栗子,我们将会有n个int类型的数要传给int类型的指针p,那么申请动态内存时需要这样写: p = (int*)malloc(n * sizeof(int));
      • 其他动态分配的函数:calloc()、realloc()等,具体见课本第221页
      • 当一块空间使用完毕后,要使用free()函数将这块空间释放!
    • 二级指针与行指针

      • 二级指针,指向的是一级指针的地址,称为指向指针的指针。有几个“*”号就代表是几级指针,目前(我自己)看来二级指针已经足矣,再多级没有必要
      • 假设有p2=&*p1,p1=&p,对p2指针进行两次取值(p2或(p2))才能得到p的值
      • 行指针实际上就是二级指针运用于二维数组,在c语言中,二维数组实际上是以一维数组为单位连续存储的,可以说二维数组是特殊的一维数组
      • 假设有一个行指针int (*p)[5],它指向a[5][5],那么p指的是a[0]一整行,p+1指的是a[1]一整行,以此类推,他们都不表示一个特定的元素
      • p[1]+1、*(p+1)+1两者都指的是a[1][1]的地址,其中p[1]、*(p+1)都代表着第1行的首地址,想要取得a[1][1]的值需要对他进行二次取值(如*(*(p+1)+1))
    • 指针作为函数返回值

      • 指针作为函数返回值返回的是一个地址
      • 我们在前面的章节中有学习到变量生存的周期,函数在结束后会销毁在它内部定义的局部变量,但肯定会有同学说:“不对啊!我在PTA上面返回局部变量的地址也能过呀!”下面我们通过一个简单的例子来看看这个问题(摘自这个网站
         #include <stdio.h>
    
         int *func(){
             int n = 100;
             return &n;
         }
    
         int main(){
             int *p = func(), n;
             //printf("cyuyanhaonanyiwuwuyi
    ");
             n = *p;
             printf("value = %d
    ", n);
             return 0;
         }
    
    • 在有这行注释的时候运行程序,会得到正确的答案100,但是如果我们把注释号去掉,最终的答案会变得十分奇怪
    • 所以在函数的返回值为指针,并且是函数中的局部变量时,一定要立刻使用!!!
    • 上文中的销毁并不是将局部数据所占用的内存全部抹掉,而是程序放弃对它的使用权限,弃之不理,后面的代码可以随意使用这块内存。对于上面的两个例子,func() 运行结束后 n 的内存依然保持原样,值还是 100,如果使用及时也能够得到正确的数据,如果有其它函数被调用就会覆盖这块内存,得到的数据就失去了意义。

    来自上面的网站的解释

    1.2 本章学习体会

    • 指针不愧为C语言的精华,真的很难学,个人感觉指针的概念就比较抽象,离的很远,到现在有的地方还是一知半解
    • 刚开始做指针部分的题目的时候真的是一脸懵逼,*号、&号各种乱加乱删……改到VS不报错为止。到后面干脆自暴自弃能不用指针就不用指针(刚开始做2840中指针的题目的时候)
    • 但现在经过一段时间的学习后,对指针的部分又有了新的认识,也去尝试着将以前的题进行修改,确实可以得到新的理解
    • 本周因为高数小测、线代考试、蓝桥校选还有各种乱七八糟的讲座搞得我这周基本没有什么空闲,也没有更多的时间去打代码了,但是参加了蓝桥校选个人感觉还是意义很大的。首先是第一次非常直观的感受到了自己和其他厉害的人的差距,各种从来没有见过的算法、跳跃的思维、新奇的解答方式,同时自己也学到了一些新东西,感觉接下来的学习又有了新的方向。还有就是提交列表基本中清一色的C++C++C++C++C++,我的CCCCCC显得格格不入
    • 这段时间的代码量统计如下(包含换行等)

    Q2.PTA实验作业

    2.1 合并两个有序数组

    2.1.1 伪代码

    	定义一个新的指针num,用于存储排列后的数据
    	定义变量i记录num中的数字个数,j、k分别用于记录a、b中当前是第几个数
    
    	为num申请动态内存
    	while (j+k < m+n) do
    		如果a中的数都记录完毕,则记录b剩下的数
                    如果b中的数都记录完毕,则记录a剩下的数
    		否则将a[j]与b[k]进行判断,记录小的那个数
    	end while
    	将num中的内容全部拷贝给a
            释放num
    

    2.1.2 代码截图

    2.1.3 本题知识点总结

    1.用malloc()函数申请动态内存,使用完毕后使用free()函数释放内存

     num = (int*)malloc((m + n) * sizeof(int));
     
     free(num);
    

    2.建立新的指针(数组)保存目标内容,对原始内容进行分部分处理

    3.使用memcpy()函数将一个数组的内容复制给另一个数组

     memcpy(目标数组,要被复制的数组,要复制的大小)
     
     /*注意事项:
    1.需要头文件cstring
    2.与strcpy不同,它可以复制任何内容*/
    

    2.1.4 PTA提交列表及说明

    • 前两个部分正确:50000个数据和100000个数据测试点超时,当时是采用嵌套循环进行遍历,再一个循环将内容还给a,严重超时
    • 第三个部分正确:舍弃循环的嵌套和新变量,直接在a中进行更改、移动等操作
    • 编译错误:重新构思代码,引入新变量来存储答案,同时更改循环内容,采用分部处理,最后忘记加头文件了
    • 答案正确:加上cstring的头文件就过了

    2.2 说反话-加强版

    2.2.1 伪代码

    	定义ch数组储存输入的字符串
    	定义变量i、n稍后用于遍历字符串
            定义变量m稍后用于处理字符串
    
    	输入字符串
    	令n为字符串长度减一
    	
    	处理字符串后面的空格
    	while n>0 do
    		让i等于n,从后往前遍历字符串,直到遇到空格或i小于0
    		令m等于单词尾部和空格之间的长度
    		for ++i to n do
    			输出字符ch[i]
                    end for
    		n -= m;
    		处理单词间的空格(遇到一个空格n就减1)
    		if 空格处理完毕后n仍然大于0 then
                            输出一个空格
                    end if
    	end while 
    
    

    2.2.2 代码截图

    2.2.3 本题知识点总结

    1.strlen()函数记录字符串的长度

    2.遇到可能前后、中间都有多空格的情况要对空格进行单独处理

    2.2.4 PTA提交列表及说明

    • 编译错误:在VS中使用gets_s,复制到PTA中忘记改成gets
    • 内部错误:PTA爆炸,与我无瓜
    • 部分正确:全空格测试点过不了,测试后发现我的代码执行后还会输出一个空格,于是增加并修改了处理空格部分的代码
    • 答案正确:修改后全部通过

    2.3 删除字符串中的子串

    2.3.1 伪代码

    	定义父串ch1,字串ch2
    	定义变量i作为循环条件
    	定义变量j、k用于遍历父串子串
    
    	读入父串
            读入子串
    	
    	for i=0 to strlen(ch1) do
    		if ch1[i]等于字串的第一个字符
    			判断字符串ch1接下来的字符是否与ch2的相等
    			if ch2能够读到
    				for j=i to strlen(ch1) - strlen(ch2)
    					ch1从第j+strlen(ch2)个开始左移
    				end for
    				i = -1;
    				ch1[j] = '';
    			end if
    		end if
    	end for
    
    	输出ch1
    
    

    2.3.2 代码截图

    2.3.3 本题知识点总结

    1.使用strstr()函数返回父串中首次出现子串的位置(虽然我的代码中没有使用)

    2.对字符串进行移动操作后要记得给字符串加上''

    3.可以将父串当前字符和子串当前字符作为循环条件,如果子串能跑到''就说明父串中存在该子串

    2.3.4 PTA提交列表及说明

    • 第一、二个部分正确:记得不太清楚了,似乎只有样例和没有可删过了,其余都错,发现是定义数组ch1和ch2的长度时定义错误
    • 第三个部分正确:全删空测试点过不了,之前第25行写的是i=0,但这样经过循环后从1开始而不是从0开始,于是改成i=-1
    • 内部错误:PTA爆炸!与我无瓜
    • 答案正确:修改代码后成功通过

    Q3.代码阅读

    #include<iostream>
    #include<string>
    #include<cstdio>
    using namespace std;
    const string weeks[]={"Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
    const string months[]={"January","February","March","April","May","June","July","August","September","October","November","December"};
    const int cnt[2][12]=
    {
       31,28,31,30,31,30,31,31,30,31,30,31,
       31,29,31,30,31,30,31,31,30,31,30,31
    };
    int leapyear(int y)
    {
        if(y%4==0&&y%100!=0||y%400==0)
           return 1;
        return 0;
    }
    int DayOfWeek(int M,int D,int Y)
    {
        if(M==1||M==2)
        {
            M+=12;
            --Y;
        }
        if(Y<1752||(Y==1752&&M<9)||(Y==1752&&M==9&&D<3))
           return(D+2*M+3*(M+1)/5+Y+Y/4+5)%7;
        else
           return (D+2*M+3*(M+1)/5+Y+Y/4-Y/100+Y/400)%7;
    }
    bool Check(int m,int d,int y)
    {
        if(!(m>=1&&m<13))
           return false;
        if(!(d>=1 && d<=cnt[leapyear(y)][m-1]))
           return false;
        if(y==1752&&m==9&&d>2&&d<14)
           return false;
        return true;
    }
    int main(void)
    {
        int m,d,y;
        while(cin>>m>>d>>y,m+d+y)
        {
            if(Check(m,d,y))
               printf("%s %d, %d is a %s
    ",months[m-1].c_str(),d,y,weeks[DayOfWeek(m,d,y)].c_str());
            else
               printf("%d/%d/%d is an invalid date.
    ",m,d,y);
        }
        return 0;
    }
    
    • 代码阅读
      • 这题题意比较明了,输入月份、日期、年份,如果合法就输出它的日期和星期,直到输入0 0 0
      • 他这里计算星期是有公式的,经过查阅后,发现1752年9月2日前后计算星期的公式不同,且因为奇怪的原因英国规定从1752年9月3日到1752年9月13日的11天并不存在(???)
      • 该代码在函数封装的方面做的非常好:判断闰年、判断日期合法、计算星期,每个函数的功能和目的都非常清楚,主函数也十分简洁,这部分非常值得我学习
      • 运用了bool类型的函数返回true和false代替我们平常的int类型函数返回0和1,也可以学着使用
      • 判断日期合法中 if(!(d>=1 && d<=cnt[leapyear(y)][m-1])) 这个写的十分巧妙,相比我之前判断日期合法的代码来说短了一大截,值得参考
  • 相关阅读:
    spring源码解析四( spring事务)
    Epoll的本质(内部实现原理)转载
    Gitlab+Jenkins+Docker+K8S实现CI/CD
    AIOps
    云运维的关键有哪些?
    Nginx代理Portainer
    nginx+tomcat+mysql进行负载均衡
    Docker安装及容器创建
    运维常用的linux命令操作
    Arm64安装docker和docker-compose
  • 原文地址:https://www.cnblogs.com/silverash/p/11968062.html
Copyright © 2020-2023  润新知