【C++入门系列】——缺省参数,函数重载,引用和内联函数

news2025/1/15 6:32:54

作者主页

📚lovewold少个r博客主页

   ⚠️本文重点c++ 缺省参数 引用与指针

😄每日一言青春就像一只容器,装满了不安躁动青涩与偶尔的疯狂。

目录

缺省参数

缺省函数的定义

缺省参数分类

函数重载

        为什么C语言不支持函数重载?

引用

引用的基本概念

常引用

引用使用场景

引用作为函数参数 

引用作为函数的返回值

传值、传引用效率比较

引用和指针

内联函数

内敛函数的概念

内联函数的优缺点

内联函数的限制

内联函数与宏

总结


前言

        本章章节,主要会从c++的特性入手,结合c语言的前面的缺陷对比讲解。从而了解和掌握缺省参数、函数重载、引用和内联函数的特点关键。

        如果我们C语言的基础比较好,相对于对C++的一些特性理解就会更加清晰,初期C++作为C语言的一个升级版本,我们其实入门掌握的就是升级了什么,多了什么,为什么要多?也是对祖师爷想法的剖析,有助于我们以后也能解决在编程问题中发现的缺陷。


缺省参数

      缺省参数,首先顾名思义既缺乏和省略的参数。相对于C语言的对参数的必须传递完整不同,C++能实现不传递声明的参数和传递部分参数。

缺省函数的定义

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

缺省参数分类

全缺省

        全缺省是声明或定义函数时为函数的全部的参数指定缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。如图所示,函数声明给abc都声明了缺省值,在函数调用过程中,缺省值的传递是可以0,1,2,3个按照声明中最多的参数个数达到上限,从左往右依次传递实参。

半缺省参数

        半缺省是声明或定义函数时为函数的部分的参数指定缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

        要注意的是函数的缺省参数必须从右往左声明,不能跳跃的和从左往右指定缺省值。这一点在传值调用是有必然联系的。因为传值调用必须规范,从左往右,对于函数参数没有指定的缺省值如果不传递值本身编译也无法通过。

注意:

  • 1. 半缺省参数必须从右往左依次来给出,不能间隔着给
  • 2. 缺省参数不能在函数声明和定义中同时出现
  • 3. 缺省值必须是常量或者全局变量
  • 4. C语言不支持(编译器不支持)

函数重载

        C++ 允许在同一范围内指定多个同名函数。 这些函数称为重载函数或重载。 利用重载函数,你可以根据参数的类型和数量为函数提供不同的语义。

        函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

        这就好比对于一个函数,我可以根据传递的参数类型的不同区分他是哪一个函数。既函数的参数声明作为了函数名修饰的一部分。因此C++可以根据参数类型和数量去提供不同的语义。对于编译器而言,语义就是上下文联系,函数名作为上文,参数作为下文,上下联系后就能确定是同名函数的哪一个。即根据多胞胎的细微区别即可区分谁是谁。  

参数类型不同

定义参数类型不同的在调用函数的时候对函数参数进行判断即可区分。

参数个数不同

参数顺序不同(本质和参数类型一样)

为什么C语言不支持函数重载?

        简单来讲是因为在链接阶段生成符号表的时候,C语言对函数取名的时候,只会拿函数名进行取名,如果有函数重载的话,没办法区分函数的不同。而C++在取名的时候,是将函数名和参数类型的首字符结合起来对函数的取名,这样就可以区分函数的不同。(细节过程后续会出一节C语言和C++的函数修饰命名规范)。

注意:对于重载函数,要保证是参数不同而非返回值不同。前者可在调用过程就对函数进行确定,但是后者不同,在确定是哪一个函数的过程中就会先产生冲突,即二义性。因此无论是重载函数的过程还是调用函数的过程,都要避免歧义和冲突。

引用

        “白龙马,蹄朝西,跟着唐三藏还有三徒弟~”等等,接下来是C++ 的引用部分。什么是引用呢?

