1.引用

1.引用基本用法

  引用是c++对c的重要扩充。在c/c++中指针的作用基本都是一样的,但是c++增加了另外一种给函数传递地址的途径,这就是按引用传递(pass-by-reference),它也存在于其他一些编程语言中,并不是c++的发明。

■变量名实质上是一段连续内存空间的别名,是一个标号(门牌号)

■程序中通过变量来申请并命名内存空间

■通过变量的名字可以使用存储空间

对一段连续的内存空间只能取一个别名吗?
c++中新增了引用的概念,引用可以作为一个已定义变量的别名。

基本语法:

Type &ref = val;

注意事项:

■&在此不是求地址运算,而是起标识作用。

■类型标识符是指目标变量的类型

■必须在声明引用变量时进行初始化。

■引用初始化之后不能改变。

■不能有NULL引用。必须确保引用是和一块合法的存储单元关联。

建立对数组的引用。

#pragma warning(disable:4996)
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;

void test01();
void test02();
int main()
{
	//test01();

	system("pause");
	return EXIT_SUCCESS;
}

//1. 认识引用
void test01() 
{

	int a = 10;
	//给变量a取一个别名b
	int & b = a;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	cout << "------------" << endl;
	//操作b就相当于操作a本身
	b = 100;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	cout << "------------" << endl;
	//一个变量可以有n个别名
	int & c = a;
	c = 200;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	cout << "c:" << c << endl;
	cout << "------------" << endl;
	//a,b,c的地址都是相同的
	cout << "a:" << &a << endl;
	cout << "b:" << &b << endl;
	cout << "c:" << &c << endl;
}
//2. 使用引用注意事项
void test02() 
{
	//1) 引用必须初始化
	//int & ref; //报错:必须初始化引用
	//2) 引用一旦初始化,不能改变引用
	int a = 10;
	int b = 20;
	int & ref = a;
	ref = b; //不能改变引用
}

调用test01()函数输出结果:

a:10
b:10
------------
a:100
b:100
------------
a:200
b:200
c:200
------------
a:0000008C24F4FA64
b:0000008C24F4FA64
c:0000008C24F4FA64
请按任意键继续. . .

调用test02()函数输出结果:

a:20
b:20
------------
a的地址:00000060C60FF404
b的地址:00000060C60FF424
请按任意键继续. . .

2.建立数组引用:

int arr[] = { 1, 2, 3, 4, 5 };
//第一种方法
//1.定义数组类型
typedef int(MY_ARR)[5];//数组类型
//2.建立引用
MY_ARR &arref = arr;//建立引用,int &b=a;

//第二种方法
//直接定义引用
int(&arref2)[5] = arr;// int &b=a

//第三种方法
typedef int(&MY_ARR3)[5];//建立引用数组类型
MY_ARR3 arref3 = arr;

3.引用的本质

  引用的本质在c++内部实现是一个常指针.

Type &ref = val; // Type * const ref = &val;

  c++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见。

//发现是引用,转换为 int* const ref = &a;
void testFunc(int& ref){
	ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
	int a = 10;
	int &aRef = a; //自动转换为int * const aRef = &a;这也能说明引用为什么必须初始化
	aRef = 20; //内部发现aRef是引用,自动帮我们转换为: *aRef = 20;
	cout << "a:" << a << endl;
	cout << "aRef:" << aRef << endl;
	testFunc(a);
	return EXIT_SUCCESS;
}

4. 指针引用

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

void test01()
{
	char* p = "翠花";
	char* &p1 = p;
	cout << p1 << endl;
}
//被调函数
void func(char* &tmp)
{
	char *p;
	p=(char*)malloc(64);
	memset(p, 0, 64);
	strcpy(p, "小花");
	tmp = p;//省了*
}
//主调函数
void test02()
{
	char * mp = NULL;
	func(mp);//省了&
	cout << mp << endl;
}

int main()
{
	test02();
	system("pause");
	return EXIT_SUCCESS;
}

在c语言中如果想改变一个指针的指向而不是它所指向的内容,函数声明可能这样:

