return、const、volatile关键字

return关键字

大家想过一个问题吗?当我们在下载一段视频时,假设为3个G,我们需要很长时间,而当我们删除这段视频时,几乎可以说瞬间就删除掉了。这合理吗?

大家知道,任何数据在内存中都以二进制的01存储。那么在下载视频时,就是下载了3个G的0和1,那么删除视频时按正常思维来说就应该是把这3个G的0和1给删除掉,但如果是这样,那么花费的时间应该和下载的时间是相同的。

但既然我们从现象中发现,删除的时间相比下载的时间是非常短的,那么就说明,删除的时候并不是把3个G的0和1给删除掉。而是对这3个G的内存做一些手脚,让这3个G变为失效即可。其实,在计算机中,删除数据并不是真的删除,而是使该数据由不可被覆盖变为可以覆盖。

如何正确理解这段代码?

#pragma warning(disable:4996)
#include <stdio.h>
#include <Windows.h>
char* func() {
	char str[] = "hello world!";
	return str;
}
int main()
{
	char* p = func();
	printf("%s\n", p);
	system("pause");
	return 0;
}

C语言是一门面向过程语言,几乎90%以上的代码都是在代码块内,而在代码块内创建的变量,在出代码块时就会销毁。那么其实这些变量,都是创建在栈上的,每一个函数称为一个栈帧。

我们先来分析上面的代码,func函数会创建一个字符串然后返回该字符串的指针,那么该函数当返回之后,该函数就被销毁了,也就是函数内任何的代码都会消失,那么创建的字符串也会消失,那么返回的指针就变为野指针了。然而事实真是如此吗?

image-20220619093417912

调试过程中,func函数返回指针之后应该是会销毁的,而上面看监视发现,并没有销毁,指针p仍然指向那块字符串。

image-20220619093656542

而当我们执行完printf函数之后,发现指针p变为野指针了。

这是因为,我们所称为的函数出代码块销毁其实并不是真的销毁,而只是使得该代码块变得可以被覆盖,所以当出func函数时,可以发现func并没有销毁,而当执行printf时会发现p变为了野指针,这是由于printf也是函数,printf函数覆盖了func函数。

返回值临时变量接收的本质是什么呢?我们再来看一段代码

#pragma warning(disable:4996)
#include <stdio.h>
#include <Windows.h>
int func() {
	int n = 1;
	return n;
}
int main()
{
	int num = func();
	printf("%d\n", num);
	system("pause");
	return 0;
}

有一个问题就衍生了,当func返回之后,该函数就会被销毁,变量n也就会销毁,那返回后,用num接收时会接收到n中的1吗?

image-20220619100113269

查看反汇编时,可以看到,func返回时,会将返回的值存到一个通用寄存器eax中,而用num接收时,会将eax中的值再放入num中。

那如果我们不使用num接收会发生什么呢?

#pragma warning(disable:4996)
#include <stdio.h>
#include <Windows.h>
int func() {
	return 1;
}
int main()
{
	func();
	system("pause");
	return 0;
}

image-20220619100507642

可以发现,我们无论接收不接收,只要函数返回这个整型值,就会把该值存入寄存器中。

也就是说,当我们接收,就会使用这个寄存器。不接收,就不使用寄存器。但是返回值一定会存到寄存器中。

const关键字也许应该被替换为readonly

const修饰的只读变量

image-20220620085852166

可以看到,被const修饰过的变量,不能直接被修改,那是否可以间接被修改呢?

image-20220620090038995

虽然有警告,但是我们是可以进行修改的。

所以,const修饰的变量并不是真的不可被修改的常量

那么const修饰变量的意义在哪里呢?

  • 让编译器进行直接修改式检查

给变量加上const之后,就告知了编译器此变量不可以被修改,而如果在某个地方此变量被修改了,那么编译器就报错,也就是说,加上了const就告诉了编译器不能被修改,而如果被修改了你就报错提醒用户。

  • 告诉其他程序员(正在改你代码或者阅读你代码的)这个变量后面不要改哦。也属于一种“自描述”含义

给变量加上const之后,就告诉其它用户,此变量不要被修改,如果你修改了就报错提醒用户。

const修饰的变量,可以作为数组定义的一部分吗?

image-20220620090956383

