首页 > 编程语言 >C++入门基础(四)

C++入门基础(四)

时间:2024-11-11 23:18:31浏览次数:3  
标签:入门 int 函数 基础 ret C++ sl size 引用

目录

引用的应用

做参数

	#include<iostream>
	using namespace std;
	void Swap(int& a, int& b)
	{
		int temp = a;
		a = b;
		b = temp;
	}
	int main()
	{
		int c = 0, d = 1;
		cout << c <<'\n' << d << endl;
		Swap(c, d);
		cout << c <<'\n' << d << endl;
		return 0;
	}

在这里插入图片描述
在C语言中的写法为

	void Swap(int* a, int* b)
	{
		int temp = *a;
		*a = *b;
		*b = temp;
	}
	int main()
	{
		int c = 0, d = 1;
		printf("%d %d ", c, d);
		Swap(&c, &d);
		printf("%d %d ", c, d);
		return 0;
	}

在这里插入图片描述
可以看出,C语言在实现Swap函数的时候,需要考虑Swap的参数类型,如果要传入地址就需要让参数类型变成int*类型,然后在传参时需要将参数的地址传进去
而C++则要简单一点,如果想要修改函数外面的变量,那么就让参数类型变成别名,这样就不需要考虑要不要传地址了

做返回值

	int func()
	{
		int a = 0;
		return a;
	}
	int main()
	{
		int ret = func();
		return 0;
	}

这段代码中不是把a的值给ret,这里需要再理解一下函数栈帧
main函数调用时会建立一块栈帧,局部变量ret会在里面开一块空间,当函数结束的时候局部变量就会销毁
在这里插入图片描述
main函数中调用了函数func,然后func函数会建立一块新的栈帧,a是func函数中的一个局部变量,所以回走func函数建立的栈帧里开一块空间
在这里插入图片描述
在这里插入图片描述
当func函数结束后,他开辟的空间就会销毁,而a也是会跟着销毁的,如果将a对值返回给ret,那么ret接受的可能是一个随机值
而不同的平台输出的值不同,VS中输出的是a的值,但并不代表a把值返回给了ret

在这里插入图片描述
因为VS会开辟一块空间,里面有一个临时变量,临时变量的值等于函数的返回值,当func函数结束后临时变量的空间是不会被销毁的,然后将临时变量的值返回给ret

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

野引用

当我们强行让函数的返回值变成别名的时候,就会报错

	int& func()
	{
		int a = 0;
		int b = 1;
		return b;
	}
	int main()
	{
		int ret = func();
		cout << ret << endl;
		return 0;
	}

在这里插入图片描述
这种情况和指针类似,指针中的野指针是指向一块内存被释放或者没有访问权限的指针,这里的引用是取了一个变量a的别名,而a的空间已经被释放掉了,但是仍然要返回a的别名,导致出现了野引用
(实质就是a的空间被释放掉了,但是最后还要去访问a的空间,而空间里的值是一个随机值,导致输出结果不确定)

不同的平台结果不一样,VS中没有将a的栈帧销毁,所以ret接收到是a的值
在这里插入图片描述
如果我们让ret是func返回值的别名会怎么样?

	using namespace std;
	int& func()
	{
		int a = 0;
		return a;
	}
	int main()
	{
		int &ret = func();
		cout << ret << endl;
		return 0;
	}

ret为a的别名,所以ret的空间就是a的空间,而a的空间在func函数新开辟的空间中,所以ret在main函数中没有空间,最后因为a的空间会被释放,导致ret也成了也引用
在这里插入图片描述
在这里插入图片描述

扩展

	int& func()
	{
		int a = 0;
		return a;
	}
	void fx()
	{
		int b = 3;
	}
	int main()
	{
		int &ret = func();
		cout << ret << endl;
		fx();
		cout << ret << endl;
		return 0;
	}

在这里插入图片描述
ret明明是a的别名,但是在调用一个函数fx后就变了
后面再调用一次func后,ret的值又变回来了

如果让fx函数的返回值后面加个引用符号会怎么样?
最终结果没有变化
在这里插入图片描述

在这里插入图片描述
上面的情况是因为空间可以互用的,当func函数调用完后,会销毁空间,而调用fx函数后,原来的那块空间就会给到fx

因为ret是野引用,ret的地址仍然指向原来空间的地址,但是由于后面那块空间被分给了fx,所以ret的值又变成了fx中b的值

如果fx中没有定义b会怎么样
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
这个例子更能体现出ret为野引用后的随机性

总结:传值返回是返回 返回值的拷贝,传引用返回是返回 返回值的别名,返回变量出了函数作用域后生命周期(局部变量)就结束了,不能引用返回而像全局变量 静态变量(static修饰) 堆上变量等这些就可以用引用返回

