Sunday, October 31, 2010

游魂

    我把过去的一周称之为游魂,因为我绞尽脑汁也不知道究竟这周做了什么,唯有天天陪着mm去笔试面试,自己面了一次,其他的时间我一直在游魂。像女人逛街一样在互联网上游荡,或是弄点美剧,再者就是玩点弱智游戏。
    提起毕设,总是觉得很痛苦,这个技术不太愿意去搞,一方面兴趣不大,另一方面和我以后的工作基本没有关系,但是又没有办法,硬着头皮看了点文章,却无法思考。
    不过一切都是借口,都是为自己的不坚定和懒惰开脱,明知道自己可以做下去,而且曾经这样做过,却还是在找借口往后推。
    该是刹车的时候了,放开其他的过虑和担忧,一心扑在当下
  • 毕设首当其冲 重要紧急
  • 累了的时候,看看技术书籍 重要
  • 与人交流,锻炼身体 培养习惯

Saturday, October 30, 2010

游魂

    我把过去的一周称之为游魂,因为我绞尽脑汁也不知道究竟这周做了什么,唯有天天陪着mm去笔试面试,自己面了一次,其他的时间我一直在游魂。像女人逛街一样在互联网上游荡,或是弄点美剧,再者就是玩点弱智游戏。
    提起毕设,总是觉得很痛苦,这个技术不太愿意去搞,一方面兴趣不大,另一方面和我以后的工作基本没有关系,但是又没有办法,硬着头皮看了点文章,却无法思考。
    不过一切都是借口,都是为自己的不坚定和懒惰开脱,明知道自己可以做下去,而且曾经这样做过,却还是在找借口往后推。
    该是刹车的时候了,放开其他的过虑和担忧,一心扑在当下
  • 毕设首当其冲 重要紧急
  • 累了的时候,看看技术书籍 重要
  • 与人交流,锻炼身体 培养习惯

Wednesday, October 20, 2010

[zz]C++虚函数表解析

转载自http://www.cppblog.com/xczhang/archive/2008/01/20/41508.html
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的 成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技 术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家 一个清晰的剖析。
当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。
言归正传,让我们一起进入虚函数的世界。

虚函数表

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为 了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。
假设我们有这样的一个类:
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout << "虚函数表地址:" << (int*)(&b) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)
虚函数表地址:0012FED4
虚函数表 — 第一个函数地址:0044F148
Base::f
通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:
(Fun)*((int*)*(int*)(&b)+0); // Base::f()
(Fun)*((int*)*(int*)(&b)+1); // Base::g()
(Fun)*((int*)*(int*)(&b)+2); // Base::h()
这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的 结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。
下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

一般继承(无虚函数覆盖)

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:
对于实例:Derive d; 的虚函数表如下:

我们可以看到下面几点:
1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面。
我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

一般继承(有虚函数覆盖)

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

我们从表中可以看到下面几点,
1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
2)没有被覆盖的函数依旧。
这样,我们就可以看到对于下面这样的程序,
Base *b = new Derive();
b->f();
由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

对于子类实例中的虚函数表,是下面这个样子:

我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。
下图中,我们在子类中覆盖了父类的f()函数。

下面是对于子类实例中的虚函数表的图:

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:
Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()

安全性

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。
一、通过父类型的指针访问子类自己的虚函数
我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:
Base1 *b1 = new Derive();
b1->f1(); //编译出错
任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)
二、访问non-public的虚函数
另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。
如:
class Base {
private:
virtual void f() { cout << "Base::f" << endl; }
};
class Derive : public Base{
};
typedef void(*Fun)(void);
void main() {
Derive d;
Fun pFun = (Fun)*((int*)*(int*)(&d)+0);
pFun();
}

结束语

C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

[zz]C++虚函数表解析

转载自http://www.cppblog.com/xczhang/archive/2008/01/20/41508.html
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的 成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技 术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家 一个清晰的剖析。
当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。
言归正传,让我们一起进入虚函数的世界。

虚函数表

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。 在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了 这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为 了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。
假设我们有这样的一个类:
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout << "虚函数表地址:" << (int*)(&b) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)
虚函数表地址:0012FED4
虚函数表 — 第一个函数地址:0044F148
Base::f
通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:
(Fun)*((int*)*(int*)(&b)+0); // Base::f()
(Fun)*((int*)*(int*)(&b)+1); // Base::g()
(Fun)*((int*)*(int*)(&b)+2); // Base::h()
这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的 结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。
下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

