对于学习Java或者C#的程序员来说,学习C++的时候,大概看到那些使用const int*& a之类的当参数时都会蒙了吧··
老实说,尽管当时我觉得自己C++书都看差不多了,学得也可以,但实际上,看到const,"*","&"的混合使用时,我也蒙了。
这篇文章就我自己的一些看法来谈谈不同的混合方式都有什么作用。
- 没有const的时候。
没有const时,也就是*和&的混合使用。这里又分&是当取地址运算符或者是引用运算符两种情况,怎么区分&是取地址还是引用呢?
很简单,如果&之前没有类型就是用作引用,否则就是当取地址符。例如:int& 这是引用,int* p = &a 这是取地址。
1.&是取地址时:
这种情况在谭浩强的C++程序设计里面有说明 P168
如果有int* p = &a;那么 &*p 是什么含义呢?
由于"&"和"*"的优先级都为3,但按自右至左方向结合。所有&*p = &(*p) = &a = p,也就是a的地址
下面程序的输出结果是一样的。
int a;
int* p = &a;
cout << "a的地址:" << &a << endl;
cout << "&*p的值为:" << &*p << endl;
cout << "p的值为:" << p << endl;
那么 *&a 的含义呢?同样可以分析,*&a = *(&a) = *p = a,也就是说,*&a与a等价。
同样的,下面程序的输出结果是一样的。
int a = 1;
cout << "a的值:" << a << endl;
cout << "*&a的值为:" << *&a << endl;
2.&是引用时:
这种情况大多出现在函数形参里,当初也是这种情况把我搞混了。
例如如果一个函数的形参是 int*& p ,那么它代表的是什么意思,又有什么作用呢?
可以这样理解:int*& = (int*)&,也就是一个整型指针的引用。这又有怎么作用呢?
这里用一个例子来说明,引自谭浩强的C++程序设计里的P173
#include <iostream>
using namespace std;
int main()
{
void swap(int* p1, int* p2);
int* pointer_1, *pointer_2, a, b;
cin >> a >> b;
pointer_1 = &a;
pointer_2 = &b;
if (a < b)
swap(pointer_1, pointer_2);
cout << "max=" << *pointer_1 << " min=" << *pointer_2 << endl;
return 0;
}
void swap(int* p1, int* p2)
{
int* temp;
temp = p1;
p1 = p2;
p2 = temp;
}
上面这段代码试图通过函数更改两个指针的指向来达到交换的功能,即不改变a和b的值,但改变pointer_1和pointer_2的指向来达到使pointer_1指向较大值,而pointer_2指向较小值。但通过输出的结果看,这并不成功。
为什么呢?
我们都知道,函数调用时会虚实结合,即把实参拷贝给形参(引用除外),这里的形参是指针也是一样的。
也就说调用时,系统在堆栈上创建了两个整型指针变量(它们确实占用了另一段内存),所以更改它们不会影响到实参。
具体过程如下图所示:
那么怎么在函数内改变实参指针的指向呢?答案就是用*&,也就是指针的引用。首先要强调一点的是,有些初学C++的人会搞不清楚引用的实现机制,以为引用跟指针用法一样(我当初就这样想的),但其实它们区别很大。下面说说我对引用的一些看法,或许对你有帮助。
书上一般会说:引用是为一个变量取一个别名。
我觉得这样反而不好理解,如果这样说呢:引用是与变量使用同一块内存。
如果将变量看成是一小块内存,那么使用不同内存的变量自然不能当成是同一个变量,尽管它们的值可以一样,但更改一处并不会改变另一处。但引用就是使用相同内存的,所以引用可以看成是与变量是同一个变量。
看下面的例子:
#include <iostream>
using namespace std;
void f1(int& a)
{
cout << &a << endl;
}
void f2(int a)
{
cout << &a << endl;
}
int main()
{
int a;
int& b = a;
int* p = &b;
cout << &a << endl;
cout << &b << endl;
cout << p << endl;
f1(a);
f2(b);
return 0;
}
从结果中可以看到,a和b的地址是一样的,而且在函数f1的形参与实参的地址也是一样的,而不是用引用的f2的地址就不同。而我们也可以用指针指向引用变量,也可以用引用当作整型参数来传递。所以说引用跟原变量完全就是同一个变量。
可以用下面一段代码来实现在函数内改变实参指针的指向:
#include <iostream>
using namespace std;
void Func1(int* p);
void Func2(int*& p);
int b = 2;
int main()
{
int a = 1;
int* p = &a;
cout << "a的地址:" << &a << endl;
cout << "b的地址:" << &b << endl;
Func1(p);
cout << p << endl;
Func2(p);
cout << p << endl;
return 0;
}
void Func1(int* p)
{
p = &b;
}
void Func2(int*& p)
{
p = &b;
}
运行上面的代码,可以看到,调用Fun1后,p的指向依然是a,但调用Fun2后,p的指向就改到b了。也验证了上面那个说法。
至于另一种情况,也就 int&* 的情况,这个情况大家上机一试就知道了,是不同通过编译的。原因很简单,没有这样的定义。
- 有const的情况
在搞懂了没有const的情况后,有const的情况无非就是加了一层常量限定了。
1.const与*的结合
首先是3个常见的const和指针的结合:const int* p,int* const p,const int* const p。
这3种在大部分的C++书籍上都有介绍了。分别是:
const int* p | 指向常量的指针 |
int* const p | 常指针 |
const int* const p | 指向常量的常指针 |
这3种也是一般用在函数参数里比较多。
这里稍微介绍一下:
const int* p 不能改变指针指向的值,可以改变其指向。即*p = 1是非法的,而p = &b是合法的。
int* const p 能改变指针所指向的值,不可以改变其指向。即*p = 1是合法的,而p = &b是非法的。
const int* const p 不能改变指针所指向的值,也不可以改变其指向。即*p = 1和p = &b都是非法的。
怎么记忆着三种奇怪的写法呢?这里是我的记忆方法:
使用左结合:
const int* p => (const int)* p 也就是一个指针所指向的是常量
int* const p => ((int*) const) p 也就是一个指针本身是常量
而const int* const 就是两者的结合。
2.const与&的结合
下面看看const 和 & 的结合使用。
最常见的就要数: const int& a 了,这个最常使用在类的拷贝构造函数里,这就是默认的写法。
这个理解起来很简单,就是使用引用,同时又是常量。跟const int a的区别就在于:
在函数虚实结合时,有&的使用引用方式,而没有的就是值传递。
我刚开始以为也会有 int& const a 的形式,后来试了一下,确实有,但是没用。
看下面的代码:
#include <iostream>
using namespace std;
const void Func3(int& const a)
{
a = 3;
}
int main()
{
int a = 1;
Func3(a);
cout << a << endl;
return 0;
}
这段代码可以通过编译,最后结果是实参a的值改为3了,也就是说int& const a 是等价于int& a的。
本来我猜想, const int& const a 是等价于const int& a的。后来才发现这种写法是编译不过的。呵呵··
3.const、*、&的结合
最后就是3种形式的结合,const 和 * 和 &的结合。
1. const int*& p 的使用
这个应该算是比较常见到一种了吧,第一次看的时候相信很多人都会蒙了吧,呵呵
上面提到了int*&是指针的引用,不要以为const int*&是(const) (int*&)是这两组的组合意思,根据左结合,
const int*&是(const int*)&的意思,前面也提到const int*是指向常量的指针的意思,那么
const int*&就是指向常量的指针的引用的意思。
看下面的代码:
#include <iostream>
using namespace std;
int b = 2;
void Func3(const int*& p)
{
//*p = 4; // 去掉这个注释的话会编译错误
p = &b;
}
int main()
{
int a = 1;
const int* p = &a; // 不加const的话会编译错误
Func3(p);
return 0;
}
综合地说,const int*&的功能如下:
不能改变指针所指向的值,可以改变指针的指向,改变形参指针的指向会改变实参的指向。
2.int*& const p 的意思
经过测试,int*& const是等价于int*&的,也就说后面的const不起作用。
3.同理,const int*& const 是不能通过编译的。
各位看客是不是以为到此为止呢,呵呵,还有一些可能你想都没想到的用法呢!
4.int* const &p 的使用
通过前面那么多的分析,大家也试试猜猜这个用法的意思。采用左结合
int* const &p => (int* const)& p 也就是一个常指针的引用。
同样用一小段代码来测试一下:
#include <iostream>
using namespace std;
int b = 2;
void Func3(int* const &p)
{
*p = 4;
//p = &b; // 去掉这个注释的话会编译错误
}
int main()
{
int a = 1;
int* p = &a; // 这里没有定义常指针!
Func3(p);
cout << a << endl;
return 0;
}
老实说,这是个只有无聊的人才会想到的用法(狂晕),我们分析一下:
我们在指针型的形参加引用是为了可以改变实参的指向,但我们又定义了形参是一个不能改变指向的常指针。所以:
int* const& p 是等价于 int* const的。
- 总结:
可能会有人说这种用法int const *,这个···翻翻书就知道了,这个是等价于int* const的,也就是const 和 * 的位置可以换的。
上面说了很多组合,但实际有用的也就那么几种。这里总结一下:
1.int*& p 这应该是最有用的一个,因为可以用它当形参,在函数体内改变实参的指向。
2.const和*的3种结合都比较常用,特别是前2种。
3.const int& a 这也是很常用的一种用法,特别在面向对象里面,引用是提高效率的钥匙。
4.const int*&,这个虽不常用,但某些场合还是会看到的。
到此为止,希望各位看客多多支持~~看过后没那么蒙的就顶顶吧。