引用型和指针很相像,任何引用可以完成的任务,指针也一样可以完成,但是他的语法的确弥补了指针的很多缺陷。
引用和指针之间的共同点:
--- 都可以作为函数的参数和返回值
--- 返回引用或地址(指针)的函数调用可以出现在赋值的任意一侧
引用和指针之间的区别:
--- 引用是实际变量的逻辑别名
--- 引用在声明的时候必须用一个实际变量来初始化
--- 引用在初始化后就不可改变
--- 不存在空引用
C++中的引用是其他变量的别名。声明一个引用型变量需要给他一个初始值。在变量的生存周期中,该值不可改变。
可以用&运算符定义引用型变量。
例如:
int actualint;
int& otherint = actualint;
这两条语句声明了一个别名为otherint的整型变量actualint。对这两个标识符的所有操作都会产生同样的效果。
////////////////////////////////////////
// The main() function.
////////////////////////////////////////
int main()
{
int actualint = 123;
int& otherint = actualint;
std::cout << actualint << std::endl;
std::cout << otherint << std::endl;
otherint++;
std::cout << actualint << std::endl;
std::cout << otherint << std::endl;
actualint++;
std::cout << actualint << std::endl;
std::cout << otherint << std::endl;
return 0;
}
运行结果:
注意:
引用既不是原对象的副本,也不是指向原对象的指针。实际上,编译器把他作为原对象的另外一个名字。
下面的代码通过比较两者的地址,说明了别名这个比喻是很恰当的。
////////////////////////////////////////
// The main() function.
////////////////////////////////////////
int main()
{
int actualint = 123;
int& otherint = actualint;
std::cout << &actualint << ' ' << &otherint;
return 0;
}
运行的结果如下:
初始化引用:
和指针不同,引用型变量的值不可改变。引用时真实变量的别名,必须对其进行初始化(显式的指定它的引用对象),除非满足下列条件之一:
--- 引用型变量被声明为外部的(extern),可以在任何地方对其进行初始化。
--- 引用型变量为类的成员,在构造函数里对他进行初始化。
--- 引用型变量作为函数声明中的形参,在函数调用时,用调用者的实参来进行初始化。
用引用来简化复杂的表达式
当某些表达式需要从数组元素和结构中获取对象时,可以利用引用来简化复杂的表达式。特别是在为了得到某个特定元素而需要反复使用一长串表达式时,引用显得极为方便。
// A Date structure.
struct Date
{
int month, day, year;
};
// An Employee structure.
struct Employee
{
int empno;
char name[35];
Date dates[3]; // hired, last review, terminated.
float salary;
};
Employee staff[] =
{
{ 1, "Bill", {{12,1,88},{2,24,92},{5,5,95}}, 35000 },
{ 1, "Paul", {{10,3,87},{5,17,94},{3,7,96}}, 25000 },
{ 1, "Jim", {{ 9,5,80},{9,11,96},{0,0, 0}}, 42000 },
{ 0 }
};
////////////////////////////////////////
// The main() function.
////////////////////////////////////////
int main()
{
Employee* pEmpl = staff;
while (pEmpl->empno != 0)
{
for (int i = 0; i < 3; i++)
{
Date& rd = pEmpl->dates[i];
std::cout << rd.month << '/'
<< rd.day << '/'
<< rd.year << " ";
}
std::cout << std::endl;
pEmpl++;
}
return 0;
}
运行结果如下:
用作函数形参的引用
引用常常被用作函数的形参。如果引用仅仅在于变量的作用域内,那么几乎没什么作用,直接使用变量的原名就可以了。
使用引用代替实参副本作为形参的优点:
--- 避免了传递大型数据结构带来的额外开销。
--- 无须像指针那样使用*和->等运算符。
// A big structure.
struct bigone
{
int serno;
char text[1000];
};
// Two function prototypes with a structure parameter.
void slowfunc(bigone p1); // Call by value.
void fastfunc(bigone& p1); // Call by reference.
////////////////////////////////////////
// The main() function.
////////////////////////////////////////
int main()
{
static bigone bo = {123, "This is a BIG structure"};
// This call will take a while.
slowfunc(bo);
// This call will be faster than the previous one.
fastfunc(bo);
return 0;
}
////////////////////////////////////////
// A call-by-value function.
////////////////////////////////////////
void slowfunc(bigone p1)
{
std::cout << p1.serno << std::endl;
std::cout << p1.text << std::endl;
}
////////////////////////////////////////
// A call by reference function.
////////////////////////////////////////
void fastfunc(bigone& p1)
{
std::cout << p1.serno << std::endl;
std::cout << p1.text << std::endl;
}
运行结果如下:
以引用方式调用函数
当函数把引用作为参数传递给另外一个函数时,被调用函数将直接对调用者的参数副本进行操作,而不是产生一个局部的副本(传递变量本身是这样的)。这称为以引用方式调用,而把参数的值传递到被调用的函数内部的副本中则称为以传值方式调用。
// Date structure.
struct Date
{
int month, day, year;
};
// Function prototypes.
void display(const Date&, const char*);
void swapper(Date&, Date&);
////////////////////////////////////////
// The main() function.
////////////////////////////////////////
int main()
{
// define two dates.
static Date now = {2,23,90};
static Date then = {9,10,60};
// Display the dates.
display(now, "Now: ");
display(then, "Then: ");
// Swap the dates and redisplay them.
swapper(now, then);
display(now, "Now: ");
display(then, "Then: ");
return 0;
}
////////////////////////////////////////
// Swap the caller's dates.
////////////////////////////////////////
void swapper(Date& dt1, Date& dt2)
{
Date save;
save = dt1;
dt1 = dt2;
dt2 = save;
}
////////////////////////////////////////
// Display a Date object.
////////////////////////////////////////
void display(const Date& dt, const char* ttl)
{
std::cout << ttl;
std::cout << dt.month << '/'
<< dt.day << '/'
<< dt.year << std::endl;
}
运行的结果如下:
作为函数返回值的引用
当然函数返回一个引用时,对函数的调用可以出现在引用可以出现的任何位置,包括赋值的接收端。
// A date structure.
struct Date
{
int month, day, year;
};
// An array of dates.
Date birthdays[] =
{
{12, 17, 37},
{10, 31, 38},
{ 6, 24, 40},
{11, 23, 42},
{ 8, 5, 44},
};
////////////////////////////////////////
// A function to retrieve a date.
////////////////////////////////////////
const Date& getdate(int n)
{
return birthdays[n-1];
}
////////////////////////////////////////
// The main() function.
////////////////////////////////////////
int main()
{
int dt = 99;
while (dt != 0)
{
std::cout << std::endl
<< "Enter date # (1-5, 0 to quit): ";
std::cin >> dt;
if (dt > 0 && dt < 6)
{
const Date& bd = getdate(dt);
std::cout << bd.month << '/'
<< bd.day << '/'
<< bd.year << std::endl;
}
}
return 0;
}
运行结果如下:
引用和指针使用场合的指到原则:
1.如果指向的变量可能不存在,那么使用指针。对于指针参数,可以用空(null)地址表示变量的不存在;而空引用是不存在的。如果确信变量肯定存在,那么就使用引用。
2.如果程序必须遍历对象的数组,那么应该考虑使用指针。这时,指针的算术运算符通常比引用使用的下标表示法更有效率。对于后者而言,程序需要使用2个变量:引用和下标;对于前者而言,只需要一个变量就够了。
3.引用能完成的工作,指针也能完成。引用型变量对指针的一个改进是:引用型变量的值无法改变,但又可以通过指针的算术运算来得到一个新的对象地址,这样减少了很多麻烦。一旦将一个引用型变量正确的初始化为指向某个对象后,就不能再让他指向别的地方,特别是其不应该指向的位置。
4.引用可以避免使用指针得到对象所需要的复杂表示法。