一般继承(无虚函数覆盖)

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:
对于实例:Derive d; 的虚函数表如下:

我们可以看到下面几点:
1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面。
我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

一般继承(有虚函数覆盖)

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

我们从表中可以看到下面几点,
1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
2)没有被覆盖的函数依旧。
这样,我们就可以看到对于下面这样的程序,
Base *b = new Derive();
b->f();
由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

对于子类实例中的虚函数表,是下面这个样子:

我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。
下图中,我们在子类中覆盖了父类的f()函数。

下面是对于子类实例中的虚函数表的图:

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:
Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()

安全性

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。
一、通过父类型的指针访问子类自己的虚函数
我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:
Base1 *b1 = new Derive();
b1->f1(); //编译出错
任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)
二、访问non-public的虚函数
另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。
如:
class Base {
private:
virtual void f() { cout << "Base::f" << endl; }
};
class Derive : public Base{
};
typedef void(*Fun)(void);
void main() {
Derive d;
Fun pFun = (Fun)*((int*)*(int*)(&d)+0);
pFun();
}

结束语

C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

类型转换-基类和派生类之间的转换

    对于内置类型,类型之间的转换比较明显,而且接触得比较多,但是对于自定义类型,尤其是基类和派生类之间到底可以有哪些转换我还是比较模糊,翻了翻书,同时自己试了试,总结如下(如有不对地方,欢迎支持):
    1.子类转成父类
using namespace std;
class A {
    public:
        void display()
        {
            cout << "in A" << endl;
        }
};

class B : public A {
    public:
        void display()
        { 
            cout << "in B" << endl;
        }
};

int main(int argc, char *argv[])

    A a;
    a.display();// A
    B b;
    //a = b; 隐式转换
    //两种旧的强制转换
    //a = A(b); function-style cast
    //a = (A)b; c-style cast
    //推荐
    b.display(); //B
    a = static_cast<A>(b);
    a.display();          // A         
}
当然转了的时候,b就转成了a

2. 父类转子类
假如参考上面的做法,将a转换给b的话,4种方法都是不可行的,那么父类在什么情况下可以转成子类呢?
参考了c++ primer的dynamic_cast操作符的解释:
可以使用dynamic_cast操作符将基类类型对象的引用或指针转换为同一层次中其他类型的引用或者指针。与dynamic_cast一起使用的指针必须是邮箱的--为0或者指向一个对象。
注意:dynamic_cast涉及运行时类型检查,如果绑定到引用或者指针的对象不是目标对象,则dynamic_cast失败的(我认为本质上指针指向的实际对象还是和目标同类型,只是指针是基类而已)。如果转换到指针类型的dynamic_cast失败,则dynamic_cast的结果为0;如果转换到引用类型的dynamic_cast失败,则抛出一个bad_cast类型的异常。
同时,需要基类至少带有一个虚函数,(这点我认为是因为运行时类型检查,类似多态) 

例子:
2.1 目标类型和运行时类型不一致,dynamic_cast的结果为0
class A {
    public:
    virtual void test()
    {
        cout << "A" << endl;
    }
};

class B : public A {
    public:
    void test()
    {
        cout << "B" << endl;
    }
};

int main(int argc, char *argv[])
{
    A *a = new A();
    B *b = dynamic_cast<B*>(a); // can't
    a->test();
    if (b != NULL ) {
        b->test();
    }
}
output:
A
2.2 没有虚函数:error
class A {
    public:
    void test()       
    {
        cout << "A" << endl;
    }
};

class B : public A {
    public:
    void test()
    {
        cout << "B" << endl;
    }
};

int main(int argc, char *argv[])
{
    A *a = new B();
    B *b = dynamic_cast<B*>(a); // 没有虚函数can't
    a->test();
    if (b != NULL ) {
        b->test();
    }
}
compiler error:cannot dynamic_cast ‘a’ (of type ‘class A*’) to type ‘class B*’ (source type is not polymorphic)  

假如目标类型和运行时类型一致,且基类含虚函数的话,即可以
如   
class A {
    public:
    virtual void test()
    {
        cout << "A" << endl;
    }
};

class B : public A {
    public:
    void test()
    {
        cout << "B" << endl;
    }
};

int main(int argc, char *argv[])
{
    A *a = new B();
    B *b = dynamic_cast<B*>(a); // can't
    a->test();
    if (b != NULL ) {
        b->test();
    }
}
                                                
output:   
B
B                                  
    

Tuesday, October 19, 2010

类型转换-基类和派生类之间的转换

    对于内置类型,类型之间的转换比较明显,而且接触得比较多,但是对于自定义类型,尤其是基类和派生类之间到底可以有哪些转换我还是比较模糊,翻了翻书,同时自己试了试,总结如下(如有不对地方,欢迎支持):
    1.子类转成父类
using namespace std;
class A {
    public:
        void display()
        {
            cout << "in A" << endl;
        }
};

class B : public A {
    public:
        void display()
        { 
            cout << "in B" << endl;
        }
};

int main(int argc, char *argv[])

    A a;
    a.display();// A
    B b;
    //a = b; 隐式转换
    //两种旧的强制转换
    //a = A(b); function-style cast
    //a = (A)b; c-style cast
    //推荐
    b.display(); //B
    a = static_cast<A>(b);
    a.display();          // A         
}
当然转了的时候,b就转成了a

2. 父类转子类
假如参考上面的做法,将a转换给b的话,4种方法都是不可行的,那么父类在什么情况下可以转成子类呢?
参考了c++ primer的dynamic_cast操作符的解释:
可以使用dynamic_cast操作符将基类类型对象的引用或指针转换为同一层次中其他类型的引用或者指针。与dynamic_cast一起使用的指针必须是邮箱的--为0或者指向一个对象。
注意:dynamic_cast涉及运行时类型检查,如果绑定到引用或者指针的对象不是目标对象,则dynamic_cast失败的(我认为本质上指针指向的实际对象还是和目标同类型,只是指针是基类而已)。如果转换到指针类型的dynamic_cast失败,则dynamic_cast的结果为0;如果转换到引用类型的dynamic_cast失败,则抛出一个bad_cast类型的异常。
同时,需要基类至少带有一个虚函数,(这点我认为是因为运行时类型检查,类似多态) 

例子:
2.1 目标类型和运行时类型不一致,dynamic_cast的结果为0
class A {
    public:
    virtual void test()
    {
        cout << "A" << endl;
    }
};

class B : public A {
    public:
    void test()
    {
        cout << "B" << endl;
    }
};

int main(int argc, char *argv[])
{
    A *a = new A();
    B *b = dynamic_cast<B*>(a); // can't
    a->test();
    if (b != NULL ) {
        b->test();
    }
}
output:
A
2.2 没有虚函数:error
class A {
    public:
    void test()       
    {
        cout << "A" << endl;
    }
};

class B : public A {
    public:
    void test()
    {
        cout << "B" << endl;
    }
};

int main(int argc, char *argv[])
{
    A *a = new B();
    B *b = dynamic_cast<B*>(a); // 没有虚函数can't
    a->test();
    if (b != NULL ) {
        b->test();
    }
}
compiler error:cannot dynamic_cast ‘a’ (of type ‘class A*’) to type ‘class B*’ (source type is not polymorphic)  

假如目标类型和运行时类型一致,且基类含虚函数的话,即可以
如   
class A {
    public:
    virtual void test()
    {
        cout << "A" << endl;
    }
};

class B : public A {
    public:
    void test()
    {
        cout << "B" << endl;
    }
};

int main(int argc, char *argv[])
{
    A *a = new B();
    B *b = dynamic_cast<B*>(a); // can't
    a->test();
    if (b != NULL ) {
        b->test();
    }
}
                                                
output:   
B
B                                  
    

Monday, October 18, 2010

c专家编程-对链接的思考

    本章主要是对如何link的思考,包括编译的过程,编译时候的选项,动态连接,静态链接等等,另外就是要提防interpositioning(编写与库函数同名函数)。
    给个直观的图来说明编译器的组成:

    静态链接:如果函数库的一份copy是可执行文件的物理组成部分。以.a结尾
    动态链接:如果可执行文件只是包含了文件名,让载入器在运行时能够寻找程序所需要的函数库。即just in time JIT 链接以.so结尾
    直观上的,静态库要比动态库大。
    动态链接的优点在于体积小,以及可以共享函数库,同时,函数库升级更加容易
    关于链接相关知识,还可以参考Makefile文件的编写,chinaunix的这个写得很有味道

