从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

目录
相关文章
|
24天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
77 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
24天前
|
算法 编译器 C语言
【C语言】C++ 和 C 的优缺点是什么?
C 和 C++ 是两种强大的编程语言,各有其优缺点。C 语言以其高效性、底层控制和简洁性广泛应用于系统编程和嵌入式系统。C++ 在 C 语言的基础上引入了面向对象编程、模板编程和丰富的标准库,使其适合开发大型、复杂的软件系统。 在选择使用 C 还是 C++ 时,开发者需要根据项目的需求、语言的特性以及团队的技术栈来做出决策。无论是 C 语言还是 C++,了解其优缺点和适用场景能够帮助开发者在实际开发中做出更明智的选择,从而更好地应对挑战,实现项目目标。
48 0
|
2月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
89 1
|
2月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
30 0
|
2月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
39 0
|
2月前
|
C语言
回溯入门题,数据所有排列方式(c语言)
回溯入门题,数据所有排列方式(c语言)
|
2月前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
2月前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
24天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
49 10
|
24天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
43 9