指针的高级应用

  1. 动态存储分配
  2. 指向函数的指针

动态存储分配

为了动态地分配存储空间,将需要调用3种内存分配函数中的一种,这些函数都是声明在<stdlib.h>中的:

  • malloc函数——分配内存块,但是不对内存块进行初始化。
  • calloc函数——分配内存块,并且对内存块进行清除。
  • realloc函数——调整先前分配的内存块
    这三种函数中,malloc函数是最常用的一种,因为malloc函数不对内存进行清除,所以更高效。
    当为申请内存块而调用内存分配函数时,由于函数无法知道计划存储在内存块中的数据是什么类型的,所以它不能返回普通的int型指针或char型指针或其他。取而代之的,函数会返回void型的值。void型的值是“通用”指针,本质上它只是内存地址。

空指针

在调用内存分配函数时,无法定位满足我们需要的足够大的内存块,这种问题始终可能出现。如果真的出现这种问题,函数会返回空指针(null pointer)。空指针是“指向为空的指针”,这是一个区别于所有有效指针的特殊值。在把返回值存储在指针变量p中以后,需要判断p是否为空指针。这是一个区别于有效指针的特殊值。在把返回值存储的奥指针变量p以后,需要判断p是否为空指针。

程序员的责任是测试任何内存分配函数的返回值,并且在返回空指针时采取适当的操作,通过空指针试图访问内存的效果是未定义的,程序可能会崩溃或者出现不可预测的行为。

由于用名为NULL的宏来表示空指针,所以可以用下列方式测试malloc函数的返回值:

p = malloc(10000);
if(p == NULL)
{
	/*allocation failed;take appropriate action*/
}

也可以把malloc函数的调用和NULL的测试混合在一起

if((p = malloc(10000)  == NULL)
{
	/*allocation failed;take appropriate action*/
}

名为NULL的宏在六个头文件中都有定义:

<locale.h>
<stddef.h>
<stdio.h>
<stdlib.h>
<string.h>
<time.h>        

使用任意内存分配函数的程序都会包含<stdlib.h>,这使NULL必然有效。
在C语言中,所有非空指针都为真,而只有空指针为假。
条件语句有可以编写成

if(!p)...
if(p)...

但作为一种书写风格,可以与NULL进行明确的比较

动态分配字符串

动态内存分配经常用于字符串操作。字符串始终存储在固定长度的数组中,而且可能很难预测这些数组需要的长度。通过动态地分配字符串,可以推迟到程序运行时才做决定。

使用malloc还敢书为字符串分配内存

malloc函数具有如下原型:

void *malloc(size_t size);

malloc函数分配size字节的内存块,并且返回存向此内存块的指针。注意size的类型睡觉哦size_t,这是在C语言库中定义的无符号整数类型。除非正在分配一个非常巨大的内存块,否则可以只把size考虑成普通整数。

用malloc函数来为字符串分配内存是很容易的,因为C语言保证char型值确切需要1个字节的内存。为给n个字符的字符串分配空间,可以写成

p = malloc(n+1);

这里的p是char型变量。在执行赋值操作时会把malloc函数返回的通用指针转换为char类型,而不需要强制执行。(通常情况下,可以把void*型值赋给任何指针类型的变量。)然而,一些程序员喜欢强制转换malloc函数的返回值:

p = (char *)malloc(n+1);

当使用malloc函数为字符串分配内存空间时,不要忘记包含空字符的空间

由于使用malloc函数分配内存不需要清除或者以任何方式的初始化,所以p指向带有n+1个字符的未初始化的数组
调用strcpy函数是对上述数组进行初始化的一种方式

strcpy(p,"abc");

在字符串函数中使用动态存储分配

动态存储分配使编写返回指向新字符串的指针的函数成为可能,所谓新字符串是指在调用此函数之前字符串并不存在。如果编写的函数不改变任何一个字符串而把两个字符串连接起来,请思考一下这样做会遇到什么问题。strcat函数改变了传递过来的字符串中的一个,所以不是希望的函数

思路:测量用来连接的两个字符串的长度,然后调用malloc函数只为结果分配适当数量的内存空间。接下来函数会把第一个字符串复制到新的内存空间中,并用strcat函数来拼接第二个字符串。

char *concat(const char *s1,const char *s2)
{
	char *result;
	
	result = malloc(strlen(s1) + strlen(s2));
	if(result == NULL)
	{
		printf("Error:malloc failed in concat\n");
		exit(EXIT_FAILURE);
	}
	strcpy(result, s1);
	strcat(result, s2);
	return result;
}

p = concat(“abc”,“def”);
p将指向字符串"abcdef",此字符串是存储在动态分配的数组中的。数组包含结尾二代空字符一共有7个字符长。

像concat这样动态分配存储空间的函数必须小心使用,当不再需要concat函数返回的字符串时,需调用free函数来释放它占用的空间。如果不这样做,函数可能会过早地运行越界。

字符串数的存储

方式一字符串数组
在这里插入图片描述
在这里插入图片描述
\0填充的空间浪费。
因为大部分字符串集都是长短字符串的混合,所以这些例子所暴露的低效性是在处理字符串的时候常见的问题。我们需要的是参差不齐的数组(ragged array),即数组的每一行有不用的长度。C语言本身不提供这种参差不齐的数组,但它确实提供了模拟这种类型的工具。秘诀就是建立一个特殊的数组,这个数组的元素都是指向字符串的指针。
下面是planets数组的另外一种写法,这次看成是指向字符串的指针的数组:

char *planets[] = {"Mercury","Venus","Earth",
					"Mars","Jupiter","Saturn",
					"Uranus","Neptune","Pluto"};

存储方式转变为
在这里插入图片描述
planets的每一个元素都指向以空字符结尾的字符串的指针。虽然必须为planets数组中的指针分配空间,但是字符串中不再有任何浪费的字符。
为了访问其中一个星星名字,只需要给出planets数组的下标。访问行星名字中的字符的方式和访问二维数组元素的方式相同,这都要感谢指针和数组之间的紧密关系。例如,为了在planets数组中搜寻以字母M开头的字符串,可以使用下面的循环:

for (i = 0; i < 9; i++)
	if(planets[i][0] == 'M')
		printf("%s begins with M\n",planets[i]);

运用指针数组解决了在数组中存储字符串的问题。我们发现按行的方式在二维字符数组中存储字符串 可能会浪费空房间,所以试图为字符产字面量设置为指针数组。上述方法正好和数组元素是指向动态分配字符串的指针方式一样。为了说明这个观点,下面编写两种存储方式的remind.c

程序:显示一个月的提示列表

程序功能:显示一个月的每日提示列表。
用户需要输入一系列提示,每条提示都要有一个前缀来说明是一个月中的哪一天。当用户用-代替有效的天录入时,程序会显示出录入的全部提示的列表,并且这些提示是按天排序的。下面是与这个程序的对话信息:
在这里插入图片描述

/* Prints a one-month reminder list*/
#include "stdio.h"
#include "string.h"

#define MAX_REMIND 50
#define MSG_LEN 60

main()
{
	char reminders[MAX_REMIND][MSG_LEN+1];//二位字符数组存储日期+提示信息
	char day_str[3],msg_str[MSG_LEN+1];//一维数组分别存储日期、提示信息
	int day,i,j,num_remind = 0;
	
	for(;;)
	{
		if(num_remind == MAX_REMIND)
		{
			printf("-- No space left --\n");
			break;
		}
		printf("Enter day and reminder:");
		scanf(%2d,&day);
		if (day == 0)
			break;
		sprintf(day_str,"%2d",day);
		gets(msg_str);
			
			
	}	
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值