引用的基本概念

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

        篇章提到白龙马,我们来看看以下人都是谁,孙悟空从起初石猴进行初始化,后续被取名,当官被取名弼马温,又称自己为齐天大圣,别人根据他的齐天大圣又称之为大圣。孙悟空又名悟空······可以看见,无论称呼为何,其本质上都是那一个石猴,虽然我们都以孙悟空称之。

        这和C++又有啥关系,关系来了。C++  中引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

        通过引用,我们给一个变量可以取一个别名,对别名的操作也会改变起本质。即给孙悟空别名叫弼马温,让他当官了,本质上是一个石猴在天庭当了个养马的官。

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

上述代码首先声明一个变量a,然后声明一个ra他是a的别名。

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

接触过C语言后接触引用,感觉和指针基本一样,例如:

对ra来讲他本身作为a的别名,对于pal来讲他是a的地址,对ra的直接访问有种(*解引用运算符隐式理解)的感觉。

        但是实际上,除了用法的不同以外,还有其他差别,比如引用在声明时期必须将其初始化,不能和指针一样,先声明再赋值。对于引用来讲,创立初始化于一个变量关联便开始服务与他。对于一个变量,可以有多个引用。对于引用,只能有唯一一个实体。

常引用

当以const声明定义一个常变量的时候,引用只能同级别引用,也得加上const,否则可以试想一下,对于一个常量10,通过引用可以对10操作改为2,这不是荒唐么,10=2?很显然不能这样子操作。

引用使用场景

引用作为函数参数 

void Swap(int x, int y)
{
	int tmp = x;
	x = y; 
	y = tmp;
}
void Swap_r(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int a = 100;
	int b = 1;
	Swap(a, b);
	cout <<"传值调用结果--" << " a:" << a << " b:" << b << endl;
	Swap_r(a, b);
	cout << "传引用调用结果--" << " a:" << a << " b:" << b << endl;

	return 0;
}

        看上述代码运行,我们声明并创建了一个交换函数用于交换a,b变量的值。在C语言中,我们知道要改变变量本事,得传递指针。因为,对于第一个函数,传值调用的过程中,传递的是a,b的值得临时拷贝,因此函数内部的过程不影响其变量本身。而引用作为函数参数,相当于在调用过程中,对引用和传递的参数进行了初始化,此时函数内部的x,y变成了a,b的别名。对xy的操作会等同于对ab的操作。

当然我们也可以通过传递指针变量对ab进行改变,这在形式上和引用有所区别,即函数内部要对此进行解引用。当然,如果传递多级指针变量,这样的操作就更加显得繁琐。而引用,能更有助于可读性的提升。

引用作为函数的返回值

        在上述代码中,我们使用传递了两个变量执行加法操作,在函数内部我们定义了一个变量,并进行了引用还回。即我们对n取了别名,并返回了这个别名,在函数外部,我们使用引用接受了返回变量。此时ret为返回变量n的别名。

        在函数创建和调用的过程中,本质上是在栈上为函数开辟了空间,执行完函数后,栈空间会被回收,因此传递过程中,起初ret作为n的别名,能得到n的值,但是后续函数空间被回收,n被回收,ret的作为n的别名,n更改了ret也会更改。

        可以看见引用作为函数返回值在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效。程序状态就未知) 。这就和返回临时变量的指针一样,出了函数的作用域,指针对于空间的指向就会被回收,指向也就失去了意义。

        如果要将引用做为返回值应当是返回作为参数传递给函数的引用,即作用域在函数外部的变量。这个变量可以为静态变量,全局变量以及需要其存在作用域内部的变量。

注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回, 如果已经还给系统了,则必须使用传值返回。

传值、传引用效率比较

struct A { int a[1000]; };

void TestFunc1(A aa) {}

void TestFunc2(A& aa) {}

void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = GetCycleCount();
	for (size_t i = 0; i < 100000; ++i)
	{
		TestFunc1(a);

	}
	size_t end1 = GetCycleCount();

	// 以引用作为函数参数
	size_t begin2 = GetCycleCount();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2(a);
	size_t end2 = GetCycleCount();

	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
	TestRefAndValue();
	return 0;
}

#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()
{
	//TestRefAndValue();
	TestReturnByRefOrValue();
	return 0;
}

