Day 08 C++面向对象编程 之 内存+引用+函数(高级)
Day 08 C++面向对象编程 之 内存+引用+函数高级
一、内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区的意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。
1. 程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序之前分为两个区域:
代码区:
- 存放CPU执行的机器指令;
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可;
- 代码是只读的,使其只读的原因是防止程序意外的修改了它的指令。
全局区:
- 全局变量和静态变量存放于此;
- 全局区还包含了常量区,字符串常量和其他常量(const 修饰的全局变量)也存放于此;
- 该区域的数据在程序结束后由操作系统释放。
//全局变量
int b = 6;//写在函数体外的变量
//const修饰的全局变量
const int d = 7;
{...
//普通局部变量
int a = 7;//写在函数体内的变量都是局部变量
//静态变量
static int c = 7;//在普通变量前面 + static,属于静态变量
//常量
//字符串常量
cout<<(int)&"hello"<<endl;//只要是双引号引起来的就是字符串常量
//const修饰的变量
//const修饰的局部变量
const int a1 = 7;
...
}
//都可以用取地址符查看各者的地址进行比对
不再全局区的数据:局部变量、const 修饰的局部变量(局部常量)
全局区的数据:全局变量、静态变量、常量(字符串常量、const 修饰的全局变量)
2. 程序运行后
栈区:
由编译器自动分配释放,存放函数的参数值、局部变量等;
//栈区的数据
int* func(int b)//形参的数据也会放在栈区
{
b = 8;
int a = 7;//局部变量
//局部变量存放在栈区,数据在函数执行完后自动释放
return &a;//返回局部变量的地址(错误)
}
int main()
{
int*p =func(1);
cout<<*p<<endl;//第一次可以打印正确的数字,因为编译器做了保留
cout<<*p<<endl;//第二次不会再保留
return 0;
}
一般不返回局部变量的地址,栈区开辟的数据由编译器自动释放。指针本身也是局部变量,保存在栈区,
3. new操作符(堆区)
堆区:由程序员分配释放,若程序员不释放,在程序结束时由操作系统回收;C++中主要利用new在堆区开辟内存
int* func()
{
int* a = new int(10);//10表示数据a的初始值的10,新开辟了一个地址,用指针接收这个地址并返回
return a;
}
int main()
{
int* p = func();
cout<< *p <<endl;//10
system("pause");
return 0;
}
指针保存的数据保存在堆区,但是指针本身是局部变量,保存在栈区。堆区的数据,只要程序员不释放或者程序关闭,就不会删除。
4. 释放堆区的数据
释放利用操作符delete。new 数据类型。利用new创建的数据,会返回该数据对应的类型的指针
//new的基本语法
int* func()
{
//在堆区创建整型数据
int* p = new int(10);//new返回的是该数据类型的指针
return p;
}
void test01()
{
int* p = func();
cout<<*p<<endl;//10
cout<<*p<<endl;//10
//释放new创建的数据
delete p;
cout<<*p<<endl;//报错,已经被释放,无法进行非法访问
}
//在堆区利用new开辟数组
void test02()
{
//创建10整型数据的数组在堆区
int* A = new int[10];//创建数组,返回的是首地址
for(int i = 0;i<10;i++)
{
A[i] = i;//给10个数组元素赋值
cout<<A[i]<<endl;//输出
}
//释放堆区的数组
delete[] A;//释放数组时要加中括号告诉系统释放的是一个数组
}
释放数组时要加中括号[]告诉系统释放的是一个数组。
二、引用
1. 引用的基本使用
作用:给变量起别名。数据类型 &别名 = 原名
int a = 7;
int &b = a;//引用起别名
cout<<a<<endl;//7
cout<<b<<endl;//7
b = 6;//修改的是同一块内存
cout<<a<<endl;//6
cout<<6<<endl;//6
若利用别名对数据进行修改,则原来的值也会改变,因为两个名字对应的是同一块内存
2. 引用的注意事项
**引用必须初始化,**引用在初始化后,不可以改变。
int a = 7;
int &b ;//错误,没有初始化
int &b = 10;//错误
const int &b = 10;//正确 (解释在下方常量引用部分)
int &b = a;//正确,初始化
int c = 6;
int &b =c;//错误
b = c;//正确,赋值,改掉b和a的值为c的值6
初始化后,就是它(a)的了,不可以对其进行改变,变成别人(c)的是三心二意是不行的。
引用初始化时,必须指向一个合法的地址空间,而不是一个常量,比如10。但是加const之后可以
别名可以和原名一样
3. 引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰(修改)实参。可以简化指针并修改实参。
//1.值传递
void swap01(int a;int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
//2.址传递
void swap02(int* a;int* b)//指针
{
int temp = *a;//*解引用得到值
*a = *b;
*b = temp;
}
//引用传递
void swap03(int &num1,int &num2)//取别名
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 7;
int b = 6;
swap01(a,b);//值传递,形参改变,不改变实参的值。
cout<<a<<endl;//7
cout<<b<<endl;//6
swap02(&a,&b)//地址传递,形参改变,实参也跟着改变
cout<<a<<endl;//6
cout<<b<<endl;//7
swap03(a,b);//引用传递,,形参改变,实参也会改变(形参修饰实参)
cout<<a<<endl;//6
cout<<b<<endl;//7
return 0;
}
4. 引用做函数返回值
作用:引用是可以作为函数的返回值存在的;但是**不要返回局部变量引用,**函数调用作为左值。
//引用做函数的返回值
//1.不要返回局部变量的引用
int& test01()
{
int a = 7;//局部变量,存放在四区的栈区,函数运行完便会被释放,所以会乱码
return a;
}
//2.函数的调用可以作为左值存在
int& test02()
{
static int a = 6;//静态变量,存放在全局区,由系统释放,函数执行完不会被释放
return a;
}
int main()
{
int &ref = test01();//ref是a的别名
cout<<ref<<endl;//7,编译器做了保留
cout<<ref<<endl;//12345xxx乱码,错误
int &b = test02();
cout<<b<<endl;//6
cout<<b<<endl;//6
//做为左值
test02() = 77;//函数返回的是a本身,但是返回的是引用,所以也相当于一个别名,进行修改时,原来的都会被修改。
cout<<b<<endl;//77
cout<<b<<endl;//77
return 0;
}
5. 引用的本质
本质:引用的本质在c++内部实现是一个指针常量。
于指针常量而言,本身是一个常量,指针的常量,指针主要的是指向,所以指针常量指针的指向不可以修改,但是值可以修改。
int &b = a;
//等价于 int* const b = &a;
b = 6;//等价于 *b = 6;解引用等于6,解引用进行赋值操作
写一个引用时,就是说明写了一个指针指向它(a),而写的这个指针的一个指针常量,所以引用不可以更改,即指针指向不可以更改。
6. 常量引用
作用:常量引用主要用来修饰形参,防止误操作。在函数形参列表中,可以加 const修饰形参**,防止形参改变实参**
//常量引用
const int &b = 10;//正确
//等价于 int temp = 10;const int &b = temp;一切操作由编译器进行操作修改
b =7;//错误,+const后,不可以修改,权限为只读
void show01(const int &num)
{
num =1000;//错误,不可修改.报错
cout<<num<<endl;//77
}
void show02(int &num)
{
num =1000;//正确,别名引用修改
cout<<num<<endl;1//1000
}
int main()
{
...
int a = 7;
show01(a);
cout<<a<<endl;//77
show02(a);
cout<<a<<endl;//1000
}
三、函数(高级)
1. 函数的默认参数
在C++中,函数的形参列表中形参是可以有默认值的。语法:返回值类型 函数名(参数=默认值){}
void func(int a=1,int b =2,int c=3)
{
return a+b+c;
}
int main()
{
int a11=0;
a11=func(7,6,5);//18
a11=fun(7)//7+2+3=12,传参不够,用默认值代替
return 0;
}
传参有默认值时,若自己传了,就用传的值,若没有传,则用默认值代替。
注意事项:
- 如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值;
- 如果函数的声明有了默认参数,那么函数的定义(实现)就不能有默认参数,不然会有歧义(二义性);
2. 函数的占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置。语法:返回值类型 函数名(数据类型){}。在现阶段函数占位参数存在意义不大,但是后面可能会用到
void func(int a,int)
{
cout<<"hello"<<ebdl;
}
int main()
{
func(7,7);//可以不用,但必须要传
...
return 0;
}
可以不用,但必须要传,且传的数据类型必须和占位参数的数据类型一致。
占位参数可以有默认参数:int =10;这样写的话,传参的时候也可以不用传了。
3. 函数重载
作用:函数名可以相同,提高复用性
3.1 基本语法
函数重载必须满足条件:
- 同一个作用域下;
- 函数名称相同;
- 函数参数类型不同或者个数不同或者顺序不同
void func()
{
cout<<"函数func的调用"<<endl;
}
void func(int a)
{
cout<<"函数func的调用!!!!!"<<endl;
}
void func(char a)
{
cout<<"函数func的调用?????"<<endl;
}
void func(char a,int b)
{
cout<<"函数func的调用777777"<<endl;
}
void func(int b,char a)
{
cout<<"函数func的调用777777"<<endl;
}
int main()
{
func();//调用时,会一一比对,找到正确的一个进行调用执行
func(7);
func('c');
func('c',7);
func(7,'c');
system("pause");
return 0;
}
调用时,会一一比对(函数名、参数个数、参数类型、参数顺序),找到正确的一个进行调用执行。
函数的返回值不可以作为函数重载的条件
3.2 注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
//1.引用作为重载条件
void func(int &a)
{
cout<<"函数func(int &a)的调用"<<endl;
}
void func(const int &a)
{
cout<<"函数func(const int &a)的调用"<<endl;
}
//2.函数重载碰到函数默认参数
void func2(int a,int b=7)
{
cout<<"函数func2(int a,int b=7)的调用"<<endl;
}
void func2(int a)
{
cout<<"函数func2(int a)的调用"<<endl;
}
int main()
{
int a = 7;
func(a);//选择第一个函数
func(10);//选择第二个函数。因为int &a=10不合法;const int &a = 10合法
func2(6);//func2的两个函数都可以调用,但是会存在二义性,编译器不知道选哪个,应该避免这种情况出现
system("pause");
return 0;
}
引用时,const与没有const二者是也是函数重载,但是调用时,系统选择的是没有const的,选择可读可写的函数版本
应该避免出现函数参数默认值的出现,否则可能出现二义性。
更多推荐
所有评论(0)