C++ Primer 第四版读书笔记(三)之数组与指针
C++语言提供了两种类似于vector和迭代器类型的低级复合类型——数组与指针。与vector类型相似,数组也可以保存某种类型的一组对象;而它们的区别在于,数组的长度是固定的。数组一经创建,就不允许添加新的元素。指针则可以像迭代器一样用于遍历和检查数组中的元素。
现代C++程序应尽量使用vector和迭代器类型,而避免使用低级的数组和指针。设计良好的程序只有在强调速度时才在类实现的内部使用数组和指针。
数组是C++语言中类似于标准库vector类型的内置数据结构。与vector类似,数组也是一种存储单一数据类型对象的容器,其中每个对象都没有单独的名字,而是通过它在数组中的位置对它进行访问。
与vector类型相比,数组显著缺陷在于:数组的长度是固定的,而且程序员无法知道一个给定数组的 长度。数组没有获取起容量大小的size操作,也不提供push_back操作在其中自动添加元素。如果需要更改数组的长度,程序员只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组空间去。
一、数组
数组是由类型名、标识符和维数组成的复合数据类型,类型名规定了存放在数组中的元素的类型,而维数则指定数组中包含的元素个数。
注:数组定义中的类型名可以是内置数据类型或类类型;除引用之外,数组元素的类型还可以是任意的复合类型。没有所有元素都是引用的数组。
1.1 数组的定义和初始化
数组的维数必须用值大于等于1的常量表达式定义。此常量表达式只能包含整型字面值常量、枚举常量或者用常量表达式初始化的整型const对象。非const常量以及要运行阶段才知道其值的const变量都不能用于定义数组的维数。
1.1.1 显式初始化数组元素
在定义数组时,可为其元素提供一组用逗号分隔的初值,这些初值用花括号{}括起来,成为出书话列表。
如果没有显式提供元素初值,则数组元素会像普通变量一样初始化:
1、在函数体外定义的内置数组,其元素均初始化为0;
2、在函数体内定义的内置数组,其元素无初始化;
3、不管数组在哪儿定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化;如果该类没有默认构造函数,则必须为该数组提供显式的初始化。
注:除非显式地提供元素初值,否则内置类型的局部数组的元素没有初始化。此时,除了给元素赋值外,其他使用这些元素的操作没有定义。
1.1.2 特殊的字符数组
字符数组既可以用一组由花括号括起来、逗号隔开的字符字面值进行初始化,也可以用一个字符串字面值进行初始化。然而要注意这两种初始化形式并不完全相同,字符串字面值包含一个额外的空字符(null)用于结束字符串。当使用字符串字面值来初始化创建的新数组时,将在新数组中假如空字符。
1.1.3 不允许数组直接复制和赋值
与vector不同,一个数组不能用另外一个数组初始化,也不能将一个数组赋值给另外一个数组,这些操作都是非法的。
1.2 数组操作
与vector元素一样,数组元素可用下标操作符来访问,数组元素也是从0开始计数。
1.2.1 检查数组下标值
正如string和vector类型,程序员在使用数组时,也必须保证其下标值在正确范围之内,即数组在在该下标位置对应一个元素。
二、指针的引入
vector的遍历可使用下标或迭代器实现,同理,也可用下标或指针来遍历数组。指针是指向某种类型对象的复合数据类型,是用于数组的迭代器:指向数组中的一个元素。在指向数组元素的指针上使用解引用操作符*和自增操作符++,与在迭代器上的用法类似。对指针进行解引用操作,可获得该指针所指向对象的值。而当指针做自增操作时,则移动指针使其指向数组中的下一个元素。
2.1 什么是指针
指针的概念很简单:指针用于指向对象。与迭代器一样,指针提供对其所指对象的间接访问,只是指针结构更通用一些。与迭代器不同的是,指针用于指向单个对象,而迭代器只能用于访问容器内中的元素。
具体来说,指针保存的是另一个对象的地址。
2.2 指针的定义和初始化
每个指针都有一个与之关联的数据类型,该数据类型决定了指针所指向的对象的类型。
2.2.1 指针变量的定义
string *pstring;
string *p1, *p2, p3;
2.2.2 另一种声明指针的风格
string* ps;
string* p1, p2;
2.2.3 连续声明多个指针易导致混淆
一般来说定义指针才采用风格为:将符号*紧贴着指针变量名放置。
2.2.4 指针可能的取值
一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一对象;或者是0值。若指针保存0值,表明它不指向任何对象。未初始化的指针是无效的,直到给该指针赋值后,才可使用它。
2.2.5 避免使用未初始化的指针
对于大多数编译器来说,如果使用未初始化的指针,会将指针中存放的不确定值视为地址,然后操纵该内存地址中存放的位内容。使用未初始化的指针相当于操纵这个不确定地址中存储的基础数据。因此,在对未初始化的指针进行解引用时,通常会导致程序崩溃。
2.2.6 指针初始化和赋值操作的约束
(1)、0值常量表达式;
(2)、类型匹配的对象的地址;
(3)、另一对象末的下一地址;
(4)、同类型的另一指针。
2.2.7 void*指针
C++提供了一种特殊的指针类型void*,它可以保存任何类型对象的地址。
void*指针只支持几种有限的操作:与另一个指针经行比较;向函数传递void*指针或从函数返回void*指针;给另外一个void*指针赋值。不允许使用void*指针操纵它所指向的对象。
2.3 指针操作
2.3.1 生成左值的解引用操作
解引用操作符返回指定对象的左值,利用这个功能可修改指针所指对象的值。
2.3.2 指针和引用的比较
虽然使用引用和指针都可间接访问另外一个值,但它们之间有两个重要区别。
1、引用总是指向某个对象:定义引用时没有初始化式错误的。
2、赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另外一个对象关联。
引用一经初始化,就始终指向同一个特定对象。
2.3.3 指向指针的指针
2.4 使用指针访问数组元素
C++语言中,指针和数组密切相关。特别是在表达式中使用数组名时,该名字会自动转换为指向数组第一个元素的指针。
2.4.1 指针的算术操作
使用指针的算术操作在在指向数组某个元素的指针上加上(或减去)一个整型数值,就可以计算出指向数组另一元素的指针值。
通常,在指针上加上(或减去)一个整型数值n等效于获得一个新指针,该新指针指向指针原来指向的元素之后(或之前)的第n个元素。
允许在指针上加减0,使指针保持不变。更有趣的是,如果一个指针具有0值(空指针),则在该指针上加0仍然是合法的,结果得到另一个值为0的指针。也可以对两个空指针做减法操作,得到的结果仍然是0。
2.4.2 解引用和指针算术操作之间的相互作用
在指针上加一个整型数值,其结果仍然是指针。允许在这个结果上直接进行解引用操作,而不必先把它赋给一个新指针。
2.4.3 下标和指针
int *p = &ia[2];
int j = p[1];//ok, p[1] equivalent to *(p+1)
//p[1] is the same element as ia[3]
int k = p[-2];//ok, p[-2] is the same element as ia[0]
2.4.4 计算数组的超出末端指针
2.4.5 输出数组元素
2.4.6 指针是数组的迭代器
2.5 指针和const限定符
2.5.1 指向const对象的指针
如果指针指向const对象,则不允许用指针来改变所指的const值。
注:不能使用指向const对象的指针修改基础对象,然而如果该指针指向一个非const对象,可用其他方法修改其所指的对象。
2.5.2 const指针
除指向const对象的指针外,C++语言还提供了const指针——本身的值不能修改。
2.5.3 指向const对象的const指针
const double pi = 3.14159;
const double *const pi_ptr = π
本例中,既不能修改pi_ptr所指向对象的值,也不允许修改该指针的指向(即pi_ptr中存放的地址值)。
2.5.4 指针和typedef
三、C风格字符串
实际上,C风格字符串既不能确切的归结为C语言的类型,也不能归结为C++语言的类型,而是以空字符null结束的字符数组。
3.1.1 C风格字符串的使用
C++语言通过(const)char *类型的指针来操纵C风格字符串。一般来说,我们使用指针的算术操作来遍历C风格字符串,每次对指针进行测试并递增1,直到到达结束符null为止。
3.1.2 C风格字符串的标准库函数
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 |
3.1.3 永远不要忘记字符串结束符null
3.1.4 调用者必须确保目标字符串具有足够的大小
3.1.5 尽可能使用标准库类型string
3.2 创建动态数组
数组类型的变量有三个重要的限制:数组长度固定不变,在编译时必须知道其长度,数组只在定义它的块语句内存在。
每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区或堆。
3.3 多维数组