void fun(int**);

给指针变量取一个别名。

Type * pointer = NULL;  
Type * &p = pointer;
struct Teacher
{
	int mAge;
};
//指针间接修改teacher的年龄
void AllocateAndInitByPointer(Teacher** teacher)
{
	*teacher = (Teacher*)malloc(sizeof(Teacher));
	(*teacher)->mAge = 200;  
}
//引用修改teacher年龄
void AllocateAndInitByReference(Teacher*& teacher)
{
	teacher->mAge = 300;
}
void test()
{
	//创建Teacher
	Teacher * teacher = NULL;
	//指针间接赋值
	AllocateAndInitByPointer(&teacher);
	cout << "AllocateAndInitByPointer:" << teacher->mAge << endl;
	//引用赋值,将teacher本身传到ChangeAgeByReference函数中
	AllocateAndInitByReference(teacher);
	cout << "AllocateAndInitByReference:" << teacher->mAge << endl;
	free(teacher);
}

5.常量的引用

  常量引用的定义格式:

const Type& ref = val;

  常量引用注意:

■字面量不能赋给引用,但是可以赋给const引用

■ const修饰的引用,不能修改。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

int main02()
{
	//普通引用
	int a = 10;
	int &ref = a;
	ref = 20;

	//int &ret2 = 10;//不能给字面量取别名 err
	const int &ref3 = 10;//可以给const修饰的引用赋予字面量
	//const修饰符修饰的引用的原理
	//编译器会把上面的代码变为:int tmp=10;const int &ref3=tmp;
	//ref3 = 200;err

	//bool类型
	//bool类型定义的变量只有两个值,true和false,真和假,1和0
	bool is = 0;//注意:is的值除0以外,都是真
	if(is)
	{
		cout << "真" << endl;
	}
	else
	{
		cout << "假" << endl;
	}
	system("pause");
	return EXIT_SUCCESS;
}

6.将引用用作函数参数

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

#pragma warning(disable:4996)
#define _CRT_SECURE_NO_WARNINGS 1
//2022年10月19日22:49:15
#include <iostream>

void swapr(int &a, int &b);//a,b是整数的别名
void swapp(int *p, int *q);//p,q是整数地址
void swapv(int a, int b);//a,b是新变量

int main()
{
    using namespace std;
    int wallet1 = 300;
    int wallet2 = 350;
    cout << "wallte1 = $" << wallet1;
    cout << ", wallet2 = $" << wallet2 << endl;
    
    cout << "Using references to swap contents:\n";
    swapr(wallet1, wallet2);//传递变量
    cout << "wallte1 = $" << wallet1;
    cout << ", wallet2 = $" << wallet2 << endl;

    cout << "Using pointers to swap contents:\n";
    swapp(&wallet1, &wallet2);//传递变量
    cout << "wallte1 = $" << wallet1;
    cout << ", wallet2 = $" << wallet2 << endl;

    cout << "Tring to using passing by value:\n";
    swapv(wallet1, wallet2);//传递变量
    cout << "wallte1 = $" << wallet1;
    cout << ", wallet2 = $" << wallet2 << endl;

    system("pause");
    return EXIT_SUCCESS;
}

void swapr(int &a, int &b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

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

    tmp = *p;
    *p = *q;
    *q = tmp;
}

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

    tmp = a;
    a = b;
    b = tmp;
}

输出结果:

wallte1 = $300, wallet2 = $350
Using references to swap contents:
wallte1 = $350, wallet2 = $300
Using pointers to swap contents:
wallte1 = $300, wallet2 = $350
Tring to using passing by value:
wallte1 = $300, wallet2 = $350
请按任意键继续. . .

7.引用的属性和特别之处

程序:

//cubes.cpp--普通和引用参数
#pragma warning(disable:4996)
#define _CRT_SECURE_NO_WARNINGS 1
//2022年10月20日19:34:29
#include <iostream>
using namespace std;

double cube(double a);
double refcube(double &ra);

int main()
{
    double x = 3.0;

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

    system("pause");
    return EXIT_SUCCESS;
}

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

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