经过比较,我们发现现传值和引用在作为传参以及返回值类型上效率相差很大。因此,传引用在和传值相比效率更加高效。

引用和指针

引用在语法上不额外开辟内存空间,但是实际上和指针的实现过程是一样的。

  • 引用和指针的不同点:
  • 1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  • 2. 引用在定义时必须初始化,指针没有要求
  • 3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型 实体
  • 4. 没有NULL引用,但有NULL指针
  • 5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占 4个字节)
  • 6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  • 7. 有多级指针,但是没有多级引用
  • 8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  • 9. 引用比指针使用起来相对更安全

内联函数

内敛函数的概念

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

        内敛函数通俗来讲就是在程序编译过程中直接在调用地方展开,不同于普通调用过程中。内联函数和正常的编写方式其实大差不差,在前面加上关键字inline而已,为何能提升效率呢。

        其主要原因得深入程序内部,在我们执行程序的时候,即由一串机器语言指令组成,在运行程序的时候,操作系统将这些指令载入到计算机内存中,因此每一串指令都有自己的内存地址。计算机随后会逐本执行这些指令。有时会进行跳转,比如循环或者分支语句,常规的函数调用也使程序跳转到另一个地址,并在函数结束时候返回。来回的跳转并记录跳跃位置意味使用函数会产生额外的花销。

        内敛不同于常规编译,即使用函数定义去替换函数调用,在调用地方直接展开,这就意味着他不需要跳转,而是直接执行编译过程。

        相应的,如果内联函数作为简短且常使用函数,则可以优化跳转过程的时间消耗,如果本身执行其函数时间就很长,跳转消耗时间相对于函数运行时间只占很小一部分,这种展开就没有任何意义,因为只优化的跳转时间只占一小部分,而展开占用的空间却大幅度增加了。因此要有选择性的使用内联函数。

内联函数的优缺点

        引入内联函数的目的是为了解决程序中函数调用的效率问题,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个利用空间代价换时间的做法。

缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。

内联函数的限制

        有些函数即使声明为内联的也不一定会被编译器内联,这点很重要,比如递归函数就不会被正常内联。递归函数不应该声明成内联函数,因为递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数。

  • 在内联函数内不允许使用循环语句和开关语句;(因为本身内敛函数就是优化指令跳转过程的额外开销,使用得不偿失)
  • 内联函数的定义必须出现在内联函数第一次调用之前;(不能定义声明分离)
  • 正常内联函数不超过10行(具体看编译器),避免过多调用造成的过多代码空间占用。

内联函数与宏

#define max(a,b) ((a)+(b))
inline int Max(int x, int y)//内联函数
{
	return (x > y) ? x : y;
}

int main()
{

	cout << "Max (20,10): " << Max(20, 10) << endl;
	cout << "max(20,20): " << max(20, 20) << endl;
	return 0;
}

宏在定义的过程中主要是替换,而不是通过传递参数实现的。

优点:增强代码的复用性(例如改变宏定义参数更改整个程序宏变量)和提高性能。

缺点:不方便调试宏(因为预编译阶段进行了替换) 。代码可读性差,可维护性差,容易误用。没有类型安全的检查 。

在c++中,有如下方式能替代宏,且解决其缺陷:

1. 常量定义换用const enum

2. 短小函数定义换用内联函数


总结

本文简要总结了C++中的函数、引用和内联函数这些关键概念和特性,以及它们的用途和优势。

缺省参数

  • 缺省参数允许在函数定义中为一个或多个参数提供默认值,提高函数的灵活性和可读性。

函数重载

  • 函数重载是通过相同的函数名定义多个函数,但它们的参数列表必须不同。这使得可以根据不同参数选择正确的函数进行调用。

引用

  • 引用允许创建变量的别名,而不是副本,提高代码效率和可读性。
  • 常引用用于指示不能修改引用的方式,用于数据保护。
  • 引用可以用作函数参数,使函数能够修改传递的变量,还可以用作函数的返回值,实现多值返回。

传值、传引用效率比较

  • 传值需要复制整个对象,传引用只传递引用,对于大型对象,传引用通常更高效。

