0.展示PTA总分

1.本章学习总结

1.1 学习内容总结

1.如果事先无法确定需要处理的数据数量,有两种处理方法:一种方法是估计一个上限,并将该上限作为数组长度,但是这样常常会造成空间浪费;另一种方法是利用指针来实现存储空间的动态分配

2.使用指针可以对复杂数据进行处理,能对计算机的内存分配进行控制,在函数调用中使用指针可以返回多个值

3.一般数据是按照“地址”存取的

4.程序执行时将变量翻译为它所在的内存地址来进行操作,这种使用变量的方法叫做“直接访问”,直接访问一般以内存单元的第1个字节的地址作为它的地址

5.在C程序中还有一种使用变量的方法,即通过变量的地址进行操作,用指针访问内存和操纵地址,比如再定义一个变量来专门存放另一个变量的地址,这种方法称为“间接访问”

6.在C语言中把专门用来存放变量地址的变量称为“指针变量”,简称为指针

7.指针是用来存放内存地址的变量,如果一个指针变量的值是另一个变量的地址,就称该指针变量指向那个变量

8.&n表示变量n的内存地址或存储位置,&被称作地址运算符,&是一元运算符,与其他的一元运算符有同样的优先级和从右到左的结合性

9.定义指针的一般形式为:

类型名 * 指针变量名

  • ps:* 指的是指针声明符

10.在许多场合,可以把指针变量简称为指针,但实际上指针和指针变量在含义又上存在一定的差异,一般来说,在C语言中,指针被认为是一个概念,而指针变量本身就是变量,和一般变量不同的是它存放的是地址。大多数情况下,并不特别强调它们的区别,都是指存放内存地址的指针变量

11.指针值可以是特殊的地址0,也可以是一个代表机器地址的正整数

12.指针变量用于存放变量的地址,由于不同类型的变量在内存中占用不同大小的存储单元,所以只知道内存地址,还不能确定该地址上的对象,因此在定义指针变量时,除了指针变量名,还需要说明该指针变量所指向的内存空间上所存放的数据的类型

13.定义多个指针变量时,每一个指针变量前面都必须加上*

14.指针变量的类型不是指指针变量本身的类型,面是指它所指向的变量的数据类型

15.无论何种类型的指针变量,它们都是用来存放地址的,因此指针变量自身所占的内存空间大小和它所指向的变量数据类型无关,尽管不同类型的变量所占的内存空间不同,但不同类型指针变量所占的内存空间大小都是相同的

16.指针变量被定义后,必须将指针变量和一个特定的变量进行关联后才可以使用它,也就是说,指针变量也要先赋值再使用,当然指针变量被赋的值应该是地址

17.对指针变量p赋值:


p=&i;//指针p被看作存放变量i的地址
p=0;//将特殊值0赋值给指针p
p=NULL;//将特殊值0赋值给指针p
p=(int * )1732;//使用强制类型转换(int * )来避免编译错误,表示指针p指向地址为1723的int型变量


18.在对指针变量命名时(除整型指针外),建议用其类型名的首字母作为指针名的首字符

19.在定义指针变量时,要注意:


<1>指针变量名是一个标识符,要按照C标识符的命名规则对指针变量进行命名

<2>指针变量的数据类型是它所指向的变量的类型,一般情况下一且指针变量的类型被确定后,它只能指向同种类型的变量

<3>在定义指针变量时需要使用指针声明符“*”,但指针声明符并不是指针的组成部分。如:定义int *p;说明p是指针变量,而不是 *p


20.在对指针变量命名时(除整型指针外),建议用其类型名的首字母作为指针名的首字符,用p或ptr作为名字,以使程序具有较好的可读性。如将单精度浮点型指针取名为fp,fptr,f_ptr等

21.如果指针的值是某个变量的地址,通过指针就能间接访问那个变量,这些操作由取地址运算符&和间接访问运算符*完成。此外,相同类型的指针还能进行赋值,比较和算术运算


(1)取地址运算和间接访问运算

