| 这个作业属于哪个班级 | C语言--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | C博客作业00--我的第一篇博客 |
| 这个作业的目标 | 学习指针相关内容 |
| 姓名 | 余智康 |

目录

0. 展示PTA总分

1. 本章学习总结

2. PTA实验作业



0.展示PTA总分


1.本章学习总结

1.1 指针定义、指针相关运算、指针做函数参数

1)指针定义:

  • 类型名 * 指针变量名
    • 类型名:指针变量所指向的变量的类型
    • 此处* 是指针声明符,(注意:与*取内容运算符不同)
  • 指针变量所占用的空间和变量类型 无关不同类型的指针变量占用的内存一致
  • EG:
      FILE* P;  //指向文件的指针
      int* p;   //指向整型变量的指针
  • 同时定义整型的指针变量和整型变量:
      int *p,a = 3;
  • 备注:
    • 指针变量的赋值是地址
    • 不能数值作为指针变量的初值
      int *p;
      //p = 100; ❌错误,不能用数值作为指针变量的初值
      p = (int*)100; //可以**避免编译错误**,但**不提倡**使用
      p = NULL;   //或者p = 0; 将指针变量**初始化**为**空指针**,避免指针成为不知去向的**野指针**。

2)指针的基本运算:

  • 取地址运算符: &(取地址)
    • 备注: 指针变量的类型与它所指变量的类型要相同
  • 间接访问运算符:*(取内容)
    • 备注:
      利用间接访问运算符对所指变量进行修改是直接内存单元进行操作,所指向的变量的值改变。
      即,可以在调用时函数中使用指针进行操作,就能修改多个主调函数中变量的值。
  • 运算符的优先级:
    • &*运算符和++--自增、自减运算符是同优先级,遵循从右向左运算
    • 如,*p++;将指针p向后移动一位,再取此时p所指向的内容
      (*p)++;将p所指向的内容加一
  • 指针的减法: -
    • 同一类型得两个指针变量相减,表示它们所指向的位置中间隔了多少个元素
    • 如果在相减的时候,用强制类型转换,将两个指针的类型强制转换为int类型,运算结果就是两个指针之间间隔的字节数
    • 注意:两个指针变量不能相加
  • 指针变量和整型数加减:
      static int num[10]; 
      int *p = NULL;

      p = num;  //表示p指向了数组num的首地址
      p = p + 1;      //表示p移动到了下一个元素的位置,即此时p指向 a[1]
      p = p - 1;     //表示p移动到了前一个元素的位置,即此时p指向 a[0]

3)指针做函数参数:

  • 利用指针做函数参数,可以在函数中修改多个变量的值
  • 因指针作为函数参数,传递到函数中的指针是所指向变量的地址
    可以通过间接运算符,直接对所指向的内存单元进行操作,修改变量的值
  • 优点:
    • 数据量少。因为传递的是地址而不是要修改的变量本身
    • 效率高。直接对地址进行操作,效果更好,效率更高
    • 与return相比,能修改更多的变量
  • 备注:
    • 主调函数中,将该变量的地址,或是指向该变量的指针作为实参
    • 被调函数中,指针形参的类型,与所指的变量类型一致
    • 被调函数中,可以通过间接访问运算符,修改形参所指向变量的值

1.2 字符指针

1) 指针如何指向字符串

*(1)字符指针指向字符数组,从而指向字符串

      char str[] = "Hello world!";
      char *pstr = str;
  • (2)指针指向字符串常量
      const char *pstr = "Hello world!";
  • 区别:
    • 存储单元:
      字符数组存储在全局数据区或栈区;
      字符串常量存储在常量区
    • 读写操作:
      字符数组因为存储在全局数据区或栈区,故可以对字符数组进行读写操作;
      字符串常量存储在常量区,只有读取的权限,无法写入修改
  • 备注:
    • 字符串指针不能改变字符串,但仍可以和正常指针一样改变指针的指向