输出结果:

27 = cube of 3
27 = cube of 27
请按任意键继续. . .

  refcube()函数修改了main()中的值,而cube()没有,这提醒我们为何通常按值传递。变量a位于cube()中,它被初始化为x的值,但修改a并不会影响x。但由于refcube()使用了引用参数,因此修改ra实际上就是修改x。如果程序员的意图是函数使用传递给他的信息,而不对这些信息进行修改,同时又想使用引用,则应使用常量引用。

  当数据比较大(如结构和类)时,引用参数将很有用。

将参数传递给引用参数的函数,传递引用的限制更严格。如果ra是一个变量的别名,则实参应是该变量。下面的代码不合适,因为表达式x+3.0并不是变量:

double z = refcube(x + 3.0);

  例如,不能将值赋给该表达式

x + 3.0 = 5.0;

临时变量、引用参数和const

  如果实参与引用参数不匹配,C++将生成临时变量。当前,仅当参数为const引用时,C++才允许这样做,但以前不是这样。下面来看何种情况下,C++将生成临时变量,以及为何对const引用的限制是合理的。

  首先,什么时候将创建临时变量?如果引用参数是const,则编译器将在下面两种情况下生成临时变量:

■实参的类型正确,但不是左值;

■实参的类型不正确,但可以转换为正确的类型。

左值参数,是可以被引用的数据对象。

double refcube(const double &ra)
{
    return ra * ra * ra;
}
double side = 3.0;
double * pd = &side;
double & rd = side;
long edge = 5L;
double lens[4] = {2.0, 5.0, 10.0, 12.0};
double c1 = refcube(side);//ra is side
double c2 = refcube(lens[2]);//ra is lens[2]
double c3 = refcube(rd); //ra is rd is side
double c4 = refcube(*pd);//ra is *pd is side
double c5 = refcube(edge);//ra is temporary variable
double c6 = refcube(7.0); //ra is temporary variable
double c6 = refcube(side + 10.0); //ra is temporary variable

  参数side、lens[2]、rd和*pd都是有名称的、double类型的数据对象,因此可以为其创建引用,而不需要临时变量(数组元素的行为与同类型的变量类似)。然而、edge虽然是变量,类型却不正确,double引用不能指向long。另一方面,参数7.0和side+10.0的类型都正确,但是没有名称,在这些情况下,编译器都将生成一个临时匿名变量,并让ra指向它。这些临时变量只在函数调用期间存在,此后编译器便可以随意将其删除。

  简而言之,如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现。解决方法是,禁止创建临时变量,现在C++标准正是这样做的(在默认情况下,有些编译器将发出警告,而不是错误信息,需注意)。

  现在来看refcube()函数。该函数的目的只是使用传递的值,而不是修改它们,因此临时变量不会造成任何不利的影响,反而会使函数在可处理的参数种类方面更通用。因此,如果声明将引用指定为const,C++将在必要时生成临时变量。实际上,对于形参为const引用的C++函数,如果实参不匹配,则其行为类似于按值传递,为确保原始数据不被修改,将使用临时变量来存储值。

注意:如果函数调用的参数不是左值或与相应的const引用参数的类型不匹配,则C++将创建类型正确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量。

应尽可能使用const

将引用参数声明为常量数据的引用的理由有三个:

■使用const可以避免无意中修改数据的编程错误;

■使用const使函数能够处理const和非const实参,否则将只能接受非const数据;

■使用const引用使函数能够正确生成并使用临时变量。

8.将引用用于结构

  引用非常适合用于结构和类(C++的用户定义类型)。引入引用的主要目的是为了用于这些类型的,而不是基本的内置类型。

#pragma warning(disable:4996)
#define _CRT_SECURE_NO_WARNINGS 1
//2022年10月20日20:41:20
#include <iostream>
#include <string>
using namespace std;

struct free_throws
{
    std::string name;
    int made;
    int attempts;
    float percent;
};

void display(const free_throws &ft);//显示对象的成员值
void set_pc(free_throws &ft);//设置percent
free_throws &accumulate(free_throws &target, const free_throws &source);

