第一章 基础知识
1.7 指针、数组和引用
最基本的数据集合类型就是数组——一种空间连续分配的相同类型的元素序列。这基本上就是硬件所提供的机制。元素类型为char的数组可像下面这样声明:
char v[6]; //含有6个字符的数组
类似地,指针可这样声明:
char* p; //指向字符的指针
在声明语句中,[]表示“……的数组”,*表示“指向……”。所有数组的下标都从0开始,因此v包含6个元素,从v[0]到v[5]。数组的大小必须是一个常量表达式。一种指针变量中存放着一个对应类型的对象的地址:
char* p = &v[3]; //p指向v的第4个元素
char x = *p; //*p是p所指的对象
在表达式中,前置一元运算符*表示“……的内容”,而前置一元运算符&表示“……的地址”。可以用下面的图形来表示上述初始化定义的结果。
考虑将一个数组的10个元素拷贝到另一个数组的任务:
void copy_fct(){
int v1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int v2[10]; //v2将成为v1的副本
for(auto i = 0; i!=10; i++) //拷贝元素
v2[i] = v1[i];
//...
}
上面的for语句可以这样解读:“将i置为0。当i不等于10时,拷贝第i个元素并递增i”。当作用于一个整型或浮点型变量时,递增运算符++执行简单的加1操作。C++还提供了一种更简单的for语句,称为范围for语句,它可以用最简单的方式遍历一个序列:
void print(){
int v[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for(auto x:v) //对于v中的每个x
cout<<x<<’\n’;
for(auto x : {10, 21, 32, 43, 54, 65})
cout<<x<<’\n’;
//...
}
第一个范围for语句可以解读为“从头到尾遍历v的每个元素,将其副本放入x并打印”。注意,当我们使用一个列表初始化数组时,无须指定其大小。范围for语句可用于任意的元素序列。
如果不希望将值从v拷贝到变量x中,而只是令x引用一个元素,则可编写如下代码:
void increment(){
int v1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for(auto& x : v) //对于v中的每个x加1
++x;
//...
}
在声明语句中,一元后置运算符&表示“……的引用”。引用类似于指针,唯一的区别是我们无须使用前置运算符*访问所引用的值。而且,一个引用在初始化之后就不能再引用其他对象了。
当指定函数的参数时,引用特别有用。例如:
void sort(vector<double>& v); //排序v(v是一个double的向量)
通过使用引用,我们保证在调用sort(my_vec)时不会拷贝my_vec,从而真正对my_vec进行排序而不是对其副本进行排序。
还有一种情况,我们既不想改变实参,又希望避免参数拷贝的代价,此时应该使用const引用。例如:
double sum(const vector<double>&)
函数接受const引用类型的参数是非常普遍的。
用于声明语句中的运算符(如&、*、[])称为声明运算符(declarator operator):
T a[n] //T[n]:n个T组成的数组
T* p //T*:p为指向T的指针
T& r //T&:r为T的引用
T f(A) //T(A):函数f接受类型为A的实参,返回类型为T的结果
空指针
我们的目标是确保指针永远指向某个对象,这样该指针的解引用操作才是合法的。当确实没有对象可指向或者需要表示“没有对象可用”的概念时(例如,到达列表的末尾),我们赋予指针值nullptr(“空指针”)。所有指针类型都共享同一个nullptr:
double* pd = nullptr;
Link<Record>* lst = nullptr; //一个Record的Link的指针
int x = nullptr; //错误:nullptr是个指针,不是整数
接受一个指针实参时检查一下它是否指向某个东西,这通常是一种明智的做法:
int count_x(const char* p, char x)
//统计x在p[]中出现的次数
//假定p指向一个以零结尾的字符数组(或者不指向任何东西)
{
if(p==nullptr)
return 0;
int count = 0;
for(; *p != 0; ++p)
if(*p == x)
++count;
return count;
}
有两点值得注意:一是如何使用++将指针移动到数组的下一个元素;二是在for语句中,如果不需要初始化操作,则可以省略它。
count_x()的定义假定char是一个c风格字符串(C_style string),即,指针指向了一个以零结尾的char数组。字符串字面值中的字符是不可变的,为了能处理count_x(“Hello!”),将count_x声明为一个const char参数。
在旧式代码中,也通常用0和NULL来替代nullptr的功能。不过,使用nullptr能够避免混淆整数(0或NULL)和指针(如nullptr)。
在count_x()例子中,对for语句我们并没有使用初始化部分,因此可以使用更简单的while语句:
int count_x(const char* p, char x)
//统计x在p[]中出现的次数
//假定p指向一个以零结尾的字符数组(或者不指向任何东西)
{
if(p==nullptr)
return 0;
int count = 0;
while(*p){
if(*p == x)
++count;
++p;
}
return count;
}
while语句重复执行,直到其循环条件变成false为止。
对数值的检验(例如count_x()中的while(*p))等价于将数值与0进行比较(例如while(*p != 0))。对指针值的检验(如if§)等价于将指针值与nullptr进行比较(如if(p != nullptr))。
“空引用”是不存在的。一个引用必须指向一个合法的对象(C++实现也都假定这一点)。的确存在聪明但晦涩难懂的能违反这条规则的办法,但不要这么做。