传值、传引用效率比较

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

#include <time.h>
	struct A { int a[10000]; };
	void TestFunc1(A a) {}
	void TestFunc2(A& a) {}
	int main()
	{
		A a;
		size_t begin1 = clock();
		for (size_t i = 0; i < 10000; ++i)
			TestFunc1(a);
		size_t end1 = clock();
		size_t begin2 = clock();
		for (size_t i = 0; i < 10000; ++i)
			TestFunc2(a);
		size_t end2 = clock();

		cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
		cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
		return 0;
	}

这段代码是为了比较传值、传引用的效率,为了更容易看出他们直接到效率差距,所以定义了一个比较大的结构体,然后分别调用10000次函数,size_t begin = clock()是记录刚开始调用函数的时间, size_t end = clock()是记录调用结束后的时间,二者相减就可以得到调用函数所耗的时间
在这里插入图片描述
这里的0毫秒是接近于0,通过这段代码可以看出,引用传参效率更高

引用和指针的区别

引用的语法和底层是有一些不一样的
举个例子鱼香肉丝语法角度看有鱼有肉丝,而底层实现只有肉丝

语法角度引用是别名,不开空间,而指针是地址需要开空间存地址
而通过下面的代码调试看汇编可以得出引用也是会开空间的,事实上引用的底层是通过指针实现的

int main()
{
	int a = 10;
	int& ra = a;
	ra = 20;

	int* pa = &a;
	*pa = 20;
	return 0;
}

在这里插入图片描述
这里补充一下汇编的查找方法
先进行调试,然后点击鼠标右键,就可以找到汇编了
在这里插入图片描述

引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
    一个同类型实体(引用不能改变指向,指针可以)
  4. 没有NULL引用,但有NULL指针(引用相对更安全,没有空引用,但是有空指针,容易出现野指针,但是不容易出现野引用)
  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
    位平台下占4个字节)
  6. 引用自加即引用的实体增加1(i++…),指针自加即指针向后偏移一个类型的大小( * a+1)
  7. 有多级指针(int ** a),但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

C++对比C语言实现顺序表

C语言实现顺序表可以查看顺序表详解
下面简单的用C++实现顺序表的一些功能

struct SeqList
{
	int* a;
	int size;
	int capacity;
	void Init()
	{
		a = (int*)malloc(sizeof(int) * 4);
		//..
		size = 0;
		capacity = 4;
	}
	void PushBack(int x)
	{
		//扩容
		a[size++] = x;

	}
	int SLGet(int pos)
	{
		assert(pos >= 0);
		assert(pos < size);
		return a[pos];
	}
};

C语言是数据和函数分离,而C++可以将函数写在结构体里,并且可以看到,许多函数传入的参数都非常少,甚至没有传参的

因为这些函数是在结构体内部,而结构体里又定义了int a int size,int capacity这些参数,结构体内部的函数是可以直接用这些参数的,而像x和pos这些结构体里没有的参数就需要我们传参,这样就使C++实现顺序表变得很简单,很容易看懂*

对比C语言中因为数据和函数是分离的,导致像int* a int size,int capacity这些作为参数必须要写明是哪个结构体中的数据,否则就会分不清,而C++因为函数在结构体内部,这些数据都不需要写明就知道是在哪个结构体中

我们用代码测试一下功能
因为函数在结构体内部,所以调用函数的时候就可以直接当成结构体中的成员访问

打印顺序表中的数据

int main()
{
	SeqList sl;
	sl.Init();
	sl.PushBack(1);
	sl.PushBack(2);
	sl.PushBack(3);
	sl.PushBack(4);
	for (int i = 0; i < sl.size; i++)
	{
		cout << sl.SLGet(i) << "";
	}
	return 0;
}

在这里插入图片描述

修改顺序表中的数据

int main()
{
	SeqList sl;
	sl.Init();
	sl.PushBack(1);
	sl.PushBack(2);
	sl.PushBack(3);
	sl.PushBack(4);
	for (int i = 0; i < sl.size; i++)
	{
		if (sl.SLGet(i) % 2 == 0)
		{
			sl.SLGet(i) *= 2;
		}
	}
	for (int i = 0; i < sl.size; i++)
	{
	cout << sl.SLGet(i) << endl;
	}
}

当想修改顺序表中的数据时,却出现表达式必须是可修改的左值,说明sl.SLGet(i) *= 2中的sl.SLGet(i)为一个常量,比如1=2,1就是一个常量,我们修改不了他
在这里插入图片描述
要想解决就先得看一下int SLGet(int pos)的实现

