C++ Primer 4 第四章 数组和指针

 

第四章 数组和指针

1. 数组

        数组定义中的类型名可以是内置数据类型或类类型;除引用之外,数组元素的类型还可以是任意的复合类型。没有所有元素都是引用的数组。

         数组的维数必须用值大于等于1的常量表达式定义。此常量表达式只能包含整型字面值常量、枚举常量或者用常量表达式初始化的整型 const 对象。非 const 变量以及要到运行阶段才知道其值的 const 变量都不能用于定义数组的维数。

         const unsigned buf_size = 512, max_files = 20;

    int staff_size = 27;            // nonconst

    const unsigned sz = get_size();  // const value not known until run time

    char input_buffer[buf_size];     // ok: const variable

    string fileTable[max_files + 1]; // ok: constant expression

    double salaries[staff_size];     // error: non const variable

    int test_scores[get_size()];     // error: non const expression

    int vals[sz];                    // error: size not known until run time

         虽然 staff_size 是用字面值常量进行初始化,但 staff_size 本身是一个非 const 对象,只有在运行时才能获得它的值,因此,使用该变量来定义数组维数是非法的。而对于 sz,尽管它是一个 const 对象,但它的值要到运行时调用 get_size 函数后才知道,因此,它也不能用于定义数组维数。

如果没有显式提供元素初值,则数组元素会像普通变量一样初始化:

         1)在函数体外定义的内置数组,其元素均初始化为 0。

         2)在函数体内定义的内置数组,其元素无初始化。

         3)不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化;如果该类没有默认构造函数,则必须为该数组的元素提供显式初始化。

显式初始化的数组不需要指定数组的维数值,编译器会根据列出的元素个数来确定数组的长度

如果指定了数组维数,那么初始化列表提供的元素个数不能超过维数值。如果维数大于列出的元素初值个数,则只初始化前面的数组元素;剩下的其他元素,若是内置类型则初始化为0,若是类类型则调用该类的默认构造函数进行初始化:

特殊的字符数组:字符数组既可以用一组由花括号括起来、逗号隔开的字符字面值进行初始化,也可以用一个字符串字面值进行初始化。然而,要注意这两种初始化形式并不完全相同,字符串字面值包含一个额外的空字符(null)用于结束字符串。当使用字符串字面值来初始化创建的新数组时,将在新数组中加入空字符:

        char ca1[] = {'C', '+', '+'};                // no null

        char ca2[] = {'C', '+', '+', '\0'};         // explicit null

        char ca3[] = "C++";     // null terminator added automatically

                   const char ch3[6] = "Daniel"; // error: Daniel is 7 elements

不允许数组直接复制和赋值: 与vector不同,一个数组不能用另外一个数组初始化,也不能将一个数组赋值给另一个数组:

                     int ia[] = {0, 1, 2}; // ok: array of ints

          int ia2[](ia);        // error: cannot initialize one array with another

          int main()

          {

              const unsigned array_size = 3;

              int ia3[array_size]; // ok: but elements are uninitialized!

 

              ia3 = ia;           //  error: cannot assign one array to another

              return 0;

          }

数组操作: 在用下标访问元素时,vector 使用 vector::size_type 作为下标的类型,而数组下标的正确类型则是 size_t:

                              const size_t array_size = 10;

              int ia[array_size]; // 10 ints, elements are uninitialized

              for (size_t ix = 0; ix != array_size; ++ix)

                    ia[ix] = ix;

              return 0;

2. 指针的引入

注意声明格式:string* ps1, ps2; // ps1 is a pointer to string,  ps2 is a string

一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一对象;或者是0值。若指针保存0值,表明它不指向任何对象。未初始化的指针是无效的,直到给该指针赋值后,才可使用它。下列定义和赋值都是合法的:

          int ival = 1024;

          int *pi = 0;       // pi initialized to address no object

          int *pi2 = & ival; // pi2 initialized to address of ival

          int *pi3;          // ok, but dangerous, pi3 is uninitialized

          pi = pi2;          // pi and pi2 address the same object, e.g. ival

          pi2 = 0;           // pi2 now addresses no object

C++ 语言无法检测指针是否未被初始化,也无法区分有效地址和由指针分配到的存储空间中存放的二进制位形成的地址。建议程序员在使用之前初始化所有的变量,尤其是指针。如果必须分开定义指针和其所指向的对象,则将指针初始化为 0。因为编译器可检测出 0 值的指针,程序可判断该指针并未指向一个对象。

