目录
一个句子哪怕其中的每个单词都拼写正确,而且语法也无懈可击,仍然可能有歧义或者并非书写者希望表达的意思。在某些C语言实现中能够正常工作,而在另一些C语言实现中却又不能工作的情形,这属于可移植性方面的问题。
C语言中指针与数组这两个概念之间的联系是如此密不可分。
C语言中的数组值得注意的地方有以下两点:
C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。
对于一个数组,我们只能够做两件事:
1.确定该数组的大小,
2.获得指向该数组下标为0的元素的指针。
任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。
要理解C语言中数组的运作机制,我们首先必须理解如何声明一个数组。例如,
int a [3];
这个语句声明了a是一个拥有3个整型元素的数组。类似地,
struct {
int p [4];
double x;
}b[17];
声明了b是一个拥有17个元素的数组,其中每个元素都是一个结构,
现在考虑下面的例子,
int calendar [12][31];
这个语句声明了calendar 是一个数组,该数组拥有12个数组类型的元素,其中每个元素都是一个拥有31个整型元素的数组。
sizeof (calendar )的值是372(31×12)与sizeof (int)的乘积。
如果calendar 不是用于sizeof 的操作数,那么calendar 总是被转换成一个指向calendar 数组的起始元素的指针。
任何指针都是指向某种类型的变量,如果有这样的语句:
int * ip;
就表明ip是一个指向整型变量的指针。又如果声明,
int i;
那么我们可以将整型变量 i 的地址赋给指针 ip ,就像下面这样:
ip = &i;
而且,如果我们给*ip赋值,就能够改变i的取值:
*ip = 17;
如果一个指针指向的是数组中的一个元素,那么我们只要给这个指针加1
如果我们给这个指针减1,得到就是指向该数组中前一个元素的指针
可以得到:如果ip指向一个整数,那么ip+1指向的是计算机内存中的下一个整数
如果两个指针指向的是同一个数组中的元素,我们可以把这两个指针相减。这样做是有意义的,例如:
Int * p = q+ i;
那么我们可以通过q-p而得到i的值。
如果我们在应该出现指针的地方,却采用了数组名来替换,那么数组名就被当作指向该数组下标为0的元素的指针。因此如果我们这样写,
p = a;
注意:
p=&a;
这种写法在ANSIC 中是非法的,因为&a是一个指向数组的指针,而p是一个指向整型变量的指针,它们的类型不匹配。
如果希望p指向数组a中下标为1的元素,可以这样写:
p=p+1;
该语句完全等同于下面的写法:
p++;
sizeof (a)的结果是整个数组a的大小,而不是指向数组a的元素的指针的大小。
可以得出一个推论:
*a即数组a中下标为0的元素的引用
在C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符(0)的内存区域的地址
char * r;
strcpy (r,s);
strcat (r,t);
我们还应该看到,不仅要让r指向一个地址,而且r所指向的地址处还应该有内存空间可供容纳字符串,
char r[100];
strcpy (r,s);
strcat (r,t);
C语言强制要求我们必须声明数组大小为一个常量,因此我们不够确保r足够大。
大多数C语言实现还提供了一个库函数strlen ,该函数返回一个字符串中所包括的字符数。我们就能够像下面这样操作了:
char* r, *malloc ();
r= malloc( strlen( s)+ strlen(t));
strcpy( r, s);
strcat (r,t);
这个例子还是错的,原因归纳起来有三个:
1. malloc 函数有可能无法提供请求的内存,这种情况下malloc 函数会通过返回一个空指针来作为“内存分配失败”事件的信号。
2. 给r分配的内存在使用完之后应该及时释放。例子中r是作为一个局部变量声明的,因此当离开r作用域时,r自动被释放了。修订后的程序显式地给r分配了内存,为此就必须显式地释放内存。
3. 前面的例程在调用malloc 函数时并未分配足够的内存。字符串以空字符作为结束标志时。库函数strlen 返回参数中字符串所包括的字符数目,而作为结束标志的空字符并未计算在内。因此,如果strlen (s)的值是n,那么字符串实际需要n+1个字符的空间。所以,我们必须为r多分配一个字符的空间我们就得到正确的结果:
char *r,malloc();
r = malloc( strlen(s)) + strlen (t) + 1);
if( ! r ){
complain();
exit(1);
}
strcpy (r,s);
strcat (r, t );
free(r);
在C语言中,我们没有办法可以将一个数组作为函数参数直接传递。
例如,下面的语句:
char hello[] = "hello";
声明了hello 是一个字符数组。将该数组作为参数传递给一个函数,
printf("%s\n",hello);
实际上将该数组第1个元素的地址作为参数传递给函数的作用完全等效,即:
printf("s\n",&hello[0]);
C语言中会自动地将作为参数的数组声明转换为相应的指针声明。这样的写法:
int strlen( char s[])
{
/*具体内容*/
}
与下面的写法完全相同:
int strlen( char* s)
{
/具体内容*/
}
C程序员经常错误地假设,在其他情形下也会有这种自动地转换
extern char * hello;
这个语句与下面的语句有着天渊之别:
extern char hello [];
如果一个指针参数并不实际代表一个数组,即使从技术上而言是正确的,采用数组形式的记法经常会起到误导作用。
一个常见的例子就是函数main的第二个参数:
main( int argc, char* argv[])
{
/*具体内容*1
}
这种写法与下面的写法完全等价:
main( int argc, char* * argv)
{
/*具体内容*
}
需要注意的是,前一种写法强调的重点在于argv是一个指向某数组的起始元素的指针,该数组的元素为字符指针类型。两种写法是等价,可以任选能清楚反映自己意图的写法。
C语言中一个常见的“陷阱”:混淆指针与指针所指向的数据。
例如:
char * p, *q;
p = "xyz";
实际上,p的值是一个指向由x、y、z和\0,4个字符组成的数组的起始元素的指针
如果我们执行下面的语句:
p=q
p和q现在是两个指向内存中同一地址的指针,这个赋值语句并没有同时复制内存中的字符。
复制指针并不同时复制指针所指向的数据。
因此,当我们执行完下面的语句之后:
q[1]='Y';
q所指向的内存现在存储的是字符串xYz。因为p和q所指向的是同一块内存,所以p指向的内存中存储的当然也是字符串'xYz'。
在C语言中将一个整数转换为一个指针,最后得到的结果都取决于具体的C编译器实现。这个特殊情况就是常数0,常数0这个值经常用一个符号来代替:
#define NULL 0
当然无论是直接用常数0,还是用符号NULL,效果都是相同的。当常数0被转换为指针使用时,这个指针绝对不能被解除引用(dereference )。
下面的写法是完全合法的:
if (p == ((char *)0)
但是如果要写成这样:
if (strcmp( p, (char * 0)0 == 0)
就是非法的了,原因在于库函数strcmp 的实现中会包括查看它的指针参数所指向内存中的内容的操作。
如果p是一个空指针,即使
printf(p);
和printf ("%s",p);
的行为也是未定义的