C++入门知识详解(2)

奋斗吧
奋斗吧
擅长邻域:未填写

标签: C++入门知识详解(2) 博客 51CTO博客

2023-07-26 18:24:24 160浏览

C++入门知识详解(2),C++入门知识详解(2),包含对函数重载和引用的认识和详解。


一、函数重载

1.什么是函数重载

重载的字面理解是重新载入的意思,也就是重新赋予某个事物新的含义,也就是所谓的一词多义。

而同名函数的形参个数或者形参类型或者形参顺序不同,那么就叫做函数重载。其中对函数的返回值并没有要求,也就是说函数返回值相同与不同不影响是否构成函数重载。

其实,函数重载就是对于函数的一词多义,两个或者多个函数的函数名相同,但是函数的功能却不同。

2.函数重载的几种情况

形参个数不同,构成重载。

同名函数,一个有参数,一个没有,构成函数重载;当把参数设置为缺省参数时,虽然依旧构成函数重载,但在无参调用函数时,会有歧义,导致编译错误。如下图

C++入门知识详解(2)_C++基础详解

C++入门知识详解(2)_引用_02

形参类型不同,构成重载。如下图

C++入门知识详解(2)_引用_03

形参顺序不同,构成重载。如下图

C++入门知识详解(2)_引用_04

返回值对函数重载无影响

函数名相同,函数参数顺序不同,构成重载,返回类型不同,依然构成重载。如下图

C++入门知识详解(2)_C++基础详解_05

3.C++为什么支持函数重载

3.1编译和链接

一个程序要想运行起来,必须经过编译和链接,每一个过程都有对应的功能。这里我们简单了解即可。

C++入门知识详解(2)_引用_06

通过编译过程,项目中的每个源文件都会在编译器的作用下生成对应的目标文件。也就是说在编译阶段,每个源文件之间是互不干扰的。而在链接阶段,会在链接器和生成的多个目标文件的作用下,生成一个可执行程序,也就是说,链接过程使得原本没有交集的源文件产生了交集。所谓的交集可以理解为,源文件之间彼此是互通共享的。如下图

C++入门知识详解(2)_函数重载_07

3.2链接的重要性

一个项目可能包含多个源文件和头文件,如下图

该项目包含两个源文件,一个头文件,其中重载函数的声明在头文件func.h当中,定义在源文件func.cpp中,源文件func.cpp和test.cpp都包含了头文件func.h。

C++入门知识详解(2)_引用_08

C++入门知识详解(2)_C++基础详解_09

C++入门知识详解(2)_引用_10

在上述项目的源文件test.cpp中,调用了在func.cpp中定义的函数,而test.cpp本身只包含了函数的声明(头文件)。那么函数的声明和定义在两个不同的源文件当中,该程序可以跑起来吗?答案是可以的,上面提到过,在链接的作用下,各个源文件之间是互通共享的,因此虽然调用函数的声明和定义不在一个源文件,但在链接的作用下,会找到是哪个源文件当中存在对应函数的定义。所以可以运行。如下图

C++入门知识详解(2)_函数重载_11

当我们把func.cpp中函数的定义注释后,因为没有函数定义,所以链接过程会因为找不到函数定义而无法运行。如下图

C++入门知识详解(2)_函数重载_12

C++入门知识详解(2)_函数重载_13

3.3函数名修饰规则

由前文所说,我们知道了在链接的作用下,可以找到不同源文件中的函数定义,以此成功通过调用函数,成功运行程序。

那么如果存在函数重载,出现同名函数的情况下,如何找到我们想要的重载函数呢?

这里需要了解函数名修饰规则。可以将其理解为,经过编译过程后,会将原本的函数名根据其独特的函数参数做出一定修改,而构成函数重载的必要条件就是函数参数不同(参数顺序,参数个数,参数类型),因为函数的参数不同,所以在对函数名进行修改时也会不同,以此可以区分两个同名函数,也就可以支持函数重载。

不同的编译器,函数名修饰规则不同。linux下的修饰规则较为简单明了,我们来看一下。

在linux下创建并使用vim写好func.h,func.cpp,test.cpp。如下图

C++入门知识详解(2)_函数重载_14

C++入门知识详解(2)_引用_15

使用makefile写入依赖关系。如下图

g++是C++的编译器,$@表示:左边的内容, $^表示:右边的内容

g++ -o $@ $^ 表示的意思就是,用g++编译器由func.cpp和test.cpp编译生成可执行程序test.exe。

虽然makefile中并没有func.h,但make指令执行时实际上会在当前路径下自己寻找,所以效果就相当于由func.h,func.cpp,test.cpp生成可执行程序test.exe