int main()
{
    //部分初始化,剩下的成员设置为0
    free_throws one = {"Ifelsa Branch", 13, 14};
    free_throws two = {"Andor Knott", 10, 16};
    free_throws three = {"Minie Max", 7, 9};
    free_throws four = {"Whily Looper", 5, 9};
    free_throws five = {"Long Long", 6, 14};
    free_throws team = {"Throwgoods", 0, 0};
    //不初始化
    free_throws dup;

    set_pc(one);//设置percent
    display(one);//显示对象的成员值
    accumulate(team, one);
    display(team);
    //在参数中使用返回值
    accumulate(accumulate(team, two), four);
    display(team);
    //在参数中使用返回值
    dup = accumulate(team, five);
    std::cout << "Displaying team:\n";
    display(team);
    std::cout << "Display dup after assignment:\n";
    display(dup);
    set_pc(four);
    //不明智的分配
    accumulate(dup, five) = four;
    std::cout << "Displaying dup after ill-advised assignment:\n";
    display(dup);


    system("pause");
    return EXIT_SUCCESS;
}

void display(const free_throws &ft)//显示对象的成员值
{
    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)//设置percent
{
    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;
}

输出结果:

Name: Ifelsa Branch
 Made: 13       Attempts: 14    Percent: 92.8571
Name: Throwgoods
 Made: 13       Attempts: 14    Percent: 92.8571
Name: Throwgoods
 Made: 28       Attempts: 39    Percent: 71.7949
Displaying team:
Name: Throwgoods
 Made: 34       Attempts: 53    Percent: 64.1509
Display dup after assignment:
Name: Throwgoods
 Made: 34       Attempts: 53    Percent: 64.1509
Displaying dup after ill-advised assignment:
Name: Whily Looper
 Made: 5        Attempts: 9     Percent: 55.5556
请按任意键继续. . .

1.程序说明

  由于display()显示结构的内容,而不修改它,因此这个函数使用了一个const引用参数。就这个函数而言,也可按值传递,但与复制原始结构的拷贝相比,使用引用可节省时间和内存。

  如果返回类型被声明为free_throws而不是free)throws &,上述返回语句将返回target(也就是team)的拷贝。但返回类型为引用,这意味着返回的是最初传递给accumulate()的team()对象。

2.为何要返回引用

  传统返回机制与按值传递传递函数参数类似:计算关键字return后面的表达式,并将结果返回给调用函数。从概念上说,这个值被复制到一个临时位置,而调用程序将使用这个值。

double m = sqrt(16.0);

  上述中,值4.0被复制到一个临时位置,然后复制给m。

dup = accumulate(team, five);

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

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

3.返回引用时需要注意的问题

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

const free_throws & clone2(free_throws & ft)
{
    free_throws newguy;//犯了严重的错误
    newguy = ft;
    return newguy;//返回引用去复制
}

  该函数返回一个指向临时变量的引用,函数运行完毕后它将不再存在。为避免这种问题,最简单的方法是,返回一个作为参数传递给函数的引用。作为参数的引用将指向调用函数的数据,因此返回的引用也将指向这些参数。

  另一种方法是用new来分配新的存储空间。前面见过这样的函数,它使用new为字符串分配空间,并返回指向该内存空间的指针。

const free_throws & clone(free_throws & ft)
{
    free_throws * pt;
    * pt = ft;//复制信息
    retutrn *pt;//返回引用去复制
}

4.为何将const用于引用返回类型

程序清单8.6包含如下:

accumulate(dup, five) = four;

  其效果如下:首先将five的数据添加到dup中,再使用four的内容覆盖覆盖dup的内容。这条语句为何能够通过编译呢?在赋值语句中,左边必须是可以修改的左值。也就是说,在赋值表达中,左边的子表达式必须标识一个可修改的内存块。在这里,函数返回指向dup的引用,它确实标识的是一个这样的内存块,因此这条语句是合法的。

  另一个方面,常规(非引用)返回类型是右值---不能通过访问的值。这种表达式可出现在赋值语句的右边,但不能出现在左边。但为何常规函数返回值是右值呢?这是因为这种返回值位于临时内存单元中,运行到下一条语句时,它们可能不再存在。

  假如要使用引用返回值,但又不允许执行像给accumulate()赋值这样的操作,只需将返类型声明为const引用:

