数组和指针
数组和指针
- 数组
- 初始化
- 特殊的字符数组
- 指针的引入
- 指针的定义和初始化
- 指针操作
- 指针和const限定符
- C风格字符串
C++提供了两种类似于vector和迭代器的低级复合类型:数组和指针。与vector类似,数组也可以保存某种类型的一组对象,只是数组一经创建就不能改变其大小。指针则可像迭代器一样遍历数组中的元素。
现代C++程序中应该尽量试用vector和迭代器类型,而避免使用低级的数组和指针。只有在追求速度的情况下才在类实现的内部使用数组和指针。
之所以还要学习数组是因为在未来一段时间内,原来依赖数组的程序仍然大量存在,因此,我们还是的学习并掌握数组的使用方法。
数组
数组的维数必须用值大于1的常量表达式定义。此常量表达式只能包含整形字面值常量、枚举常量或者用常量表达式初始化的整形const对象。非const变量以及运行阶段才知道其值得const得对象都不能用于定义数组得维数。
// 运行时才知道其值得const对象
const unsigned sz = get_size();
int vals[sz]; // ×int a = 10;
const int b = a;
int vals[b]; // ×
初始化
- 没有初始化得内置类型数组只有在全局区域才会自动初始化,在函数内部定义则无自动初始化
- 没有初始化的类类型数组,无论其定义位置,都会调用该类的默认构造函数进行初始化,若没有默认构造函数,则必须为该数组提供显示初始化。
特殊的字符数组
- 使用字符串初始化的字符数组要考虑字符串末尾的空字符符
- 使用一组字符初始化
char cal1[] = "test";
char cal2[] = {'t', 'e', 's', 't'};
char chl3[] = {'t', 'e', 's', 't', '\0'};
char chl4[4] = {'t', 'e', 's', 't', '\0'}; // ×,不可超出数组长度
char chl5[4] = "test"; // ×,不可超出数组长度,未考虑字符串末尾空字符
char chl6[4] = {'t', 'e', 's', 't', '\0'}; // 未初始化部分值0
指针的引入
指针和数组容易产生不可预料的错误,建议使用vector类型和迭代器类型取代一般的数组、采用string类型取代C风格字符串。
指针的定义和初始化
// 一般存在两种声明指针的方式,这两种效果是相同的, 重要的是统一自己的编码风格
// *号靠近标识符
string *ps1;
// *号靠近类型名
string* ps1;
// 当在一条语句中声明多个指针时,一般使用第一种方式
string *ps3, *ps4;
如果可能的话除非指向的对象已经存在,否则不要先定义指针,这样就可以避免定义一个未初始化的指针。
如果必须分开定义指针和其指向的对象,则将指针初始化为0。c++语言虽然无法检测指针被初始化,但可以检测出0值得指针,程序可判断该指针是否指向一个有效对象。
对指针进行初始化或赋值只能使用以下几种类型:
- 0值常量表示式,例如编译时获得0值整型const对象或字面值常量0;
- 类型匹配的对象的资质;
- 另一对象之后的下一地址;可对一个对象的地址进行+运算,从而指向该对象地址的下一地址,在解引用这个地址的时候要考虑是否有效。常用于数组、迭代器之中。
- 同类型的另一个有效指针
c++还提供了一种void*指针类型,它可以保存任何类型对象的地址,它表明该指针与一地址值相关,但不清楚在此地址上对象的类型。因此它只支持几种有限的操作:
- 与另一个指针进行比较;
- 向函数传递或函数返回void*指针;
- 给另一个void*指针赋值;
- 值得注意的是,不允许void*指针操作它所指向的对象,必须的将其转化为对应指针类型之后才可进行操作。
指针操作
指针的算数操作只有在原指针和计算出来的指针新指针都指向同一个数组的元素,或指向该数组存储空间的下一单元时才是合法的。值得注意的是后者虽然合法,但不支持解引用。
// 假设数组ia只有4个元素,则在ia上加10是错误的
int *ip = ia + 10; // ×int *p = &ia[2];
int j = p[1]; // j == ia[3]
int k = p[-2]; // k == ia[0]
指针和const限定符
- 指向const对象的指针
const double *cptr
cptr是一个指向double类型const对象的指针,限制了指向对象的内容不可以修改,但指向的地址可以修改,可以重新指向另一个const double对象。
const double pi = 3.14;
double *ptr = π // ×
const double *cptr = π
不能const对象的地址赋给非const指针。
const int universe = 42;
void *pv = &universe; // ×
const void *cpv = &universe;
void*指针也需要加const才能指向const对象。
int val = 1024;
const int *ptr = &val;
非const对象地址赋值给const指针是没有限制的。值得注意的是无法保证ptr指向的内容不被改变,可能存在其他访问val的方式改变了其值。
- const指针
int *const ptr;
const指针限制指向的地址不可以变,指向的内容可变。
- 指向const对象的const指针
const double *const ptr;
指向的内容和地址都不可变。
- 指针和typedef
typedef string *pstring;
const pstring cstr;const string *cstr; // ❌
string *const cstr; // ✔️
声明const pstring时,const修饰的时pstring的类型,这是一个指针。因此该声明语句应该是把cstr定义为指向string类型对象的const指针。
pstring const cstr;
string *const cstr;
讲const写在类型名后更加容易理解。