本阶段是探讨C++中的核心和精髓,面向对象编程技术。
程序的内存模型
首先需要了解内存的分布,在C++中,内存共存放在四块区域:代码区、全局区、栈区、堆区
(1)代码区:存放的是函数内部的一些二进制代码,由操作系统管理;代码区是可共享的,
其中的函数里面的代码只有一份,对于被频繁执行的程序而言很方便;代码区是只读的,防止程序意外地修改了它的指令。
(2)全局区:全局区存放的是全局变量、静态变量(static修饰)、常量;该区在程序结束时由操作系统释放掉; 在函数体外部定义的变量叫全局变量;函数体内部定义的变量叫局部变量;常量与可以分为const 修饰的常量 和字符串常量
总结:程序在运行前分为代码区和全局区,代码区的特点是只读和共享;全局区存放的是全局变量、静态变量、常量;常量又可以分为 字符串常量 和const修饰的变量。
(3)栈区:栈区存放的是局部变量和函数参数值,由编译器自动分配释放;栈区需要注意的事项:不要返回局部变量的地址,因为在局部变量出作用域之后,局部变量的地址就已经被释放了会出现报错;栈区的数据由编译器管理和释放。
(4)堆区:由程序员手动分配 、手动释放,若程序员不释放,程序结束后由操作系统回收
在C++中利用new关键字在堆区开辟内存。
new运算符
语法:new 数据类型;
new出来的数据会返回一个指针类型(实际上是返回在堆区的存放的地址);由程序员手动开辟和释放,释放使用关键字 delete 变量名;
int *p = new int(10); delete p;
利用new关键字来创建数组:
int * arr = new int [10]; // 在堆区开辟一块空间来存放数组大小为10数组
delete [] arr; //释放new出来的数组
释放数组语法: delete [ ] 变量名;
内存四区的意义:不同区域存放不同的数据,赋予不同的生命周期,更灵活地编程
C++中的引用
作用:给变量名取别名; 语法: 数据类型 & 别名 = 原名 ;
注意事项: 引用必须初始化; 引用不可以更改指向(理解为 不可以在给其它的变量 取这个别名,因为这个别名已经被使用了) ,但引用可以进行赋值
int a =10;
int &b = a; //引用 必须初始化
int c =20;
int &b = c; //这是错误的,引用的指向不可以改
b = c; //这是正确的引用可以进行赋值
(1)引用做函数参数:通过地址传递形参可以更改实参的值, 引用也是一样,通过引用对实参取别名然后通过对别名的更改 从而更改实参
(2)引用做函数返回值: 不要返回局部变量的引用,函数的调用可以作为左值,本质上函数的类型设置为引用, 然后返回一个引用, 此时函数就等于这个引用,可以将函数作为左值从而进行赋值
(3)引用的本质:引用实际上是一个指针常量 int * const p = &a; 本质上是引用的一块地址,将a的地址 赋值给p(别名)指针指向不可以改, 值可以改, 理解的时只需要把引用当成别名来理解,但是要知道其本质,
(4)常量引用:对形参修饰来防止误操作,在形参面前 加上 const 修饰,形参就不能够更改。
对于引用的初始化来说 : 加上const修饰之后可以指向一个值,如: const int &b = 10;
但 int &b = 10;是不合法的,因为指向的不是合法的内存空间
函数高级
(1)函数的默认参数
语法: 函数返回类型 函数名 (参数 = 默认值 ){ }
void fun(int a =10) {}
注意:如果自己传递了数据过来 就使用自己的数据,否则就使用 默认参数
当函数中的某个参数设置了值;那么从这个参数往后,从左往右都要设置默认值
函数的声明和函数的定义 两个只能有一个由默认值
(2)函数的占位参数
语法: 函数返回类型 函数名 (数据类型) {}
void fun(int ) {}
占位参数是用来占位的,传过来的数据的数据类型必须与占位的数据类型一致。
占位参数也可以有默认值:
void fun(int = 10) {}
(3)函数重载
作用:函数名可以相同提高复用性
函数重载满足的条件:
同一个作用域; 函数名相同;函数的参数个数、类型、 顺序不同。
注意:函数返回类型不可以作为函数重载的条件
void fun()
{
}
void fun(int ) //和第一个相比 属于函数参数个数不同
{}
void fun(double) // 和第二个相比 属于函数参数类型不同
{}
void fun(int ,double)
{}
void fun(double = 3.15 , int = 4) // 和第四个相比 属于函数参数顺序不同
引用也可以作为函数重载的条件; 函数重载时碰到默认参数
void fun (int a)
{}
void fun ( int & a) // 本质上是类型不同
void test (int a , int b = 10)
{}
void test (int a){}
test (10) // 不知道该调用哪个test 因为存在二义性