随笔 - 30, 文章 - 0, 评论 - 64, 引用 - 0
数据加载中……

类型转换高级 (Advacned Class Type-casting)

reinterpret_cast

reinterpret_cast 可以将一个指针转换为任意其它类型的指针
它也可以用来将一个指针转换为一个整型,或反之亦然。

这个操作符可以在互不相关的类之间进行指针转换,
操作的结果是简单的将一个指针的二进制数据(binary copy)复制到另一个指针。
对指针指向的内容不做任何检查或转换。

例如:

class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast<B*>(a);

reinterpret_cast 对所有指针的处理与传统的类型转换符所作的一模一样。

_________________________________________________________________

static_cast

static_cast 可以执行所有能够隐含执行的类型转换,以及它们的反向操作(即使这种方向操作是不允许隐含执行的)。

用于类的指针,也就是说,它允许将一个引申类的指针转换为其基类类型(这是可以被隐含执行的有效转换),同时也允许进行相反的转换:将一个基类转换为一个引申类类型。

在后面一种情况中,不会检查被转换的基类是否真正完全是目标类型的。例如下面的代码是合法的:

class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast(a);

static_cast除了能够对类指针进行操作,还可以被用来进行类中明确定义的转换,以及对基本类型的标准转换:

double d=3.14159265;
int i = static_cast<int>(d);

译者注:如果你对这部分看不太懂,请结合下面的dynamic_cast一起看,也许会帮助理解。











dynamic_cast

dynamic_cast 完全被用来进行指针的操作。它可以用来进行任何可以隐含进行的转换操作以及它们被用于多态类情况下的方向操作。

然而与static_cast不同的是,
 dynamic_cast 会检查后一种情况的操作是否合法,
也就是说它会检查类型转换操作是否会返回一个被要求类型的有效的完整的对象。

这种检查是在程序运行过程中进行的。如果被转换的指针所指向的对象不是一个被要求类型的有效完整的对象,返回值将会是一个空指针NULL 。

   class Base { virtual dummy(){}; };
class Derived : public Base { };


Base* b1 = new Derived;
Base* b2 = new Base;
Derived* d1 = dynamic_cast(b1); // succeeds Derived* d2 = dynamic_cast(b2); // fails: returns NULL

如果类型转换被用在引用(reference)类型上,而这个转换不可能进行的话,一个bad_cast 类型的例外(exception)将会被抛出:

  class Base { virtual dummy(){}; };
class Derived : public Base { };

Base* b1 = new Derived;
Base* b2 = new Base;
Derived d1 = dynamic_cast(b1); // succeeds Derived d2 = dynamic_cast(b2); // fails: exception thrown








const_cast

这种类型转换对常量const 进行设置或取消操作:

class C {};
const C * a = new C;
C * b = const_cast<C*> (a);

其他3种cast 操作符都不可以修改一个对象的常量属性(constness)。




typeid

ANSI-C++ 还定义了一个新的操作符叫做 typeid ,
它检查一个表达式的类型:

typeid (expression)

这个操作符返回一个类型为type_info的常量对象指针,这种类型定义在标准头函数中。这种返回值可以用操作符 == 和 != 来互相进行比较,
也可以用来通过name()函数获得一个描述数据类型或类名称的字符串,

例如:

    // typeid, typeinfo
            #include <iostream.h>
            #include <typeinfo>
            class CDummy { };
            int main () {
            CDummy* a,b;
            if (typeid(a) != typeid(b)) {
            cout << "a and b are of different types:\n";
            cout << "a is: " << typeid(a).name() << '\n';
            cout << "b is: " << typeid(b).name() << '\n';
            }
            return 0;
            }
            
a and b are of different types:
a is: class CDummy *
b is: class CDummy


posted @ 2008-12-08 12:58 henry08 阅读(457) | 评论 (0)编辑 收藏

名空间 (Namespaces)

标准名空间(Namespace std)


 

我们能够找到的关于名空间的最好的例子就是标准C++ 函数库本身。

如ANSI C++ 标准定义,
标准C++库中的所有类、对象和函数都是定义在名空间std下面的。

你可能已经注意到,我们在这个教程中全部忽略了这一点。作者决定这么做是因为这条规则几乎和ANSI 标准本身一样年轻 (1997) ,许多老一点的编译器并不兼容这条规则。

几乎所有的编译器,即使是那些与ANSI 标准兼容的编译器,都允许使用传统的头文件 (如iostream.h, stdlib.h, 等等),就像我们在这个教程中所使用的一样。


然而,ANSI标准完全重新设计了这些函数库,
利用了模板功能,
而且遵循了这条规则将所有的函数和变量定义在了名空间std下。

该标准为这些头文件定义了新的名字,对针对C++的文件基本上是使用同样的名字,但没有.h的扩展名,

例如, iostream.h 变成了iostream。

如果我们使用ANSI-C++ 兼容的包含文件,我们必须记住所有的函数、类和对象是定义在名空间 std 下面的,例如:

    // ANSI-C++ compliant hello world
            #include <iostream>
            int main () {
            std::cout << "Hello world in ANSI-C++\n";
return 0;
}
Hello world in ANSI-C++

更常用的方法是使用using namespace ,这样我们就不必在所有标准空间中定义的函数或对象前面总是使用范围操作符::了 :

    // ANSI-C++ compliant hello world (II)
            #include <iostream>
            using namespace std;
            int main () {
            cout << "Hello world in ANSI-C++\n";
            return 0;
            }
            
Hello world in ANSI-C++

对于STL 用户,强烈建议使用ANSI-compliant 方式来包含标准函数库。

posted @ 2008-12-08 12:33 henry08 阅读(335) | 评论 (0)编辑 收藏

模板与多文件工程

template <class T> // 最常用的:一个class 参数。

template <class T, class U> // 两个class 参数。

template <class T, int N> // 一个class 和一个整数。

template <class T = char> // 有一个默认值。

template <int Tfunc (int)> // 参数为一个函数。

从编译器的角度来看,模板不同于一般的函数或类。
它们在需要时才被编译(compiled on demand),
也就是说一个模板的代码直到需要生成一个对象的时候(instantiation)才被编译。
当需要instantiation的时候,编译器根据模板为特定的调用数据类型生成一个特殊的函数。


当工程变得越来越大的时候,程序代码通常会被分割为多个源程序文件。
在这种情况下,通常接口(interface)和实现(implementation)是分开的


用一个函数库做例子
     接口通常包括所有能被调用的函数的原型定义。
                                    它们通常被定义在以.h 为扩展名的头文件 (header file) 中;
      而实现 (函数的定义) 
                                    则在独立的C++代码文件中。


模板这种类似宏(macro-like) 的功能,对多文件工程有一定的限制:
函数或类模板的实现 (定义) 必须与原型声明在同一个文件中
也就是说我们不能再 将接口(interface)存储在单独的头文件中,
而必须将接口和实现放在使用模板的同一个文件中。

回到函数库的例子,如果我们想要建立一个函数模板的库,我们不能再使用头文件(.h) ,取而代之,我们应该生成一个模板文件(template file),将函数模板的接口和实现 都放在这个文件中 (这种文件没有惯用扩展名,除了不要使用.h扩展名或不要不加任何扩展名)。


在一个工程中多次包含同时具有声明和实现的模板文件并不会产生链接错误 (linkage errors),因为它们只有在需要时才被编译,而兼容模板的编译器应该已经考虑到这种情况,不会生成重复的代码。

posted @ 2008-12-08 12:03 henry08 阅读(451) | 评论 (0)编辑 收藏

静态成员(Static members)

静态成员与全域变量(global variable)具有相同的属性,但它享有类(class)的范围




C++ 标准,为了避免它们被多次重复声明,
在class的声明中只能够包括static member的原型(声明),
而不能够包括其定义(初始化操作)。
为了初始化一个静态数据成员,
我们必须在class之外(在全域范围内),
包括一个正式的定义,就像上面例子中做法一样。