<1>单目运算符&用于给出变量的地址
<2>“*”除了被用于定义指针变量以外,还被用于访问指针所指向的变量,它也被称为间接访问运算符。例如:当p指向a时,*p和a访问同一个存储单元,*p的值就是a的值
<3>表达式*p=*p+1、++*p和(*p)++,都是将指针p所指向变量的值加1,而表达式*p++等价于*(p++),先取*p的值作为表达式的值,再将指针p的值加1,运算后,p不再指向变量a
<4>带有间接地址访问符*的变量的操作在不同的情况下会有完全不同的含义

(2)赋值运算

<1>一旦指针被定义并赋值后,就可以如同其他类型变量一样进行赋值运算
<2>给指针赋值是使指针和所指向变量之间建立关联的必要过程
<3>指针之间的相互赋值只能在相同类型的指针之间进行,可以在定义时对指针进行赋值,也可在程序运行过程中根据需要对指针重新赋值。但要特别注意:指针只有在被赋值以后才能被正确使用


22.C语言中的变量在引用前必须先定义并赋值,指针变量在定义后也要先赋值再引用,在定义指针变量时,可以同时对它赋初值


int a;
int * p1=&a;//在定义指针p1的同时给其赋值,使指针p1指向变量a
int * p2=p1;//在定义指针p2的同时对其赋值,使p2和p1的值相同
//以上对指针pl和p2的赋值都是在定义时进行的,使得指针p1和p2都指向变量a


23.在进行指针初始化的时候需要注意以下几点:


(1)在指针变量定义或者初始化时变量名前面的“*”只表示该变量是个指针变量,它既不是乘法运算符也不是间接访问符。
(2)把一个变量的地址作为初始化值赋给指针变量时,该变量必须在此之前已经定义。因为变量只有在定义后才被分配存储单元,它的地址才能赋给指针变量。
(3)可以用初始化了的指针变量给另一个指针变量作初始化值。
(4)不能用数值作为指针变量的初值,但可以将一个指针变量初始化为一个空指针。例如int *p=1000;是不对的,而int *p=0;是将指针变量初始化为空指针,是正确的,这里0是ASCII字符NULL的值。
(5)指针变量定义时的数据类型和它所指向的目标变量的数据类型必须一致,因为不同的数据类型所占用的存储单元的字节数不同。


24.在C语言中实参和形参之间的数据传递是单向的“值传递”方式,当指针变量作为函数参数时也遵守这一个规则,调用函数不能改变实参指针变量的值,但可以改变实参指针变量所指向的变量的值。这样的机制被称为引用调用。

25.采用引用调用机制需要在函数定义时将指针作为函数的形参,在函数调用时把变量的地址作为实参。

26.要通过函数调用来改变主调函数中某个变量的值,可以把指针作为函数的参数。在主调函数中,将该变量的地址或者指向该变量的指针作为实参。在被调函数中,用指针类型形参接受该变量的地址,并改变形参所指向变量的值。

27.函数只能通过return语句返回一个值。如果希望函数调用能将多个计算结果带回主调函数,用 return语句是无法实现的,而将指针作为函数的参数就能使函数返回多个值。

28.指针和数组的比较:


指针和数组在许多方面有相似之处,例如指针名和数组名都代表内存地址。不同的是,指针名是一个变量,而数组名是一个常量。换言之,指针名所代表的地址是可以改变的,而数组一旦被定义后内存空间就被分配,也就是说数组名所代表的地址是不能改变的。因此研究指针和数组的关系类似于研究一个同类型的变量和常量之间的关系。


29.指针、数组和地址间的关系


<1>数组的基地址是在内存中存储数组的起始位置,它是数组中第一个元素(即下标为0)的地址,因此数组名本身就是一个地址即指针值。
<2>在访问内存方面,指针和数组几乎是相同的,但也有一些微妙又重要的区别:指针是以地址作为值的变量,而数组名的值是一个特殊的固定地址,可以把它看作是指针常量。所以p=a;和p=&a[0];两条语句是等价的,都把同一个地址值赋给了指针p。
<3>p=a+1是合法的,但是a=a+1就是非法的。


30.对数组元素求和的三种方法:

方法一:


//已经对数组a的元素进行了赋值
sum=0;
for(p=a;p<=&a[99];p++)
   sum+=*p;


