C++缺省参数、函数重载、引用

一、缺省参数

定义:缺省参数就是在声明或者定义函数时在函数参数列表中指定默认值。再调用函数时,如果没有指定实参则采用该默认值,否则采用指定的实参。

分类:全缺省参数和半缺省参数

注意(1)半缺省参数必须从右向左依次给出,不可以间隔着给默认值。(2)缺省参数不可以同时出现在函数声明和定义的位置,因为如果同时出现,而且默认值不一样,编译器不知道按照哪个执行。

一般在缺省我们只在函数声明位置出现。因为我们一般会把函数声明和链接库给用户,供用户调用,具体代码实现是保密的。所以我们就一般不把缺省值写到函数定义,因为用户看不到。

(3)缺省值必须是全局变量或者常量。(4)C语言编译器不支持缺省参数。

二、函数重载

定义:C++中允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的参数列表不同(参数个数,参数类型,参数顺序三者至少有一个不同),常用来处理实现功能类似数据类型不同的问题。与函数返回值是否一样无关

为什么可以函数重载?因为编译器在编译期间会对实参的类型进行推演,选择合适的函数。

一个函数不可以既作为函数重载,又作为有默认参数的函数(缺省),因为可能半缺省参数可能导致编译器无法确定调用哪个函数。

问题:为什么C语言不可以函数重载,而C++可以?

这主要和两种语言的函数名字修饰有关。一个程序要运行起来包括预处理、编译、汇编、链接。名字修饰就是在编译过程中编译器会将函数、变量的名称重新改编的机制,简单地说,编译器为了唯一的区分各个函数,通过某种算法,重新给每个函数起一个唯一的名字,用于唯一标识某个函数。

C语言名字修饰非常简单,仅仅在函数名字前面加一个下划线,所以如果C语言中定义了几个同名函数,就会导致编译错误,因为不能够唯一标识某个函数,就会导致产生冲突。

从结果可以看出只是在函数前面加了有一个下划线。

对于C++而言,因为要支持函数重载和命名空间,所以相对于而言C++的命名修饰比较复杂。我们可以只声明一个函数而不定义它,可以看出C++命名修饰确实比C复杂,而且可以唯一标识每一个函数。

从上述错误中可以看书,编译器在底层不是使用的_Add,而是比较复杂的名字,而且底层函数名字中包含了函数名,返回值,参数类型,而函数重载要求函数参数列表不同,所以在底层可以唯一的表示某个函数。

有时候我们希望某一个函数按照C风格编译,那么我们需要在函数声明处加上extern “C”

三、引用

定义:引用不是新定义一个变量,而是给已经存在的一个变量起一个别名。编译器不会为引用重新开辟内存空间来存储引用变量,但是引用变量和它所引用的实体共用同一段内存空间,所以操作引用变量相当于间接操作实体值。

引用的特性:(1)引用变量类型必须和引用实体类型一样。否则会导致最终引用变量所引用的实体不是我们所期望的,它所引用的其实是某一个中间变量。这个中间变量我们不知道他的名字和地址。例如:

从图中我们可以看到最终引用变量ra和a地址不一样,所以其实ra引用的是一个中间变量,因为这个中间变量我们不知道他的名字和地址,所以认为它是const。但是如果不给ra加上const修饰就会编译出错,因为编译器认为中间变量就是不可变的。

(2)引用在定义时必须要初始化。(函数参数不需要初始化,因为我们在调用函数时实参已经传给引用变量);

(3)一个引用变量只能引用一个实体;一个实体可以让多个引用变量引用;

常引用

    const int a = 10;
	//int& ra = a;  //错误,因为a是常量,不可修改
	const int& ra = a;

	//int& b = 10;  //错误,b是常量
	const int& b = 10;

引用使用场景

(1)做函数参数

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

int main(){
	int a = 10;
	int b = 20;
	Swap(a,b);
	return 0;
}

(2)作为返回值

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main(){
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << ret << endl;
	return 0;
}

在上面程序中,最终打印结果是7,但是执行完打印就成为随机值。为什么会产生随机值,其实这与函数栈帧有关,首先第一次调用函数返回值3,但是返回参数c是局部变量,所以函数调用结束,函数释放(函数释放只是这一块内存不可使用,只有下一次调用函数才能覆盖清理使用)。紧接着第二次调用函数,因为两个函数完全一样,所以两次调用函数地址空间基本一样,所以在原来被释放的ret空间上继续执行两数相加,最终ret值为7,但是函数调用结束同样ret地址已经相当于不存在。最后cout打印ret,因为cout相当于也是一个函数,所以先给函数传参ret = 7,然后cout函数地址空间会完全覆盖ret原来空间,所以ret最后变为随机值。

结论:函数返回时,栈上的空间已经还给系统,因此我们不可以把栈上的空间作为函数引用类型返回。如果返回值是引用类型,那么返回值的生命周期要比函数长(即比函数生命周期长)。具体做法:(1)可以把返回值设为static静态局部变量(2)全局变量(3)怎么一个引用参数,把最终结果带出。

传值、传引用和传址效率比较

传值:传值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者返回函数中变量本身,而是传递实参和返回值变量的一份临时拷贝,这就使得传值效率比较低,尤其是参数或者返回值类型比较大,效率更低。

传指针和传引用性能比较函数:(1)指针和引用做为函数参数情况

#include <time.h>
struct A
{
	int a[10000];
};

void TestFunc1(A a)
{}

void TestFunc2(A& a)
{}

void TestRefAndValue()
{
	A a;

	// 以指针作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();

	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(int*)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(int&)-time:" << end2 - begin2 << endl;
}

// 运行多次,检测指针和引用在传参方面的效率区别
int main()
{
	for (int i = 0; i < 10; ++i)
	{
		TestRefAndValue();
	}

	return 0;
}

(2)指针和引用作为函数返回值

#include <time.h>
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;
}

// 测试运行10次,指针和引用作为返回值效率方面的区别
int main()
{
	for (int i = 0; i < 10; ++i)
		TestReturnByRefOrValue();

	return 0;
}

通过上述代码对比,我们可以发现指针和引用作为参数或者返回值效率几乎相同;

(1)在语法概念上:引用就是一个变量的别名,没有独立的空间,和引用的实体共用同一块空间;

(2)在底层实现上:引用其实是有空间的,而且依靠指针实现的。

引用指针
int& ra = a;int *const pa = &ra;
const int& ra = aconst int *const pa = &ra;


指针和引用的区别:


(1)引用在定义时必须要初始化,指针没有要求;

(2)对于sizeof,引用大小是它所引用实体类型的大小,而指针如果在32平台指针大小是4个字节,64位平台是8个字节。

(3)存在空指针,不存在空引用

(4)引用是能对应一个实体,而指针可以指向任意一个地址

(5)存在多级指针,不存在多级引用

(6)引用加一就是实体加1,指针加一表示向后移动一个指针类型大小的单位

(7)访问实体的方式不同,指针需要解引用,引用由编译器处理

(8)访问较指针安全(指针容易发生对空指针解引用),而且指针容易造成野指针(最常见的野指针出现情况就是free之后,我们接着对指针使用,而且野指针不能简单的使用NULL判断,所以我们应该在free之后就把指针置为空)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_41318405

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值