C++常用问题汇总

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_caststatic_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 lvaluervalue引用,用来区分不可更改的对象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、所有的其他引用类型之间的折叠都将变成左值引用

模板自变量推到是实现完美转发的重要因素。

C++引用(&)详解_c++ &_彡笙的博客-CSDN博客

visual studio 2017文件编译到生成exe文件的流程

Visual Studio 2017 C++使用_visual studio c++_aisuperdoger的博客-CSDN博客

constexpr的用法

constexpr_Gamer_code的博客-CSDN博客

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值