一、基本知识
指针和引用的声明方式:
声明指针: char* pc;
声明引用: char c = 'A'
char& rc = c;
它们的区别:
①从现象上看,指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变。这句话可以理解为:指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。
②从内存分配上看,程序为指针变量分配内存区域,而不为引用分配内存区域,因为引用声明时必须初始化,从而指向一个已经存在的对象。引用不能指向空值。
③从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针。
④不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。
⑤理论上,对于指针的级数没有限制,但是引用只能是一级。如下:
int** p1; // 合法。指向指针的指针
int*& p2; // 合法。指向指针的引用
int&* p3; // 非法。指向引用的指针是非法的
int&& p4; // 非法。指向引用的引用是非法的
注意上述读法是从左到右。
#include "stdio.h"
int main(void)
{
// 声明一个char型指针pc,且让它指向空值
char* pc = 0;
char a = 'a';
// 声明一个引用rc,且让它引用变量a
char& rc = a;
printf("%d, %c\n", pc, rc);
char *pc2;
// 声明一个指针,但可以不初始化
pc2 = pc;
// char& rc2;
// 上面语句编译时,会产生如下错误:
// error C2530: 'rc2' : references must be initialized
// 即,应用必须初始化
// rc = *pc;
// 上面语句编译不会有问题,但运行时,会报如下错误:
// "0x00401057"指令引用的"0x00000000"内存。该内存不能为"read"
// 说明引用在任何情况下,都不能指向空值
return 0;
}
二、作为参数传递利用引用的这个特性,可以用它作为函数的传出参数。如程序3:
#include <iostream>
#include <string>
using namespace std;
int newEvaluation(string& aStr)
{
string bStr("Hello,");
aStr = bStr + aStr;
return 0;
}
int main(void)
{
string aStr("Patrick!");
newEvaluation(aStr);
std::cout << aStr << endl; // 输出结果:"Hello, Patrick!"
return 0;
}
当然程序3引用传递的方式也可以写成指针传递的方式,如程序5:
#include <iostream>
#include <string>
using namespace std;
int newEvaluation(string* const aStr)
{
string bStr("Hello,");
*aStr = bStr + *aStr;
return 0;
}
int main(void)
{
string aStr("Patrick!");
newEvaluation(&aStr);
std::cout << aStr << endl; // 输出结果:"Hello, Patrick!"
return 0;
}
三、函数指针和函数引用
函数指针是C++最大的优点之一。和使用普通指针相比,高级程序员只要有可能都更愿意使用引用,因为引用更容易处理一些。然而,当处理函数时,函数引用对比函数指针就未必有这个优势了。现有的代码很少使用函数引用。下面将向介绍如何函数指针、如何使用函数引用以及分别在什么情况下使用它们。
① 函数指针的例子
#include <iostream>
void print(int i)
{
std::cout << i << std::endl;
}
void multiply(int& nDest, int nBy)
{
nDest *= nBy;
}
void print_something()
{
std::cout << "something" << std::endl;
}
int sayHello()
{
std::cout << "Hello, World!" << std::endl;
return 10;
}
int main()
{
void (*pFunction_1)(int);
pFunction_1 = &print;
pFunction_1(1);
// 输出结果为1
void (*pFunction_2)(int&, int) = &multiply;
int i = 1;
pFunction_2(i, 10);
std::cout << "i = " << i << std::endl;
// 输出结果为10
void (*pFunction_3)();
pFunction_3 = &print_something;
pFunction_3();
// 输出结果为something
int (*pFunction_4)();
pFunction_4 = &sayHello;
int a = pFunction_4();
// 输出结果为Hello, World!
std::cout << a << std::endl;
// 输出结果为10
return 0;
}
② 函数引用的例子
#include <iostream>
void print(int i)
{
std::cout << i << std::endl;
}
void print2(int i)
{
std::cout << i << std::endl;
}
void multiply(int& nDest, int nBy)
{
nDest *= nBy;
}
void print_something()
{
std::cout << "something" << std::endl;
}
int sayHello()
{
std::cout << "Hello, World!" << std::endl;
return 10;
}
int main()
{
// void (&rFunction_1)(int);
// 错误:未初始化引用!引用必须初始化
void (&rFunction_2)(int) = print;
rFunction_2(1);
// 输出1
rFunction_2 = print2;
rFunction_2(2);
// 输出2
void (&rFunction_3)(int&, int) = multiply;
int i = 1;
rFunction_3(i, 10);
std::cout << i << std::endl;
// 输出10
void (&rFunction_4)() = print_something;
rFunction_4();
// 输出something
int (&rFunction_5)();
rFunction_5 = sayHello;
int a = rFunction_5(); // 输出Hello, World!
std::cout << a << std::endl;
// 输出10
return 0;
}
总结:
函数指针的声明使用方式:
<想要指向的函数之返回类型>(*函数指针的名称)<想要指向的函数之参数类型…>
如要想声明一个函数指针指向以下函数:
void print(int i)
{
std::cout << i << std::endl;
}
那么就可以如下操作: void (*pFunction)(int);
然后如下用函数的地址给pFunction赋值: pFunction = &print;
在然后,pFunction就可以和函数print一样使用了,比如, pFunction(1); 等等。
函数引用的声明和使用方式:
<欲引用的函数之返回类型>(&函数引用的名称)<欲引用的函数之参数类型…>=<欲引用的函数的名称>,至所以如此,是引用在声明的时候必须初始化,引用不能指向空值。
如要想声明一个函数引用指向以下函数:
void print(int i)
{
std::cout << i << std::endl;
}
那么就可以如下操作: void (&rFunction)(int)=print;
在然后,rFunction就可以和函数print一样使用了,比如, rFunction(1); 等等。
四、const修饰指针和引用
大致而言,const修饰指针和引用分三种情况,即const修饰指针、const修饰引用和const修饰指针的引用。下面分别讨论之。
① const修饰指针
const修饰指针又分为三种情况,即const修饰指针本身、const修饰指针所指的变量(或对象)以及const修饰指针本身和指针所指的变量(或对象)。
a. const修饰指针本身
在这种情况下,指针本身是常量,不能改变,任何修改指针本身的行为都是非法的,例如:
double pi = 3.1416;
double* const PI = π
double alpha = 3.14;
PI = α // 错误。因为指针PI是常量,不能再被改变。
*PI = alpha; // OK。虽然指针PI不能被改变,但指针所指的变量或者对象可变。
b. const修饰指针指向的变量(或对象)在这种情况下,指针本身可以改变,但const所修饰的指针所指向的对象不能被改变,例如:
double pi = 3.1416;
const double* PI = π
double alpha = 3.14;
*PI = alpha; // 错误。因为PI所指向的内容是常量,因此*PI不能被改变。
PI = α // OK。虽然指针所指的内容不能被改变,但指针PI本身可改变。从而通过这种方式改变*PI。
c. const修饰指针本身和指针所指向的变量(或对象)在这种情况下,指针本身和指针指向的变量(或对象)均不能被改变,例如:
double pi = 3.146;
const double* const PI = π
//double const* const PI = π
cout << "PI = " << PI << endl;
cout << "*PI = " << *PI << endl;
double alpha = 3.14;
//*PI = alpha; // 错误。因为PI所指向的内容是常量,因此*PI不能被改变。
//PI = α // 错误。因为指针PI是常量,不能再被改变。
② const修饰引用const修饰引用没有指针修饰指针那么复杂,只有一种形式。引用本身不能被改变,但所指向的对象是可以被改变的,见上面“一、基本知识”。
double pi = 3.1416;
//const double& PI = pi;
double const& PI = pi; //和上面一句是等价的
//double& const PI = pi; //有问题。很多编译器会产生warning
cout << PI << endl;
③ const修饰指针引用我们用例子来说明。
double pi = 3.14;
const double* pPI = π
//const double*& rPI = π //错误。不能将double* 转换成const double *&
const double*& rPI = pPI; //OK。声明指针引用的正确方法
说明:const double*& rPI = π 为什么会出现错误呢?我们知道,引用是被引用对象的别名,正因为如此,由于rPI是pPI的别名,因此rPI和pPI的类型必须完全一致。从上面的代码段我们可以看到,rPI的类型是const double*,而&pi的类型是double*,因此这句程序是错误的。
下面这段代码和 ① 中的b中的情形对应(即内容不可变,指针可变):
double pi = 3.1416;
double api = 3.14;
const double* pPI = π
const double* pAPI = &api;
const double*& rPI = pPI;
const double*& rAPI = pPI;
*rAPI = api; // 错误。指针所指向的值不能被直接改变
rAPI = pAPI; // OK。指针本身可以被改变
指针引用的用法还有其它的情形,由于罕用,故此不谈及。
本文转自:http://patmusing.blog.163.com/blog/static/135834960200911308413342/