第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; }