转]C语言灵魂——指针

来源:互联网 发布:大数据产业链的构成 编辑:程序博客网 时间:2024/04/27 22:55
转]C语言灵魂——指针 (2011-01-18 10:09:28)
转载
标签: 

it

分类: C/CPP

原作者:wuliming wuliming_sc@163.com

 

指针与数组1(c缺陷与陷阱3.1)

c语言中的数组值得注意的地方有以下两点:

1c语言中只有一维数组而且数组的大小必须在编译期间就作为一个常数确定下来(C99标准允许变长数组,GCC编译器中实现了变长数组)。然而,c语言中数组的元素可以是任何类型的对象,当然也可以是另外一个数组。这样,要仿真出一个多维数组就不是一件难事。

2、对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针。其他有关数组的操作,哪怕它们乍看上去是以数组下标进行运算的,实际上都是通过指针进行的。换句话说,任何一个数组下标运算都等同于一个对应的指针运算,因此我们完全可以依据指针行为定义数组下标的行为。

 

现在考虑下面的例子:

int i;

int *p;

int calendar[12][31];

上面声明的calendar是一个数组,该数组拥有12个数组类型的元素,其中的每个元素都是一个拥有31个整型元素的数组。因此,sizeof(calendar)的值是:31×12×sizeof(int)

 

考虑一下,calendar[4]的含义是什么?因为calender是一个有着12个数组类型元素的数组,它的每个数组类型元素又是一个有着31个整型元素的数组,所以calendar[4]calendar数组的第5个元素,是calendar数组中12个有着31个整型元素的数组之一。因此,calendar[4]的行为也表现为一个有着31个整型元素的数组的行为。例如,sizeof(calendar[4])的结果是:31×sizeof(int)

 

又如,p = calendar[4];这个语句使指针p指向了数组calendar[4]中下标为0的元素。因为calendar[4]是一个数组,我们可以通过下标的形式来指定这个数组中的元素:i = calendar[4][7],这个语句也可以写成下面这样而表达的意思保持不变i = *( calendar[4] + 7 ),还可以进一步写成:i = *( *( calendar + 4 ) + 7 )

 

下面我们再看:p = calendar; 这个语句是非法的,因为calendar是一个二维数组,即“数组的数组”,在此处的上下文中使用calendar名称会将其转换为一个指向数组的指针。而p是一个指向整型变量的指针,两个指针的类型不一样,所以是非法的。显然,我们需要一种声明指向数组的指针的方法。

 

int calendar[12][31];

int (*monthp)[31];

monthp = calendar;

int (*monthp)[31] 语句声明的 *monthp 是一个拥有31个整型元素的数组,因此,monthp就是一个指向这样的数组的指针。monthp指向数组calendar的第一个元素。

 

指针与数组2(c和指针.P141.)

·1、数组的名的值是一个指针常量,不能试图将一个地址赋值给数组名;

·2、当数组名作为sizeof操作符的操作数时,sizeof(arrayname)返回的是整个数组的长度,而不是指向数组的指针的长度;

·3、当数组名作为单目操作符&的操作数,取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针。

·4、指针和数组并不总是相等的。为了说明这个概念,请考虑下面这两个声明:

int a[5];

int *b;

ab能够互换吗?它们都具有指针值,它们都可以进行间接访问和下标操作。但是,它们还是有很大的区别的:声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为指向任何现有的内存空间,如果它是一个自动变量,它甚至根本不会被初始化。把这两个声明用图的方法表示,可以发现它们之间存在显著的不同:

        a

 

 

 

 

 

        b

    因此,上述声明后,表达式*a是完全合法的,但表达式*b却是非法的。*b将访问内存中某个不确定的位置,或者导致程序终止。另一方面,表达式b++可以通过编译,但是a++却不能,因为a的值是一个常量。

 

#include<stdio.h>

 

int main()

{

     //注意sizeof(num)的长度应该为10*4=40

         int num[] = {0,1,2,3,4,5,6,7,8,9};

         printf(" sizeof(num) = %d\n", sizeof(num) );

 

         //注意sizeof(str)的长度应该为11,包括字符串后面的'\0'

         char str[] = "0123456789";

         printf(" sizeof(str) = %d\n", sizeof(str) );

 

         //注意sizeof(str1)的长度应该为10,不包括字符串后面的'\0',但是,最好将字符串的最后一个字符设定为空

         char str1[] = {'0','1','2','3','4','5','6','7','8','9'};

         printf(" sizeof(str1) = %d\n", sizeof(str1) );

 

         //&num的类型为'int (*)[10]',表示的是一个指向长度为10的整形数组的指针

         int (*ptoint)[10] = &num;

         printf(" sizeof(ptoint) = %d, (*ptoint)[9] = %d\n", sizeof(ptoint), (*ptoint)[9] );

 

         //&str的类型为'char (*)[11]',表示的是一个指向长度为11的字符数组的指针,注意str数组的长度是11,而不是10

         char (*ptostr)[11] = &str;

         printf(" sizeof(ptostr) = %d, (*ptostr)[9] = %c\n", sizeof(ptostr), (*ptostr)[9] );

 

         //由于p指向的是数组num[5],所以对下标取负值后,不会超出数组的正常取值范围

         //该例子也说明了为什么下标检查在c语言中是一项困难的任务:下标引用可以作用于任意的指针,而不仅仅是数组名

         //作用于指针的下标引用的有效性即依赖于该指针当时恰好指向什么内容,也依赖于下标的值

         int *p = num + 5;

         printf(" p[-1] = %d, p[0] = %d, p[1] = %d \n", p[-1],p[0],p[1] );

 

         //下面的表达式中,'num[5]5[num]'的值是一样的,把它们转换成对等的间接访问表达式,它们都等同于*(num + 2)

         //'5[num]'这个古怪的表达式之所以可行,缘于C实现下标的方法。对编译器来说,这两种形式并无差别

         //但是,决不应该编写形如'5[num]'的表达式,因为它会大大的影响程序的可读性

         printf(" num[5] = %d, 5[num] = %d \n", num[5], 5[num] );

     getchar();

         return 0;

}

