从C语言到C++②(第一章_C++入门_中篇)缺省参数+函数重载+引用(中)

简介: 从C语言到C++②(第一章_C++入门_中篇)缺省参数+函数重载+引用

从C语言到C++②(第一章_C++入门_中篇)缺省参数+函数重载+引用(上):https://developer.aliyun.com/article/1513635

3.引用

3.1引用的概念

引用 不是新定义一个变量,而 给已存在变量取了一个别名

编译器不会为引用变量开辟内存空间 ,它和它引用的变量 共用同一块内存空间。

比如你的真实姓名已存在, 你的小名或者外号就是一个别名。

语法:数据类型 &  引用名 =  引用实体;

这里的&可不是取地址啊!它是放在数据类型后面的 &,一定要区分开来!

代码演示:

#include <iostream>
using namespace std;
 
int main()
{
    int a = 10;
    int& ra = a;//<====定义引用类型
//(这里取名为 ra,因为引用的英文是 reference,所以后面命名变量时会简写为 r,或者 ref 来代表引用)
    printf("%p\n", &a);
    printf("%p\n", &ra);
 
    cout << a << endl;
    cout << ra << endl;
    return 0;
}

引用在语法层,我们要理解这里没有开新空间,就是对原来的取了一个新名称而已。

再次注意:

① 引用并不是新定义一个变量,只是给一个变量取别名。

② 编译器不会为引用的变量开辟内存空间,它和它引用的变量会共用同一块内存空间。

3.2引用的特性

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

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

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

前两个好理解,第三个给出代码演示:

#include <iostream>
using namespace std;
 
int main()
{
  int a = 10;
  int& ra = a;
 
  int b = 20;
  ra = b;       // ?
 
  cout << a << endl;
  cout << ra << endl;
  cout << b << endl;
  cout << ra << endl;
 
  return 0;
}

问号处是什么意思呢?这里是让 ra 变成  b 的别名,还是把 b 的值赋值给  ra 呢?

这里打印了四个20,所以是把 b 的值赋值给  ra 。

引用是不会变的,我们定义它的时候它是谁的别名,就是谁的别名了。

以后就不会改了,它是从一而终的!!!

引用和指针是截然不同的,指针是可以改变指向的:

平常这么写其实没什么意义:

int a = 10;

int& ra = a;

它真正有用的地方在于它能够做参数和做返回值。

3.3引用做参数

我们在C语言教学中讲过 Swap 两数交换的三种方式。

我们当时用的最多的就是利用临时变量去进行交换。

如果把它写成函数形式就是这样:

#include <iostream>
using namespace std;
 
void Swap(int* px, int* py)
{
    int tmp = *px;
    *px = *py;
    *py = tmp;
}
 
int main()
{
    int a = 10;
    int b = 20;
    cout << a << ' ' << b << endl;
    Swap(&a, &b);  // 传址
    cout << a << ' ' << b << endl;
    return 0;
}

这里我们调用 Swap 函数需要传地址,

因为形参是实参的一份临时拷贝,改变形参并不会对实参产生实质性的影响。

但是,我们学了引用之后我们就可以这么玩:

#include <iostream>
using namespace std;
 
void Swap(int& ra, int& rb) 
{
    int tmp = ra;
    ra = rb;
    rb = tmp;
}
int main()
{
    int a = 10;
    int b = 20;
    cout << a << ' ' << b << endl;
    Swap(a, b); // 这里既没有传值,也没有传地址,而是传引用
    cout << a << ' ' << b << endl;
    return 0;
}

是怎么做到交换的?

我们知道,形参是定义在栈帧里面的。

实际调用这个函数的时候,才会给 ra 和 rb 开空间。调用这个函数的时候,把实参传给形参。

那什么时候开始定义的?实参传给形参的时候开始定义的。

ra 是 a 的别名,rb 是 b 的别名,所以 ra 和 rb 的交换,就是 a 和 b 的交换。

因此,我们利用这一特点,就可以轻松实现两数的交换。

我们来梳理一下,顺带复习一下之前讲的函数重载。

在我们一共学了三种传参方式:传值、传地址、传引用。

#include<iostream>
using namespace std;
void Swap(int x, int y) 
{
    int tmp = x;
    x = y;
    y = tmp;
    cout << 1 << endl;
}
 
void Swap(int* px, int* py) 
{
    int tmp = *px;
    *px = *py;
    *py = tmp;
    cout << 2 << endl;
}
 
void Swap(int& rx, int& ry) 
{
    int tmp = rx;
    rx = ry;
    ry = tmp;
    cout << 3 << endl;
}
 
int main()
{
    int a = 10;
    int b = 20;
    Swap(&a, &b);
    //Swap(a, b);  // 报错
    return 0;
}

这里 Swap(a,b) 为什么会报错呢?


这三个 Swap 是可以构成函数重载的,


只要不影响它的函数名修饰规则,就不会构影响!


换言之,修饰出来的函数名不一样,就支持重载!


但是 Swap(a,b) 调用时存在歧义。调用不明确!