对指针进行初始化或赋值只能使用以下四种类型的值:

         1)0 值常量表达式,例如,在编译时可获得 0 值的整型 const 对象或字面值常量 0。

         2)类型匹配的对象的地址。

         3)另一对象末的下一地址。

         4)同类型的另一个有效指针。

除了使用数值0或在编译时值为 0 的 const 量外,还可以使用 C++ 语言从 C 语言中继承下来的预处理器变量 NULL,该变量在 cstdlib 头文件中定义,其值为 0。如果在代码中使用了这个预处理器变量,则编译时会自动被数值 0 替换。因此,把指针初始化为 NULL 等效于初始化为 0 值:

    int *pi = NULL; // ok: equivalent to int *pi = 0;

由于指针的类型用于确定指针所指对象的类型,因此初始化或赋值时必须保证类型匹配。指针用于间接访问对象,并基于指针的类型提供可执行的操作,例如,int 型指针只能把其指向的对象当作 int 型数据来处理,如果该指针确实指向了其他类型(如 double 类型)的对象,则在指针上执行的任何操作都有可能出错。

void* 指针:C++ 提供了一种特殊的指针类型 void*,它可以保存任何类型对象的地址:

void* 指针只支持几种有限的操作:与另一个指针进行比较;向函数传递 void* 指针或从函数返回 void* 指针;给另一个 void* 指针赋值。不允许使用 void* 指针操纵它所指向的对象。

指针和引用的比较:虽然使用引用(reference)和指针都可间接访问另一个值,但它们之间有两个重要区别。第一个区别在于引用总是指向某个对象:定义引用时没有初始化是错误的。第二个重要区别则是赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象(这就是为什么引用必须在定义时初始化的原因)。

指向指针的指针:指针本身也是可用指针指向的内存对象。指针占用内存空间存放其值,因此指针的存储地址可存放在指针中。

使用指针访问数组元素:C++ 语言中,指针和数组密切相关。特别是在表达式中使用数组名时,该名字会自动转换为指向数组第一个元素的指针:

                   int ia[] = {0,2,4,6,8};

            int *ip = ia; // ip points to ia[0]

         如果希望使指针指向数组中的另一个元素,则可使用下标操作符给某个元素定位,然后用取地址操作符 & 获取该元素的存储地址:

                   ip = &ia[4];    // ip points to last element in ia

指针的算术操作:指针的算术操作只有在原指针和计算出来的新指针都指向同一个数组的元素,或指向该数组存储空间的下一单元时才是合法的。如果指针指向一对象,我们还可以在指针上加1从而获取指向相邻的下一个对象的指针。

        两个指针减法操作的结果是标准库类型(library type)ptrdiff_t 的数据。与 size_t 类型一样,ptrdiff_t 也是一种与机器相关的类型,在 cstddef 头文件中定义。

         允许在指针上加减 0,使指针保持不变。更有趣的是,如果一指针具有 0 值(空指针),则在该指针上加 0 仍然是合法的,结果得到另一个值为 0 的指针。也可以对两个空指针做减法操作,得到的结果仍是 0。

在使用下标访问数组时,实际上是对指向数组元素的指针做下标操作。只要指针指向数组元素,就可以对它进行下标操作:

         int ia[] = {0,2,4,6,8};

      int *p = &ia[2];     // ok: p points to the element indexed by 2

         int j = p[1];        // ok: p[1] equivalent to *(p + 1),

       int k = p[-2];       // ok: p[-2] is the same element as ia[0]

如果指针指向 const 对象,则不允许用指针来改变其所指的 const 值。为了保证这个特性,C++ 语言强制要求指向 const 对象的指针也必须具有 const 特性:

         const double *cptr;  // cptr may point to a double that is const

