我の第六篇博客

| 这个作业属于哪个班级 | C语言--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | C博客作业05--指针 |
| 这个作业的目标 | 学习指针相关内容 |
| 姓名 | 付峻霖 |

0.展示PTA总分

1.本章学习总结

基础知识点

  1. 指针与指针变量
    指针存放的是地址
    普通变量存放的是数据
    指针变量的类型:就是它存放的地址指向的数据类型
  2. 定义指针变量
    类型名 *指针变量名
    char *pa; //定义一个指向字符型的指针变量
    int *pb; //定义一个指向整型的指针变量
  3. 取地址运算符和取值运算符
    如果需要获取某个变量的地址,可以使用取地址运算符(&):
    char *pa = &a;
    char *pb = &f;
    如果需要访问指针变量指向的数据,可以使用取值运算符
    printf("%c, %d\n", *pa, *pb);
  4. 指针变量都是四个字节,因为地址都是四个字节,与他们指向的数据类型没关系
    类型名 *指针变量名
    char *pa; //定义一个指向字符型的指针变量
    int *pb; //定义一个指向整型的指针变量
  5. 如何使用指针的相关知识?
    代码:
#include<stdio.h>
int main()
{
	char a = 'F';//定义一个指向字符型的指针变量
	int f = 123;//定义一个指向整型的指针变量

	//获取某个变量的地址
	char* pa = &a;
	int* pb = &f;

	//‘*’号可访问指针变量指向的数据
	printf("a = %c\n", *pa);//a = F
	printf("f = %d\n", *pb);//f = 123

	//间接对 a 和 f 访问
	*pa = 'C';//通过指针间接给变量a赋值
	*pb += 1;//通过指针间接对变量f进行运算

	printf("a = %c\n", *pa);//a = C
	printf("f = %d\n", *pb);//f = 124

	printf("sizeof pa = %d\n", sizeof(pa));
	printf("sizeof pb = %d\n", sizeof(pb));
	//      sizeof pa = 4
	//      sizeof pb = 4

	return 0;
}
  1. 注意事项
    ①避免访问未初始化的指针,否则不知道它到底哪儿
    下面这个就是“野指针”
#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\n", a);  //内容
	printf("%d\n", *&a);//内容
	printf("%d\n", *p); //内容
	printf("%d\n", *&p);//地址
	printf("%d\n", p);  //地址
	printf("%d\n", &a); //地址
	printf("%d\n", *&p);//地址
	printf("%d\n", (int)p - (int)q);//相隔的存储单元数目
	printf("%d\n", 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\n", i, j);
    return 0;
}
void Swap(int* p, int* q)
{
    int buf;
    buf = *p;
    *p = *q;
    *q = buf;
    return;
}

此时实参形参传递不是变量ij数据而是变量ij地址

  • 注意点:
    形参中变量名分别为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\n", p);
	printf("%s\n", 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

计算不包括'\0'字符串的长度

1.3 指针做函数返回值

(1) 格式,如何定义,如何使用
指针做函数返回值时:应该返回的是地址

#include<stdio.h>
int* func() 
{
    int n = 100;
    return &n;//返回指针,指针指向地址
}
int main() 
{
    int* p = func();//调用函数
    printf("value = %d\n", *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\n", *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 指针数组及其应用

基础知识点

  1. 指针数组区别
    数组名只是一个地址,而指针是一个左值
  2. 指针数组数组指针
    ①指针数组数组数组指针指针
    ②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] = '\0';//不要烫

	/*输入子串*/
	for (开头直到回车+)
	{
		给数组s2赋值;
	}
	s2[i] = '\0';//不要烫

	while (原字符串中存在子串时)  //strstr(s1,s2) 函数用于判断字符串s2是否是s1的子串。
	{       //p保存字串的位置     //如果存在,则该函数返回s2在s1中首次出现的地址;
                
		*p = '\0';  //切断子串                
		跳过需要删除的字符串减剩下的拷贝到s3里面去 
		strcat函数字符串的连接函数将s1和s3连接在一起
	}

	打印s1数组
	return 0;

2.1.2 代码截图

2.1.3 找一份同学代码(尽量找思路和自己差距较大同学代码)比较,说明各自代码特点。



代码特点:

  • me
    (1) 查找子串,我直接用了strstr函数查找是否有子串,并返回子串位置
    (2) 输入子串原字符串的时候,我是用for循环输入
    (3)找到子串,是把原字符串的子串位置用'\0'切开,只留下无子串的部分
  • 进源
    (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函数好理解,也节省空间
    超星:巧妙利用指针进行逆向遍历

posted @ 2020-12-27 22:27  强扭的甜瓜  阅读(97)  评论(0编辑  收藏  举报