C++——函数重载,引用

简介: ✅<1>主页:我的代码爱吃辣📃<2>知识讲解:C++🔥<3>创作者:我的代码爱吃辣☂️<4>开发环境:Visual Studio 2022💬<5>前言:补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的,比如:作用域方面、IO方面、函数方面、指针方面、宏方面等。

一.函数重载

在自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重

载了。

比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个

是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”


(1)函数重载概念

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这

些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型

不同的问题。

1.参数类型不同

int Add(int left, int right)
{
  cout << "int Add(int left, int right)" << endl;
  return left + right;
}
double Add(double left, double right)
{
  cout << "double Add(double left, double right)" << endl;
  return left + right;
}
int main()
{
  Add(10, 20);
  Add(10.1, 20.2);
  return 0;
}


2.参数个数不同

void f()
{
  cout << "f()" << endl;
}
void f(int a)
{
  cout << "f(int a)" << endl;
}

3.参数类型顺序不同

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;
}


(2)C++函数重载的原理

我们知道C语言不支持函数重载,为什么C语言不支持函数重载呢,当有两个函数名相同的函数时,C语言就已经无法区分了,是因为C语言仅仅就是通过函数名来区分每个函数。但是在C++里面,C++通过对函数名配合参数进行修饰,就可以通过函数名以及函数参数特点,对每个函数进行区分。


通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修

饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。


如果两个函数函数名和参数都是一样的,仅仅是返回值不同是不构成重载的,因为调用时编译器没办法区分。


二.引用

(1)引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空

间,它和它引用的变量共用同一块内存空间。


比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。


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

看一段代码:

#include<iostream>
using namespace std;
int main()
{
  int a = 10;
  int& ra = a;
  std::cout << "a=" << a << endl;
  std::cout << "ra=" << ra << endl;
  ra = 20;
  std::cout << "a=" << a << endl;
  std::cout << "ra=" << ra << endl;
}



引用和对象使用同一块空间:

int main()
{
  int a = 10;
  int& ra = a;//<====定义引用类型
  printf("%p\n", &a);
  printf("%p\n", &ra);
}



大家还记得这段代码吗:

void ListPushBank(ListNode**phead,int x)
{
  ListNode* newhead = (ListNode*)malloc(sizeof(ListNode));
  newhead->val = x;
  newhead->next = NULL;
  if (*phead == NULL)
  {
    *phead = newhead;
  }
  else
  {
    ListNode* Tail = *phead;
    while (Tail->next)
    {
      Tail = Tail->next;
    }
    Tail->next = newhead;
  }
}
int main()
{
  ListNode* head=NULL;
  ListPushBank(&head,100);
  ListPushBank(&head, 200); 
  return 0;
}

在PushBank时,如果我们遇到第一个结点时,就需要把第一个结点作为头结点,此时需要更改函数外面的 head 指针,就必须要 head 的地址,在函数里面就得使用二级指针。

现在我们有了引用就可以很方便的解决这个问题:

void ListPushBank(ListNode*& phead, int x)
{
  ListNode* newhead = (ListNode*)malloc(sizeof(ListNode));
  newhead->val = x;
  newhead->next = NULL;
  if (phead == NULL)
  {
    phead = newhead;
  }
  else
  {
    ListNode* Tail = phead;
    while (Tail->next)
    {
      Tail = Tail->next;
    }
    Tail->next = newhead;
  }
}
int main()
{
  ListNode* head = NULL;
  ListPushBank(head, 100);
  ListPushBank(head, 200);
  return 0;
}

(2)引用特征

引用有一些很重要的特征需要我们注意:

1.引用在定义时必须初始化

这里不难可能出语法直接时报错的。

2.一个变量可以有多个引用

nt main()
{
  int a = 10;
  int& ra = a;
  int& rb = a;
  int& rc = a;
  printf("ra=%d,rb=%d,rc=%d\n",ra,rb,rc);
  return 0;
}



一个变量可以有多个引用,并且多个引用都共用一个地址空间。

3.引用一旦引用一个实体,再不能引用其他实体

int main()
{
  int a = 10; 
  int b = 30;
  int& ra = a;
  ra = b;//仅仅只是把b的值赋值给了ra
  std::cout << &a << endl;
  std::cout << &ra << endl;
  std::cout << &b << endl;
  std::cout << "a=" << a << " b=" << b << endl;
  return 0;
}



a与ra 仍然公用一块空间,ra = b,并没有改变ra的引用实体,实际上也改变不了ra的引用实体。

(3)常引用

变量权限可缩小或平移,但不可放大

场景一:



因为常量只具有可读属性,如果引用成功的话,就可以通过引用来实现对变量的修改,变量权限就是放大了,这样也是说不通的。

但是也不是没有办法使得引用的实体是常量。



变量是只可读的,变量的引用对象也只能是可读的,这也就是变量权限平移.

上述的结论,不仅针对引用,对指针也是有着同样的效果:



场景二:


  1. 1.这里的报错,大家是不是认为是类型的原因导致的,但是往后看,加上一个const就没有报错了,而且仍然是int类型的引用。
  2. 2.首先来了解类型转的原理:类型转换是有一个中间变量的,而引用的对象的就是那个临时变量,因为临时变量具有常属性,所以加上一个 const 就可以消除报错。


(4) 使用场景

1.做参数

我们之前使用指针,实现函数来交换两个变量的值:

void Swap(int* pleft, int* pright)
{
  int tmp = *pleft;
  *pleft = *pright;
  *pright = tmp;
}

原理我就不多解释了,想必大家都已经熟记于心了,今天我们用引用也可以实现:

void Swap(int& left, int& right)
{
  int temp = left;
  left = right;
  right = temp;
}
int main()
{
  int a = 10; int b = 20;
  std::cout << "交换前a="<< a << ",b=" << b << endl;
  Swap(a, b);
  std::cout << "交换后a=" << a << ",b=" << b << endl;
  return 0;
}


1214d49219984ca7b4aa52ad5adbe4b6.png


2.作为返回值

int& add(int a, int b)
{
  int sum = a + b;
  return sum;
}
int main()
{
  int& ret = add(1, 2);
    std::cout << ret << endl;
  add(3., 4);
  std::cout << ret << endl;
  return 0;
}


bccf2007601f489abe1eb016001815c3.png


问题来了,我们只对ret进行了一次赋值,但是两次输出却是两个值。这又是为什么呢?


457491e8097d414a92320146e16dbef6.png


 怎样可以返回引用呢?其实只要出了函数作用域,函数的返回值还在,就可以返回引用。

例如:static 修饰的变量存储在静态区,不会因为函数栈帧的销毁而销毁。

int& add(int a, int b)
{
    static int sum = a + b;
    return sum;
}
int main()
{
    int& ret = add(1, 2);
    std::cout << ret << endl;
    add(3., 4);
    std::cout << ret << endl;
    return 0;
}


361ec595b2de4ab2b72efa6d58f7de7f.png


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

(5)传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直
接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

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 < 1000000; ++i)
    TestFunc1(a);
  size_t end1 = clock();
  // 以引用作为函数参数
  size_t begin2 = clock();
  for (size_t i = 0; i < 1000000; ++i)
    TestFunc2(a);
  size_t end2 = clock();
  // 分别计算两个函数运行结束后的时间
  cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
  cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
  TestRefAndValue();
  return 0;
}


ee56fab3d0504055a29986f5fd551493.png


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 < 1000000; ++i)
    TestFunc1();
  size_t end1 = clock();
  // 以引用作为函数的返回值类型
  size_t begin2 = clock();
  for (size_t i = 0; i < 1000000; ++i)
    TestFunc2();
  size_t end2 = clock();
  // 计算两个函数运算完成之后的时间
  cout << "TestFunc1 time:" << end1 - begin1 << endl;
  cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
  TestReturnByRefOrValue();
  return 0;
}


a97656f6555b4f6e925f9cd1b0e4e2dd.png


通过上述代码的比较,发现传值和传引用在作为传参以及返回值类型上效率相差很大。

(6)引用和指针的区别

  • 在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
  • 在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。


53a28ff22dc64a60a11a5bb12c74f0ad.png


引用和指针的不同点:


1. 引用概念上定义一个变量的别名,指针存储一个变量地址。

2.引用在定义时必须初始化,指针没有要求。

3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。

4.没有NULL引用,但有NULL指针。

5.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)。

6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。

7.有多级指针,但是没有多级引用。

8.访问实体方式不同,指针需要显式解引用,引用编译器自己处理。

9.引用比指针使用起来相对更安全。


相关文章
|
6天前
|
存储 安全 编译器
【C++入门】缺省参数、函数重载与引用(下)
【C++入门】缺省参数、函数重载与引用
|
1天前
|
C++
C++程序中对象成员的引用
C++程序中对象成员的引用
7 2
|
6天前
|
C++
c++引用是什么意思?
c++引用是什么意思?
6 2
|
6天前
|
C++
c++引用看这个就够了
c++引用看这个就够了
11 0
|
6天前
|
存储 安全 C++
深入理解C++中的指针与引用
深入理解C++中的指针与引用
12 0
|
6天前
|
编译器 C语言 C++
【C++入门学习指南】:函数重载提升代码清晰度与灵活性
【C++入门学习指南】:函数重载提升代码清晰度与灵活性
24 0
|
6天前
|
编译器 C语言 C++
【C++入门】缺省参数、函数重载与引用(上)
【C++入门】缺省参数、函数重载与引用
|
6天前
|
编译器 C++
【C++进阶】引用 & 函数提高
【C++进阶】引用 & 函数提高
|
6天前
|
存储 人工智能 编译器
【重学C++】【引用】一文看懂引用的本质与右值引用存在的意义
【重学C++】【引用】一文看懂引用的本质与右值引用存在的意义
34 0
|
6天前
|
编译器 C++
C++函数重载详解
C++函数重载详解
9 1