cptr 是一个指向 double 类型 const 对象的指针,const 限定了 cptr 指针所指向的对象类型,而并非 cptr 本身。也就是说,cptr 本身并不是 const。在定义时不需要对它进行初始化,如果需要的话,允许给 cptr 重新赋值,使其指向另一个 const 对象。但不能通过 cptr 修改其所指对象的值。

         把一个 const 对象的地址赋给一个普通的、非 const 对象的指针也会导致编译时的错误:

          const double pi = 3.14;

          double *ptr = π        // error: ptr is a plain pointer

          const double *cptr = π // ok: cptr is a pointer to const

         不能使用 void*保存 const 对象的地址,而必须使用 const void* 类型的指针保存 const 对象的地址:

          const int universe = 42;

          const void *cpv = &universe; // ok: cpv is const

          void *pv = &universe;        // error: universe is const

         允许把非 const 对象的地址赋给指向 const 对象的指针,例如:

          double dval = 3.14; // dval is a double; its value can be changed

          cptr = &dval;       // ok: but can't change dval through cptr

         尽管 dval 不是 const 对象,但任何企图通过指针 cptr 修改其值的行为都会导致编译时的错误。cptr 一经定义,就不允许修改其所指对象的值。如果该指针恰好指向非 const 对象时,同样必须遵循这个规则。

         重要的是要记住:不能保证指向 const 的指针所指对象的值一定不可修改。

const 指针:——本身的值不能修改

                   int errNumb = 0;

        int *const curErr = &errNumb; // curErr is a constant pointer

         与任何 const 量一样,const 指针也必须在定义时初始化。

         指针本身是 const 的事实并没有说明是否能使用该指针修改它所指向对象的值。指针所指对象的值能否修改完全取决于该对象的类型。

指向 const 对象的 const 指针:

                   const double pi = 3.14159;

    // pi_ptr is const and points to a const object

              const double *const pi_ptr = π

用 typedef 写 const 类型定义时,const 限定符加在类型名前面容易引起对所定义的真正类型的误解:

          string s;

          typedef string *pstring;

          const pstring cstr1 = &s; // written this way the type is obscured

          pstring const cstr2 = &s; // all three decreations are the same type

          string *const cstr3 = &s; // they're all const pointers to string

         把 const 放在类型 pstring 之后,然后从右向左阅读该声明语句就会非常清楚地知道 cstr2 是 const pstring 类型,即指向 string 对象的 const 指针。

3. C 风格字符串

字符串字面值的类型就是 const char 类型的数组。C++ 从 C 语言继承下来的一种通用结构是C 风格字符串,而字符串字面值就是该类型的实例。实际上,C 风格字符串既不能确切地归结为 C 语言的类型,也不能归结为 C++ 语言的类型,而是以空字符 null 结束的字符数组:

C++ 语言通过(const)char*类型的指针来操纵 C 风格字符串。

    const char *cp = "some value";

C 风格字符串的标准库函数:要使用这些标准库函数,必须包含相应的 C 头文件,cstring 是 string.h 头文件的 C++ 版本,而 string.h 则是 C 语言提供的标准库。这些标准库函数不会检查其字符串参数。传递给这些标准库函数例程的指针必须具有非零值,并且指向以 null 结束的字符数组中的第一个元素。

                   strlen(s) 返回 s 的长度,不包括字符串结束符 null

                  strcmp(s1, s2) 比较两个字符串 s1 和 s2 是否相同。若 s1 与 s2 相等,返回 0;若 s1 大于 s2,返回正数;若 s1 小于 s2,则返回负数

                  strcat(s1, s2) 将字符串 s2 连接到 s1 后,并返回 s1

                  strcpy(s1, s2)      将 s2 复制给 s1,并返回 s1

                  strncat(s1, s2,n) 将 s2 的前 n 个字符连接到 s1 后面,并返回 s1

                   strncpy(s1, s2, n) 将 s2 的前 n 个字符复制给 s1,并返回 s1

C++ 语言提供普通的关系操作符实现标准库类型 string 的对象的比较。这些操作符也可用于比较指向C风格字符串的指针,但效果却很不相同:实际上,此时比较的是指针上存放的地址值,而并非它们所指向的字符串:

                   if (cp1 < cp2) // compares addresses, not the values pointed to

         字符串的比较和比较结果的解释都须使用标准库函数 strcmp 进行

创建动态数组:每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区或堆。C 语言程序使用一对标准库函数 malloc 和 free 在自由存储区中分配存储空间,而 C++ 语言则使用 new 和 delete 表达式实现相同的功能。

         动态分配数组时,只需指定类型和数组长度,不必为数组对象命名,new 表达式返回指向新分配数组的第一个元素的指针:

                   int *pia = new int[10]; // array of 10 uninitialized ints

         动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化;如果数组元素是内置类型,则无初始化:

          string *psa = new string[10]; // array of 10 empty strings

          int *pia = new int[10];       // array of 10 uninitialized ints

         圆括号要求编译器对数组做值初始化:也可使用跟在数组长度后面的一对空圆括号,对数组元素做值初始化, int *pia2 = new int[10] (); // array of 10 uninitialized ints

         对于动态分配的数组,其元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组元素提供各不相同的初值。

