0.展示PTA总分
1.本章学习总结
1.1 学习内容总结
a. 指针:C语言中把专门用来存放变量地址的变量称为指针变量
,简称为指针
定义:类型名 *指针变量名
b. “*”除了被用于定义指针外,还被用于访问指针所指向的变量;不能用数据做指针变量的初值;
c. 字符串和字符指针:字符串常量是用一对双引号括起来的字符序列,它实质上是一个指向该字符串首字符的指针常量,如果定义一个字符指针接收字符字符串常量,该指针就指向字符串的首字符;
d. 指针做循环变量:
定义一个指针*p,定义一个存放*p的指针*q;
q=p;
for(*q=0;*q<*p;*(q+1))通过改变*q来实现*p做循环变量
e. 字符指针如何表示字符串:首先我们知道字符指针指向的是字符串首地址,故字符指针指向的只是字符串的第一个字符;
- 如:
char* p;
char str[20];
scanf("%s", str);
p = str;
printf("%c\n", *p);
printf("%c", *(p + 2));
- 若输入的str为
123456
,则输出为1
和3
,因为p指向的是str首地址,p指向的就是第一个字符,通过对p进行变化就可以输出对应的字符。
f. 动态内存分配:在C99之前,我们的编译器需要的内存是需要我们自己申请的,加之在进行庞大数据处理时,静态分配预先分配的存储空间已经不够了,这时我们需要更大的空间来实现我们的程序,使用动态内存分配可以在程序执行过程中动态的分配内存,且可以根据程序的要求扩大或者缩小,可以有效的利用内存空间,提高空间的使用效率,方法如下:
1.动态存储分配函数malloc:malloc用sizeof计算存储块大小,如需要申请大小为m类型为int型的存储单位(int)malloc(m sizeof(int))
函数原型:void malloc (unsigned size)2.动态存储释放函数free:既然向系统借来了存储大小,那么用完后也应该将借来的还回去,以免造成过多占用空间导致程序出错;
函数原型:void free(void *ptr)
由a,若将申请的单位赋给c,则此时释放应为:free(c);3.计数动态存储分配函数calloc:它与malloc的区别在于,malloc对所分配的存储块不做任何事情,calloc对整个区域进行初始化,malloc函数返回值是一个对 象。
calloc函数返回值是一个数组。从安全角度考虑,一般申请动态存储空间时选用calloc好一些;如需要申请大小为m类型为int型的存储单位(int*)calloc(m , sizeof(int))
函数原型:void *calloc(unsigned n,unsigned size)
4.分配调整函数realloc:更改以前的存储分配。如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
注意:这里原始内存中的数据还是保持不变的。当内存不再使用时,应使用free()函数将内存块释放。
函数原型: void realloc(void ptr,unsigned size)
g. 指针数组及其应用:
1.如果数组的各个元素都是指针类型,用于存放内存地址,那么这个数组就是指针数组;
- 2.因为指针数组中各个元素都是指针,所以我们不仅可以对这个数组的各个元素进行操作,还可以对每个元素所指的单元进行操作,如:
char *s[5]={"monday","tuesday","wednesday","thursday","friday"};
char **p=s;
printf("%s ",*p);
printf("%c",*(*p+2));
输出为:monday n
其中monday为数组的第一个元素,n为第一个元素的第三个字符;
通过指针数组,我们可以实现对不同元素的每个单元进行访问从而达到修改,查询等的目的;
h. 二级指针、行指针:
- 1.在C语言中,指向指针的指针一般定义为二级指针;任何值都有地址 ,一级指针的值虽然是地址,但这个地址做为一个值亦需要空间来存放,是空间就具有地址 ,这就是存放地址这一值的空间所具有的地址,二级指针就是为了获取这个地址,二级指针所关联的数据只有一个类型一个用途,就是地址,指针就是两个用途:提供目标的读取或改写, 那么二级指针就是为了提供对于内存地址的读取或改写。
int num=1;
int *p=#
int **pp=&p;
p是一级指针,指向的是a的首地址,pp是二级指针,指向的是p,由此可知:&&a==&p==pp;&a==p==*pp;a=*p=**pp;
2.二维数组:二维数组本质上是以数组作为数组元素的数组,即“数组的数组”,二维数组的数组名是个二级指针;
指针数组:与二维数组名类似,指针数组名也是二级指针;
行指针:二维数组实际上是以一维数组为单位连续存储的,而数组名a是这个“特殊的”一维数组的名称,也就是首地址(常量地址),也就是第一个元素的地址,也就是第一行的首地址,是指首行一整行,并不是指某个具体元素,对于这种,我们称之为“行指针”。
int a[2][3]={{1,2,3},{4,5,6}};
int (*p)[3];
p=a;
printf("%d ", *(*(p + 1)));
printf("%d", *(*(p) + 1));
输出为:4 2
这里的((p + 1))指a[1], ((p) + 1)指a[0][1];
故对于行指针,a[i][j]=((p+i)+j)=(*(p+i))[j]=p[i][j];
这里的p表示表示的是a的首地址,是一个二级指针,
如图,通过返回指针相当于返回数据首地址,来返回所有数据,同样的,如果接受的数为数组,通过返回数组首地址就能返回整个数组的值
1.2 本章学习体会
指针的学习对我来说最困难的就是二级指针的使用,在已知二维数组的情况下,对于二级指针就觉得没什么意义,同时因为难以理解且容易写错与一级指针混淆所以二级指针学的特别混乱;
指针学习最大的收获就是指针作为函数返回值,之前有提出过如何用函数返回多个数据,这里的指针就很好的解决了这个问题,通过返回一个指针,相当于返回这一串数据的首地址,也就返回了所有数据。
代码量统计
周数 | 代码量 |
---|---|
十三周 | 380~450 |
十四周 | 630~700 |
总代码量 | 1010~1120 |
2.PTA提交
2.1 7-2藏尾诗
2.1.1 伪代码
定义一个存放数据的二维数组 str[4][20];
定义存放藏尾诗的数组op[16]
for (i 0 to 4)
读取诗句,并计算每一句的长度len
从长度最后取字,一个字两个字节
op[2 * i] = str[i][len - 2];
op[2 * i + 1] = str[i][len - 1];
puts(op);
2.1.2 代码截图
2.1.3 总结本题的知识点
- 首先是关于汉字的存储,也就是多个字节的存储,如
风平浪静
的静占据的是str[0][7],str[0][8],所以它存储到另一个数组时也需要两个位置; - 关于二维数组的行输入,直接
scanf("%s",str[0])
就可以输入第一句诗
2.1.4 PTA提交列表及说明
- 答案正确:虽然PTA上直接是答案正确但是我在VS上试了很多次,问题就在于对两个字节的存储没有处理好,一开始是单纯的直接按以前的方法四个字放四个元素,结果调试后是两个不认识的字,后面考虑到字节却因为放反了,
str[i][len-2]
放到了op[2*i+1]
中又是那两个不认识的字,所以为了方便我使用了string.h
用strlen直接算数据长度,方便调用元素,这才正确。
2.2 6-9合并两个有序数组
2.2.1. 伪代码
需要两个函数实现操作
void printArray(int* arr, int arr_size); /* 打印数组,细节不表 */
void merge(int* a, int m, int* b, int n); /* 合并a和b到a */
int main(int argc, char const *argv[])
{
首先确定输入数据的m,n
数组首地址*a,*b
申请a的空间=(int*)malloc((m + n) * sizeof(int));
申请b的空间= (int*)malloc(n * sizeof(int));
merge(a, m, b, n);
printArray(a, m + n);
free(a); free(b);
return 0;
}
void merge(int* a, int m, int* b, int n)
{
定义存放a,b,的数组首地址*c;
为c申请能放下a,b的空间= (int*)malloc((m + n) * sizeof(int));
for (; i < m && j < n;)
{
判断a[i],b[j],的大小,有序放入c
if (a[i] < b[j])
{
c[k++] = a[i++];
}
end if
else
{
c[k++] = b[j++];
}
end else
}
end for
while(没有放完的数组)
有序放入c
end while
将c的所有值赋给a
free(c);
}
2.2.2 代码截图
2.2.3 总结本题知识点
- 本题有一个主要的点在于PTA中对时间的控制,所以我们就需要使代码运行时间缩短,用空间去换时间,这里的c就是换时间的方法,通过a,b是有序排列,故只需要依次比较它们来放入c。
- 这题还值得注意的是放不完的问题,如果一个数组放完了循环就会停止,但是另一个不一定放完了,所以需要再判断,通过循环变量是否达到数组长度就可以很好判断。
2.2.4 PTA提交列表及说明
编译错误:这个好像是我忘了打大括号
部分正确:一开始的部分正确是我想用冒泡法来做,因为我害怕这题限制时间,选择法耗时多,但是我的冒泡法只通过了两个点(好像我的冒泡法也有问题)
部分正确:因为冒泡法只过了两个测试点所以我选择了选择法,但是意料之中的问题,运行超时,100000的测试点过不去;
部分正确::因为选择法运行超时所以选择再定义一个数组,但是数组出现问题,数组全部放入了但是最后有一个乱码数字,考虑到应该是我的数组边际出问题了,所以我将后面的代码全部进行了修改;
多种错误:在数组合并那出了问题,c存储空间的释放一直在出错,而且数组没放完
答案错误:同样的,c正常释放了,可是数组边际还是有问题,所以我将后面的循环做了修改
答案正确:修改后的代码顺利过了所有测试点
2.3 字符串的冒泡排序
2.3.1 伪代码
定义存放所有字符串的数组 str[101][11];
定义一个工具数组 op[11];
for (i = 0 to N)
输入所有字符串gets(str[i]);
end for
for (i =0 to k)
冒泡排序
for (j to N-i-1)
{
判断大小 = strcmp(str[j], str[j + 1]);
if (temp > 0)
{ 改变顺序
strcpy(op, str[j]);
strcpy(str[j], str[j + 1]);
strcpy(str[j + 1], op);
}
end if
}
end for
for (i = 0 to N)
puts(str[i]);
end for
2.3.2 代码截图
2.3.3 总结本题知识点
- 本题其实不难,主要在于冒泡法的正确、灵活使用,以及字符串如何交换,我们以前做过将数组元素进行交换,但是字符串如何交换还是第一次,这里就考察了对复制函数strcpy的运用,通过它本题就特别简单;
2.3.4 PTA提交列表及其说明
编译错误:scanf中n,k写成了小写
答案正确:这题我的提交除了一些细节可以算一次过,但是这题一开始我并没有思绪,主要在于字符串的交换,我有想过直接通过行指针来交换这一整行的数据,可是我个人觉得麻烦,所以后面突然想到了strcpy这个函数,我们平时用的最多的应该是strlen,strcmp,这个我很少用过,但是在这里它真的非常好用,我只需要定义一个空数组去放我想放的字符串,用这个函数直接复制,不用去在意有没有复制完全,我的数据有没有溢出,这是我拿出这道题来讲解的原因;
3.阅读代码
代码分析:根据题目这道题的选择是一个循环,选最重的两块石头来比,而不是任意两块,改代码while的处理方法特别好,无论怎么拿,拿的石头个数都是2,所以只需要保证大于2就可以实现题意,之后通过判断石头的重量来控制返回的数值
代码优点:while循环的使用很好的实现了题目中对两个最重石头的选择,qsort函数的使用方便快捷,最后对于返回值的控制特别巧妙,通过判断是不是有两块石头和是不是一样重来返回不同的值,以实现题目要求。