C++(缺省参数和引用)

1.缺省参数

1.1 前言

在C语言中,函数的形参与实参的个数必须保持一致,同时对应的形参与实参的数据类型也应该保持一致。而在C++中我们允许在调用函数的时候不输入实参(即C++允许缺省参数)。那什么是缺省参数呢?

1.2 缺省参数的概念

我们说缺省参数是声明或者定义函数的时候为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。比如

void fun(int a = 0)
{
	cout<<a<<endl;
}
int main()
{
	Func();   // 如果我们没有传参,使用函数参数的默认值,所以此时形参a=0
	Func(10);  // 如果我们进行传参,那么函数就使用指定的实参,所以此时形参a=10
	return 0;
}

运行结果
在这里插入图片描述

1.3 缺省参数的分类

  1. 全缺省参数

    void fun(int a = 0,int b = 0,int c = 1)
    {
        cout<<"a = "<<a<<endl;
        cout<<"b = "<<b<<endl;
        cout<<"c = "<<c<<endl;
    }
    
  2. 半缺省参数

void fun(int a, int b = 10, int c = 20)
{
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;
}

1.4 注意

  • 半缺省参数必须从右往左依次给出,不能间隔给出
  • 缺省参数不能在声明和定义中同时给出
  • 缺省值必须是常量或者全局变量
  • C语言不支持缺省参数
  • 就像我们上面给出的半缺省参数的例子一样,它们必须从右往左依次给出,否则会出现如下报错

    void fun(int a = 0, int b, int c = 10)
    {
        cout<<"a = "<<a<<endl;
        cout<<"b = "<<b<<endl;
        cout<<"c = "<<c<<endl;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QOuU2nmx-1665716916786)(C:\Users\小白同志\AppData\Roaming\Typora\typora-user-images\image-20221012193219701.png)]

  • 缺省参数不能在函数声明和定义中同时出现。这个是比较常见的错误(因为我们在写比较大的项目的时候习惯了将函数的声明与定义进行分离),这也是不对的,比如:

    如果我们在头文件对fun函数的声明如下

    void fun(int a = 10)
    

    但是如果我们在.cpp文件中对fun函数进行以下的定义

    void fun(int a = 20)
    {
        cout<<"a = "<<a<<endl;
    }
    

    我们看到声明和定义对a提供的值不同,那么在编译的过程中编译器就无法确定a到底应该使用哪个缺省值。所以在我们后续对类进行初始化时(写析构函数时),我们需要在头文件中完成对析构函数的定义。


2.引用

2.1 前言

在C语言中,我们学习了指针以及指针的使用。许多同学发现指针并不是很好用(比如使用指针返回函数值,或者使用数组指针等)。那为了减少或者避免指针的使用,C++中加入了引用。

2.2 引用的概念

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

比如:你的兄弟或者比较亲近的朋友在叫你时可能会直接叫你的外号或者小名,而你的外号或者小名就可以认为是一种引用,它所引用的变量就是你这个人。

2.3 如何使用引用

int main()
{
    int a = 0;
    int& ra = a;
    cout<<a<<endl;
    cout<<ra<<endl;
    printf("%p\n", &a);
    printf("%p\n", &ra);
    return 0;
    //我们看到一般定义引用是 数据类型& 引用的变量名 = 被引用的对象(引用实体);
}

在这里插入图片描述

通过打印a与ra的值和地址我们更加可以看到,引用和被引用的变量共用同一块内存空间(这与指针是不同的)

2.4 注意

  • 引用在定义时必须初始化
  • 一个变量可以有多个引用
  • 引用一旦引用一个实体,那就不能再次引用其他实体。

我们看到引用和const有着相同的地方

