C博客作业05--指针

| 这个作业属于哪个班级 |


| ---- | ---- | ---- |
| 这个作业的地址 |
| 这个作业的目标 | 学习指针相关内容 |
| 姓名 | 黄静 |

0.展示PTA总分

1.本章学习总结

1.1 基础知识

1.1.1 指针定义

指针可以直接进行物理操作,是c语言的特色之一。使用指针可以对复杂数据进行处理,能对计算机的内存分配进行控制,在函数调用中使用指针还可以返回多个值。

在程序中定义了一个变量 x 来存放密码,再定义一个特殊的指针变量 p ,用于存放变量 x 的地址,这样既可以通过变量名 x 直接得到数据,也可以通过指针变量 p 所存放 x 的地址间接得到数据。

//直接访问:
int x = 20,y = 1,z = 155;
printf("%d",x);
//间接访问:通过地址访问存放地址的变量
int *p = &x;//把 x 的地址赋给 p
printf("%d",*p);

指针变量:存放地址的变量

类型名 * 指针变量名      // * 是指针声明符
int *ptr;      //p是整型指针,指向整型变量
int *fptr;      //fp是浮点型指针,指向浮点型变量
char *cptr;      //cp是字符型指针,指向字符型变量
//指针变量的类型和它所指向变量的类型相同

1.1.2 指针变量初始化

//指针变量先定义,赋值必须是数组
int a;
int *p;
p = &a;
//在定义指针变量时,可以同时对它赋初值
int *p = &a;
//不能用数值作为指针变量的初值,但可以将一个指针变量初赋值为一个空指针
int *p;
p = 0;
p = NULL;
p = (int*)1732;//使用强制类型转换来避免编译错误,不提倡该种做法!!!

1.1.3 指针相关运算

指针的值:所指变量的地址(&是取地址运算符)
 & 表示取地址, * 表示取内容 
 &*p 与 &a 相同,是地址
 *&a 与 a 相同,是变量
 (*p)++ 等价于 a++  // 将 p 所指向的变量值加一
 *p++ 等价于 *(p++)  // 先取 *p ,然后 p 自加,此时指针 p 不再指向变量 a 
 q - p 指针 p 和 q 之间相隔的存储单元数目
 (int)q - (int)p 指针 p 和 q 之间的字节数 
 p + 1/p - 1 指向下一个/上一个存储单元
 p < q 相同类型指针可以用关系运算符比较大小
  • 指针相加,相乘和相除,加减浮点数等其他操作都是违法的
    注意点:
  • 指针变量类型不是指针变量本身类型
  • 指针变量类型要和指向变量类型一致
  • 占用内存空间与类型无关,不同类型指针变量所占内存空间大小一样
  • 指针一定要有指向,否则为野指针,会导致段错误
  • 相同类型的指针才可以相互赋值

1.1.4 指针做函数参数

在c语言中实参与形参之间的数据传输是单项的值传递,函数中形参无法影响实参,而return一次只能返回一个值,使用指针变量可以直接改变实参指针变量所指向的变量的值,也就是说明,传地址可以返回多个值。
优点:传地址,数据量少,直接对地址操作,可以改变多个变量值

形参:指针变量    int *p
实参:变量的地址或变量的指针,数组名        &a  某个指针

代码示例:
swap(&a, &b);//函数调用
void swap(int* px, int* py)//函数定义
{
   //使用指针交换a,b的值
	int t;      
	t = *px;
	*px = *py;
	*py = t;
}

1.1.5 指针做函数返回值

  • 优点:一次可改变和返回多个变量值
  • 示例:输入年和天数,输出对应的年,月,日(用两个指针作为参数,带回两个结果)