2)字符串相关函数及函数原型理解(课内)

  • 字符串输入

    • scanf("%s",pstr);
      特点:遇到空格 停止输入。常用于输入不含空格的字符串,
    • fgets(pstr,n,stdin); n为字符串长度,可以输入n-1个字符
      特点:遇到换行 停止输入。若输入的字符不足n-1个,吸收'\n',尾巴有'\n'和'\0'
    • getchar()循环输入字符,末尾注意加上结束标志"\0"
  • 字符串输出

    • printf("%s",pstr);,正常输出
    • puts(pstr);
      特点:输出字符串后自动换行
    • putchar()循环输出字符
  • (0)下面使用前需要先包含头文件,#include <string.h>

  • (1)strcpy() (字符串复制函数):将 strSrc(源字符串)所指的内容复制到strDest(目标字符串)中

    • 注意: strSrc的长度不能超过strDest
    • 函数形参,为了保护原函数不被修改,传入的原字符串用const char*
    • 函数3、4行,判断传入的指针是否有效(非空),无效则返回空,结束函数
    • 函数5、6行,判断传入的两个指针是否相同,相同则直接返回,结束函数
    • 函数7、9行,存放strDest的原地址,最后返回tempDest(即strDest的原地址)
    • 函数8行赋值移动指针,直到字符串结束
  • (2)strcat()(字符串连接函数): 把 strSrc 所指向的字符串追加到 strDest 所指向的字符串的结尾

    • 备注: strDest要足够,以防溢出
    • 函数形参,为了保护原函数不被修改,传入的原字符串用const
    • 函数4行:,为保护目标串的原地址,给一个新指针赋目标串的地址
    • 函数5行,判断传入的指针是否有效(非空),无效则报错,并结束
    • 函数6~9行,将指针移动到 结束标记'\0'的位置
    • 函数11~14行,将源字符串的内容逐个赋在目标字符串后面
  • (3)strcmp()(字符串比较函数): 把 str1 所指向的字符串和 str2 所指向的字符串进行比较。若返回值大于0,则str1所指字符串大于str2的。小于0->小于,等于0->等于

    • 备注:字符串的大小是指从第一个字符开始,顺次向后直到出现不同的字符为止,然后以第一个不同的字符的ASCII码值确定,ASCII码值大的字符串就 ("abc">"aabdfg")
    • 函数5~9行,移动到str1或str2的结束标志,或当str1所指字符ASCII码值不等于str2所指字符时,结束循环
    • 函数10行,返回当*str1 != *str2时或者str1或str2结束时的第一个字符ASCII码值相减(*str1-*str2)
  • (4)strlen()(返回字符串长度的函数)

    • 函数6~9行,指针移动,长度增加。当指针移动到结束标记时,结束循环。因此,'\0'(结束标记)进入统计

3)字符串相关函数及函数原型理解(课外)

  • (1)strchr()(字符查找函数)
    • 作用:在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的地址。如果未找到该值,则函数返回一个空指针
    • EG:
    • 函数声明:
      char *strchr(const char *str, int c); 
  • (2)strncpy()(自定义字符数的字符串复制函数)
    • 作用: strncpy()会将字符串src前n个字符拷贝到字符串dest。与strcpy()不同,strncpy()不会向dest追加结束标记'\0'
    • 备注:因为strncpy()不会自动追加结束标记'\0',需要程序员手动添加结束标记。也可以在拷贝之前初始化dest为0
    • EG:
    • 函数声明:
      char *strncpy(char *dest, const char *src, size_t n);
  • (3)memset()(复制字符到字符串前n个)
    • 作用: 将指针变量 str 所指向的前 n 字节的内存单元用一个字符 c 替换。因为str 是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。
    • memset 一般使用0 初始化内存单元,而且通常是给数组结构体进行初始化。
    • 函数声明:
      void *memset(void *str, int c, size_t n);
     char str[10];
     char *p = str;
     memset(str, 0, sizeof(str));  //只能写sizeof(str), 不能写sizeof(p)。因为 p 是指针变量,不管 p 指向什么类型的变量,sizeof(p) 的值都是 4。

1.3 指针做函数返回值

1)具体格式

  • 示例:
      char* Match(char* str, char ch);