方法二:


//一般地,如果i是int型变量,那么p+i就是距地址p的第i个偏移,类似地,a+i是距数组a的基地址的第i个偏移,*(a+i)与a[i]等价
sum=0;
for(i=0;i<100;i++)
   sum+=*(a+i);


方法三:


//数组名可以使用指针形式,而指针变量也可以转换为数组形式
p=a;
sum=0;
for(i=0;i<100;++i)
   sum+=p[i];


31.数组a为指针常量,不是变量,像a=p、a++和a+=2这样的表达式都是非法的;变量p是指向某个指定类型变量的指针,所以p++、p+i、p+1和p+=2这样的表达式都是有意义的

32.如果p和q是指向数组元素的指针,那么p-q产生一个int型的值,该值表示在p和q之间的数组元素的个数。即两个相同类型的指针相减,表示它们之间相隔的数组元素数目

33.在C语言中,指针的算术运算只包括两个相同类型的指针相减以及指针加上或减去一个整数,其他的操作如指针相加、相乘和相除,或指针加上和减去一个浮点数都是非法的

34.两个相同类型指针还可以使用关系运算符比较大小

35.使用数组和指针可以实现相同的操作,但是指针的效率高、更灵活,实际上在C编译器中,对数组的操作是自动转换为指针进行的。但与数组操作相比,指针操作的程序代码读上不够直观,特别对C的初学者来说较难掌握

36.当进行参数传递时,主函数传递的是指针数组的基地址,数组元素本身不被复制。作为一种表示习惯,编译器允许在作为参数声明的指针中使用数组方括号

37.查找和排序一样,都是程序设计的最基本的算法。顺序查找是对数组元素从头到尾进行遍历,一旦数组元素量很大,其查找的效率就不高。二分查找是查找效率较高的一种,但前提是数组元素必须是有序的

38.二分查找法的算法思路:


设n个元素的数组a已有序(假定a[0]到a[n-1]升序排列),用low和high两个变量来表示查找的区间,即在a[low]~a[high]间去查找x。初始状态为low=0,high=n-1。首先用要查找的x与查找区间的中间位置元素a[mid](mid=(low+high)/2)比较,如果相等则找到,算法终止;如果x<a[mid],由于数组是升序排列的,则只要在low~mid-1区间继续查找;如果x>a[mid],则只要在mid+1-high区间继续查找。也就是根据与中间元素比较的情况产生了新的区间值low、high值,当出现low>high时算法终止,即不存在值为x的元素。


39.对于搜索大型数据库来说,对数据进行排序的算法是至关重要的。排序是一种非常有助于解决查找问题的技术,此外,如何有效地排序本身就是计算机算法的一个重要的研究领域

40.冒泡排序之所以叫做冒泡排序,是因为在进行从小到大排序时,小的数经过交换会慢慢从下“冒”上来。冒泡排序效率不高,这是因方它需要约n^2/2次比较,然而对一些小数组来说,它的性能通常还是可以接受的

41.冒泡算法分析:


定义一个数组a,假定输入后值为{7,3,66,3,-5,22,-77,2},再调用 bubble(a,8)
在第一次循环的开始处,把a[0]与a[1]比较,由于它们不符合次序要求,对它们要做交换;然后把a[1]与a[2]比较,由于它们符合次序要求,对它们不做交换;再把a[2]与a[3]比较,依此类推。若邻接元素不符合次序要求,则要对它们进行交换。第一次循环的效果是把数组中的最大元素“冒泡”到a[7]。在第二次循环后,不再检查a[7],即不再改变它,把a[0]再与a[1]比较,如此等等。在第二次循环后;第二大的数存放在a[6]中。由于每次循环都把当前最大的元素放在数组的合适位置中,在n-1循环后,算法就完成了所有元素的排序


42.在C语言中,字符串(string,简称串)是一种特殊的char型一维数组。可以把字符串中的字符作为数组中的元素访问,或利用char型指针对其访间,这种灵活性使得在用C语言编写字符串处理程序时特别有用

