C博客作业05--2019-指针

0.展示PTA总分

1.本章学习知识总结

1.1 学习内容总结

  • 指针概念

指针就是指向某一个地址的内容 指针变量就是存放地址的变量
例如:

地址 内容 变量名
1001 10 a
1002 1 b
1003 45 c

若指针p指向a则有

地址 内容 变量名
2001 1001 p
2002 1002 p+1
2003 1003 p+2
  • 指针的定义与赋值
    • 先定义指针的类型如char、int、float、double,然后在变量名前加一个指针标识符* 完整的表示 如定义一个字符串指针 char *strP; (命名时推荐带上P或者Ptr)
    • 若定义多个指针变量 每个变量名前面都要+* 如定义多个字符串指针 char *str1P,*str2P;
    • 在进行指针的赋值时 只能传给相同类型的指针变量 赋值char str[6] = "happy"; char *p=str; 初始化int a,*p;p=&a;*p=0;
      要注意的是,进行初始化的时候 一定要先让指针变量指向一个变量 再进行赋初值,否则指针变量会指向一个未知地址 导致程序崩溃
    • 不能用数值作为指针变量的初值,但可以将一个指针变量初始化为一个空指针

int *p; p = 0; p = NULL;

int    *ip;    //一个整型的指针
double *dp;    //一个 double 型的指针
float  *fp;    //一个浮点型的指针
char   *ch;     // 一个字符型的指针
  • 指针的运算

    • p++*p++(*p)++ 的区别:
      p++:是移动到下一个地址 其等价于 *p++ 其中这里的*p++是由于++的自增运算符优先级要低于指针标识符才会等价p++
      它同时也等价于 *(p+1)
      (*p)++:是将 p 所指向的变量值加1
  • 指针作为函数参数

    • 定义一个函数 int exam(char *p)
    • 定义一个变量 char str[N]=0;
    • 调用 exam(&str);
      注意:通过指针可以实现函数调用返回多个值

例如:当这个应用于对三个数的交换时

void swap(int *a,int *b);//函数声明
int main()
{
  int a=4,b=5;
   swap(&a,&b);
}
void swap(int *x,int *y)
{
   int temp;
   temp=*x;
   *x=*y;
   *y=temp;
}

因为这里传的是地址 所以可以函数里面的参数可以影响主函数
成功交换✔

  • 指针与数组
    • 首先要明白数组名实际上代表着一个地址,它的值是数组元素的首地址
    • 所以数组就是一个指针
      假设 p指向a数组
      表格展示:
地址 内容 数组元素
p 3000 a[0]
p+1 3002 a[1]
p+i …… a[i]
p+99 3198 a[99]
for(i=0;i<n;i++)
    printf("%d",a[i]);

可改写为

for(i=0;i<n;i++)
    printf("%d",*(p+i));

指针加1 实际上就是下移一个单位

  • 关于const

    • const 是在定义变量时 加在定义类型前面的 一旦加上了const 说明这个定义的变量就不能再被更改了
    • 一般不推荐使用 const 因为容易出错 不方便
  • 字符指针如何表示字符串

    • 先定义一个字符数组
    • 在定义一个字符指针 让 字符指针指向这个字符数组
    • 值得注意的是 指针只能指向 不能修改 要修改只能对字符数组进行修改
    • 字符串的赋值一般是使用strcpy 其头文件在string.h中
char str[]="happy";
char *strP=str;

也等价于

char str[]="happy";
const char *strP=str;
  • 指针数组
    • 就是一个数组里 所有的元素都是一个指针
    • 定义:类型名 *数组名[数组长度]
    • 指针数组可以灵活运用于字符串中
    • 和二维数组有一些相似的地方

例如:定义一个指针数组 char *color[5] = {"red", "blue", "yellow", "green", "black" };

  • 其中每一个元素都是一个字符指针

  • 指针数组和二维数组

    • 例如当用二维数组定义时
char month[13][20]={"0","January","February","March","April","May","June","August","September","October","November","December" }

由于定义二维数组必须定义列的长度,所以当输入的是字符串的时候 很容易造成越界的麻烦,所以这时候就要引入一个指针数组的概念

char month[]={"0","January","February","March","April","May","June","August","September","October","November","December" }

两个代码输出的结果是一样的

  • 指针做循环
    • 指针代表的是地址
    • 例如指针代表一个字符串的时候
static char str[N];
char *p;
fgest(str,N,stdin);
for(p=str;*p&&*p!='\n';*p++)

所以循环的终止条件 是字符串结束时 等于什么那么指针所指的便是什么

  • 字符串的排序和数组之间的区别

  • 指针动态分配内存

    • 计数动态存储分配函数 calloc()
    • 通过三个函数 malloc()函数 free()函数 realloc函数()
      这四个函数全都在头文件#include<stdlib.h>中
  1. 计数动态存储分配函数 calloc ()
    函数定义:
    void *calloc ( unsigned n, unsigned size)

