【C++】学习笔记四十——引用变量

引用变量

引用是已定义的变量的别名。引用变量的主要用途是用作函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是其副本。这样除指针之外,引用也为函数处理大型结构提供了一种非常方便的途径。对于设计类来说,引用也是必不可少的。

创建引用变量

用&来声明引用:

int rats;
int & rodents = rats;

其中,&不是地址运算符,而是类型标识符的一部分。int &指的是指向int的引用,上述引用声明允许将rats和rodents互换——它们指向相同的值和内存单元。

程序8.2

#include <iostream>

int main()
{
    using namespace std;
    int rats = 101;
    int & rodents = rats;
    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;
    rodents++;
    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;

    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << endl;
    return 0;
}

这里写图片描述

从输出可知,rats和rodents的值和地址都相同。将rodents加1将影响这两个变量(实质上是有两个名称的同一个变量)。

必须在声明引用时将其初始化,而不能像指针那样,先声明再赋值。

引用更接近与const指针,必须在创建时进行初始化:

int & rodents = rats;
//实际上是下面代码的伪装表示:
int * const pr = &rats;
//引用rodents与表达式*pr相同。

程序8.3

#include <iostream>
int main()
{
    using namespace std;
    int rats = 101;
    int & rodents = rats;

    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;

    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << endl;

    int bunnies = 50;
    rodents = bunnies;  //can we change the reference?
    cout << "bunnies = " << bunnies;
    cout << ", rats = " << rats;
    cout << ", rodents = " << rodents << endl;

    cout << "bunnies address = " << &bunnies;
    cout << ", rodents address = " << &rodents << endl;
    return 0;
}

这里写图片描述

将bunnies赋给rodents,只是改变了变量的值,而没有改变其地址,同事rats的值也变了。

将引用用作函数参数

引用用作函数参数,使得函数中的变量名称为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。
程序8.4

#include <iostream>
void swapr(int & a, int & b);
void swapp(int *p, int * q);
void swapv(int a, int b);

int main()
{
    using namespace std;
    int wallet1 = 300;
    int wallet2 = 350;
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << endl;

    cout << "Using references to swap contents:\n";
    swapr(wallet1, wallet2);
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << endl;

    cout << "Using pointers to swap contents again:\n";
    swapp(&wallet1, &wallet2);
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << endl;

    cout << "Trying to use passing by value:\n";
    swapv(wallet1, wallet2);
    cout << "wallet1 = $" << wallet1;
    cout << " wallet2 = $" << wallet2 << endl;

    return 0;
}

void swapr(int & a, int & b)
{
    int temp;

    temp = a;
    a = b;
    b = temp;
}

void swapp(int * p, int * q)
{
    int temp;

    temp = *p;
    *p = *q;
    *q = temp;
}

void swapv(int a, int b)
{
    int temp;

    temp = a;
    a = b;
    b = temp;
}

这里写图片描述

程序8.4交换两个变量的值,交换函数必须能修改调用程序中的变量的值,因此按值传递变量将不管用。而传递引用和指针则可以访问原始数据。

引用的属性和特别之处

程序8.5采用两个函数来计算参数的立方,一个函数接受double类型的参数,另一个接受double引用。
程序8.5

#include <iostream>
double cube(double a);
double refcube(double &ra);
int main()
{
    using namespace std;
    double x = 3.0;

    cout << cube(x);
    cout << " = cube of " << x << endl;
    cout << refcube(x);
    cout << " = cube of " << x << endl;

    system("pause");
    return 0;
}

double cube(double a)
{
    a *= a * a;
    return a;
}

double refcube(double &ra)
{
    ra *= ra * ra;
    return ra;
}

这里写图片描述

为了说明按引用传递的特点,程序将函数写得比较奇怪。refcube()函数修改了main()中的x值,而cube()没有,这提醒我们为何通常按值传递。

若想让函数使用引用,同时又不对这些变量进行修改,则应使用const:

double refcube(const double &ra);

如果这样做,当编译器发现代码修改了ra的值时,将生成错误消息。

临时变量、引用参数和const
如果实参和引用参数不匹配,C++将生成临时变量。