43.虽然可以把对字符串的处理看作是对数组的处理的一种特别情况,但是对字符串的处理也有自身的特点,如可以用字符值'\0'终止字符串

44.字符串常量的存储:


字符串常量是用一对双引号括起来的字符序列,与基本类型常量的存储相似,字符串常量在内存中的存放位置由系统自动安排。由于字符串常量是一串字符,通常被看作一个特殊的一维字符数组,与数组的存储类似,字符串常量中的所有字符在内存中连续存放。所以,系统在存储一个字符串常量时先给定一个起始地址,从该地址指定的存储单元开始,连续存放该字符串中的字符。显然,该起始地址代表了存放字符串常量首字符的存储单元的地址,被称为字符串常量的值,字符的指针常量。


45.如果定义一个字符指针接收字符串常量的值,该指针就指向字符串的首字符。这样,字符数组和字符指针都可以用来处理字符串

46.输出字符串时,输出参数给出起始位置(地址),'\0'用来控制结束。因此,字符串中其他字符的地址也能作为输出参数

47.定义字符指针后,如果没有对它赋值,指针的值是不确定的,不能明确它指向的内存单元。因此,如果引用未赋值的指针,可能会出现难以预料的结果,所以,为了尽量最免引用未赋值的指针所造成的伤害,在定义指针时,可以先将它的初值置为空,如 char *s=NULL

48.在C语言的标准库中含有很多非常有用的字符串处理函数。它们都要求以字符串作为参数,并且它们都返回整数值或指向char的指针。在头文件stdio.h和 string.h中给出了字符串处理函数原型,所以使用这些字符串处理函数时要引入相应的头文件

49.字符串的输入和输出:


函数scanf()和gets()可用来输入字符串,而printf()和puts()输出字符串。它们在系统文件stdio.h中定义。

(1) scanf(格式控制字符串,输入参数表)
格式控制字符串中使用格式控制说明%s,输入参数必须是字符型数组名。该函数遇回车或空格输入结束,并自动将输入的数据和字符串结束符‘\0’送入数组中。例如
scanf("%s",s); /*假设s为字符型数组*/

(2) printf(格式控制字符串,输出参数表)
格式控制字符串中相应的格式控制说明用%s,输出参数可以是字符数组名或字符串常量输出遇‘\0’结束。例如
printf("%s",s);

ps:由于在C语言中是使用一维字符数组存放字符串的,因此可以在字符串操作函中直接使用数组名进行输入输出。

(3)字符串输入函数gets(s)
参数s是字符数组名。函数从输入得到一个字符串,遇回车输入结束,自动将输入的数据和’\0’送入数组中。采用函数gets()输入的字符串允许带空格。
实际上函数gets()有返回值,如果输入成功则返回值是字符串第一个字符的地址,如果输入失败则返回NULL。但一般情况下使用gets()主要是为了输入字符串,而并不关心它的返回值。

(4)字符串输出函数puts(s)
参数s可以是字符数组名或字符串常量。输出时遇’\0’自动将其转换为’\n’,即输出字符串后换行。同样函数puts()也有返回值,如果成功执行了输出字符串的操作,则返回换行符号’\n’,,否则返回EOF。


50.字符串的复制、连接和比较及字符串长度:


字符串复制、连接和比较以及计算字符串长度的函数,在系统头文件 string.h中定义。

(1)字符串复制函数char *strcpy(char *s1,char *s2),简化后为strcpy(s1,s2)
参数s1必须是字符型数组基地址,参数s2可以是字符数组名或字符串常量。

(2)字符串连接函数strcat(s1,s2)
参数s1必须是字符型数组基地址,参数s2可以是字符数组名或字符串常量。

(3)字符串比较函数strcmp(s1,s2)
函数 strcmp()中的参数s1和s2可以是字符数组名或字符串常量。
函数 strcmp()返回一个整数,给出字符串s1和s2的比较结果:
①若s1和2相等,返回0。
②若s1大于s2,返回一个正数。
③若s1小于s2,则返回一个负数。
设strl和st2都是字符串,在C语言中,str1==srt2、str1>st2和str1<=str2比较的是两个字符串的起始地址,而strcmp(str1,str2)==0,strcmp(str1,str2)>0,strcmp(str1,str2)<=0比较两个字符串的内容。
字符串比较的规则是:从两个字符串的首字符开始,依次比较相对应的字符(比较字符的ASCⅡ码),直到出现不同的字符或遇’\0’为止。如果所有的字符都相同,返回0;否则,以第一个不相同字符的比较结果为准,返回这两个字符的差,即第一个字符串中的字符减去第二个字符串中的字符得到的差。

