1、static_cast 、dynamic_cast
dynamic_cast是将一个基类对象指针(或引用)转换到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针做相应处理。
用法
dynamic_cast<type-id>(expression)
//该运算符将expression转换为type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;
dynamic_cast运算符可以在执行期决定真正的类型。如果downcast是安全的(也就是说,如果基类指针指向一个派生类对象),这个运算符会传回转型过的指针。如果downcast不是安全的,这个运算符会返回空指针。
dynamic_cast对expression进行类型转换时,expression必须要有虚函数。这是因为在运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(virtual funciton table,vtbl)
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的。
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
上行转换:将派生类引用或指针转换为基类引用或指针称为upcasting
下行转换:将基类指针或引用转换为派生类指针或引用称为downcasting
class B
{
public:
int m_iNum;
virtual void foo(){cout << "this is B" << endl;}
};
class D :public B
{
public:
char* m_szName[100];
void foo(){cout << "this is D" << endl;}
};
void func(B* pb)
{
D* pd2 = dynamic_cast<D*>(pb);
cout << pd2 << endl;
B* pd3 = dynamic_cast<B*>(pd2);
cout << pd3;
}
int main()
{
B* pb = new D();
func(pb);
return 1;
}
static_cast
1、用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换(进行downcast由于没有动态类型检查,是不安全的)
2、基本数据类型的转换
const_cast
该运算符用来修改类型的const或volatile属性。除了const或volatile修饰之外,type_id和expression的类型是一样的。常量指针、引用转化为非常量的指针、引用,并且仍然指向原来的对象。
问题:
1、什么时候必须使用dynamic_cast?
当两个没有关系的类进行转换时,使用static_const不能编译。
2、虚继承
class A {}
class B : virtual public A{}
class C : virtual public A{}
class D : public B, public C{}
类A派生出类B和类C,类D继承自类B和类C,这时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自A->B->D这条路径,另一份来自A->C->D这条路径。假如A有一个成员变量a,那么在D中直接访问a就会产生歧义,编译器不知道它来自哪条路径。
#include <iostream>
using namespace std;
class A
{
public:
A():m_a(0)
{
cout << "A init" << endl;
}
~A()
{
cout << "A end" << endl;
}
public:
int m_a;
};
class B :public A
{
public:
B():A()
{
cout << "B init " << endl;
}
~B()
{
cout << "B end" << endl;
}
};
class C :public A
{
public:
C():A()
{
cout << "C init " << endl;
}
~C()
{
cout << "C end" << endl;
}
};
class D :public B, public C
{
public:
D()
{
cout << "D init" << endl;
}
~D()
{
cout << "D end" << endl;
}
};
int main()
{
D d;
return 1;
}
运行结果:其中A的构造被调用了两次,这也就意味这m_a 被两次赋值,当继承基类初始化时,不清楚m_a的值应该来自哪条路径。在D类方法中调用m_a时,提示未含义不明确。
为了消除歧义,可以指明m_a来自哪个类
B::m_a = 5;
C::m_a = 6;
修改为
#include <iostream>
using namespace std;
class A
{
public:
A():m_a(0)
{
cout << "A init" << endl;
}
~A()
{
cout << "A end" << endl;
}
public:
int m_a;
};
class B :public virtual A
{
public:
B()
{
cout << "B init " << endl;
}
~B()
{
cout << "B end" << endl;
}
};
class C :public virtual A
{
public:
C()
{
cout << "C init " << endl;
}
~C()
{
cout << "C end" << endl;
}
};
class D :public B, public C
{
public:
D()
{
cout << this->m_a << endl;
cout << "D init" << endl;
}
~D()
{
cout << "D end" << endl;
}
};
int main()
{
D d;
return 1;
}
运行结果为:
当使用virtual虚继承之后,A类构造函数只被调用了一次,这也就意味m_a只有一份存储。
为了解决多继承的命名冲突和冗余数据问题,C++提出了虚继承,使得在派生类中只保留一份间接基类的成员。
需要注意的是,在虚继承中,需基类是由最终的派生类实现的,最终派生类的构造函数必须要调用虚基类的构造函数,对最终的派生类来说,虚基类是间接基类(间接继承),不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数只能调用直接基类的构造函数,不能调用间接基类的。
参考链接:C++中虚继承_king_weng的博客-CSDN博客_c++ 虚继承
3、std::function函数和std::bind函数
std::function函数是一个函数包装模板,可以包装下列几种可调用元素类型:函数、函数指针、类成员函数指针。
包装普通函数:
int g_Minus(int i, int j)
{ return i - j; }
function <int (int,int)> f = g_Minus;
f(1,2);
std::bind函数
可以看到,在bind的时候,第一个位置是TestFunc,除了这个,参数的第一个位置为占位符std::placeholders::_2,这就表示,在调用bindFunc3的时候,它的第二个参数和TestFunc的第一个参数匹配,以此类推。
auto bindFunc3 = bind(TestFunc, std::placeholders::_2, std::placeholders::_3,
std::placeholders::_1);
bind最常用的用法之一:是由类成员函数构造bind对象 。这里类的成员函数必须通过对象或者指针调用,因此在绑定时,bind必须要拿出第一个参数位置来指定一个类的实例、指针、或引用。
std::bind(&BindInfoBaseRouter::bindPhoneRouter,this,
std::placeholders::_1, std::placeholders::_2);
c++ std::function_老菜鸟的每一天的博客-CSDN博客
4、&&的含义
右值引用,这个功能自C++11起才可用,移动语义是C++11新增的。重点是对右值操作,右值可以看作程序运行中的临时结果,右值引用可以避免复制提高效率。
&&可以减少对重载函数的需求,并且有助于避免转发转发问题。当编写引用作为其参数的泛型函数时,会引发“转移问题”,如果它采用类型const T&(常引用)的参数,则被调用的函数无法修改该参数的值。
解决问题:
a、如果泛型函数采用T&的参数,则无法使用rvalue(如临时对象或整数文本)来调用该函数。
通常,若解决此问题,必须为每个参数提供T&和const T&的重载版本的泛型函数。
因此,重载函数的数量将基于参数的数量呈现指数方式增加。
rvalue引用允许编写一个接受任意参数的函数版本。然后,该函数可以将它们转移到另一个函数,就像直接调用了另一个函数一样。
#include<utility>
template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
//此示例使用右值作为factory函数的参数
//目的是std::forward将工厂函数的参数转发到模板类的构造函数
return new T(a1, a2);
}
struct NewArray
{
int a;
int b;
NewArray(int a, int b) : a(a), b(b) {};
};
int main()
{
int a = 4, b = 5;
NewArray* pw = factory<NewArray>(a, b);
//没有与参数列表匹配的函数模板"factory实例"
NewArray* pz = factory<NewArray>(2, 2);
return 1;
}
使用&&对函数模板进行修改
#include<utility>
template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
//此示例使用右值作为factory函数的参数
//目的是std::forward将工厂函数的参数转发到模板类的构造函数
return new T(std::forward<A1> (a1), std::forward<A2> (a2));
};
struct NewArray
{
int a;
int b;
NewArray(int a, int b) : a(a), b(b) {};
};
int main()
{
int a = 4, b = 5;
NewArray* pw = factory<NewArray>(a, b);
//编译通过
NewArray* pz = factory<NewArray>(2, 2);
return 1;
}
使用右值引用(&&)可以将修改后的facotry函数按照其参数(左值和右值)转发给适当的类构造函数。
b、可以通过重载函数来采用const lvalue和rvalue引用,用来区分不可更改的对象lvalue和临时值rvalue
#include <iostream>
using namespace std;
class MemoryBlock
{
};
void g(const MemoryBlock&)
{
cout << "In g(const MemoryBlock&)." << endl;
}
void g(MemoryBlock&&)
{
cout << " In g(MemoryBlock &&)." << endl;
}
MemoryBlock&& f(MemoryBlock&& block)
{
g(block);
return move(block);
}
int main()
{
g(f(MemoryBlock()));
}
在此示例中,对f的第一个调用将局部变量(左值)作为其自变量传递,对f的第二个调用将临时对象作为其自变量传递。
输出结果
基类指针可以指向派生类吗?
可以,A作为基类,B作为派生类,基类指针指向派生类对象,只能调用基类原有的,而不能调用派生类中的对象。派生类指针或者引用转换为基类指针或引用被称为向下强制转换(downcasting),is-a的关系是不可逆的,如果允许的话,将会定义一个基类指针,指向子类中并不需要的方法。
构造函数和析构函数可以是虚函数吗?
构造函数不能是虚函数,在创建类对象时,一般先调用派生类的构造函数,而不是基类的构造函数。虚函数一般在对象创建后调用,而构造函数在对象创建时已经调用。
析构函数应是虚函数,除非类不用做基类。如果默认使用静态联编,delete将调用基类构造函数。但它不会释放子类内存,必须重写析构函数释放子类成员的数据。
编译器会将已命名的右值视为左值,而将未命名的右值视为右值。
c、可以将lvalue强制转换为rvalue引用
C++标准库std::move函数可以将某个对象转换为该对象的rvalue引用。也可以使用static_cast关键字将lvalue引用强制转换为rvalue引用。
g(std::move(m));
g(static_cast<MemoryBlock&&>(m));
d、函数模板会推到出模板自变量类型,然后使用折叠规则
将其参数传递(或”转发“)给另一个函数的模式是一种常见模式。如果函数参数是右值,则编译器将参数推倒为右值引用。
#include <string>
#include <iostream>
using namespace std;
template<typename T> struct S;
template<typename T> struct S<T&> {
static void print(T& t)
{
cout << "print <T&> " << t << endl;
}
};
template<typename T> struct S<const T&>
{
static void print(const T& t)
{
cout << "print <const T&> " << t << endl;
}
};
template<typename T> struct S<T&&>
{
static void print(T&& t)
{
cout << "print <T&&> " << t << endl;
}
};
template<typename T> void print_type_and_value(T&& t)
{
cout << "right cite" << endl;
S<T&&>::print(std::forward<T>(t));
}
template<typename T> void print_type_and_value(T& t)
{
cout << "left cite" << endl;
S<T&&>::print(std::forward<T>(t));
}
const string fourth()
{
return string("fourth");
}
int main()
{
string s1("first");
print_type_and_value(s1);
const string s2("two");
print_type_and_value(s2);
print_type_and_value(string("third"));
print_type_and_value(fourth());
}
输出结果为:
为了解析每个对print_type_and_value函数的调用,编译器首先会执行模板参数推导。然后,编译器在用推导出的模板参数替换参数类型时应用引用折叠规则。
下表汇总了模板自变量类型推导的引用折叠原则,这也就是解释了为什么模板函数是右值引用,还可以接受左值。
1、所有的右值引用折叠到右值引用上仍然是一个右值引用
2、所有的其他引用类型之间的折叠都将变成左值引用
模板自变量推到是实现完美转发的重要因素。
visual studio 2017文件编译到生成exe文件的流程
Visual Studio 2017 C++使用_visual studio c++_aisuperdoger的博客-CSDN博客
constexpr的用法