程序的内存四区 引用 函数高级

一.程序运行前

 在程序编译后(指的是整体的编译),会生成一个可执行程序exe,未执行该程序前分为两个区域

1.代码区

代码区主要是存放cpu执行的机器命令

代码区是共享的,在内存中只有这一份代码,共享的目的是对于频繁被执行的程序,免得每次执行一次都要在内存中存一份这样的代码,浪费空间

代码区是只读的,避免程序意外的修改了它的命令

2.全局区

全局区存放:全局变量,静态变量(static修饰),常量

而常量又分为:字符串常量和const修饰的全局变量(也叫做全局常量)

注:const修饰的局部变量是存放在栈区中的,那叫局部常量,要跟const修饰的全局变量作区别

全局区存放的数据在程序执行完毕后由操作系统操来释放

二.程序运行后

1.栈区

栈区是一种线性表的存储结构的应用,它是一种受限的线性表,只能由同一端进出(先进后出)

栈区存放:局部变量,const修饰的局部变量(局部常量),函数的形参

这里要主义的一点是:不要返回栈区数据的地址

为什么?
因为当你返回去的时候,确实是返回了一个指向栈区数据的地址,但我们知道,栈区存放的数据在函数结束后由编译器自动释放掉,也就是说,你想要的数据已经被释放掉了,你这时候再去访问这个地址会造成非法访问(越界)

而在vs编译器下,你会发现你访问第一次是正常的,第二次就造成乱码了,这是因为编译器优化了,认为可能是你误访问,所以保存了一次,但你的下次访问它就不会再保存

2.堆区

这是一个由程序员可自由调动的一块内存区域,你可以使用new函数进行动态的内存开辟(也就是c语言中的动态内存开辟那一讲中我们所提到的malloc calloc realloc free),在堆区存储的数据可由程序员主动释放或是程序运行结束后由操作系统释放掉

这里我们看一下比较容易错的点:

int* p = new int(10);
return p;

int* p这个变量是创建在栈区上的,它存储的数据是一个指向堆区数据的地址,在函数结束后这个变量的数据将会被释放掉,但这样返回是没错的,因为返回的是一个堆区数据的地址,堆区除了程序结束会被释放掉,只有程序员主动释放才会没,所以不会造成非法访问

2.1 new函数的使用

首先new返回的是一个你所开辟数据类型的指针,我们来举例

int* p = new int(10);这段代码的意思就是动态开辟一个int类型的数据来存储10这个数据,然后将这个地址返回去

delete p; 这个释放和c语言中的free有点相似,也是给一个指针,通过这个指针来释放掉动态开辟的内存

int* p = new int[10];动态开辟一个整形数组装着十个元素,它返回的是这个数组首元素的地址

注意:用delete释放数组时得小心

delete[] p;你得用[]来告诉delete这是一个数组,不然它只会释放掉那个首元素,从而造成内存泄漏

我们在c语言中想要动态开辟一个数组,可以用柔性数组,或者用一个指针指向一个大块的内存,具体请看动态内存开辟那一讲的内容

三.引用

(I)基本使用

语法:数据类型 &别名 = 原名 eg:int a = 10;int &b = a;

注意:

1.引用必须初始化,就是必须让它等于一个数据,因为你是给一个数据起别名,不初始化怎么知道是给谁的

2.引用初始化之后,就不可以更改了(当你是给a起别名了并且这个别名初始化了,你后面就无法将这个别名变量改为给b起别名,除非再创建一个新的别名变量)

(II)引用做参数时的具体细节

我们知道在函数传参时,我们有两种方式传参:1.传值,就是拷贝一份实参到栈区中(空间利用率低) 2.传址,可以在函数体内部更改外面的实参

引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参(就是相当于可在函数内部影响外部的实参,相当于指针)

优点:简化指针修改实参

void mySwap(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}

形参是引用变量,当你传参时,就是让这些引用变量赋值为实参,变成它们的别名

引用做函数返回值

注意:1.不要返回局部变量的引用(有点类似于不要返回局部变量的地址)

2.函数的调用可以作为左值(既可以修改的值)

int& test01()
{
	static int a = 10;
	return a;
}
int main()
{
	int& ret = test01();
	cout << ret << endl;
	test01() = 1000;
	cout << ret << endl;

	return 0;
}

test01()返回的是a,所以调用它就可以把它当成那个返回值,这时候给它赋值相当于这个引用赋值

数据存储的地方都是同一个空间,所以我们用别名赋值,都是对同一块空间进行修改

引用的本质

本质:引用的本质就是一个指针常量(指针本身不能被修改,相当于用const修饰了指针这个变量,指针的指向是固定的,但它所指向的值可以修改,不要复杂理解)

int& ret = a;当编译器内部发现这是一个引用时,就会自动帮我们转化为int* const ret = &a;

正是因为它是一个指针常量,所以引用初始化后就不能修改其指向了

ret = 20;自动转化为*ret = 20;

常量引用

作用:修饰形参,防止误操作

操作方法:就是在形参引用前面加个const,使其指向的空间无法被修改

注意:int& ret = 10;10是一个常量存放在常量区(全局区中的常量区),而引用必须引一块合法的内存空间(堆区或栈区上的数据)

但在前面加了个const,就是const int& ret = 10;这样就不会出错了,因为刚刚10是个常量,我们按原先那样写是一个指针常量,但它所指向的空间是可以被修改的,而加了个const就可以让这个引用所指向的数据不被修改了(相当于const int* const ret = 10;)双重保险hhh

四.函数高级

4.1函数默认参数

在c++中,函数的形参是可以有默认值的

语法:返回值类型 函数名(参数 = 默认值){}

注意:如果你在传值的时候(调用函数的时候有传值进去),那么这个值的优先级是比默认参数的优先级要高的

注意事项:1.如果某个位置已经有默认参数了,则这从这个位置开始的后面的参数都得有默认参数。

2.如果函数声明有默认参数,函数实现就不能有默认参数,所以在函数实现和函数声明只能有一个默认参数。

4.2函数占位参数

c++中函数参数列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

语法:返回值类型 函数名 (数据类型){}

注意:函数的占位参数是可以有默认值的

4.3函数重载

作用:可以让函数名相同,提高复用性

函数重载的满足条件:

1.同一个作用域下(比如都在全局作用域下)

2.函数名称相同

3.函数参数类型不同,或者个数不同,或者顺序不同

注意

1.函数的返回值类型不同是不能作为函数重载的条件

2.引用也可以作为重载条件

void func(int& a)
{
	cout << "func(int& a)的调用" << endl;
}
void func(const int& a)
{
	cout << "func(const int& a)的调用" << endl;
}
int main()
{
	int a = 10;
	func(a);
	func(10);

}

如图所示,这边的函数重载是利用形参的类型不同为条件,利用const修饰的话那么我们传进去一个常量就行了,因为常量是无法修改的值 

3.函数重载碰到默认参数

void func(int a,int b = 10)
{
	cout << "func(int& a)的调用" << endl;
}
void func(int a)
{
	cout << "func(const int& a)的调用" << endl;
}
int main()
{
	int a = 10;
	func(10);

}

这会出错,因为上面也能调用,下面也能调用,这时候就出错了(出现二义性)

所以写函数重载就不要写默认参数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值