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])) 这个写的十分巧妙,相比我之前判断日期合法的代码来说短了一大截,值得参考
原文地址:https://www.cnblogs.com/silverash/p/11968062.html