Sunday, October 17, 2010

c专家编程-对链接的思考

    本章主要是对如何link的思考,包括编译的过程,编译时候的选项,动态连接,静态链接等等,另外就是要提防interpositioning(编写与库函数同名函数)。
    给个直观的图来说明编译器的组成:

    静态链接:如果函数库的一份copy是可执行文件的物理组成部分。以.a结尾
    动态链接:如果可执行文件只是包含了文件名,让载入器在运行时能够寻找程序所需要的函数库。即just in time JIT 链接以.so结尾
    直观上的,静态库要比动态库大。
    动态链接的优点在于体积小,以及可以共享函数库,同时,函数库升级更加容易
    关于链接相关知识,还可以参考Makefile文件的编写,chinaunix的这个写得很有味道

Wednesday, October 13, 2010

c风格字符串的疑问

最近再看c++ primer,比本科时候看的时候体会要深得多,以前看来真的是打酱油的。
看到c风格字符串的时候,有了几个疑问,如下:
程序 1 如下:
#include
#include
#include

using namespace std;
int main(int argc, char *argv[])
{
const char ca[] = {'h', 'e', 'l', 'l', 'o'};
cout << strlen(ca) << endl;
int i = 0;
while (ca[i] != '\0') {
cout << ca[i++] << endl;
}
cout << strlen(ca) << endl;
}
windows xp下的mingw 结果
D:\c_c_plus>a.exe
5
h
e
l
l
o

6
当时我非常纳闷怎么ca的长度会变化了(5,6),试了几次结果都是这样,没有想明白,于是换到了linux下同样的程序,结果如下
sulong@sulong-desktop:~/Documents/c_c_plus$ ./a.out 
8
h
e
l
l
o

6
这里,ca的长度再次变化了(5,8),我的第一反应是在此好像strlen对ca不起作用了,仔细看书,归结出原因在此:
  • strlen等标准库函数的参数是c风格字符串(c++ primer中描述到,传递给这些函数的指针必须具有非零值,而且指向以null结束的字符数组中的第一个元素); 
  • 而c风格字符串有一点值得注意,需要以null结束,如char ca1[] = {'1', '2'}就不是,而char ca2={'1', '2', '\0'}则是,同时字符串字面量也是c风格字符串的实例; 
  • strlen总是假定其参数字符串以null字符结束,当调用该函数时,系统将会从实参指向的内存空间开始一致搜索结束符,知道恰好遇到null位置,strlen返回的这一段内存空间内总共有多少个字符; 
  • 当实参是非c风格字符的时候,这个数值是不可预知的;
了解原因之后,修改程序,程序 2 和结果如下(windows和linux都正确,这里只列出linux的):
#include
#include
#include

using namespace std;
int main(int argc, char *argv[])
{
const char ca[] = {'h', 'e', 'l', 'l', 'o', '\0'};
cout << strlen(ca) << endl;
int i = 0;
while (ca[i] != '\0') {
cout << ca[i++] << endl;
}
cout << strlen(ca) << endl;
}

output:
5
h
e
l
l
o
5
不过有一点不是很明白,对于第一个程序,为什么对ca进行解引用之后,ca的长度变化了?windows(5,6),linux(8,6),我的猜测是c++允许计算数组的超出末端的地址,但是不允许对此地址进行解引用操作,否则结果是未定义的。
除了strlen函数,我同时测试了其他cstring中的库函数,参数必须严格安装说明和建议,否则结果也是未定义的,如下 程序 3 :
#include
#include

using namespace std;

int main(int argc, char* argv[])
{
const char *c1 = "hello";
const char *c2 = "world";
char pc[5 + 5 + 1];
strncpy(pc, c1, 5);

cout << pc << endl;
}

output:
hello6
因为strncpy(pc, c1, 5)的5只够存储hello,而null字符也是需要空间的,使用的时候,时刻记住一定要算上结束符null,需要修改为6以上(当然不能超过pc的size),结果才能正确输出为hello。

两点感悟:
1. 少用c风格字符串,用string
2. 多多编程测试细节

Tuesday, October 12, 2010

c风格字符串的疑问

最近再看c++ primer,比本科时候看的时候体会要深得多,以前看来真的是打酱油的。
看到c风格字符串的时候,有了几个疑问,如下:
程序 1 如下:
#include
#include
#include

using namespace std;
int main(int argc, char *argv[])
{
    const char ca[] = {'h', 'e', 'l', 'l', 'o'};
    cout << strlen(ca) << endl;
    int i = 0;
    while (ca[i] != '\0') {
        cout << ca[i++] << endl;
    }
    cout << strlen(ca) << endl;
}
windows xp下的mingw 结果
D:\c_c_plus>a.exe
5
h
e
l
l
o

6
当时我非常纳闷怎么ca的长度会变化了(5,6),试了几次结果都是这样,没有想明白,于是换到了linux下同样的程序,结果如下
sulong@sulong-desktop:~/Documents/c_c_plus$ ./a.out 
8
h
e
l
l
o

6
这里,ca的长度再次变化了(5,8),我的第一反应是在此好像strlen对ca不起作用了,仔细看书,归结出原因在此:
  • strlen等标准库函数的参数是c风格字符串(c++ primer中描述到,传递给这些函数的指针必须具有非零值,而且指向以null结束的字符数组中的第一个元素); 
  • 而c风格字符串有一点值得注意,需要以null结束,如char ca1[] = {'1', '2'}就不是,而char ca2={'1', '2', '\0'}则是,同时字符串字面量也是c风格字符串的实例; 
  • strlen总是假定其参数字符串以null字符结束,当调用该函数时,系统将会从实参指向的内存空间开始一致搜索结束符,知道恰好遇到null位置,strlen返回的这一段内存空间内总共有多少个字符; 
  • 当实参是非c风格字符的时候,这个数值是不可预知的;
了解原因之后,修改程序,程序 2 和结果如下(windows和linux都正确,这里只列出linux的):
#include
#include
#include

using namespace std;
int main(int argc, char *argv[])
{
    const char ca[] = {'h', 'e', 'l', 'l', 'o', '\0'};
    cout << strlen(ca) << endl;
    int i = 0;
    while (ca[i] != '\0') {
        cout << ca[i++] << endl;
    }
    cout << strlen(ca) << endl;
}

output:
5
h
e
l
l
o
5
不过有一点不是很明白,对于第一个程序,为什么对ca进行解引用之后,ca的长度变化了?windows(5,6),linux(8,6),我的猜测是c++允许计算数组的超出末端的地址,但是不允许对此地址进行解引用操作,否则结果是未定义的。
除了strlen函数,我同时测试了其他cstring中的库函数,参数必须严格安装说明和建议,否则结果也是未定义的,如下 程序 3 :
#include
#include

using namespace std;

int main(int argc, char* argv[])
{
    const char *c1 = "hello";
    const char *c2 = "world";
    char pc[5 + 5 + 1];
    strncpy(pc, c1, 5);
    
    cout << pc << endl;
}

output:
hello6
因为strncpy(pc, c1, 5)的5只够存储hello,而null字符也是需要空间的,使用的时候,时刻记住一定要算上结束符null,需要修改为6以上(当然不能超过pc的size),结果才能正确输出为hello。

两点感悟:
1. 少用c风格字符串,用string
2. 多多编程测试细节

Sunday, October 10, 2010

c专家编程-数组和指针的恩怨情仇

这里把4,9,10章的内容结合在一起,主要谈论的是数组,指针的使用,以及何时相同,何时不同。
通常情况下对于数组和指针单独使用的时候,还是比较清晰的,这里就只简单提下容易混的地方。

什么时候数组和指针式相同的
c语言标准作了如下说明:
    规则1. 表达式中的数组名(与声明不同)被编译器当做一个指向数组第一个元素的指针
int a[10], *p, i = 2;
p = a;
p[i];
p = a;
*(p + i);
p = a + i;
*p是等同的
需要声明这里有极其特殊的理我,对数组的引用不能用指向数组第一个元素的指针来代替
sizefo的时候,sizeof(数组)是数组的大学,而sizeof(指针)是指针的长度
    规则2. 下标总是与真正的偏移量相同
