常见未定义行为(将函数改成constexpr就能在编译时检测出)
左移只能左移0-31位
constexpr int func(int i)
{
return 1<<i;
}
int main()
{
constexpr auto _1 = func(-1);
return 0;
}
数组越界
int a[32]={};
a + 33;//越界了,虽然没使用这个结果 g++不报错,clang报错
//a+32是可以获取但不能访问的,这是为了便于数组遍历。而a+33是不能获取也不能访问的
忘记delete:因此constexpr可以检测是否发生内存泄漏
重复delete
未初始化
push_back导致迭代器失效
4种强制类型转换
-
静态转换static_cast
编译时期完成,可以用于基本类型间转换,不能用于基本类型的指针间转换。还可以用于基类与派生类指针之间的转换(上转安全、下转危险)、void*的转换 -
动态转换dynamic_cast
运行时期完成,用于带虚函数的基类指针下转型。RTTI信息辅助判断是不是能转,如果不能,得到的结果是空指针。 -
常量转换const_cast
去掉表达式的const属性 -
重解释转换reinterpret_cast
各种类型指针之间的强转重新解释
#include <iostream>
struct A
{
virtual void fun(){};
int m=2;
};
struct B: public A{
void fun()
{
std::cout<<"xcx"<<std::endl;
}
int n=3;
};
int main()
{
//1.const_cast
//去除const
const char * a = "nh";
char* b = const_cast<char*>(a);
//2.static_cast
//基本类型转换
std::string s = static_cast<std::string>(b);
//类对象转换
B bb;
A aa = static_cast<A>(bb);//上转安全
//类指针转换
A* aa_ptr = static_cast<A*>(&bb);
//void*强转
void * v = &b;
char* vv = static_cast<char*>(v);
//3.reinterpret_cast不同类型强转:不能用于constexpr常量表达式。同理,C风格的指针强转也是调用reinterpret_cast,也是不行的
char cc = 'a';
char* cc_ptr = &cc;
int* cc_int = reinterpret_cast<int*>(cc_ptr);
std::cout<<*cc_int<<std::endl;//609
//char占一个字节,8位,对应ascill码为97(16进制61),存储为01100001,
//强转为int后,int占4字节,这串二进制作为低位,高位是后面地址存储的值
//这里后面地址存储的值是02 00 00,即为00 00 02 61:1001100001,转为10进制就是609
//4.dynamic_cast在运行时将父类指针(引用) 转换为 子类指针(引用):需要父类有虚函数
A* aaa_ptr = new B();
B* bb_ptr = dynamic_cast<B*>(aaa_ptr);
return 0;
}
智能指针的转换
static_pointer_cast、dynamic_pointer_cast、const_pointer_cast、reinterpret_pointer_cast
虚函数表
动态多态是怎么实现的:
如果一个类写了虚函数,那么编译器处理它时会生成虚函数表。类进行实例化时,对象中不仅有该类的数据成员,还会有一个虚函数表指针,同一个类实例化出来的对象使用的是同一个虚函数表。
当实例化子类对象时,子类从父类继承了虚函数,子类有一个自己的虚函数表,只不过该虚函数表里面的函数指针指向的函数和父类暂时一样,这样子类就能访问父类函数。
父类指针指向子类对象时:通过子类对象的虚函数表指针找到虚函数表,进而通过偏移在表中找到子类的函数。
虚函数表指针是用来指向该类虚函数表的,每个子类都有自己的虚函数表指针。
虚函数表就是带有函数指针的一个结构体,编译器为每个子类生成虚函数表的对象,因为具体实现的虚函数不同,使得虚函数表中的函数指针成员指向了不一样的实现函数,表现出来的结果就是调用结果不一样。
多继承导致的问题:
多继承就多引入一个虚函数表指针,就多一个虚表,由于获取类成员地址时是根据对象地址加一个虚表指针的偏移量,而这里有多个虚表指针,如果使用的虚表指针不对,就不能正确取到类成员。解决方法是将对象指针强转为 类成员所在的父类 类型(上转是安全的),这样进行偏移就是正确的。
总而言之,多继承时,使用static_cast进行上转,dynamic_cast进行下转,而不是使用()进行强转。
奇异递归模板模式(CRTP)
静态多态(编译期多态)的实现有两种:函数重载、函数模板,CRTP是使用函数模板实现静态多态,比动态多态更快。
CRTP实现了颠倒继承,也就是通过父类向子类添加功能。
#include<iostream>
template<class T>
class Base{
public:
//准备让子类来具体实现的函数:实现静态多态,不使用virtual
void fun(){
static_cast<T*>(this)->sonfun();//这里是编译期就确定了,而不是运行时确认,因此上转时使用的static_cast
}
//这里应该写一个默认的sonfun实现。否则子类没实现这个函数时,编译不会报错(CRTP的缺点)
};
class Son1:public Base<Son1>{
public:
//实现
void sonfun(){
std::cout<<"son1"<<std::endl;
}
};
class Son2:public Base<Son2>{
public:
//实现
void sonfun(){
std::cout<<"son2"<<std::endl;
}
};
int main() {
Base<Son1>* s1 = new Son1;//父类指针指向子类对象
Base<Son2>* s2 = new Son2;
s1->fun();
s2->fun();
return 0;
}
注意:父类需要写虚析构函数
虚析构函数:如果基类指针向派生类对象,则删除此指针时,我们希望调用该指针指向的派生类析构函数,而派生类的析构函数又调用基类的析构函数,这样整个派生类的对象完全被释放。