//1)内存分区模型-----不同区域存放的数据,赋予不同的生命周期,灵活编程
/*分区--四区 代码区--存放函数体的二进制代码,由操作系统进行管理---只读、共享
常量区--存放常量(程序运行期间不能被改变的量)(字符串常量、const修饰的全局常量)
全局区(静态区)--存放全局变量和静态变量---程序结束之后由操作系统释放
堆区--由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
c++中主要利用new在堆区开辟内存---利用delete释放内存
注意:new返回是该类型的指针应用同类型的指针来接收
如int* a = new int(10)-创建一个数据, int* arr = new int[10]-创建一个数组
delete释放数组时应加[],如delete[] arr;
栈区--由编译器自动分配释放,存放函数的参数(形参)值,局部变量等
注意:不要返回局部变量的地址--存放在栈区,栈区的数据在函数执行完后自动释放
参考:https://www.cnblogs.com/inception6-lxc/p/8686156.html
*/
//2)引用-----起别名
/*语法 数据类型 &别名 = 原名
注意事项 1)引用必须要初始化
2)引用初始化后,不可以改变
引用做函数参数-----可以简化指针修改实参--引用传递(取代地址传递)
引用做函数的返回值 1)不要返回局部变量的引用
2)函数的调用可以作为左值
引用的本质 在c++内部实现是一个指针常量---系统内部识别别名是引用之后,自动进行解引用的操作(*)
常量引用 使用场景:主要用来修饰形参,防止误操作
注意:引用必须引一块合法的内存空间 反例: int a = 10; int &ref = 10;错误,内存空间不合法
const int & ref =10;合法,
*/
//3)函数提高
/*
1)函数默认参数 语法:返回值类型 函数名(形参 = 默认值){}
不传入值,就用默认值
注意事项 1)若某个位置有了默认参数,那么这个位置之后从左到右必须有默认参数值
2)若函数的声明有默认参数,函数的实现就不能有默认参数
-----即声明和实现只能定义一次默认参数不能重定义默认参数,不然会造成歧义
2)函数占位参数 语法:返回值类型 函数名(数据类型){}
形参列表可以有占位参数,用来做占位,调用函数时必须填补该位置
占位参数也可以有默认参数
3)函数重载------函数名可以相同,提高复用性
函数重载满足条件:
1)同一个作用域下
2)函数名称相同
3)函数参数类型不同 或者个数不同 或者顺序不同
函数的返回值不能作为函数重载条件
注意事项 1)引用作为函数重载条件--有无const情况
2)函数重载遇到函数默认参数--尽量避免,容易出现歧义性
*/
//类和对象
/*定义 class 类名
{
访问权限--private---成员 类内可以直接访问,类外不可以直接访问--子类不可以直接访问父类的私有内容
protected---成员 类内可以直接访问,类外不可以直接访问--子类可以直接访问父类中保护的内容
public---成员 类内可以直接访问,类外可以直接访问
数据成员及成员函数
}
*/
/*三大特性-----封装、继承和多态
1)封装-----意义 1)将属性和行为作为一个整体,表现生活中的事物
---类中的属性和行为都称为成员,
---属性 成员属性 成员变量 数据成员
---行为 成员函数 成员方法
2)将属性和行为加以权限控制
与struct的区别-----默认的访问权限不同
struct默认权限为共有
class 默认权限为私有
成员属性设置为私有--优点如下
1)可以自己控制读写权限
2)对于写权限,可以检测数据的有效性
2)对象的初始化和清理-----构造函数和析构函数--必须有,若自己不提供,编译器会提供一个空实现的构造和析构
构造函数-----创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
语法: 类名(形参列表){}
1)没有返回值也不写void
2)构造函数名与类名相同
3)构造函数可以有参数,因此可以发生重载
4)程序在调用对象时会自动调用构造,无需手动调用,且只会调用一次
分类: 按参数 无参构造(默认构造) 有参构造
按类型 普通构造 拷贝构造 类名(const 类名 &p)--将传入的人身上的所有属性拷贝到自己身上
调用: 1)括号法
注意1): 调用默认构造函数时不需要加()---因为编译器会认为是一个函数的声明----Person p1;
调用有参构造时,直接在括号内传入参数 如Person p2(10);
调用拷贝构造 如Person p3(&p2);
2)显示法
默认--Person p1;
有参--Person p2 = Person(10);
拷贝--Person p3 = Person(p2);
注意:Person(10)--匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
注意2): 不要利用拷贝构造函数,初始化匿名对象--编译器会认为是对象的声明--如Preson(P3)=== Person p3;
3)隐式转换法
有参--Preson p4 = 10;----等价于Person p4 = Person(10);
拷贝--Preson p5 = p4;----等价于Person p5 = Person(p4);
拷贝构造函数的调用时机
1)使用一个已经创建完毕的对象来初始化一个新对象(常用)
Person p1(20);
Person p2(p1);
2)值传递的方式给函数参数传值---值传递是拷贝一个临时的副本
3)值方式返回局部对象--返回时返回的不是原对象,而是拷贝出来的一个副本
析构函数-----对象销毁前系统自动调用,执行一些清理工作---析构代码,将堆区开辟数据做释放操作
语法: ~类名(){}
1)没有返回值也不写void
2)析构函数名与类名相同,在前面加上~
3)析构函数不可以有参数,因此不可以发生重载
4)程序在对象销毁前会自动调用析构,无需手动调用,且只会调用一次
构造函数调用规则
默认情况下,c++编译器至少会给一个类添加4个函数
--1)默认构造函数(无参,空实现);
--2)默认析构函数(无参,空实现);
--3)默认拷贝构造函数,对属性值进行值拷贝;
--4)赋值运算符 operator = 对属性进行值拷贝----若类中有属性指向堆区,做赋值时也会出现深浅拷贝的问题(赋值运算符重载实现)
构造函数调用规则:
1)若用户定义有参构造函数,c++不在提供默认无参构造,但会提供默认拷贝构造
2)若用户定义拷贝构造函数,c++不在提供其他构造函数
深拷贝、浅拷贝(面试)
浅拷贝:简单的赋值拷贝--利用编译器提供的拷贝构造函数,会做浅拷贝操作--问题(堆区的内存重复释放--自己实现拷贝构造函数,可以利用深拷贝进行解决)
深拷贝:在堆区重新申请空间,进行拷贝操作(重新在堆区创建一块内存)
总结:若内存有在堆区开辟的,需要自己实现拷贝构造函数释放堆区内存
初始化列表-----用来初始化属性
语法: 构造函数(): 属性1(值1),属性2(值2)...{}
类对象对位类成员--成员(对象成员)
构造顺序--当其他类的对象作为本类的成员,构造时先构造类对象,再构造自身(相当于胳膊腿)
析构顺序--与构造相反
静态成员--静态成员变量/函数--前面加static
静态成员函数--1)所有对象共享一个函数---可以通过类名访问--通过对象访问
-2)静态成员函数只能访问静态成员变量
-3)有访问权限
3)C++对此昂模型和this指针
1)成员变量和成员函数分开存储
--空对象占用内存空间为:1(原因:C++编译器会给每个空对象也分配一个字节空间,为了区分空对象占内存的位置--每个空对象内存地址独一无二)
--只有非静态成员变量属于类的对象上
--非静态成员函数和静态成员不属于类的对象上--只有一份函数实例
2)this指针--this指针指向被调用的成员函数所属的对象---本质是指针常量
--隐含在每一个非静态成员函数内的一种指针,不需要定义,直接使用
用途
--解决名称冲突---当形参和成员变量同名时,可用this指针来区分--类似于python中self?
--在类的非静态成员函数中返回对象本身(&引用的方式返回),可使用return *this--链式编程思想
3)空指针访问成员函数
空指针访问非静态成员属性时,指针为空访问不了非静态成员属性程序崩溃--防止乱传入指针加入this == NULL 判断,增强代码健壮性
4)const修饰成员
常函数--在成员函数()后面加const,修饰的this指针,使指针指向的值也不可修改
--void func() const {}
--常函数不可以修改成员属性
--成员属性声明时加mutable后,在常函数中可修改
常对象--在对象前面加const
--const Person p1
--常对象只能调用常函数,不可以调用普通成员函数
--成员属性声明时加mutable后,在常对象可修改
*/