• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

C++入门笔记3--数组和指针

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

一、数组:

    数组是由类型名、标识符和维数组成的复合数据类型,类型名规定了存放在数组中的元素的类型,而维数则指定数组中包含的元素个数。

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

 

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

 

     c.显式初始化数组元素:在定义数组时,可为其元素提供一组用逗号分隔的初值,这些初值用花括号{}括起来,称为初始化列表:

         const unsigned array_size = 3;
         int ia[array_size] = {0, 1, 2};
   d.如果内置类型没有显式提供元素初值,则数组元素会像普通变量一样初始化:在函数体外定义的内置数组,其元素均初始化为 0。在函数体内定义的内置数组,其元素无初始化。
 
   e.不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化;如果该类没有默认构造函数,则必须为该数组的元素提供显式初始化。
 
   f.显式初始化的数组不需要指定数组的维数值,编译器会根据列出的元素个数来确定数组的长度:
        int ia[] = {0, 1, 2}; // an array of dimension 3
   g.如果指定了数组维数,那么初始化列表提供的元素个数不能超过维数值。如果维数大于列出的元素初值个数,则只初始化前面的数组元素;剩下的其他元素,若是内置类型则初始化为0,若是类类型则调用该类的默认构造函数进行初始化:
          const unsigned array_size = 5;
          // Equivalent to ia = {0, 1, 2, 0, 0}
          // ia[3] and ia[4] default initialized to 0
          int ia[array_size] = {0, 1, 2};
          // Equivalent to str_arr = {"hi", "bye", "", "", ""}
          // str_arr[2] through str_arr[4] default initialized to the empty string
          string str_arr[array_size] = {"hi", "bye"};
 

      h.数组操作:正如 stringvector 类型,程序员在使用数组时,也必须保证其下标值在正确范围之内,即数组在该下标位置应对应一个元素。

 

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

          char ca1[] = {'C', '+', '+'};                // no null
          char ca2[] = {'C', '+', '+', '\0'};         // explicit null
          char ca3[] = "C++";     // null terminator added automatically
 
二、指针
    a.指针的概念很简单:指针用于指向对象。

      指针保存的是另一个对象的地址:

           string s("hello world");
           string *sp = &s; // sp holds the address of s
    取地址操作符只能用于左值,因为只有当变量用作左值时,才能取其地址。同样地,由于用于 vector 类型、string 类型或内置数组的下标操作和解引用操作生成左值,因此可对这两种操作的结果做取地址操作,这样即可获取某一特定对象的存储地址。
 
    b.每个指针都有一个与之关联的数据类型,该数据类型决定了指针所指向的对象的类型。例如,一个 int 型指针只能指向 int 型对象。
    
    c.连续声明同一类型的多个指针有两种通用的声明风格:
              string* ps1; string* ps2;   或  string *ps1, *ps2; 
 
    d.一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一对象;或者是0值。若指针保存0值,表明它不指向任何对象。未初始化的指针是无效的,直到给该指针赋值后,才可使用它。
    
    e.对指针进行初始化或赋值只能使用以下四种类型的值:
      1. 0 值常量表达式,例如,在编译时可获得 0 值的整型 const 对象或字面值常量 0。除了使用数值0或在编译时值为 0 的 const 量外,还可以使用 C++ 语言从 C 语言中继承下来的预处理器变量 NULL

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

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

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

     f.由于指针的类型用于确定指针所指对象的类型,因此初始化或赋值时必须保证类型匹配。指针用于间接访问对象,并基于指针的类型提供可执行的操作,例如,int 型指针只能把其指向的对象当作 int 型数据来处理,如果该指针确实指向了其他类型(如 double 类型)的对象,则在指针上执行的任何操作都有可能出错。
 
    g.void* 表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型。void* 指针只支持几种有限的操作:与另一个指针进行比较;向函数传递 void* 指针或从函数返回 void* 指针;给另一个 void* 指针赋值。不允许使用 void* 指针操纵它所指向的对象
 
    h.指针和引用的比较:虽然使用引用(reference)和指针都可间接访问另一个值,但它们之间有两个重要区别。第一个区别在于引用总是指向某个对象:定义引用时没有初始化是错误的。第二个重要区别则是赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象(这就是为什么引用必须在定义时初始化的原因)。给指针赋值修改的是指针对象本身,也就是使该指针指向另一对象,指针在不同时刻可指向不同的对象(只要保证类型匹配)。 
 
    i.使用指针访问数组元素:
         1.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
    j.两个指针减法操作的结果是标准库类型(library type)ptrdiff_t 的数据。与 size_t 类型一样,ptrdiff_t 也是一种与机器相关的类型,在 cstddef 头文件中定义。size_tunsigned 类型,而 ptrdiff_t 则是 signed 整型。

 

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

        const double *cptr;  // cptr may point to a double that is const
        1.把一个 const 对象的地址赋给一个普通的、非 const 对象的指针也会导致编译时的错误.
        2.不能使用 void* 指针保存 const 对象的地址,而必须使用 const void* 类型的指针保存 const 对象的地址.
        3.允许把非 const 对象的地址赋给指向 const 对象的指针.本质上来说,由于没有方法分辩指向 const 对象的指针所指的对象是否为 const,系统会把它所指的所有对象都视为 const
    L.const 指针:const 指针——本身的值不能修改,任何 const 量一样,const 指针也必须在定义时初始化。
      int errNumb = 0;       
      int *const curErr = &errNumb; // curErr is a constant pointer
 
