柚子快报邀请码778899分享:开发语言 c++过渡知识2

http://yzkb.51969.com/

嗨咯,大家好,今天阿鑫给大家带来的是c++的过渡知识2,本篇属于c++过渡知识的收尾哦,难度不是很大,下面请大家阅读本篇博客吧!

c++过渡知识2

函数重载

函数重载的概念c++如何支持函数重载之名字修饰

初识引用

引用的概念引用特性细谈引用和指针权限的放大与缩小

1.函数重载的概念

在c语言中不支持函数重载而在c++中支持函数重载 函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。 下面我们通过几个例子来认识一下函数重载的概念

#include

using namespace std;

void Swap(int* pa, int* pb)

{

cout << "void swap(int* pa, int* pb)" << endl;

}

void Swap(double* pa,double* pb)

{

cout << "void swap(double* pa,double* pb)" << endl;

}

int main()

{

int a = 1, b = 2;

double c = 0.1, d = 1.1;

Swap(&a, &b);

Swap(&c, &d);

return 0;

}

我们创建了两个同名的函数Swap,这两个函数同时在全局域中,但是由于这两个函数形参部分的参数类型不同,所以构成了重载函数,并且在调用这两个函数时,编译器会自动进行匹配

下面我们结合上一节提到的命名空间域的内容创建三个函数

#include

using namespace std;

namespace ZJ1

{

void Swap(int* pa, int* pb)

{

cout << "void swap(int* pa, int* pb)" << endl;

}

}

namespace ZJ2

{

void Swap(int* px, int* py)

{

cout << "void swap(int* pa, int* pb)" << endl;

}

}

void Swap(double* pa,double* pb)

{

cout << "void swap(double* pa,double* pb)" << endl;

}

using namespace ZJ1;

using namespace ZJ2;

int main()

{

int a = 1, b = 2;

double c = 0.1, d = 1.1;

Swap(&a, &b);

Swap(&c, &d);

return 0;

}

当我们在创建了这三个函数时进行调用会报错,下面请大家想想这是由于这两个函数没有构成重载关系的缘故吗?

实则不然,上节我们提到过,不同作用域中的函数和变量是可以同名的,在这里我们这两个函数分别封装在ZJ1和ZJ2两个域中,即使展开了也是处于不同的作用域(注意:展开并不是将函数放在全局处,而是在不指定的情况下,默认编译器也可以去命名空间域中搜索这个函数),并不是因为没有构成重载函数的原因而报错 实际原因是这两个函数在调用过程中会发生歧义,导致编译器既可以调用第一个函数也可以调用第二个函数,所以我们在日常写代码的过程中,要避免产生歧义的这种情况

下面我们介绍一下顺序不同如何形成函数重载

#include

using namespace std;

void f(int a, char b)

{

cout << "f(int a,char b)" << endl;

}

void f(char b,int a)

{

cout << "f(char b,int a)" << endl;

}

int main()

{

int a = 1;

char b = 'abc';

f(a, b);

f(b, a);

return 0;

}

通过上面的代码我们可以看出当对应的参数类型相同时,我们可以通过改变形参的顺序来使之成为函数重载

最后,我们来结合一下缺省函数来构造出一个重构函数

#include

using namespace std;

void f(int a = 4)

{

cout << "f(int a)" << endl;

}

void f()

{

cout << "f()" << endl;

}

int main()

{

f();

return 0;

}

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c09d

同学们请思考一下上面为什么会调用出错,是因为没有构成函数重载吗? 其实还是因为指向不明确,在我们调用函数的时候,编译器发现第一个函数和第二个函数都能够调用,所以会发生调用错误

最后提醒同学们注意的是:返回值不同不能构成重载

2. c++如何支持函数重载之名字修饰

好的在我们介绍完函数重载后,大家有没有过疑问,为什么c语言不支持函数重载,而c++支持函数重载,这是因为我们的c++在编译与链接过程对函数名进行了函数名的修饰,如果有不了解编译与链接过程的同学,可自行阅读博主之前的编译与链接的博客

在想要弄清楚函数重载的秘密,我们先来创建三个文件,以便我们后续的讲解

在之前,我们就已经分析了编译与链接各个过程所完成的事情,而我们首先需要注意的是在链接过程之前的过程各个文件都是独自完成的,这点大家先要搞清楚

