C++程序在执行时,将内存大方向划分为4个区域
内存分区的意义:
不同区域存放的数据,赋予不同的生命周期,给我们更大的空间和灵活度编程。
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:
存放CPU执行的机器质量
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局变量和静态变量存放在此
全局区还包含了常量区,字符串常类和其它常量也存放在此
该区域的数据在程序结束后由操作系统释放
/*
定义在外部的变量是全局变量
局部变量只在其函数内生效,外部引用需重新定义
*/
int b = 5;
//这是const修饰的全局变量,全局常量
const int e = 2;
int main() {
//这种定义在函数里面的就是局部变量
int a = 10;
//分别打印全局变量和局部变量的地址,发现其存储地址不在一个区间
//需要注意的是,全局变量定义之后,其地址不会变动
cout << "a的内存地址:" << int(&a) << endl;
cout << "b的内存地址:" << int(&b) << endl;
//全局区不仅存放全局变量,还有静态变量,常量
//在变量前加上staitc则为静态变量
//可以看到,静态变量和全局变量的存放地址是在一个区间的
static int c = 15;
cout << "c的内存地址:" << int(&c) << endl;
//常量,有字符串常量和const修饰常量之分;其内存地址与全局变量相近
//字符串常量
cout << "字符串常量的内存地址:" << int(&"hello world") << endl;
cout << "const全局常量的内存地址:" << int(&e) << endl;
//const修饰的变量,又分为修饰全局变量和修饰局部变量
//修饰全局变量
const int f = 10;
cout << "const全局常量的内存地址:" << int(&e) << endl;
//局部变量
//存放地址与局部变量相近
const int f = 10;
cout << "const局部常量的内存地址:" << int(&f) << endl;
}
栈区数据由编译器自动分配和释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
示例:
//返回一个int类型的指针
int* func(int b) {
b = 100; //形参数据也存放在栈区
int a = 10; //局部变量,存放在栈区,栈区的数据在函数执行完后自动释放
return &a; //返回局部变量的地址(释放后再使用属于非法操作)
}
int main() {
int * p = func();
cout << *p << endl; //第一次可以打印,编译器做了一次保留
cout << *p << endl; //乱码,数据不再保留
system("pause");
return 0;
}
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
示例:
int* func() {
//利用new关键字将数据开辟到堆区
//括号内为定义的值,返回值为所创建内存地址,用指针存放
//指针本质也是局部变量,放在栈区,指针保持的数据是放在堆区
int * p = new int(10);
return p;
}
int main() {
int *p = func();
cout << *p << endl;
//堆区数据由程序员管理,不主动释放就不会释放
cout << *p << endl;
system("pause");
return 0;
}
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete。
语法:new 数据类型(数据的值)
利用new创建的数据,会返回该数据对应的类型的指针
示例:
int * func() {
//在堆区创建整型数据,返回该数据类型的指针,其余类型数据同理
int * p = new int(1);
return p;
}
void OutPut() {
int * p = func();
cout << *p << endl;
//多次打印,检验编译器是否释放内存,结论是编译器不会自动释放
cout << *p << endl;
cout << *p << endl;
//利用关键字delete释放内存
delete p;
//释放后,没有权限访问相关内存地址,运行至此报错
//cout << *p << endl;
}
void OutPut2() {
//在堆区创建一个有10个元素的整型数组,括号中的10代表元素个数
int * arr = new int[10];
//赋值
for (int i = 0; i < 10; i++) {
arr[i] = i + 100;
}
for (int i = 0; i < 10; i++) {
cout << arr[i] << endl;
}
//释放数组内存
delete[] arr;
}
int main() {
OutPut();
OutPut2();
system("pause");
return 0;
}
作用:给变量起别名
语法:数据类型 &别名 = 原名
示例:
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;
}
int main() {
int a = 10;
int &b = a;
//int &c; //报错,“变量c需要初始值设定项”
int a_1 = 5;
//int &b = a_1; //报错“b重定义,多次初始化”
int &c = a; //变量可以拥有多个别名
c = b; //赋值操作,不是更改引用
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
system("pause");
return 0;
}
作用:函数传参时,可以利用引用的技术让形参修饰实参
有点:可以简化指针修改实参
示例:
void Swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 1;
int b = 2;
Swap(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
原理:引用可以理解为给变量起别名,用引用的方式来接收,等同于拿到了实参,可以对其进行修改。
这里跟指针传递做一个比较:
void Swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
作用: 引用可以作为函数的返回值存在
注意: 不要返回局部变量引用
用法: 函数调用作为左值。
示例:
int& func_1() {
int a = 10; //局部变量
return a;
}
int& func_2() {
static int a = 5; //静态变量存放与全局区,内存在程序结束后释放
return a;
}
int main() {
int &rec = func_1();
cout << "rec = " << rec << endl; //第一次打印,编译器保留一次,可打印结果
cout << "rec = " << rec << endl; //第二次打印结果非法,变量内存已释放
int &rec_2 = func_2();
//打印多次得到目标结果,因为静态变量在程序结束后才释放
cout << "rec_2 = " << rec_2 << endl;
cout << "rec_2 = " << rec_2 << endl;
cout << "rec_2 = " << rec_2 << endl;
//如果函数的返回值是引用,这个函数可以作为左值
//相当于利用别名对变量赋值
func_2() = 100;
cout << "rec_2 = " << rec_2 << endl;
cout << "rec_2 = " << rec_2 << endl;
system("pause");
return 0;
}
本质:引用的本质在C++内部实现是一个指针常量
//发现是引用,转换为int* const ref = &a;
vpod func(int& ref) {
ref = 100; //ref是引用,转换为*ref = 100
}
int main() {
int a = 10;
//自动转换为int* const ref = &a;指针常量是指针指向不可改,也说明为什么引用不可更改
int& ref = a;
ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;
cout << "a:" << a << endl;
cout << "ref:" << ref << endl;
func(a);
return 0;
}
结论:C++推荐使用引用技术,语法方便,本质是指针常量,但是所有的指针操作编译器都帮我们做了
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
示例:
#include
using namespace std;
int main() {
int a = 10;
//int & ref = 10;写法错误,引用不能直接指向内存中的值
const int & ref = 10; //这是正确写法
//前部加入const修饰之后,系统做出以下修改:
//int temp = 10;
//const int & ref = temp;
//ref = 5;加入const修饰之后,ref变为只读模式,不可修改
return 0;
}
#include
using namespace std;
#include
//形参为引用,会修改实参的值
//void ShowValue(int & val) {
//加入const修饰,对形参的改动会报错,防止误操作
void ShowValue(const int & val) {
//val = 50;
cout << "val = " << val << endl;
}
int main() {
int a = 10;
ShowValue(a);
cout << "a = " << a << endl;
system("pause");
return 0;
}
在C++中,函数的形参列表中的形参是可以有默认值的
语法:返回值类型 函数名 (参数 = 默认值) {}
示例:
#include
using namespace std;
void Function(int a, int b, int c = 3) {
cout << a + b + c << endl;
}
int main() {
Function(1, 2);
system("pause");
return 0;
}
使用要点:
C++中函数的形参列表可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型) {}
示例:
//第二个形参就是占位参数,在函数中暂时用不到,起占位作用
//调用函数时,必须给所有形参传参
//如果占位参数有默认参数,则可以不传
void Function(int a, int = 10) {
cout << "Hello world" << endl;
}
int main() {
Function(10)
return 0;
}
作用:函数的名称可以相同,通过形参的不同来区分,提高复用性
函数重载满足条件:
注意:函数的返回值不可以作为函数重载的条件
void Function() {
cout << "Func1" << endl;
}
void Function(int a) {
cout << "Func2" << endl;
}
void Function(double b) {
cout << "Func2" << endl;
}
void Function(int c, double d) {
cout << "Func2" << endl;
}
以上四个函数名称都相同,但可以通过传入的参数来区分。
但是以下写法是错误的,理由是返回值不能作为函数重载的条件
void Function() {
cout << "Func5" << endl;
}
int Function(int a) {
cout << "Func6" << endl;
return 0;
}
//都是引用作为形参,const修饰与不修饰为两个类型,可以区分
void Function(int & a) {
cout << "int & a calling" << endl;
}
void Function(const int & a) {
cout << "const int & a calling" << endl;
}
int main() {
int a = 10;
//调用引用方法
Function(a);
//调用const修饰引用
Function(10);
system("pause");
return 0;
}
注意:以下写法具有二义性,无法判别调用哪个函数,应尽量避免。
void Function(int a, int b = 10) {
cout << "func1" << endl;
}
void Function(int a) {
cout << "func2" << endl;
}
int main() {
Function(10);
system("pause");
return 0;