C++ PRIMER 学习笔记 第四章

Posted on 2022-08-10 10:34  金色的省略号  阅读(27)  评论(0编辑  收藏  举报

第4章 数组和指针

  C++程序应尽量使用vector 和 迭代器类型,避免使用低级的数组和指针

  4.1 数组

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

    4.1.1 数组的定义和初始化

  数组的维数必须用值大于等于1的 常量表达式 定义,此常量表达式只能包含 整形字面值常量枚举常量 或者 用常量表达式初始化的 整形const对象

  在函数体外定义的内置数组,其元素均初始化为0在函数体内 定义的内置数组,其 元素无初始化,除非显式地提供元素初值,否则内置类型的局部数组的元素没有初始化

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

    4.1.2 数组操作

  数组元素可以用下标操作符来访问,数组元素也是从0开始计数,数组下标的正确类型是 size_t,使用循环,可以把一个数组复制给另一个数组

  4.2 指针

  指针 是指向某种类型对象的复合数据类型,是 用于数组的迭代器指向数组中的一个元素(保存元素的地址)对指针进行解引用操作,可获得该指针所指向对象的值

    4.2.1 什么是指针

  指针用于指向对象,具体来说,指针保存的是另一个对象的地址,取地址操作符&只能用于左值,因为 有当变量用作左值时 ,才能取其地址

  许多有用的程序都可不使用数组或指针实现,现代C++程序采用vector类型和迭代器取代一般的数组采用string类型取代C风格字符串

    4.2.2 指针的定义和初始化

  每个指针都有一个与之关联的数据类型,该数据类型决定了指针所指向的对象的类型,理解指针声明语句时,请从右向左阅读,将符号 * 紧贴着指针变量名放置不易引起误解

   一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址指向 某个对象后面的 另一个对象,或者是 0值

  对大多数的编译器来说,如果使用未初始化的指针,会将指针中存放的不确定值视为地址,然后操纵该内存地址中存放的位内容

  除非所指向的对象已经存在,否则不要先定义指针,如果必须分开定义指针和其所指向的对象,则将指针初始化为0(字面值0,或者在编译时可获得0值的const量,或者C++语言从C语言中继承下来的预处理变量 NULL,该变量在cstdlib头文件中定义,其值为0),除了两种例外情况,指针只能初始化或赋值为同类型的变量地址或另一指针

   C++提供了一种特殊的指针类型void*,它可以保存任何类型对象的地址,void*指针支持的操作:与另一个指针进行比较,向函数传递void*指针或从函数返回void*指针,给另一个void*指针赋值,不允许使用void*指针操纵它所指向的对象

    4.2.3 指针操作

  指针提供间接操纵其所指对象的功能,*操作符(解引用操作符)将获取指针所指的对象,解引用操作符,返回指定对象的左值 ,如果对左操作数进行解引用,则修改的是指针所指对象的值,如果没有使用解引用操作,则修改的是指针本身的值

    4.2.4 使用指针访问数组元素

  在表达式中使用 数组名 时,该名字会 自动转换 为指向该 数组   第一个元素的  指针,如果希望使指针指向数组中的一个元素,可使用下标操作符给某个元素定位,然后用取地址操作符&获取该元素的存储地址 

  指针的算术操作:指针上加上或减去一个整形数值,就可以计算出指向数组另一元素的指针值,在指针上加上或减去一个整形数值n等效于获得一个新的指针,该新指针指向指针原来指向的元素之后或之前的第n个元素,指针的算术操作只有在原指针和计算出来的新指针 都指向同一个数组的元素,或指向 该数组存储空间的下一单元  时才是合法的

  在指针上加上一个整形数值,其结果仍然是指针,允许在这个结果上直接进行解引用操作,而不必先把它赋给一个新指针,两个指针减法操作的结果是标准库类型 ptrdiff_t 的数据,也是一种与机器相关的类型,在cstddef头文件中定义,是signed整形,在使用下标访问数组时,实际上是对指向数组元素的指针做下标操作  

  指针加数组长度即可以计算出数组的超出末端指针,C++允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作

    4.2.5 指针和const限定符

  允许把非const对象的地址赋给指向const对象的指针,不能用指向const对象的指针修改基础对象,然而如果该指针指向的是一个非const对象,可用其他方法修改其所指向的对象,指向const的指针常用作函数的形参,确保传递给函数的实际对象在函数中不因为形参而被修改

  与任何const量一样,const指针也必须在定义时初始化,任何企图给const指针赋值的行为即使给指针赋回同样的值,都会导致编译时的错误

  指向const对象的const指针,既不能修改指针所指向对象的值,也不允许修改该指针的指向

  指向const对象的指针,是指不能通过该指针修改其所指向的对象,const指针,是其值不能修改,这两种指针  所指向  对象是否可以修改完全取决于该对象的类型

    int a = 12;
    const int *cp = &a;  //不能通过cp修改a,cp不一定指向const对象
    int *const p  = &a;   //不能修改p的值,即p不能再指向其他的对象

  不能使用void*指针保存const对象的地址,必须使用const void* 类型的指针保存const对象的地址(允许把非const对象的地址赋给指向const对象的指针

    int x=128;
    const void* p = &x;

  4.3 C风格字符串

  C风格字符串,是以  空字符NULL结束  字符数组,不能归结为c语言的类型,也不能归结为C++语言的类型,字符串字面值就是该类型的实例,C++语言通过 (const) char*类型的指针来操纵C风格字符串

  C风格字符串的标准库,C++版本的是cstring,标准库函数 不会检查其字符串参数,传递给这些标准库函数的指针必须具有非零值,并且指向以NULL结束的字符数组中的第一个元素

  指向C风格字符串(空字符NULL结束的字符数组)的指针,关系操作符的比较:是比较指针上存放的地址值,而并不是它们所指向的字符串,如果两个指针指向不同的数组,则该表达式实现的比较没有定义

  字符串的比较和比较结果的解释都须使用标准函数库函数strcmp进行,标准库函数strlen总是假定其参数字符串以NULL字符结束(strlen返回的是字符串的长度,并不包括字符串结束标志),传递给标准库函数strcat和strcpy的第一个实参数组必须具有足够大的空间存放新生成的字符串,使用C风格字符串,则使用标准库函数strncat strncpy 比strcat和strcpy函数更安全

  使用标准库类型string,除了增强安全性外,效率也提高了,因此应该尽量避免使用C风格字符串

    4.3.1 创建动态数组

  动态地分配数组,虽然数组长度是固定的,但动态分配的数组不必在编译时知道长度,可以在运行时才确定数组长度,动态分配的数组将一直存在,直到程序显式释放为止,C语言程序使用一对标准库函数malloc和free在自由储存区中分配存储空间,而 C++语言  则 使用new 和 delete表达式 实现相同的功能

  new表达式返回指向新分配数组的第一个元素的指针,new表达式需要指定指针类型以及在方括号中给出的数组维数,动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化,如果数组元素是内置类型,则无初始化,可以使用跟在数组长度后面的 一对空圆括号,对数组元素做值初始化,对于动态分配的数组,其元素只能初始化为元素类型的默认值

  C++虽然不允许定义长度为0的数组变量,但明确指出,调用new动态创建长度为0的数组是合法的,C++语言为指针提供delete[] 表达式 释放指针所指向的数组空间

    4.3.2 新旧代码的兼容

  由于C风格字符串与字符串字面值具有相同的数据类型,而且是以空字符NULL结束,因此可以把C风格字符串用在任何可以使用字符串字面值的地方,string类提供了一个名为  c_str的成员函数返回C风格字符串 ,因为c_str返回的 指针 指向 const char*类型的 数组,即返回指向字符串数组首地址的指针,该数组存放了与string对象相同的内容,并且以结束符NULL结束

  C++允许使用数组初始化vector对象

  4.4多维数组

  C++中没有多维数组,通常所指的多维数组其实就是 数组的数组

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

  如果 数组的元素 又是 数组,则称为二维数组,其每一维对应一个下标,第一维称为行,第二维则称为列,C++并未限制可用的下标个数,也就是说,可以定义元素是数组(其元素又是数组,以此类推)的数组

  为了对多维数组进行索引,每一维都需要一个下标,当需要访问数组中的特定元素时,必须提供其行下标和列下标,行下标指出需要哪个内部数组,列下标则选取该内部数组的指定元素

  使用 多维数组名 时,实际上将其自动转换为指向该数组第一个元素的指针

#include<stdio.h>
int main()
{
    int arr[2][3] = { 1,2,3, 4,5,6 };       
    int(*q)[3] = &arr[0]; 
    //int(*q)[3] = arr;    //arr 自动转换为,指向,该数组第一个元素( arr[0] )的地址
                           //arr[0] 是一维数组名,一维数组元素的首地址,*arr[0]即arr[0][0] 元素为1
                           //int *p = arr[0]; p++; printf("%d",*p);
    q++; 
    printf("%d",**q);    
    return 0;
}
#include<stdio.h>
int main()
{
    int arr[2][3] = { 1,2,3, 4,5,6 };        
    typedef int int_arr[3]; //定义类型为int[3]的新的类型为int_arr
    int_arr *q = &arr[0]; // int(*q)[3]
    //遍历二维数组 arr
    for( ; q != arr+2; ++q ){  //q++         q  保存的是行的地址
        for( int* p=*q; p != *q+3; ++p ){ //*q  解引用是行的地址
            printf("%d ", *p );
        }
        printf("\n");
    }    
    return 0;
}
#include<stdio.h>
int main()
{
    int arr[2][3] = { 1,2,3, 4,5,6 };       
    int(*q)[3] = &arr[0];    //取arr第一个元素的地址 
                             //int(*q)[3] = ( int(*)[3] ) arr[0];    
    int*p = *q;              //一维指针的指向,即arr第一个元素的地址
                             //int* p = arr[0];
    //打印结果一样
    printf("%p\n",arr[0]);   //第一个元素的地址
    printf("%p\n",&arr[0]);  //取第一个元素的地址
    printf("%p\n",p);        //一维指针的指向
    printf("%p\n",q);        //行指针的指向
    
    return 0;
}