- 动态存储分配
- 指向函数的指针
动态存储分配
为了动态地分配存储空间,将需要调用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);
}
}