三、C 风格字符串
   A.尽管 C++ 支持 C 风格字符串,但不应该在 C++ 程序中使用这个类型。C 风格字符串常常带来许多错误,是导致大量安全问题的根源。
 
   B.字符串字面值的类型就是 const char 类型的数组。实际上,C 风格字符串是以空字符 null 结束的字符数组:
         char ca1[] = {'C', '+', '+'};        // no null, not C-style string
          char ca2[] = {'C', '+', '+', '\0'};  // explicit null
          char ca3[] = "C++";     // null terminator added automatically
          const char *cp = "C++"; // null terminator added automatically
          char *cp1 = ca1;   // points to first element of a array, but not C-style string
          char *cp2 = ca2;   // points to first element of a null-terminated char array
         

     C.C++ 语言通过(const)char*类型的指针来操纵 C 风格字符串。一般来说,我们使用指针的算术操作来遍历 C 风格字符串,每次对指针进行测试并递增 1,直到到达结束符 null 为止:

       const char *cp = "some value";
          while (*cp) {
              // do something to *cp
              ++cp;
          }

     D.如果必须使用 C 风格字符串,则使用标准库函数 strncatstrncpystrcatstrcpy 函数更安全:

           

       const char *cp1 = "A string example";
       const char *cp2 = "A different string";
       char largeStr[16 + 18 + 2]; // to hold cp1 a space and cp2
       strncpy(largeStr, cp1, 17); // size to copy includes the null
       strncat(largeStr, " ", 2);  // pedantic, but a good habit
       strncat(largeStr, cp2, 19); // adds at most 18 characters, plus a null

四、动态数组

    A.创建动态数组:数组类型的变量有三个重要的限制:数组长度固定不变,在编译时必须知道其长度,数组只在定义它的块语句内存在。实际的程序往往不能忍受这样的限制——它们需要在运行时动态地分配数组。虽然数组长度是固定的,但动态分配的数组不必在编译时知道其长度,可以(通常也是)在运行时才确定数组长度。与数组变量不同,动态分配的数组将一直存在,直到程序显式释放它为止。

 

    B.每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区。C 语言程序使用一对标准库函数 mallocfree 在自由存储区中分配存储空间,而 C++ 语言则使用 newdelete 表达式实现相同的功能。

 

    C.动态数组的定义:数组变量通过指定类型、数组名和维数来定义。而动态分配数组时,只需指定类型和数组长度,不必为数组对象命名,new 表达式返回指向新分配数组的第一个元素的指针:

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

new 表达式需要指定指针类型以及在方括号中给出的数组维数,该维数可以是任意的复杂表达式。创建数组后,new 将返回指向数组第一个元素的指针。在自由存储区中创建的数组对象是没有名字的,程序员只能通过其地址间接地访问堆中的对象。

 

D.动态数组的初始化:动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化;如果数组元素是内置类型,则无初始化。也可使用跟在数组长度后面的一对空圆括号,对数组元素做值初始化:

      int *pia2 = new int[10] (); // array of 10 uninitialized ints

     圆括号要求编译器对数组做值初始化,在本例中即把数组元素都设置为0。

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

 

E.const 对象的动态数组:如果我们在自由存储区中创建的数组存储了内置类型的 const 对象,则必须为这个数组提供初始化:因为数组元素都是 const 对象,无法赋值。C++ 允许定义类类型的 const 数组,但该类类型必须提供默认构造函数.

 

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

 

   G:动态空间的释放:C++ 语言为指针提供 delete [] 表达式释放指针所指向的数组空间:       

delete [] pia;

    在关键字 delete 和指针之间的空方括号对是必不可少的:它告诉编译器该指针指向的是自由存储区中的数组,而并非单个对象。

 

  H:由于 C 风格字符串与字符串字面值具有相同的数据类型,而且都是以空字符 null 结束,因此可以把 C 风格字符串用在任何可以使用字符串字面值的地方,反之则不成立:在要求C风格字符串的地方不可直接使用标准库 string 类型对象。

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

const char *str = st2.c_str(); // almost ok, but not quite
c_str 返回的数组并不保证一定是有效的,接下来对 st2 的操作有可能会改变 st2 的值,使刚才返回的数组失效。
 
 I:使用数组初始化 vector 对象:

     C++ 允许使用数组初始化 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 ivec(int_arr, int_arr + arr_size);
 

五、多维数组

   A:严格地说,C++ 中没有多维数组,通常所指的多维数组其实就是数组的数组.如果数组的元素又是数组,则称为二维数组,其每一维对应一个下标:
     ia[2][3] // fetches last element from the array in the last row
     第一维通常称为行(row),第二维则称为列(column)。C++ 中并未限制可用的下标个数,也就是说,我们可以定义元素是数组(其元素又是数组,如此类推)的数组。
 
  B:多维数组的初始化:和处理一维数组一样,程序员可以使用由花括号括起来的初始化式列表来初始化多维数组的元素。对于多维数组的每一行,可以再用花括号指定其元素的初始化式:
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 */
     };
这种初始化方式与下面等价:
// equivalent initialization without the optional nested braces for each row 
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
 
  C:指针和多维数组:与普通数组一样,使用多维数组名时,实际上将其自动转换为指向该数组第一个元素的指针。
     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

窍门在于(数组)变量的名字其实是指针,因此需在标识符前加上 *。如果从内向外阅读 ip 的声明,则可理解为:*ipint[4] 类型——即 ip 是一个指向含有 4 个元素的数组的指针。

在下面的声明中,圆括号是必不可少的:

    int *ip[4]; // array of pointers to int
    int (*ip)[4]; // pointer to an array of 4 ints
就我个人的理解应该和运算符的优先级有关吧

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C#扩展方法学习c#扩展方法奇思妙用发布时间:2022-07-14
下一篇:
一个读取数据库字典的C#类发布时间:2022-07-14
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap