这个作业属于哪个班级 | C语言--网络2011/2012 |
---|---|
这个作业的地址 | C语言博客作业05--指针 |
这个作业的目标 | 学习指针相关内容 |
姓名 | 韩龙飞 |
0.展示PTA总分
1.本章学习总结
1.1 指针定义、指针相关运算、指针做函数参数
※为何使用指针
通常,管理大量数据的有效方法不是直接处理数据本身
,而是使用指向数据的指针
。例如,如果需要对大量的大型记录进行排序,对指向该记录的指针列表进行排序效率要比直接对记录排序高得多,因为这样无需在内存中移动数据。
1.指针的定义
-
假设变量p位于2000单元,该单元中存放变量x的地址1000,若取出变量p的值1000就可以访问内存1000单元,实现对变量x的操作,也就是通过p间接访问变量x。这种专门用来存放变量地址的变量是指针。
-
一般形式:类型名 *指针变量名
-
当指针声明符*在定义指针变量时被使用,说明被定义的变量是指针。
-
星号不是运算符,故想要对指针做处理时,前面不需要带
*
。 -
定义多个指针变量时,每个指针变量前都必须加上*。
2.指针相关运算
①取地址运算和间接访问运算
int* p,a=3;
p=&a;
将整型变量a的地址赋给整型指针p,使指针p指向变量a。也就是说用取地址符&
取变量a的地址,并将该地址值作为p的值,使p指向a。
指针的类型和它所指的类型需要相同。
②赋值运算
int a=3,*p1,*p2;
p1=&a;
p2=p1;
将a的地址给p1,再将p1的值给p2,所以指针p1与p2都指向a,即p1=p2=a。
③指针间的加减运算
一个指针加上一个整数代表着,访问在当前指针所指的位置挪动整数个位置后的数。
星号与自增自减优先级相同,但因为结合方向是从右到左,所以需要注意*p++
和(*p)++
的区别。前者是指针移动后的元值,后者是当前指针所指元素增一后的结果。
④赋值
指针变量要先赋值再使用。为了避免引用为赋值的指针所造成的危害,在定义指针时,可先将它的初值置为空,不能用数值作为指针变量的初值。
对指针p进行赋值p=0;和p=NULL都表示指针p为空指针,空指针不指向任何一个单元
3.指针做函数参数
void Swap1(int *p, int *q)
{
int buf;
buf = *p;
*p = *q;
*q = buf;
return;
}
void Swap2(int a, int b)
{
int buf;
buf = a;
a = b;
b = buf;
return;
}
函数swap2不能进行数值的交换,因为当返回主调函数后,函数中的定义全部消亡,并没有将交换后的值传回实参。
而函数swap1中p指向a,q指向b,在改变*p
的值后,就改变了该存储单元的内容,因此在函数中调换*p
和*q
的数值,主调函数的a、b也相应发生了变化。
1.2 字符指针
①字符指针:指向字符型数据的指针变量。每个字符串在内存中都占用一段连续的存储空间,并有唯一确定的首地址。即将字符串的首地址赋值给字符指针,可让字符指针指向一个字符串
②
char *ptr = "Hello";//将保存在常量存储区的"Hello"的首地址赋值给ptr
与
char *ptr;
ptr = "Hello";//是等价的,注意不能理解为将字符串赋值给ptr
③
char str[10] = "Hello";
char *ptr = str;//数组名代表数组的首地址
/*等价于*/
char *ptr;
ptr = str;//等价于ptr = &str[0];将数组的首地址赋给字符指针ptr
④对于数组名str,不能使用str++
操作使其指向字符串中的某个字符,因为数组名是一个地址常量,其值是不能被改变的。
*(ptr+i):字符串中第i+1个字符,相当于*(str+i),即str[i]
也可以用ptr++,移动指针ptr,使ptr指向字符中的某个字符
⑤字符串的长度(指字符串所含的字符个数,但不包括最后的’ ’)与字符数组的大小(指字符串所包含的字符个数加上’ ’,故+1)不一样。
for(i = 0;str[i] != ' '; i++ )
{
printf("%c",str[i]);//常用借助字符串结束标志' '识别字符串的结束
}
scanf("%s",str);//表示读入一个字符串,直到遇到空白字符(空格、回车符、制表符)为止,如果输入带有空格的字符串,只会读到空格以前而空格以后不会读入
printf("%s",str);//表示输出一个字符串,直到遇到字符串结束标志' '为止(注意这里可以带有空格输出)
※常用的字符串处理函数
1.3 指针做函数返回值
函数的返回值是一个指针(地址),这样的函数称为指针函数。
用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针尽量不要指向这些数据,不然它们在后续使用过程中可能会引发运行时错误。
指针做函数返回值有一个好处,就是它可以返回多个值到主调函数当中去。
1.4 动态内存分配
一般情况下,运行中的很多存储要求在写程序时无法确定,因此需要在运行的实际需求中分配合适的存储区。
- 1.从栈区(stack)上分配
- 在执行函数调用时,系统在栈上为函数内的局部变量及形参分配内存,函数执行结束时自动释放这些内存。
- 2.从堆区(heap)上分配
- 在程序运行期间,用动态内存分配函数来申请的内存都是从堆上分配的,动态内存的生存期由程序员决定。
/*动态存储分配函数*/
void *malloc(unsigned size)//动态存储分配函数
//功能:在内存的动态存储区中分配一连续空间,其长度为size。申请成功,则返回指向所分配内存空间的起始地址指针;失败,则返回NULL。
void *calloc(unsigned n,unsigned size)//计数动态储存分配函数
//功能:在内存的动态存储区中分配n个连续空间,每一个存储空间的长度为size,分配后把存储块全部初始化为0。申请成功,则返回指向所分配内存空间的起始地址指针;失败,则返回NULL。
void free(void *ptr)//动态存储释放函数
//功能:释放由动态存储分配函数申请到的整块内存空间。
void *realloc(void *ptr.unsigned size)//分配调整函数
//功能:更改以前的存储分配。
/*动态分配n个整型大小的空间*/
if((p=(int *)malloc(n*sizeof(int)))==NULL)
{
printf("NOT able to allocate memory
");
exix(1);
}
1.5 指针数组及其应用
-
1.定义
-
一般形式
类型名 *数组名[数组长度];
-
指针数组中的每个元素都是指针类型,用于存放内存地址。
-
-
2 多个字符串用二维数组表示和用指针数组表示区别
-
二维字符数组一旦定义,那么每个字符串的最大长度、首地址都不能改变了
-
字符指针数组是存放字符指针的数组。由于它仅用来存放指针,所以它指向的每个字符串的首地址可以改变,字符串最大长度也可以改变。
-
-
举例:
char str[5][5]={"abc","abcd","aaaa","ad","k"};
str[0]到str[4]五个字符串的最大长度被限为(5-1)=4,注意处理字符串时不应溢出。由于每个字符串的地址已经确定,所以str[0]="news";是不允许的
而
char* str[5];
str[0]="Welcome!";
可以
1.6 二级指针
二级指针的概念
一般形式:类型名 **变量名;
若定义整型变量a=10,*p=&a,**pp=&p
,则*p==**pp==a
以此类推三级、四级……
1.7 行指针、列指针
使用行指针或者列指针可以指定到字符串中的某个字符。
行指针:一般形式:类型名 (*变量名)[数组长度]
列指针:一般形式:类型名 [数组长度](*变量名)
使用例:
char color[5][10] = { "red","blue","yellow","green","black" };
char (*pc)[10];
pc = color;
printf("%s
", *(pc + 1));
printf("%c
",*(*(pc+1)+2));
结果:
blue
u
2.PTA实验作业
2.1 删除字符串中的子串(https://pintia.cn/problem-sets/1333954783207010304/problems/1333954910390890510)
2.1.1 伪代码
定义字符数组s[1000]、t[1000]、q[1000]
定义字符变量x
输入字符串s
输入字符串t
定义整形变量lens=s的字符串长度
定义整形变量lent=t的字符串长度
定义字符指针p
while(p=strstr(s,t))//找到子串首字符在字符串中的地址
do while(*(p+lent)!=' ')//字符前移
do 令字符前移至遍历自子串首字符位置之后的字符串到结束符
end while
删除已前移的多余字符
end while
输出字符串s
2.1.2 代码截图
2.1.3同学代码
在函数的调用上,该同学利用了strcat函数,直接完成了在删除子串后的后续字符串的前移,十分的简便,运行耗时也很少。
反观我的代码,我利用的是每次单个字符的前移,这大大的增加了计算机的运算量,而且代码可读性不高(我看自己的是这样的)
2.2 合并2个有序数组(https://pintia.cn/problem-sets/1333954783207010304/problems/1333954910390890504)
2.2.1 伪代码
merge(指针a, 整型变量m, 指针b, 整型变量n)/*用于归纳并排序的函数*/
定义整型变量i, j, k, temp均为0
定义整型指针p = a
while i<m&&j<n
if *a>=*b
then a[k]=*b
b++
j++
k++
else a[k]=*p
p++
i++
k++
end if
end while
if i<m
while k<=m+n-1
do a[k]=*p
p++
k++
end while
end if
if j<n
while k<=n+m-1
then a[k]=*b
b++
k++
end while
end if
for i=1 to m+n-1
do for j=i+1 to m+n-1
do if a[i]>a[j]
then temp=a[i]
a[i]=a[j]
a[j]=temp
end if
end for
end for
printArray(指针arr, 整型变量arr_size)/*用于输出的函数*/
定义整型变量i
for i=0 to arr_size-1
if i=0
then 输出arr[i](不带空格)
else 输出 arr[i](带空格)
end if
end for
2.2.2 代码截图
2.2.3同学代码
同学的代码利用另一个指针来存储两个数组,在没有归到一起之前就进行了排序,并且利用了动态内存分配,防止溢出。但二级指针在这里定义之后没有任何意义。
我则是先将两个数组放到一起再进行排序,而且整合的过程十分复杂,导致变得十分繁琐。(脑死状态下写的果然都没法看)
更改后
2.3 说反话-加强版(https://pintia.cn/problem-sets/1333954783207010304/problems/1333954910390890509)
2.3.1 伪代码
定义字符数组a
定义指针p、q分别用来存储字符串a的首地址和尾地址
定义整型变量flag用来判断输出条件
输入字符串a
令p的指针指向a的首字符
while(*p!='
'&&*p!=' ')
do p++
end while
令q的指针指向a字符串尾字母
while(q!=a)
do if (*q==' ')
then if (* (q + 1) != ' ' && *(q + 1) != ' ')
then if (flag==1)
then 输出q+1后的字符串
令flag=0
else 输出q后的字符串
end if
*q=' '
end if
end if
q--
end while
if (*a!=' ')
then if (flag == 1)
then 输出q + 1后的字符串
令flag = 0
else 输出q后的字符串
end if
end if
2.3.2 代码截图
2.3.3和超星视频做法区别
超星视频里的做法用了一种没见过的输出方式,即printf("%.*s,len,p")
,可以从某个地址开始子串,但具体用法还是不是很熟悉。
视频中考虑了空格的输出,而我的方式则不用再在输出中加空格,可以直接利用句子中的空格。除了这里不大相同以外其他地方基本相同。
我借鉴了视频中的逆向输出的方法,代码量减少了很多,这是我需要学习的地方。
※知识点
逆向扫描字符:while(p!=beginPtr) {p--}
字符串指针表示字符串:printf("%.s",len,str);
利用不同的指针指向所需的地址效率更高。