在内存的动态存储区堆中分配n个连续空间 每一存储空间的 长度为size 并且分配后还把存储块里全部 初始化为0
p = (int *) calloc (n, sizeof(int))

  • 若申请成功,则返回一个指向被分配内存空间的起始地址的指针
  • 若申请内存空间不成功,则返回NULL
  1. 动态存储分配函数malloc()
    这个函数和calloc函数非常相似
    但是区别在于 calloc函数会对所有区域进行初始化0但是malloc不会对所分配的储存块进行任何操作
  • 它们分配内存后都可以对指针进行直接赋值。
    函数定义:
    void *malloc(unsigned size)
  • 若申请成功,则返回一个指向所分配内存空间的起始地址的指针p
  • 若申请内存空间不成功,则返回NULL(值为0)
  • 返回值类型:void (*),赋给指针要强制转换。
    p = (int *)malloc(n*sizeof(int))

注意:要与free结合使用 否则影响程序效率
同时注意不要越界使用,sizeof是运算符用来计算存储长度
这一点和calloc函数一样

3)动态存储释放函数 free()
当某个动态分配的存储块不再用时,要及时将它释放
函数定义:
这个比较简单 你申请了哪个 最后在return 0前面释放即可
例如:
void free (void *ptr)
free(p)

使用时申请:malloc,calloc
用完就释放:free

  • 二级指针、行指针
    这个我是真的不怎么能分得清楚
    • 二级指针
      它的定义首先就是 指向指针的指针 类型名 **变量名
      当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置

假设 a、p1、p2、p3 的地址分别是 0X00A0、0X1000、0X2000、0X3000,它们之间的关系可以用下图来描述:

此图来自方框里面是变量本身的值,方框下面是变量的地址。

  • 二级地址做*运算后还是地址
    例如:*(a+i)=a[i]:一级地址

  • a+ia[i],前者是二级地址,后者是一级地址
    一级地址*运算后就是内容
    例如:*a[i]=**(a+i)=a[i][0]

  • 行指针

  • 定义:int (*p)[n]

  • 行指针p是行地址性质的指针

  • 类似于二维数组
    第一行的首地址,是指首行一整行,并不是指某个具体元素 就是行指针
    例如:p+0,p+1,p+2,都是行指针 特指行

行指针可以和数组名互换使用
p[i][j]=a[i][j];

扩展:列指针

  • 行指针是指一整行,那么列指针就是指向行中的具体元素
    例如:p[0]+1,p[0],p[0]+2都是具体的元素

  • 指针做函数返回值及其注意

  • 指针做函数返回值

    • 返回的是一个地址
    • 一般形式:char *fun()
    • 与其他返回类型函数类似
      如果遇到要求输入下标:
      可以用后来得到的首地址减去一开始的首地址 得到这个下标
  • 注意:

    • 不能返回在函数中定义的局部对象的地址,因为在函数返回时这个地址就会自动消亡
    • 返回回指针的函数一般都返回全局数据对象指向字符串常量指针或堆区的指针主调函数中数据对象的地址

1.2 本章学习体会

  • 学习体会

用我的话来说就是 “乱!太乱了!我凌乱了!” 真的看着 指针,字符指针,指针数组,二级指针,二维数组,行指针,列指针,真的很乱!!而且让我惊讶的是指针不能直接被输入,还要调用strcpy,其次就是指针再利用scanf时,不需要调用&,因为本身就是一个地址,我把指针数组跟字符数组混乱起来了,导致我在刷PTA的编程题的时候遇到利用指针数组或者数组的题的时候我就一脸懵逼,都是要问同学或者助教,才能得出结果,而且也不知道是因为太蠢的原因还是啥,我再写题的时候大部分用的还是数组的知识,从侧面反映了我对指针的知识就是太模糊了,导致实践运用时,我不太敢去使用它!我还是希望能把它学好!!

  • 代码量
    因为14周线代考试一直在复习线代,代码量较少
周次 代码量
14周 645
15周 1254
合计 1899

2.PTA实验作业

2.1 题目:6-9 合并两个有序数组 函数题

要求实现一个函数merge,将元素个数为m的升序数组a和长度为n的升序数组b合并到数组a,合并后的数组仍然按升序排列。假设数组a的长度足够大。

函数接口定义:

void printArray(int* arr, int arr_size);  /* 打印数组,细节不表 */
void merge(int* a, int m, int* b, int n); /* 合并a和b到a */

其中a和b是按升序排列的数组,m为数组a中元素的个数,n为数组b的长度;合并后的升序数组仍然存放在a中。

裁判测试程序样例:

#include <stdio.h>
#include <stdlib.h>

void printArray(int* arr, int arr_size);  /* 打印数组,细节不表 */
void merge(int* a, int m, int* b, int n); /* 合并a和b到a */

int main(int argc, char const *argv[])
{
    int m, n, i;
    int *a, *b;

    scanf("%d %d", &m, &n);
    a = (int*)malloc((m + n) * sizeof(int));
    for (i = 0; i < m; i++) {
        scanf("%d", &a[i]);
    }

    b = (int*)malloc(n * sizeof(int));
    for (i = 0; i < n; i++) {
        scanf("%d", &b[i]);
    }
    merge(a, m, b, n);
    printArray(a, m + n);

    free(a); free(b);
    return 0;
}

/* 请在这里填写答案 */

输入样例:

输入包含三行。 第一行为两个整数m和n,分别为数组a和数组b中元素的个数。 第二行为包含m个整数的有序数组a。 第三行为包含n个整数的有序数组b。

7 11
1 2 14 25 33 73 84
5 6 17 27 68 68 74 79 80 85 87

输出样例:

输出为合并后按升序排列的数组。

1 2 5 6 14 17 25 27 33 68 68 73 74 79 80 84 85 87

2.1.1 伪代码

  • 思路:
void merge(int* a, int m, int* b, int n)
新建一个数组c
for i=j=k=0 to i或j有一方满m或n时 do //其中i,j,k分别为a数组下标,b数组下标,新建数组c下标
  if a[i]<b[j] 
     把a[i]放进新数组c中
     a和c的下标同时加一
  else 把b[j]放进新数组c中
     b和c的下标同时加一
   end if
end for
//当两个数组不一样长的时候 就把剩下的按顺序存入c数组中
while i<m do 把a数组剩下的放进c数组中 //a数组比较长
end while
while j<n do 把b数组剩下的放进c数组中 //b数组比较长
end while

2.1.2 代码截图


2.1.3 总结本题知识点

  • 重构数组的做法
    当遇到要对两个或者一些排序的做法时,可以新建立一个数组 然后存放
  • 利用指针作为参数 可以影响主函数 因此在本题中 在函数中把c的值赋给a 回到主函数输出时可以改变
  • 动态内存的分配 malloc()和free()
  • 注意,两个数组不一样长的情况

2.1.4 PTA提交列表及说明

  • 提交列表
  • 说明
    • Q1:部分正确 第一次我使用的是讲两个数组先合并起来放在a里 然后再进行排序 但是发现运行超时,老师在这道题上控制了时间
    • A1: 整体换思路 引入了重构数组的思路
    • Q2:部分正确 这里错误是我只考虑了两个数组相等的情况
    • A2: 所以在第一个循环结束之后 我设置了两个循环来保证多出来的数组剩下的元素能存放进c中
    • Q3:部分正确 题目要求是输出a数组,而我没有只对c数组进行了操作
    • A3: 在最后还需要再把c数组赋值给a数组才算圆满!
      这里编译错误是因为我把另一个函数也放了进去

2.2 题目:7-4 说反话-加强版 编程题

给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出。

输入格式:

测试输入包含一个测试用例,在一行内给出总长度不超过500 000的字符串。字符串由若干单词和若干空格组成,其中单词是由英文字母(大小写有区分)组成的字符串,单词之间用若干个空格分开。

输出格式:

每个测试用例的输出占一行,输出倒序后的句子,并且保证单词间只有1个空格。

输入样例:

Hello World   Here I Come

输出样例:

Come I Here World Hello

2.2.1 伪代码

  • 思路:
利用fgets输入str字符串 计算len的时候strlen多一个换行符-1
for i=len-1 to i>=0;i-- do //i时str的下标
  if str[i]不是空格
    先记录单词最后一个字母的位置last
    for j=i-1 to str[j]成为空格时
    end for
    //此时 下标为j+1的就是这个单词头的字母
    记录j+1这个下标head
    判断是否是第一个单词 不是则输出空格
    用count来判断
    for head to last do
      输出最后一个单词 以此类推
      count++;//判断格式
    end for
  end if
  i=j//从遇到空格那里 继续向前查询
end for 

2.2.2 代码截图

2.2.3 总结本题知识点

  • 由于这题我实在不会用指针来完成 所以我回顾了字符数组
    字符数组的逆序遍历
for i=长度-1 to i=0 do
     ~~~
end for
  • 这题非常需要注意的是 格式问题 ,所以一定要引入一个count来记数 否则无法控制格式
    这里的count的作用就仿佛是flag

  • 思路比较巧妙 采用了边判断边输出的方法

