C语言-----指针进阶(下)

目录

1、指针与字符串

1.1 使用指针创建字符串 

1.2 使用指针处理字符串

 2、函数指针数组

 3、指向函数指针数组的指针


1、指针与字符串

1.1 使用指针创建字符串 

字符串的定义自动包含了指针,例如定义 char str1[20];为 20 个字符声明存储空间,并自动地创建一个包含指针(地址)的常量 str1 ,存储的是 str1[0] 的地址。与一般的常量一样,指针(地址)常量指向是明确的,不能被修改。

对于字符串,可以不按照声明一般数组的方式定义数组的维数和每一维的个数,可以使用新的方法,即用指针创建字符串。例如下面的代码:

char* str2 = "How are you?";

这个 str2 和 str1 是不同的。str1 是按照数组定义方式定义的,如:

char str1[20] = "How are you?";

这种形式要求 str1 有固定的存储该数组的空间,而且,因为 str1 本身是数组名称,一旦被初始化后,再执行下面的语句就是错误的,如:

str1 = "fine,and you?";

 str2 本身就是一个指针变量,对 str2 执行了初始化后,再执行下面的代码就是正确的:

str2 = "fine,and you?";

 从分配空间的角度来分析:二者是不同的str1 指定了一个存储 20 个字符位置的空间。str2 只能存储一个地址,也就是只能存储指定字符串的首个字符的地址。

例1.字符指针存储字符数组的首地址:

#include<stdio.h>

int main()
{
	char str[] = "abcdef";//字符串常量
	char* pstr = str;//数组名就是首元素地址,也就是字符串首个字符 a 的地址存放到 pstr 里。
	//数组名就是首元素地址,从 str 里面存的地址数开始打印这个常量字符串,
	//常量字符串里面有“ \0 ”,打印的时候遇到“\0 ”,就停止打印。
	printf("%s\n", str);
	// pstr 存放的是 a 地址,从 pstr 里面存的地址数开始打印这个常量字符串,
	//常量字符串里面有“ \0 ”,打印的时候遇到“\0 ”,就停止打印。
	printf("%s\n", pstr);
	return 0;
}

例2.用指针创建字符串:

#include<stdio.h>

int main()
{
	char* p = "abcdef";
	printf("%c\n", *p);
	printf("%s\n", p);
	return 0;
}

 分析:“ char* p = " abcdef " ; ” ,“ abcdef ”是一个常量字符串,常量字符串 “ abcdef ” 赋值给指针变量  p ,并不是把字符串的内容赋值给 p ,而是把字符串首字符的地址赋给指针变量 p。(char* p = " abcdef " ; 特别容易以为是把字符串 abcdef 放到字符指针里,但是本质是把字符串 abcdef 首字符的地址放到了 p 中)分析图如下:

对打印的结果进行分析:printf("%c\n", *p); ”,p 存储的是 a 的地址,*p(解引用 p)可得到字符 a ; printf("%s\n", p); ”的含义是:打印常量字符串 “ abcdef ”,p 存放的是 a 地址,从 p 里面存的地址数开始打印这个常量字符串,常量字符串里面有“ \0 ”,打印的时候遇到“ \0  ”,就停止打印。

例3.假如对以上代码进行修改,我们能这样修改字符串里面的字符吗?如下:

#include<stdio.h>

int main()
{
	char* p = "abcdef";
	*p = 'w';//段错误: 非法访问内存报的错误;"abcdef"是一个常量字符串,常量是不能被改变。
	printf("%s", p);
	return 0;
}

分析:答案是:不能。以上代码是 error 的,虽然程序没有报错,运行进来的时候,就会出现程序崩溃。这种错误属于段错误,非法访问内存,因为 " abcdef " 是一个常量字符串,常量是不能被改变的。

例4.为了防止这种错误的出现,对程序进行优化,代码如下: 

#include<stdio.h>

int main()
{
	const char* p = "abcdef";
	printf("%s", p);
	return 0;
}

 分析:const 修饰了,是不能进行修改的。否则,程序会报出错误来提醒,如下图:

 例5.下面看一个相关的面试题,代码如下:

#include<stdio.h>

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	const char* p1 = "abcdef";//p1和p2指向的是一个常量字符串,常量字符串它本身是不能修改的
	const char* p2 = "abcdef";//p1、p2指向的是同一块空间
	if (arr1 == arr2)
	{
		printf("arr1 and arr2 are same\n");
	}
	else
	{
		printf("arr1 and arr2 are not same\n");
	}
	if (p1 == p2)
	{
		printf("p1 and p2 are same\n");
	}
	else
	{
		printf("p1 and p2 are not same\n");
	}
	return 0;
}