(4)字符串长度函数strlen(s1)
参数s1可以是字符数组名或字符串常量。
函数strlen()返回字符串s1的’\0’之前的字符个数,即字符串有效字符的个数(不包括字符结束符’\0’)。


51.动态内存申请得到的是一个没有名字、只有首地址的连续存储空间,相当于一个无名的一维数组。该动态内存的首地址经过强制类型转换并存放在指针变量中,通过移动指针来存取各个数据

52.程序中需要使用各种变量来保存被处理的数据和各种状态信息,变量在使用前必须被定义且安排好存储空间(包括内存起始地址和存储单元大小)。C语言的全局变量、静态局部变量的存储是在编译时确定的,其存储空间的实际分配在程序开始执行前完成。对于局部自动变量,在执行进入变量定义所在的复合语句时为它们分配存储单元,这种变量的大小也是静态确定的

53.以静态方式安排存储的好处主要是实现比较方便,效率高,程序执行中需要做的事情比较简单。但这种做法也有限制,某些问题不太好解决

54.一般情况下,运行中的很多存储要求在写程序时无法确定,因此需要一种机制,可以根据运行时的实际存储需求分配适当的存储区,用于存放那些在运行中才能确定数量的数据。C语言为此提供了动态存储管理机制,允许程序动态申请和释放空间

55.在C语言中主要用两种方法使用内存:一种是由系统分配的内存区;另一种是用内存动态分配方式,留给程序动态分配的存储区

56.动态内存分配的步骤


(1)了解需要多少内存空间。
(2)利用C语言提供的动态分配函数来分配所需要的存储空间。
(3)使指针指向获得的内存空间,以便用指针在该空间内实施运算或操作。
(4)当使用完毕内存后,释放这一空间。


57.动态内存分配函数


在进行动态存贮分配的操作中,C语言提供了一组标准函数,定义在stdlib.h里面

<1>动态存储分配函数malloc()
函数原型是:
void *malloc(unsigned size)
功能:在内存的动态存储区中分配一连续空间,其长度为sm,若申请成功,则返回指向所分配内存空间的起始地址的指针;若申请内存空间不成功,则返回NULL(值为0)。
调用malloc()时,应该利用sizeof计算存储块大小,不要直接写数值,因为不同数据类型占用空间大小可能不同。此外,每次动态分都必须检查是否成功,考到意外情况的处理。虽然这里存储块是动态分配的,但它的大小在分配后也是确定的。

<2>计数动态存储分配函数 calloc()
函数原型是:
void *calloc (unsigned n, unsigned size)
功能:在内存的动态存储区中分配n个连续空间,每一存储空间的长度为size,并且分配后还把存储块里全部初始化为0。若申请成功,则返回一个指向被分配内存空间的起始地址的指针;若申请内存空间不成功,则返回NULL(0)。
malloc()对所分配的存储块不做任何事情, calloc()对整个区域进行初始化。

<3>动态存储释放函数free()
函数原型是:
void free (void *ptr)
功能:释放由动态存储分配函数申请到的整块内存空间,ptr为指向要释放空间的首地址。如果ptr的值是空指针,则free什么都不做。该函数无返回值。
为了保证动态存储区的有效利用,在知道某个动态分配的存储块不再用时,就应该将它释放,释放后不允许再通过该指针去访问已经释放的块,否则也可能引起灾难性错误。

