C++ References


1. Reference concepts and examples

The C language pointer stores the address of the variable, and then the variable can be accessed or changed by dereferencing, and the address stored in the pointer variable can also be changed
insert image description here

Modifying variables requires dereferencing pointer variables, which is troublesome, and modifying addresses is somewhat unsafe, so there are nouns in C++ that can also modify variables and access variables. This is called reference reference reference: reference is not newly
defined variable, but an alias to the existing variable,
but the compiler will not open up memory space for the reference variable, it shares the same memory space with the variable it refers to
insert image description here
insert image description here

The reference operator & is the same as the address-taking operator, but the reference is a binocular when used, the data type on the left of &, and the reference variable name on the right.
insert image description here

The address-taking operator is a unary operator, and the right side is the address-taking variable name
insert image description here

Quoting is equivalent to giving someone a nickname. Although a person may have many nicknames, these nicknames still correspond to that person and one of the nicknames is changed. Other nicknames are changing. For example, there is a person named
Zhang Three, others call him an outlaw lunatic, and now we have sentenced an outlaw lunatic to ten years in prison, is it true that Zhang San was sentenced to 10 years in prison?

Although a variable may have different aliases, the spaces of these aliases are all in the same space, and changing one of the aliases will also affect other aliases and even itself.
insert image description here

#include<iostream>

//在引用时要初始化,要说明是谁的引用
using namespace std;
int main()
{
    
    
	int a = 0;
	int& b = a;//对a起别名
	int& c = b;//对b起别名

	c++;//b的别名改变
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;

	b++;//a的别名改变
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;

	//b是a的引用,c是b的引用,它们和它所引用的对象共用同一块空间,所以它们地址都相同
	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;

	return 0;
}

insert image description here

2. References as function parameters

Referencing a formal parameter as a function parameter is equivalent to a formal parameter being a reference to an actual parameter when the function is called, so the operation on the actual parameter inside the function is also the operation on the actual parameter. References are used as parameters, formal parameters and actual parameters share a memory space, and no new memory space is opened up for formal parameters, which reduces the memory loss due to formal parameters when calling, and improves efficiency