2)注意事项

  • (1),函数运行结束后,内部定义栈区局部数据对象会消亡。
    因此函数的返回的指针若指向栈区会出错,返回指针的函数一般返回全局数据对象或指向字符串常量堆区的指针,或是主调函数中数据对象的地址

  • (2)

    • 如图
    • n 为func()函数内定义的局部变量,func()返回了指向n的指针。而func()结束后,n将消亡,但为何*p获取到了n的值 0.0
      欲知后事如何,往下瞅
    • 如图![]
    • 在取消11行的注释之后,p指向的数据则不再是原来的n,而是无意义的随机数了。与前面相比仅是在*p之前添加了一个函数调用。
    • 原因:
      函数运行结束后销毁所有局部数据,这里的销毁并不是将局部数据所占用的内存全部抹掉,而是程序放弃对它的权限,对他弃之不理,使得后面的代码可以随意使用这块内存。如果有其他函数被调用就会覆盖这块内存
  • (3),使用堆区返回指针时,要注意可能产生的内存泄露问题,要在使用完毕后主函数中释放该段内存

    • “内存泄漏”:指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

1.4 动态内存分配

1)动态分配的原因

  • 使用动态内存分配能够根据实际输入的数据多少来申请和分配内存,从而提高了内存的使用率
  • 动态内存分配完有程序员手动编程释放,可以在多个函数中使用;而静态内存函数结束后自动消失
  • 动态内存堆区中申请,可以使用的空间;而静态内存在栈区,可以使用的空间较小

2)堆区、栈区的区别

  • 堆区:
    • 申请方式:堆区内存,由程序员申请,并指明大小;若程序员忘记释放,会造成内存泄漏,进程结束时由系统回收
    • 申请大小:堆区,看主机多少位的,若是32位的,则就是4G
    • 申请效率:由mollloc、calloc函数分配,一般速度较慢,且容易导致内存碎片,但比较方便
    • 生长方向:堆区,从低到高
  • 栈区:
    • 申请方式:栈区内存,由函数运行时自动分配函数结束自动释放
    • 申请大小:栈区,一般是1~2M,最大不超过8M
    • 申请效率:栈区,由系统自动分配,速度较,但程序员无法控制
    • 生长方向:栈区,从高到低

3)动态内存分配及相关函数用法

  • malloc()函数:
    • malloc()函数的功能是“分配内存”,调用前要包含头文件<stdlib.h>
    • 函数声明:
      void *malloc(unsigned long size);
    • malloc()只有一个形参,并且是整型。该函数的功能是在内存的动态存储空间(堆)中分配一个长度为size的连续空间
    • 函数的返回值是一个指向所分配内存空间起始地址指针类型为void*
    • 即malloc()返回一个地址,为动态分配的内存空间的起始地址,若内存申请失败(如内存空间不足),则返回空指针
    • 需要用强制类型转换,将malloc的返回值转换为指针变量p相同的类型,再赋给p
    • 区别:(与calloc())malloc()不会将内存空间初始化为0,即内存空间的值是随机的
    • EG:
      int *p;
      p = (int *)malloc(sizeof(int)*N); //N为元素的个数
      free(p);
  • calloc()函数:
    • 函数声明:
      void* calloc (size_t num, size_t size);
    • 作用:calloc()在内存中动态分配num个 长度为size连续空间,并将每一个字节初始化为0
    • 区别: calloc()在动态分配完内存后自动初始化该内存空间为0;而malloc()不会将内存空间初始化,里面是未知的数据。
    • EG:
      int *p;
      p = (int *)calloc(N,sizeof(int));      //N为个数
      free(p);
  • free()函数:
    • 函数声明:
      void free(void *ptr)
    • 作用:释放由ptr所指的内存,并将它返回给堆;
    • 备注:
    • free()函数只能作用*于以前动态分配的指针,若调用无效指针可能会毁坏内存管理机制,并引起系统破坏
    • free()函数如果调用的是空指针,则不会执行任何动作
      所以可以在free()某个指针变量,将指针指向NULL(空)
      int *p;
      p = (int *)calloc(N,sizeof(int));      //几个calloc或者malloc对应几个free 

      free(p);
      p = NULL;

1.5 指针数组及其应用