输出结果为: [转]C语言灵魂——指针 

 

 

指针和数组的相同与不同(c专家编程.P199.)

在实际应用中,数组和指针可以互换的情形要比两者不可互换的情形更为常见。让我们分别考虑“声明”和“使用”这两种情况。声明本身还可以进一步分为3种情况:

·外部数组的声明;

·数组的定义(定义是声明的一种特殊情况,它分配内存空间,并可能提供一个初始值);

·函数参数的声明;

 

[转]C语言灵魂——指针

也既是:
作为函数参数时、在语句或表达式中使用数组时,我们可以采用数组或者指针的任何一种形式,除此之外的其他情况下,指针和数组不要互换。下面就数组和指针相同的情况做详细的说明:

 

·规则1、表达式中的数组名被编译器当作一个指向该数组第一个元素的指针。

假如我们声明:

int a[10];

int *p = a;

就可以通过一下任何一种方式来访问a[i]

p[i]       *( p + i )        *( a + i )      ·····

事实上,可以采用的方法很多。对数组的引用如a[i] 在编译时总是被编译器改写成*(a+i)的形式,C语言标准要求编译器必须具备这个概念性的行为。

编译器自动把下标值的步长调整到数组元素的大小。如果整型数的长度是4个字节,那么a[i+1]a[i]在内存中的距离就是4。对起始地址执行加法操作之前,编译器会负责计算每次增加的步长。这就是为什么指针总是有类型限制,每个指针只能指向一种类型的原因所在,因为编译器需要知道对指针进行解除引用操作时应该取几个字节,以及每个下标的步长应取几个字节。

 

·规则2、下标总是和指针的偏移量相同。

把数组下标作为指针加偏移量是c语言从BCPL(C语言的祖先)继承过来的技巧。在人们的常规思维中,在运行时增加对c语言下标的范围检查是不切实际的。因为取下标操作只是表示将要访问该数组,但并不保证一定要访问。而且程序员完全可以使用指针来访问数组,从而绕过下标操作符。在这种情况下,数组下标范围检测并不能检测所有对数组的访问的情况。事实上,下标范围检测被认为不值得加入到c语言当中。

还有一个说法是,在编写数组算法时,使用指针比使用数组更有效率。这个颇为人们所接收的说法在通常情况下是错误的。使用现代的产品质量优化的编译器,一维数组和指针引用所产生的代码并不具有显著的差别。不管怎样,数组下标是定义在指针的基础上,所以优化器常常可以把它转化为更有效率的指针表达式,并生成相同的机器指令。

 

 

·规则3、在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。

    在函数形参定义这个特殊情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式。编译器只向函数传递数组的地址,而不是整个数组的拷贝。这种转换意味着在声明函数的时候,以下三种形式都是合法的(同时无论实参是数组还是真的指针也都是合法的)

my_function( int *turnip ) {···}

my_function( int turnip[] ) {···}

my_function( int turnip[200] ) {···}

 

array_name&array_name的异同

前者是指向数组中第一个元素的指针,后者是指向整个数组的指针。
char a[MAX];        //array of MAX characters
char *p = a;         //p
为指向数组的指针
char *pa = &a;      //
该语句是不正确的,pa的类型为'char *',而&a的类型为'char (*)[MAX]'

char (*pb)[MAX] = &a;       //该语句是正确的,pb的类型为'char (*)[MAX]'  

 

#include<stdio.h>

 

void main()

{

char a[5] = {'a','b','c','d','\0'};   

char *p = a;    

 

//运行下面这句后, vc6.0 提示的错误为:cannot convert from 'char (*)[5]' to 'char *'&a的类型应该是指向一个数组的指针

//char *pa = &a;

//所以,应该定义一个指向相同类型和大小的数组的指针来获得“&a”的值

char (*point_to_str)[5];

   point_to_str = &a;

 

 printf("%d\n%d\n",&p, &point_to_str);

 printf("%s\n%s\n", p,  point_to_str);

}

 

运行结果为:

1245044

1245040

abcd

abcd

 

0 0