很显然是不可以的,即使你变量n被const修饰了,它仍然还是变量,只不过是不能直接被修改而已。

const只能在定义的时候直接初始化,不能二次赋值。为什么?

image-20220620091222167

很简单,因为不能直接对变量n进行修改嘛,那么二次赋值当然也是不可以的了。

case语句后面是否可以是const修饰的变量呢?

image-20220620091620825

很显然,也是不可以的!

我们一般称const修饰的变量为只读变量,因为该变量在意义上是不允许被修改的,只是用来读取的。

const修饰一般变量

一般变量是指简单类型的只读变量。这种只读变量在定义时,修饰符const可以用在类型说明符前面,也可以用在后面。

//demo
const int a = 5;
int const b = 10;

const修饰数组

//demo
const int arr1 = { 0,1,2,3,4 };
int const arr2 = { 5,6,7,8,9 };

当然了,数组的元素也是不允许直接被修改的

image-20220620092226018

const修饰指针

const int* p;	//p指向的内容不可以被修改,但是p本身可以被修改
int const* p;	//p指向的内容不可以被修改,但是p本身可以被修改
int* const p;	//p指向的内容可以被修改,但是p本身不可以被修改
const int* const p;   //p指向的内容和p本身都不可以被修改

有一个方法就能很好的记忆以上四种情况

若const在*的左边,则指针指向的内容不可以被修改

若const在*的右边,则指针本身不可以被修改

const修饰函数的参数

image-20220621145536787

看上面的show函数,它的功能就只是用来输出字符串,所以为了防止show函数可能会改变字符串,咱们加上一个const限定符修饰,如果字符串被改变了编译器报警,加上const也是为了告诉其他用户不要修改字符串。

image-20220621145824699

其实加与不加const,对该函数的影响并不是特别的大,而是我们身为程序员在编写代码时,要尽可能考虑清楚该段代码可能存在哪些问题,从而使用一些手段来解决这些问题或者是告诉我们出现了问题!const就是一个典型的例子,我们为了防止某个变量改变,从而加上const修饰。

const修饰函数的返回值

image-20220621150615542

该函数的返回值加上了const,意思是不想通过返回值改变n的大小,而如果把返回值赋值给一个没有const修饰的指针,该指针是可以修改n的大小的,这就违背了该函数的意愿。所以编译器警告了。
image-20220621150946068

这样子的话,就不会出现警告,并且也不能通过返回值改变n的大小,也符合了该函数的意愿。

对了,对于一般的内置类型,加const是无意义的,因为一般内置类型本身就是临时拷贝,return之后该函数就销毁了。加上const的意义又何在呢?

看下面的图片,可以出一个规律

image-20220621151714208

在C语言中,一个变量限定等级低的赋值给等级高的,一般不会警告。而等级高的赋值给等级低的,一般就会警告。

这也很容易理解,对于变量a来说,我们让它变得相对更安全一点,而变量b安全性会变得更差,也违背了b不想被修改的意愿。

最易变的关键字 - volatile

image-20220621152559188

先看while函数,很明显是一个死循环

它的正常执行流程是cpu中的寄存器从内存中读取到pass中的值,然后进行逻辑判断,执行空语句,再次循环从内存中读取……

但是,cpu可能会对while函数做出优化,优化方案现在理解就是直接将pass中的值放入寄存器中,不再从内存中读取,每次循环直接逻辑判断,执行,逻辑判断执行……,所以万一当pass中的值发生修改使得while不再死循环时,这时cpu并不会从内存中读取新的值,使得每次逻辑判断的时候永远都是寄存器中的值。

而volatile就是解决这个问题的,给pass加上volatile之后,使得该函数在优化之后,仍然是每次从内存中读取,这样就会使得万一当pass发生变化时,寄存器中的值也能及时发生改变。
的值放入寄存器中,不再从内存中读取,每次循环直接逻辑判断,执行,逻辑判断执行……,所以万一当pass中的值发生修改使得while不再死循环时,这时cpu并不会从内存中读取新的值,使得每次逻辑判断的时候永远都是寄存器中的值。

而volatile就是解决这个问题的,给pass加上volatile之后,使得该函数在优化之后,仍然是每次从内存中读取,这样就会使得万一当pass发生变化时,寄存器中的值也能及时发生改变。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云朵c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值