1)指针数组:

  • 指针数组是什么:
    • C语言中的数组可以是任何类型,如果数组的各个元素都是指针类型,用于存放内存地址,那么这个数组就是指针数组
  • 一维指针数组的定义:
    • 类型名 *数组名[数组长度]
      char *color[5];
      //其中,color是一个数组,数组内有5个元素,每个元素的类型都是 char*的指针
  • 备注:
    • 指针数组元素的操作和对同类型指针变量的操作相同
    • 指针数组元素可以互相赋值
    • 指针数组也可以间接访问操作数组元素所指向的单元内容

2)多个字符串用二维数组和指针表示的区别

  • (1) 二维数组表示:
    • 二维数组:存放字符,组成字符串,可以对字符串进行修改
        char str[i][j] = { "welcome","to","tomorrow}; //注:
                                                              //在实际应用中,i(行数),j(列数)**应是常量**,不能是变量;
                                                              //每个字符串的长度应该不超过 **j(列数)- 1**,留一个存放结束标记"\0"
      	char str[3][6] = { "happy","new","year" }; //每个字符串的长度不能超过列数减一
  • (2) 指针数组表示:
    • 指针数组:存放的是地址,(即指针数组,数组内的元素是指针)
    • 若指向字符串常量,是多个字符串常量的集合,不能对字符串常量修改
      //指向字符串常量
      	const char* str[] = { "hello","world!" }; 
  • (3) 区别:
    • 指针数组在处理字符串,需要改变字符串顺序时,指针互相交换,不需要strcpy做字符串复制,间都更优化

1.6 二级指针

  • 二级指针指向指针指针
  • 二级指针定义:类型名 **变量名
      int num = 12;
      int* pNum = &num;
      int** pp = &pNum;

      char* color[5] = { “red”, ”blue”, ”yellow”, ”green”, ”black” };
      char** pc;  /* 定义二级指针变量 */
      pc = color;   /* 二级指针赋值 */

  • 备注:
    • 二级地址二维数组名a,二级指针int **p;
    • 二级地址*运算后一级地址
      a+i ≠ a[i]前者二级地址,后者一级地址
      *(a+i)=a[i]:一级地址

1.7 行指针、列指针

1)定义格式

  • 列指针: int *p;
      int *p;
      p=a[0];     
      *(p+i);       //表示离a[0][0]第i个位置的元素
  • 行指针: int (*p)[n];
    • 含义: p为指向含有n个元素的一维数组指针变量。二级指针。
      int (*p)[n];
      p=a;
      p+i;       //表示第i行的首地址(即a[i]),为二级指针 

2)主要用法

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


2.PTA实验作业

2.1 删除字符串中的子串

1)伪代码

    主函数(main)
	dim pparent As char*
	dim pson As char*
	dim len As int
	
	fgets输入母串(pparent)
	fgets输入子串(pson)

	len = strlen(pparent);      //计算母串的长度
	pparent[len - 1] = NULL;  //因为fgets输入,末尾有换行符。用NULL覆盖换行符

	if* pson! = ‘\0';
	then
		进入函数FindSameStr(),寻找相同的字符串,并删除
	END if

	输出母串(pparent)

	释放(pparent)
	释放(pson)


void FindSameStr(pson, pparent);	//寻找重复字符串
	dim ps1, ps2 As char* 分别指向母串和子串
	dim ptemp As char*

	dim i As int
	dim flag As int
	flag = 1;
	i = 0;

	dim lenS2 As int
	lenS2 = strlen(pson) - 1;//获取子串的长度	

	while (flag)            //循环寻找,直到在母串中找不到子串
		flag = 0;
		ps1 = pparent;

		for ps1 to* ps1 = '\0'      //遍历母串
			if* ps1 = ps2[i]      
			then
				for ptemp = ps1 to ps2[i] = '\n',i++,ptemp++
					if *ptemp != ps2[i]
					then 
						break
					end if

					if i == lenS2 - 1 && *ptemp == ps2[i]
					then
						进入DelSter(ps1,lenS2)
						flag = 1
					end if
				end for
				i = 0;
			end if
		end for
	end while

void DelStr(char* pdel, int delLen); //删除重复子串

	for pdel+deLen to '\0',pdel++	//数组左移
		*pdel = *(pdel + delLen)
	end for
	
	*pdel = '\0'	//赋上结束标记
  

