C 语言中的多态数据结构/指针
指针是 C 语言中最基本的构造之一。指针是一个变量,它存储另一个变量的地址(在内存中),供函数引用。通过使用指针,我们能够实现更高层次的数据操作,因为在 C 语言中,没有开发出任何算法支持在创建它们的函数之外的函数中修改静态变量。
开发人员可以使用指针运算符 (*) 显式声明指针
int *p_int ;
本书中使用的标准约定是在被指向的变量前面加上 "p_",以表示它是指针。这在函数中存在多个相同类型但有些是指针,有些不是指针时很有用。要访问指针指向的信息,程序员必须使用解引用运算符(也是星号)来表示引用。
printf( "%d" , *p_int ) ;
如果一个变量不是指针,但需要在被调用的函数内部修改,那么可以使用地址运算符 (&) 来创建一个 "即时" 指针传递给函数。许多程序员理解 scanf() 函数需要在读取的变量前面加上地址运算符,但并非所有程序员都知道原因。这是因为 scanf() 需要影响存储信息的变量。
指针通常用于跟踪动态分配的内存。在本演示中,指针是多余的,但它将说明指针的原理。
#include <stdio.h>
int main( int argc , char *argv[] ) {
int i ;
int *p_i = &i ;
i = 5 ;
printf( "%d " , i ) ;
*p_i = 7 ;
printf( "%d\n" , *p_i ) ;
return 0 ;
}
首先,创建一个整数 i,它是 main() 的静态变量。然后,创建指针 p_i 并将其值设置为 i 的地址。接下来,将 i 的值赋值为 5 并打印。最后,将 p_i 指向的 整数赋值为 7 并打印。
注意解引用和地址运算符的使用。正确使用指针、解引用和地址运算符是理解多态的关键。
指针最重要的用途可能是跟踪动态分配(在运行时)的内存。内存使用 malloc() 函数动态分配。 malloc() 只有一个参数(要分配的空间大小,以字节为单位),并返回指向 void 的指针,该指针通常被重新强制转换为指向其他类型的指针。动态内存分配的挑战在于类型大小可能依赖于机器,这意味着一台计算机上的整数在内存中的大小可能与另一台计算机上的整数不同。为了克服这个问题,C 描述了一个 sizeof 运算符,它接受一个类型名称作为参数,并返回该类型的大小(以字节为单位),作为一个整数。例如,要为一个字符动态分配内存空间,开发人员可以编写
char *p_char ;
p_char = (char *)malloc( sizeof ( char ) ) ;
更复杂地说,动态内存分配通常用于编译时不知道要保留的空间大小的情况。在下面的程序中,系统会询问用户他们想要存储多少个整数,并存储它们。
#include <stdio.h>
#include <stdlib.h>
int main( int argc , char *argv[] ) {
int i , amount , *p_int ;
printf( "How many integers would you like to store? " ) ;
scanf( "%d" , &amount ) ;
p_int = (int *)malloc( sizeof ( int ) * amount ) ;
printf( "Please enter %d integers, separated with a space, to store. " , amount ) ;
for ( i = 0 ; i < amount ; i++ ) scanf( "%d" , p_int[i] ) ;
for ( i = 0 ; i < amount ; i++ ) printf( "%d " , p_int[i] ) ;
printf( "\n" ) ;
return 0 ;
}
注意 stdlib 是如何在头文件中包含的。malloc() 在 stdlib.h 中声明,因此它必须与 stdio.h 一起包含在使用它的程序中。
还要注意 malloc() 保留的空间是如何使用数组的标准语法引用的。这是因为 malloc() 按顺序保留空间(全部排成一行),因此可以将指针运算(下面讨论)应用于它。换句话说,当 malloc() 为一个给定类型的多个元素保留空间时,它会为该类型的数组分配空间。
除了标准成员运算符 (.) 之外,ANSI C 还包括一个不同的成员运算符符号 ->。它同时执行两个操作:它解引用运算符前面的结构体名称,并计算其成员(运算符后面的)。这在使用指针引用结构体时很有用。使用上一章中的员工数据结构
struct employee_data *p_search ;
p_search = (struct employee_data *)malloc( sizeof ( struct employee_data ) ) ;
strcpy(p_search -> name, "Ryan");
strcpy((*p_search).name, "Ryan");
最下面的两个语句执行完全相同的操作。因为在本书的绝大部分内容中都会使用指向结构体的指针,所以会更多地使用解引用-成员运算符。请注意,子成员仍然必须使用正常的成员运算符引用。
C 语言中的函数只能返回一个值。这是过程式编程范式的必然结果。但是,有时需要更改多个变量。为此,必须将指向该变量的指针传递给被调用的函数。有关此主题的更多信息,请参阅附录 1,函数调用。那里的信息非常宝贵,请务必在继续之前仔细阅读。本书的其余部分假设读者已经阅读并理解了附录 1 的内容。
执行指针运算是一个非常简单的概念,但实际操作起来有些困难。对数组进行指针加法是隐式完成的,使用下标运算符 []。以下语句将返回 TRUE
int A[4] ;
( A[2] == *(A + 2) ) ;
对指针进行加法会导致引用地址 "向前移动" 指定的单位数,乘以类型的尺寸。因此,&A[2] 是 A[0] 的位置,加上 两个整数在内存中的大小(从而得到数组中的第三个元素,因为数组从索引 0 开始)。指针运算也可以应用于联合体和结构体,但这种方法不像成员引用那样常用。
C 语言的一个非常强大的功能是能够获取函数的地址并将其存储为指针。这样,理论上可以编写一个函数,然后让该函数调用一个仅在运行时才知道的另一个函数(从而减少了所需的条件编程量)。每个 C 程序员都熟悉标准函数声明语法
return_type function( parameter_1 , parameter_2 ... ) ;
与变量类似,函数也有类型。假设它们不是 void 类型,所有函数都会返回数据,并且该数据必须有类型。作为这种结构的副作用,程序员可以声明指向函数的指针。
return_type (*p_function)( parameter_1 , parameter_2 ... ) ;
此代码可用于在同一行代码中执行任意数量的函数,在本文档中的文本处理程序中演示。
#include <stdio.h>
void print_reverse( char *string ) ;
void print_normal( char *string ) ;
int main( int argc , char *argv[] ) {
char input_string[32] ;
int state , i ;
void (*p_function)( char *string ) ;
for ( i = 0 ; i < 32 ; i++ )
input_string[i] = '\0' ;
printf( "Please enter a short line of text: " ) ;
scanf( "%s" , input_string ) ;
printf( "Would you like to print this string in reverse? 1 for yes, 0 for no. " ) ;
scanf( "%d" , &state ) ;
if ( state == 1 )
p_function = print_reverse ;
else if ( state == 0 )
p_function = print_normal ;
else {
printf( "Error: Must enter 0 or 1. Exiting." ) ;
exit 1;
}
p_function( input_string ) ;
return 0 ;
}
void print_reverse( char *string ) {
int i ;
for ( i = 31 ; i >= 0 ; i-- ) {
if ( string[i] != '\0' )
putchar( string[i] ) ;
}
printf( "\n" ) ;
}
void print_normal( char *string ) {
int i = 0 ;
while ( ( i < 32 ) && ( string[i] != '\0' ) ) {
putchar( string[i] ) ;
i++ ;
}
printf( "\n" ) ;
}
在这个程序中,有两个函数,print_reverse() 和 print_normal()。一个只是打印字符串,另一个则以相反顺序打印字符串的内容。但是,main() 中的实际函数调用在这两种情况下都是一样的
p_function( input_string ) ;
这是通过 C 语言能够通过指向该函数的指针引用函数,并正确地将参数传递给该函数而实现的。