const free_throws &accumulate(free_throws &target, const free_throws &source);

9.将引用用于类对象

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

程序:

#pragma warning(disable:4996)
#define _CRT_SECURE_NO_WARNINGS 1
//2022年10月22日19:20:33
//8.7strquote.cpp--不同的设计
#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 orginal string: " << input << endl;

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

    cout << "Reseting orginal string.\n";
    input = copy;
    result = version3(input, "@@@");
    cout << "Your string enhanced: " << result << endl;
    cout << "Your orginal string: " << input << endl;
    
    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;
    //安全地返回传递给函数的引用
    return s1;
}

const string &version3(string &s1, const string &s2)//不好的设计
{
    string temp;//临时变量

    temp = s2 + s1 + s2;
    //返回传递给局部变量的引用是不安全的
    return temp;
}

输出结果:

Enter a string: It's not my fault.
Your string as entered: It's not my fault.
Your string enhanced: ***It's not my fault.***
Your orginal string: It's not my fault.
Your string enhanced: ###It's not my fault.###
Your orginal string: ###It's not my fault.###
Reseting orginal string.
Your string enhanced:
Your orginal string: It's not my fault.

  它存在一个致命的缺陷:返回一个指向version3()中的变量的引用。这个函数能够通过编译(但编译器会发出警告),但当程序试图执行该函数时将崩溃。具体地说,问题是下面的赋值语句引发的:

result = version3(input, "@@@");

10.对象、继承和引用

  ofstream对象可以使用ostream类的方法,这使得文件输入/输出的格式与控制台输入/输出相同。使得能够将特性从一个类传递给另一个类的语言特性被称为继承。ostream是基类,ofstream是派生类。派生类继承了基类的方法,这意味着ofstream对象可以使用基类的特性。

  继承的另一个特征是,基类引用可以指向派生类对象,而无需进行强制类型转换。这种特征的一个实际结果是,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。

程序清单8.8

#pragma warning(disable:4996)
#define _CRT_SECURE_NO_WARNINGS 1
//2023年2月9日09:39:30
//filefunc.cpp -- 带有ostream引用参数的函数
#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:\n";
	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 EXIT_SUCCESS;
}

void file_it(ostream& os, double fo, const double fe[], int n)
{
	os << "Focal length of objective: " << fo << " mm\n";
	os << "f.1. eyepiece   " << "magnification" << endl;

	for (int i = 0; i < n; i++)
	{
		os << "     " << fe[i] << "              " << int(fo / fe[i] + 0.5) << endl;
	}
}

输入:

Enter the focal length of your  telescope objective in mm:
1800
Enter the focal lengths, in mm, of 5 eyepieces:
Eyepiece #1: 33
Eyepiece #2: 55
Eyepiece #3: 22
Eyepiece #4: 66
Eyepiece #5: 44

输出:

Focal length of objective: 1800 mm
f.1. eyepiece   magnification
     33              55
     55              33
     22              82
     66              27
     44              41
Done
请按任意键继续. . .

11.何时使用引用参数

1.使用引用参数的主要原因有两个。

■程序员能够修改调用函数中的数据对象。

■通过传递引用而不是调用整个数据对象,可以提高程序的运行速度。

2.对于传递的值而不做修改的函数

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

■如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。

■如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制到结构所需的时间和空间。

■如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因。因此,传递类对象参数的标准方式是按引用传递。

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

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

■如果数据对象是数组,则只能使用指针。

■如果数据对象是结构,则使用引用或指针。

■如果数据对象是类对象,则使用引用。

参考资料

参考资料来源于黑马程序员、C++ Primer Plus、C++ Primer等

posted @ 2022-08-16 10:41  CodeMagicianT  阅读(93)  评论(0编辑  收藏  举报