. | 这个作业属于哪个班级 | C语言--网络2011/2012 |
---|---|---|
这个作业的地址 | C博客作业05--指针 | |
这个作业的目标 | 学习指针相关内容 | |
姓名 | 章审 |
0.展示PTA总分
1.本章学习总结
1.1 指针定义、指针相关运算、指针做函数参数。
-
地址和指针
在C语言中,指针被认为是计算及内存地址的代名词之一,指针变量本身就是变量,本身有一个地址,不同的是指针的内容是地址,即存放的是地址。指针中有两个重要的符号,即*和&,其中 *号第一次使用的时候,是用来定义一个指针变量,第一次使用后,再次使用,则表示这个指针的内容,也就是某个地址,而&号则是表示取地址的意思,在scanf()函数里,则是我们对&号的第一次接触,就是取地址的意思。 -
指针的定义
如果在程序中声明一个变量并使用地址作为该变量的值,那么这个变量就是指针变量。定义指针变量的一般形式为:
类型名 指针变量名;
类型名指定指针变量所指向变量的类型,必须是有效的数据类型,如int,float,char等。指针变量名是指针变量的名称,必须是一个合法的标识符。定义指针变量要使用指针声明符""。
指针声明符在定义指针变量时被使用,说明被定义的那个变量是指针。
指针变量本身就是变量,和一般变量不同的是它存放的是地址。指针变量用于存放变量的地址,由于不同类型的变瓧在内存中占用不同大小的存储单元,所以只知道内存地址,还不能确定该地址上的对象。因此在定义指针变量时,除了指针变量名,还需要说明该指针变量所指向的内存空间上所存放数据的类型。
定义多个指针变量时,每一个指针变量前面都必须加上*。
注意,指针变量的类型不是指指针变量本身的类型,而是指它所指向的变量的数据类型。指针变量自身所占的内存空间大小和它所指向的变量数据类型无关,不同类型指针变量所占的内存空间大小都是相同的。指针变量被定义后,指针变量也要先赋值再使用,当然指针变量被赋的值应该是地址。 -
指针的运算
C语言中的变量在引用前必须先定义并赋值.指针变量在定义后也要先赋值再引用。在定义指针变量时,可以同时对它赋初值。例如:
int a;
int p1 = &a;
int p2 = p1;
以上对指针pl和p2的赋值都是在定义时进行的,使得指针pl和p2都指向变量a。
再如表达式p=p+1、++p和(p)++,都是将指针p所指向变量的值加1。
而表达式p++等价于(p++),先取*p的值作为表达是的值,再将指针p的值加1,运算后,p不再指向变量a。
如果p和q是指向数组元素的指针,那么p-q产生一个int型的值,该值表示在p和q之间的数组元素的个数。同样,指针每一次加一减一,并非指针的值加一或减一,而是加上或减去该指针所指向的那个变量数据类型的长度,即它所指向的存储单元所指向的字节数。
- 指针做函数参数
函数参数也可以是指针类型,如果将某个变量的地址作为函数的实参,相应的形参就是指针。
在C语言中实参和形参之间的数据传递是单向的“值传递”方式,调用函数不能改变实参变量的值。如果传递的值是指针变量,调用函数时可以改变实参指针变量所指向的变量的值。此时需要在函数定义时将指针作为函数的形参,在函数调用时把变量的地址作为实参。
要通过函数调用来改变主调函数中某个变量的值,可以把指针作为函数的参数。在之前的学习中,我们知道了函数只能通过return语句返回一个值。如果希望函数调用能将多个计算结果带回主调函数,用return语句是无法实现的,而将指针作为函数的参数就能使函数返回多个值。
1.2 字符指针
字符串常量是用一对双引号括起来的字符序列,字符串常量在内存中的存放位置由系统自动安排。由于字符串常量是一串字符,通常被看作一个特殊的一维字符数组,字符串常量中的所有字符在内存中连续存放。所以,系统在存储一个字符串常量时,先给定一个起始地址,从该地址指定的存储单元开始,连续存放该字符串中的字符。字符串常量实质上是一个指向该字符串首字符的指针常量。例如,字符串"hello"的值是一个地址,从它指定的存储单元开始连续存放该字符串的6个字符。如果定义一个字符指针接收字符串常虽的值,该指针就指向字符串的首字符。
字符数组与字符指针都可以处理字符串,但两者之间有重要区别。例如:
char sa[] = "This is a string";
char* sp = "This is a string";
字符数组a在内存中占用了一块连续的单元,有确定的地址,每个数组元素放字符串的一个字符。字符指针p只占用一个可以存放地址的内存单元,存储字符串首字符的地址。如果要改变数组a所代表的字符串,只能改变数组元素的内容。如果要改变指针p所代表的字符串,通常直接改变指针的值,让它指向新的字符串。因为p是指针变量,它的值可以改变,转而指向其他单元。
为了避免引用未赋值的指针所造成的危害,在定义指针时,可先将它的初值赋为空,如
char*s=NULL;
1.3 指针做函数返回值
在C语言中,函数返回值也可以是指针类型,不过,我们要注意,不能在实现函数时返回在函数内部定义的局部数据对象的地址,这是因为所有的局部数据对象在函数返回时就会消亡,其值不再有效。因此,返回指针的函数一般都返回全局数据对象或主凋函数中教据对象的地址,不能返回在函数内部定义的局部数据对象的地址。
例如
char* getmonth(int n)
{
static char a[12][15] =
{
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
switch (n)
{
case 1:return &a[0][0];
case 2:return &a[1][0];
case 3:return &a[2][0];
case 4:return &a[3][0];
case 5:return &a[4][0];
case 6:return &a[5][0];
case 7:return &a[6][0];
case 8:return &a[7][0];
case 9:return &a[8][0];
case 10:return &a[9][0];
case 11:return &a[10][0];
case 12:return &a[11][0];
default:p = NULL;
}
return p;
}
1.4 动态内存分配
在c语言中主要用两种方法使用内存:一种是由编译系统分配的内存区;另一种是用动态分配方式,留给程序动态分配的。动态分配的储存区在用户的·程序之外,不是由编译系统分配的,而是由用户在程序中通过动态分配获取的。使用动态分配能有效地使用内存,同一段内存区域可以被多次使用,使用时申请,用完就释放。
- 动态内存分配的步骤
(1)了解需要多少内存空间。
(2)利用c语言提供的动态分配函数来分配所需要的储存空间。
(3)使指针指向获得的内存空间,以便指针在该空间内实施运算或操作。
(4)当使用完毕内存后,释放这一空间。 - 动态内存分配函数
在进行动态存贮分配的操作中,c语言提供了一组标准函数,定义在stdlib.h里面。
(1)动态存储分配函数malloc()
函数原型是:
void * malloc (unsigned size)
功能:在内存的动态存储区中分配一连续空间,其长度为size。若申请成功,则返回指向所分配内存空间的起始地址的指针;若申请内存空间不成功,则返回NULL(值为零)。malloc的返回值为(void*)类型(这是通用指针的一个重要途径)。在具体使用中,将malloc()的返回值转换为特定指针类型,赋给一个指针。调用malloc()时,应该利用sizeof计算储存块大小,不要直接写数值,因为不同平台数据类型占用空间大小可能不同。此外,每次动态分配都必须检查是否成功,考虑到意外情况的处理。
(2)计数动态储存分配函数calloc()
函数原型是:
void *calloc(unsigned n, unsigned size)
功能:在内存的动态存储区中分配n个连续空间,每一个存储空间的长度为size,并且分配后还把储存块里全部初始化为0.若申请成功,则返回指向所分配内存空间的起始地址的指针;若申请内存空间不成功,则返回NULL(值为零)。
(3)动态存储释放函数free()
函数原型是:
void free(void *prt)
功能:释放由动态储存分配函数申请到的整块内存空间,ptr为指向要释放空间的首地址。如果ptr的值是空指针,则free什么都不做。该函数无返回值。
为了保证动态储存区的有效利用,在知道某个动态分配的储存块不再用时,就应该及时将它释放。
1.5 指针数组及其应用
一般而言,如果i是int型变量,那么p+i就是距地址p的第i个偏移,类似的,a+i是距数组a的基地址的第i个偏移,*(a+i)与a[i]等价。虽然在很多方面数组和指针都能处理同样的问题,但他们之间有一个本质的不同。数组a是指针常量,不是变量,所以像a=p、a++和a+=2这样的表达式都是非法的,不能改变指针常量a的值。
1.6 二级指针
在c语言中,指向指针的指针一般定义为:
类型名**变量名;
也称为二级指针。例如
int a = 10;
int *p = &a;
int **pp = &p;
定义了3个变量a、p和pp并初始化。一级指针p指向整型变量a,二级指针指向一级指针p。
**pp=*p=a *pp=p=&a pp=&p;
1.7 行指针、列指针
假设有如下定义:
int a[3][4];
二维数组的指针形式:可以把二维数组a看成是由a[0]、a[1]、a[2]组成的一维数组,而a[0]、a[1]、a[2]各自又是一个一维数组。也即二维数组是数组元素为一维数组的一维数组。由此,数组名a就是a[0]的地址。也即二维数组是数组元素为一维数组的一维数组。由此,数组名a就是a[0]的地址,即二维数组是数组元素为一维数组的一维数组。由此,&a[0]与&&a[0][0]等价。所以,二维数组名a是一个二级指针,而a[0]是一级指针。以此类推,a+1是第1行首元素的地址,(a+1)第1行首元素的值;a+i是第一行的地址,*(a+i)是第i行首元素的地址,(a+i)第i行首元素的值。
(1)虽然a、a的值相同,但含义不同。a是行元素数组的首地址,又称为行地址,是二级指针,而a是首行第一个元素的地址,又称为列指针,是一级地址。
(2)由于有a[i]等价于(a+i)的关系,因此既可以用下标表示法,也可以用指针表示法,或者是混合使用。例如a[a][j]等价于((a+j)+J),也可以写成(a[i]+j)。
2.PTA实验作业
2.1删除字符串中的子串
2.1.1 伪代码
char str1[90], str2[90];//定义主串和子串
int i, j, k, l;//定义循环控制变量
int m, n;//定义主串和子串的长度
fgets(str1, 90, stdin);//输入主串
fgets(str2, 90, stdin);//输入子串
m = strlen(str1);//给主串长度赋值
n = strlen(str2);//给子串长度赋值
for (int a = 1; a <= 10; a++)//强制循环,防止最后一个子串没有删除
{
for (i = 0; str1[i] && str1[i] != '
'; i++)//遍历主串
{
for (j = i, k = 0; str1[j] && str1[j] != '
' && str1[j] == str2[k]; j++, k++)//判断主串中是否有字符和子串的首字符相等
{
if (str2[k + 1] == ' ' || str2[k + 1] == '
')//判断子串是否读完
{
for (l = 0; str1[i + n + l - 1]; l++)//替换,因为最后是换行符,所以要减一
{
str1[i + l] = str1[i + l + n - 1];
}
str1[i + l] = ' ';//赋结束符
}
}
}
}
printf("%s", str1);//打印
2.1.2 代码截图
2.1.3 找一份同学代码(尽量找思路和自己差距较大同学代码)比较,说明各自代码特点。
这是林近源的做法,他运用了数组指针,将处理主串的操作封装成一个函数,相比我来说就更能实现对指针做法的练习。
2.2合并2个有序数组
2.2.1 伪代码
void merge(int* a, int m, int* b, int n)
{
int i = m - 1;
int j = n - 1;//定义控制变量
int k = n + m - 1;//计算合并后的长度
while (j >= 0 && i >= 0)
{//判断是否其中一个有序数组合并完毕
if (a[i] >= b[j])
{//判断两个数组的大小
a[k--] = a[i--];
}//如果a大,将数a[i]放到a[k]中,k和i都自减;
else
{
a[k--] = b[j--];
}//如果b大,将数b[j]放到a[k]中,k和i都自减;
}
while (j >= 0)
{//如果a数组先被赋完
a[k--] = b[j--];
}//将剩余的b数赋值到a中
}
2.2.2 代码截图
2.2.3 找一份同学代码(尽量找思路和自己差距较大同学代码)比较,说明各自代码特点。
这是学长的代码,他重新定义了三个数组去分别存储a数组,b数组和ab合并后的数组,并且存储时与我的顺序相反,新数组存储后将数组重新赋值给a数组;
虽然步骤比我的代码繁琐,但是比我的更加容易理解
2.3说反话-加强版
2.3.1 伪代码
#include<stdio.h>
#define MAX 500000
int main()
{
char s;//指单独一个字符
char t[MAX];//创建一个字符数组
int i = 0, count = 0, flag = 0;
while ((s = getchar()) != '
')
{//getchar每次从标准输入读入一个字符
if (s != ' ')
{
flag = 1; //标记遇到单词
t[i++] = s;
count = 0;
}
else if (count > 0)
{
continue;//contiue跳过
}
else if (flag)
{
t[i++] = s; //只有之前遇到单词的情况下碰到空格才把这个空格写入目标字符串
count++;//这里换成count=1也是完全一样
}
} //删除多余的空格,将目标字符串放入 t 中
count = 0;
int j;
for (i -= 1; i >= 0; i--)
{//i-=1.i=i-1,最后一个标号为i里面是没存东西的
if (t[i] != ' ')
{
count++; // 这里的 count 统计的是一个单词里字母的个数
}
else if (t[i] == ' ' && count > 0)
{
for (j = i + 1; j <= i + count; j++)
{
printf("%c", t[j]);
} //遇到空格就输出单词
printf(" ");
count = 0;
}
} // 还剩最后一个单词没输出,因为最后一个单词可能前方无空格 ,只完成了count++,但是没有遇到 空格,那么逻辑是一样的
for (j = i + 1; j <= i + count; j++)
{
printf("%c", t[j]);
}
return 0;
}
2.3.2 代码截图
2.3.3 请说明和超星视频做法区别,各自优缺点。
超新运用了一个新的知识点, printf(“%. *s”,len,str),可以使字符串按照指定的长度输出,不再额外需要count计数,使代码更为简洁,此外,运用了指针,同时用了fgets,在输入时没有删除多余空格,因为他直接在处理时用 p!=‘ ’&&(p-1)==‘ ’去判断是否为单词的首字母,代码更加简洁。