这个作业属于哪个班级 | C语言--网络2011/2012 |
---|---|
这个作业的地址 | C博客作业05-指针 |
这个作业的目标 | 学习指针相关内容 |
姓名 | 陈宇杭 |
0.展示PTA总分
1.本章学习总结
1.1 指针定义、指针相关运算、指针做函数参数
- \指针定义
众所周知,C语言中存在着许多变量,他们大多都被赋上了一个数值并存放在内存中;而指针,也是一个变量,只是他所赋上的值是(某一个)变量的地址;
指针类型变量在使用前需要声明类型,如:char *p;//声明一个字符型指针变量
此时的指针 (*)p
并未赋值,为野指针;未防止程序报错,在声明一个指针型变量时最好给其赋上 NULL
,即为 0 ,使其成为一个空指针;
-
\指针相关运算
-
地址运算
指针虽然也是一个变量,但是有关他的运算比较特殊;
指针所赋的值是一个地址,在进行赋值时,需要在变量前加上取地址运算符,如p = &a//此时指针p指向a的地址
;
在进行自增(p++)运算时,是将指针p所指向的地址向后移动一位;在进行自减(p--)运算时,是将指针p所指向的地址向前移动一位;
而两个指针变量无法进行相加(+)运算,但可以进行相减(-)运算,所得差值为两个指针的距离; -
数值运算
而指针也可以进行取所指向地址上的值来进行运算,并且可以修改所指向地址上的值;
取地址上的值需要一元运算符*
,如p = &a
;*p = *p + 1
等价于a = a + 1
-
小贴士
若指针所赋的值是一个数组,那么指针所指的位置默认是指向此数组的头地址;
但需要注意的是*p++
与(*p)++
所表示的意思并不相同;
自增符(++)与指针(*)的优先度相同,而单目运算符是从右向左进行判定;
所以*p++
是p所指向的下一位的地址的值;而(*p)++
是p所指向的值加一;
-
-
\指针做函数参数
指针变量也同样可以作为函数的参数,传入的是指针所指的地址;
void merge(int* a, int m, int* b, int n);//此时传入函数的是对应的地址
因为传入的数据是地址,所以在函数中对此地址上的数值的修改是可以保存的;
同时在多次调用函数时,使用指针传参通常比直接传入数值参数更加节省性能;
1.2 字符指针
字符串即是含有结束符的字符数组,在赋值给字符型指针时,赋给的是字符串的首地址;
指针进行自增(++)运算时,每自增一次,指针就指向原先的地址的下一位地址;
在C 标准库中,<string.h>库中贴心地包含有关于字符串的相关函数;
序号 | 函数声明 | 函数功能 |
---|---|---|
1 | void *memchr(const void *str, int c, size_t n) | 在参数 str 所指向的字符串的前 n 个字节中搜索第一次出现字符 c(一个无符号字符)的位置。 |
2 | int memcmp(const void *str1, const void *str2, size_t n) | 把 str1 和 str2 的前 n 个字节进行比较。 |
3 | void *memcpy(void *dest, const void *src, size_t n) | 从 src 复制 n 个字符到 dest。 |
4 | void *memmove(void *dest, const void *src, size_t n) | 另一个用于从 src 复制 n 个字符到 dest 的函数。 |
5 | void *memset(void *str, int c, size_t n) | 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。 |
6 | char *strcat(char *dest, const char *src) | 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。 |
7 | char *strncat(char *dest, const char *src, size_t n) | 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。 |
8 | char *strchr(const char *str, int c) | 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。 |
9 | int strcmp(const char *str1, const char *str2) | 把 str1 所指向的字符串和 str2 所指向的字符串进行比较,返回差值。 |
10 | int strncmp(const char *str1, const char *str2, size_t n) | 把 str1 和 str2 进行比较,最多比较前 n 个字节,返回差值。 |
11 | int strcoll(const char *str1, const char *str2) | 把 str1 和 str2 进行比较,结果取决于 LC_COLLATE 的位置设置。 |
12 | char *strcpy(char *dest, const char *src) | 把 src 所指向的字符串复制到 dest。 |
13 | char *strncpy(char *dest, const char *src, size_t n) | 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。 |
14 | size_t strcspn(const char *str1, const char *str2) | 检索字符串 str1 开头连续有几个字符都不含字符串 str2 中的字符。 |
15 | char *strerror(int errnum) | 从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。 |
16 | size_t strlen(const char *str) | 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。 |
17 | char *strpbrk(const char *str1, const char *str2) | 检索字符串 str1 中第一个匹配字符串 str2 中字符的字符,不包含空结束字符,当被检验字符在字符串 str2 中也包含时,则停止检验,并返回该字符位置。 |
18 | char *strrchr(const char *str, int c) | 在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置。 |
19 | size_t strspn(const char *str1, const char *str2) | 检索字符串 str1 中第一个不在字符串 str2 中出现的字符下标。 |
20 | char *strstr(const char *haystack, const char *needle) | 在字符串 haystack 中查找第一次出现字符串 needle(不包含空结束字符)的位置。 |
21 | char *strtok(char *str, const char *delim) | 分解字符串 str 为一组字符串,delim 为分隔符。 |
22 | size_t strxfrm(char *dest, const char *src, size_t n) | 根据程序当前的区域选项中的 LC_COLLATE 来转换字符串 src 的前 n 个字符,并把它们放置在字符串 dest 中。 |
1.3 指针做函数返回值
在声明函数时,将指针作为函数的返回值声明;
char *search( char *s, char *t );
此时函数的返回值是对应声明类型的指针,需注意返回类型;
同时在函数中若出现不需要返回指针的情况,需要返回一个空指针代替,如return NULL;
;
1.4 动态内存分配
在处理超多的数据时,直接定义数组来接收数据通常会因为栈溢出而报错,栈区的空间是有限的,此时就需要进行动态内存分配,在空间更大的堆区进行内存申请存放;
- 定义
栈区(stack): 函数运行时分配,函数结束时释放。由编译器自动分配释放,存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
堆区(heap): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)回收。分配方式类似于链表。
- 动态内存相关函数
动态内存申请需要用到calloc();
函数或malloc();
函数; calloc()函数在申请时顺便将内存初始化,而malloc()函数则是直接申请,并未初始化;
在申请内存时,需要将返回的指针进行强制类型转换,才能将地址赋值到对应指针;
- 示例
若已知所需要开辟动态内存大小,即可直接使用malloc()
函数;如char* str = (char*)malloc(500002);//申请了500002个字符型变量内存并赋予指针str
若要为多个未知长度的字符串进行动态内存申请,则可通过strlen()
函数来记录长度后再进行动态申请;
char s[1000];
while(i++ <= n)
{
fgets(s,1000,stdio);
int length = strlen(s);
char *str = (char *)malloc(length + 1);
strcpy(str,s);
......(处理字符串);
free(str);
}
在动态内存申请中,所申请下来的内存是属于堆区的,而这里的内存不像栈区不会自动释放,而是通过程序员手动释放;
如果不及时释放内存,在内存中会出现许多内存碎片,而对于程序员来说,每一点内存都是宝贵的;
在所申请的空间结束使用之后,需通过free(*)
函数来释放堆区的内存;
1.5 指针数组及其应用
如同其他变量,指针变量也可以声明指针数组,声明方式如:
char *p[10];
,每一个数组元素都是一个指针,在内存中也是按序排列;
二维字符数组表示字符串,申请时在栈区申请,无法处理过多的数据;每一行的长度都相同,可能会产生内存的浪费;
char str[10][50]
;
用指针数组表示字符串,申请时可以通过动态内存申请在堆区申请内存,可以处理大量数据,同时可以确定申请内存大小,不容易产生浪费;
char *sp[10]
;;指针数组比较适合用来指向若干个字符串,使字符串处理更加方便、灵活。
1.6 二级指针
任何值都有地址 ,一级指针的值虽然是地址,但这个地址做为一个值亦需要空间来存放,是空间就具有地址 ,这就是存放地址这一值的空间所具有的地址,二级指针就是为了获取这个地址。
char *p = (char*)malloc(100);
char **sp = &p;
free(p);
二维数组的首地址也是二级指针;
具体用法类比指针数组;
1.7 行指针、列指针
-
行指针
- 形式
int (*p)[n];
- 含义
p为指向含有n个元素的一维数组的指针变量。同时p也为二级指针
此时指针是行地址性质的指针;对标指针数组;
- 形式
-
列指针
- 形式
int *p; p = a[0];
- 含义
列指针为指向某一列数组的指针;
*(p + i)
,表示在列上离a[0][0]第 i 个位置的元素
- 形式
2.PTA实验作业
2.1 删除字符串中的子串
2.1.1 伪代码
Begin
get 子串1(S1);
get 子串2(S2);
检索子串1
if 有检索到存在子串2
删除子串2(从该地址开始,后面的元素前移);
back 检索子串1;
else
输出子串1;
end if
End
2.1.2 代码截图
2.1.3 余智康同学的代码
代码通过手动str检索字符串,巧妙地通过while()
函数与flag变量来进行遍历次数的控制,确保了如果删除合并后组合的字符串中不会又出现子串;
反观我的代码,通过多次调用函数来进行遍历次数控制,占用内存较大,失败;重复内容较多,失败;不知道该说什么,总之失败中的失败;
2.2 合并2个有序数组
2.2.1 伪代码
函数(子串a, 子串a长度m, 子串b, 子串b长度n ) Begin//正向重组数据,会覆盖原数据导致错误,所以逆向重组;
int t = m + n - 1;//定位到子串a的尾部
while(a或b到达子串头)
比较原子串a与子串b最后一位数值;
将较大的数据赋给子串a尾部,并将对应下标前移;
子串a尾部前移;
end while
if a先到子串头
子串b的剩余值赋给子串a;
end if
End
2.2.2 代码截图
2.2.3 余智康同学的代码
代码大体相同,在遍历到一个子串头时,继续通过判断来进行赋值;
我是在子串a判断到头的时候跳出,再通过遍历把子串b剩余的数值赋给子串a;
2.3 说反话-加强版
2.3.1 伪代码
Begin
get 一长串字符str;
void Reverse(当前地址)//检索子串
找到单词并定位
移动到单词末尾
Reverse(当前地址)//back检索子串
输出单词//通过递归逆向输出单词
end Reverse()//结束递归
End
2.3.2 代码截图
2.3.3 与超星视频做法区别,各自优缺点。
超星视频中是通过逆向扫描数组并按序输出单词;遍历数组并直接输出,内存占用率小,方便快捷;
我的做法是正向扫描数组并通过递归函数逆向输出;内存占用率大,在处理大量数据时容易溢出与超时,并不理智;