数组、指针和动态内存也是密切相关的。容易出现的错误仍然是边界和初始化问题。
18.3.1 非数组的指针
【例18.8】下面程序将数组t和s中的内容赋给指针变量p,但输出结果并没有包括s的全部内容。找出错误之处并改正之。
#include <stdio.h> #include <string.h> int main () { int i=0 ,j=0 ; char t="abcdefghij" ,s="klmnopqrstuvwxyz" ,*p ; p=t ; i=strlen (t ); while (( p[i+j] = s[j] ) !='\0' ) j++ ; printf ("%s\n" ,p ); return 0 ; }
原因是用数组t初始化指针的想法是想利用超出t的存储空间来存储s,这是危险的做法。越界之后,并不能保证有连续的有效存储空间用以存储字符串s。
可以另外定义一个大于s和t总长度的字符数组。例如
char st[30] ; p=st ;
然后使用如下两个循环完成赋值:
while (( p[i] = t[i] ) !='\0' ) i++ ; while (( p[i+j] = s[j] ) !='\0' ) j++ ; p[i+j]='\0' ;
一般采用申请动态内存的方法,即为指针变量申请足够的存储空间。
p= (char* )malloc ( strlen (t )+strlen (t )+1 )
strlen函数计算的是实际字符串长度,所以要增加一个结束位。实际使用时,需要判别申请是否成功。这块内存虽然是非数组的指针,但却可以像数组那样使用下标。程序中演示了两种反序输出的方法,特别是演示下标为负值的使用方法,以便更好地理解动态内存的特点及指针的灵活使用方法。
// 完整的程序 #include <stdio.h> #include <string.h> #include <stdlib.h> int main () { int i=0 ,j=0 ; char t="abcdefghij" ,s="klmnopqrstuvwxyz" ,*p ; if ( (p= (char* )malloc ( strlen (t )+strlen (t )+8 )) == NULL ) { printf ( " 内存分配错误!\n" ); exit (1 ); } while (( p[i] = t[i] ) !='\0' ) i++ ; while (( p[i+j] = s[j] ) !='\0' ) j++ ; p[i+j]='\0' ; printf ("%s\n" ,p ); for (i=25 ; i>-1 ; i-- ) printf ("%c" ,p[i] ); printf ("\n" ); p=p+25 ; for (i=0 ; i>-26 ; i-- ) printf ("%c" ,p[i] ); printf ("\n" ); p=p-25 ; free (p ); return 0 ; }
程序输出结果如下:
abcdefghijklmnopqrstuvwxyz zyxwvutsrqponmlkjihgfedcba zyxwvutsrqponmlkjihgfedcba
释放内存,必须保证指针指向申请的动态内存的开始位置,否则会出错。所以程序中执行“p=p-25;”。申请内存时多申请了6个,是为了保证free可靠执行。
数值数组的使用方法与此类似,不再赘述。
18.3.2 NULL指针
在语句
if ( (p= (char* )malloc ( strlen (t ) + strlen (t ) + 8 )) == NULL )
中使用了空指针。空指针的表示为:
p=NULL ;
有时在赋值或比较运算的情况下会使用NULL指针,但在其他情况不能使用NULL指针。因为NULL指针并不指向任何对象,而且空指针也不是空字符串,所以对空指针p而言,使用如下两个语句会得到什么结果呢?
printf ("%s\n" , p ); printf (p );
为了代码的文档化,常采取如下定义:
#define NULL 0
由此可见,p的行为没有定义,这两条语句在不同的机器上可能有不同的效果。
在禁止读取内存0地址的机器上,语句
printf ("%d\n" , *p );
将会执行失败。在允许的机器上,则会以十进制方式输出内存位置0中存放的字符内容。
要注意的是,空指针并不是空字符串。无论使用0还是NULL,效果都是相同的。当将0赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。
有些C语言实现对内存位置0只允许读,不允许写。在这种情况下,NULL指针指向的也是垃圾信息,所以也不能错用NULL指针。
所以,对指针进行递增和递减操作必须预防越界。在达到最后一个边界时,要特别小心谨慎。释放不用的内存时,必须保证指针指向所申请内存的首地址,否则就会出错。在某些场合,为了保证释放,甚至需要多申请部分内存区域。