在提醒一次,它其实是一个全域变量。唯一的不同是它的名字跟在class的后面。

就像我们会在class中包含static数据一样,我们也可以使它包含static 函数。
它们表示相同的含义:static函数是全域函数(global functions),但是像一个指定class的对象成员一样被调用。
它们只能够引用static 数据,永远不能引用class的非静态(nonstatic)成员。
它们也不能够使用关键字this,因为this实际引用了一个对象指针,
但这些 static函数却不是任何object的成员,而是class的直接成员。

posted @ 2008-12-04 18:34 henry08 阅读(326) | 评论 (0)编辑 收藏

定义一个class而没有明确定义构造函数的时候,编译器会自动假设两个重载的构造函数

实际上,当我们定义一个class而没有明确定义构造函数的时候,


编译器会自动假设两个重载的构造函数
 (默认构造函数"default constructor" 和复制构造函数"copy constructor")。


例如,对以下class:

   class CExample {
public:
int a,b,c;
void multiply (int n, int m) { a=n; b=m; c=a*b; };
};

没有定义构造函数,


编译器自动假设它有以下constructor 成员函数:

  • Empty constructor

    它是一个没有任何参数的构造函数,被定义为nop (没有语句)。它什么都不做。

    CExample::CExample () { };
  • Copy constructor

    它是一个只有一个参数的构造函数,该参数是这个class的一个对象,这个函数的功能是将被传入的对象(object)的所有非静态(non-static)成员变量的值都复制给自身这个object。

       CExample::CExample (const CExample& rv) {
    a=rv.a; b=rv.b; c=rv.c;
    }

必须注意:这两个默认构造函数(empty construction 和 copy constructor )

只有在没有其它构造函数被明确定义的情况下才存在。

如果任何其它有任意参数的构造函数被定义了,这两个构造函数就都不存在了。

在这种情况下,

如果你想要有empty construction 和 copy constructor ,

就必需要自己定义它们。

posted @ 2008-12-04 18:04 henry08 阅读(2013) | 评论 (5)编辑 收藏

C在struct里面可以用public、private,默认的是public,而C++默认是private。

C在struct里面可以用public、private,默认的是public,而C++默认是private。

C在struct里面可以用public、private,默认的是public,而C++默认是private。

posted @ 2008-12-04 18:01 henry08 阅读(2692) | 评论 (1)编辑 收藏

研究几个C/C++编译器

今天在这里看一个程序,做了一点修改,拿Dev-C++编译了一下,运行通过,只是有几个Warning。文章作者说他是用LCC-Win32编译的,上网查了一下,LCC-Win32现在已经收费了,只在天空下载到一个LCC-Win32 V3.0。LCC-Win32是个C语言编译器,编译出来的程序只有14K,比起Dev-C++的460K来真是小太多了。一直很疑惑Dev-C++编译出来的可执行文件怎么那么大,难道GCC在Windows下只能编译得那么大?还是我没有配置好?

在网上搜了一下其他Windows下的C++编译器,看到这篇《微软的免费 C++ 编译器》,提到了微软的Visual C++ Toolkit 2003。这套软件是免费的,但是小气的微软已经不再提供下载了。上网搜了一下,找到了微软网站的VCToolkitSetup.exe文件下载链接,当然这个链接已经不能用了,我直接扔到迅雷里下载,迅雷帮忙找到了两个下载地址:地址一地址二。下载下来查看了一下MD5,90D8B963CA196AA9855B2CA6C3174C14,没问题。

文章中说这个VC 7.1可以用来编译python和Firefox,不过我安装的时候安装程序自动重启了我的系统,555,今天不研究了,逃。

Update:好像C++程序开头加了#include <iostream>的话,生成的exe文件大小就会从20k左右上升到三、四百k。iostream不能不用啊,伤脑筋啊。另外,把编译器选项里的连接器->剥除附加信息设成yes,可以有效的缩小编译的程序的大小,大概可以从400多k缩小的200多k。
http://yskin.net/2006/10/cpp-compiler.html

posted @ 2008-12-03 13:50 henry08 阅读(6875) | 评论 (16)编辑 收藏