<4>分配调整函数 realloc()
函数原型是:
void *realloc (void *ptr, unsigned size)
功能:更改以前的存储分配。ptr必须是以前通过动态存储分配得到的指针。参数size为现在需要的空间大小。如果分配失败,返回NULL,同时原来ptr指向存储块的内容不变。如果成功,返回一片能存放大小为size的区块,并保证该块的内容与原块的一致。如果size小于原块的大小,则内容为原块前size范围内的数据;如果新块更大,则原有数据存在新块的前一部分。如果分配成功,原存储块的内容就可能改变了,因此不允许再通过ptr去使用它。


58.指针数组的概念:


C语言中的数组可以是任何类型,如果数组的各个元素都是指针类型,用于存放内存地址,那么这个数组就是指针数组。

指针数组定义的一般格式为:
类型名 *数组名[数组长度];

指针数组是由指针变量构成的数组,在操作时,既可以直接对数组元素进行赋值(地址值)和引用,也可以间接访问数组元素所指向的单元内容,改变或引用该单元的内容。

对指针数组元素的操作与对同类型指针变量的操作相同。


59.二级指针

<1>指向指针的指针也被称为二级指针,其一般定义形式为:类型名 **变量名;
<2>假设有如下定义:
int a[3][4];
可以把二维数组看成是由a[0]、a[1]、a[2]组成的一维数组,而a[0]、a[1]、a[2]各自又是一个一维数组。也即二维数组是数组元素为一维数组的一维数组。由此,数组名a就是a[0]的地址,即&a[0],而a[0]即&a[0][0],因此,&a[0]与&&a[0][0]等价。所以,二维数组名a是一个二级指针,而a[0]是一级指针。以此类推,a+1是第1行的地址,(a+1)是第1行首元素的地址,**(a+1)是第1行首元素的值;a+i是第i行的地址,(a+i)是第i行首元素的地址,**(a+i)第i行首元素的值。
虽然a、* a的值相同,但含义不同。a是行元素数组的首地址,又称为行地址,是二级指针,而* a是首行第一个元素的地址,又称为列地址,是一级指针。
定义二维字符数组时必须指定列长度。

60.指针做函数返回值

<1>不能返回在函数内部定义的局部数据对象的地址,这是因为所有的局部数据对象在函数返回时就会消亡,其值不再有效。
<2>返回指针的函数一般都返回全局数据对象的或者主调函数中数据对象的地址,不能返回在函数内部定义的局部数据对象的地址。

1.2 本章学习体会

