C++自问自答
Posted manziluo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++自问自答相关的知识,希望对你有一定的参考价值。
1.为什么派生层次上的类,同一个虚函数在各个类的虚表中的位置一样?
因为:对虚函数的调用是通过虚指针+偏移地址构成,由于对虚函数的调用都是通过这种方式,所以对同一个虚函数的偏移值就必须相同。
2.为防止对象切片有什么办法?
可以将基类定义为纯虚类
3.为什么构造函数里面的虚机制不起作用?
a.如果构造函数调用层次上,中间构造函数调用的虚函数属于派生类,由于派生类为初始化完成,所以会有问题。
b.调用层次上的每一个构造函数,都会使虚指针指向该构造函数所属的类的虚表,而对象最终的虚指针是由最后调用的构造函数决定(辈分最小的类),如果在中间层次的构造函数调用虚函数,由于当前构造函数设置的虚指针是指向这个类的虚表,所以调用的虚函数还是自己的函数。
4.为什么纯虚析构函数必须有函数体?
因为,基类的析构函数总会被子类的析构函数调用,如果没有函数体,就不知道该怎么析构一个纯虚类。
5.如果一个派生类没有重定义基类的纯虚函数,会发生什么?
该派生类不能实例化。
6.为什么在析构函数中的虚机制不起作用。
因为虚函数归属的类可能已经被删除。
7.为什么在使用dynamic_cast时需要格外小心。
因为dynamic_cast用来做向下类型转换,操作结果不一定总是成功的,在失败的情况下返回的指针值是零,如果继续使用会coredump.
8.为什么对不带有虚函数的类层次执行向下类型转换,使用static_cast比dynamic_cast好
因为,dynamic_cast会使用虚表里面的类型信息,所以不合理,就算没有虚表,额外的调用会导致效率比较低。
9.为什么头文件的规则:不要把分配存储单元的东西放到头文件 ?
分配存储单元必须指定一个名字,二头文件会被多次包括,如果没有使用排重宏,则会造成多次定义的错误,另一方面就算是有排重宏,由于多个单元可见,怎么协调和同步访问该块存储也是一个问题。
10.c语言的setjump和longjump在c++中有什么问题?
a.保存点和返回点紧耦合
b.不会调用析构函数。试想如果析构函数里面有关闭文件,关闭网络连接,释放内存等操作。
11.c++的异常机制有什么好处?
将业务逻辑代码和错误处理代码解耦,使代码结构清晰,易理解。
12.为什么类型转换构造函数在异常处理机制中不工作?
????,也许是因为异常处理器需要的是更原始,更真实,更具体的信息,如果发生转换这些信息很可能打折扣。
13. catch(...)存在的意义是什么?
释放资源本层,配合不带参数的throw可以将异常完整的传递到上一级,上一级可以继续获取异常信息进行处理。
14.如果被抛出的异常对象,析构函数会抛出异常,会发生什么?
本层处理器将不会处理该异常,如果没有上一层处理器,则terminate会被调用;如果有上一层处理器,上层可以 捕获这个异常,但是这种情况被认为是糟糕的设计或编码。
15.如果构造函数里边会抛出异常应该怎么做,因为析构函数不会被调用?
a.可以在构造函数里边进行异常捕获用于释放资源!
b.也可以将需要释放资源的指针封装成对象,然后将这些对象作为成员变量放在类里面,因为成员变量是对象,所以会在构造函数之前调用其构造函数,所以如果这个时候抛出了异常那么栈反解的时候就会把已经成功构造的给释放掉。
因为:对虚函数的调用是通过虚指针+偏移地址构成,由于对虚函数的调用都是通过这种方式,所以对同一个虚函数的偏移值就必须相同。
可以将基类定义为纯虚类
b.调用层次上的每一个构造函数,都会使虚指针指向该构造函数所属的类的虚表,而对象最终的虚指针是由最后调用的构造函数决定(辈分最小的类),如果在中间层次的构造函数调用虚函数,由于当前构造函数设置的虚指针是指向这个类的虚表,所以调用的虚函数还是自己的函数。
因为虚函数归属的类可能已经被删除。
a.保存点和返回点紧耦合
b.不会调用析构函数。试想如果析构函数里面有关闭文件,关闭网络连接,释放内存等操作。
a.可以在构造函数里边进行异常捕获用于释放资源!
b.也可以将需要释放资源的指针封装成对象,然后将这些对象作为成员变量放在类里面,因为成员变量是对象,所以会在构造函数之前调用其构造函数,所以如果这个时候抛出了异常那么栈反解的时候就会把已经成功构造的给释放掉。
16.为什么最好不要直接从Exception派生自己的异常类。
因为Exception类没有接收字符串的构造函数,应该从其子类runtime_error或者logic_error来继承。
17.如果抛出的异常没有包含在异常规格说明,会发生什么?
unexpected会被调用。
18.对异常机制的使用规则
a.不要在中断处理程序中抛出异常,因为中断代码是独立的
b.简单程序不用异常
c.不要把异常用作流程控制。
d.能够处理的异常因尽早处理
e.避免在模板函数中进行异常规格说明,因为异常类型无法预测
f.如果异常只会被本类使用,应将其定义置于该类的名字空间中
g.从标准异常类派生,会比较友好
19.设计assert的意义是什么?
验证设计的正确性,导致断言失败的唯一原因是程序逻辑bug;
20.为什么要编写单元测试?
从代码层面上进程持续测试。
21.为什么要先编写单元测试?
帮助开发者在开始编写业务代码之前,做全名的考虑。
22.单元测试通过代表了什么?
理解了需求。
实现了需求。
23.为什么可以把标准输入输出,文件,内存,字符串都作为流操作?
因为他们都符合流操作的特征。
24.怎么利用字符串流实现数据类型转移?
template <class T> T stringTo(const string & str)
{
T x;
istringstream inStr(str);
inStr>>x;
return x;
}
template <class T> string toString(const T & t)
{
ostringstream ostr;
ostr<<t;
return ostr.str();
{
T x;
istringstream inStr(str);
inStr>>x;
return x;
}
template <class T> string toString(const T & t)
{
ostringstream ostr;
ostr<<t;
return ostr.str();
}
25.怎么利用字符串流,实现浮点数拆分。
void splitDouble(const string & strDouble, int & pre, double & aft)
{
istringstream iStrDoub(strDouble);
iStrDoub>>pre>>aft;
}
{
istringstream iStrDoub(strDouble);
iStrDoub>>pre>>aft;
}
26.输出流有哪些格式控制
27.为什么会有操纵算子?
因为操纵算子书写起来比格式设置函数简单,方便。
28.相应查操作算子有哪些?
29.为什么成员模板函数不能声明为虚函数。
因为,在编译器解析类的时候希望知道该类虚表的大小,如果允许成员模板虚函数,则必须提前知道所有该成员函数调用引起的实例化版本数量,这个需要扫描所有项目文件,非常不灵活,尤其在多项目文件的情况下。
30.为什么 模板的模板参数缺省值在所有地方必须出现?
。。。。。??
31.为什么通过单一模板参数声明的模板函数的多个函数参数,这些函数参数都由模板参数来定义,在调用时,即使给定的不同类型的参数有自动转换的路径,为什么还是要报错。
不符合模板函数声明,首先就找不到用来实例化的模板函数,根本走不到自动类型转换这一步。
32.类成员函数指针与普通函数指针的差别
a. 类成员函数指针是需要指向某类限定的名字空间中的函数,所以在声明时必须用类名来限定 void (className::*funcname)(paramlist)
b.因为类成员函数是需要通过类对象来调用以便设置this指针,所以在成员函数指针调用是也需要 objectName.*pfunc
c.成员函数指针,是指向某类名字空间下的具有指定参数列表和返回值的,所以在赋值时需要选取相应的类成员函数 pFunc=&className::funcname
33.什么是模板的半有序
取特殊程度最高的模板(和异常类型的匹配差不多),但是有时没有特化程度最高的,有多个并列的从而出现歧义,编译报错。
34.模板显示特化时,必须使用特化类型替换原来的模板参数类型,如果遇到const需要进行修正。
35.template<>说明所有参数都已经特化 , template<class T>说明T参数还没有特化。
36.为什么模板类只有被调用的成员函数才会实例化?
省事,省资源哇
37.函数模板不能特化只能重载。
38.由于模板类特化时要重新实现成员函数,所以特化的越多,代码工作量就越大。但是可以通过如下技巧减小代码量。
将需要特化的类型分为几个类别,然后为每个类别做一个完全特化,该类别下面的其他类型就从这个特化进行派生
例如:
template <class T>class Stack;
//完全特化
template<> class Stack<void *>
//为其他指针进行部分特化
template<class T> class Stack<T *> : private Stack<void *>
39.但编译器遇到一个标识符时,必须确定哪些信息,为什么?
必须确定类型、作用域
知道类型才能确定长度和其他关联信息,知道作用域才能确定该标识符出现的位置是否正确。
40.特征类。
比如需要一个画人物肖像的类,这个类需要根据操作的对象,画出不同的器官特征,那么可以使用特征类技术。
假设画肖像的类的操作为画出眼睛,鼻子,嘴巴。
那么先定义 几个类 ,大眼睛,小眼睛,挺鼻子,塌鼻子,小嘴巴,大嘴巴
然后定义几个对象的特征类,在特征类中设置特征名称,美女特征类(眼睛特征类为大眼睛,鼻子特征类为挺鼻子,嘴巴特征类为小嘴巴),丑女特征类(眼睛特征类为小眼睛,鼻子特征类为塌鼻子,嘴巴特征类为小嘴巴)
最后操作类中首先根据模板类型所代表的特征类来设置特征类型,后续直接操作特征类型。
又如:
#include <iostream>
using namespace std;
//牛奶类
class Milk
{
public:
friend ostream & operator << (ostream & os,const Milk &)
{
return os<<"Milk";
}
};
//压缩牛奶类
class CondensedMilk
{
public:
friend ostream & operator << (ostream & os,const CondensedMilk &)
{
return os<<"CondensedMilk";
}
};
//蜂蜜类
class Honey
{
friend ostream & operator<<(ostream & os,const Honey &)
{
return os<<"Honey";
}
};
// 饼干类
class Cookies
{
friend ostream & operator<<(ostream & os,const Cookies &)
{
return os<<"Cookies";
}
};
//定义几个访客类
//熊类
class Bear
{
public:
friend ostream & operator<<(ostream & os,const Bear&)
{
return os<<"Big bear";
}
};
//小孩类
class Boy
{
public:
friend ostream & operator << (ostream & os ,const Boy &)
{
return os<<"Little boy";
}
};
//主要traits模板(空模板可以持有所有普通类型)
template <class Guest> class GuestTraits;
//特化的traits类
template<> class GuestTraits<Bear>
{
public:
typedef CondensedMilk beverage_type;
typedef Honey snack_type;
};
template<> class GuestTraits<Boy>
{
public:
typedef Milk beverage_type;
typedef Cookies snack_type;
};
class MixedUpTraits
{
public:
typedef Milk beverage_type;
typedef Honey snack_type;
};
//驱动类
template<class Guest,class traits=GuestTraits<Guest> >
class BearCorner
{
Guest theGuest;
typedef typename traits::beverage_type beverage_type;
typedef typename traits::snack_type snack_type;
beverage_type bev;
snack_type snack;
public:
BearCorner(const Guest & g):theGuest(g){}
void entertain()
{
cout<<"Entertaining "<<theGuest
<< " serving "<<bev
<< " and "<<snack<<endl;
}
};
int main(int argv,char ** args, char ** env)
{
Boy boy;
Bear bear;
BearCorner<Boy> bc1(boy);
bc1.entertain();
BearCorner<Bear> bc2(bear);
bc2.entertain();
BearCorner<Boy,MixedUpTraits> bc3(boy);
bc3.entertain();
return 0;
using namespace std;
//牛奶类
class Milk
{
public:
friend ostream & operator << (ostream & os,const Milk &)
{
return os<<"Milk";
}
};
//压缩牛奶类
class CondensedMilk
{
public:
friend ostream & operator << (ostream & os,const CondensedMilk &)
{
return os<<"CondensedMilk";
}
};
//蜂蜜类
class Honey
{
friend ostream & operator<<(ostream & os,const Honey &)
{
return os<<"Honey";
}
};
// 饼干类
class Cookies
{
friend ostream & operator<<(ostream & os,const Cookies &)
{
return os<<"Cookies";
}
};
//定义几个访客类
//熊类
class Bear
{
public:
friend ostream & operator<<(ostream & os,const Bear&)
{
return os<<"Big bear";
}
};
//小孩类
class Boy
{
public:
friend ostream & operator << (ostream & os ,const Boy &)
{
return os<<"Little boy";
}
};
//主要traits模板(空模板可以持有所有普通类型)
template <class Guest> class GuestTraits;
//特化的traits类
template<> class GuestTraits<Bear>
{
public:
typedef CondensedMilk beverage_type;
typedef Honey snack_type;
};
template<> class GuestTraits<Boy>
{
public:
typedef Milk beverage_type;
typedef Cookies snack_type;
};
class MixedUpTraits
{
public:
typedef Milk beverage_type;
typedef Honey snack_type;
};
//驱动类
template<class Guest,class traits=GuestTraits<Guest> >
class BearCorner
{
Guest theGuest;
typedef typename traits::beverage_type beverage_type;
typedef typename traits::snack_type snack_type;
beverage_type bev;
snack_type snack;
public:
BearCorner(const Guest & g):theGuest(g){}
void entertain()
{
cout<<"Entertaining "<<theGuest
<< " serving "<<bev
<< " and "<<snack<<endl;
}
};
int main(int argv,char ** args, char ** env)
{
Boy boy;
Bear bear;
BearCorner<Boy> bc1(boy);
bc1.entertain();
BearCorner<Bear> bc2(bear);
bc2.entertain();
BearCorner<Boy,MixedUpTraits> bc3(boy);
bc3.entertain();
return 0;
}
42.特征类组合策略,实现策略类,比如上面的画肖像,可以通过策略类转换为拍肖像等。
43.对象计数的高级实现
#include <iostream>
using namespace std;
template<class T>
class ObjCount
{
static int cnt;
public:
ObjCount(){++cnt;}
ObjCount(const ObjCount<T> & cnt){++cnt;}
~ObjCount(){--cnt;}
static int getCount(){return cnt;}
};
template<class T> int ObjCount<T>::cnt=0;
using namespace std;
template<class T>
class ObjCount
{
static int cnt;
public:
ObjCount(){++cnt;}
ObjCount(const ObjCount<T> & cnt){++cnt;}
~ObjCount(){--cnt;}
static int getCount(){return cnt;}
};
template<class T> int ObjCount<T>::cnt=0;
class TestCnt1:public ObjCount<TestCnt1>{};//父类唯一
class TestCnt2:public ObjCount<TestCnt2>{};//父类唯一
int main(int argv,char ** args, char ** env)
{
TestCnt1 t11,t12;
TestCnt2 t21,t22;
cout<<"TestCnt1::getCount="<<TestCnt1::getCount()<<endl;
cout<<"TestCnt2::getCount="<<TestCnt2::getCount()<<endl;
return 0;
{
TestCnt1 t11,t12;
TestCnt2 t21,t22;
cout<<"TestCnt1::getCount="<<TestCnt1::getCount()<<endl;
cout<<"TestCnt2::getCount="<<TestCnt2::getCount()<<endl;
return 0;
}
关于bind1st/bind2nd
相关类一元函数类,二元函数类,binder2nd/binder1st
一般一元函数和二元函数继承自一元函数类,二元函数类,这中函数父类存在的意思是把参数类型名称以及返回类型名称统一化,以便外部接口统一访问。
bind2nd函数将一般的二元函数对象转换为binder2nd一元函数对象,binder2nd对象的调用重载会调用传入的二元函数对象。由于bind2nd接收的第一个参数是const限定的常量对象,所以传入的二元函数对象的operator()重装必须要用const限定。
以上是关于C++自问自答的主要内容,如果未能解决你的问题,请参考以下文章