C++的虚析构函数
只要你允许一个类拥有子类,就一定要把析构函数写成虚函数,否则没有人能安全的扩展这个类。
如果用基类指针来引用派生类对象,那么基类的析构函数必须是virtual的,否则C++只会调用基类的析构函数,不会调用派生类的析构函数。
利用虚函数可实现多态,从基类指针调用分派到子类的函数
唯一的差别是,每个析构函数结束时会自动(隐含的)调用上父类的析构函数,而普通虚函数并不会。
C++的类型转换
double value = 5.25;
//c风格的转换
double a = (int)value+5.3;
//c++风格的类型转换
double s = static_cast<int>(value)+5.3;
C++风格,四种主要的cast:static_cast,dynamic_cast,const_cast,reinterpret_cast共四种主要的cast
必须认识到的是,它们不做任何C风格类型转换不能做的事情。即它们可能会做其他的事情,但是实际的结果也只是一个成果的类型转换而已。C风格的强制转换可以实现所有这些(语法糖),对应的,cpp的转换可能比c的要慢,因为他们会做一些额外的事情,但是cpp风格,一方面方便代码阅读,另外一方面也可以减少一些类型转换错误,因为编译器会帮我们检查一下,会让你的代码更加可靠。
在静态类型转换的情况下(static_cast),它们还会做一些其他的编译时检查,看看这种转换是否真的可能;reinterpert_cast则如单词reintpret的重新解释的意思一样,把这段内存重新解释成别的东西。所以dynamic_cast是一个很好的方法,来查看转换是否成功。它与运行时类型信息RTTI(runtime type information)紧密联系,它会做运行时检查。const_cast是用来添加或移除const修饰符的,你可以用它来隐式的添加const,但大部分情况下是用来移除const的
条件与操作断点
因为bug复现可能会产生一些困难(在大型项目中),所有如果你希望再设置一个条件断点以debug,不能再在代码里面添加一个if+断点然后重新编译,因为你可能不会再进入那个状态了,而且如果这种事情比较多,我们不能每次想要加一个条件断点就改代码重新编译运行——这太麻烦了。
右键点击断点,选择条件
在操作里面可以设置打印你的操作到屏幕,大括号里面的是你要打印的变量,前面的是文字。当然,你也可以通过在程序里面添加代码来打印,但是这样子不需要重新编译也不需要给自己的代码加一些乱七八糟的东西——一会你还要删了它。
而且你还可以设置条件,就像把输出代码放在if语句里面一样。
对于一些大型项目,这真的很有用。
现代C++中的安全以及如何教授
C++中说的安全是什么意思?
安全编程,或者说在编程中,我们希望降低崩溃,内存泄漏,非法访问等问题。
用于生产环境使用智能指针,用于学习和了解工作积累,使用原始指针,当然,如果你需要定制的话,也可以使用自己写的智能指针。
C++的预编译头文件
没啥好说的,就是预编译成pch二进制文件以大幅度提升速度,因为不这样做编译器会重复编译所有引入的头文件,即,如果你引用了两个相同的头文件,他就会编译相同的代码两次 。唯一要提的就是,我在yengine中添加了pch.h和pch.cpp,貌似预编译后所有cpp文件都必须要包含pch.h才行,否则会报错。
还要就是,不要将频繁更改的文件放入预编译头文件中。应该放进去是标准库,window的api等等经常使用而你又不会更改的东西。
pch.cpp设置:
项目设置
C++的dynamic_cast
.dynamic_cast是专门用于沿继承层次结构进行的强制类型转换,并且dynamic_cast只用于多态类类型。
#include"pch.h"
class Entity {
};
class Player :public Entity {
};
class Enemy :public Entity {
};
int main()
{
Player* player = new Player();
Entity* e = player;//父类等于子类是方便的
Player* p = e;//儿子等于父亲就报错了,因为其实你不知道e是player还是enemy,除非强制转换,
//但是这就比较危险了,你真的确定它是你所认为的类型吗?
}
#include"pch.h"
class Entity {
virtual void PrintName(){}//dynamic_cast只接受多态类型,所有要让父类有虚函数
};
class Player :public Entity {
};
class Enemy :public Entity {
};
int main()
{
Player* player = new Player();
Entity* e = player;//父类等于子类是方便的
Player* p = dynamic_cast<Player*>(e);//这样子如果e不是player了,就会返回Null,这样子就不怕出现危险了
}
它是怎么知道你是错的还是对的呢?
是因为RTTI
:dynamic_cast与运行时类型信息RTTI(runtime type information)紧密联系。它会做运行时检查。它存储我们的所有类型的运行时类型信息,这是增加开销的,但它可以让你做动态类型转换之类的事情。
这里有两件事需要考虑:
首先,RTTI增加了开销,因为类型需要存储更多关于自己的信息,其次,dynamic_cast也需要时间,因为我们需要检查类型信息是否匹配,这个实体是玩家还是敌人,是什么类型的?
我们也可以在代码中关闭运行时类型信息,如果我们不需要它的话:
但要注意,强制类型转换会产生成本,所以如果你只想优化,如果你想要编写非常快的代码,你可能会想要避免这种情况。
C++中的benchmarking(如何衡量代码性能)
#include<iostream>
#include <memory>
#include<chrono>
class Timer
{
public:
std::chrono::duration<float> duration;
Timer()
{
m_StartTimepoint = std::chrono::high_resolution_clock::now();
}
~Timer()
{
Stop();
}
void Stop()
{
auto endTimepoint = std::chrono::high_resolution_clock::now();
auto start = std::chrono::time_point_cast<std::chrono::microseconds>(m_StartTimepoint).time_since_epoch().count();
auto end = std::chrono::time_point_cast<std::chrono::microseconds>(endTimepoint).time_since_epoch().count();
auto duration = end - start;
double ms = duration * 0.001;
std::cout << duration << "us (" << ms << "ms)\n";
}
private:
std::chrono::time_point<std::chrono::high_resolution_clock> m_StartTimepoint;
};
int main()
{
int value = 0;
{
Timer timer;
for (int i = 0; i < 100000; i++)
{
value += 2;
}
}
std::cout << value << std::endl;
__debugbreak();//导致当前进程中发生断点异常。 这样,调用线程就可以向调试器发出信号来处理异常。
std::cin.get();
}