C++入门知识详解(2)_函数重载_16

在makefile写下依赖关系后,每次使用这条指令只需要输入make即可自动执行该语句,并且生成可执行程序 test.exe,使用./test.exe也可以运行该程序。如下图

C++入门知识详解(2)_C++基础详解_17

当准备工作完成后,就可以使用指令objdump -S test.exe (该指令会尽可能将源代码生成汇编代码)来查看是否存在函数名修饰的情况发生(根据参数对函数名做出修改)。如下图

C++入门知识详解(2)_C++基础详解_18

对于汇编代码我们无需理会,我们只要看到<_Z4funcii> 和<_Z4funcdd>即可

我们不难看出他们的共同点,都有_Z4和func,其中func是函数名,而_Z是函数修饰规则的一个默认前缀,数字4表示的是原本函数的长度

再看ii和dd,不难联想到,这其实就是函数参数类型的首字母。

也就是说,在c++编译器下,确实是存在函数名修饰规则的,这也就使得,即使是重载函数也会因为参数不同,导致在函数名修饰规则的作用下变得有所不同,也因此C++支持函数重载

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

C语言没有函数名修饰规则来修饰同名函数,所以不支持函数重载

同样的方法,再来查看,可以发现,C语言编译器下,函数名依旧是其本身,没有任何的修饰和修改,所以C语言无法区分同名函数,也因此不支持函数重载

C++入门知识详解(2)_函数重载_19

C++入门知识详解(2)_函数重载_20

C++入门知识详解(2)_C++基础详解_21


二、引用

1.什么是引用

1.1引用的概念

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

1.2引用符号与语法

引用符号为&,在C语言中的取地址符号,在C++中,也是引用符号

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

1.3引用的使用要求

前言:引用类型必须和引用实体是同种类型的。如果不同,则会报错。如下图

C++入门知识详解(2)_引用_22

引用在定义时必须初始化。如下图

引用不初始化会报错,初始化则正常

C++入门知识详解(2)_C++基础详解_23

C++入门知识详解(2)_C++基础详解_24

一个变量可以有多个引用,但是一个引用只能引用一个实体。也就是说一个变量可以有多个别名,而一个别名只能对应一个实体。如下图

C++入门知识详解(2)_函数重载_25

C++入门知识详解(2)_C++基础详解_26

2.引用需要注意的地方

2.1关于引用

引用就相当于萧炎化名岩枭一样,无论是哪个名字,他们都是同一个人。

如下图,引用实体和几个引用变量的地址都是相同的,对引用实体做出改变时,引用变量也会改变

C++入门知识详解(2)_C++基础详解_27

C++入门知识详解(2)_C++基础详解_28

2.2引用的一些问题

1.引用还是赋值

前情提要:引用只能认定一个引用实体,不能更改成为另一个变量的别名。

在C++中,语句c=b,是将b的值赋值给c

C++入门知识详解(2)_引用_29

同时,牵一发而动全身,别名和引用实体实际上是同一个身份,因此a和c都会被更改。如下图

C++入门知识详解(2)_C++基础详解_30

2.不同类型能否被引用

在引用的使用要求中提过,引用实体的类型和引用变量的类型必须相同才能使用引用。

引用实体为double类型,引用变量是int类型时,确实不能引用,如下图

C++入门知识详解(2)_函数重载_31

在发生类型转换的时候,拷贝的是右值的临时变量。如下图

b拷贝给c,类型不同,使用发生类型转换,b会产生一个临时变量,而临时变量具有常性,常性可以理解为是被const修饰

C++入门知识详解(2)_引用_32

类型不同不能引用只是一部分原因,根本原因是引用时,权限不能放大

C++入门知识详解(2)_C++基础详解_33

上图中,引用实体和引用变量的类型不同时,并不能引用,当在引用变量的类型前添加const时,可以成功通过。这是因为,c引用b时,因为类型不同,会发生隐式类型转换,而隐式类型转换会产生一个具有常性的b的临时变量,而引用过程权限不能放大,只能平移或者缩小,因此添加const即可通过

2.3常引用

前情提要:在引用过程中,权限不能放大,只能平移或者缩小权限放大,如下图

C++入门知识详解(2)_引用_34

a有const修饰,只有读权限,拿b做引用时,b的类型确是int ,读写权限都有,因此不可以引用

权限平移,如下图

C++入门知识详解(2)_C++基础详解_35

a,b的类型完全相同,都是const int,权限也都是可读不可写,可以引用

权限缩小,如下图