如果引用参数是const,则编译器将在下面两种情况下生成临时变量:

  • 实参的类型正确,但不是左值;
  • 实参的类型不正确,但可以转换为正确的类型。
    左值:左值参数是可被引用的数据对象,例如:变量、数组元素、结构成员,引用和解除引用的指针都是左值。
    非左值包括字面常量(用引号括起的字符串除外,它们由其地址表示)和包含多项的表达式。
     
    C++11新增了右值引用,使用&&声明:
double && rref = std::sqrt(36.00);
double j = 15.0;
double && jref = 2.0*j + 18.5;
std::cout << rref << '\n';
std::cout << jref << '\n';

将引用用于结构

引用非常适合用于结构和类。引入引用主要是为了这些类型。

程序8.6

#include <iostream>
#include <string>
struct free_throws
{
    std::string name;
    int made;
    int attempts;
    float percent;
};

void display(const free_throws & ft);
void set_pc(free_throws & ft);
free_throws & accumulate(free_throws & target, const free_throws & source);

int main()
{
    //partial initializations = remaining menbers set to 0
    free_throws one = { "Ifslsa Branch", 13, 14 };
    free_throws two = { "Andor Knott", 10,16 };
    free_throws three = { "Minnie Max", 7, 9 };
    free_throws four = { "Whily Looper", 5, 9 };
    free_throws five = { "Long Long", 6, 14 };
    free_throws team = { "Throwgoods", 0, 0 };
    //no initialization
    free_throws dup;

    set_pc(one);
    display(one);
    accumulate(team, one);
    display(team);
    //use return value as argument
    display(accumulate(team, two));
    accumulate(accumulate(team, three), four);
    display(team);
    //use return value in assignment
    dup = accumulate(team, five);
    std::cout << "Display team:\n";
    display(team);
    std::cout << "Display dup after assignment:\n";
    display(dup);
    set_pc(four);
    //ill-advised assignment
    accumulate(dup, five) = four;
    std::cout << "Displaying dup after ill-advised assignment:\n";
    display(dup);

    system("pause");
    return 0;
}

void display(const free_throws & ft)
{
    using std::cout;
    cout << "Name: " << ft.name << '\n';
    cout << "  Made:" << ft.made << '\t';
    cout << "Attempts: " << ft.attempts << '\t';
    cout << "Percent: " << ft.percent << '\n';
}

void set_pc(free_throws & ft)
{
    if (ft.attempts != 0)
        ft.percent = 100.0f *float(ft.made) / float(ft.attempts);
    else
        ft.percent = 0;
}

free_throws & accumulate(free_throws & target, const free_throws & source)
{
    target.attempts += source.attempts;
    target.made += source.made;
    set_pc(target);
    return target;
}

这里写图片描述

程序使用了一条赋值语句:

accumulate(dup,five) = four;

这条语句将值赋给函数调用,这是可行的,因为函数的返回值是一个引用。如果函数按值返回,这条语句将不能通过编译。由于返回的是指向dup的引用,因此上述代码与下面的代码等效:

accumulate(dup,five);
dup = four;

对于下面的语句:

dup=accumulate(team,five);

如果accumulate()返回一个结构,而不是指向结构的引用,将把整个结构复制到一个临时位置,在讲这个拷贝复制给dup;但在返回值为引用时,将直接把team复制到dup,其效率更高。

返回引用的函数实际上是被引用变量的别名。

返回引用时最重要的一点是,要避免返回函数终止时不在内存单元的引用。应避免编写下面这样的代码:

const free_throws & clone2(free_throws & ft)
{
    free_throws newguy;
    newguy = ft;
    return newguy;
}

该函数返回一个指向临时变量(newguy)的引用,函数运行完毕后它将不再存在。
为避免这种问题,可以返回一个作为参数传递给函数的引用,如程序8.6。
还可以使用new来分配新的存储空间,并返回指向该内存空间的指针。

将引用用于类对象

将类对象传递给函数时,C++通常的做法是使用引用。

程序8.7

#include <iostream>
#include <string>
using namespace std;
string version1(const string & s1, const string & s2);
const string & version2(string & s1, const string & s2);
const string & version3(string & s1, const string & s2);

int main()
{
    string input;
    string copy;
    string result;

    cout << "Enter a string: ";
    getline(cin, input);
    copy = input;
    cout << "Your string as entered: " << input << endl;
    result = version1(input, "***");
    cout << "Your string enhanced: " << result << endl;
    cout << "Your original string: " << input << endl;

    result = version2(input, "###");
    cout << "Your string enhanced: " << result << endl;
    cout << "Your original string: " << input << endl;

    cout << "Resetting original string.\n";
    input = copy;
    result = version3(input, "@@@");
    cout << "Your string enhanced: " << result << endl;
    cout << "Your original string: " << input << endl;

    system("pause");
    return 0;
}

string version1(const string & s1, const string & s2)
{
    string temp;

    temp = s2 + s1 + s2;
    return temp;
}

const string & version2(string & s1, const string & s2)
{
    s1 = s2 + s1 + s2;
    //safe to return reference passed to function
    return s1;
}

const string & version3(string & s1, const string & s2)
{
    string temp;

    temp = s2 + s1 + s2;
    //unsafe to return reference to local variable
    return temp;
}

这里写图片描述

version3()返回一个指向函数中声明的变量的引用,这个函数能通过编译,但编译器会发出警告,当程序试图执行该函数时将会崩溃。

对象、继承和引用

基类引用可以指向派生类对象,而无需进行强制类型转换。可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。例如,参数类型为ostream &可以接受ostream对象(如cout)或您声明的ofstream对象作为参数。

#include <iostream>
#include <fstream>
#include <cstdlib> 
using namespace std;

void file_it(ostream & os, double fo, const double fe[], int n);
const int LIMIT = 5;
int main()
{
    ofstream fout;
    const char * fn = "ep-data.txt";
    fout.open(fn);
    if (!fout.is_open())
    {
        cout << "Can't open " << fn << ".Bye.\n";
        exit(EXIT_FAILURE);
    }
    double objective;
    cout << "Enter the focal length of your "
        "telescope objective in mm: ";
    cin >> objective;
    double eps[LIMIT];
    cout << "Enter the focal lengths, in mm, of " << LIMIT
        << " eyepieces:\n";
    for (int i = 0; i < LIMIT; i++)
    {
        cout << "Eyepiece #" << i + 1 << ": ";
        cin >> eps[i];
    }
    file_it(fout, objective, eps, LIMIT);
    file_it(cout, objective, eps, LIMIT);
    cout << "Done\n";
    system("pause");
    return 0;
}
void file_it(ostream & os, double fo, const double fe[], int n)
{
    ios_base::fmtflags initial;
    initial = os.setf(ios_base::fixed);  //save initial formatting state
    os.precision(0);
    os << "Focal length of objective: " << fo << " mm\n";
    os.setf(ios::showpoint);
    os.precision(1);
    os.width(12);
    os << "f.l. eyepiece";
    os.width(15);
    os << "magnification" << endl;
    for (int i = 0; i < n; i++)
    {
        os.width(12);
        os << fe[i];
        os.width(15);
        os << int(fo / fe[i] + 0.5) << endl;
    }
    os.setf(initial);  //restore initial formatting state
}

这里写图片描述
对于该程序,最重要的一点是,参数os(其类型为ostream &)可以指向ostream对象(如cout),也可以指向ofstream对象(如fout)。

方法setf()能够设置各种格式化状态,例如,方法调用
setf(ios_base::fixed)将对象置于使用定点表示法的模式;
setf(ios_base::showpoint)将对象置于显示小数点的模式,即使小数部分为零。
方法precision()指定显示多少位小数(假设对象处于定点模式下)。
所有这些设置都一致驳斥不变,直到再次调用相应地方法重新设置它们。
方法width()设置下一次输出操作使用的字段宽度,这种设置只在显示下一个值时有效,然后将回复到默认设置。默认的字段宽度为零,这意味着刚好能容纳下要显示的内容。

ios_base::fmtflags是存储格式化设置信息的数据类型。

何时使用引用参数

使用引用参数的原因主要有两个:

  • 程序员能够修改调用函数中的数据对象;
  • 通过传递引用而不是真个数据对象,可以提高程序的运行速度。
      
      
    对于使用传递的值而不作修改的函数:

  • 如果数据对象很小,如内置数据类型或小型结构,则按值传递;

  • 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针;
  • 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间;
  • 如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递。

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

  • 如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码,则很明显,该函数将修改x;

  • 如果数据对象是数组,则只能使用指针;
  • 如果数据对象是结构,则使用引用或指针;
  • 如果数据对象是类对象,则使用引用。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值