2)代码截图



3)同学代码

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

void DeleteStr(char* S1, char* S2);
void DeleteEnter(char* s);//清除回车

int main()
{
	char *S1 = (char*)malloc(85);
	char *S2 = (char*)malloc(85);
	fgets(S1, 85, stdin);
	fgets(S2, 85, stdin);
	DeleteEnter(S1);
	DeleteEnter(S2);
	DeleteStr(S1, S2);
	free(S2);
	printf("%s", S1);
	free(S1);
	return 0;
}


void DeleteStr(char* S1, char* S2)
{
	int i;
	int length = strlen(S2);
	char* p = strstr(S1, S2);
	if (p != NULL)
	{
		for (i = p - S1; *(S1 + i) != '\0'; i++)
		{
			*(S1 + i) = *(S1 + i + length);
		}
		DeleteStr(S1, S2);
	}
}

void DeleteEnter(char* s)
{
	int length = strlen(s);
	*(s + length - 1) = 0;
}

4)值得学习

  • 函数28行中:函数strstr()的使用:strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL

2.2 合并2个有序数组

1)伪代码

dim i,j,k   
	
    i = m - 1;
    j = n - 1;
    k = m + n - 1;

	for k to -1             //从最后一位开始放置
    then
		if i >= 0 && a[i] > b[j]     //若a[i]大于b[j],则大的那个数为最大,放置到最后
		then
			a[k] = a[i]
			i--;
		end if

		else if j >= 0              //若a[i]不大于b[j],则置入b[j]
		then	
			a[k] = b[j]
			j--;
		end if
	end for

2)代码截图

3)同学代码

void merge(int* a, int m, int* b, int n)
{
	int i = m - 1;
	int j = n - 1;
	int k = n + m - 1;
    
	while (j >= 0 && i >= 0)
	{
		if (a[i] >= b[j])
			a[k--] = a[i--];
		else
			a[k--] = b[j--];
	}
    
	while (j >= 0)
		a[k--] = b[j--];
}

4)区别

  • 思路上大致一致,同学的代码更精简,清爽

2.3 说反话-加强版

1)伪代码

  • 大致思路:用指针数组存放每个单词的地址,最后倒序输出
新建函数 int GetStr(char str[]) 用于输入单词(遇空格结束),并根据返回值判断是否结束
    dim temp[500] As char;
    dim i As int;
    i = 0;
    
    while(1)
        temp[i] = getchar() 
        if temp[i] = '\n' //输入的句子结束
        then
            str[i] = '\0';
            return 0;       //返回0,表示句子结束
        end if
        
        if temp[i] = ' ' //空格,单词输入结束
        then
            str[i] = '\0';
            return 1;
        end if
        str[i] = temp[i];
        i++;
    end while

主函数(main)
    dim p[500000] As char*;
    dim str[500000] As char;
    dim n As int;
    dim flag As int;
    
    n = 0;
    
    do
        flag = GetStr(str);
        p[n] = (char*)malloc(sizeof(char) * strlen(str) + 1); //动态分配内存

        if str[0] != '\0'
        then
            strcp(p[n],str);
            n++;
        end if
    while(flag)
    
    for n=n-1 to 0 //逆序输出
        if n = 0                    //输出仅带一个空格
        then
            printf("%s", p[n]);     
        end if
        
        else
        then
            printf("%s ", p[n]);
        end else
    end for

2)代码截图


3)与超星的区别,及自己需要的学习

  • 区别:思路大致不同
    • 超星:,输入句子后,逆向扫描(直到首地址时结束),当发现空格时,使用printf("%.*s",len,str) 输出字符串。在循环外再考虑首个单词的首字符不为空格
    • 自己:新建输入函数,将各个字符串的地址存入指针数组中,最后逆序输出
  • 学习:
    • printf("%.*s",len,str)函数的使用:len为长度,str为首地址
    • 定义指针指向字符串,移动指针到末尾
      while(*endPtr && *endPtr != '\n')
      endPtr++;
    • 逆向扫描字符串
      while(p != beginPtr)
      p--;