《c和指针》之总结 四

6、指针

         计算机内存中的每个位置都由一个地址标识。通常,邻近的内存位置合成一组,这样就允许存储更大范围的值。指针就是它的值表示内存地址变量。

         无论是程序员还是计算机都无法通过值的位模式来判断它的类型。类型是通过值的使用方法隐式地确定的。编译器能够保证值的声明和值的使用之间的关系是适当的,从而帮助我们确定值的类型。

         指针变量的值并非它所指向的内存位置所存储的值。我们必须使用间接访问来获得它所指向位置存储的值。对一个“指向整型的指针”施加间接访问操作的结果将是一个整型值。

         声明一个指针变量并不会自动分配任何内存。在对指针执行间接访问前,指针必须进行初始化:或者使他指向现有的内存,或者给它分配动态内存。对未初始化的指针变量执行间接访问操作是非法的,而且这种错误常常难以检测。其结果常常是一个不相关的值被修改。这种错误是很难被调试发现的。

         NULL指针就是不指向任何东西的指针。它可以赋值给一个指针,用于表示那个指针并不指向任何值吗。对NULL指针执行间接访问操作的后果因编译器而异,两个常见的后果分别是返回内存位置零的值以及终止程序。

         和任何其他变量一样,指针变量也可以作为左值使用。对指针执行间接访问操作所产生的值也是个左值,因为这种表达式标识了一个特定的内存位置。

         除了NULL指针之外,再也没有任何内建的记法来表示指针常量,因为程序员通常无法预测编译器会把变量放在内存中的什么位置。在极少见的情况下,我们偶尔需要使用指针常量,这时我们可以通过把一个整型值强制转换为指针类型来创建它。

         在指针值上可以执行一些有限的算是运算。你可以把一个整型值加到一个指针上,也可以从一个指针减去一个整型值。在这两种情况下,这个整型值会进行调整,原值将乘以指针目标类型的长度。这样,对一个指针加1将使它指向下一个变量,至于该变量在内存中占几个字节的大小则与此无关。

         然而,指针运算只有作用于数组中其结果才是可以预测的。对任何并非指向数组元素的指针执行算术运算是非法的(但常常很难被检测到)。如果一个指针减去一个整数后,运算结果产生的指针所指向的位置在数组第一个元素之前,那么它也是非法的。加法运算稍有不同,如果结果指针指向数组最后一个元素后面的那个内存位置仍是合法(但不能对这个指针执行间接访问操作),不过再往后就不合法了。

         如果连个指针都指向同一个数组中的元素,那么它们之间可以相减。指针减法的结果经过调整(除以数组元素类型的长度),表示两个指针在数组中相隔多少个元素。如果两个指针并不是指向同一个数组的元素,那么他们之间进行相减就是错误的。

         任何指针之间都可以进行比较,测试他们相等或不相等。如果两个指针都指向同一个数组中的元素,那么他们之间还可以执行< 、<=、 >、 >=等关系,用于判断他们在数组中的相对位置。对两个不相关的指针执行关系运算,其结果是未定义的。

     如果指针并不指向任何有意义的东西,就把它设置为NULL。

 

7、函数

         函数中对于没有参数的函数,它的原型在参数列表中有个一个关键字void 。常见的原型使用方法是把原型放在一个单独的文件中,当其他源文件需要这个原型时,就用#include 指令把这个文件包含进来。这个技巧可以使原型必需的拷贝份数降低,有助于提高程序的可维护性。

         return 语句用于指定从一个函数返回的值。如果return语句没有包含返回值,或者函数不包含任何return 语句,那么函数就没有返回值。在许多其他语言中,这类函数被称为过程。在ANSI C中没有返回值的函数的返回类型应该声明为void。

         当一个函数被调用时,编译器如果无法看到它的任何声明,那么它就假定函数返回一个整数值。对于那些返回值不是整型的函数,在调用之前对它们进行声明是非常重要的,这可以避免由于不可预测的类型转换而导致的错误。对于那些没有原型的函数,传递给函数的实参进行缺省参数提升:char和short类型的实参被转换为int 类型,float类型的实参被转换为double类型。

         函数的参数是通过传值的方式进行传递的,它实际所传递的事实参的一份拷贝。因此,函数可以修改它的形参(也就是实参的拷贝),而不会修改调用程序实际传递的参数。数组名也是通过传值方式传递的,但它传给函数的是一个指向该数组的指针的拷贝。在函数中,如果在数组形参中使用了下标引用操作,就会引发间接访问操作,它实际所访问的是调用程序的数组元素。因此,在函数中修改参数数组的元素实际上修改的是调用程序的数组。这个行为称为传址调用。如果你希望在传递标量参数时也具有传址调用的语义,你可以向函数传递指向参数的指针,并在函数中使用间接访问来访问或修改这些值。

         抽象数据类型,或称黑盒,由接口和实现两部分组成。接口是公开的,它说明客户如何使用ADT所提供的功能。实现是私有的,是实际执行任务的部分。将实现部分声明为私有可以防止客户程序所依赖于模块的实现细节。这样,当需要的时候,我们可以对实现进行修改,这样做并不会影响客户程序的代码。

         有些函数的参数列表包含可变的参数量和类型,他们可以使用stdarg.h头文件所定义的宏来实现。参数列表的可变部分位于一个或多个普通参数(命名参数)的后面,它在函数原型中以一个省略号表示。命名参数必须以某种形式提示可变部分实际所传递的参数数量,而且如果预先知道的话,也可以提供参数的类型信息。当参数列表中的可变部分的参数实际传递给函数时,他们将经历缺省参数提升。可变部分的参数只能从第1个到最后1个依次进行访问。

 