const 对象的动态数组(这样的数组实际上用处不大):如果我们在自由存储区中创建的数组存储了内置类型的 const 对象,则必须为这个数组提供初始化:因为数组元素都是 const 对象,无法赋值。实现这个要求的唯一方法是对数组做值初始化:         

                   const int *pci_bad = new const int[100]; // error: uninitialized const array

                 const int *pci_ok = new const int[100](); // ok: value-initialized const array

         C++ 允许定义类类型的 const 数组,但该类类型必须提供默认构造函数:

              const string *pcs = new const string[100];  // ok: array of 100 empty strings

C++ 虽然不允许定义长度为 0 的数组变量,但明确指出,调用 new 动态创建长度为 0 的数组是合法的:

         char arr[0];            // error: cannot define zero-length array

       char *cp = new char[0]; // ok: but cp can't be dereferenced

         用 new 动态创建长度为 0 的数组时,new 返回有效的非零指针。该指针与 new 返回的其他指针不同,不能进行解引用操作,因为它毕竟没有指向任何元素。而允许的操作包括:比较运算,因此该指针能在循环中使用;在该指针上加(减)0;或者减去本身,得 0 值。

如果不再需要使用动态创建的数组,程序员必须显式地将其占用的存储空间返还给程序的自由存储区。C++ 语言为指针提供 delete [] 表达式释放指针所指向的数组空间:delete [] pia; 该语句回收了 pia 所指向的数组,把相应的内存返还给自由存储区。在关键字 delete 和指针之间的空方括号对是必不可少的:它告诉编译器该指针指向的是自由存储区中的数组,而并非单个对象。

C 风格字符串与 C++ 的标准库类型 string 的比较:通常,由于 C 风格字符串与字符串字面值具有相同的数据类型,而且都是以空字符 null 结束,因此可以把 C 风格字符串用在任何可以使用字符串字面值的地方:

         1)可以使用 C 风格字符串对 string 对象进行初始化或赋值。

         2)string 类型的加法操作需要两个操作数,可以使用 C 风格字符串作为其中的一个操作数,也允许将 C 风格字符串用作复合赋值操作的右操作数。

在要求C风格字符串的地方不可直接使用标准库 string 类型对象。例如,无法使用 string 对象初始化字符指针:

                   char *str = st2; // compile-time type error

         但是,string 类提供了一个名为 c_str 的成员函数,以实现我们的要求:

                   char *str = st2.c_str(); // almost ok, but not quite

         如果 c_str 返回的指针指向 const char 类型的数组,则上述初始化失败,这样做是为了避免修改该数组。正确的初始化应为:

                   const char *str = st2.c_str(); // ok

         c_str 返回的数组并不保证一定是有效的,接下来对 st2 的操作有可能会改变 st2 的值,使刚才返回的数组失效。如果程序需要持续访问该数据,则应该复制 c_str 函数返回的数组。

使用数组初始化 vector 对象:使用数组初始化 vector 对象,必须指出用于初始化式的第一个元素以及数组最后一个元素的下一位置的地址:

                   const size_t arr_size = 6;

             int int_arr[arr_size] = {0, 1, 2, 3, 4, 5};

          // ivec has 6 elements: each a copy of the corresponding element in int_arr

             vector<int> ivec(int_arr, int_arr + arr_size);

         被标出的元素范围可以是数组的子集:

                   vector<int> ivec(int_arr + 1, int_arr + 4);

4. 多维数组

多维数组的初始化:

         int ia[3][4] = {     /*  3 elements, each element is an array of size 4 */

         {0, 1, 2, 3} ,   /*  initializers for row indexed by 0 */

         {4, 5, 6, 7} ,   /*  initializers for row indexed by 1 */

         {8, 9, 10, 11}   /*  initializers for row indexed by 2 */

     };

     int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};//与前面的声明完全等价

     int ia[3][4] = {{ 0 } , { 4 } , { 8 } };//只初始化了每行的第一个元素

     int ia[3][4] = {0, 3, 6, 9};//初始化了第一行的元素,其余元素都被初始化为 0

指针和多维数组:

                   int ia[3][4];      // array of size 3, each element is an array of ints of size 4

              int (*ip)[4] = ia;  // ip points to an array of 4 ints

              ip = &ia[2];       // ia[2] is an array of 4 ints

                   int *ip[4]; // array of pointers to int

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值