int main()
{
    int a = 0;
    int& ra = a;
    const int _a = 10;
    return 0;
}
//我们看到引用和const+类型+变量一样,必须在定义的时候必须进行初始化。
int main()
{
    int a = 0;
    int b = 10;
    int& ra = a;
    cout<<ra<<endl;
    ra = b;//注意这行代码并不是表示将ra的引用实体改成b,而是将b的值赋给ra的引用实体(a)中
    cout<<ra<<endl;
    cout<<a<<endl;
    return 0;
}

运行结果如下

在这里插入图片描述

2.5 常引用

int main()
{
    const int a = 10;
  	int& ra = a;  // (1)
  	const int& ra = a;
  	int& b = 10; // (2)
  	const int& b = 10;
  	double d = 12.34;
  	int& rd = d; // (3)
  	const int& rd = d;
    return 0;
}

我们看到在编译的时候(1)会发成报错。而其实在我们讲常量a赋给引用ra时,会将权限变大。常量a并不允许改变其值(比如不允许a=0;这样的语句存在),而ra并没有const修饰,所以ra的引用实体应该是可以被改变值的(比如允许ra=10;),这样会将a的权限放大!同理b也是一样的情况。

而通过(3)的报错,我们可以看到引用和引用实体必须保证一致

2.6 使用的场景

2.6.1 引用做参数

在我们C语言写交换函数的时候,是这样写的:

void Swap(int* left, int* right)
{
    int tmp = *left;
    *left = *right;
    *right = tmp;
}

可以看到我们使用指针变量来解决两个数交换的问题,而在C++中,我们可以将引用做成参数

void Swap(int& left, int& right)
{
    int tmp = left;
    left = right;
    right = tmp;
    //在一定程度上可以提高了代码的可读性
}
2.6.2 引用做返回值
int count()
{
    static int n = 0;
    n++;
    return n;
}
int main()
{
    int ret = count();
    return 0;
}

在进行值传递时,电脑并不会直接将n的值传到ret中,而是在销毁函数栈帧时会将n的值赋给一个临时变量,比如

在这里插入图片描述

虽然这里只是拷贝了一个int类型的变量,但是在后续我们要传回所占内存比较大的变量时(比如定义的struct),无疑会降低传递效率。而如果我们使用引用作为返回值时

int& count()
{
    static int n=0;
    n++;
    return n;
}
itn main()
{
    int ret = count();
    return 0;
}

由于传回的是n的引用,所以在传递时并不会创造一个临时变量。而是直接将n带回(加快了传递效率)。我们可以通过下面的代码来观察两种方法的时间差。

#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;
}
int main()
{
    TestReturnByRefOrValue();
    return 0;
}
//通过运行的结果我们可以看到值传递和引用传递所用时间还是有很大的差距的

在这里插入图片描述

但是由于函数在被调用结束时会被销毁,所以如果我们运行下列代码时,会出现意想不到的效果

int& count()
{
    int n = 0;
    n++;
    return n;
}
int main()
{
    int ret = count();
    cout<<ret<<endl
    return 0;
}

我们看到,最终的运行结果将会是一个随机值。因为函数被销毁(即函数所占用的内存被系统收回并清理),连带的所创建的变量n也一同销毁,所以最终ret会收获一个随机值。而在最开始的那个例子中,由于n被定义为静态局部变量(静态局部变量会被存在静态区),所以在函数销毁时,n并不会被销毁(因为函数在栈区被销毁,而n在静态区)。所以这也告诉我们使用引用作为返回值时要注意返回的引用实体是否会被销毁!

2.7 引用与指针的区别

  • 在语法层面,引用是一个别名,没有独立的空间,而指针在创建时会占用内存(一般是4/8字节)
  • 引用在定义时必须初始化,指针并没有做要求(只是我们为了防止野指针的存在,会在定义指针变量时赋其为NULL)
  • 存在空指针(NULL),但是不存在空引用
  • 存在多级指针,但是不存在多级引用
  • sizeof(指针)显示的是指针变量所占字节个数(32位是4字节,64位是8字节)
  • 引用在初始化引用一个变量后,就不能再引用其他变量了,而指针可以随意的指向任何一个实体
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值