首页 > 编程语言 >【C++】引用

【C++】引用

时间:2024-11-17 11:47:31浏览次数:3  
标签:ps 变量 int C++ 引用 返回值 ret

目录

引用的概念

引用的特点

引用定义时必须初始化

引用的类型必须与被引用对象的类型相同

一个变量可以设置多个引用

引用只能对应一个实体

引用有传递性

赋值与引用的区别

引用作参数

引用作参数-利用引用的变量共同指向同一块内存空间

交换两个变量

传递单链表节点指针

引用传参的优势

引用作返回值

传值返回

传引用返回错误案例

当接收返回值的变量不是别名类型

错误案例一

当接收返回值的变量也是别名类型

错误案例一:

错误案例二:

修改方式:

传引用返回正确使用

引用与指针的区别

使用方法的不同

对内存影响不同


引用的概念

给已经存在的变量取一个别名,编译器不会为引用的变量开辟新的空间,引用的变量共同指向同一块内存空间

void Text()
{
    int a = 1;
    int b = a;
    int& c = a;
    b--;
    c++;
    cout << "a: " << a << endl;
    cout << "c: " << c << endl;
    cout << "b: " << b << endl;
}

  • 引用的变量改变,也就是原来的变量发生改变

引用的特点

引用定义时必须初始化

void Text()
{
    int& b
}

引用的类型必须与被引用对象的类型相同

int a = 1;
const double& b = a;

  • 注意:如果使用常量限定"const"关键字,则可以改变类型
int a = 1;
const double& b = a;

一个变量可以设置多个引用

int a = 1;
int& b = a;
int& c = a;

引用只能对应一个实体

int a = 1;
int c = 2;
int& b = a;
int& b = c;

引用有传递性

void Text()
{
    int a = 1;
    int& b = a;
    int& c = b;
    cout<< "a: " << a << endl;
    cout<< "b: " << b << endl;
    cout<< "c: " << c << endl; 
}

赋值与引用的区别

void Text()
{
    int a = 1;
    int b = a;//把a的值赋值给b
    int& c = a;//c对a进行引用
    cout << "a的地址: "<< &a << endl;
    cout << "c的地址: "<< &c << endl;
    cout << "b的地址: "<< &b << endl;
}
int main()
{
    Text();
    return 0;
}

由此可证明:引用的变量共同指向同一块内存空间,赋值的新变量会开辟出新的空间

引用作参数

引用作参数-利用引用的变量共同指向同一块内存空间

交换两个变量

  • C语言交换两个变量
void Swap (int* left , int* right)
{
    int temp = *left;
    *left = *right;
    *right = temp;
}
int main()
{
    Swap(&a , &b)//传地址
}
  • C++利用引用的变量共同指向同一块内存空间,交换变量
void Swap (int &left , int &right)
{
    int temp = left;
    left = right;
    right = temp;
}
int main()
{
    Swap(c , d)//"left"是"c"的别名,"right"是"d"的别名
}
  • Left & right 和 c&d指向同一块 ,left 和 right 的改变就是 c 和d的改变

传递单链表节点指针

//单链表——定义结构体节点
typedef struct SListNode
{
    struct SListNode* next;
    int val;
}SLTNode ,*PSListNode;
void SListPushBack (PSLTNode& phead , int x)
{
    //......
}

PSLNode plist = NULL;
SListPushBackk(plist , 1);
SListPushBackk(plist , 2);

*PSListNode:每一个单链表节点指针,指向节点自身地址

PSListNode plist:创建结构体指针变量 plist

PSListNode& phead phead是一个别名,别名的类型是PSListNode(结构体指针),相当于是plist的别名

所以,改变phead相当于直接改变plist

引用传参的优势

struct A
{
    int a[10000];
};
void TestFunc1(struct A a) {}//通过值传递,每次调用TestFunc1时,都会复制一个A类型的对象
void TestFunc2(struct A& a){}//接受一个struct A类型的引用参数a。通过引用传递可以避免复制整个结构体
//每个结构体算一个域,不同域中是可以有同名变量的