int SLGet(int pos)
	{
		assert(pos >= 0);
		assert(pos < size);
		return a[pos];
	}

显然SLGet函数返回类型为int类型,也就是说返回的是一个临时变量,C++规定临时变量具有常性,也就是说这个临时变量就是一个常量

为了解决这个问题,就需要用到引用int& SLGet(int pos),由于pos位置的值是用malloc开辟出来的,当出了作用域后不会被销毁,所以用引用取别名,仍然可以访问到pos位置的值
在这里插入图片描述

标签:入门,int,函数,基础,ret,C++,sl,size,引用
From: https://blog.csdn.net/2401_86956109/article/details/143639924

相关文章

  • D64【python 接口自动化学习】- python基础之数据库
    day64SQL-DQL-基础查询学习日期:20241110学习目标:MySQL数据库--133SQL-DQL-基础查询学习笔记:基础数据查询基础数据查询-过滤总结基础查询的语法:select字段列表|*from表过滤查询的语法:select字段列表|*from表where条件判断......
  • D65【python 接口自动化学习】- python基础之数据库
    day65SQL-DQL-分组聚合学习日期:20241111学习目标:MySQL数据库--133SQL-DQL-分组聚合学习笔记:分组聚合总结分组聚合的语法分组聚合的注意事项groupby中出现了哪个列,哪个列才能出现在select中的非聚合中......
  • RL 基础 | 如何使用 OpenAI Gym 接口,搭建自定义 RL 环境(详细版)
    参考:官方链接:Gymdocumentation|Makeyourowncustomenvironment腾讯云|OpenAIGym中级教程——环境定制与创建知乎|如何在Gym中注册自定义环境?g,写完了才发现自己曾经写过一篇:RL基础|如何搭建自定义gym环境(这篇博客适用于gym的接口,gymnasium接口也差不......
  • 现代IT基础设施管理(2):Terraform进阶
    上一篇对Terraform进行了简单介绍,并尝试一个创建虚拟机实例的演示实验,对IaC(基础设施即代码)有了初步的认识,这一篇我们稍微深入一些,继续对Terraform进行进阶尝试,使用高级特性更安全高效管理基础设施,尽量还原实际生产使用。代码仓库地址:https://github.com/robin-2016/terraform-dem......
  • 目录结构和基础命令
    一、目录结构【1】、Windows和Linux目录结构windows目录结构:D:\新建文件夹\OneDrive\桌面\Linux\oldboy\第一阶段C:\PerfLogsE:\node_modulesLinux目录结构:类似Windows只有一个C盘,使用“/”表示,“/”是一个目录/root/etc/ssh【2】、相对路径和绝对路径相对路径:当......
  • Linux基础日志分析
    四、日志分析【1】、whatis日志日志log分析,检查系统,服务是否正常运行,一般都要看日志。后面学习的一些服务软件,遇到故障就要查看日志。Linux日志一般存放在/var/log/目录。【2】、核心日志ip,root,密码,22(端口)Linux默认核心日志说明/var/log/secure(麒麟,......
  • C++结构体中的资源释放
    一般情况下,结构体不需要手动释放。如果结构体只包含基本类型(如int、float等)或没有指针成员,其内存由栈或堆自动管理,不需要手动释放。然而,如果结构体包含动态分配的资源(例如指针、文件句柄等),则需要在结构体的生命周期结束时手动释放这些资源。通常可以通过以下方式来管理:使用......
  • C++数据结构实验题目解析
    目录题目:考点分析:难点1:就地逆置步骤:代码实现:核心代码详细解释:难点2:①非递减链表,②删除相同元素代码详解①:代码详解②:完整代码:大家好,今天我就来给大家分析一下我上期分享的题目的详细解析,编程的能力都是逐步提升的,但是思维的锻炼可以提前进行,这样有助于我们以后自......
  • C++中需要资源释放的变量
    资源或变量需要释放的情况通常是在其内存或其他系统资源是动态分配的或非自动管理的,尤其是在手动分配资源时(如new、malloc、文件句柄、网络连接等)。未释放这些资源会导致内存泄漏或资源泄漏。以下是一些典型需要释放资源的场景:1.动态内存分配通过new、new[]、malloc、calloc......
  • 用C++写数字直角三角形和摘苹果问题
    题目描述给出n,请输出一个直角边长度是 n的数字直角三角形。所有数字都是2位组成的,如果没有2位则加上前0。输入格式输入一个正整数n。输出格式输出如题目要求的数字直角三角形。输入输出样例输入#1复制5输出#1复制010203040506070809101112131415说明......