分析:p1 和 p2 指向的是同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,它们实际会指向同一块内存。用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以 arr1 和arr2 是不同,p1 和 p2 是相同。如下图:

1.2 使用指针处理字符串

例1.字符串复制。

#include<stdio.h>

int main()
{
	char str1[10] = { 0 };
	char str2[10] = { 0 };//初始化数组
	char* pstr1 = str1;
	char* pstr2 = str2;//将数组首元素地址赋给字符指针
	printf("请输入原字符串:\n");
	scanf("%s", pstr1);
	for (pstr1 = str1; *pstr1 != '\0';pstr1++, pstr2++)
	{
		*pstr2 = *pstr1;//循环复制 str1 中的字符到 str2
	}
	*pstr2 = '\0';// str2结尾补\0
	printf("原字符串是: %s\n复制后字符串是: %s\n", str1, str2);
	return 0;
}

 分析:本例定义了两个字符指针存储字符串首字符的地址(为什么需要字符指针呢?直接用数组名不行吗?答案是:不行。原因是:数组名 --> 字符串首字符的地址 --> 属于常量 --> 常量不能进行自加/自减运算,下面也会说到)。然后,通过指针移动,解引用,将 str1 中的字符赋值给 str2,并且在 str2 结尾添加了字符串结束标志。需要注意以下两点。

(1) 假如题目中没有使用指针变量,而是直接在 for 循序中使用了 str1++、str2++ 这样的表达式,程序就会出错,因为 str1、str2 都是字符串的名字,是常量。

(2) 如果没有写 *pstr2=' \0 ';这行代码,输出的目标字符串长度是 9 位,而且很可能后面的字符是乱码,因为 str2 没有结束标志,直至遇见了声明该字符串时设置好的结束标“ \0 ”。

例2. 字符串连接

#include<stdio.h>

int main()
{
	char str1[10] = { 0 };
	char str2[10] = { 0 };
	char str3[20] = { 0 };
	char* pstr1 = str1;
	char* pstr2 = str2;
	char* pstr3 = str3;
	printf("请输入字符串 1 : \n");
	scanf("%s", pstr1);
	printf("请输入字符串 2 : \n");
	scanf("%s", pstr2);
	while (*pstr1 != '\0')
	{
		*pstr3 = *pstr1;
		pstr1++;
		pstr3++;
	}
	for (pstr2 = str2; *pstr2 != '\0'; pstr2++, pstr3++)
	{
		*pstr3 = *pstr2;
	}
	*pstr3 = '\0';
	printf("字符串 1 : %s\n字符串 2 : %s\n连接后是: %s\n", str1,str2, str3);
	//也可这样写
	pstr1 = str1;//把指针的指向复位后,就可拿字符指针存储的地址数打印字符串,
	pstr2 = str2;
	pstr3 = str3;
	printf("字符串 1 : %s\n字符串 2 : %s\n连接后是: %s\n", pstr1, pstr2, pstr3);
    //这样写,就必须把字符指针的指向复位,重新指向首字符的地址。
	return 0;
}

 例3.已知一个字符串,使用返回指针的函数,实现这样的功能:把该字符串中的“ # ”号删除,同时把后面连接的字符串前移。

#include<stdio.h>

char* strmanage(char* str)
{
	char* pstr = str;//pstr指向数组
	char* temp = NULL;
	while (*pstr != '\0')//数组没有到结束就一直循环
	{
		if (*pstr == '#')//当指针指向的值时“#”
		{
			temp = pstr;//temp指向数组
			while (*temp != '\0')
			{
				*temp = *(temp + 1);//数组前移一位
				temp++;//temp后移
			}
			pstr--;//pstr前移,重新检查该位置的值
		}
		pstr++;//pstr后移
	}
	return str;
}
int main()
{
	char str[] = "abcdef##abcde##abc##abcd##abc";
	char* pstr = str;
	printf("未去掉#,字符串str为: %s\n", pstr);
	printf("去掉#后,字符串str为: %s\n", strmanage(pstr));
	return 0;
}

分析:(1)保当前地址,代码中的 pstr=str 和 pstr=temp ,都是此的含义,用于恢复到当前位置;(2)针对连续出现 “ # ” 的解决办法,采用了先向前移动,然后再重新检查该位置字符的办法,如代码 pstr--,后再 pstr++。strmanage 函数返回了字符指针,该指针始终指向该字符串 str 的首地址。对 pstr 前移,再后移分析图如下:

 2、函数指针数组

什么是函数指针数组呢? 把函数的地址存放到一个数组中,那么这个数组就叫函数指针数组。函数指针数组的定义:

int (*parr[4])(int, int);

分析:parr先和 [ ] 结合,说明 parr 是数组,数组的内容是什么呢?答案是:int (*)(int,int) 类型的函数指针。分析图如下: 

 例1.函数指针和函数指针数组写法

#include<stdio.h>

char* my_strcpy(char* dest, const char* src)
{
	//......
}
int main()
{
	//1.写一个函数指针pf,能够指向my_strcpy
	char* (*pf)(char*, const char*) = my_strcpy;
	//2.写一个函数指针数组pfarr,能够存放5个my_strcpy函数的地址
	char* (*pfarr[5])(char*, const char*) = { my_strcpy,my_strcpy, my_strcpy, my_strcpy, my_strcpy };
	return 0;
}

例2.函数指针数组的简单应用

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int i = 0;
	//函数指针数组 --> 数组中存放的是函数的地址。
	int (*parr[4])(int, int) = { Add,Sub,Mul,Div };//每个元素都是是一个函数的地址
	for (i = 0; i < 4; i++)/*以下两种写法都没有问题*/
	{
		//printf("%d ", (*(parr[i]))(2, 3));// parr[i],是第 i 个函数的地址,*(parr[i])解引用,找这个函数,调用它。
		printf("%d ", parr[i](2, 3));// parr[i]是一个函数地址,去调用这个地址,直接指向函数也是没有问题的。
	}
	return 0;
}

分析:四个函数的类型、形参的类型和形参的个数都是一样的,所以函数的地址可以存放在函数指针数组里面。

 例3.函数指针数组用途(计算器)

#include <stdio.h>

void menmu()//菜单显示
{
	printf("|---------------------------------|\n");
	printf("|---- 1、Add      2、Subtract ----|\n");
	printf("|---- 3、Multiply 4、Divide  -----|\n");
	printf("|----------  0、exit  ------------|\n");
	printf("|---------------------------------|\n");
	printf("请选择:> ");
}

int Add(int x, int y)//加
{
	return x + y;
}

int Subtract(int x, int y)//减
{
	return x - y;
}

int Multiply(int x, int y)//乘
{
	return x * y;
}

int Divide(int x, int y)//除
{
	return x / y;
}

int main()
{
	int x = 0;
	int y = 0;
	int ret = 0;
	int input = 1;
	int (*parr[5])(int, int) = { 0,Add,Subtract,Multiply,Divide };//函数指针数组 --> 把函数的地址存放到数组中
	while (input)
	{
        menmu();//菜单显示
		scanf("%d", &input);
		if (input <= 4 && input >= 1)//功能选择
		{
			printf("请输入操作数:> ");
			scanf("%d %d", &x, &y);
			ret = (*parr[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出成功\n");
		}
		else
		{
			printf("输入有误\n");
		}
	}
	return 0;
}

 3、指向函数指针数组的指针

指向函数指针数组的指针 --> 存放函数指针数组的地址。指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针。分析图如下:

 例:指向函数指针数组的指针。

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int arr[10] = { 0 };
	int (*parr)[10] = &arr;//parr --> 数组指针,存放数组arr的地址。
	int (*pf)(int, int) = &Add;//函数指针 --> 存放函数的地址
	int (*pfarr[5])(int, int) = { 0 };//pfarr --> 是一个数组 --> 函数指针数组 --> 数组存放的是函数的地址
	int (*(*ppfarr)[5])(int, int) = &pfarr;//ppfarr是一个指向[函数指针数组]的指针 --> 存放 [函数指针数组]的地址
	//ppfarr 是一个数组指针,指针指向的数组有五个元素
	//指向的数组的每个元素的类型是一个函数指针 int(*)(int,int)
	return 0;
}

分析:从 函数指针函数指针数组 再到 指向函数指针数组的指针 的递进,三者相互关联。

(1)函数指针 --> 是指针,指针中存放函数的地址。

(2)函数指针数组 -->  是数组,数组中存放函数的地址。

(3)指向[函数指针数组]的指针 --> 是指针,指针中存放 [函数指针数组] 的地址。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值