C博客作业05-指针

| 这个作业属于哪个班级 | 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 与超星视频做法区别,各自优缺点。

超星视频中是通过逆向扫描数组并按序输出单词;遍历数组并直接输出,内存占用率小,方便快捷;

我的做法是正向扫描数组并通过递归函数逆向输出内存占用率大,在处理大量数据时容易溢出与超时,并不理智;

posted @ 2020-12-27 22:48  DuckSword  阅读(155)  评论(0编辑  收藏  举报