8、数组

         在绝大多数表达式中,数组名的值是指向数组第1个元素的指针。这个规则只有两个例外。sizeof 返回整个数组所占用的字节而不是一个指针所占用的字节。单目操作符&返回一个指向数组的指针,而不是一个指向数组第1个元素的指针的指针。

         除了优先级不同以外,下标表达式array【value】和间接表达式*(array + (value))是一样的。因此,下标不仅可以用于数组名,也可以用于指针表达式中。不过这样一来,编译器就很难检查下标的有效性。指针表达式可能比下标表达式效率更高,但下标表达式绝不可能比指针表达式效率更高。但是,以牺牲程序的可维护性为代价获得程序的运行时效率的提高可不是个好主意。

         指针和数组并不相等。数组的属性和指针的属性大相径庭。当我们声明一个数组时,它同时也分配了一些内存空间,用于容纳数组元素。但是,当我们声明一个指针时,它只分配了用于容纳指针本身的空间。

         当数组名作为函数参数传递时,实际传递给函数的是一个指向数组第1个元素的指针。函数所接收到的参数实际上是原参数的一份拷贝,所以函数可以对其进行操纵而不会影响实际的参数。但是,对指针参数执行间接访问操作允许函数修改原先的数组元素。数组形参既可以声明为数组,也可以声明为指针。这两种声明形式只有当他们作为函数的形参时才是相等的。

         数组也可以用初始值列表进行初始化,初始值列表就是由一对花括号包围的一组值。静态变量(包括数组)在程序载入到内存时得到初始值。自动变量(包括数组)每次当执行流进入它们声明所在的代码时都要使用隐式的赋值语句重新进行初始化。如果初始值列表包含的值的个数少于数组元素的个数,数组最后几个元素就用缺省值进行初始化。如果一个被初始化的数组长度在声明中未给出,编译器将使这个数组的长度设置为刚好能容纳初始值列表中所有值的长度。字符数组也可以用一种很想字符串常量的快速方法进行初始化。

         多维数组实际上是一维数组的一种特型,就是它的每个元素本身也是一个数组。多维数组中的元素根据行主序进行存储,也就是最右边的下标率先变化。多维数组名的值是一个指向它第1个元素的指针,也就是一个指向数组的指针。对该指针进行运算将根据它所指向数组的长度对操作数进行调整。多维数组的下标引用也是指针表达式。当一个多维数组名作为参数传给一个函数时,它所对应的函数形参的声明中必须显式指明第2维(和接下去所有维)的长度。由于多维数组实际上是复杂元素的一维数组,一个多维数组的初始化列表就包含了这些复杂元素的值。这些值的每一个都可能包含嵌套的初始值列表,由数组各维的长度决定,只有第1维的长度会被自动计算出来。

         我们还可以创建指针数组。字符串的列表可以以矩阵的形式存储,也可以以指向字符串常量的指针数组形式存储。在矩阵中,每行必须与最长字符串的长度一样长,但它不需要任何指针。指针数组本身要占用空间,但每个指针所指向的字符串所占用的内存空间就是字符串本身的长度。

         只要有可能,函数的指针形参都应该声明为const。在有些环境中,使用register关键字提高程序的运行时效率。

 

posted @ 2010-05-23 16:22  stardream  阅读(311)  评论(0)    收藏  举报