2.2.4 PTA提交列表及说明

  • 提交列表
  • 说明
    没错你没有看错,就是多种错误
    Q1: 格式错误,我直接就是逆序存放单词又逆序输出 考虑不了空格的情况
    A1: 这种错误真的只能换思路,所以我在想如果是遇到空格的的话 要怎么样才能跳过许多个空格呢 bigo!就是判断 进行空格判断
    Q2: 段错误,因为这题一开始我的思路是 逆序存放单词 然后逆序输出 不存放空格 所以数组越界了
    A2: 所以要解决这个问题 就得想想这个输出是不是有问题了,于是想到了 一个一个单词的输出! 记录头和尾巴即可

2.3 题目:7-3 字符串的冒泡排序 编程题

我们已经知道了将N个整数按从小到大排序的冒泡排序法。本题要求将此方法用于字符串序列,并对任意给定的K(<N),输出扫描完第K遍后的中间结果序列。

输入格式:

输入在第1行中给出NK(1≤K<N≤100),此后N行,每行包含一个长度不超过10的、仅由小写英文字母组成的非空字符串。

输出格式:

输出冒泡排序法扫描完第K遍后的中间结果序列,每行包含一个字符串。

输入样例:

6 2
best
cat
east
a
free
day

输出样例:

best
a
cat
day
east
free

此题是比较简单的,但是很多细节就需要注意

2.3.1 伪代码

  • 思路:
输入要求判断的字符数和遍数
输入变数后 getchar()吸收换行符
 建立二维数组str 然后当成行指针进行运用
 每行存放一个单词
 for i=1 to n-1 do
   for j=0 to n-i-1 do
     if(strcmp(str[j]>str[j+1])>0) //判断这相邻两行的大小
       交换 str[j]和str[j+1]两行的值
      end if
    end for
    if i=k //判断是不是k遍 因为题目只要求输出k遍的结果 而不是最后的结果
    输出
    end if
end for

2.3.2 代码截图

2.3.3 总结本题知识点

  • 指针数组的冒泡排序与数组是有区别的
    指针数组是
for i=1 to n-1 do
  for j=0 to n-i-1 do
      找最大值交换 相邻的
  end for
end for

而数字数组的排序是

for i=0 to n-2 do//值比较到n-2个 到n-1个数的时候就停止了
    for j=0 to n-i-2 do 
        找最小数min
        min与a[i]交换
     end for
end for
  • 还需要注意的是 如果使用scanf输入 然后后面需要输入字符 不能忘记使用getchar() 来吸收换行符

  • 这题还需要考虑他和普通数组不一样的是 要求的是k遍就要 每一遍的结束判断是不是要输出

  • 二维数组和行指针可以交换使用~

2.3.4 PTA提交列表及说明

  • 提交列表

    这题大部分死在细节
  • 说明
    Q1:段错误 一开始我是用的是行指针 但是在赋值的时候一直出错
    A1: 所以我改成了二维数组 因为二维数组可以当成行指针来使用 输入的时候赋值也不容易出错
    Q2:段错误 修改了之后 在比较的时候 我没有使用strcpy函数来进行赋值 我是直接进行交换的 导致出错
    A2: 应该要使用strcpy函数来对一个字符串进行赋值
    Q3:答案错误 我在进行冒泡排序的时候是使用的比较数字的数组的冒泡排序导致
    A3: 所以我又重新翻了一下资料再对比了课件 发现选择排序冒泡排序对于指针字符串的应用和对数字数组都不一样 所以进行了修改
    Q4:部分正确 经过上面修改了之后 我的k遍输入放在了冒泡排序的过程中 但我发现不能这样做 否则结果会是错的
    A4: 所以我决定在每一遍的最后 判断i是否等于k 然后输出 跳出循环
    也可能第四点有的小伙伴能做到,不过这样写,我觉得更明了一些啦

3. 阅读代码

  • 题目

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true

此题目来源于 力扣

  • 代码截图

    在众多解法中 我选择了一个C语言的解法
  • 代码功能及优点
    • 由题目可知 这是判断是否是合法括号的代码 它使用了bool这个函数类型
    • bool为布尔型用作逻辑判断,bool取值false和true,是0和1的区别;false可以代表0,但true有很多种,并非只有1
    • 利用这个函数类型 可以简化整个函数 优化了代码 更加简便的完成判断
    • 但我觉得有点不足的是 他为stack申请内存 但是却没有给它释放内存 这样会影响程序效率的
    • 直接在判断那里控制引用了一个top 然后最后再!top的时候 直接防止了中文字符的判断 又可以是作为返回值输出正确
    • 同时判断不满足条件的括号 然后返回错误
posted @ 2019-12-01 14:15  雪梨wink  阅读(760)  评论(0编辑  收藏  举报