回顾一下面向对象的知识点:
自己的编程习惯:
类:相当于一个数据类型的定义
对象:相当于一个数据结构的实例化
在CPP 当中可以使用struct、 class来定义一个类,区别只是:默认权限不同
注意:实际开发中,用class表示类比较多,保留 struct 可能是为了让 C 程序员好过渡
分析:看起来我们的成员函数也写在 class 当中,为什么不占用对象的内存呢?
问题:怎么知道使用成员函数的时候,成员函数要访问成员变量,怎么知道它访问的是哪一个成员变量?
答:通过 this 指针
对象的内存可以存在于 3 个地方
发明的目的:成员函数只有一份,怎么做到成员函数当中的成员变量不同。
解决问题:哪个对象调用成员函数,那么就把这个对象的地址保存下来,通过这个地址就可以找到这个对象的属性。
通过指针的方式来调用成员函数(传入的是 p 的值)
疑问:如果我们将 p 的值更改,那么通过指针访问的 this 的值是不是也会更改?
验证: p的值 == this 的值??
结论:p的值 == this 的值
注意:-858993460 其实是 0xcc, 二进制的 0xcc 其实一个中断向量。
因为栈空间是每次都要使用的,所以肯定是脏的,系统每次都会自动填充 0xcc,意义是断点,让 cpu 停下来,不要瞎运行。
定义:
我的理解:
private属性 :隐藏了类内的属性,不能让外界函数进行调用。 (对于 C 语言来说就没有这样的特性)
成员变量私有化,提供公共的getter和setter给外界去访问成员变量
栈空间(.stack)
每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间
自动分配和回收
堆空间(.heap)
代码段(.text)
数据段(.data)
目的:在程序运行过程,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存
分析:如果没有堆空间那会发生什么?
(1)假设开发植物大战僵尸,所有僵尸对象、 植物对象、子弹对象等等全部放到全局区,会发生什么?
(2)假设开发植物大战僵尸,所有僵尸对象、 植物对象、子弹对象等等全部放到栈区,会发生什么?
堆空间的申请和释放:
现在的很多高级编程语言不需要开发人员去管理内存(比如Java),屏蔽了很多内存细节,利弊同时存在
注意:
探讨一:我们刚申请堆空间的时候,平台会帮我们进行初始化吗?
答案:windows 下的 VS 并不会帮助我们进行初始化。
探讨二:我们为什么要进行初始化?
探讨三:C++ 当中有几种初始化?
int *p2 = new int( );
int *p3 = new int[3] ; // 未初始化
int *p3 = new int[3] { }; //将所有元素初始化为 0
int *p3 = new int[3] { 5 }; // 将第一个元素 初始化为 5
另外一种情况:我们写一个空的、无参的构造函数,看看会不会进行初始化?
结论:
如果没有定义构造函数
如果自定义了构造函数
有一种做法,不管什么情况,都会初始化为 0
特性:
注意:通过 malloc 分配的对象不会调用构造函数, 通过 new 分配的对象会调用构造函数。
一个广为流传的、很多教程\书籍都推崇的错误结论:默认情况下,编译器会为每一个类生成空的 无参 的构造函数
正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数
其中的一种情况:
还有哪些特定的情况?以后再提
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eH4FErfb-1621958053575)(C:\Users\duxingdong\AppData\Roaming\Typora\typora-user-images\image-20210524222001649.png)]
可以发现,少了两个构造函数。这两个被看作是 函数声明。
特点:
无返回值(void都不能写)。
函数名以~开头,与类同名。
无参,不可以重载,有且只有一个析构函数。
注意:
我们知道在对象销毁的时候,会调用析构函数,那么我们在析构函数当中需要做哪些内存清理的工作?
(1)我们用不用清理对象内部的成员变量?
答:不需要,因为在函数结束的时候,我们的对象会被销毁了,栈空间会自己进行回收。
疑问:那么我们析构函数到底要做什么内存清理工作呢?
案例:
#include
using namespace std;
struct Car
{
int m_price;
Car()
{
cout << "Car::Car() " << endl;
}
~Car()
{
cout << "Car::~Car() " << endl;
}
};
struct Person
{
int m_age;
Car *m_car; // 包含一个指针,为了使用堆内存
// 用来做一些 初始化的工作
Person()
{
m_age = 0;
m_car = new Car(); // 初始化指针
cout << "Person::Person()" << endl;
}
// 用来做一些 内存清理的工作
~Person()
{
cout << "Person::~Person()" << endl;
}
};
int main()
{
{
Person p1;
}
getchar();
return 0;
}
分析:
运行结果:可以发现car 的对象被定义了,但是没有执行它的析构函数。
Car::Car( )
Person::Person( )
Person::~Person( )
因为:car 对象并没有销毁,所以会造成内存泄露。
解决:在析构函数当中销毁(因为人都不在了,要汽车也没用了)
~Person()
{
delete m_car;
cout << "Person::~Person()" << endl;
}
// 运行结果
Car::Car()
Person::Person()
Car::~Car()
Person::~Person()
如果不在析构函数当中清理,在外面清理,那就太麻烦了。
总结一句话:在类内定义的变量,那么就应该在类内的析构函数进行回收
什么是命名冲突呢?
解决办法:
class zhangsan_Person
{
};
class lisi_Person
{
};
使用 using namespace 命令
解析 :using namespace std; 命令
如果没有这条指令:很多标准函数,我们必须添加前缀
std :: cout << "hello_world" << std :: cin;
如果使用了这条指令,我们就不需要添加前缀了
using namespace std;
cout << "hello world" << endl;
所以肯定有一个文件在 std 的命名空间当中实现了很多标准函数
namespace std
{
cout( )
{
}
cint ( )
{
}
}
同时使用两个命名空间,可以编译通过吗?
解决:可以使目标明确一点,前面加上作用域,就不会有二义性了。
命名空间的合并:
应用场景:
含义:可以让子类拥有父类的所有成员(变量\函数)
问题:如果两个类当中有很多相同的特性,也有很多自己的特性。
关系描述:
明确一点:子类对象到最后确实占用空间很大,因为包含了基类的所有成员。
问题:如果基类的成员没有用到,那么子类的空间不就浪费了吗?
如果整个程序的所有对象,都没有用到,那确实是浪费了。
但是如果有一个对象用到,那就不能算是浪费。
这个不是 C++ 本身语法的问题,而是写代码的人的问题,既然所有对象都用不到,那么为什么要将他设计进去。
首先 成员访问权限 有三种:
其次 子类继承父类的方式 也有三种:
对继承方式的理解:对所继承的成员变量,进行权限修饰。
总结:GoodStudent 能不能访问父类当中的成员取决于两点:
一般我们写 public 继承:
public 继承可以很好的将父类原有的权限继承下来。
因为取权限的最小值,所以原来是 protected ,继承之后还是 protected
问题:既然我们子类没有访问父类 private 成员的权限,那么这个成员还会被继承下来吗?这个成员还占用内存吗?
答:访问权限不影响继承的内存布局
问题:既然我们不能直接访问,那么留下这个成员变量还有什么意义?
发明目的:顾名思义,初始化列表,就是为了初始化发明的。
特点:
怎么理解更加便捷呢?
m_age、 m_height的值是多少?
答案: 23, 183。
总结:
我们想:如果我们先初始化 m_height 的话,那么 m_age 就不会是乱码了。
尝试一:更改初始化列表的变量的顺序
尝试二:更改 成员变量声明的顺序:
总结:
可以实现一种效果:仿佛有三种构造函数
注意:如果函数声明和实现是分离的(声明在类里面,实现在类外面)