C++入门知识详解(2)_函数重载_36

a的权限是可读可写,但是引用b的权限是可读不可写,权限缩小,这是可以的

此时仍然可以对引用实体a进行写操作,但是不可以对b进行写操作。如下图

C++入门知识详解(2)_引用_37

可以理解为别名的身份没有本体混得好(权限缩小),所以处于别名身份时,会受到约束,而本体不会受到约束

2.4别名的别名

给某个引用实体起别名,再给这个别名起别名,新起的别名也是引用实体的别名,三者本质相同。如下图

C++入门知识详解(2)_引用_38

3.引用的用途

3.1引用做参数

3.1.1引用做参数的好处
1.输出型参数(形参的改变可以改变实参)

在C语言中,参数传递分为值传递和地址传递,当我们想要通过改变形参进而改变实参时,必须采用地址传递的方法。

而在C++中,可以使用引用做参数,此时引用不需要初始化。因为引用只是起别名,所以形参的改变也会改变实参。如下图

C++入门知识详解(2)_C++基础详解_39

其次,在不带头的单链表中对链表进行增删的时候,函数参数采用的都是二级指针,现在有了引用,可以直接使用引用做参数,来改变实参。

关于typedef struct SingleLinkedListNode{}*node;

node就是一个结构体指针,它的类型为struct SingleLinkedListNode*

C++入门知识详解(2)_引用_40

C++入门知识详解(2)_C++基础详解_41

我们只将头插函数的形参改为引用,查看结果即可。同时,头插函数也需要做出相应修改。因为链表头指针的类型为struct SingleLinkedListNode*,所以引用的类型也是struct SingleLinkedListNode*,也就是Node。如下图

C++入门知识详解(2)_函数重载_42

C++入门知识详解(2)_引用_43

函数调用更改为引用做形参的头插函数,和二级指针的效果一样。如下图

C++入门知识详解(2)_C++基础详解_44

2.提高效率(大对象)

使用引用做形参,一定程度可以提高效率。如下图

C++入门知识详解(2)_C++基础详解_45

3.2引用做返回值

3.2.1值作为返回值

值作为返回值.如下图

C++入门知识详解(2)_引用_46

可以看到,我们在函数func内创建了变量a,并且加上了static修饰,使得局部变量改为在静态区存储,因此变量a在func函数调用结束后也不会销毁。

实际上,值作为返回值,无论变量a是出了函数作用域就会被销毁的局部变量,还是不会被销毁的静态区变量,最后返回的都并不是a本身,而是a的一个临时拷贝的值,最后将这个拷贝的值赋值给ret。

3.2.2引用作为返回值的问题

引用作为返回值时,返回的就会是变量本身,而不是变量的临时拷贝。如如下图

C++入门知识详解(2)_C++基础详解_47

此时,变量a就必须加上static使其函数调用结束后不会被销毁。因为func函数返回的是变量a的别名,如果a是局部变量,函数调用结束后,func的函数栈帧也会被销毁,a也不复存在,也不会有引用一说。

但实际上,函数调用后函数栈帧空间更准确来说应该是归还。因此,如果函数调用结束后,如果没有调用别的函数,那么栈帧原本的栈帧空间不会被覆盖,也就是不会被清理,那么会正确返回变量的别名,如果栈帧已经被清理(覆盖),那么返回的将是一个随机值。

也因此,对局部变量进行引用返回时,是一件不靠谱且危险的事情,要慎用引用返回。如下图

C++入门知识详解(2)_引用_48

3.2.3用引用接收引用

如下图

C++入门知识详解(2)_C++基础详解_49

函数func的返回值是引用,接收引用的变量ret也是一个引用,此时是什么情况呢?其实就是上文提到的引用的引用

函数func返回的是变量a的引用,而ret是返回的引用的引用,也就是说,ret也是变量a的引用,ret和a共用同一空间。如下图

C++入门知识详解(2)_C++基础详解_50

3.2.4引用做返回值的好处

和引用做参数一样,引用做返回值时,也无需进行拷贝,尤其是返回对象较大时,因此也可以提升效率

3.2.5关于引用的小结

在任何情况下,都可以拿引用做参数。但是引用的使用需要慎重。

这是因为,引用做返回值时,如果引用的是局部变量,返回是可能会返回随机值,使用存在风险。

引用做返回值时,全局变量,静态变量,或者malloc出来的都可以引用返回,因为他们不会被立即销毁。

好博客就要一起分享哦!分享海报

此处可发布评论

评论(0展开评论

暂无评论,快来写一下吧

展开评论

您可能感兴趣的博客

客服QQ 1913284695