C++PrimerPlus 学习笔记 | 第八章 函数探幽 | 2.引用参数

引用变量

引用变量的实现

在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++生成临时变量。有以下两种情况

  1. 实参类型正确,但不是左值(可以被取地址的值)

左值:可以被引用的数据对象,非左值包括字面常量(用引号扩起来的字符串除外,他们由地址表示)和包括多项式表达式

在C语言中左值最初是指可以出现在赋值语句左边的实体,,但是这是在引入const关键字之前的情况,现在const变量和常规变量都可以是为左值,但是常规变量属于可变左值,const变量属于不可变左值

  1. 实参类型错误,但是可以转换为正确的类型

来看以下例子

#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 引用

  1. 使用const引用可以避免无意中修改数据的编程错误
  2. 使用const引用可以使得函数可以处理const和非const实参数,否则只能只能接受非const 实参。
  3. 使用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对象

何时使用引用参数

使用原因如下

  1. 程序员能够修改调用函数的数据对象
  2. 通过传递引用而不是整个数据对象,可以提高程序运行速度

当数据对象较大时,第二个原因最重要。

还有其他原则

对于使用传递的值而不做修改的函数

  1. 如果数据对象很小,如内置数据类型或者小型结构,则按值传递
  2. 如果数据对象时数组,使用指针,这是唯一的选择,且将指针声明const
  3. 如果数据对象是较大的结构,使用const指针或者const引用,提高程序效率,节省复制结构所需要的时间和空间。
  4. 如果数据对象是类对象,则使用const引用,类设计的语意常常要求使用引用。这是C++增加引用的主要原因,传递类对象的标准方式是引用传递。

对于修改调用函数中数据的函数

  1. 如果数据对象是内置数据类型,则使用指针。
  2. 如果是数组则使用指针
  3. 如果是结构,使用指针或者结构
  4. 如果是类对象,使用引用

以上只是指导原则,在特定条件下需要具体考虑,例如 cin >> n 使用引用而不是指针

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值