我们在stack.cpp定义一个StackInit函数,并且在Test.cpp文件中进行函数的调用,将代码进行反汇编可以得到如图所示的汇编语言 我们可以很明显的看出在call函数的地址之后,才会进行后续的函数调用的指令 (因为:call本质是跳转,函数有一堆要执行的指令,函数地址是第一句指令) 这有点类似数组,有了第一个就能访问剩下的元素

下面我们来思考一下,函数的地址是什么时候获取的,又是如何获取的呢? 我们需要注意的是,在链接过程之前,Test.cpp只有函数声明,可以通过编译,因为语法检查是匹配,但是没有函数的地址 只有有了有函数的定义,才能生成函数一堆汇编指令,第一句指定的地址,才是函数的地址,Stack.cpp->Stack.o中才有函数的定义 聪明的同学已经可以猜到了,因为在链接过程中之前,我们各个文件都是单向的,只有在链接过程时,我们才会进行文件的合并,而由我们之前的知识可以知道,函数的地址会存放在stack.o中的符号表里,在我们链接时,Test.o会拿着StackInit的函数名到Stack.o的符号表中去寻找

在类似的符号表中,存有每个函数名对应的地址,在我们文件链接完成后,就能拿到函数的地址,从而进行后续的函数调用的一系列操作

此时我们的小伙伴们,是不是就知道这个错误时如何来的了?没错,就是因为我们缺少了函数的定义,事实也确实如此,我在vs中将函数的定义注释掉,就会得到这样的错误信息

那我们现在给出总结,以及为什么c++能够支持我们的函数重载 链接时: 1、直接用函数名字去查找,是否支持重载,不支持。C语言 2、直接用修饰后的函数名字去查找,就可以支持重载。C++

我们也可以很清晰的看出,括号里那段内容不一样,就是因为我们的编译器将我们的函数名字进行了修改,从而实现了我们的函数重载

3.引用的概念

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

我们进行调试可以得出,abcd公用一块空间,我们对任意一个变量进行操作,其他的变量都会随之而改变 下面我们来简单介绍一下引用的好处和用处

typedef int SLTDataType;

typedef struct SListNode

{

int val;

struct SListNode* next;

}SLTNode;

void SLTPushBack(SLTNode** pphead, SLTDataType x);

void SLTPushBack(SLTNode** pphead, SLTDataType x)

{

assert(pphead);

SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));

newnode->val = x;

newnode->next = NULL;

if (*pphead == NULL)

{

*pphead = newnode;

return;

}

SLTNode* ptail = *pphead;

while (ptail->next)

{

ptail = ptail->next;

}

ptail->next = newnode;

}

我们在没有接触引用之前,创建一个链表并进行尾插,此时需要传二级指针,这会导致不少同学犯错,从而产生bug,而当我们学习了引用,此时我们就可以将节点的别名传过去,此时可以直接对节点进行操作,不需要再传二级指针,类似

void SLTPushBack(SLTNode*& pphead, SLTDataType x);

此时我也给大家解决一个问题,就是有不少的考研书上会这样写

typedef struct SListNode

{

int val;

struct SListNode* next;

}SLTNode,*PNode;

void SLTPushBack(PNode& pphead, SLTDataType x);

这其实是他们进行了两次重命名,第二次是将struct SListNode*重命名为PNode

4.引用的特性

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

5. 细谈引用和指针权限的放大与缩小

接下来我们来谈谈引用和指针的权限

int main()

{

const int m = 0;

int& n = m;

}

请问同学们,上面的代码能够运行成功吗? 哈哈,很明显不行,因为涉及到了权限的放大,m是只读的,而n变成别名以后可读可写 此时我们有两种改法

const int m = 0;

const int& n = m;

int m = 0;

const int& n = m;

这两种方法都是可行的

那么,这样可以吗?哈哈

int main()

{

int a = 5;

const int* p1 = &a;

int* p2 = p1;

}

也是不行的,因为const修饰*p1,p1不能改,而p2能修改

所以,我们可以得到的是,指针和引用有权限的放大,而普通的拷贝没有权限的放大

好的谢谢大家阅读本篇博客,期待我们的下一次相遇,觉得博主写的还可以的记得订阅点赞收藏加关注哦!哈哈,下次再见啦!

柚子快报邀请码778899分享:开发语言 c++过渡知识2

http://yzkb.51969.com/

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: