C++ 知识点1

C++标准库  Boost库

1>: 浮点数:0.12f  12.3E12f  0.12D  12.3E12L 分别表示什么意思?


后面的f,D和L分别是表示使用不同大小的内存来存储:
f代表float
D代表double
L代表long double,64位
中间E是表示科学记数法,例如12.3E12表示如图,E前的数表示乘号前的数,E后面的数表示10的多少次方。

//------------------
C++提供了四个转换运算符:
const_cast <new_type> (expression)
static_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
dynamic_cast <new_type> (expression)


结合网络上各个站点看到的关于C++转换符的知识,以及前面那些文章得到的反馈,可以将各个转换运算符的使用总结如下:


对于传统的转换方式(C式或函数式),只在数值类型(包括整型、浮点型、字符类型和枚举)上使用。这也是延续C的形式,当然这类转换也是可以用static_cast来替换,但是因为是基本类型,所以传统转换已经很直观。


对于const_cast转换运算符,用在需要去除掉const限定的时候。其实这种情况出现的很少,可能的方法在const_cast一文中已经又举例,不过还是反复强调, 使用const_cast转换后,绝对不可试图修改结果的值。


对于reinterpret_cast转换运算符,一般用在将对象指针类型转换到整数类型或者void * (空指针)。如同在文中举出的隐患,因此注意的是,若要使用其结果,一定要将类型转换回去后使用。也不要将随意的整数转换成指针类型。


对于static_cast转换运算符,将其用在对象的转换之上(虽然static_cast也可以用在有继承关系的类型指针之间,但是还是将这方面的转换交给dynamic_cast来操作吧), static_cast会调用相应的构造函数或者重载的转换运算符。


通过文章的留言反馈,以及Google C++ Style Guide的推荐格式,知道对于单参构造函数的存在可能会引发一些隐式的转换,因此用static_cast也可以明确的指出类型的转换过程,避免生成多余的临时对象造成效率下降。


对于dynamic_cast转换运算符,将其用在具有继承关系的指针类型之间的转换。无论是从基类到子类的转换,还是子类到基类的转换,都将dynamic_cast套上去,也算是标识它们是一家子。


如果任何一种基于指针或引用的转换,套上四个转换运算符之后都失败,那么所要进行的转换可能就触到了"雷区"了:进行了没意义的转换。比如,对于没有关系的两个类型的指针进行了转换,比如试图转换指向方法的指针了。所以转换运算符对于避免代码出错也很有帮助。
//------------------
2>: reinterpret_cast
reinterpret_cast是C++里的强制类型转换符。
reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。
总结来说:reinterpret_cast用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。


(所谓"足够大的整数类型",取决于操作系统的参数,如果是32位的操作系统,就需要整形(int)以上的;如果是64位的操作系统,则至少需要长整形(long)。具体大小可以通过sizeof运算符来查看)。


比如下边的代码
typedef int (*FunctionPointer)(int); 
int value = 21; 
FunctionPointer funcP; 
funcP = reinterpret_cast<FunctionPointer> (&value); 
funcP(value);
我先用typedef定义了一个指向函数的指针类型,所指向的函数接受一个int类型作为参数。然后我用reinterpret_cast将一个整型的地址转换成该函数类型并赋值给了相应的变量。最后,我还用该整形变量作为参数交给了指向函数的指针变量。


这个过程编译器都成功的编译通过,不过一旦运行我们就会得到"EXC_BAD_ACCESS"的运行错误,因为我们通过funcP所指的地址找到的并不是函数入口。


由此可知,reinterpret_cast虽然看似强大,作用却没有那么广。IBM的C++指南、C++之父Bjarne Stroustrup的FAQ网页和MSDN的Visual C++也都指出:错误的使用reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式。


这样说起来,reinterpret_cast转换成其它类型的目的只是临时的隐藏自己的什么(做个卧底?),要真想使用那个值,还是需要让其露出真面目才行。那到底它在C++中有其何存在的价值呢?


MSDN的Visual C++ Developer Center 给出了它的使用价值:用来辅助哈希函数。下边是MSNDN上的例子:



// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>
// Returns a hash code based on an address
unsigned short Hash( void *p ) {
unsigned int val = reinterpret_cast<unsigned int>( p );
return ( unsigned short )( val ^ (val >> 16));
}


using namespace std;
int main() {
int a[20];
for ( int i = 0; i < 20; i++ )
cout << Hash( a + i ) << endl;
}
//如果跟我一样是64位的系统,可能需要将unsigned int改成 unsigned long才能运行。
这段代码适合体现哈希的思想,暂时不做深究,但至少看Hash函数里面的操作,也能体会到,对整数的操作显然要对地址操作更方便。在集合中存放整形数值,也要比存放地址更具有扩展性(当然如果存void *扩展性也是一样很高的),唯一损失的可能就是存取的时候整形和地址的转换(这完全可以忽略不计)。


3>:dynamic_cast < type-id > ( expression )
该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*;
如果type-id是类指针类型,那么expression也必须是一个指针,
如果type-id是一个引用,那么expression也必须是一个引用。

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<D *>(pb);
D *pd2 = dynamic_cast<D *>(pb);
}
在上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;
但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针。




B要有虚函数,否则会编译出错;static_cast则没有这个限制。
B中需要检测有虚函数的原因:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,
没有定义虚函数的类是没有虚函数表的。
另外,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<D *>(pb); //compile error
D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL
delete pb;
}
在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错,而使用 dynamic_cast的转换则是允许的,结果是空指针。


问题
1)什么时候应必须使用dynamic_cast
2)什么时候dynamic_cast可以使用static_cast代替
实例
// TestCast.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
using namespace std;
class Base
{
public:
virtual void f() { cout << "Base::f" << endl; }
void f1(){cout << "Base::f1" << endl;}
private:
double x;
double y;
};
class Derived : public Base
{
public:
virtual void f(){cout << "Derived::f" << endl; }
virtual void k(){cout << "Derived::k" << endl; }
private:
double z;
};
class Base1
{
public:
virtual void g(){ cout << "Base1::g" << endl;}
void g1(){cout << "Base1::g1" << endl;}
};
class Derived1 : public Base,public Base1
{
public:
virtual void f(){ cout << "Derived1::f" << endl;}
virtual void h(){ cout << "Derived1::h" << endl;}
};
void Test1()
{
// 对于单继承,
// 如果pD真的指向Derived,用dynamic_cast和static_cast效果相同
Base *pD = new Derived;
Derived *pD1 = dynamic_cast<Derived*>(pD);
pD1->f();
pD1->k();
pD1->f1();
Derived *pD2 = static_cast<Derived*>(pD);
pD2->f();
pD2->k();
pD2->f1();
// 但是如果pB不是真的指向Derived,则用dynamic_cast则返回NULL,能够更早的禁止error的发生,
// 如果用static_cast虽然返回的不为NULL,但是运行时可能抛出exception。
/**/ Error code
//Base *pB = new Base();
//Derived *pD3 = static_cast<Derived*>(pB);
//pD3->f();
//pD3->k();
//pD3->f1();
//Derived *pD4 = dynamic_cast<Derived*>(pB);
//pD4->f();
//pD4->k();
//pD4->f1();
}
void Test2()
{
// 对于多重继承,
// 如果pD真的指向的是Derived1,使用dynamic_cast和static_cast都可以转化为Derived1,
// 但是如果要转化为Base的兄弟类Base1,必须使用dynamic_cast,使用static_cast不能编译。
Base *pD = new Derived1;
Derived1 *pD1 = dynamic_cast<Derived1*>(pD);
pD1->f();
pD1->h();
pD1->f1();
Base1 *pB1 = dynamic_cast<Base1*>(pD);
pB1->g();
Derived1 *pD2 = static_cast<Derived1*>(pD);
pD2->f();
pD1->h();
pD2->f1();
/**/ error can not compiler
//Base1 *pB2 = static_cast<Base1*>(pD);
//pB2->g();
// 当然对于pB不是真的指向Derived1,想要转化为Derived1或Base的兄弟类Base1,情况与Test1中的error情况相同。
}
int _tmain(int argc, _TCHAR* argv[])
{
Test1();
Test2();
return 0;
}