//函数调用
month_day(year, yearday, & month, & day)
//函数定义
void month_day(int year, int yearday, int* monthptr, int* dayptr)
{
	int k, leap;
	int tap[2][13] =
	{
		{0,31,28,31,30,31,30,31,31,30,31,30,31},//非闰年每月天数数据
		{0,31,29,31,30,31,30,31,31,30,31,30,31},//闰年每月天数数据
	};
	//判断是否闰年
	leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;//闰年为1,非闰年为0
	for (k = 1;yearday > tap[leap][k];k++)//剩余天数大于这一个月天数时,继续下一月
	{
		yearday = yearday - tap[leap][k];
	}
	*monthptr = k;
	*dayptr = yearday;
}

1.1.6 指针与数组

数组名代表一个地址,它的值是数组首元素 a[0] 的地址
a + i 是数组 a 首地址的第 i 个偏移量,指针加一,实际下移一个数据类型存储单元
&a[i] = a + i;
*a[i] = *(a + i);

提示:

  • 任何由数组下标来实现的操作都能够用指针来完成
指针做循环变量,务必了解初始和结束地址
示例:

int a[100];
int* p;
int n;
······
p = a;//或p=&a[0]
int sum = 0;
for (p = a;p < a + n;p++)//初始:首地址  结束:首地址+数组长度
{
	sum = sum + *p;
}

注意点:在循环等操作中,注意是否需要数组初始位置,一般要保存数组初始位置,可重新定义一个指针进行循环等操作

1.2 字符指针

//定义形式
char sa[] = "This is a string";
char *sp = sa;

//const定义的是只读变量,相当于常量,不允许给它重新赋值,且必须在定义的时候给它赋初值
//使用const约束,提高程序健壮性
const char* sp = "This is a string";

数组名sa,指针sp和字符串"string"的值都是地址

输出:printf("%s",地址);      //%s输出从指定地址开始,'\0'结束的字符串

1.3 字符串相关函数

常见函数:

函数名 函数定义格式 函数功能 返回值
strcat char* strcat(char* s, char* t) 把字符串t连接到s,使s成为包含s和t的字符串 字符串s
strcmp int strcmp(char* s, char* t) 逐个比较字符串s和t中的对应字符,直到对应字符不等或比较到串尾 相等:0 不等:不相等字符的差值
strcpy char* strcpy(char* s, char* t) 报字符串t复制到s中 字符串s
strlen unsigned int strlen(char* s) 计算字符串s的长度(不包括'\0') 字符串长度
strchr char* strchr(char* s, char c) 在字符串s中查找字符c首次出现的地址 找到:相应地址 找不到:NULL
strstr char* strstr(char* s, char* t) 在字符串s中查找字符串t首次出现的地址 找到:相应地址 找不到:NULL
注意事项:
  • 加入const提高程序健壮性
  • strcpy 和 strcat:源字符串要足够大,否则会溢出崩溃

strncpy函数

  • char* strncpy(char* dest,const char* src,size_t n)
  • 把src中的字符串复制到dest,最多复制n个字符,当src的长度小于n时,dest的剩余部分将用空字符补充

strncat函数

  • char* strncat(char* dest,const char* src,size_t n)
  • 把src所指向的字符串追加到dest所指向字符串的结尾,最多追加n个字符

strcmp函数:(按照ASCII码序)

  • 如果str1等于str2:返回0 如:sea 与 sea
  • 如果str1大于str2:返回1 如:sea 与 saa
  • 如果str1小于str2:返回-1 如:sea 与 sead
  • 比较字符串内容 if(strcmp(str1,str2)>0)

strlen函数

  • 计算字符串长度时不包括'\0' (fgets会包括'\n') //不要放循环内重复操作

strpbrk函数

  • 检索字符串str1中第一个匹配字符串str2中字符的字符

strchr函数

  • 参数str所指向字符串中搜索最后一次出现字符c的位置

islower函数

  • 如果c有相对应的小写字母,返回c的小写字母,否则c保持不变

toupper函数

  • 如果c有相对应的大写字母,返回c的大写字母,否则c保持不变

1.4 动态内存分配

原因:

  • 堆区申请的空间,想要多少申请多少
  • 数组指定数组长度,会造成空间浪费
  • 栈区空间有限
    相关函数:

