1.内存分区模型

在C++程序执行时,内存被划分为四个主要区域:代码区全局区栈区堆区

这些区域分别存储不同类型的数据,并具有不同的生命周期和管理方式,使编程更加灵活

 

C++在程序运行前分为全局区和代码区。

代码区

代码区存放程序的二进制代码(我们写的全部代码翻译后的二进制机器指令),代码区具有共享只读的特点。

只读的目的是防止程序意外修改指令。

共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。

全局区

全局区存放全局变量、static静态变量和const常量的数据,不存放 局部变量和局部常量 全局区的数据在程序结束后由操作系统释放。全局区还包含常量区,存放字符串常量和其他常量(const修饰的变量)

#include<iostream>
using namespace std;

// 全局变量
int g_a = 10;
int g_b = 10;
// const修饰的全局变量
const int c_g_a = 10;
const int c_g_b = 10;

int main() {
	// 局部变量
	int a = 10;
	int b = 10;
	cout << "局部变量a的地址为:" << (int)&a << endl;
	cout << "局部变量b的地址为:" << (int)&b << endl;

	// 全局变量
	cout << "全局变量g_a的地址为:" << (int)&g_a << endl;
	cout << "全局变量g_b的地址为:" << (int)&g_b << endl;

	// 静态变量
	static int s_a = 10;
	static int s_b = 10;
	cout << "静态变量s_a的地址为:" << (int)&s_a << endl;
	cout << "静态变量s_b的地址为:" << (int)&s_b << endl;

	// 字符串常量
	cout << "字符串常量Hello World的地址为:" << (int)&"Hello World" << endl;

	// const修饰的全局变量
	cout << "全局常量c_g_a的地址为:" << (int)&c_g_a << endl;
	cout << "全局常量c_g_b的地址为:" << (int)&c_g_b << endl;

	// const修饰的局部变量
	const int c_l_a = 10;
	const int c_l_b = 10;
	cout << "局部常量c_l_a的地址为:" << (int)&c_l_a << endl;
	cout << "局部常量c_l_b的地址为:" << (int)&c_l_b << endl;

	system("pause");
	return 0;
}

运行结果说明局部变量、局部常量与其他的地址差异明显,它们不在代码区内。

栈区

栈区由编译器自动分配和释放,主要存放函数的参数值和局部变量。栈区的数据在函数执行完后自动释放,因此不要返回局部变量的地址。如下图代码,当执行输出后,第二次输出时数据就已经释放了,所以会输出一个乱码。

#include<iostream>
using namespace std;

int* func() {
int a = 10;
return &a; // 返回局部变量的地址
}

int main() {
int* p = func();
cout << *p << endl; // 第一次可以打印正确的数字
cout << *p << endl; // 第二次数据不再保留
system("pause");
return 0;
}

堆区

堆区由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。在C++中,主要利用new在堆区开辟内存。delete用于提前释放new的内存

而我们自己new的数据就会直到程序运行结束后才会释放。

注:指针本质上也是局部变量,放在栈上。指针保存的数据放在堆区。

#include<iostream>
using namespace std;

int* func() {
//利用new关键字在堆区开辟内存
	int* p = new int(10);
	return p; // 
}

int main() {
	int* p = func();
	cout << *p << endl; 
	cout << *p << endl; 
    //释放堆区内存
    delete p;
    cout << *p << endl; 
	system("pause");
	return 0;
}

new运算符

创建整型:new int( )   创建数组:new int[ ]

#include<iostream>
using namespace std;
//1.new的基本语法
int* func() {
	//在堆区创建整型数据
	//new返回是 该数据类型的指针
	int* p = new int(10);
	return p; // 
}
void test01()
{
	int* p = func();
	cout << *p << endl;
	delete p;
	cout << *p << endl;
}
//2、在堆区利用new开辟数组
void test2()
{
//创建10整型数据的数组,在堆区
int* arr = new int[10];
for (int i = 0; i < 10; i++)
{
	arr[i] = i + 100;
  }
for (int i = 0; i < 10; i++)
{
	arr[i] = i + 100;
}
//释放堆区的数组
//如果不加[],只会释放一个数据
delete[]arr;
}

2.引用

2.1基本使用

作用:给变量起别名

语法:数据类型 &别名=原名