内联函数

  • 内联函数通过inline关键字定义,将函数的代码直接嵌入到调用处,减少函数调用的开销,提高程序执行效率。
  • 内联函数适用于小型函数,提高代码可读性。

    作者的实力有限,文章难免出现纰漏,有不对的地方欢迎指正!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1099300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单片机判断语句与位运算的坑

一.问题描述 在我判断Oled的某点的值是否为1时,用到了如下判断语句 if(oled[x][y/8] &1<<(y%8)但是,当我将其改为如下的判断语句,代码却跑出BUG了 if((oled[x][y/8]&1<<(y%8))1)二.原因分析 1.if语句理解错误 首选让我们看看下面的代码运行结果 #inc…

Day1力扣打卡

打卡记录 最长相邻不相等子序列 I&#xff08;脑筋急转弯&#xff09; 链接 思路&#xff1a;形如 11100110001 要达到最大&#xff0c;必须在重复数字选出一个&#xff0c;即在111中取一个1&#xff0c;在00中取一个0&#xff0c;以此类推最终便得到最长相邻不相等子序列。 c…

uCOSIII实时操作系统 七 中断与时钟管理

目录 中断管理&#xff1a; 中断处理过程&#xff1a; 临界区保护&#xff1a; 时间管理 OSTimeTick()函数 OSTimeDly()函数 OSTimeDlyHMSM()函数&#xff08;常用的&#xff09; OSTimeDlyResume()函数 中断管理&#xff1a; 中断处理过程&#xff1a; 中断是一个硬件…

Bugku sql注入 基于布尔的SQL盲注 经典题where information过滤

目录 绕过空格 /**/绕过 ()绕过 回车绕过 &#xff08;键按钮&#xff09;绕过 等号绕过 绕过&#xff0c;&#xff08;逗号&#xff09;使用substr 下面存在基本绕过方式 注释符绕过 /**/绕过 #绕过 /*注释内容*/绕过 //注释绕过 大小写绕过 绕过information过…

Spring中注入的使用

目录 一、什么是注入&#xff08;Injection&#xff09; 1.1 为什么要注入 二、注入的基本使用 三、Spring注入原理分析 一、什么是注入&#xff08;Injection&#xff09; 注入就是通过Spring的配置文件&#xff0c;为所创建对象的成员变量进行赋值 1.1 为什么要注入 书接上…

寻找替代Redmine项目管理工具的常见方案

RedMine是一个非常受欢迎的项目管理工具&#xff0c;但它并不是万能的。随着时间的推移&#xff0c;许多功能和特性可能会发生变化或被取消。因此&#xff0c;有许多其他工具可以成为RedMine的替代品。 以下是六种可能的选择&#xff1a; 1、Zoho Projects&#xff1a; Zoho P…

“高级小程序开发指南“

目录 引言小程序视图层小程序逻辑层及生命周期总结 引言 随着移动互联网的快速发展&#xff0c;小程序作为一种轻量级的应用形态&#xff0c;在用户使用体验和开发者便捷性方面受到了广泛关注。本篇博客将带你深入探索小程序的视图层和逻辑层&#xff0c;并介绍其生命周期。 …

高压互锁(HVIL)

文章目录 简介高压互锁的作用高压互锁原理高压互锁检测电路设计直流源PWM直流源和PWM比较 高压互锁常见故障MSD 简介 高压互锁(High voltage Inter-lock&#xff0c;简称HVIL)&#xff0c;又称高压互锁回路系统。 高压互锁是混合动力和全电动汽车的安全功能&#xff0c;利用低…

GEO生信数据挖掘(八)富集分析(GO 、KEGG、 GSEA 打包带走)

第六节&#xff0c;我们使用结核病基因数据&#xff0c;做了一个数据预处理的实操案例。例子中结核类型&#xff0c;包括结核&#xff0c;潜隐进展&#xff0c;对照和潜隐&#xff0c;四个类别。第七节延续上个数据&#xff0c;进行了差异分析。 本节对差异基因进行富集分析。 …

函数防抖(javaScript)

防抖说明 &#xff08;1&#xff09;防抖的目的&#xff1a; 当多次执行某一个动作的时候&#xff0c;限制函数调用的次数&#xff0c;节约资源。 &#xff08;2&#xff09;防抖的概念&#xff1a; 函数防抖&#xff08;debounce&#xff09;&#xff1a;就是指触发事件后&…

docker离线安装和使用

通过修改daemon配置文件/etc/docker/daemon.json来使用加速器sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": ["https://ullx9uta.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo syste…

Qt ModelView显示数据库数据

利用qt的model view来显示数据表userudps里的数据 用了一个label 两个combox和一个tableview&#xff0c;实现如下效果&#xff1a; 我这里用到是mysql数据库&#xff0c;一般配置mysql数据库就两种有驱动或者没驱动&#xff0c;有的话把mysql的bin目录的libmysql.dll复制到q…

ESP32-IPS彩屏ST7789-Arduino-简单驱动

目的&#xff1a; 使ESP32能够驱动点亮ST7789显示屏 前提条件&#xff1a; ESP32 ST7789 &#xff08;240 x240&#xff0c;IPS&#xff09; 杜邦线 Arduino 过程&#xff1a; 0x00--接线 0x01--驱动&#xff1a; 彩屏驱动库 针对不同的彩屏驱动芯片&#xff0c;常用的 Arduino…

Java中的数组

前言&#xff1a; 本篇博客将为大家介绍Java中的数组的相关知识。 目录 基本介绍 概念相关 数组的使用 数组是引用类型 应用场景 保存数据 作为方法的参数 作为方法的返回值 练习 数组转字符串 数组拷贝 求数组中元素的平均值 查找数组中的指定元素&#xff08;二…

传输线感性耦合和距离的关系

传输线感性耦合和距离的关系 传输线感性耦合是指两条或多条传输线之间由于磁场或电场的相互作用而产生的耦合现象。这种耦合现象对于传输线的信号质量和完整性有很大的影响。其中&#xff0c;传输线之间的距离是一个重要的影响因素。本文将从传输线感性耦合的基本概念入手&…

新年学新语言Go之一

一、前言 搜索相关知识后续内容等上班后再继续&#xff0c;新年新气象&#xff0c;从今天开始学习一下Go语言&#xff0c;第一次听说这门语言还是2016年的时候&#xff0c;然后2018年买了一本书 Go In Action&#xff0c;然后就没有然后了&#xff0c; 转眼这么多年过去了&am…

输入字符串,判断里面有多少个大写字母,多少小写字母,多少数字

public static void main(String[] args) {//输入字符串&#xff0c;判断里面有多少个大写字母&#xff0c;多少小写字母&#xff0c;多少数字countVary("fsdfsD4f4sf&#xffe5;#&#xffe5;%~&*&#xff01;sg9tssfffSFSFS");}public static void countVary(…

【网络】总览(待更新)

网络Ⅰ 零、概述0. 网络协议1. 网络协议分层OSI 七层模型TCP/IP 五层模型 2. 协议报头3. 通信过程 一、应用层1.1 &#x1f517;HTTP 协议1.2 &#x1f517;HTTPS 协议 二、传输层2.1 端口号2.2 netstat - - 查询网络状态2.3 pidof - - 查看服务器的进程 id2.4 &#x1f517;UD…

亚马逊云科技正式发布Amazon DataZone,一项新的数据管理服务

Amazon DataZone现已正式发布。作为一项新的数据管理服务&#xff0c;它能够在组织中对数据生产者和消费者之间产生的数据进行编目、发现、分析、共享和管理。 早在2022年的亚马逊云科技re:Invent上&#xff0c;就预告了Amazon DataZone产品的发布&#xff0c;并在2023年3月对其…

常见场景面试题(二)

typora-copy-images-to: imgs theme: cyanosis 敏感词库的设计&#xff0c;要求增删改查敏感词。敏感词文本匹配&#xff0c;敏感词一万个&#xff0c;文本长度在 20 - 1000 答&#xff1a;使用 trie 树来实现敏感词库的设计&#xff0c;可以利用字符串公共前缀来节约存储空间。…