1. 异常对象没有专门的头文件。
2.不应该抛出指向局部对象的指针以及局部对象。
3.异常的类型与与catch说明符完全匹配:
(1)允许从非const 到 const的转换。
(2)允许从派生类到基类的转换。
(3)将数组转换为指向数组类型的指针,将函数转化为指向函数类型的指针。
不允许标准算术转换,比如int到double;不允许类类型定义的转换。如下例子:
/*
功能:1.测试异常类需要头文件吗?答案:不需要
2.测试try中的局部对象在throw对象时能否丢局部变量? 答案: 在throw a前,清空try块中所有局部对象。
3.测试是否允许int对象转换为double?答案:不允许。
*/
#include <iostream>
using namespace std;
class B
{
};
class A
{
public:
~A(){
delete p;p= NULL; }
operator B()
{
B b;
return b;
}
public:
char* p;
};
void test()
{
A a;
try
{
//A a;
a.p = new char[10];
strcpy(a.p,"abs");
//A *b;
//b = new A();
//int i = 3;
//throw i;
throw a;
}
catch (B e)
{
//cout << e.p << endl; //输入乱码
cout << "yes" << endl;
}
}
int main()
{
range_error r("error");
out_of_range rr("越界");
test();
cout << "test" << endl;
return 0;
}
4. 允许动态绑定,通过指针或引用。见例子
/*
功能:1.测试异常类需要动态绑定吗?答案:允许。
*/
#include <iostream>
using namespace std;
class A
{
public:
virtual ~A(){
delete p;p= NULL; }
virtual void output()
{
cout << "A:output" << endl;
}
public:
char* p;
};
class B : public A
{
public:
virtual ~B(){ }
void output()
{
cout << "B:output" << endl;
}
};
void test1()
{
try
{
B *b = new B();
b->p = new char[10];
strcpy(b->p,"abs");
throw b;
}
catch (A* e)
{
e->output();
}
}
int main()
{
range_error r("error");
out_of_range rr("越界");
test1();
cout << "test" << endl;
return 0;
}
5. 重新抛出
如果某个catch子句接到了throw的对象,但是处理不好,可以重新抛出,使用throw;语句即可。注意:重新抛出的对象并不是该catch子句形参的对象,而是当时传入进来的实参对象,除非该catch子句形参为引用类型或指针,改变了原先对象的状态,才能保持改变。
/*
功能:1.测试重新抛出是否改变对象状态?答案:不改变,除非为引用 或指针
*/
#include <iostream>
using namespace std;
class A
{
public:
virtual ~A(){
delete p;p= NULL; }
virtual void output()
{
cout << "A:output" << endl;
}
public:
char* p;
};
class B : public A
{
public:
virtual ~B(){ }
void output()
{
cout << "B:output" << endl;
}
};
void test1()
{
try
{
B *b = new B();
b->p = new char[10];
strcpy(b->p,"abs");
throw *b;
}
catch (A e)
{
//cout << e.p << endl; //输入乱码
throw;
}
catch( B b)
{
b.output();
}
}
int main()
{
range_error r("error");
out_of_range rr("越界");
try{
test1();
}
catch( B b)
{
b.output(); // 输出B::output
}
cout << "test" << endl; //不执行,不明所以
return 0;
}
6. catch(...)语句
该语句是在所有异常处理都失败的情况,放到最后的,相当于case条件的default语句,用来做些清理工作。它也可以重新throw;异常。
7. 函数测试块与构造函数。
构造函数要处理来自构造函数初始化式的异常,唯一的方法是将构造函数编写为函数测试会。
/*
注意:关键字try出现在成员初始化列表之前,并且测试块的复合语句包围了构造函数的函数体。catch子句既可以处理从成员列表抛出的异常,也可以处理从构造函数体中抛出的异常。
*/
template<class T>
Handle<T>::Handle(T *p)
try : ptr(p) , use( new size_t(1) )
{
} catch( const std::bad_alloc &e)
{ handle_out_of_memory(e) ;}
8. 自动资源释放
在发生异常时,自动撤销局部对象,这是为什么鼓励大家使用标准库类的原因。考虑下面这段代码
void exercise( int *b,int *e )
{
vector<int> v(v,e);
int *p = new int[v.size()];
ifstream in("this");
//exception occurs here
}
当异常发生后,p申请的资源得不到释放。但v会被释放。
那我们怎么解决呢?方法有两种,如下
//方法一
void exercise( int *b,int *e )
{
vector<int> v(b,e);
int *p = new int[v.size()];
try{
ifstream in("this");
//异常发生在此。
} catch
{ delete p; //释放数组
...
}
}
//方法二,定义一个类来封装数组的分配和释放,保证正确释放资源
class Resource
{
public :
Resource( size_t sz ): r( new int[sz] ){ }
~Resource() { if(r) delete r; }
//其它操作
private:
int *r;
}
//函数exercisei修改为:
void exercise( int *b,int *e )
{
vector<int> v(b,e);
Resource res(v.size());
ifstream in("this");
//异常发生在此....
}
注意:此处给出的Resource类操作非常简略,要到到实用,必须定义其它相关操作,以支持内置指针及数组使用方式并保证自动删除Resource对象所引用的数组。
9. 应该只用get询问auto_prt对象或者使用返回的指针值,不能用get作为创建其它auto_ptr对象的实参。因为它违反auto_ptr类设计原则:在任意时刻只有一个auto_ptr对象保存给定指针,如果两个auto_ptr对象保存相同的指针,该指针就会被delete两次。
10. auto_ptr对象与内置指针的另一个区别是,不能直接将一个地址赋给auto_ptr对象:
p_auto = new int(1024); //error
相反,必须调用reset函数来改变指针:
if( p_auto.get() )
*p_auto = 1024;
else
p_auto.reset( new int(1024) );
11. auto_ptr的缺陷
也相当于是适用auto_ptr要注意的地方:
1. 不要使用auto_ptr对象保存指向静态分配对象的指针,否则,当它被撤销的时候,它将试图删除指向非动态分配对象的指针,导致未定义的行为。
2. 永远不要使用两个auto_ptr对象指向同一个指针对象。
3. 不要使用auto_ptr保存指向动态分配的数组的指针。因为删除的时候,它只释放一个对象===它使用的是delete,而不是 []delete。
4. 不要将auto_ptr对象存储在容器中。 因为auto_ptr不满足复制之后,两个对象具有相同值。
12. 基类中虚函数的异常说明,可以与派生类中对应虚函数的异常说明不同。但是,派生类虚函数的异常说明必须与对应基类虚函数的异常说明同样严格或者更受限。
比如:
class Base
{
virtual double f1() throw(); //表示不抛出任何异常
virtual int f2() throw ( std::logic_error ); //只抛出逻辑错误
virtual std::string f3() throw ( std::logic_error,std::runtime_error );//抛出逻辑,运行错误
}
class Derived: public Base
{
virtual double f1() throw( std::underflow_error ); // error
virtual int f2() throw ( std::logic_error ); // ok
virtual std::string f3() throw ( );//error,表示不抛出任何异常,当然不行
}
13.命名空间不能以分号结束。
14. Using声明和Using指示之间的区别
答:区别在于:一个using声明只能引入特定命名空间中的一个成员,一个using指示使得特定命名空间中的所有名字都成为可见的。考虑下面的代码样本:
namespace Exercise{
int ivar = 0;
double dvar = 0;
const int limit = 1000;
}
int ivar = 0;
// 位置1
void manip() {
//位置2
double dvar = 3.14
int iobj = limit +1;
++ivar;
++::ivar;
}
问题是:将Exercise的所有成员的using声明分别放在位置1和位置2的区别? 同理,将using指示分别放在位置1和位置2的区别?
答案: using 声明:
(1)位置1: Exercise中所有成员全局可见。 ivar会导致重复定义的编译错误,因为全局中已经定义了一个ivar。
dvar是局部定义的变了,它屏蔽了全局中命名空间定义的dvar。
(2) 位置2:Exercise中的所有成员局部可见。 using Exercise::ivar会屏蔽全局定义的ivar。
dvar会导致重复定义编译错误。
using 指示:
(1)位置1: dvar局部定义的变量将屏蔽 Exercise::dvar。
++ivar访问到的将是Exercise::ivar。(屏蔽了 int ivar = 0)
(2)位置2:跟定义在全局作用域中一样。 dvar屏蔽掉Exercise::dvar。
++ivar出现二义性。
++::ivar访问全局的。
15. 多重继承
在多重继承下,比如 C: public A,public B。那么当我们定义基类指针A指向C时,不能调用只在C中出现的成员及变量,你可以这样理解,因为C由A,B共同组建,不能凭一半指针访问子类成员。但是虚拟函数还是正常运作的。还有要注意一点:如果子类中有函数同名,当调用时会引发二义性,即使这两个函数带有不同参数,或者不同访问级别。(除非你定义的指针为基类类型,如果你定义子类类型,它不知道调用哪个基类的函数)。
/*
功能:测试多重继承下,基类中含有相同函数,是否引发冲突? 答案:没有编译错误。但是当调用的时候会引发二义性问题。解决办法如程序。
*/
#include <iostream>
using namespace std;
class A{
public:
virtual void print() { cout << "testA" << endl; }
virtual void pureVir() = 0;
};
class B : public A{
public:
virtual void print(){ cout <<"testB" << endl; }
void pureVir(){} //如果基类定义了纯虚函数,那么必须定义
};
class D
{
public:
void dd(){ cout << "testD" << endl;}
};
class C:public B,public D{
public:
void dd(){ cout << "testD" << endl; }
};
void testFuc(const A&){}
void testFuc(const B&){}
int main()
{
C c;
//c.A::test();
//testFuc(c); //二义性
A* pa = new C();
pa->dd();
return 0;
}
当我们定义一个指向子类的基类指针,如 B* bb = new C(), A *aa = new C(); C *c = new C()。当我们析构时,delete bb, delete aa,delete c时,执行析构函数的顺序是一致的,与构造函数相反。
16. 虚基类继承
虚基类是在当两个类b1,c1有相同的父类a1时,采用虚拟继承该父类a1,然后当另外一对象d1多重继承b1,c1时,在d1内部只会产生一个父本a1,不然会出错。
在虚拟继承的时候,要注意一个问题,就是子类在构造的初始化列表中,要显示的列出继承体系里边 虚基类的构造函数,不然会出错误,除非虚基类提供了默认构造函数。
/*
功能: 1.测试虚基类继承的时候,子类在初始化的时候必须显示的提供虚基类的构造: MI(string str) : Base(str), D1(str),D2(str)
2.如果有相同的子类没有使用虚基类,那么在调用该基类函数时,会导致编译错误。
*/
#include <iostream>
#include <string>
using namespace std;
class Class
{
public:
Class(string str)
{
cout << str << endl;
}
void testC()
{
cout << "Class" << endl;
}
};
class Base: public Class
{
public:
Base( string str ): Class(str)
{
}
void testB()
{
cout << "testB" << endl;
}
};
class D1: virtual public Base
{
public:
D1(string str) : Base(str)
{
}
void testD1()
{
cout << "testD1" << endl;
}
};
class D2 : virtual public Base
{
public:
D2(string str) : Base(str)
{
}
void testD2()
{
cout << "testD2" << endl;
}
};
class MI : public D1 ,public D2
{
public:
MI(string str) : Base(str), D1(str),D2(str)
{
}
void testMI()
{
cout << "testMI" << endl;
}
};
class Final: public MI,public Class
{
public:
Final(string str) : Base(str),MI(str),Class("hello")
{
}
void testF()
{
cout << "testFinal" << endl;
}
};
int main()
{
Final f1("dd");
f1.testC(); //编译出错
return 0;
}
17. printf函数居然是从右至左来输出的,即 int i =1; printf("%d %d",i,i++),输出为3, 2 !
同理,函数的调用过程中,参数也是由右向左入栈的,因为,下面的代码:
#include <iostream>
using namespace std;
void test(int i,int j)
{
cout << i << endl; //1
cout << j << endl; //0
}
int main()
{
int i =0;
test(i,i++);
return 0;
}
18. dynamic_cast
对于dynamic_cast的用法要注意以下4点:
(1)dynamic_cast是在运行时检查的,dynamic_cast用于在继承体系(因此就不能用于内置类型的转换)中进行安全的向下转换(当然也可以向上转换,但是完全没必要,因为完全可以用虚函数实现),即基类指针/引用到派生类指针/引用的转换(因此不能用于 dynamic_cast<Base>(derive)这种对象转换)。如果源和目标类型没有继承/被继承关系,编译器会报错;否则必须在代码里判断返回值是否为NULL来确定转换是否成功。
(2)dynamic_cast不是扩展c++中style转换的功能,而是提供类型安全性。你无法用dynamic_cast进行一些无理的转换。
(3)dynamic_cast是4个转换中唯一的RTTI操作符,提供运行时类型检查。
(4)dynamic_cast不是强制转换,而是带有某种“咨询”性质的。如果不能转换,dynamic_cast会返回NULL,表示不成功。这是强制转换做不到的。
#include "stdafx.h"
#include <iostream>
using namespace std;
class A
{
public:
virtual void foo(){cout << "A foo" << endl;}
void pp(){cout << "A pp" << endl;}
};
class B : public A
{
public:
void foo(){cout << "B foo" << endl;}
void pp(){cout << "B pp" << endl;}
void function()
{
m_a = 1;
}
private:
int m_a;
};
int main()
{
B *b = new B;
A *p1 = dynamic_cast<A*>(b); //()是必须要打加上的
void* p2 = dynamic_cast<B*>(b); //这样是可以的
return 0;
}
以下是一道笔试题目,描述了dynamic_cast的相关用法,以下程序是错误的。
#include "stdafx.h"
#include <iostream>
using namespace std;
class A
{
public:
virtual void foo(){cout << "A foo" << endl;}
void pp(){cout << "A pp" << endl;}
};
class B : public A
{
public:
void foo(){cout << "B foo" << endl;}
void pp(){cout << "B pp" << endl;}
void Function()
{
m_a = 1;
}
private:
int m_a;
};
int main()
{
A a;
A *pa = &a;
(dynamic_cast<B*>(pa))->Function(); //error 编译不通过
(dynamic_cast<B*>(pa))->foo(); //error 编译不通过
(dynamic_cast<B*>(pa))->pp(); //编译可以通过
return 0;
}
在上面代码中,如果pa指向B类型的对象,对这种情况执行任何操作都是安全的;但是,实际上pa指向的是一个A类型的对象,那么(dynamic_cast<B*>(pa))返回的值将是一个空指针,所以题目中的代码:
(dynamic_cast<B*>(pa))->Function();
(dynamic_cast<B*>(pa))->foo();
(dynamic_cast<B*>(pa))->pp();
//以上代码等同于
B *p = NULL;
p->Function();
p->foo();
p->pp();
那为什么p->pp()函数可以通过呢?因为这个函数未使用任何成员数据,也不是虚函数,不需要this指针,也不需要动态绑定,而且即使pp函数中调用类成员函数,也可以通过编译。
而p->Function()和p->pp()不能编译通过分别是由于用到了类成员数据,以及虚函数机制。需要用到this指针,而此时为空,为非法访问。
说到这儿,有必要将c++中几种类型转换机制进行详尽叙述与对比,摘自某博客
使用标准C++的类型转换符:static_cast、dynamic_cast、reinterdivt_cast、和const_cast
1. static_cast
用法:static_cast < type-id > ( exdivssion )
该运算符把exdivssion转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类和子类之间指针或引用的转换。
进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉exdivssion的const、volitale、或者__unaligned属性。
2. dynamic_cast
用法:dynamic_cast < type-id > ( exdivssion )
该运算符把exdivssion转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;
如果type-id是类指针类型,那么exdivssion也必须是一个指针,如果type-id是一个引用,那么exdivssion也必须是一个引用。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class B
{
public:
int m_iNum;
virtual void foo();
};
class D:public B
{
public:
char *m_szName[100];
};
void func(B *pb)
{
D *pd1 = static_cast(pb);
D *pd2 = dynamic_cast(pb);
}
在上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;
但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针。
另外要注意:B要有虚函数,否则会编译出错;static_cast则没有这个限制。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。
另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。
class A
{
public:
int m_iNum;
virtual void f(){}
};
class B:public A
{
};
class D:public A
{
};
void foo()
{
B *pb = new B;
pb->m_iNum = 100;
D *pd1 = static_cast(pb); //compile error
D *pd2 = dynamic_cast(pb); //pd2 is NULL
delete pb;
}
在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。
3. reindivter_cast
用法:reindivter_cast (exdivssion)
type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。
它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,
在把该整数转换成原类型的指针,还可以得到原先的指针值)。
该运算符的用法比较多。
4. const_cast
用法:const_cast (exdivssion)
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和exdivssion的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
Voiatile和const类试。举如下一例:
class B
{
public:
int m_iNum;
}
void foo()
{
const B b1;
b1.m_iNum = 100; //comile error
B b2 = const_cast(b1);
b2. m_iNum = 200; //fine
}
上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。
其实对于,const你还有很多功课要学习,它最确切的翻译应该是只读。而且对于const_cast的用法,上面的讲述不够。
参考这个:
http://www.cnblogs.com/ider/archive/2011/07/22/cpp_cast_operator_part2.html
== ===========================================
== dynamic_cast .vs. static_cast
== ===========================================
class B { ... };
class D : public B { ... };
void f(B* pb)
{
D* pd1 = dynamic_cast(pb);
D* pd2 = static_cast(pb);
}
If pb really points to an object of type D, then pd1 and pd2 will get the same value. They will also get the same value if pb == 0.
If pb points to an object of type B and not to the complete D class, then dynamic_cast will know enough to return zero. However, static_cast relies on the programmer’s assertion that pb points to an object of type D and simply returns a pointer to that supposed D object.
即dynamic_cast可用于继承体系中的向下转型,即将基类指针转换为派生类指针,比static_cast更严格更安全。dynamic_cast在执行效率上比static_cast要差一些,但static_cast在更宽上范围内可以完成映射,这种不加限制的映射伴随着不安全性。static_cast覆盖的变换类型除类层次的静态导航以外,还包括无映射变换、窄化变换(这种变换会导致对象切片,丢失信息)、用VOID*的强制变换、隐式类型变换等...
== ===========================================
== reinterdivt_cast .vs. static_cast
== ===========================================
reinterdivt_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。(这句话是C++编程思想中的原话)
static_cast 和 reinterdivt_cast 操作符修改了操作数类型。它们不是互逆的; static_cast 在编译时使用类型信息执行转换,在转换执行必要的检测(诸如指针越界计算, 类型检查). 其操作数相对是安全的。另一方面;reinterdivt_cast 仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换, 例子如下:
int n=9; double d=static_cast < double > (n);
上面的例子中, 我们将一个变量从 int 转换到 double。 这些类型的二进制表达式是不同的。 要将整数 9 转换到 双精度整数 9,static_cast 需要正确地为双精度整数 d 补足比特位。其结果为 9.0。而reinterdivt_cast 的行为却不同:
int n=9;
double d=reinterdivt_cast (n);
这次, 结果有所不同. 在进行计算以后, d 包含无用值. 这是因为 reinterdivt_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析.
因此, 你需要谨慎使用 reinterdivt_cast
19. 请看下面一段代码
#include <iostream>
using namespace std;
int main()
{
int *pa = NULL;
int *pb = pa+15;
printf("%x",pb); //输出为3c
return 0;
}
在32位机器上运行如下,为何时3c呢?
因为 pa= NULL。因为pa =0。 定义了一个pb指针,它的地址被初始化为pa+15,注意这个15不能单纯的只加15,因为移动1,代表的是移动pa所指的类型大小,那么这里即使15*4,因此总的
为 0+15*4 = 60。十六进制表示为3c。
20.请看下面一段代码
printf("%f",5); //输出未知
printf("%f",5.0); //输出5.000000。
因为5为int类型,占4个字节,而%f认为该参数为为double型(在printf()函数中float会自动转换为double),那么显然需要读取8个字节,内存访问过界,发生什么情况不可预计。
5.0被自动解释为double型。所以输出无误。
21.结构体位制概念
请看下面的代码:
struct A
{
int x:1;
int y:2;
int z:32;
};
int main()
{
A a;
a.y = 2;
cout << a.y << endl; //输出为-2
return 0;
}
x:1表示 x虽然是int类型(注意有符号哦),但是我们只用1位,那么这1位即是符号位,又是有效位。意思就是说x只能表示0和-1。 为什么答案输出为-2,因为a.y=2的二进制为10,我们现在只是用两位,那么最高位为1,表示负数,10为2那么整个就是-2。
22.字符串
char *c = "122333";
c[1] = '4'; //编译通过,运行出错,常量区
char cc[] = "23323"; //栈区
cc[2] = '4'; //ok
五个区:栈区,堆区,全局区,文字常量区,程序代码区。
23.关于类中vector初始化问题。
当类中定义了stl中的顺序容器,我们是不能对其赋予处置的比如,定义vector<int> v(10)。而只能写vector<int> v。另外,即使你在初始化列表中指定了v的大小,仍然无效。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
template<class T>
class array
{
public:
array(int size);
size_t getVectorSize(){ return _data.capacity();}
size_t getSize(){return _size;}
private:
vector<T> _data; //定义时就已经初始化了
size_t _size;
};
template<class T>
array<T>::array(int size)
: _size(size),_data(_size)
{
}
int main()
{
array<int> *arr = new array<int>(3);
cout << arr->getVectorSize() << endl; // 0
cout << arr->getSize() << endl; //3
vector<int> ii(10);
cout << ii.size() << endl; //10
return 0;
}
24.网易笔试
只用位运算,实现两数的加法。
http://www.cnblogs.com/luowei010101/archive/2011/11/24/2261575.html.
25. sizeof(++i,i++)
根据C99规范, sizeof是一个编译时刻就起效果的运算符,在其内的任何运算都没有意义, j = sizeof(++i+++i); 在编译的时候被翻译成 j=sizeof((++i+++i的数据类型)) 也就是 j = sizeof(int); 也就是 j= 4; (32bit系统,如果是16位系统,则j=2) 然后才会继续编译成最终的程序,当然在最终程序执行的时候,自然不会执行任何++i了。
26. RTTI 之 typeid操作符
typeid最常见的用途就是比较两个表达式的类型是否相等。只有当typeid的操作数是带虚函数的类类型的对象的时候,才返回动态类型信息。测试指针(相对于指针指向的对象)返回指针的静态的,编译时类型。
#include<iostream>
#include<list>
#include <vector>
using namespace std;
class A
{
public:
virtual void test()
{
cout << "a" << endl;
}
};
class B : public A
{
};
class C: public B,public A
{
};
int main()
{
vector<int> ii(40);
int *a = new int[10];
cout << sizeof(a) << endl;
A *c = new C();
//c.test();
C* cc = dynamic_cast<C*>(c);
if( typeid(*c) == typeid(C) ) //A中必须要有虚函数
{
cout << "yes" << endl;
}
if( typeid(c) != typeid(C) )
cout << "no" << endl; //因为c为 A*类型
return 0;
}
27.关于虚函数的写法以及访问控制符
如果基类中定义了一个虚函数,它的实参和返回类型都是基类引用类型,那么在子类的实现中,可以改变返回值类型为子类引用类型,但不能更改形参类型。
而且,如果基类的虚函数控制符为public,在子类实现中,我们可以将其改为protected和private类型。并不影响动态调度。
#include<iostream>
#include<list>
#include <vector>
using namespace std;
class A
{
public:
virtual A& test( A& a)
{
cout << "a" << endl;
A *p = new A();
return a;
}
};
class B : public A
{
private:
virtual B& test( A& b)
{
cout << "b" << endl;
B *p = new B();
return *p; //如果这里我们 return b;那么将会导致编译失败。因为不能将A类型返回给B类型。
}
};
int main()
{
A* bb = new B();
A* pb = new B();
bb->test(*bb);
return 0;
}
如果你在子类的test实现函数后面加上const修饰符,那么也会干扰掉虚函数的动态调度,因为这构成了一个函数覆盖。他们之类类型已经不匹配了。
28.考虑下面这样一个问题:
int main()
{
char a[1000];
int i;
for( i = 0; i < 1000; i++ )
a[i] = -1-i;
printf("%d",strlen(a));
return 0;
}
然后注意两点,首先+0和-0在内存中存储均为0,没有正负之分,然后有符号的类型如何和无符号类型碰到一起,会自动转换为无符号类型,看以下例题:
void main()
{
int i = -40;
unsigned int j = 10;
//int-> unsigned int
cout << i+j << endl; //因此结果为无符号整形的,所以为4294967266
cout << (int)(i+j) << endl; // -30
}
29. 浮点数运算要注意,不要一个很大的浮点数和很小的浮点数之间进行运算。
30. 对于bool类型的变量,这样用 if( bFlag ) 或 if( !bFlag )。
31. 关于 define和const
32.请看下面一段代码,看看你是否真弄懂了const修饰的是值还是指针?
#inlude<iostream>
using namespace std;
typedef struct Type
{
int a;
int b;
}TYPE,PTYPE;
int main()
{
const PTYPE p1 = new Type(); //(1)
PTYPE const p2 = new Type(); //(2)
}
请问(1),(2)两句分别是表示值为常数,还是指针为常数。
答案是(1)(2)均表示指针p1,p2为常指针,即不能改变指针的值,可以改变a,b的值。为什么呢?
我们说 const int p 和 int const p是相同的,const int* p 和 int* const p是不同的,其实编译器是这么做的:忽略类型名,即忽略掉int再来看,变成了 cont p, const p 表示修饰的是值;另外一个 const *p , * const p,一个修饰的是值(*p),一个修饰的是指针(p)。所以对于上面的问题, PTYPE其实是 struct Type{}*的别名,但编译器将PTYPE当作了一种类型名,因此直接忽略掉,因此(1)(2)变为 const p1,const p2,不就是表示常指针嘛。
33. 关于typedef与#define 。
我们知道typedef是定义类型别名。
(1) #define INT32 int
unsinged INT32 i = 10;
(2) typedef int int32;
unsinged int32 i = 10;
其中(2)编译出错,(1)正确。因为在预编译的时候INT32被替换为int, 而 unsigned int i = 10;语句是正确的。但是,很可惜,用typedef取的别名不支持这种替换扩展。 比如用 typedef LONG long;来替换long Long i,是不对的。
下面再看一个与#define宏有关的例子:
(1) #define PCHAR char*
PCHAR p3,p4;
(2) typedef char* pchar;
pchar p1,p2;
两组代码编译均没有问题,但是p4并不是指针,而是char类型的字符,因为 char *p3,p4,定义了一个p3指针,和p4对象。这种错误很容易忽略,所以用#define的时候要谨慎。
34. 前几天腾讯出了到#define的题目,还不错。顺便介绍下#define是定义一个宏,#undef是解除一个宏,表示不能在下面用这个宏定义了。
#include<iostream>
using namespace std;
#define func(a,b) a--;b++;a*b;
#define X 3
#define Y X*2
#undef X
#define X 2
int main()
{
int a = 4;
int b = 4;
for(int i = 0; i < 5; i++)
func(a,b);
cout << a <<" " << b << endl; //输出-1,5
cout << Y << endl;//输出4
return 0;
}
35. 关于enum该知道的!
http://www.cppblog.com/chemz/archive/2007/06/05/25578.html
36.内存池的概念
内存池是一种内存分配方式。通常我们习惯直接使用new,malloc等API申请分配内存,这样做的缺点在于:由于所申请的内存块大小不定,当频繁使用时会造成大量的内存碎片进而降低性能。
内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够则在申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。
优势相比在于:
(1)效率高,快
(2)避免内存碎片
(3)避免内存泄露
37. 一般函数指针与成员函数指针的区别
成员函数因为包含默认的this,而且不同的类this指向内容不同,因此在使用成员函数指针的时候要注意定义域的限制。
见一个例子:
#include <iostream>
using namespace std;
class CA;
/*指向类的非静态成员函数的指针*/
typedef int (CA::*pClassFun)(int, int);
/*指向一般函数的指针*/
typedef int (*pGeneralFun)(int, int);
class CA
{
public:
int Max(int a, int b)
{
return a > b ? a : b;
}
int Min(int a, int b)
{
return a < b ? a : b;
}
static int Sum(int a, int b)
{
return a + b;
}
/*类内部的接口函数,实现对类的非静态成员函数的封装*/
int Result(pClassFun fun, int a, int b)
{
return (this->*fun)(a, b);
}
};
/*类外部的接口函数,实现对类的非静态成员函数的封装*/
int Result(CA* pA, pClassFun fun, int a, int b)
{
return (pA->*fun)(a, b);
}
/*类外部的接口函数,实现对类的静态成员函数的封装*/
int GeneralResult(pGeneralFun fun, int a, int b)
{
return (*fun)(a, b);
}
void main()
{
CA ca;
int a = 3;
int b = 4;
cout<<"Test nonstatic member function pointer from member function:"<<endl;
cout<<"The maximum number between a and b is "<<ca.Result(&CA::Max, a, b)<<endl;
cout<<"The minimum number between a and b is "<<ca.Result(&CA::Min, a, b)<<endl;
cout<<endl;
cout<<"Test nonstatic member function pointer from external function:"<<endl;
cout<<"The maximum number between a and b is "<<Result(&ca, &CA::Max, a, b)<<endl;
cout<<"The minimum number between a and b is "<<Result(&ca, &CA::Min, a, b)<<endl;
cout<<endl;
cout<<"Test static member function pointer: "<<endl;
cout<<"The sum of a and b is "<<GeneralResult(&CA::Sum, a, b)<<endl;
}
参见:http://www.cnblogs.com/xianyunhe/archive/2011/11/26/2264709.html
38.为什么构造函数不能为虚函数,析构函数可以为虚函数?
1. 从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
3. 构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过实验室的基类的指针或引用去访问它但析构却不一定,我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。
4. 从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有必要成为虚函数。
5. 当一个构造函数被调用时,它做的首要的事情之一是初始化它的VPTR。因此,它只能知道它是“当前”类的,而完全忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是为它的派生类(因为类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。而且,只要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE, 但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的 VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置VPTR指向它自己的VTABLE。如果函数调用使用虚机制,它将只产生通过它自己的VTABLE的调用,而不是最后的VTABLE(所有构造函数被调用后才会有最后的VTABLE)。
如果析构函数不是虚函数,当你使用基类的指针指向子类对象的时候,例如 Base *b = &derived。当执行delete b 的时候,只会调用Base的析构函数释放空间,而不会调用子类的析构函数,因此造成了资源浪费。
39.new 和 operator new有啥区别?
40. 前置申明对象理解
参见: http://software.intel.com/zh-cn/blogs/2010/05/04/c-2/
41.不要改变虚函数中 参数的默认值,因为参数都是静态绑定,即使你在子类虚函数中改变默认值,仍然无效,它还是显示基类的默认值。
#include<iostream>
using namespace std;
class A
{
public:
virtual void test(int i = 2)
{
cout << i << endl;
}
};
class B:public A
{
public:
virtual void test(int i = 3)
{
cout << i << endl;
cout << "test" << endl;
}
};
int main()
{
A* p = new B();
p->test(); //output: 2 test
return 0;
}