c语言把数组下标改成指针偏移量的根本原因是指针和偏移量是底层硬件使用的基本类型
使用&去数组的地址
数组是一个字符串(或宽字符串)常量初始值
    规则3. 在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针
作为形参的数组和指针等同主要出于效率的考虑,假如用传值,传递整个数组代价很大,而指针则不同
这里建议参数定义为指针

除上述情况外,定义和指针必须匹配,如果定义数组,在其他文件对他进行声明时候,必须声明为数组,指针也是

同时需要注意的是,数组名被改写成一个指针参数 并不是地规定义的,数组的数组改写为数组的指针,而不是指针的指针,本来是数组的需要改变,而本来是指针的不需要改变

其他:关于多维数组以及字符串数组等情况不再说明,需要详细参考原文啦:)
--
BlogSpot http://xusulong.blogspot.com Twitter  http://twitter.com/econsh

c专家编程-分析c语言的声明

本章主要说明声明如何构成,如何去解读声明,以及哪些声明是非法的,包括对typedef的详解

1. 声明合法与否
a. 函数的返回值不能是一个函数,如f()(),可以是函数指针,如int(* fun())();
b. 函数的返回值不能是一个数组,如f()[],可以是指向数组的指针,如int(*foo())[];
c. 数组里面不能有函数,如a[](),数组里面可以有函数指针,如int(* a[])(),可以有其他数组,如
int a[][]

2. c语言声明的优先级规则
        A 声明从它的名字开始读取,然后按照优先级顺序依次读取;

B 优先级从高到低依次是:

B.1 声明中被括号括起来的那部分;

B.2 后缀操作符:括号()表示这是一个函数,而方括号[]表示这是一个数组;

B.3 前缀操作符:星号*标识“指向……的指针”;

C 如果const和(或者)volatile关键字的后面紧跟类型说明符(如int,long等),那么它作用于类型说明符,在其他情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。
举例说明:char * const * (*next)();
        A      next                ——next为声明的名字

B.1 (*next) ——next为一个指向……的指针

B.2 (*next)() ——next是一个函数指针

B.3 *(*next)() ——next是一个函数指针,这个函数返回一个指向……的指针

C char * const ——指向字符类型的常量指针
故 char * const *(*next)();的含义就是: next是一个函数指针,这个函数返回一个指向字符类型的常量指针

3. 图示解析c语言的声明
 此图也是一种解析c声明的方法,不过2中的ABC的方式更加简单明了

c专家编程-数组和指针的恩怨情仇

这里把4,9,10章的内容结合在一起,主要谈论的是数组,指针的使用,以及何时相同,何时不同。
通常情况下对于数组和指针单独使用的时候,还是比较清晰的,这里就只简单提下容易混的地方。

什么时候数组和指针式相同的
c语言标准作了如下说明:
    规则1. 表达式中的数组名(与声明不同)被编译器当做一个指向数组第一个元素的指针
int a[10], *p, i = 2;
p = a;
p[i];
p = a;
*(p + i);
p = a + i;
*p是等同的
需要声明这里有极其特殊的理我,对数组的引用不能用指向数组第一个元素的指针来代替
sizefo的时候,sizeof(数组)是数组的大学,而sizeof(指针)是指针的长度
    规则2. 下标总是与真正的偏移量相同
c语言把数组下标改成指针偏移量的根本原因是指针和偏移量是底层硬件使用的基本类型
使用&去数组的地址
数组是一个字符串(或宽字符串)常量初始值
    规则3. 在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针
作为形参的数组和指针等同主要出于效率的考虑,假如用传值,传递整个数组代价很大,而指针则不同
这里建议参数定义为指针

除上述情况外,定义和指针必须匹配,如果定义数组,在其他文件对他进行声明时候,必须声明为数组,指针也是

同时需要注意的是,数组名被改写成一个指针参数 并不是地规定义的,数组的数组改写为数组的指针,而不是指针的指针,本来是数组的需要改变,而本来是指针的不需要改变

其他:关于多维数组以及字符串数组等情况不再说明,需要详细参考原文啦:)
--
BlogSpot http://xusulong.blogspot.com Twitter  http://twitter.com/econsh

c专家编程-分析c语言的声明

本章主要说明声明如何构成,如何去解读声明,以及哪些声明是非法的,包括对typedef的详解

1. 声明合法与否
a. 函数的返回值不能是一个函数,如f()(),可以是函数指针,如int(* fun())();
b. 函数的返回值不能是一个数组,如f()[],可以是指向数组的指针,如int(*foo())[];
c. 数组里面不能有函数,如a[](),数组里面可以有函数指针,如int(* a[])(),可以有其他数组,如
int a[][]

2. c语言声明的优先级规则
        A 声明从它的名字开始读取,然后按照优先级顺序依次读取;

        B 优先级从高到低依次是:

            B.1 声明中被括号括起来的那部分;

            B.2 后缀操作符:括号()表示这是一个函数,而方括号[]表示这是一个数组;

            B.3 前缀操作符:星号*标识“指向……的指针”;

        C 如果const和(或者)volatile关键字的后面紧跟类型说明符(如int,long等),那么它作用于类型说明符,在其他情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。
举例说明:char * const * (*next)();
        A      next                ——next为声明的名字

        B.1 (*next)              ——next为一个指向……的指针

        B.2 (*next)()          ——next是一个函数指针

        B.3 *(*next)()         ——next是一个函数指针,这个函数返回一个指向……的指针

        C    char * const     ——指向字符类型的常量指针
故 char * const *(*next)();的含义就是: next是一个函数指针,这个函数返回一个指向字符类型的常量指针

3. 图示解析c语言的声明
 此图也是一种解析c声明的方法,不过2中的ABC的方式更加简单明了

c专家编程-这货不是bug,而是语言特性

本章从c语言的一些看上去有点缺陷的地方来提醒我们对相应的知识点需要加倍注意

1 switch的fall through,这个很明了,case后记得加break,否则依次执行

2 字符串会自动连接,如
#include

int main(int agrc, char* argv[])
{
printf("hello"
"world \n");
}
会打印出结果helloworld,这个时候要注意,在下面例子中
char * a[] = {
"one",
"two"
"three"
};
因为"two" 之后少了逗号","而变成了"one"和"twothree"组成的字符串数组

3 优先级以及操作符的重载(比如*可以是乘法,也用于指针),有时候并不像想象的很自然的意思,需要对优先级更加理解和掌握

4 局部变量在堆栈分配内存,函数退出,内存被回收问题,可以通过用全局变量,静态变量,显示分配内存,让调用者提供内存(传入以分配内存指针)等等方式来解决

5 lint程序不应该分出来,主要意思是,代码需要更多的检验

c专家编程-穿越时空的迷雾

这是本书的第一部分,主要讲述c语言的前世今生,这里点一下几点
1     K&R C,即Brain KernighanDennis Ritchie
2     ANSI C,这里面说的很幽默,其实ANSI C应该叫做ISO C,因为ANSI采纳的是ISO C,因为在标准之前,已经交了ANSI C,已经广泛使用了。
3     可移植的代码( portable code):严格遵循标准的程序应该是这样的
3.1    只使用已经确定的特性(在某些正确情况下的做法,标准并未明确规定应该怎样做,如参数求职顺序)
3.2    不突破任何由编译器实现的限制
3.3    不产生任何依赖与编译器定义的未确定的或未定义的特性的输出
4     多多阅读ANSI C,里面对细节的问题,描述的很清楚,有相应的约束条件
4.1    里面例举了const char **p的形参,char ** a的实参不相容的例子来说名赋值如何合法等等

Saturday, October 9, 2010

c专家编程-这货不是bug,而是语言特性

本章从c语言的一些看上去有点缺陷的地方来提醒我们对相应的知识点需要加倍注意

1 switch的fall through,这个很明了,case后记得加break,否则依次执行

2 字符串会自动连接,如
#include

int main(int agrc, char* argv[])
{
    printf("hello"
            "world \n");
}
会打印出结果helloworld,这个时候要注意,在下面例子中
char * a[] = {
    "one",
    "two"
    "three"
};
因为"two" 之后少了逗号","而变成了"one"和"twothree"组成的字符串数组

3 优先级以及操作符的重载(比如*可以是乘法,也用于指针),有时候并不像想象的很自然的意思,需要对优先级更加理解和掌握

4 局部变量在堆栈分配内存,函数退出,内存被回收问题,可以通过用全局变量,静态变量,显示分配内存,让调用者提供内存(传入以分配内存指针)等等方式来解决

5 lint程序不应该分出来,主要意思是,代码需要更多的检验

c专家编程-穿越时空的迷雾

这是本书的第一部分,主要讲述c语言的前世今生,这里点一下几点
1     K&R C,即Brain KernighanDennis Ritchie
2     ANSI C,这里面说的很幽默,其实ANSI C应该叫做ISO C,因为ANSI采纳的是ISO C,因为在标准之前,已经交了ANSI C,已经广泛使用了。
3     可移植的代码( portable code):严格遵循标准的程序应该是这样的
3.1    只使用已经确定的特性(在某些正确情况下的做法,标准并未明确规定应该怎样做,如参数求职顺序)
3.2    不突破任何由编译器实现的限制
3.3    不产生任何依赖与编译器定义的未确定的或未定义的特性的输出
4     多多阅读ANSI C,里面对细节的问题,描述的很清楚,有相应的约束条件
4.1    里面例举了const char **p的形参,char ** a的实参不相容的例子来说名赋值如何合法等等

Friday, October 8, 2010

初读C专家编程(Expert C Programming)

    国庆前入手c专家编程,甚为喜欢,虽然假期回家做了不少活,但是依然看得津津有味,粗略地完成了10章,习题之类的并没有去做,回顾的时候会去试试,先有个总体的概念也比较不错。
    Expert C Programming其实是tooold的书了,94年,但因为ANSI C并没有很大的改动,以及所述内容的典型和有趣,一直畅销。
    书的style不像其他教条的书籍,很多故事充斥其中,让你豁然开朗,也会令人捧腹,其中多次调侃sun公司,也设计了apple,以及不少知名it公司和名人。
    在讲述每个知识点的时候,通常会涉及以下内容
  • 阐明观点
  • 铺开来陈述原理,包括为何ansi c这么去定规则,规则的细细剖析
  • 类似知识点,或者容易混淆的知识点,之间的比较
  • 例子
  • 图示
  • 编程挑战
  • 轻松一下,回顾过往因为相关知识点引起的bug造成的趣闻等
通过这些方面的陈述,对一个知识点的理解慢慢加深,搞清楚所以然来
    另外,本书并不是一本c语言的语法,使用等的详细的讲解,而是对其中比较关键的点,难点进行的仔细剖析,需要少许的c语言基础。推荐下C程序设计语言,徐大宝文老师翻译的Brian W. Kernighan和Dennis M. Ritchie的经典书籍。
    后续将回顾每章的知识点:)

Thursday, October 7, 2010

初读C专家编程(Expert C Programming)

    国庆前入手c专家编程,甚为喜欢,虽然假期回家做了不少活,但是依然看得津津有味,粗略地完成了10章,习题之类的并没有去做,回顾的时候会去试试,先有个总体的概念也比较不错。
    Expert C Programming其实是tooold的书了,94年,但因为ANSI C并没有很大的改动,以及所述内容的典型和有趣,一直畅销。
    书的style不像其他教条的书籍,很多故事充斥其中,让你豁然开朗,也会令人捧腹,其中多次调侃sun公司,也设计了apple,以及不少知名it公司和名人。
    在讲述每个知识点的时候,通常会涉及以下内容
  • 阐明观点
  • 铺开来陈述原理,包括为何ansi c这么去定规则,规则的细细剖析
  • 类似知识点,或者容易混淆的知识点,之间的比较
  • 例子
  • 图示
  • 编程挑战
  • 轻松一下,回顾过往因为相关知识点引起的bug造成的趣闻等
通过这些方面的陈述,对一个知识点的理解慢慢加深,搞清楚所以然来
    另外,本书并不是一本c语言的语法,使用等的详细的讲解,而是对其中比较关键的点,难点进行的仔细剖析,需要少许的c语言基础。推荐下C程序设计语言,徐大宝文老师翻译的Brian W. Kernighan和Dennis M. Ritchie的经典书籍。
    后续将回顾每章的知识点:)