引用变量
引用变量的实现
在C++中允许使用 & 符号来声明引用
int a = 1;
int & b = a; // b 是 a 的别名且 &b == &a
引用在声明的时候必须初始化,而指针可以先声明再赋值
且引用不许更改引用对象
int a = 1;
int & b = a;
int c = 2;
b = c // ? &b == &c NO! ,&b == &a a == b == c
看似更改了b的引用位置其实不然,只是将 c 的值赋给了 b,而b 是 a的别名,所以 b == c == a
引用函数作为函数参数
按引用传递能够允许被调用的函数能够访问调用函数的变量。C++这个特性是C语言所不存在的,C语言只允许按值传递,即使是可以突破的指针也是按值传递
// 函数实现
void swap_Cstyle(int * a,int * b){
int t = *a;
*a = *b;
*b = t;
}
void swap_Cppstyle(int & a,int & b){
int t = a;
a = b;
b = t;
}
// 调用方式
int a = 1,b = 2;
swap_Cstyle( &a, &b); // C交换函数调用
swap_Cppsttle( a, b); // Cpp交换函数调用
使用基本数据类型时应该(推荐)使用按值传递,而不要使用按引用传递,当数据(类或者是结构)比较大的时候,引用参数将会很有用.
同时传递引用的会更加严格,实参应该是变量,而不是表达式。
当参数为const,如果实参与引用参数不匹配,C++生成临时变量。有以下两种情况
- 实参类型正确,但不是左值(可以被取地址的值)
左值:可以被引用的数据对象,非左值包括字面常量(用引号扩起来的字符串除外,他们由地址表示)和包括多项式表达式
在C语言中左值最初是指可以出现在赋值语句左边的实体,,但是这是在引入const关键字之前的情况,现在const变量和常规变量都可以是为左值,但是常规变量属于可变左值,const变量属于不可变左值
- 实参类型错误,但是可以转换为正确的类型
来看以下例子
#include <iostream>
void printa_nonconst(int & a){
std::cout << a << std::endl;
}
void printa_const(const int & a){
std::cout << a << std::endl;
}
int main(){
int a = 1;
printa_nonconst(a); // 允许
printa_const(a); // 允许
// printa_nonconst(1); 实参数不是变量 编译失败
printa_const(1); // 参数为const引用,实参为类型正确,但不是左值,C++将创建临时变量
int b = 2;
printa_nonconst(b + 2); // 实参不是左值
printa_const(b + 2); // 参数为const引用,实参为类型正确,但不是左值,C++将创建临时变量
const int c = 1;
//printa_nonconst(c); // 不被允许,在被调用函数a是可改变的,但是实际上a是const,会导致const很荒诞。
}
应尽可能的使用 const 引用
- 使用const引用可以避免无意中修改数据的编程错误
- 使用const引用可以使得函数可以处理const和非const实参数,否则只能只能接受非const 实参。
- 使用const引用可以使得函数正确生成和使用临时变量
将引用用于结构
引用非常适合用于结构和类(C++的用户自定义类型),引入引用正是为了这种类型,而不是基本的内置类型
假设有以下结构
typedef enum {MALE,FEMAIL,OTHER} Sex;
struct Person {
std::string name;
int age;
Sex sex;
};
常量引用作为参数
void display(const Person & person){
const char charsOfSex[][7] = {"Male","Female"};
const char formatOfName[] = "Name: %s\n";
const char formatOfAge[] = "Age: %d\n";
const char formatOfSex[] = "Sex: %s";
printf(formatOfName,person.name.c_str());
printf(formatOfAge,person.age);
printf(formatOfSex,charsOfSex[person.sex]);
}
非常量引用作为参数,返回引用
Person & initPerson(Person & person,std::string name,int age,Sex sex){
person.name = name;
person.age = age;
person.sex = sex;
return person;
}
将返回的非常量引用引用作为常量引用的实参
display(initPerson(lcosvle,"lcosvle",20,MALE));
/* 运行结果
Name: lcosvle
Age: 20
Sex: Male
*/
将非常量引用作为返回值并赋值
// 函数实现
std::string & setName(Person & person){
return person.name;
}
// 执行代码
setName(lcosvle) = "lcosvle2";
display(lcosvle);
/* 结果
Name: lcosvle2
Age: 20
Sex: Male
*/
在理解非常量引用返回值的时候,可以将左边的函数体视作其返回的引用值,就是在给引用赋值,与函数实际无关.简单来说返回引用的函数实际上是引用的变量的别名。
不应该返回函数终止时不再存在的内存单元的引用如下
Person & newPerson(std::string name,int age,Sex sex){
Person newPerson{name,age,sex};
return newPerson; // newPerson 变量将被释放,返回将会出错
}
如果不是返回引用则没有关系,或者返回传入的参数或者使用new来开辟内存
Person newPerson(std::string name,int age,Sex sex){
Person newPerson{name,age,sex};
return newPerson; // newPerson将会被复制一份,没有问题
}
为什么要将const用于引用返回类型
假如你既需要返回引用值又需要不允许修改,则要使用const引用,如果将initPerson改用const引用返回
const Person & initPerson(Person & person,std::string name,int age,Sex sex){
person.name = name;
person.age = age;
person.sex = sex;
return person;
}
则可以避免如下荒诞的错误
initPerson(llonvne, "lonvne", 19, MALE) = lcosvle; // 将执行完函数的llonvne用lcosvle赋值
将引用用于类结构
将类对象传递给函数的时候,C++常用的方法是引用,例如可以通过引用让string,ostream类的对象等作为函数的参数。
string insertAtBothStartAndEnd1(const string & a, const string & b)
{
return b + a + b;
}
// 正确
const string & insertAtBothStartAndEnd2(string & a, const string & b)
{
a = a + b + a;
return a;
}
// 返回了正确的结果,但是导致了a被更改
const string & insertAtBothStartAndEnd3(const string & a, const string & b)
{
string temp = b + a + b;
return temp;
}
// 错误! 返回了局部变量的引用
使用C风格的字符串作为string对象的参数
string类的对象可以接受一个C风格字符串有个条件
就是必须要将参数声明为 const 引用参数,才能调用string内置的转换 char * -> string 的转换功能
将原来的C风格字符串转换为一个临时变量(必须要声明为常量引用,才能转换否则就会失败)。
cout << insertAtBothStartAndEnd1("123",a);
如下函数就不可以接受C风格字符串
// 函数实现
string insertAtBothStartAndEnd1_nonconst(string & a, string & b)
{
return b + a + b;
}
// 调用
// insertAtBothStartAndEnd1_nonconst("123", "###") 失败!非const引用只能接受左值
对象,继承和引用
继承:使得一个特性从一个类传递到另一个类的语言特性叫做继承
例如 ostream 作为基类(因为ofstream建立于它之上),ofstream作为派生类(因为它是从ostream派生出来)。派生类继承了基类的方法,
继承的另一个特征是基类引用可以指向派生类对象,而无需进行强制转换。
例如参数类型为 ostream & 的对象可以接受ostream对象或者你声明的ofstream对象
何时使用引用参数
使用原因如下
- 程序员能够修改调用函数的数据对象
- 通过传递引用而不是整个数据对象,可以提高程序运行速度
当数据对象较大时,第二个原因最重要。
还有其他原则
对于使用传递的值而不做修改的函数
- 如果数据对象很小,如内置数据类型或者小型结构,则按值传递
- 如果数据对象时数组,使用指针,这是唯一的选择,且将指针声明const
- 如果数据对象是较大的结构,使用const指针或者const引用,提高程序效率,节省复制结构所需要的时间和空间。
- 如果数据对象是类对象,则使用const引用,类设计的语意常常要求使用引用。这是C++增加引用的主要原因,传递类对象的标准方式是引用传递。
对于修改调用函数中数据的函数
- 如果数据对象是内置数据类型,则使用指针。
- 如果是数组则使用指针
- 如果是结构,使用指针或者结构
- 如果是类对象,使用引用
以上只是指导原则,在特定条件下需要具体考虑,例如 cin >> n 使用引用而不是指针