#include<iostream>
using namespace std;
//交换函数
//形参x是实参a的引用
//形参y是实参b的引用
void Swap(int& x, int& y)
{
    
    
    //交换变量x和y的值就是交换变量a和b的值
	int temp = x;
	x = y;
	y = temp;
	//x与a的地址相同
	cout <<"形参x地址:"<< &x << endl;
	//y与b的地址相同
	cout <<"形参y地址:" << &y << endl;
	//编译器并没有为它们开辟临时空间

int main()
{
    
    
	int a = 10;
	int b = 20;
	cout << "交换前:"<<endl;
	cout << "a:"<<a << endl;
	cout <<"b:"<< b << endl;

	Swap(a, b);
	cout << "交换后:"<<endl;
	cout <<"a:"<< a << endl;
	cout <<"b:"<< b << endl;

	return 0;
}

insert image description here

Instead of using references as parameters, if you want to achieve the exchange function, you need to pass the address of the actual parameter when calling the function, the formal parameter is a pointer, and you need to open up a temporary space for this formal parameter (pointer variable).

//传址
void Swap(int* x, int* y)
{
    
    
	int tmp = *x;
	*x = *y;
	*y = tmp;
	cout <<"形参x地址:"<< &x << endl;//x与a的地址并不相同,即使x指向a的地址,但是只要它创建了一个变量,那么编译器就会为起分配内存空间
	cout <<"形参y地址:"<< &y << endl;//y与b的地址并不相同
}

//传值
int add(int a, int b)
{
    
    
	cout <<"形参a地址:"<< &a << endl;
	cout <<"形参b地址:"<< &b << endl;
	return a + b;
}

int main()
{
    
    
	int a = 10;
	int b = 20;
	cout << "交换前:" << endl;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;

	cout <<"实参a地址:"<< &a << endl;
	cout <<"实参b地址:"<< &b << endl;
	Swap(&a, &b);
	cout << "交换后:" << endl;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;

	int x = 1;
	int y = 2;
	cout <<"实参x地址:"<< &x << endl;
	cout <<"实参y地址:"<< &b << endl;

	int ret = add(x, y);
	cout << ret << endl;
	
	return 0;
}

insert image description here

It is not a reference type parameter. Whether the actual parameter is passed by value or address when the function is called, the compiler will open up a temporary memory space for the formal parameter variable when it is defined. The formal parameter has its own address, which will be There is a certain loss in opening temporary space for formal parameters, which reduces efficiency. When citing parameters as parameters, there is no temporary space for formal parameters, which reduces the loss due to opening temporary space and improves efficiency. When the parameter is a large one, the efficiency is much lower than that of reference as a parameter

Take the formal parameter as a large structure as an example

struct A
{
    
    
	int a[100000];
};

void f(A c)//c++可以直接用结构体名
{
    
    

}

//引用没有开临时空间,没有那些消耗
void f1(A&c){
    
    }

void TestValue()
{
    
    
	A a;
	size_t begin1 = clock();
	for (int i = 0; i < 10000; i++)
	{
    
    
		f(a);
	}
	size_t end1 = clock();

	size_t begin2 = clock();
	for (int i = 0; i < 10000; i++)
	{
    
    
		f1(a);
	}
	size_t end2 = clock();

	cout << "f_time:" << end1 - begin1 << endl;
	cout << "f1_time:" << end2 - begin2 << endl;
}

int main()
{
    
    
	TestValue();
	return 0;
}


insert image description here

Using references as parameters greatly improves efficiency

2. Refer to the return value of the function

Value return and reference return. When reference is returned, the returned object has lvalue characteristics and can be modified

值返回

int fun1()
{
    
    
	int n = 0;
	n++;
	return n;
}

int main()
{
    
    
	int ret = fun1();
	cout << ret << endl;
	return 0;
}

insert image description here

n is not directly returned to ret, it will generate a temporary variable in the middle, usually a register, and then return it to ret as a return value, but it is not necessarily a register, the register is only four bytes or 8 bytes, and the amount of data is large. No, so sometimes the generated temporary variable is not necessarily a register.
The return value is copied to the temporary variable, and the temporary variable is copied to the receiving variable. Two copies

insert image description here

Why is a temporary variable space generated?

The function call opens up space for the function. In the main function, a space is applied for the variable ret to store the value of ret. The main function calls the fun1 function, and a space is applied for n in the fun1 function. When the fun1 function call ends, the fun1 space is destroyed, but the value of n in the fun1 function needs to be returned. n is in the local domain, and n cannot be used as the return value. How can the fun1 function be returned after the fun1 function has been called? The value is put into the register, and the value is brought back to the function that called it through the register, and the process of bringing the value back generates a temporary variable, and then the value is copied to the temporary variable, and the return value is brought back. Consumption caused by temporary space.

引用返回
How to avoid the consumption caused by generating temporary variables?

Returning by reference, not generating temporary variables reduces the consumption caused by copying and improves efficiency. Small objects have little impact, but large objects greatly improve efficiency
insert image description here

When returning large objects, there is a big difference in the efficiency of reference and value return

struct A {
    
     int a[10000]; };

A a;
// 值返回
A TestFunc1() {
    
     return a; }
// 引用返回
A& TestFunc2() {
    
     return a; }

void TestReturnByRefOrValue()
{
    
    
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();

	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();

	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

int main()
{
    
    
	TestReturnByRefOrValue();

	return 0;
}

insert image description here

Returning by reference can greatly improve efficiency

But not every scene can be returned by reference
insert image description here

The fun1 function ends and the stack frame is destroyed. If the stack frame is not cleaned up, the result of ret will be correct. The fun1 function
ends and the stack frame is destroyed. If the stack frame is cleaned up, the result of ret will be a random value. The content in the stack frame space has been cleaned up, and random values ​​will appear when accessing it again.

int& fun()
{
    
    
	//static int n = 0;
	int n = 0;
	n++;

	cout << &n << endl;
	return n;
}

int main()
{
    
    
	
	const int& ret = fun();//由于这个常量返回是通过寄存器保存,然后回来时再将寄存器里面的值赋值给ret,
	cout << ret << endl;
	cout << &ret << endl;
	
	return 0;
}

insert image description here
Its two addresses are the same
insert image description here

The stack frame of the fun function is not cleaned up, and the compiler generally does not clean up the contents of the stack frame space, but the contents of this space may be random values, and this space is reassigned.
insert image description here

ret is an alias of n, and then calling the fun function will overwrite the stack frame space of the previous function to allocate a stack frame space for the newly called fun, and then access ret at this time, the value of ret may be a random value, and access the destruction space Variables above are accessed out of bounds like an array.
The function stack frame is not cleaned up, and another function is called, and this function needs to create a stack frame and initialize, and then this stack frame will cover the stack frame space of the previous function, and then access ret at this time, it will be behind the random
insert image description here
value The stack frame overwrites the previous stack frame, and access at this time is a random value

Using references to return out of the function scope and then accessing such operations is equivalent to wild pointer access, which is very dangerous. Therefore, the value to be returned can only be used in this function, and cannot be returned by reference. When the function stack frame is destroyed, it will not affect the returned value. It can be returned by reference. The return value returned by reference is not applicable if the reference object is not in the scope 适用于全局变量,静态变量(static),由malloc出来的
. Reference returns.

Assign the value of a certain position to the sequence table + modify
Change the value of the pos position to 10. Generally, you need to obtain the value of the pos position, then modify the value of this position to 10, and then assign 10 to the pos position of the sequence table.

#include<assert.h>
struct SeqList
{
    
    
	int a[100];
	size_t size;
};

//将pos位置的值修改为x
void SLModify(SeqList* ps, int pos, int x)
{
    
    
	assert(pos >= 0 && pos < 100);
	ps->a[pos] = x;
}

//获取pos位置的值
int Get(SeqList* ps, int pos)
{
    
    
	assert(pos >= 0 && pos < 100);
	return ps->a[pos];
}

int main()
{
    
    
	SeqList s;
	SLModify(&s, 0, 1);

	int ret = Get(&s, 0);
	cout << ret << endl;
	ret += 5;//只是得到pos位置的值修改并没有做到对顺序表进行修改
	cout << ret << endl;
	SLModify(&s, 0, ret);//要修改还需要重新调用修改顺序表的接口
}

insert image description here

This is very troublesome. Obviously, the value has been modified, and the interface function for modifying the sequence table must be called again. Therefore, returning by reference can modify it when obtaining the pos position value.

The reference is used as the return value of the function. The object it returns has the characteristics of an lvalue and can be modified. The return value object of a general function cannot be modified, so you can directly obtain the alias of the value at the pos position, and then modify it. That is, modifying the sequence table

#include<assert.h>
struct SeqList
{
    
    
	int a[100];
	size_t size;
};


void SLModify(SeqList* ps, int pos, int x)
{
    
    
	assert(pos >= 0 && pos < 100);
	ps->a[pos] = x;
}

int& SLAt(SeqList* ps, int pos)
{
    
    
	assert(pos >= 0 && pos < 100);
	return ps->a[pos];
}

int main()
{
    
    
	SeqList s;
	SLModify(&s, 0, 1);

	SLAt(&s, 0) = 10;//引用返回的具有左值特性,可修改
	cout << SLAt(&s, 0) << endl;
	cout << SLAt(&s, 0)+5 << endl;
	return 0;
}

insert image description here

return value by reference

  1. Reduce copying and improve efficiency
  2. Modify the return value and get the return value

Four, often cited

When defining a reference variable, use const to modify it. The defined reference variable is also a constant reference .
The general reference
insert image description here
insert image description here
a can be modified and readable.

insert image description here

When referencing here, the a variable is modified by const and cannot be modified, read-only, but if it is referenced, it will become modifiable, and it is wrong to modify its permissions. 在引用过程中权限不能放大.
So how to make it correct, in

insert image description here

Add const modification to become a constant reference

insert image description here

insert image description here

z is an alias of x, but now it only has a readable attribute and cannot be modified. The modifiable attribute of the original variable is gone, so the authority can be reduced during the reference process. When accessing by z, it cannot be modified and x cannot be modified, but when accessing by x, x changes, and z will also change at this time, z and x share space, but cannot be changed through z, but z can be changed through x
insert image description here
insert image description here
Why not?
Temporary variables are generated due to
insert image description here
different types of conversions. Temporary variables are rvalues ​​and are constant, which is equivalent to adding const modification and cannot be modified, so they can be often quoted
insert image description here

insert image description here

The authority cannot be enlarged when quoting, and it needs to be modified with const to make it a constant reference

insert image description here

insert image description here

This is ok, permission panning

Often quoted
insert image description here

Five, the difference between references and pointers

References must be initialized when they are defined, and pointers are not required
. References are aliases for known variables, and pointers are addresses. References
cannot refer to other entities after referencing one entity, but pointers can point to other entities
. There are no NULL references, but there are NULL pointers
to calculate the size word At festival time, the size of the reference is the size of the reference type
, and the number of bytes occupied by the pointer is always 4 or 8 bytes in the address space Size
References are safer to use than pointers (pointer null pointers, wild pointers).
Syntactically, references have no open space, and pointers have open space

insert image description here


However, from the perspective of the underlying assembly instructions, referencing the pointer assembly implemented in a pointer-like manner
insert image description here
refers to the assembly
insert image description here

Guess you like

Origin blog.csdn.net/m0_67768006/article/details/130237188