这个作业属于哪个班级 | C语言--网络2011/2012 |
---|---|
这个作业的地址 | C博客作业05--指针 |
这个作业的目标 | 学习指针相关内容 |
姓名 | 付峻霖 |
0.展示PTA总分
1.本章学习总结
⭐基础知识点
- 指针与指针变量
① 指针存放的是地址
② 普通变量存放的是数据
③ 指针变量的类型:就是它存放的地址指向的数据类型 - 定义指针变量
①类型名 *指针变量名
②char *pa; //定义一个指向字符型的指针变量
③int *pb; //定义一个指向整型的指针变量 - 取地址运算符和取值运算符
①如果需要获取某个变量的地址,可以使用取地址运算符(&):
char *pa = &a;
char *pb = &f;
②如果需要访问指针变量指向的数据,可以使用取值运算符:
printf("%c, %d ", *pa, *pb); - 指针变量都是四个字节,因为地址都是四个字节,与他们指向的数据类型没关系
①类型名 *指针变量名
②char *pa; //定义一个指向字符型的指针变量
③int *pb; //定义一个指向整型的指针变量 - 如何使用指针的相关知识?
①代码:
#include<stdio.h>
int main()
{
char a = 'F';//定义一个指向字符型的指针变量
int f = 123;//定义一个指向整型的指针变量
//获取某个变量的地址
char* pa = &a;
int* pb = &f;
//‘*’号可访问指针变量指向的数据
printf("a = %c
", *pa);//a = F
printf("f = %d
", *pb);//f = 123
//间接对 a 和 f 访问
*pa = 'C';//通过指针间接给变量a赋值
*pb += 1;//通过指针间接对变量f进行运算
printf("a = %c
", *pa);//a = C
printf("f = %d
", *pb);//f = 124
printf("sizeof pa = %d
", sizeof(pa));
printf("sizeof pb = %d
", sizeof(pb));
// sizeof pa = 4
// sizeof pb = 4
return 0;
}
- 注意事项
①避免访问未初始化的指针,否则不知道它到底指向哪儿
下面这个就是“野指针”
#include<stdio.h>
int main()
{
int* a;
*a = 123;
return 0;
}
1.1 指针定义、指针相关运算、指针做函数参数。
(1) 指针的定义
char a = 'F';//定义一个指向字符型的指针变量
int f = 123;//定义一个指向整型的指针变量
//获取某个变量的地址
char* pa = &a;
int* pb = &f;
(2) 指针相关运算
#include<stdio.h>
int main()
{
int a = 50;//定义一个指向整型的指针变量
int b = 60;//定义一个指向整型的指针变量
int* p = &a;//获取a变量的地址
int* q = &b;//获取b变量的地址
printf("%d
", a); //内容
printf("%d
", *&a);//内容
printf("%d
", *p); //内容
printf("%d
", *&p);//地址
printf("%d
", p); //地址
printf("%d
", &a); //地址
printf("%d
", *&p);//地址
printf("%d
", (int)p - (int)q);//相隔的存储单元数目
printf("%d
", p - q); //相隔的字节数
}
注意事项:
①指针类型要和指向数据类型一样
②不能把数值作为指针变量的初值,否则不知道它到底指向哪儿
③指针变量都是四个字节,因为地址都是四个字节,与他们指向的数据类型没关系
(3) 指针做函数参数
例子:利用指针交换数字
#include<stdio.h>
void Swap(int* p, int* q); //函数声明
int main(void)
{
int i = 3, j = 5;
Swap(&i, &j);//交换
printf("i = %d, j = %d
", i, j);
return 0;
}
void Swap(int* p, int* q)
{
int buf;
buf = *p;
*p = *q;
*q = buf;
return;
}
此时实参向形参传递的不是变量i和j的数据,而是变量i和j的地址。
- 注意点:
形参中变量名分别为p和q,变量类型都是int* 型。所以实参i和j的地址&i和&j是分别传递给p和q,而不是传递给p和q
Q1:为什么不用指针传递,就无法交换i,j呢?
A1:因为实参和形参之间的传递是单向的,只能由实参向形参传递。被调函数调用完之后系统为其分配的内存单元都会被释放。
所以虽然将i和j的值传给了a和b,但是交换的仅仅是内存单元a和b中的数据,对i和j没有任何影响。
Q2:为什么不用 return 语句?
A2:因为 return 语句只能返回一个值,并不能返回两个值。
1.2 字符指针
(1) 指针如何指向字符串
#include <stdio.h>
int main()
{
char a[100] = "breadfruit";
char* p = a;
printf("%s
", p);
printf("%s
", p + 3);
return 0;
}
p指向的是a的首地址,而(p+3)指向的是a的第四个字母的地址
(2) 字符串相关函数
函数名 | 定义 | 功能 | 返回值 |
---|---|---|---|
strcmp | int strcmp(char* str1, char* str2); | 比较字符串 | 返回正值或者1;返回0;返回负值或者-1; |
strcat | char* strcat(char* str1, char* str2); | 连接字符串 | str1字符串的首地址 |
strcpy | char* strcpy(char* str1, char* str2); | 复制字符串 | str1字符串 |
strlen | int strlen(char* str1); | 求字符串长度 | 字符串长度 |
(3) 字符串相关函数用法
strcmp
① str1<str2,返回负值或者-1; ② str1=str2,返回0; ③ str1>str2,返回正值或者1;
strcat
将字符串str2连接在str1后,并且str1最后的结束字符NULL会被覆盖掉,并且连接后的字符串的尾部会再增加一个NULL
注意:str1和str2所指的内存空间不能重叠,且str1要有足够的空间来容纳要复制的字符串。
strcpy
将str2所指的字符串复制到str1所指的字符串中。
注意:src1和str2所指内存区域不可以重叠且str1必须有足够的空间来容纳str2的字符串。
strlen
计算不包括' '字符串的长度
1.3 指针做函数返回值
(1) 格式,如何定义,如何使用
指针做函数返回值时:应该返回的是地址
#include<stdio.h>
int* func()
{
int n = 100;
return &n;//返回指针,指针指向地址
}
int main()
{
int* p = func();//调用函数
printf("value = %d
", *p);//输出指针指向地址的内容
return 0;
}
1.4 动态内存分配
(1) 为什么?
①避免造成内存浪费,基本上都是有多少用多少。
②能够随时增加,减少。
(2) 堆区和栈区区别?
①栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了。
②堆区:就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。
(3) 动态内存分配相关函数及用法
⭐malloc函数
原型:void *malloc(unsigned int size)
返回一个通用指针,可以用强制转换的方法将返回的指针值转换为所需的类型
⭐free函数
原型:void free(void *pi);
形参指向分配的内存地址
malloc与free函数的用法如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = NULL;
p = (int*)malloc(sizeof(int));//先申请内存
*p = 8; //使用内存
printf("%d
", *p);
free(p);//释放之前申请的内存
system("pause");//任意键继续
return 0;
}
- 注意点:
1.如果内存开辟成功,就返回一个指向开辟好空间的指针
2.如果内开辟失败,就会返回一个空指针
⭐calloc函数
原型:void *calloc(unsigned int num,unsigned int size);
第一个参数表示申请空间的数量,第二个参数表示每个空间的字节数
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = calloc(10, sizeof(int));
if (p != NULL)
{
//说明空间开辟成功
//使用动态开辟的空间
}
free(p);
p = NULL;
system("pause");
return 0;
}
- calloc函数也能申请内存,与malloc函数的区别只在于calloc函数会在返回地址之前把申请的空间的每个字节初始化为全0
1.5 指针数组及其应用
⭐基础知识点
- 指针和数组的区别
① 数组名只是一个地址,而指针是一个左值 - 指针数组和数组指针
①指针数组是数组,数组指针是指针。
②int *p1[5];是指针数组 **
int (*p2)[5];是数组指针**
⭐多个字符串用二维数组表示
二维字符数组一旦定义,那么每个字符串的最大长度、首地址都不能改变了。
char str[5][5]={"I","love","eat","apple","!"};
⭐多个字符串用指针数组表示
字符指针数组是存放字符指针的数组。由于它仅用来存放指针,所以它指向的每个字符串的首地址可以改变,字符串最大长度也可以改变。
char* str[5];
str[0]="I";
str[1]="love";
str[2]="eat";
str[3]="apple";
str[4]="!";
⭐二维数组和指针数组区别?
- 指针数组表示:字符串长度无限定
- 在定义二维数组时,就已经分配给二维数组空间,但是定义一个指针数组,指针数组里面的指针却不会自动初始化,此时的指针仍然是野指针,这时不能直接对其赋值。
1.6 二级指针
(1) 概念
如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。
(2) 如何使用?
题目:假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量
①关系图
②代码定义:
int a =100;
int *p1 = &a; //一级指针,指向a的地址
int **p2 = &p1;//二级指针,指向p1的地址
printf("%d %d %d",a,*p1,**p2);//打印了三个100
1.7 行指针、列指针
(1) 行指针
常用的二维数组,如:
int a[3][5];
行指针是指向数组的指针,即上面几种指针类型中的 int (*a)[5];所以,当二维数组要被当做参数进行传递时,可以这样声明:
void funcByRowPtr(int p[][5],const int row);
写法 | 解释 | 指针类型 |
---|---|---|
a+0或&a[0] | 指向第1个一维数组的地址(指向第1行) | 行指针 |
a+1或&a[1] | 指向第2个一维数组的地址(指向第2行) | 行指针 |
a+2或&a[2] | 指向第3个一维数组的地址(指向第3行) | 行指针 |
(2) 列指针
对于一个二维数组:
int a[3][5];
如果用列指针进行函数传参,可以直接声明如下:
void funcByColPtr(int * const colPtr,const int row,const int col);
写法 | 解释 | 指针类型 |
---|---|---|
a[0]+0或&a[0][0] | 指向第1行第1列的地址 | 列指针 |
a[0]+1或&a[0][1] | 指向第1行第2列的地址 | 列指针 |
a[1]+0或&a[1][0] | 指向第2行第1列的地址 | 列指针 |
a[1]+1或&a[1][1] | 指向第2行第2列的地址 | 列指针 |
2.PTA实验作业
2.1 删除字符串中的子串 (20分)
基本思路:
- 首先把原字符串存入s1中,再把字串存入s2中
- 查找有无字串,并且保存子串位置
- 输出没有子串的部分
2.1.1 伪代码
int i;
char s1[81];//原字符串
char s2[81];//字串
char s3[81];//中间产物
char* p;//结束标志
/*输入原字符串*/
for (开头直到回车)
{
给数组s1赋值;
}
s1[i] = ' ';//不要烫
/*输入子串*/
for (开头直到回车+)
{
给数组s2赋值;
}
s2[i] = ' ';//不要烫
while (原字符串中存在子串时) //strstr(s1,s2) 函数用于判断字符串s2是否是s1的子串。
{ //p保存字串的位置 //如果存在,则该函数返回s2在s1中首次出现的地址;
*p = ' '; //切断子串
跳过需要删除的字符串减剩下的拷贝到s3里面去
strcat函数字符串的连接函数将s1和s3连接在一起
}
打印s1数组
return 0;
2.1.2 代码截图
2.1.3 找一份同学代码(尽量找思路和自己差距较大同学代码)比较,说明各自代码特点。
代码特点:
- me
(1) 查找子串,我直接用了strstr函数查找是否有子串,并返回子串的位置
(2) 输入子串和原字符串的时候,我是用for循环输入
(3) 我找到子串了后,是把原字符串的子串位置用' '切开,只留下无子串的部分 - 进源
(1) 查找子串,进源是用了while语句内嵌一个for循环去判断查找子串
(2) 输入子串和原字符串的时候,进源是直接调用fgets函数
(3) 找到子串了后,他是用for循环左移
2.2 合并2个有序数组
基本思路:
- 首先把两行内容分别存入a[]数组和b[]数组
- 比较a,b数组中最大元素的大小,即最末尾的元素**
- 如果a[]数组末尾的元素比b[]数组的大,那么把a[]数组末尾元素放到该去的地方
- 如果b[]数组末尾的元素比a[]数组的大,那么把b[]数组末尾元素放到该去的地方
- 如此反复,即可得到一个合并后的排好序的数组
2.2.1 伪代码
int i = m - 1;//a数组长度
int j = n - 1;//b数组长度
int k = n + m - 1;//最终数组长度
while (a数组没排完 且 b数组也没排完)
{
比较两数组最末尾元素的大小,比较后把大的元素放好位置
}
while (a数组排完了 而 b数组还没排完)
{
单独处理b数组,把b数组的元素全部放过去
}
2.2.2 代码截图
2.2.3 找一份同学代码(尽量找思路和自己差距较大同学代码)比较,说明各自代码特点。
代码特点:
-
me
(1) 我是从后面开始判断,排到a数组末尾去,
(2) 可能b数组会多出来,要单独处理,而a数组多出来没关系,因为本来就是要以a数组作为输出结果,不动即可。 -
学长
(1) 学长是从前面开始判断,排到一个新数组中
(2) 所以a数组多出来元素和b数组多出来元素,都需要处理,把剩余元素排到新数组中
(3) 最后再把新数组中的元素全部复制给a数组
2.3 说反话-加强版
基本思路:
- 首先把一行内容存入a[]数组中,保存 "I love apple"
- 从后往前遍历整个字符串
- 如果不是空格就进入,把字母保存在s[]数组中
- 如果是空格,输出刚刚保存的内容,分别输出 "apple","love","I"
- kong用来控制是否输出空格
2.3.1 伪代码
int a[100];//保存 "I love apple"
int s[100];//输出 "apple","love","I"
for (开头到回车)
把内容保存到a数组中
for (从开头遍历)
计算开头的空格数
//倒着遍历
for (从尾部到开头)
{
if (不是空格)//不是空格,必能输出字符串
{
while (不是空格)
保存单词
for (逆序输出s数组,即一个单词)
输出单词
if (循环还未到最后一个单词)
{
判断空格是否输出
}
}
else //是空格
就不管
}
2.3.2 代码截图
2.3.3 请说明和超星视频做法区别,各自优缺点。
-
区别:
①我用puts(s1)输出结果,超星用printf(" %.*s", len, p) -
优点:
我:用puts函数和strstr函数,好理解,也节省空间
超星:巧妙利用指针进行逆向遍历