第四章数组和指针
注:现代C++程序采样vector类型和迭代器取代一般的数组、采用string类型取代C风格字符串。
4.1 数组
数组是C++中类似标准库vector类型的内置数据结构。与vector类型相比,数组的显著缺陷在于:数组的长度是固定的,而且程序员无法知道一个给定数组的长度。数组没有获取器容量大小的size操作,也不提供push_back操作在其中自动添加元素。
4.1.1 数组的定义和初始化
数组的位数必须用值大于等于1的常量表达式定义(整型字面值常量、枚举常量或者用常量表达式初始化的整型const对象)。如果没有显示提供元素初始值,在函数体外定义的内置数组元素均初始化为0,在函数体内定义的局部数组则无初始化(相应操作也是无意义)。
const unsigned array_size = 3;
int ia[array_size] = {0, 1, 2};
int ia[] = {0, 1, 2}; //显示初始化可以不需要指定数组的维数值
字符数组既可以用一组字符字面值初始化,也可以用一个字符串字面值进行初始化,但是要考虑其末尾的’\0’。另外注意,数组不允许直接复制和赋值。
4.1.2 数组操作
数组支持下标操作,下标的类型是size_t。防止数组越界,没有别的方法,只有程序员自己注意和彻底测试。
4.2 指针的引入
指针是用于数组的迭代器。指针保存的是一个对象的地址。
4.2.1 指针的定义和初始化
string *ps1, *ps2; //将ps1和ps2都声明为string类型的指针
一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一个对象;或者是0值。若保存为0值,表面它不指向任何对象。指针只有初始化后才能使用(未初始化的指针中存放的不确定值在运行时不能被识别,会操作该内存地址中的内容,引起程序崩溃)。
如果可能的话,除非所指向的对象已经存在,否则不要先定义指针。如果必须分开定义指针和其所指对象,则将指针初始化为0。
对指针进行初始化或赋值只能使用一下四种类型的值:(1)字面值常量0和整型const对象0值(NULL等效,在cstdlib头文件中定义,不是std命名空间中定义);(2)类型匹配的对象地址;(3)另一个对象之后的下一地址;(4)同类型的另一个有效指针。
int *pi = 0; //pi不指向任何对象,且值为0
void*指针可以保存任何类型对象的地址,但是只支持几种有限的操作:与另一个指针进行比较;向函数传递void*指针或从函数返回void*指针;给另一个void*指针赋值。不允许使用void*指针操作它所指向的对象。
4.2.2 指针操作
与迭代器进行解引用操作一样,*操作符将获取指针所指对象。指针的算术操作和迭代器的算术操作以相同方式实现,也有相同的约束。指针加上或者减去一个整型数值,就可以计算出指向后面或者前面的另一元素的指针值。
ptrdiff_t= ip2 – ip1; //ip1和ip2是两个指向同一数组或有一个指向该数组末端的下一个单元。ptrdiff_t类型,在cstddef头文件中定义,是signed整型。
使用下标访问数组时,实际上是使用下标访问指针:
int *p = &ia[2];
int j = p[1]; //p[1]等价于:*(p + 1)
类似vector中的end操作,允许指针指向数组或对象的超出末端的首个地址,但不允许对此地址进行解引用操作。
4.2.3 指针和const控制符
(1)指向const对象的指针(“自以为指向const的指针”,不能通过指针修改对象值)
如果指针指向const对象,则不允许用指针来改变其所指的const值。为了保证这个特性,C++强制要求指向const对象的指针也必须具有const特性:
const double *cptr;
cptr本省并不是const,允许不初始化定义或者指向其它对象。不可以把const对象的地址赋给一个非const对象的指针,若要使用void*指针,则必须使用const void*类型。允许把非const对象的地址赋值给指向const对象的指针,但是同样不允许通过指针修改其值。
指向const对象的指针常用于函数的形参,以确传递给函数的实际对象在函数中不因为形参而被改变。
(2)const指针(指针本身的值不能修改)
int *errNumb = 0;
int *const curErr = &errNumb; //curErr不允许在指向其它对象
(3)指向const对象的const指针(本身和所指对象都不能修改)
const double pi = 3.14159;
const double *const pi_ptr = & pi;
(4)指针和typedef
typedef string *pstring;
const pstring cstr; //其实可以写成:pstring const cstr;这样更容易理解,它们都等价于string *const cstr;
因为,声明const pstring时,const修饰的是pstring类型,这是一个指针。因此,该声明语句应该是把cstr定义为指向string类型对象的const指针。
4.3 C风格字符串
4.3.1 什么是C风格字符串
C风格字符串既不能确切归结为C语言的类型,也不能归结为C++语言的类型,而是以空字符null结束的字符数组。
C语言标准库提供了一系列处理C风格字符串的库函数,要使用这些函数,必须包含头文件csting。这些标准库函数不会检查其字符串参数:
strlen(s) //返回s的长度,不包括null
strcmp(s1,s2) //比较两个字符串s1和s2,相等为0,s1大返回整数,否则为负数
strcat(s1,s2) //将s2连接到s1之后,返回s1
strcpy(s1,s2) //将s2复制给s1
strncat(s1,s2, n) //将s2的前n个字符连接到s1后面
strncpy(s1,s2, n) //将s2的前n个字符复制给s1
永远不要忘记字符串结束符null:
char ca[] = {‘C’, ‘+’, ‘+’};
cout<< strlen(ca) << endl;
其中,ca是一个没有null结束符的字符数组,则计算结果不可预料,会计算从ca开始,直到遇到内存中内容为null的地址结束。
传递给strcat和strcpy的第一个实参数组必须具有足够大的空间存放新生成的字符串。这样的程序很容易出错,若非使用C风格字符串,则使用strncat和strncpy更安全,因为可以适当控制连接或复制的字符个数。但是不管怎样,还是尽量使用string类型。
4.3.2 创建动态数组
每个程序在执行时都占有一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区或堆。C语言中使用malloc和free,C++使用new和delete来分配和释放存储空间。
动态数组的定义:int *pia = new int[n]; //n可以是任意复杂表达式
初始化动态分配的数组:int *pia = new int[n] (); //对数组初始化为全0
对于动态分配的数组,气元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组元素提供各不相同的初值。
允许动态非配空数组,用new动态创建长度为0的数组时,new返回有效的非零指针,但是该指针不能进行解引用操作,只允许进行比较,在该指针上加减,或减去本身得0值。
动态分配的内存空间最后必须进行释放,delete [] pia;回收pia所指向的数组空间。
4.3.3 新旧代码的兼容
string类提供了一个名为c_str的成员函数,以实现将string转化为对应的C风格字符串,且返回指向字符数组首地址的指针,数组末尾为null。c_str返回的指针指向的是const char类型的数组,为了避免修改该数组。
const char *p_str = str.c_str();
使用c_str返回的数组不保证一定有效,因为接下来的对str的操作可能改变str的值,那么刚才返回的数组就失效了。
可以使用数组初始化vector对象,必须指出用于初始化的第一个元素和最后一个元素的下一位置地址:
const aize_t arr_size = 6;
int int_arr[arr_size] = {0, 1, 2, 3, 4, 5};
vector<int> ivec (int_arr + 1, int_arr + 4); //用了int_arr[1],int_arr[2], int_arr[3]
4.4 多维数组
多维数组其实就是数组的数组。
4.4.1 多维数组的初始化
int ia[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8,9, 10, 11} };
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, 1, 2 }; //初始化了前3个元素,其余默认初始化
4.4.2 多维数组的下标引用
4.4.3 指针和多维数组
int (*ip)[4]; //ip是一个指向含有4个int元素的数组指针。
int *ip[4]; //ip是一个含有4个元素的指针数组。
4.4.4 用typedef简化指向多维数组的指针
typedef int int_array[4]; //int_array是int[4]的别名
int_array *p = ia; //ia是一个3*4二维数组
for(int_array *p = ia; p != ia + 3; ++p) //循环行
for(int *q = *p; q != p + 4; ++q) //循环每行的每列
cout<< *q <<endl;