#include<iostream>
using namespace std;
int main()
{
int a=10;
int &b=a;
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
b=20;
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
system("pause");
return 0;
}
/*输出结果:
a=10
b=10
a=20
b=20
*/

2.2 注意事项

1.引用必须初始化(int &b 不可以,int &b=a正确)

2.初始化后就不可以再当其他变量的别名了

2.3 引用做函数参数

作用:函数传参时,可以利用 引用 让形参修饰实参,简化指针修改实参。

#include<iostream>
using namespace std;
//交换函数
//01 值传递
void swap1(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;
}
//02 地址传递
void swap1(int*a, int*b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}
//03 引用传递
void swap3(int &a, int &b)
{
	int temp = a;
	a = b;
	b = temp;
}
int main()
{
	int a = 1;
	int b = 2;
	swap3(a, b);
	system("pause");
	return 0;
}

引用传递相当于把主函数里的a和b放到引用函数中进行交换。

2.4 引用做函数返回值

X64代码无错误,X86局部变量作为返回值的函数只能输出一回,之后就被释放。

#include<iostream>
using namespace std;
//局部变量作为返回值
int& arr()
{
	int a = 10;
	return a;
}
//静态变量作为返回值
int& arr2()
{
	static int a = 10;
	return a;
}
int main()
{
	int& a = arr();
	cout << a << endl;
	cout << a << endl;
	int& b = arr2();
	cout << b << endl;
	cout << b << endl;
	arr2() = 1000;
	cout << arr2() << endl;
	cout << arr2() << endl;
	system("pause");
	return 0;
}

2.5 引用的本质

本质:在C++内部实现是一个指针常量(int* const a)

#include<iostream>
using namespace std;
void func(int& ref)
{
	ref = 100;//ref是引用,转换为*a=100
}
int main()
{
	int a = 10;
	//自动转换为 int* const ref = &a;指针常量是指指针指向不可更改,也说明为什么引用不可更改
	int& ref = a;
	ref = 10;//内部发现ref是引用,自动帮我们转换为:*ref=10;
	cout << ref << endl;
	cout << a << endl;
	func(a);
	cout << ref << endl;
	cout << a << endl;
	return 0;
	
}

结论:C++推荐使用引用技术,因为语法方便,引用本质是指针常量

2.6  常量引用

const int& 就相当于 const const int*,地址唯一且值唯一

#include<iostream>
using namespace std;
//引用使用的场景,通常用来修饰形参
void func(const int& ref)
{
	cout << ref << endl;
}
int main()
{
	//int& a=10;错误 引用本身需要一个合法的内存空间。
	//加入const就可以了,编译器优化代码,int temp=10;const
	const int& a = 10;
	//加入const后不可以修改变量
	cout << a << endl;
	//函数中利用常量引用防止误操作修改实参
	int a = 10;
	func(a);
	return 0;
	
}

3 函数高级

3.1 函数的默认参数

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

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

int func2(int a, int b=10, int c=20)
{
	return a + b + c;
}
//返回 40
func2(10);
//返回50
func2(10,20);

如果某个位置参数有默认值,那么这个位置往后的参数都应该有默认值

下图代码是错误的

int func3(int a, int b=10, int c)
{
	return a + b + c;
}

如果函数声明有默认参数,函数实现就不能有默认参数

int func(int a,int b);
int func(int a=10,int b=10)
{
}

//或者
int func(int a=10,int b=10);
int func(int a,int b)
{
}

3.2 函数的占位参数

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

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

现阶段占位参数存在意义不大

void func(int a,int){}

func(10,10);

占位参数还可以用到默认参数

void func(int a,int =10){}

func(10);

3.3 函数重载

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

需要的条件:1.在同一个作用域

                      2.名称相同

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

注意:函数的返回值不可以作为函数重载的条件

3.4 函数重载的注意事项

   1.引用作为函数重载的条件

int func(int& a)
{
	return a;
}
int func(const int& a)
{
	return a;
}
int main()
{
	int a = 10;
	// int& a=a;
	func(a);
	// const int& a=10
	func(20);

}

2.函数重载遇到函数默认参数

int func(int a){}

int func(int a,int b=10){}
//此时会报错,因为这两个函数都适用,会出现二义性
func(10);
//这个就不会报错了
func(10,20);

 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