1.2.1 学习体会

  • 这一段时间的学习比较吃力,关于指针的一些概念理解得不是很明白,做题时也遇到了一些麻烦,在VS上写代码时一直会出错又出错,然后就一点一点地去改,有时候改的原因是什么自己也不清楚(O_O)?
  • 指针的基本运算,符号不同不知道到底怎么分辨,到底有什么区别,有什么特殊含义,比较混乱,没有弄懂
  • 在C语言中实参和形参之间的数据传递是单向的“值传递”方式,当指针变量作为函数参数时,就更搞不明白参数传递了,格式总是写错,在函数调用中不清楚怎么用指针
  • 不明白为什么指针的运算中相加,相乘等运算是非法的
  • 指针的代码读起来比较困难,不太好理解,在刷PTA的时候,也不怎么会用指针来做,有时候就是提一两笔,比较模糊,好多其实都没有用指针,有点尴尬哈 ̄□ ̄||
  • 不过我还是有点问题,就是VS,Dev-C++和PTA有时候这个对,那个错的,让人很纠结啊(ー`´ー)

1.2.2 代码累计

我的代码量(不包括重复)

代码量(行)
4 241
5 506
6 771
7 842
8 793
9 724
10 1980
11 983
12 879
13 545
14 479
累计 9726

2.PTA实验作业

2.1 题目名1


合并两个有序数组(2) (15 分)
要求实现一个函数merge,将元素个数为m的升序数组a和长度为n的升序数组b合并到数组a,合并后的数组仍然按升序排列。假设数组a的长度足够大。


裁判测试程序样例:


#include <stdio.h>
#include <stdlib.h>

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[])
{
    int m, n, i;
    int *a, *b;

    scanf("%d %d", &m, &n);
    a = (int*)malloc((m + n) * sizeof(int));
    for (i = 0; i < m; i++) {
        scanf("%d", &a[i]);
    }

    b = (int*)malloc(n * sizeof(int));
    for (i = 0; i < n; i++) {
        scanf("%d", &b[i]);
    }
    merge(a, m, b, n);
    printArray(a, m + n);

    free(a); free(b);
    return 0;
}


printArray函数


void printArray(int* arr, int arr_size)
{
	int i;
	for (i = 0; i < arr_size; i++)
	{
		printf("%d ", arr[i]);
	}
}


2.1.1 伪代码


merge函数
开辟c数组
i表示a数组下标,j表示b数组下标,k表示c数组下标
for i=j=k=0 to i<m&&j<n
{
    if(a[i]<b[j])//a数组中的数和b数组中的数比较,较小的放入c数组中
       a[i]放入c数组,i++,k++
    else
       b[j]放入c数组,j++,k++
}
while(i<m)  a剩下数据元素放入c
while(j<n)  b剩下数据元素放入c
c数组复制a数组//题目要求合并后的升序数组仍然存放在a中
释放c数组


2.1.2 代码截图


2.1.3 总结本题的知识点


1. 可以多定义一个新的数组c来先储存最后得到的数列,然后再将c数组复制到a数组中
2. a和b是按升序排列的数组,所以可以让a数组中的数和b数组中的数从第一个数开始依次比较,然后将较小的放入c数组中
3. 使用c数组结束后需要释放数组


2.1.4 PTA提交列表及说明

提交列表说明:


1. 编译错误:在复制到PTA时将printArray函数也复制了进来,以后在题目中有细节不表字样时则不用输入
2. 部分正确:我把最后得到的数组的排序问题放到了printArray函数中,导致在VS上显示的答案是正确的,删除printArray函数后,答案就没有排序了
3. 部分正确: 把数组排序的代码从printArray函数中剪切,放入merge函数中
4. 部分正确:数组排序使用顺序排序的方法,导致运行超时,两个数据个数多的测试点没有过
5. 答案正确:按照老师讲的方法,把a数组和b数组顺次比较,这样程序内部简化,运行时间变短


2.2 题目名2


说反话-加强版 (20 分)
给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出。


2.2.1 伪代码


#define N 500010//在一行内给出总长度不超过500000的字符串
定义函数ReverseStr(char* beginPtr);
定义char类型数组str[N]

用fgets输入str,长度为N - 1

调用函数ReverseStr(str)

定义void型函数ReverseStr(调用char类型*beginPtr,即str数组中第一个位置编号)
{
定义char型尾部指针endPtr
定义char型指针p
定义len来记录每个单词长度
定义flag

令endPtr=beginPtr
令flag = 1

while (*endPtr && *endPtr != '\n')//当endPtr指针不是字符串尾部
{
	endPtr++,增加endPtr至字符串尾部
}

通过p = --endPtr来遍历指针
令len初始化为0

while (p != beginPtr)
{
	if (*p不是空格)
	{
		len++,统计单词长度len
		if (*p不是空格但是前一个字符*(p - 1)是空格)
		{
			if (flag为1,说明不是每个单词的首字母,不需要在前面输出空格)
			{
				输出字符串指针所表示的字符串
				flag重新赋值为0
			}
			else
			{
				输出字符串指针所表示的字符串,但是是单词首字母,需要在前面输出空格
			}
			单词长度len还原为0
		}
	}
	p--
}

if (*p不是空格)
{
	if (flag)
	{
		输出第一个单词的非首字母
		flag重新赋值为0
	}
	else
	{
		输出第一个单词的首字母
	}
}

}


2.2.2 代码截图




2.2.3 总结本题的知识点


1. 定义指针指字符串,更能动态了解当前字符及位置
■ while( *endPtr&&*endPtr!='n') endPtr++;
2. 逆向扫描字符串
■ while (p != beginPtr) {p--;}
3. 怎么找字符串单词,即当前字符不是空格而前一个字符是空格
■ if(*p != ' ' && *(p - 1) == ' ') {}
4. 字符串指针能灵活表示某个子串
■ 配合printf("%.*s", len, p),方便表示某个地址开始子串


2.2.4 PTA提交列表及说明

提交列表说明:


1. 答案正确:这道题我看了超星平台上老师的讲解,看的时候感觉,emmmm,明白了,懂了,老师讲的好清楚啊,然后,自己再写时,就华丽丽地忘记了(ー`´ー),需要多下功夫啊