算法

void main()
{
    long a1 = 34, a2 = 23, a3 = 12;
    if( a1 > a2 )
    {
        long temp = a1;
        a1 = a2;
        a2 = temp;
    }
    if( a1 > a3 )
    {
        long temp = a1;
        a1 = a3;
        a3 = temp;
    }
    if( a2 > a3 )
    {
        long temp = a2;
        a2 = a3;
        a3 = temp;
    }
}
上面就在每个if后面的复合语句中定义了一个临时变量temp以借助编译器的静态分配内存功能来提供临时存放卡片的内存。上面的元素交换并没有按照前面所说映射成函数,是因为在这里其只有三条语句且容易理解。如果要将交换操作定义为一函数
如果要将交换操作定义为一函数,则应如下:
void Swap( long *p1, long *p2 )             void Swap( long &r1, long &r2 )
{                                           {
    long temp = *p1;                            long temp = r1;
    *p1 = *p2;                                  r1 = r2;
    *p2 = temp;                                 r2 = temp;
}                                           }
void main()                                 void main()
{                                           {
    long a1 = 34, a2 = 23, a3 = 12;             long a1 = 34, a2 = 23, a3 = 12;
    if( a1 > a2 )                               if( a1 > a2 )
        Swap( &a1, &a2 );                           Swap( a1, a2 );
    if( a1 > a3 )                               if( a1 > a3 )
        Swap( &a1, &a3 );                           Swap( a1, a3 );
    if( a2 > a3 )                               if( a2 > a3 )
        Swap( &a2, &a3 );                           Swap( a2, a3 );
}                                           }
    先看左侧的程序。上面定义了函数来表示给定盒子之间的交换操作,注意参数类型使用了long*,这里指针表示引用(应注意指针不仅可以表示引用,还可有其它的语义,以后会提到)。



下面看右侧的程序。参数类型变成long&,和指针一样,依旧表示引用,但注意它们的不同。后者表示它是一个别名,即它是一个映射,映射的地址是记录作为参数的数字的地址,也就是说它要求调用此函数时,给出的作为参数的数字一定是有地址的数字

posted @ 2008-11-27 16:31 henry08 阅读(242) | 评论 (0)编辑 收藏

函数

上面的返回类型为void,前面提过,void是C++提供的一种特殊数字类型,其仅仅只是为了保障语法的严密性而已,即任何函数执行后都要返回一个数字(后面将说明),而对于不用返回数字的函数,则可以定义返回类型为void,这样就可以保证语法的严密性。

可以认为函数类型的地址类型的数字编译器会隐式转换成指针类型的数字


重载函数表示函数名字一样,但参数类型及个数不同的多个函数

 声明是告诉编译器一些信息,以协助编译器进行语法分析,避免编译器报错。而定义是告诉编译器生成一些代码,并且这些代码将由连接器使用。

extern long a, *pA, &ra;
    上面就声明(不是定义)了三个变量a、pA和ra。
因为extern表示外部的意思,因此上面就被认为是告诉编译器有三个外部的变量,为a、pA和ra,故被认为是声明语句,所以上面将不分配任何内存。
同样,对于函数,它也是一样的:
    extern void ABC( long );  或  extern long AB( short b );

posted @ 2008-11-26 23:46 henry08 阅读(170) | 评论 (0)编辑 收藏

类型修饰符(type-specifier)

数组和指针都是类型修饰符,之前提过的引用变量的“&”也是类型修饰符

指针修饰符“*”——其总是接在变量名的前面,表示当前类型为原类型的指针。故:
short a = 10, *pA = &a, **ppA = &pA;

 引用修饰符“&”——其总是接在变量名的前面,表示此变量不用分配内存以和其绑定,而在说明类型时,则不能有它,下面说明。由于表示相应变量不用分配内存以生成映射,故其不像上述两种类型修饰符,可以多次重复书写,因为没有意义

posted @ 2008-11-26 15:52 henry08 阅读(624) | 评论 (0)编辑 收藏

仅列出标题
共3页: 1 2 3