void TestReturnByRefOrValue()
{
        //在当前作用域内声明一个A类型的变量a
        A test;//C++中结构体声明变量可以不使用struct,只要构体的定义在当前作用域内是可见
        /
        size_t begin1 = clock();// 记录当前时间(以时钟周期为单位)
        for (size_t i = 0; i < 100000; ++i)
        {
                TestFunc1(test);//for循环调用TestFunc1函数100000次,每次都会通过值传递test
        }                
        size_t end1 = clock();//记录调用结束后的时间
        
        
        size_t begin2 = clock();// 记录当前时间
        for (size_t i = 0; i < 100000; ++i)
        {
                TestFunc2(test);//for循环调用TestFunc2函数100000次,每次都会通过值传递test
        }
        size_t end2 = clock();// 记录调用结束后的时间
        ///
        // 计算两个函数运算完成之后的时间
        cout << "TestFunc1 time:" << end1 - begin1 << endl;
        cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
        // 调用测试函数
        TestReturnByRefOrValue();
        // 返回0表示程序成功执行
        return 0;
}

很明显,通过引用传递可以避免复制整个结构体,从而显著提高性能。

引用作返回值

传值返回

思考:int Count函数的返回值是否是n

//传值返回
    int Count()
    {
        int n = 0;
        n++;
        return n;
    }
    int main()
    {
        int ret = Count();
        return 0;
    }

ret存储Count()的返回值

②Count 函数返回时,是 n 是一个局部变量,它的生命周期仅限于Count 函数的作用域内,n的生命周期结束,其内存会被释放

③所以实际传递的n的拷贝,存储在寄存器中

ret获取的是Count()返回值的拷贝

传引用返回错误案例

接收返回值的变量不是别名类型

错误案例一
//传引用返回
    int& Count()
    {
        int n = 10;
        n++;
        return n;
    }
    int main()
    {
        int ret = Count();
        return 0;
        cout << "ret:" << ret << endl;
    }

调试后结果

ret存储Count()的返回值

②Count 函数返回时,是 n 是一个局部变量,它的生命周期仅限于Count 函数的作用域内,n 的生命周期结束,其内存会被释放

③返回值类型为int&,所以返回n的引用(由编译器自动生成),相当于n自身, n 的引用会导致悬空引用,即引用指向一个已经不再有效的内存地址。