4>:const_cast:很少用到
用法:const_cast<type_id> (expression)
const_cast转换符是用来移除变量的const或volatile限定符。注意使用表达式时type_id和expression的类型是一样的。
一、常量指针被转化成非常量指针,并且仍然指向原来的对象;
二、常量引用被转换成非常量引用,并且仍然指向原来的对象;

但是,我们真的通过modifier修改了constatn的值了吗?修改const变量的数据真的是C++去const的目的吗?
const int constant = 21;
const int* const_p = &constant;
int* modifier = const_cast<int*>(const_p);
*modifier = 7;


如果我们把结果打印出来:

cout << "constant: "<< constant <<endl;
cout << "const_p: "<< *const_p <<endl;
cout << "modifier: "<< *modifier <<endl;
/**
constant: 21
const_p: 7
modifier: 7
**/
constant还是保留了它原来的值。
可是它们的确指向了同一个地址呀:
cout << "constant: "<< &constant <<endl;
cout << "const_p: "<< const_p <<endl;
cout << "modifier: "<< modifier <<endl;


/**
constant: 0x7fff5fbff72c
const_p: 0x7fff5fbff72c
modifier: 0x7fff5fbff72c
**/

这真是一件奇怪的事情,但是这是件好事:说明C++里是const,就是const,外界千变万变,我就不变。不然真的会乱套了,const也没有存在的意义了。

5>: static_cast
用法:static_cast < type-id > ( expression )

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。

1、C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作。因此,被做为隐式类型转换使用。比如:
int i;
float f = 166.71;
i = static_cast<int>(f);
此时结果,i的值为166。
前面提到过reinterpret_cast可以用在"没有关系"的类型之间,而用static_cast来处理的转换就需要两者具有"一定的关系"了。

6>:c++中判断父类指针实际指向的子类类型
比如,精灵父类。。
其子类有 英雄 NPC 动态景物


解答:
1 c++里识别类型的是RTTI技术, 使用typeid操作符:
比如 有 class D1 : public B;  class D2 : public B
且有B* pB;那么
if(typeid(*pB)== typeid(D1))
{
// pB指向D1
}
else if(typeid(*pB)== typeid(D2))
{
// pB指向D2
}
else
{
// pB指向非D1,D2的其他对象
}



7>:volatile:volatile 是强制cpu去内存读取数据.
volatile:就象大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:无法编写多线程程序.
使用该关键字的例子如下:
int volatile nVint;
>>>>;当要求使用volatile 使用该变量的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。


8>:inline限定符
对函数用inline限定符是告知编译器,这段函数非常的简单,可以直接嵌入到调用定义之处。


当然inline的函数并不一定会被编译器作为inline来实现,如果函数过于复杂,编译器也会拒绝inline。


因此简单说来,代码最好短到只有3-5行的才作为inline。有循环,分支,递归的函数都不要用做inline。
对于在类定义内定义实现的函数,编译器自动当做有inline请求(也是不一定inline的)。因此在下边,我把带有inline限定符的函数成员和写在类定义体内的函数成员统称为“要inline的函数成员”


9>:数据成员初始化


数据成员的申明与定义都是放在.h文件的类定义内部。对于数据类型,关键问题是其初始化要放在什么地方进行。


对于只含有static限定符的数据成员,它的初始化要放在.cpp文件中。因为它是所有类对象共有的,因此必须对它做合适的初始化。


对于只含有const限定符的数据成员,它的初始化只能在构造函数的初始化列表中完成。因为它是一经初始化就不能重新赋值,因此它也必须进行合适的初始化。


对于既含有static限定符,又含有const限定符的数据成员,它的初始化和定义同时进行。它也是必须进行合适的初始化


对于既没有static限定符,又没有const限定符的数据成员,它的值只针对本对象可以随意修改,因此我们并不在意它的初始化什么时候进行。












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值