void* calloc(unsigned n,unsigned size)

  • 在内存动态存储区堆中分配n个连续空间,每一存储空间的长度为size,并且分配后储存块里全部初始化为0
  • 申请成功:返回起始地址
  • 申请失败:返回NULL

void* malloc(unsigned size)

  • 在内存的动态存储区分配一连续空间,长度为size
  • 申请成功:返回起始地址
  • 申请失败:返回NULL
  • 不会对存储块初始化

free()

  • 释放动态申请到的整块内存空间,ptr为指向要释放空间的首地址
  • 当某个动态分配的存储块不再用时,要立即释放
//赋给指针要注意强制转化
p = (int*)calloc(n, sizeof(int));
p = (int*)malloc(n*sizeof(int));
free(p);

1.5 指针数组及其应用

  • 使用数组需要先申请较大内存存放,浪费空间
  • 指针数组表示多个字符串,节省空间
  • 一维指针数组定义:
    类型名* 数组名[数组长度]; int*p[n];
  • 指针数组可以相互赋值
  • 可以间接访问操作数组元素所指向的单元内容
char* color[5]={"red","blue","yellow","green","black"}
* color是一个数组,有五个元素
* 每个元素的类型都是字符指针
* &color[0],&color[1],&color[2]···
* 一维指针数组输入
for(i=0;i<n;i++)
{
      scanf("%s",color[i]);
}
* 一维指针数组输出
for(i=0;i<5;i++)
{
      printf("%s",color[i]);
}

1.6 二级指针

定义:指向指针的指针
类型名变量名
int
pp = &p;

  • p变了,pp也跟着改变
  • 地址加数值还是地址
  • 二级地址,一个后是一级地址,2个后才是内容

1.7 行指针、列指针

1.7.1 行指针

定义:int *(p)[n];
含义:p为指向含有n个元素的一维数组的指针变量

  • 行指针可以和数组名互换使用
int a[4][5];
int*(p)[5];
p=a;
//(*p)[0]=a[0][0],(*p)[1]=a[0][1],(*(p+1))[0]=a[1][0],(*(p+1))[1]=a[1][1]
p+i:表示第i行首地址a[i]

a[i][j]=((p+i)+j)=(*(p+i))[j]=p[i][j]

1.7.2 列指针

定义:

int a[3][3],*p;
//p=a;列指针,移动指向下一个元素
p=a[0];
*(p+i):表示离a[0][0]第i个位置的元素

2.PTA实验作业

2.1 删除字符串中的子串

题目详情

代码思路

想要删除字符串中的子串,首先要找到子串,所以使用strstr函数能够快速直接寻找字符串中的子串,找到子串的首地址之后,就要开始删除子串,因为子串前后都可能有字符,所以再定义一个数组,使用strcpy函数将子串之后的字符数据复制储存到新数组中,再使用strcat把它拼接连接到字符串在字串之前的数据,从而达到删除目的,再以此循环,直到字符串中不存在子串。

2.1.1 伪代码

定义字符串str1,子串str2,中途储存数据的新数组temp;
    遍历str1的变量i,遍历str2的变量j;
使用while和getchar为str1和str2输入数据并赋给结束符;
while (strstr找到子串首地址p)
      *p赋值为'\0';
      strcpy复制子串之后的数据(p + j)到新数组temp中;
      strcat把temp数据拼接到str1中;
end while
输出(str1);

2.1.2 代码截图

2.1.3 代码比较

两种代码对比分析:

我的代码更多的是采取字符串函数来进行代码的寻找,复制,拼接,把需要删除的字符串的前后重新拼接为一个新字符串,从而达到删除字符的目的,再以此循环,直到字符串中没有出现子串为止。
而该同学的代码是使用一个数组p记录子串所在的初始位置,然后使用len来计算子串长度,将指针指向内容作为while循环条件,并对p指针所指向的内容进行修改,将子串过后的数据内容重新赋予指针p到p+len,等于把子串过后的数据向前挪,最后赋予结束符,从而达到将子串删除的目的。

