一、内存分区模型。
1、在C++程序执行时,将内存大方向可以划分为4个区域。
1)代码区:存放函数的二进制代码,由操作系统进行管理的。
2)全局区:存放全局变量和静态变量以及常量。
3)栈区:由编译器自动分配释放,存放函数的参数值,局部变量等。
4)堆区:由程序员分配和释放,若不释放,程序结束时由操作系统回收。
2、内存四区意义?
不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。
二、程序运行前。
在程序编译后,生成了可执行程序,未执行该程序前分为两个区域。
1.代码区:
存放CPU执行的机器指令。 -> 其实就是0101这种数据。
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
无论执行了多少次程序,都是使用同一个代码。
代码区是只读的,使其只读的原因是防止程序意外地修改它的指令。
2、全局区:
全局变量与静态变量存放在此。
全局区还包含了常量区、字符串常量和其他常量(const修饰的变量)也存放在此。
该区域的数据在程序结束后由操作系统来释放。
#include <iostream>
using namespace std;
//创建全局变量
int g_a = 10; //全局变量 -> 全局区(数据段)
int g_b = 20;
//const修饰的全局变量
const int c_g_a = 10;
const int c_g_b = 20;
int main()
{
//全局区: 全局变量、静态变量、常量
//创建局部变量
int a = 10;
int b = 20;
cout << "&a = " << &a << endl; //局部变量: 栈区
cout << "&b = " << &b << endl;
cout << "&g_a = " << &g_a << endl;
cout << "&g_b = " << &g_b << endl;
//静态变量,在普通变量前面加static,属于静态变量
static int s_a = 10;
static int s_b = 20;
cout << "&s_a = " << &s_a << endl; //静态变量: 全局区(数据段)
cout << "&s_b = " << &s_b << endl;
//常量区
//字符串常量
cout << "string addr: " << &"helloworld" << endl; //字符串常量: 全局区(常量区)
//const修饰的变量
//const修饰全局变量,const修饰局部变量
cout << "&c_g_a = " << &c_g_a << endl; //全局常量 -> 全局区(常量区)
cout << "&c_g_b = " << &c_g_b << endl;
const int c_l_a = 10; //局部常量
const int c_l_b = 20;
cout << "&c_l_a = " << &c_l_a << endl; //局部常量 -> 栈区
cout << "&c_l_b = " << &c_l_b << endl;
return 0;
}
gec@ubuntu:/mnt/hgfs/GZ2067期/12 C++/02 引用/code$ ./type_space
&a = 0x7fff461e5fe8
&b = 0x7fff461e5fec
&g_a = 0x601070
&g_b = 0x601074
&s_a = 0x601078
&s_b = 0x60107c
string addr: 0x400c46
&c_g_a = 0x400c04
&c_g_b = 0x400c08
&c_l_a = 0x7fff461e5ff0
&c_l_b = 0x7fff461e5ff4
结论:
不在全局区:
局部变量
使用const修饰的局部变量(局部常量)
在全局区:
全局变量
静态变量 使用static修饰
常量
字符串常量
使用const修饰的全局变量(全局常量)
三、程序运行后。
栈区:由编译器自动分配释放,存放函数的参数值,局部变量等。
注意事项: 不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。
#include <iostream>
using namespace std;
int *fun(int b) //b是形式参数,是存放在栈区
{
int a = 10; //a是局部变量,是存放在栈区
return &a;
}
int main()
{
int *p = fun(1);
cout << "p = " << p << endl;
cout << "*p = " << *p << endl;
return 0;
}
结果:
p = 0 ->(空指针)
Segmentation fault (core dumped)
堆区:由程序员分配和释放,若不释放,程序结束时由操作系统回收。
在C++中主要利用new在堆区开辟空间。
#include <iostream>
using namespace std;
int *func()
{
//利用new关键词,可以将数据开辟到堆区。
int *a = new int(10);
return a; -> 返回时,堆区的数据并没有释放。
}
int main()
{
int *p = func();
cout << "p = " << p << endl;
cout << "*p = " << *p << endl;
return 0;
}
结果:
p = 0x249dc20
*p = 10
四、new操作符。
C++中利用new操作符在堆区中开辟数据。
堆区中开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针。
1、堆区的基本语法实例。
#include <iostream>
using namespace std;
int *func()
{
//在堆区创建整型数据
//new返回的是该数据类型的指针
int *p = new int(10);
return p;
}
int main()
{
int *p = func();
cout << "*p:" << *p << endl; //10
//堆区的数据由用户自己释放
delete p;
cout << "*p:" << *p << endl; //0
return 0;
}
2、在堆区利用new开辟数组。
#include <iostream>
using namespace std;
int main()
{
//创建10个整型的数组在堆区。
int *arr = new int[10]; //10代表数组由10个元素。
int i;
for(i=0;i<10;i++)
{
arr[i] = i + 100; //给10个元素赋值 100~109
}
for(i=0;i<10;i++)
{
cout << arr[i] << endl;
}
//释放数组时,需要加[]才可以。
delete[] arr;
return 0;
}
五、引用的基本使用。
1、引用作用:
给变量起别名。
2、语法。
数据类型 &别名 = 原名
#include <iostream>
using namespace std;
int main()
{
//引用基本语法:
//数据类型 &别名 = 原名
int a = 10;
//创建引用
int &b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
b = 100;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
结果:
a = 10
b = 10
a = 100
b = 100
六、引用注意事项。
1、引用必须初始化。
2、引用在初始化后,不可以改变。
#include <iostream>
using namespace std;
int main()
{
//1、引用必须初始化。
//int &c; //编译出错,引用必须初始化。 而且要引用一块合法内存空间。
//2、引用在初始化后,不可以改变。
int a = 10;
int b = 20;
//创建引用
int &c = a; //一旦初始化,就不可以更改。 (c永远都是a的别名)
c = b; //这是赋值操作,
//不是让c去做b的别名。
cout << "a = " << a << endl; //20
cout << "b = " << b << endl;
cout << "c = " << c << endl;
return 0;
}
七、引用做函数参数。
作用:函数传参时,可以利用引用的技术让形参修饰实参。
优点:可以简化指针修改实参。
#include <iostream>
using namespace std;
//1. 值传递
void mySwap01(int a,int b)
{
int temp = a;
a = b;
b = temp;
}
//2. 地址传递
void mySwap02(int *a,int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void mySwap03(int &a,int &b) -> 操作形参a,等价于操作实参a
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
/*
mySwap01(a,b);
cout << "a = " << a << endl; //10
cout << "b = " << b << endl; //20
mySwap02(&a,&b);
cout << "a = " << a << endl; //20
cout << "b = " << b << endl; //10
*/
mySwap03(a,b);
cout << "a = " << a << endl; //20
cout << "b = " << b << endl; //10
return 0;
}
八、引用做函数返回值。
作用: 引用时可以作为函数返回值存在的。
1、不要返回局部变量的引用。
2、函数调用作为左值的存在。
#include <iostream>
using namespace std;
/*
作用: 引用时可以作为函数返回值存在的。
1、不要返回局部变量的引用。
2、函数调用作为左值的存在。
*/
/*
int -> 10(本体的值)
int&-> a(本体)
*/
int& test01()
{
//1、不要返回局部变量的引用。
//int a = 10;
static int a = 10; //局部变量,函数返回时,空间就会释放。
return a; //返回的是a的本体。
}
int main()
{
int&ret = test01();
cout << "ret = " << ret << endl; //10
//2、函数调用作为左值的存在。
//test01()就是a的本体
a = 1000;
cout << "ret = " << ret << endl; //1000
return 0;
}
九、引用的本质。
本质:引用的本质在C++内存实现就是一个指针常量。
test.cpp
#include <iostream>
using namespace std;
void func(int &ref)
{
ref = 100; //内部发现ref是一个引用,自动转换 *ref = 100;
//这也解析了为什么形参的值可以修改实参。
}
int main()
{
int a = 10;
//系统会自动转换为 int *const ref = &a;
//指针常量ref,一旦把&a赋值给ref这个指针,由于是指针常量,ref只能永远指向&a
//这也解析了为什么引用初始化了值之后,不能修改。
int& ref = a;
//内部发现ref是引用,自动帮我们转换为: *ref = 20
ref = 20;
cout << "a = " << a << endl; //20
cout << "ref = " << ref << endl; //20
func(a);
cout << "a = " << a << endl; //100
cout << "ref = " << ref << endl; //100
return 0;
}
总结:
C++推荐使用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了。
十、常量引用。
作用:常量引用主要用来修改形参,防止误操作。
在函数形参列表中,可以加const修饰形参,防止形参改变实参。
#include <iostream>
using namespace std;
//在函数形参列表中,可以加const修饰形参,防止形参改变实参。
void show_value(const int &val) //如果不想形参修改实参,就在形参前面添加一个const
{
//val = 130;
cout << val << endl;
}
int main()
{
//常量引用
//使用场景: 在函数参数列表中,用来修饰形参。
//int &ref = 10; //错误
//引用必须要引用一块合法的空间,如果这样写可以的话,那么将来ref=20,肯定也是可以的,但是这样做,不就是10=20,这样肯定错误。
const int &ref = 10; //正确
/*
加了const之后,编译器将代码修改为:
int temp = 10;
const int &ref = temp;
ref -> 别名
temp -> 原名
*/
//ref = 20; //因为ref现在是一个常量引用。
cout << "ref = " << ref << endl;
int a = 170;
show_value(a);
cout << "a = " << a << endl;
return 0;
}
结果:
ref = 10
val = 170
a = 170