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 二级指针
定义:指向指针的指针
类型名变量名
intpp = &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 >= n
或i >= 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控制输出长度