IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 9.15c++基础 -> 正文阅读

[C++知识库]9.15c++基础

目录

引用:

引用的实际的用处:

const引用:

临时变量如何处理:

引用的实质:

引用的总结:

c++11小语法

auto


引用:

接着我们上节课讲的一些引用的基础知识:

1:引用就是别名,所以当变量a的引用为ra时,ra不仅值与a相同,连地址也与a相同。

引用需要注意的问题:

1:引用必须进行初始化,不能凭空引用:

2:引用一旦引用一个实体,就不能再引用另外的实体。

int main()
{
	int a = 10;
	int b = 20;
	int&ra = a;
	ra = b;
	return 0;
}

如图所示,我们进行调试:

当main函数结束的时候,假如b和ra的地址是相同的,那么引用就可以引用多个实体,否则引用只能引用一个实体。

?如图所示:我们可以发现,ra和b的值是相同的,ra和b的地址是不同的,这证明了引用多个实体是不行的。

引用的实际的用处:

1:作为输出型函数的参数:

我们先举一个输出型参数的例子:

void Swap(int&a, int&b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

输出型参数表示我们要对参数本身进行修改。

输入型参数:

void Sort(int*a, int n)
{

}

输入型参数就是我们平常使用的排序函数,输入要排序的对象和对应数组的元素个数,我们在函数内部实现调用。

2:引用来做返回值。

在这里,我们首先讲一下内存的划分:

内存分为一下四部分:

1:常量区(代码段):存放普通常量。

2:静态区:存放的static修饰的静态变量。

3:堆区:动态内存申请的空间

4:栈帧:函数调用所开辟的空间

int&Count()
{
	static int n = 0;
	n++;
	return n;
}

要理解这个引用返回,我们可以先理解一下传值返回:

int Count()
{
	static int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

要理解这个传值返回,我们把最简单的传值返回讲解一下:

int Count()
{
    int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

?这个最简单的传值返回对应的内存图像是这样的:

具体的步骤是这样的:

我们先调用Count函数,调用函数,形成Count函数对应的栈帧,局部变量n就在栈帧空间内部,n++,n变成1,我们把n返回,这里的返回并不是直接把n返回给ret的,这里的返回是通过我们创建一个临时变量,用这个临时变量来接收n,但接收的只是n的值,然后Count函数调用结束,对应的栈帧销毁,对应的空间返还给操作系统,我们再把临时变量的值返回给Count值。

注意:当这个临时变量比较小时,被存放在寄存器中。

当这个临时变量比较大时,被存放在上一层函数的栈帧中,也就是main函数的栈帧中。

要证明以上的想法,我们需要判断ret和n的地址是否相同:

对应的ret和n的地址不同。

接下来,我们来分析更难的传值调用:

int Count()
{
	static int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

?这段代码和上面的唯一的不同就是变量n用了static修饰,static修饰的话就是静态变量,静态变量存放在静态区,所以不会随着栈帧的销毁而释放。当我们调用函数完毕后,返回n,这时候,我们还是创建一个临时变量来接收n,再由临时变量返回给ret。

对应的ret还是1.

但是当我们使用引用返回时,我们需要小心:

我们举出一个错误案例:

int& Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int &ret = Count();
	return 0;
}

我们首先对代码分析:

调用函数Count,创建变量n,n++后等于1,返回1,这时候因为我们的返回值类型用的引用,所以我们返回的就是n的别名,但是函数调用完毕之后,属于n的那部分空间也已经被操作系统回收,这时候我们对属于n的空间进行访问实际上就是一种越界访问了。

我们进行测试:

int& Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int &ret = Count();
	cout << ret << endl;
	return 0;
}

进行编译:

?打印的结果仍为1,原因是什么呢?

答:我们画图进行解释:当我们调用Count函数时,我们对应的内存图像是这样的。

?但是当我们函数调用完毕后,我们对应的空间被释放:

?虽然我们的空间不属于我们了,但是对应的n值仍旧存在,没有被覆盖,我们用对应的同样的引用ra来接收a,a其实是n的引用,所以ra就是n的引用。

我们再打印几次:

int& Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int &ret = Count();
	cout << ret << endl;
	cout << ret << endl;
	cout << ret << endl;
	return 0;
}

?为什么剩下的两次打印都是随机值?

答:因为我们的ra是n的引用,当我们刚刚调用完毕Count函数时,我们对应的图象是这样:

?当我们第一次调用打印函数时,对应的图像是这样:

?我们对应的n值已经被覆盖,但是注意:当我们调用打印函数的时候传参的时候,n值仍是存在的,n值为1,所以我们打印的n为1.

我们第一次打印函数调用完毕的图象是:

我们第二次调用打印函数:我们的参数n对应的空间现在是打印函数内部的随机值,所以我们打印的结果是随机值

第三次调用也是同样。

正确的写法:

int& Count()
{
	static int n = 0;
	n++;
	return n;
}
void p()
{
	int x = 100;
}

我们进行检测:

?正确的原因如下:

因为我们是静态变量,静态变量n并不会随着函数的调用结束而被释放,并且我们返回的是n的引用,所以我们并不需要把返回值拷贝到临时变量中,进而能够节省空间。

结论:出了函数作用域,返回的变量不存在了,不能用引用返回,因为引用返回的结果是未定义的。

总结:传引用返回的作用

1:减少拷贝,提高效率

2:修改返回值。

引用做参数的作用:

我们写两个交换函数进行分析:

void Swap(int a, int b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
void Swap(int&a, int &b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

其中,对于第一个函数,我们在调用函数时,需要拷贝实参作为形参,而对于第二个函数,我们只是引用了实参,所以第二个函数的效率更高。

对于第一个函数,我们的交换并不能成功,原因是形参的改变并不影响实参。

而对于第二个函数,我们的交换可以成功,原因是我们的形参是实参的引用,所以形参修改,实参也跟着修改了。

总结:

引用做参数的作用:

1:减少拷贝,提高效率

2:对于输出型参数,当形参改变的时候,实参也跟着改变。

这两个函数构不构成重载:

构不构成重载是根据编译器对于两个函数参数的理解,当编译器把int &识别成int时,这两个函数就不构成重载,当编译器把int&的类型识别为引用类型时,这两个函数就构成重载。

我们进行运行检测:

void Swap(int a, int b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
void Swap(int&a, int &b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	
}

代码并没有报错,证明函数构成了重载。

但是这里其实构不构成重载无关紧要,因为无论构不构成重载,我们都无法调用这两个函数,因为Swap(2,3)同时适用于这两个函数,造成歧义性,所以无法调用。

const引用:

    int a = 0;
	int&ra = a;

这种写法是正确的。

    const int b = 1;
	int&rb = b;

但是这种写法却不对。

错误的原因是什么?

因为b是只能读而不能写的参数,而rb是可读可写的参数,所以对应的b的权限增大了,所以导致错误。

我们写一个权限缩小的例子:

    int b = 1;
	const int&rb = b;

?b是可读可写的,而ra是只读不可写的,所以由b到ra是权限的缩小,我们进行编译:

我们再写一个权限不变的例子。

    const int a = 10;
	const int&ra = a;

这两个都是只读而不可写的参数,所以由a到ra,权限是不变的。

所以,我们进行总结:

在指针或引用中,权限可以缩小,但是不可以扩大。?

void Func(int&ra)
{

}

假如我们要调用这个引用的函数用来减少拷贝来提高效率可以吗?

答:不可以,原因是当我们调用这个函数的时候,我们的传参是受限的。

void Func(int&ra)
{

}
int main()
{
	int a = 10;
	const int b = 20;
	Func(a);
	Func(b);
}

如图所示,我们调用Func(a)是可以的,但是我们无法调用Func(b),因为b是只读的参数,我们传参的时候,我们把只读的参数传给可读可写的参数。

所以我们正确的写法是这样:

void Func(const int&ra)
{

}
int main()
{
	int a = 10;
	const int b = 20;
	Func(a);
	Func(b);
}

但是,注意这种情况

    int a = 10;
	const int b = 20;
	a = b;

b是只读的,a是可读可写的,把b赋给a是不是导致权限的放大?

并不是,因为这里根本不涉及引用和指针,这里这是简单的赋值操作,传递的是参数值,所以没有任何影响。

临时变量如何处理:

void Func(int a=10) 
{

}

这个是普通的缺省参数。

当我们加入一个引用符号时:

这里错误的原因我们可以这样理解:

10是一个常量,我们要对一个常量引用的话,我们要保证自身要是一个常量,所以我们可以用const修饰。

void Func(const int&a=10) 
{

}

?

?

我们再举一个例子:

?为什么这里会发生错误?

许多人的想法是类型不同,但是为什么下面这种写法可以呢?

?

这里发生了隐式类型转换,转换的方式:首先,创建一个临时变量,临时变量接收了(d的数值被转换成int类型的形式),然后再把临时变量赋给变量i。(注意:d并没有改变,只是d的数值在传给临时变量的时候被转换了)

我们可以进行实验:

?如图所示,我们可以发现d被赋值给i后,其本身并没有发生改变。

这种写法其实就等价于强制类型转换。

int main()
{
	double d = 12.34;
	/*int&ri = d;*/
	int i = (int)d;
	return 0;
}

无论是强制类型转换,还是隐式类型转换,或者是整型提升,亦或者是传值返回,都有临时变量的参与。

那这个时候,对应的i值是多少呢?

?

临时变量的一大特点就是:临时变量具有常性。

这个时候,我们就能解释为什么int&ri=d为什么报错了:

d首先要进行转换,转换的时候,媒介是临时变量,我们要把临时变量赋给ri,而临时变量具有常性,我们要对具有常性的整型进行引用,那我们本身也要是常数才行。

const int&ri = d;

所以当我们这样写的时候,就不会报错。

我们再写一串代码加强理解:

为什么这里会有错误呢?

答:因为这里是传值返回,传值返回的媒介就是临时变量,临时变量具有常性,所以我们无法用普通的引用来进行接收。

我们需要加上const。

?

引用的实质:

在语法角度,我们的引用相当于给变量取一个别名,所以不会额外开辟空间,但是在底层实现的话,引用需要额外开辟空间。

我们拿引用和指针解引用进行对比:

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

	int*pb = &b;
	*pb = 25;
}

?第一个是通过引用把a的值修改为15.

第二个是通过指针解引用把pb的值修改为25.

我们转到汇编代码看一下:

我们虽然看不懂,但是可以发现mov和lea都出现多次,我们查一下mov和lea对应的意思:

?mov:

?这个很容易理解,既然我们要修改a和b的值,我们就肯定要用数据传送指令。

lea

这里就能看出问题了,在我们的印象中,指针相关的操作是需要传递地址的,引用并不需要。

这里的lea就证明了在底层实现的逻辑上来看:引用也是需要额外开辟空间的。

引用的总结:

?

c++11小语法

auto

int main()
{
	int a = 10;
	auto b = a;
}

auto的意思就是根据a的类型推导b的类型,所以b的类型也是int了。

auto在for循环中的使用:

我们现在写for循环要这么写:

int main()
{
	int i = 0;
	int a[5] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		cout << a[i] << " ";
	}
	cout << endl;
}

是不是非常复杂,我们使用auto可以这样写:

int main()
{
	int i = 0;
	int a[5] = { 1, 2, 3, 4, 5 };
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;
}

这串代码的意思是:我们首先调用for循环,把数组a中的内容赋给e,然后自动判断结束,自动迭代,打印出所有的数组值。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-21 00:08:17  更:2022-09-21 00:09:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 12:10:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码