C++语言导学 第一章 基础知识 - 1.7 指针、数组和引用

第一章 基础知识

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所指的对象

在表达式中,前置一元运算符*表示“……的内容”,而前置一元运算符&表示“……的地址”。可以用下面的图形来表示上述初始化定义的结果。
C++语言导学 第一章 基础知识 - 1.7 指针、数组和引用
考虑将一个数组的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++实现也都假定这一点)。的确存在聪明但晦涩难懂的能违反这条规则的办法,但不要这么做。

上一篇:NULL和nullptr的区别


下一篇:【Leetcode】101.对称二叉树