ITEEDU

5.5 指针与常量限定符

const限定符可以使程序员告诉编译器特定变量的值不能修改。

软件工程视点5. 1

const限定符可以执行最低权限原则。利用最低权限原则正确设计软件可以大大减少调试时间和不正确的副作用,使程序更容易修改与维护。

可移植性提示5.2

尽管ANSI C和C++中定义了const定符,但有些编译器无法正确实现。

几年来,大量C语言遗留代码都是在没有const限定符的情况下编写的。因此,使用旧版C语言代码的软件工程有很大的改进空间。许多目前使用ANSI C和C++的程序员也没有在程序中使用const限定符,因为他们是从C语言的早期版本开始编程的,这些程序员错过了许多改进软件工程的好机会。

函数参数使用或不用const限定符的可能性有六种,两种用按值调用传递参数,四种按引用调用传递参数,根据最低权限原则来进行选择。在参数中向函数提供完成指定任务所需的数据访问,但不要提供更多权限。

第3章曾经介绍,按值调用传递参数时,函数调用中要生成参数副本并将其传递给函数。如果函数中修改副本,则调用者的原值保持不变。许多情况下,需要修改传入函数的值以使函数能够完成任务。但有时即使被调用函数只是操作原值的副本,也不能在被调用函数中修改这个值。

假设函数取一个单下标数组及其长度为参数,并打印数值。这种函数应对数组进行循环并分别输出每个数组元素。函数体中用数组长度确定数组的最高下标,以便在打印完成后结束循环。在函数体中不能改变这个数组长度。

软件工程视点5.2

如果函数体中不能修改传递的值,则这个参数应声明为const以避免被意外修改。

如果试图修改const类型的值,则编译器会捕获这个错误并发出一个警告或错误消息(取决于特定的编译器)。

轶件工程视点5.3

按值调用时,只舱在调用函数中改变一个值。这个值通过函数返回值进行赋值。要在调用函数中改变多个值,就要按引用传递多个参数。

编程技巧5.3

使用函数之前,检查函数原型以确定可以修改的参数。

将指针传递给函数有四种方法:非常量数据的非常量指针、常量数据的非常量指针、非常量数据的常量指针和常量数据的常量指针。每种组合提供不同的访问权限。

最高访问权限是非常量数据的非常量指针,可以通过复引用指针而修改,指针可以修改成指向其他数据。声明非常量数据的非常量指针时不用const。这种指针可以接收函数中的字符串,用指针算法处理或修改字符串中的每个字符。图5.10中的函数convertToUppercase声明参数sPtr(char*sPtr)为非常量数据的非常量指针。函数用指针算法一次一个字符地处理字符串string。字符串中,st到,x,的字符用函数toupper变为相应的大写字母,其余字符不变。函数toupper取一个字符作为参数。

如果是小写字母,则返回相应的大写字母,否则返回原字符。函数toupper是字符处理库ctype.h中(见第16章)的一部分。

常量数据的非常量指针,指针可以修改成指向其他数据,但数据不能通过指针修改。这种指针可以接收函数的数组参数,函数处理数组每个元素而不修改数据。例如,图5.1l的函数printCharacters将参数sPtr声明为const char*类型.表示“sPtr是字符常量的指针”。函数体用for循环输出字符串中的每个字符,直到遇到null终止符。打印每个字符之后,指针sPtr递增,指向字符串中下一个宇符。

// Fig. 5.10: fig0510.cpp
 // Converting lowercase letters to uppercase letters
 // using a non-constant pointer to non-constant data
 #include < iostream.h>
 #include < ctype.h>
 void convertToUppercase( char * );
 int main()
 {
   char string[] = "characters and $32.98";
   cout << "The string before conversion is: "<< string;
   convertToUppercase( string );
   cout << "\nThe string after conversion is:  "
   cout << string << endl;
   return 0;
 }
 void convertToUppercase{ char *sPtr )
 {
   while ( *sPtr != '\0' ) {
      if (*sPtr >= 'a' && *sPtr <= 'z' )
        *sPtr = toupper( *sPtr );  // convert to uppercase
     ++sPtr;  // move sPtr to the next character
     }
 }

输出结果:

The string before conversion is: characters and $32.98

The string after conversion is: CHARACTERS AND $32.98

图5.10将字符串变成大写

// Fig. 5.11:fig05 ll.cpp
 // Printing a string one character at a time using
 // a non-constant pointer to constant data
 #include < iostream.h>
 void printCharacters( const char * );
 int main()
 {
   char string[] = "print characters of a string";
   cout << "The string is:\n";
   printCharacters( string );
   cout << endl;
   return 0;
 }
 // In printCharacters, sPtr is a pointer to a character
 // constant. Characters cannot be modified through sPtr
 // (i.e., sPtr is a "read-only" pointer).
 void printCharacters( const char *sPtr )
 {
   for ( ; *sPtr != '\0'; sPtr++ )  // no initialization
     cout << *sPtr;
 }  