学习知识点

  • 学习取内容和取地址的灵活使用,明确什么时候用地址,什么时候用指针所指内容
    (如该做法中重新赋值和判断循环条件都灵活使用指针所指内容)
  • 学习使用指针将数据向前挪)
    (如使用*p=*(p+len)重新把后面的赋值到前面

2.2 合并两个有序数组

代码思路

要想把a,b两个数组内的数据按序排列合并到a中,可以从a数组的最后倒排到最前面,将最大的数放在合并后的最后一个数的位置,再找出第二大的数放在倒数第二个位置中,不断按从大到小的顺序倒排在数组a中,依此类推,当到a[0]时数据已经全部按顺序合并好了。

2.2.1 伪代码

a数组中有m个数,b数组中有n个数
定义:a数组下标为j,b数组下标为k,合并后a数组下标为i
//用三种下标可取的最大下标赋值,便于下方倒排
    i = m + n - 1;
    j = m - 1;
    k = n - 1;
for i to i=0
      if j 大于 0 和 a[j]大于b[k]      //如果a数组中还有数,比较此时a数组与b数组的数据大小
            a[i]等于a[j]      //重构数组中的数等于大的那个数
                 j--;      //数组向前移动一位,继续比较
       else  a[i]等于b[k]
                 k--;
end for

2.2.2 代码截图

2.2.3 代码比较


两种代码对比分析

* 我们两个人都是按照依次比较两个数组中的数据大小,并对数据进行排序整合。
* 我的代码是将两个数组中的数据重新储存到数组a中,从合并后数组a的最大下标开始重新对数组a进行赋值,数组a,b倒排向前比较,将数据从大到小给数组a赋值,达到按顺序合并
  的目的。
* 而该同学的代码则是通过重新定义一个数组,通过对数组a,b中的数据正序比较,将较小的数放入新定义的数组中,最后将新数组中的数据写入数组a中。

学习知识点

  • 学习动态申请内存的方法,不造成储存空间浪费
  • 同学的下标使用非常灵活巧妙,学习下标用法,灵活使用下标
    (如while循环的条件i + j < m + n,表示数组a和数组b中的数据还没有完全遍历,等等···)
  • 考虑问题要周全
    (如果数组a或b有一个到达最后一个数了,就把另一个数组的数全部储存进去,使用j >= ni >= m来控制条件)

2.3 说反话-加强版

2.3.1 伪代码

void ReverseStr(char* beginPtr)
{
   定义尾部指针endPtr = beginPtr;判断是否为第一个输出单词flag = 1;
   while (*endPtr)	endPtr++;//指针定位到字符尾部
   遍历指针p = --endPtr;//尾部指针前一个位置
   while(p!=beginPtr)
   {
      if p 不等于空格	len++;
            if 前一个* (p - 1)等于空格
                  if flag等于1	输出(前不带空格);flag = 0;
		  else   输出(前带空格);
	    len = 0;
	    end if
      p--;
   }
      输出第一个单词;
}

2.3.2 代码截图

2.3.3 代码比较

因为我一开始使用字符指针写代码并提交,总有两个测试点不过,我就使用了之前学的方式,没有使用指针。
使用getchar一个个字符输入,如果输入并存储每个单词和一个空格,之后再根据遍历并输出。
看过超星视频后学习老师使用指针的写法,使用字符指针,可以使用%.*s来表示从某个位置开始子串并输出的长度,从而控制长度输出。
而且使用函数分装,使代码更加清晰明了。

知识点

  • 定义指针指字符串,更能动态了解当前字符及位置
while (* endPtr&&* endPtr != '\n')      endPtr++;
  • 逆向扫描字符串
while (p != beginPtr)      { p--; }
  • 寻找字符串中单词
if(* p != ' '&&* (p-1) == ' ')//当前字符非空格而前一个字符是空格
  • 字符串指针灵活表示某个子串
printf(" %.*s ", len , p)//方便表示某个地址开始子串,使用len控制输出长度
posted @ 2020-12-27 22:47  栀夏~  阅读(181)  评论(0编辑  收藏  举报