c++阶梯之引用与内联函数

1. 引用

1.1 引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

语法

类型& 引用变量名(对象名) = 引用实体;

示例 

很显然,在下面这个例子中,a与b共用同一地址。 

c++阶梯之引用与内联函数_第1张图片

这里需要注意:引用类型必须和引用实体是同种类型的

1.2 引用特性

1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体

示例 

引用定义时必须初始化。 

c++阶梯之引用与内联函数_第2张图片

一个变量可以多次引用 

c++阶梯之引用与内联函数_第3张图片

 引用一旦引用一个实体,再不能引用其他实体

c++阶梯之引用与内联函数_第4张图片

1.3 常引用

1.权限只能缩小,不能放大。

2.类型转换时会产生临时变量,临时变量具有常性 

 关于这两个概念,我们结合代码来看,详见代码注释

c++阶梯之引用与内联函数_第5张图片

所谓权限,无非就是对对象的操作权限,对常量我们的操作权限为只读,对变量则可读可写。 

那么类型转换时会产生临时变量是什么意思呢?

我们来看这一段代码:

int main()
{
	double a = 13.14;
	int b = a;//double转换为int

	int i = 97;
	char ch = 'a';
	if (i == ch) //char转换为int
	{
		cout << "相等" << endl;
	}
	cout << b << endl;
	return 0;
}

c++阶梯之引用与内联函数_第6张图片

这一段代码有两个类型转换的例子,两者相类似,都是在类型转换时产生一个临时变量。

double a = 13.14;  int b = a;中,产生一个值为13的临时变量赋值给b。

i与ch的比较中,产生一个值为该字符ascll对应值的整型临时变量。

这些临时变量有一个共性,那就是具有常性。 

1.4  关于引用的使用场景

1.做参数

2.做返回值

1.做参数

我们以下述代码为例,曾经的交换数据,我们需要借助指针来实现,而现在引用就可以,而且还比指针简单很多。

引用版:
void Swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}

int main()
{
	int a = 10;
	int b = 20;
	cout << "a:" << a << "\t" << "b:" << b << endl;
	Swap(a, b);
	cout << "a:" << a << "\t" << "b:" << b << endl;
	return 0;
}
指针版: 

 c++阶梯之引用与内联函数_第7张图片

明显引用更为方便简洁。

2.做返回值

下面这段代码,大家觉得有没有问题呢?

int& add(int x, int y)
{
	int c = x + y;
	return c;
}
int main()
{
	int a = 10;
	int b = 5;
	int c = 9;
	int d = 10;
	int ret = add(a, b);
	cout << "ret:\t" << ret << endl;
    cout << "ret:\t" << ret << endl;
	return 0;
}

我们看看运行结果: 

c++阶梯之引用与内联函数_第8张图片

很好,貌似没有问题,但我们发现,编译器给我们发出了一个小小的警告,如果是以前我们可能会嗤之以鼻不管不顾,但现在的我们不一样了,一个小小的警告也要抹杀在牢笼里。

完蛋,当我们准备解决警告时,却发现他是一个大问题。

这个问题我们之前遇到过,费了很大的力气才解决掉,现在他又出来了。

这是为什么呢?

很简单,一个函数的生命周期很短,函数调用,产生函数栈帧;函数调用结束,函数栈帧销毁。 那函数内产生局部变量或临时变量的生命周期自然随之结束,这一段空间就还给了操作系统,而我们直接对局部变量起别名并返回,然后在其他地方一直使用,不就相当于一直在退了房的宾馆里转悠嘛,违法行为不可取。

大家不爱看字,那就画个图给大家看。 

c++阶梯之引用与内联函数_第9张图片

 如果你的东西还放在里面,宾馆换了个把锁,你进不去了,东西就丢了。

所以如果是全局变量,staic修饰过的变量,或者动态开辟的内存就不受限制。

1.5 传值,传引用效率比较

我们使用如下代码进行比较两种效率。 ( 关于clock () 的用法大家可以在网上查阅 )

struct A
{
	int arr[10000];
};

void TestTranVal(A a)//传值
{
}

void TestTranQuote(A& a)//传引用
{
}
int main()
{
	A a;

	size_t begin1 = clock();
	for(int i=0;i<10000;i++)
	TestTranVal(a);
	size_t end1 = clock();

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

	cout << "TestTranVal--time:\t" << end1 - begin1 << endl;
	cout << "TestTranQuote--time:\t" << end2 - begin2 << endl;

	return 0;
}

 c++阶梯之引用与内联函数_第10张图片

至于为什么会这样?且看下面这段文字!

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。 

1.6值和引用的作为返回值类型的性能比较
 

这里只需要将上面的代码略微改变。

struct A
{
	int arr[10000];
};

A a;//全局变量

A TestTranVal()
{
	return a;
}

A& TestTranQuote()
{
	return a;
}
int main()
{

	size_t begin1 = clock();
	for (int i = 0; i < 10000; i++)
		TestTranVal();
	size_t end1 = clock();

	size_t begin2 = clock();
	for (int i = 0; i < 10000; i++)
		TestTranQuote();
	size_t end2 = clock();

	cout << "TestTranVal--time:\t" << end1 - begin1 << endl;
	cout << "TestTranQuote--time:\t" << end2 - begin2 << endl;

	return 0;
}

 

 造成这一现象的原因在1.5后面哦!

1.7引用和指针的区别

相信大家在学习引用的时候,总是有一种似曾相识的感觉,这是因为他的功能和指针有很大部分的重叠。

在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

但在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

int main()
{
	int a = 10;
	int c = 9;
	int& b = a;
	b = 20;
	cout << " a " << a << endl;

	int* p = &a;
	*p = 30;
	cout << " a " << a << endl;
	p = &c;
	cout << " *p " << *p << endl;
	return 0;
}

c++阶梯之引用与内联函数_第11张图片

我们来看看汇编视角的引用与指针:

c++阶梯之引用与内联函数_第12张图片

 很显然,在汇编看来,指针与引用并没有区别。

我们试着来总结一下指针与引用的不同点

1.引用是给一个变量起别名,而指针存储变量的数据地址。

2.引用在定义时必须初始化,指针则没有这个要求。

3.引用定义后就不能再更改指向,指针可以,这是他们两个最本质的区别,也是引用无法完全替代指针的根本原因。

 4.在sizeof中,引用的结果就是引用类型的大小,而指针恒定为4或者8(取决于编译器)。

5.引用相对于指针更加安全,没有空引用,但有空指针。

6.譬如自增自减,引用操作的是对象的实体内容,而指针操作的是地址块。

7.在底层汇编视角,引用与指针并没有什么区别。

8.对于对象的访问方式不同,指针需要解引用,而引用由编译器处理。

2.  内联函数

2.1 概念 

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。

c++阶梯之引用与内联函数_第13张图片
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的
调用。

c++阶梯之引用与内联函数_第14张图片

 2.2 内联函数的特性

1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。
2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到

2.3 内联函数与宏

宏的优缺点? 

优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。

C++有哪些技术替代宏?
 

1. 常量定义 换用const enum
2. 短小函数定义 换用内联函数 

结语 

    指舞键盘上,悠然博弈回。如果您感兴趣,不妨看看我其他的文章,也许会有更多的收获。希望我们能在未来的日子里一起成长,共同进步。 

你可能感兴趣的:(c++,c++,数据结构,c语言,开发语言)