C++入门系列博客三 引用和指针
C++ 引用和指针
作者:AceTan,转载请标明出处!
引用和指针对于C++来说很重要,是学习C++绕不过去的一道坎。
引用
引用(reference) 就是给对象起别名。对引用的操作与对变量直接操作完全一样。
这里说的引用泛指“左值引用(lvalue reference)”,C++11新增了一种引用,即所谓的“右值引用(rvalue reference)”,这里不作讨论。
引用即别名,引用并非一个对象,不能定义引用的引用。
定义引用时,程序把引用和它的初始值进行绑定(bind)在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用和它的初始值对象一直是绑定在一起的,死也不分开。
举个栗子:胡一菲的小名叫小菲菲,那么对小菲菲的所有操作既是对胡一菲的操作。同样,小菲菲这个名字就是胡一菲绑定在一起了。当然,胡一菲还可以有其他的别名,比如叫菲菲菲。但需要注意的是,一旦有了这个小名,那么这个小名就不能指代其他人。不然会死的很惨。
引用的定义
引用定义时就需要初始化。允许一条语句定义多个引用,其中每个引用标示符都必须以符号&开头。
int value = 1;
int &refValue = value; // refValue 指向value
int &refValue2; // 报错,引用必须初始化
引用的写法也还可以是这样:
int value = 1;
int& refValue = value; // refValue 指向value
int& refValue2; // 报错,引用必须初始化
注意&符号和int之间是否紧挨着,两种写法都是对的(下面介绍的指针也可以用这两种写法), 这个只是个人的代码风格问题,并无实质差异。
引用更为常见的用法应该是作为函数的参数,利用引用的特性,可以让一个函数返回多个"返回值"。另外,当使用一个类作为函数参数时,比较好的做法也是使用引用。这样可以减少创建类副本的一些开销。请看一个具体的示例:
#include <iostream>
using namespace std;
const double PI = 3.14;
// 圆的数据结构
struct Circle
{
double radius; // 半径
double circumference; // 周长
double square; // 面积
};
// 给出圆的半径,计算圆的周长和面积,返回是否计算成功。
bool CalCirle(Circle& c)
{
// 给的圆的半径为负数,返回计算错误
if (c.radius <= 0)
{
return false;
}
// 周长的计算
c.circumference = 2 * PI * c.radius;
// 面积的计算
c.square = PI * c.radius * c.radius;
return true;
}
int main()
{
Circle c;
cout << "请输入圆的半径:";
cin >> c.radius;
if (CalCirle(c))
{
cout << "该圆的周长是" << c.circumference<< endl;
cout << "该圆的面积是" << c.square << endl;
}
else
{
cout << "您输入的半径不合法!" << endl;
}
return 0;
}
指针
指针(pointer) 是“指向(point to)”另外一种类型的复合类型。
复合类型(compound type) 是指基于其他类型定义的类型。比较常见的是指针和引用。
指针本身就是一个对象,允许对指针的复制和拷贝,而且,在指针的生命周期内,它可以先后指向几个不同的对象。指针和其他变量一样,定义的时候可以不赋值。如果指针未被初始化,那么它将拥有一个不确定的值。指针未初始化和空指针经常引起程序崩溃。
指针通常难以理解,即使是有经验的程序员也常常因为调试指针引发的bug而备受折磨。
举个栗子,便于理解指针:指针好比门牌号,它所指向的是这个房间的东西,它本身也在有值的,它就是门牌上的号码。你可以把门牌号取下来,挂在别的房间,那么这个门牌号就指向了另一个房间。当然,你觉得有个门牌号不吉利,把它重新刷成其他门牌号,这样也是允许的。但你不能把两个不同的房间都挂一样的门牌号,那么别人就分不清了。某天你打开了某个房间(你需要钥匙,钥匙就是解引用),发现里面有个门牌号,这个就是多级指针。房间里的门牌号告诉你,你要找的东西在这个门牌号所指代的房间里。
指针存放某个对象的地址,要想获得该地址,需要使用取地址运算符(操作符&)。
注意这里的取地址运算符&和声明引用时标示符以符号&开头的意义和用法是不一样的。
int value = 2;
int* p = &value; // p存放变量value的地址,或者说p是指向变量value的指针
利用指针访问对象:如果一个指针指向一个对象,则允许解引用符(操作符*) 来访问该对象。
int value = 2;
int* p = &value; // p存放变量value的地址,或者说p是指向变量value的指针
*p = *p * 3; // 取这个变量,并乘以3
cout << *p << endl; // 输出结果为6
cout << value << endl; // 输出结果为6
指针可以为空(引用是不可以的)。空指针不指向任何对象,一个良好的习惯就是使用指针前,一定要判断是否为空。
得到空指针最直接的方法是用字面值nullptr 来初始化指针。(C++11引入的一种方法)
以前的方法是将指针初始化为字面值0或者NULL。(NULL为一个预处理变量,它的值就是0)。
void* 指针 是一个特殊类型的指针,它可用于存放任意对象的地址。
指针的指针 指针级数是没有限制的。但常见的也就是一级指针和二级指针,二级以上比较少见。给出一个多级指针的例子:
#include <iostream>
using namespace std;
int main()
{
int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int b[3][4] =
{
11, 22, 33,
44, 55, 66,
77, 88, 99,
111, 222, 333
};
int *p1 = nullptr, *p2 = nullptr, **p3 = nullptr;
p1 = a; // p1指向a数组
p3 = &p1; // p3指向p1
for (int i = 0; i < 10; ++i)
{
cout << *(*p3 + i) << " "; // 输出: 0 1 2 3 4 5 6 7 8 9
}
cout << endl;
for (p1 = a; p1 - a < 10; ++p1)
{
p3 = &p1;
cout << ** p3 << " "; // 输出: 0 1 2 3 4 5 6 7 8 9
}
cout << endl;
for (int i = 0; i < 3; ++i)
{
p2 = b[i];
p3 = &p2;
for (int j = 0; j < 4; ++j)
{
cout << *(*p3 + j) << " "; // 输出:11 22 33 44 55 66 77 88 99 111 222 333
}
}
return 0;
}
函数指针 是C++中最大优点之一了。一般来说,使用函数指针比函数引用更为方便一些。
函数指针的声明使用方式:
<想要指向的函数之 返回类型>(*函数指针的 名称)<想要指向的函数之 参数类型…>
#include <iostream>
using namespace std;
void sayHello(const char* name)
{
cout << "Hello, " << name << endl;
}
int add(int a, int b)
{
return a + b;
}
int main()
{
void(*pFunc1)(const char*);
pFunc1 = &sayHello;
pFunc1("Ace Tan"); // 输出:Hello, Ace Tan
int(*pFunc2)(int, int);
pFunc2 = &add;
cout << pFunc2(1, 2) << endl; // 输出3
return 0;
}
函数指针也可以作为函数的参数,在这儿不做详细介绍了。
总结
引用和指针有如下区别:
-
修改性:指针可以重新赋值以指向另一个对象。引用在初始化时就被绑定对象,以后无法改变。
-
非空性: 指针可为空,引用不可为空
-
传递性: 指针传递参数和值传递是一样的,被当做局部函数来处理,不影响主调函数的实参变量。引用传递对形参的任何操作都影响了主调函数中的实参变量。
结束语
引用和指针在C++中应用非常多,也非常灵活。指针也是C++最难学的部分之一,少年,赶快去征服它吧。