ret得到的是已经销毁的n,致未定义行为系统会自动返回随机值(在VS编译器会返回1

接收返回值的变量也是别名类型

错误案例一:
int& Count()
{
        int n = 10;
        n++;
        return n;//返回n的引用
}
int main()
{
        int& ret = Count();//注意,由 int ret -> int& ret ret是函数返回值的引用
        cout << "ret: " << ret << endl;
        return 0;
}

由 int ret -> int& ret,此时ret变成n的引用的引用,得到ret是随机值

错误案例二:
    int& Add(int a, int b) 
    {  
        int c = a + b;
        return c;  //返回c的引用
    }  
    int main()
    {  
        int& ret = Add(1, 2);//注意,相当于ret就是函数的返回值的引用的引用
        //(和返回值C一样直接指向同一块内存空间)
        Add(3, 4); 
        cout << "Add(1, 2) is :"<< ret << endl;
    }

1.Add()没有销毁时,Add()返回c的引用

2.函数调用结束,Add()内存空间销毁,返回值C内存空间随着函数销毁,返回值C的引用也随着C销毁而销毁,

3.再一次调用Add(),没有设置变量接收返回值,但是可以看到ret依旧发生了变化

思考:为什么没有修改retret依旧改变

虽然没有设置变量接收返回值 ->ret是返回值C引用的引用(和返回值C一样直接指向同一块内存空间)->ret依旧指向返回值C第一次销毁前的内存空间

打开反汇编,发现Add(1, 2)与Add(3, 4)两次调用Add函数,两次调用Add函数存储在同一块空间,关于函数栈帧的销毁是另一个知识点,这里仅简单带过

总结:所以ret指向的第一次销毁后的返回值C的空间,在第二次调用Add函数时又重新使用,并且填入新的返回值C的数据,ret指向的内存空间发生变化,ret得到了新的返回值数据,所以,以上对引用返回的使用均是不正确

4.函数调用结束,Add()内存空间销毁,返回值C内存空间随着函数销毁,返回值C的引用也随着C销毁而销毁,

修改方式:

方法一

  int& Add(int a, int b) 
    {  
       static int c = a + b;//使用静态变量,或者malloc动态内存分配
        return c;  
    }  

  • 局部静态变量,只在第一次进入函数时初始化,所以static int C只初始化了Add(1,2)
  • 改变c存储的位置

方法二

  int& Add(int a, int b) 
    {  
       static int c; //每次进入函数都会初始化一次
       c = a + b;
        return c;  
    }  

传引用返回正确使用

  • 传值返回:顺序表每个位置的值++
typedef struct SeqList
{
        int a[100];
        int size;
}SL;
void SLInit(SL* ps, int* arr, int len)
{
        assert(ps);  // 确保传入的指针不为空
        ps->size = len;  // 设置顺序表的大小
        for (int i = 0; i < len; ++i)
        {
                ps->a[i] = arr[i];  // 将数组arr中的值复制到顺序表中
        }
}
void SLModify(SL* ps, int pos,int x)
{
        assert(ps);
        assert(pos < ps->size);
        ps->a[pos] = x;
}
int main()
{
        SL s;
        int arr[] = { 1, 2, 3, 4, 5 };  // 创建一个整数数组
        int len = sizeof(arr) / sizeof(arr[0]);  // 计算数组的长度
        SLInit(&s, arr, len);
        //顺序表每个位置的值++
        for(size_t i = 0;i<s.size; i++)
        {
                s.a[i]++;  // 将顺序表中每个位置的值加1
        }
        cout << "传值返回++:";
        for (size_t i = 0; i < s.size; ++i)
        {
                cout << s.a[i] << " ";  // 输出顺序表中每个元素的值
        }
        cout << endl;//换行
}

  • 传引用返回:顺序表每个位置的值++
typedef struct SeqList
{
        int a[100];
        int size;
}SL;
void SLInit(SL* ps, int* arr, int len)
{
        assert(ps);  // 确保传入的指针不为空
        ps->size = len;  // 设置顺序表的大小
        for (int i = 0; i < len; ++i)
        {
                ps->a[i] = arr[i];  // 将数组arr中的值复制到顺序表中
        }
}
void SLModify(SL* ps, int pos,int x)
{
        assert(ps);
        assert(pos < ps->size);
        ps->a[pos] = x;
}
int main()
{
        SL s;
        int arr[] = { 1, 2, 3, 4, 5 };  // 创建一个整数数组
        int len = sizeof(arr) / sizeof(arr[0]);  // 计算数组的长度
        SLInit(&s, arr, len);
        //顺序表每个位置的值++
        for(size_t i = 0;i<s.size; i++)
        {
                s.a[i]++;  // 将顺序表中每个位置的值加1
        }
        cout << "传值返回++:";
        for (size_t i = 0; i < s.size; ++i)
        {
                cout << s.a[i] << " ";  // 输出顺序表中每个元素的值
        }
        cout << endl;//换行
}

总结:由此看出,可以利用引用直接修改返回对象的值

引用与指针的区别

使用方法的不同

  •  初始化:①指针可以在定义时初始化也可以在之后赋值。②引用必须在定义时初始化
int a = 10;
int* p;
p = &a
\\\\\\\\\\\\\\\\\\\\\\\\
int a = 10;
int& b = a;

对内存影响不同

指针

  • 指针是一个变量,它本身占用内存来存储地址。
  • 指针有独立的内存地址,用于存储被指向对象的地址。

引用

  • 引用本身不占用内存(或者说它的内存开销可以忽略不计),因为它只是一个别名。
  • 引用没有独立的内存地址,它使用的是被引用对象的地址。

引用无法改变指向对象:引用一旦初始化后不能改变引用的对象

int a = 10;
int d = 5;
int&b = a;
b = d;//思考:是d变成b的别名,还是d赋值给b
//这里是d赋值给b

注意:a,b的地址没有发生改变,b的指向没有发生改变

标签:ps,变量,int,C++,引用,返回值,ret
From: https://blog.csdn.net/F091500/article/details/143804089

相关文章

  • GESP2023年12月认证C++四级( 第一部分选择题(11-15))
    ......
  • GESP2023年12月认证C++四级( 第二部分判断题(1-5))
    ......
  • cmake系列-怎么在构建C++库文件时动态的选择构建动态库还是静态库
    在之前我们介绍的内容里,关于构建动态库还是静态库都是在CMakeLists.txt里指定的,那如果一个解决方案原来是构建动态库,然后因为某些原因又希望构建静态库了,那岂不是还要修改CMakeLists.txt,对于平时用的构建系统来说好像还真的是需要修改,哈哈,但是cmake确实有方案能够在不用修改......
  • 【C++笔记】一维数组元素处理
    目录1.插入元素方法代码2.删除元素方法代码3.交换元素方法代码1.插入元素方法概念:插入元素是指在数组的某个位置添加一个新元素,并将原来的元素向后移动。例如,将5插入到数组[1,2,4,6]的第二个位置,结果变为[1,5,2,4,6]。关键点:确定插入位置:首先要明......
  • 如何让Excel公式中的参数实现动态引用
        如果你想成为Excel函数高手,仅仅掌握VLOOKUP和Countif等函数是远远不够的,起码你得学会使用INDIRECT函数,熟练掌握INDIRECT函数能让你从一个初学者晋级为高手,学会它就好比孙悟空掌握了72般变化的基本功,你说厉不厉害。    INDIRECT函数用于将文本字符串转换为......
  • C++继承权限
    目录1.相关概念 2.继承权限private:protectedpublic:3.继承中的构造函数与析构函数4.继承同名成员的处理方式5.多继承1.相关概念  定义:允许一个类继承另外一个类的属性和方法。 好处:可以减少一些重复性的代码 语法:classA :继承权限 B     ......
  • 数据结构与算法刷题(参考代码随想录结构,C、C++实现)
    目录数组数组理论基础二分查找移除元素有序数组的平方长度最小的子数组螺旋矩阵Ⅱ总结篇链表1.链表理论基础2.移除链表元素3.设计链表4.反转链表5.两两交换链表中的节点6.删除链表的倒数第N个节点7.链表相交8.环形链表Ⅱ9.总结篇哈希表1.哈希表理论基础2.有效的字母异位词3.两个数......
  • 【C++复习】栈-下篇
    大家好,这里是不会写开场白的Yinph。今天我们先来复习一下中缀表达式、前缀表达式和后缀表达式,以及如何用栈来实现它们之间的运算。一、中缀表达式‌‌中缀表达式‌是一种算术或逻辑公式的表示方法,其中操作符位于操作数的中间。这种表示方法符合人们的日常书写习惯,因此被广泛使......
  • 【C++】static(静态)
    类外静态变量或函数意味着,当需要将这些变量或函数与实际定义的符号链接时,链接器不会在这个翻译单元的作用域之外寻找那个符号定义,即只会在这个翻译单元内部链接(文件内可用)如果这句话并不理解,可以看一下【C++】HowtheC++CompilerWorks和【C++】HowtheC++LinkerWork......
  • 根据二叉树的前序和中序构建树,并按层次输出(C++)vector存树
    L2-006树的遍历#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;#defineendl'\n'intpo[35];intino[35];vector<int>ans[50];intdfs(intl1,intr1,intl2,intr2){ for(inti=l2;i<=r2;i++){ if......