1.c++内存分区模型
c++程序在运行的过程中,内存会被划分为以下四个分区
代码区:程序的所有程序的二进制代码,包括注释会被放到此区
全局区:存放静态变量、全局变量、常量(字符串常量和const修饰的常量),此区的数据将在程序结束后由操作系统释放
using namespace std; //不在任何函数内的变量是全局变量 int a = 10; int b = 23; int main() { //在某个函数内的变量 int c = 12; cout << (int)&a << endl; cout << (int)&b << endl; cout << (int)&c << endl; }
栈区:用于存放局部变量、函数参数等,是由编译器自动释放和分配,所以不能让栈区返回一个地址,
#include <iostream> #include <string> using namespace std; int* f() { int a = 10; return &a;//栈区不要返回地址。局部变量是由编译器自动释放和分配 } int main() { int* p = f(); cout << *p;//10 第一次编译器会做保留 cout << *p;//不会打印10 }
堆区:存储对象(使用new操作符,后面会介绍)由程序员分配释放,若我们不释放,程序结束由操作系统释放
c++中通过new关键字将数据开辟到堆区
#include <iostream> using namespace std; int* f() { /* *1.指针本身也是局部变量,存放在栈长中,但是保存的数据在堆中 2.new关键字开辟一块堆区,返回的是该数据类型的指针 */ int *a = new int(10); return a; } int main() { int* p = f(); cout << *p;//10 cout << *p;//10 //3.堆区的数据程,序员可以通过该delete释放 delete p; //cout << *p;异常 }
如果是new一个数组
//new一个数组 int* arr = new int[10]; //释放一个数组 delete[] arr;
注意
代码区和全局区是程序编译成exe可执行文件的时候就已经有了,但是栈区和堆区是程序exe文件执行后产生的
2.为什么划分内存(内存划分意义)
将不同的数据放在不同的区域,赋予不同的生命周期,提高编程灵活程度
3.引用
使用引用给一个变量起别名
#include <iostream> using namespace std; int main() { int a = 10; /*1.定义引用的格式 数据类型 &别名=原名 *2.引用必需要初始化 *3.引用初始化后,不可更改 */ int& b = a; cout << b;//10 }
前面文章中的地址传递会修改实参,值传递不会修改实参,引用作为函数参数会修改实参,简化使用指针修改实参的复杂过程
#include <iostream> using namespace std; void swap(int &a,int &b) { int temp = a; a = b; b = temp; } int main() { int a = 10; int b = 20; swap(a,b); cout << a;//20 cout << b;//10 }
局部变量不能作为函数的返回值返回
#include <iostream> using namespace std; int& f() { int a = 10;//栈区中的局部变量在函数执行完后释放 return a; } int main() { int &a = f(); cout << a;//10 编译器保留 cout << a;//不在是10 }
如果是局部静态变量,可以返回
#include <iostream> using namespace std; int& f() { static int a = 10;//栈区中的局部变量在函数执行完后释放 return a; } int main() { int &a = f(); cout << a;//10 cout << a;//10 }
#include <iostream> using namespace std; int& f() { static int a = 10;//栈区中的局部变量在函数执行完后释放 return a; } int main() { int &a = f(); cout << a;//10 cout << a;//10 f() = 100;//如果函数的返回是一个引用,可以作为左值 cout << a;//100 cout << a;//100 }
引用本质是指针常量
int main() { int a = 1; //内部发现是引用,自动转成指针常量 int * const b=&a; int& b = a; b = 2;//内部发现是引用,自动转成*b=20; cout << a;//2 cout << b;//2 }
常量引用
#include <iostream> using namespace std; //使用const修改函数形参,防止误操作 void f(const int& a) { //a = 100;不允许修改 } int main() { int& b = 1;//引用本身需要一个合法内存空间,所以这行代码有误 int a = 10; f(a); }
4.函数相关
前面c++基础系列有关函数知识有所描述,这里补充一些函数高级知识
1.c++中函数可以有默认值
#include <iostream> using namespace std; //1.c++中函数可以有默认值,并且某个位置有了默认值,那么从这个位置开始左到右都的有默认值 int f(int a, int b = 10,int c=20) { return a + b + c; } int main() { int a = 10; //2.如果函数有默认值,当我们传值使用传递的值,不传值使用默认的 cout<<f(a);//40 cout << f(a, 20);//50 } //3.声明和实现只能有一个有默认参数 int f1(int a, int b = 10); int f1(int a, int b) { return a + b; }
2.c++函数中可以有占位参数用来占位,调用函数必需填补该位置
#include <iostream> using namespace std; //1.只写一个数据类型就是占位 void f(int a,int) { cout << "test"; } //2.占位参数可以有默认值 void f1(int a, int=10) { cout << "test"; } int main() { int a = 10; f(a, 10);//占位参必须填补 }
3.函数重载
定义:同一个作用域下,两个函数参数类型不同或者参数顺序不同或者个数不同。此时这两个函数名字可以相同。提高复用性
#include <iostream> using namespace std; void f(int a,int b) { cout << "test"; } void f(int a) { cout << "test"; } int main() { int a = 10; f(a, 10); }
注意:函数返回值不能作为函数重载的条件
4.引用也可作为函数重载条件
#include <iostream> using namespace std; void f(int &a) {//int &a=10;不合法 cout << "test"; } void f(const int &a) {//const int &a=10;合法 cout << "test111"; } int main() { int a = 10; f(a);//test f(10);//test111 }
5.函数重载遇到默认参数需要注意
void f(int a,int b=10) {//int &a=10;不合法 cout << "test"; } void f(int a) {//const int &a=10;合法 cout << "test111"; } int main() { int a = 10; f(a);//报错,出现二义性 }