编译器不知道调用哪一个,是传值还是传引用,所以会报错。


当时再讲数据结构单链表的时候用的是二级指针,当时没有采用头结点的方式。


那么要传指针的地址,自然要用二级指针的方式接收。


现在我们学了引用,我们就可以试着用引用的方法来解决了(这里我们把 .c 改为 .cpp)


任何类型都是可以取别名的,指针也不例外:

int a = 10;
int& ra = a;
 
int* pa = &a;
int*& rpa = pa

我们来看如何用引用的方法来实现!

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
typedef int SLNodeDataType;
 
typedef struct SingleListNode 
{
    SLNodeDataType data;           // 用来存放节点的数据
    struct SingleListNode* next;   // 指向后继节点的指针
} SLNode;                          
 
void SListPrint(SLNode* pHead);
void SListPushBack(SLNode*& rpHead, SLNodeDataType x);
// ... 略

SList.cpp:

#include "SList.h"
 
/* 打印 */
void SListPrint(SLNode* pHead) 
{
    SLNode* cur = pHead;
    while (cur != NULL) 
{
        printf("%d -> ", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}
 
/* 创建新节点 */
SLNode* CreateNewNode(SLNodeDataType x) 
{
    //创建,开辟空间
    SLNode* new_node = (SLNode*)malloc(sizeof(SLNode));
    //malloc检查
    if (new_node == NULL) 
    {
        printf("malloc failed!\n");
        exit(-1);
    }
    //放置
    new_node->data = x; //存传入的数据
    new_node->next = NULL; //next默认置空
 
    return new_node; //递交新节点
}
 
/* 尾插(指针的引用) */
void SListPushBack(SLNode*& rpHead, SLNodeDataType x) 
{
    //创建新节点
    SLNode* new_node = CreateNewNode(x);
    //如果链表是空的
    if (rpHead == NULL) 
    {
        //直接插入即可
        rpHead = new_node;
    }
    else 
    {
        //找到尾结点
        SLNode* end = rpHead;
        while (end->next != NULL) 
        {
            end = end->next; //令end指向后继节点
        }
        //插入
        end->next = new_node;
    }
}

解读: 这里的 SLNode* & rpHead 就是 pHead 的一个别名。

Test.cpp:

#include "SList.h"
 
// 这里我们不传二级指针了。
//void TestSList1()
//{
//  SLNode* pList = NULL;
//  SListPushBack(&pList, 1);
//  SListPushBack(&pList, 2);
//  SListPushBack(&pList, 3);
//  SListPushBack(&pList, 4);
//
//  SListPrint(pList);
//}
 
// 使用引用的方法:
// 我们传 指针的 引用!
void TestSList2()
{
  SLNode* pList = NULL;
  SListPushBack(pList, 1);
  SListPushBack(pList, 2);
  SListPushBack(pList, 3);
  SListPushBack(pList, 4);
 
  SListPrint(pList);
}
 
 
int main()
{
  TestSList2();
  return 0;
}

3.4 传值返回

这是我们以前的传值返回:

int Add(int a, int b) 
{
    int c = a + b;
    return c;
}
 
int main()
{
    int ret = Add(1, 2);
    cout << ret << endl;
    
    return 0;
}

这里 return 的时候会生成一个临时变量(c 为 3)

将 3 复制给这个临时变量,然后返回给 ret

如果我们直接把 c 交给 ret,就会出现一些问题。

如果直接取 c 给 ret,取到的是 3 还是 随机值,就要取决于栈帧是否销毁空间!


这个时候严格来说,其实都是非法访问了。


因为这块空间已经还给操作系统了,这就取决于编译器了。


有的编译器会清,有的编译器不会清,这就太玄学了!


所以,在这中间会生成一个临时变量,来递交给 ret 。


而不是直接用 c 作为返回值,造成非法访问。


所以这里不会直接用 c 作为返回值,而是生成一个临时变量。


那么问题来了,这个临时变量是存在哪里的呢?


① 如果 c 比较小(4或8),一般是寄存器来干存储临时变量的活。


② 如果 c 比较大,临时变量就会放在调用 Add 函数的栈帧中。


总结:所有的传值返回都会生成一个拷贝


(这是编译器的机制,就像传值传参会生成一份拷贝一样)

 从C语言到C++②(第一章_C++入门_中篇)缺省参数+函数重载+引用(下):https://developer.aliyun.com/article/1513637?spm=a2c6h.13148508.setting.21.5e0d4f0emCh6wU

目录
相关文章
|
10天前
|
程序员 C++
C++中的函数重载有什么作用
【10月更文挑战第19天】C++中的函数重载有什么作用
13 3
|
10天前
|
编译器 程序员 C++
C++中的函数重载是什么
【10月更文挑战第19天】C++中的函数重载是什么
10 0
|
18天前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
16 0
|
18天前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
17 0
|
27天前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
27天前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
17天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
20 4
|
17天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
18 4
|
17天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
17 1
|
27天前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)