第六次作业
这个作业属于哪个班级 | C语言--网络2011/2012 |
---|---|
这个作业的地址 | C博客作业05--指针 |
这个作业的目标 | 学习数组相关内容 |
姓名 | 林进源 |
0.展示PTA总分
1.本章学习总结
1.1 指针定义、指针相关运算、指针做函数参数
(1) 指针的定义
类型名 *指针变量名
- 类型名指定指针变量指向变量的类型,例如int flaot char等。
- 定义指针变量要使用指针声明符*
- 无论什么类型的指针变量,它们都是用来存放地址的,指针变量自身所占的内存空间大小与其指向的变量数据类型无关,不同类型指针变量所占的内存空间大小是相同的。
- 指针变量被定义后,必须将指针变量和一个特定的变量进行关联后才可使用,否则为野指针。
例:
int i,*p;
语句一: p=&i;//p指向i的地址,将i在其地址的值赋给它
语句二: p=0;//代表空指针
语句三: p=NULL;//代表空指针
语句四: p=(int *)1732;//使用强转避免错误,表示p指向地址为1732的int型变量
- 指针声明符并不是指针的组成部分。例如: int p;说明p是指针变量,而不是p。
(2) 指针相关运算
- 取地址运算和间接访问运算
- " * " 除了定义指针变量外,还用于访问指针所指的变量,也称为间接访问运算符,例如,当P指向a时,*p的值也就是a的值。
- 表达式* p = * p +1;++ * p;( * p)++都表示p指向的变量的值加1。而表达式 * p++等价于* (p++),将*p的值加1,运算后,p不再指向a,这里需要注意。
例:
int a=3,*p;
p=&a;//此时p的值为3
*p=10;//此时对指针p所指向的变量赋值相当于对a赋值,此时a的值为10
(*p)++;//此时指针p和a都值都自加1
- 赋值运算
- 指针之间的相互赋值只能在相同类型的指针间进行,只能将一个指针赋给另一个相同类型的指针。
例:
- 指针之间的相互赋值只能在相同类型的指针间进行,只能将一个指针赋给另一个相同类型的指针。
int a=3,*p1,*p3;
p1=&a;
p2=p1;
此时p1和p2都指向a
- 指针变量的赋值
- 指针变量的引用需要先定义并赋值。
- 把一个变量的地址作为初始化值赋给指针变量时,该变量必须在之前就已定义。
- 不能把数值作为指针变量的初值,但可初始化为空指针。例: int * p=1000;是错误的。int * p=0;即可。
(3)指针做函数参数
- 一般return只能返回一个值,而指针作为参数的函数可返回多个值。
例:
int main()
{
int a,*p,b[],c;
*p=&a;
str1(*p);
str2(b);
str3(&c);//需要有传地址符
}
str1(int *px);//此时px指向a
str2(int *py);//此时py指向数组b的首地址
str3(int *pz);//此时pz指向c
1.2 字符指针
(1)字符指针基础知识
- 字符串常量实质上是一个指向该字符串首字符的指针常量。
- 定义一个字符指针指向一个字符串时,该指针指向字符串首字符的地址。
例:
char *sp="point";
printf("%s",sp);//输出为point,sp指向的是point的首地址
printf("%s",sp+2);//输出为int,sp+2作为首地址
- char * sp="point"不可,sp为指针不可直接赋值。const char * sp="point"可以,此时的sp不可重新赋值。
- 字符指针只占用一个可以存放地址的内存单元,存储字符串的首字符地址。
(2)字符串相关函数
- scanf(格式控制字符串,输入参数表),格式控制说明用%s.该函数遇到回车或者空格输入结束,自动将输入的数据和字符串结束符'\0'送入数组。
- printf(格式控制字符串,输出参数表),格式控制说明用%s,输出遇到'\0'结束。
- 字符串复制函数strcpy(a1,a2),a1保证足够的空间,将a2覆盖a1的内容。
表达格式:
strcpy(a1,a2);
- 字符串连接函数strcat(s1,s2),保证s1有足够的空间,将s2的内容连接到s1后面,并将s1后面的'\0'放到连接以后的结束位置。
表达格式:
stract(s1,s2);
- 字符串比较函数strcmp(s1,s2),对s1和s2进行比较,若s1和s2相等则返回0。若s1>s2,返回正数。若s1<s2,则返回负数。比较规则:从两个字符串开始,依次比较相应的字符,直到出现不同字符或遇到'\0'为止。
表达格式:
strcmp(s1,s2);
- 字符串长度函数strlen(s1),统计字符串有效字符的个数(不包括结束符'\0'。
表达格式:
strlen(s1)
1.3 指针做函数返回值
- 指针做函数返回值的时候,应返回的是要输出的数据或者字符串的首地址,需要再用一个新的指针来表示。
例:
返回地址:
char *A(char *str)
{
char *p;
char *loc=NULL;//需要定义为空指针
p=str;
while(条件)//
{
if(条件)
{
loc=p;
}
p++;
}
return loc;//此时loc的地址即为要返回的首地址
}
1.4 动态内存分配
- 全局变量、静态局部变量的存储是在编译时确定的,其存储空间的实际分配在程序开始执行前完成,局部变量的大小是静态确定的,但当数据很大时,这种变量的空间不足。一般情况下运行中的很多存储要求在写程序时无法确定,因此需要一种机制可以根据运行时的实际存储需求分配适当的存储区,用于存放那些在运行中才能确定数量的数据。在C语言中主要用两种方法使用内存:一种是由编译系统分配的内存区(栈区);另一种是用内存动态分配方式,留给程序动态分配的存储区(堆区)。
- 动态内存分配函数
- malloc(),功能:在内存的动态存储区中分配一个长度为size的连续空间,若申请成功,返回指向分配内存空间的起始地址的指针;若申请内存空间不成功,则返回NULL。
- 调用malloc时,要用sizeof计算存储块的大小,不可直接写值。
- 定义在stdlib里面
- 格式: 指针=(int * )malloc(个数 * sizeof(int))
- 计数动态内存分配函数
- calloc(),功能:在内存的动态存储区中分配n个连续空间,每一存储空间的长度为size,并且分配后还把存储块里全部初始化为0.
- 格式: 指针=(int * )calloc(个数 * sizeof(int))
- 动态存储释放函数
- free(),功能: 释放由动态存储分配函数申请到的整块内存空间,指向要释放空间的首地址。
- 动态申请后要释放,否则会产生内存碎片,影响效率。
- 格式: free(指针)
1.5 指针数组及其应用
- 数组名代表一个地址,它的值是数组的首元素的地址。a+i是数组的基地址的第i个偏移量。
- 任何由数组下标来实现的操作都能用指针来完成。
例:
int a[100],*p;
p=a;等价于p=&a[0];
p=a+1等价于p=&a[i];
求数组的和
sum=0;
for(p=a;p<=&a[99];p++)等价于for(p=a;p<=p+100;p++)
sum+=*p;
- *(a+i)和a[i]的值是等价的
- 如果p和q是指向数组元素的指针,那么p-q产生一个int型的值,改值表示在p和q之间的数组元素的个数。同样,指针每一次加1或者减1,并不是其值改变,而是其指向改变。
例:
q p分别指向不同数组
q-p;表示指针p和q之间的元素个数
(int)q-(int)p;表示指针p和q之间的字节数
- 多个字符串用二维数组表示:格式 char str[n][m],这样申请需要比较大的空间来存放。而用指针数组来表示可节省极大的空间,格式: char *str;
例:
类型名 *数组名[数组长度]
- char *str[5]表示其有5个元素,每个元素的类型都是一个字符指针。
1.6 二级指针
- 指向指针的指针称为二级指针,一般定义为:类型名 **变量名
例:
int a=10;
int *p=&a;
int **pp=&p;//pp指向a
char *color[5]={"red","blue","yellow","green","black"};
char **pc;
pc=color;
pc+i为二级地址,*(pc+i)为一级地址
pc相当于color[0]
*pc相当于color[0]
*(pc+i)相对于color[i]
**pc相当于color[0]的第一个字符r
- 例题加强理解:指针a指向a[3][3],a表示二级指针指向a[0]。a+i表示二级指针指向a[i]。* (a+i)表示一级指针,为第i行首元素的地址。*(a+i)+j表示一级地址,为第i行第j个元素的地址。**(a+i)为a[i][0]的地址。 * (( * a+i)+j)为a[i][j]的地址。
- 二级地址做* 运算后还是地址,一级地址* 运算后为内容,* a[i]=* * (a+i)=a[i][0]。
1.7 行指针、列指针
- 行指针,形式:int(*p)[n]。含义: p指向含有n个元素的一维数组的指针变量,二级地址。此时p+i=a+i!=a[i]。
例:
int a[4][5];
int (*p)[5];
p=a;
(*p)[1]=a[0][1];
(*(p+1))[1]=a[1][1];
*(*(p+i))+j=a[i][j];
- 列指针,形式: int a[3][3],* p;p=a。此时p=a[0],* (p+i)表示离a[0][0]的第i个元素。
2.PTA实验作业
2.1删除字符串中的子串
题目:
输入2个字符串S1和S2,要求删除字符串S1中出现的所有子串S2,即结果字符串中不能包含S2。
2.1.1
- 伪代码
char s1;//第一个数组
char s2;//要删除的数组
输入s1和s2
for(条件)//进行外界的重复循环
{
for(条件)//进行顺序查找
{
记录此时的下标
while(s2)
{
if(此时的元素等于要删除的元素)//进行字符串的判断
{
要删除的数组自加
记录下标的数组自加
if(前面字符串都成立且此时要删的数组没有元素)
{
利用循环左移覆盖
}end if
}
else
{
跳出
}
}end while
} end for
}
- 数据处理
- 数据表达: char s1,s2(s1为第一个数组,s2为要删除的数组),int number(记录下标)
- 数据处理:
- 输入两个数组后传递到函数里面
- 先设立一个外界循环,进行重复检索。内部则是正常的循环,与要删除的函数进行比对。
- 当找到要删除的字符串时,进行循环左移覆盖
2.1.2 代码截图
2.1.3同学代码比较
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void DeleteStr(char* S1, char* S2);
void DeleteEnter(char* s);//清除回车
int main()
{
char S1[85];
char S2[85];
fgets(S1, 82, stdin);
fgets(S2, 82, stdin);
DeleteEnter(S1);
DeleteEnter(S2);
DeleteStr(S1, S2);
printf("%s", S1);
return 0;
}
void DeleteStr(char* S1, char* S2)
{
int i;
int j, k;
int m;
int count = 0;
int length = strlen(S2);
for (i = 0; *(S1 + i) != '\0'; i++)
{
for (j = i, k = 0; *(S1 + j) == *(S2 + k) && *(S2 + k) != '\0' && *(S1 + j) != '\0'; j++, k++)//判断字符串的语句
{
if (*(S2 + k + 1) == '\0')
{
for (m = 0; m < length; m++)
{
for (j = i; *(S1 + j) != '\0'; j++)//左移覆盖
{
*(S1 + j) = *(S1 + j + 1);
}
}
count++;
}
}
}
if (count != 0)
{
DeleteStr(S1, S2);//重复操作
}
else;
}
void DeleteEnter(char* s)
{
int length = strlen(s);
*(s + length - 1) = 0;//删除'\n'加上结束符号
}
代码区别:
- 在查找子字符串的时候,我是用了while语句内嵌一个for循环来判断查找子字符串,同学的代码则是直接合并,利用一个for循环直接即可判断
- 在重新从字符串开始查找一遍的时候,我是在外面强行嵌了for循环强制其重新查找,但有个毛病是如果字符串长度较长,子字符串的个数较多,就会超时。同学的代码则是将其所在的函数重新执行一遍。
2.2合并2个有序数组
题目:
要求实现一个函数merge,将元素个数为m的升序数组a和长度为n的升序数组b合并到数组a,合并后的数组仍然按升序排列。假设数组a的长度足够大。
2.2.1
- 伪代码
int n,m;//n为数组a长度,m为数组b的长度
while(两个数组都还有元素)
{
if(数组a尾部元素大于数组b尾部元素)
{
数组a尾部元素放入其末位没有元素的部位
}end if
if(数组b尾部元素大于数组a尾部元素)
{
数组b尾部元素放入数组a末位没有元素的部位
}end if
}
if(数组b还有元素)
{
数组b顺序放入数组a
}end if
- 数据处理:
- 数据表达:int n,m;(分别为数组a和b的长度,同时也是下标)
- 数据处理:
- 利用下标同时判断两个数组是否还有元素
- 从后往前判断两个数组的元素大小,并将其放入数组a的尾部
- 最后判断数组b是否还有元素,若有则逐个放入数组a
2.2.2 代码截图
2.2.3同学代码比较
void merge(int* a, int m, int* b, int n) /* 合并a和b到a */
{
/*数据定义*/
int i;
int j;
int k;
i = m - 1;
j = n - 1;
k = m + n - 1;
/*数据处理*/
for (; k >= 0; k--)
{
if (i >= 0 && a[i] > b[j])//a还有元素且a[i]>b[j]
{
a[k] = a[i];
i--;
}
else if(j >= 0)
{
a[k] = b[j];
j--;
}
}
}
代码区别:
- 我的外界循环是数组a和数组b都会有元素,会导致后面需要讨论哪个数组还有元素剩余。
- 而他的外界条件则是数组a的尾部,在内部利用if (i >= 0 && a[i] > b[j])这个语句规避了讨论哪个数组还有元素剩余。其它大体的思路差不多。
说反话-加强版
题目:
给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出。
2.2.1
伪代码:
int left,right;//左界和右界
int n;//数组的长度
int flag;//判断空格
for(尾部开始递减)
{
if(此时元素不为空格,上一元素为空格)
{
记录右界
}
if(此时元素不为空格,下一元素为空格)
{
记录左界
for(条件)
{
判断空格
从左界输出到右界
}
}
}
if(首元素不为空)
{
判断空格并输出
}
- 数据处理:
- 数据表达:int left,right;(左界和右界),int flag(控制空格),int n(数组长度)。
- 数据处理:
- 从后往前递减,判断找到左界和右界
- 判断空格并输出
- 最后判断首元素是否为空格
2.3.2 代码截图
2.3.3 说明和超星视频做法区别,各自优缺点
超星视频的做法
int main()
{
char a[500002];
fgets(a, 500002, stdin);
Return(a);
return 0;
}
void Return(char *begin)
{
char *end;
char *p;
int len=0;
int flag = 1;
end = begin;
while (*end != '\0'&&*end != '\n')
{
end++;
}
p = --end;
while (p != begin)
{
if (*p != ' ')
{
len++;//记录长度
if (*(p - 1) == ' ')
{
if (flag == 1)//判断空格
{
printf("%.*s", len, p);
flag = 0;
}
else
{
printf(" %.*s", len, p);
}
len = 0;
}
}
p--;
}
if (*p != ' ')//判断首字符是否为空格
{
if (flag == 1)
{
printf("%.*s", len+1, p);
flag = 0;
}
else
{
printf(" %.*s", len+1, p);
}
}
}
* 代码区别:
* 我的方法是记录下标,通过找到左界和右界的下标,利用for循环输出。而超星视频的做法则是利用printf(" %.*s", len, p)这种做法来实现,不需要记录下标
* 同时超星视频利用函数加上指针进行查找,在最后判断首字符的时候我也是借鉴了超星的做法。