2.3 题目名3


删除字符串中的子串 (20 分)
输入2个字符串S1和S2,要求删除字符串S1中出现的所有子串S2,即结果字符串中不能包含S2。


2.3.1 伪代码


#define N 100
定义void型函数DelSubStr(char* str, char* substr)

定义char型数组str[N]为主串
定义char型数组substr[N]为子串

用fgets输入str,长度为N
用fgets输入substr,长度N

调用函数DelSubStr(str, substr);

输出删除子串后的数组str

定义void型函数DelSubStr(传入主串和子串)
{
定义len来表示子串长度
定义char型指针locPtr
定义char型指针p

把locPtr初始化为空指针
用strlen()函数求子串长度,并用len储存
len--表示子串中字符的数量,所以需要-1,去掉最后的'\0'所占的一个位数
将substr[len]初始化为0

while (使用strstr函数遍历主串,找到子串在主串中的首地址,储存到locPtr中,直到在主串中找不到子串,则循环结束)
{
	for p = locPtr to *p,p++
	{
		将每个字符往前挪动len个位置
		if (已经找到一个子串或者遍历结束没有找到子串)
		{
			break,退出循环
		}
	}
	*p重新赋为0
}

}


2.3.2 代码截图


2.3.3 总结本题的知识点


1. 在主串中寻找子串时,可以多次遍历主串,找到后就可以退出循环了
2. 用strstr函数可以直接遍历数组,比较方便


2.3.4 PTA提交列表及说明

提交列表说明:


1. 答案错误:理解错题意,以为只要遍历一次数组就可以了
2. 答案错误:经过老师的讲解之后,我学会了使用strstr函数,用len来记录子串长度,这样数组长度改变时也方便
3. 答案正确:将p++错写成了p--


3.阅读代码

「Heap」字符串题目合集 - 力扣(LeetCode) https://leetcode-cn.com/problemset/algorithms/?topicSlugs=heap%2Cstring

比较字符串最小字母出现频次

题目描述



代码示例


/*
 * Note: The returned array must be malloced, assume caller calls free().
*/
/* 最小字符出现次数 */
int calcNum(char *chr)
{
    int idx = 0;
    int alpha[26] = {0};
    int min = 25;
    int out = 0;
    while (chr[idx] != '\0') {
        int alp = chr[idx] - 'a';
        alpha[alp]++; /* 将所有字符的出现次数放在26大小的数组中 */
        if (alp <= min) {
            min = alp;
            out = alpha[alp]; /* 取出数组最前面的那个 */
        }
        idx++;
    }
    return out;
}

int* numSmallerByFrequency(char ** queries, int queriesSize, char ** words, int wordsSize, int* returnSize){
    int *qurCnt;
    int *wordCnt;
    
    qurCnt = (int *)malloc(queriesSize * sizeof(int));
    wordCnt = (int *)malloc(wordsSize * sizeof(int));
    
    int *out = (int *)malloc(queriesSize * sizeof(int));
    
    for (int i = 0; i < wordsSize; i++) {
        wordCnt[i] = calcNum(words[i]);
    }
    for (int i = 0; i < queriesSize; i++) {
        qurCnt[i] = calcNum(queries[i]);
        out[i] = 0;
        for (int j = 0; j < wordsSize; j++) {
            if (qurCnt[i] < wordCnt[j]) {
                out[i]++;
            }
        }
    }
    
    *returnSize = queriesSize;
    return out;
}


代码优点


1. 通过哈希数组来求得字符传中最小的字符出现次数
2. 指针运用得很熟练,但是定义动态内存后没有进行释放,可能会导致程序
3. 如果对words的长度再进行排序,可以把时间缩短,程序更快完成
4. 整个代码写得很详细,代码很优秀,值得我们学习


posted on 2019-12-01 13:31    阅读(1088)  评论(0编辑  收藏  举报

/*
*/