输出结果:

The string is:

print characters of a string

图5.11 用常量数据的非常量指针打印字符串(一次打印一个字符)

图5.12演示了函数接收常量数据的非常量指针,并试图通过指针修改数据在编译时产生的语法错误消息。

// Fig. 5.12: fig05_12.cpp
 // Attempting to modify data through a
 // non-constant pointer to constant data.
 #include < iostream.h>
 void f( const int* );
 int main()
 {
   int y;
   f( &y );    // f attempts illegak modification
   return 0;
 }
 // In f, xPtr is a poin er to an integer constant
 void f( const int *xPtr )
 {
   *xPtr = 100;  // cannot modify a const object
 }

输出结果:

Compiling FIG05 12.CPP:

Error FIG05 12.CPP 20: Cannot modify a const object

Warning FIGOS_12.CPP 21: Parameter 'xPtr' is never used

图5.12 试图通过常量数据的非常量指针修改数据

众所周知,数组是累计数据类型,用同一名称存放相同类型的相关数据项。第6章将介绍另一种形式的累计数据类型——结构(structure),也称为记录(record)。结构可以用同一名称存放不同类型的相关数据项(例如,存放公司每个员工的信息)。调用带数组参数的函数时,数组模拟按引用调用自动传递给函数。但结构则总是按值调用,传递整个结构的副本。这就需要复制结构中每个数据项目并将其存放在计算机函数调用堆栈中的执行时开销(函数执行时用函数调用堆栈存放函数调用中使用的局部变量)。结构数据要传递绐函数时,可以用常量数据的指针(或常量数据的引用)得到按引用调用的性能和按值调用对数据的保护。传递结构的指针时,只要复制存放结构的地址。在4字节地址的机器上只要复制4字节内存而不是复制结构的几百或几千字节。
性能提示5.7

要传递结构之类的大对象时,可以用常量数据的指什(或常量数据的引用)得到按引用调用的性能和按值调用对数据的保护。

非常量数据的常量指针总是指向相同的内存地址,该地址中的数据可以通过指针修改。这里的数组名是默认的。数组名是数组开头的常量指针,数组中的所有数据可以用数组名和数组下标访问和修改。非常量数据的常量指针可以接收数组为函数参数,该函数只用数组下标符号访问数组元素。声明为const的指针应在声明时初始化(如果是函数参数,则用传入函数的指针初始化)。图5.13的程序想修改常量指针,指针ptr的类型声明为int *const,图中的声明表示“ptr是整数的常量指针”,指针用整型变量x的地址初始化。程序要将y的地址赋给ptr,但产生一个错误消息。注意数值7赋

给*ptr时不产生错误,说明ptr所指的值是可修改的。
常见编程错误5.6

声明为const的指针不在声明时初始化是个语法错误。

常量数据的常量指针的访问权限最低。这种指针总是指向相同的内存地址,该内存地址的数据不能修改。数组传递到函数中,该函数只用数组下标符号读取,而不能修改数组。图5.14的程序演示声明指针变量ptr为const int*const,表示“ptr是常量整数的常量指针”。图中显示了修改ptr所指数据和修改存放指针变量的地址时产生的错误消息。注意输出ptr所指的值时不产生错误,因为输出语句中没有进行修改。

// Fig. 5.13:fig05 13.cpp
 // Attempting to modify a constant pointer to
 // non-constant data
 #include < iostream.h>
 int main()
 {
   int x, y;
  int * const ptr = &x; // ptr is a constant pointer to an
                     // integer. An integer can be modified
                     // through ptr, but ptr always points
                     // to the same memory location.
  *ptr = 7;
  ptr = &y;
  return 0;
 }

输出结果:

Compiling FIG05 13.CPP:

Error FIG05 13.CPP 15: Cannot modify a const object

Warning FIGOS_13.CPP 18: 'y' is declared but never used

图5.13修改非常量数据的常量指针

 // Fig. 5.141 fig05 14.cpp
 // Attempting to modify a constant pointer to
 // constant data.
 #include < iostream.h>
 int main()
 {
   int x = 5, y;
  const iht *const ptr = &x; // ptr is a constant pointer to a
                         // constant integer, ptr always
                         // points to the same location
                         // and the integer at that
                         // location cannot be modified.
  eout << *ptr << endl;
  *ptr = 7;
  ptr = &y;
  return 0;
 }

输出结果:

Compiling FIG05 14.CPP:

Error FIG05_14.CPP 16: Cannot modify a const object

Error FIG05_14.CPP 17: Cannot modify a const object

Warning FIG05_14.CPP 20: 'y' is declared but never used

图5.14 修改常量数据的常量指针