宝杉的博客

UNIX/LINUX;ACE;SNMP;C++
posts - 33, comments - 23, trackbacks - 0, articles - 0

2007年10月26日

 

1、什么是const?
   常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。(当然,我们可以偷梁换柱进行更新:)

2、为什么引入const?
  const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。

3、cons有什么主要的作用?
   (1)可以定义const常量,具有不可变性。
        例如:
             const int Max=100;
             int Array[Max];       
   (2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
 例如:
             void f(const int i) { .........}
        编译器就会知道i是一个常量,不允许修改;
   (3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
        同宏定义一样,可以做到不变则已,一变都变!如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!
   (4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
        还是上面的例子,如果在函数体内修改了i,编译器就会报错;
        例如:
             void f(const int i) { i=10;//error! }
    (5) 为函数重载提供了一个参考。
         class A
         {
           ......
           void f(int i)       {......} file://一个函数
           void f(int i) const {......} file://上一个函数的重载
            ......
          };
     (6) 可以节省空间,避免不必要的内存分配。
         例如:
              #define PI 3.14159         file://常量宏
              const doulbe  Pi=3.14159;  file://此时并未将Pi放入ROM中
              ......
              double i=Pi;               file://此时为Pi分配内存,以后不再分配!
              double I=PI;               file://编译期间进行宏替换,分配内存
              double j=Pi;               file://没有内存分配
              double J=PI;               file://再进行宏替换,又一次分配内存!
         const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
     (7) 提高了效率。
           编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

3、如何使用const?
   (1)修饰一般常量
     一般常量是指简单类型的常量。这种常量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。
       例如:  
           int const x=2;  或  const int x=2;
   (2)修饰常数组
       定义或说明一个常数组可采用如下格式:
       int const a[5]={1, 2, 3, 4, 5}; 
         const int a[5]={1, 2, 3, 4, 5};
   (3)修饰常对象
      常对象是指对象常量,定义格式如下:
  class A;
       const A a;
         A const a;
      定义常对象时,同样要进行初始化,并且该对象不能再被更新,修饰符const可以放在类名后面,也可以放在类名前面。 
   (4)修饰常指针
        const int *A;        file://const修饰指向的对象,A可变,A指向的对象不可变
        int const *A;       file://const修饰指向的对象,A可变,A指向的对象不可变
        int *const A;       file://const修饰指针A,     A不可变,A指向的对象可变
        const int *const A;  file://指针A和A指向的对象都不可变
   (5)修饰常引用
        使用const修饰符也可以说明引用,被说明的引用为常引用,该引用所引用的对象不能被更新。其定义格式如下:
       const double & v;
  (6)修饰函数的常参数
        const修饰符也可以修饰函数的传递参数,格式如下:
        void Fun(const int Var);
        告诉编译器Var在函数体中的无法改变,从而防止了使用者的一些无意的或错误的修改。    
   (7)修饰函数的返回值:
        const修饰符也可以修饰函数的返回值,是返回值不可被改变,格式如下:
            const int Fun1();
            const MyClass Fun2();
   (8)修饰类的成员函数:
        const修饰符也可以修饰类的成员函数,格式如下:
            class ClassName
     {
             public:
                  int Fun() const;
                    .....
             };
        这样,在调用函数Fun时就不能修改类里面的数据
    (9)在另一连接文件中引用const常量
         extern const int i;     file://正确的引用
         extern const int j=10;  file://错误!常量不可以被再次赋值
    另外,还要注意,常量必须初始化!
         例如:
             const int i=5; 

4、几点值得讨论的地方:
   (1)const究竟意味着什么?
        说了这么多,你认为const意味着什么?一种修饰符?接口抽象?一种新类型?
        也许都是,在Stroustup最初引入这个关键字时,只是为对象放入ROM做出了一种可能,对于const对象,C++既允许对其进行静态初始化,也允许对他进行动态初始化。理想的const对象应该在其构造函数完成之前都是可写的,在析够函数执行开始后也都是可写的,换句话说,const对象具有从构造函数完成到析够函数执行之前的不变性,如果违反了这条规则,结果都是未定义的!虽然我们把const放入ROM中,但这并不能够保证const的任何形式的堕落,我们后面会给出具体的办法。无论const对象被放入ROM中,还是通过存储保护机制加以保护,都只能保证,对于用户而言这个对象没有改变。换句话说,废料收集器(我们以后会详细讨论,这就一笔带过)或数据库系统对一个const的修改怎没有任何问题。
   (2)位元const V.S. 抽象const?
        对于关键字const的解释有好几种方式,最常见的就是位元const 和 抽象const。下面我们看一个例子:
        class A
        {
         public:
               ......
               A f(const A& a);
               ......
         };
         如果采用抽象const进行解释,那就是f函数不会去改变所引用对象的抽象值,如果采用位元const进行解释,那就成了f函数不会去改变所引用对象的任何位元。
         我们可以看到位元解释正是c++对const问题的定义,const成员函数不被允许修改它所在对象的任何一个数据成员。
         为什么这样呢?因为使用位元const有2个好处:
         最大的好处是可以很容易地检测到违反位元const规定的事件:编译器只用去寻找有没有对数据成员的赋值就可以了。另外,如果我们采用了位元const,那么,对于一些比较简单的const对象,我们就可以把它安全的放入ROM中,对于一些程序而言,这无疑是一个很重要的优化方式。(关于优化处理,我们到时候专门进行讨论)
         当然,位元const也有缺点,要不然,抽象const也就没有产生的必要了。
         首先,位元const的抽象性比抽象const的级别更低!实际上,大家都知道,一个库接口的抽象性级别越低,使用这个库就越困难。
         其次,使用位元const的库接口会暴露库的一些实现细节,而这往往会带来一些负面效应。所以,在库接口和程序实现细节上,我们都应该采用抽象const。
         有时,我们可能希望对const做出一些其它的解释,那么,就要注意了,目前,大多数对const的解释都是类型不安全的,这里我们就不举例子了,你可以自己考虑一下,总之,我们尽量避免对const的重新解释。
   (3)放在类内部的常量有什么限制?
        看看下面这个例子:
        class A
        {
         private:
           const int c3 = 7;           // ???
       static int c4 = 7;          // ???
       static const float c5 = 7;  // ???
          ......
  };
         你认为上面的3句对吗?呵呵,都不对!使用这种类内部的初始化语法的时候,常量必须是被一个常量表达式初始化的整型或枚举类型,而且必须是static和const形式。这显然是一个很严重的限制!
         那么,我们的标准委员会为什么做这样的规定呢?一般来说,类在一个头文件中被声明,而头文件被包含到许多互相调用的单元去。但是,为了避免复杂的编译器规则,C++要求每一个对象只有一个单独的定义。如果C++允许在类内部定义一个和对象一样占据内存的实体的话,这种规则就被破坏了。
    (4)如何初始化类内部的常量?
         一种方法就是static 和 const 并用,在内部初始化,如上面的例子;
         另一个很常见的方法就是初始化列表:
         class A
         {
          public:
                A(int i=0):test(i) {}
          private:
                const int i;
          };
          还有一种方式就是在外部初始化,例如:
         class A
         {
          public:
                A() {}
          private:
                static const int i;  file://注意必须是静态的!
          };
          const int A::i=3;
     (5)常量与数组的组合有什么特殊吗?
          我们给出下面的代码:
           const int size[3]={10,20,50};
           int array[size[2]];
           有什么问题吗?对了,编译通不过!为什么呢?
           const可以用于集合,但编译器不能把一个集合存放在它的符号表里,所以必须分配内存。在这种情况下,const意味着“不能改变的一块存储”。然而,其值在编译时不能被使用,因为编译器在编译时不需要知道存储的内容。自然,作为数组的大小就不行了:)
         你再看看下面的例子:
          class A
         {
          public:
                A(int i=0):test[2]({1,2}) {} file://你认为行吗?
          private:
                const int test[2];
          };
         vc6下编译通不过,为什么呢?
         关于这个问题,前些时间,njboy问我是怎么回事?我反问他:“你认为呢?”他想了想,给出了一下解释,大家可以看看:我们知道编译器堆初始化列表的操作是在构造函数之内,显式调用可用代码之前,初始化的次序依据数据声明的次序。初始化时机应该没有什么问题,那么就只有是编译器对数组做了什么手脚!其实做什么手脚,我也不知道,我只好对他进行猜测:编译器搜索到test发现是一个非静态的数组,于是,为他分配内存空间,这里需要注意了,它应该是一下分配完,并非先分配test[0],然后利用初始化列表初始化,再分配test[1],这就导致数组的初始化实际上是赋值!然而,常量不允许赋值,所以无法通过。
        呵呵,看了这一段冠冕堂皇的话,真让我笑死了!njboy别怪我揭你短呀:)我对此的解释是这样的:C++标准有一个规定,不允许无序对象在类内部初始化,数组显然是一个无序的,所以这样的初始化是错误的!对于他,只能在类的外部进行初始化,如果想让它通过,只需要声明为静态的,然后初始化。
         这里我们看到,常量与数组的组合没有什么特殊!一切都是数组惹的祸!
   (6)this指针是不是const类型的?
        this指针是一个很重要的概念,那该如何理解她呢?也许这个话题太大了,那我们缩小一些:this指针是个什么类型的?这要看具体情况:如果在非const成员函数中,this指针只是一个类类型的;如果在const成员函数中,this指针是一个const类类型的;如果在volatile成员函数中,this指针就是一个volatile类类型的。
   (7)const到底是不是一个重载的参考对象?
        先看一下下面的例子:
        class A
         {
           ......
           void f(int i)       {......} file://一个函数
           void f(int i) const {......} file://上一个函数的重载
            ......
          };
        上面是重载是没有问题的了,那么下面的呢?
         class A
         {
           ......
           void f(int i)       {......} file://一个函数
           void f(const int i) {......} file://?????
            ......
         };
         这个是错误的,编译通不过。那么是不是说明内部参数的const不予重载呢?再看下面的例子:
        class A
         {
           ......
           void f(int& )       {......} file://一个函数
           void f(const int& ) {......} file://?????
            ......
         };
         这个程序是正确的,看来上面的结论是错误的。为什么会这样呢?这要涉及到接口的透明度问题。按值传递时,对用户而言,这是透明的,用户不知道函数对形参做了什么手脚,在这种情况下进行重载是没有意义的,所以规定不能重载!当指针或引用被引入时,用户就会对函数的操作有了一定的了解,不再是透明的了,这时重载是有意义的,所以规定可以重载。
   (8)什么情况下为const分配内存?
        以下是我想到的可能情况,当然,有的编译器进行了优化,可能不分配内存。
        A、作为非静态的类成员时;
        B、用于集合时;
        C、被取地址时;
        D、在main函数体内部通过函数来获得值时;
        E、const的 class或struct有用户定义的构造函数、析构函数或基类时;。
        F、当const的长度比计算机字长还长时;
        G、参数中的const;
        H、使用了extern时。
        不知道还有没有其他情况,欢迎高手指点:)       
   (9)临时变量到底是不是常量?
        很多情况下,编译器必须建立临时对象。像其他任何对象一样,它们需要存储空间而且必须被构造和删除。区别是我们从来看不到编译器负责决定它们的去留以及它们存在的细节。对于C++标准草案而言:临时对象自动地成为常量。因为我们通常接触不到临时对象,不能使用与之相关的信息,所以告诉临时对象做一些改变有可能会出错。当然,这与编译器有关,例如:vc6、vc7都对此作了扩展,所以,用临时对象做左值,编译器并没有报错。
   (10)与static搭配会不会有问题?
        假设有一个类:
        class A
        {
         public:
             ......
             static void f() const { ......}
             ......
         };
         我们发现编译器会报错,因为在这种情况下static不能够与const共存!
         为什么呢?因为static没有this指针,但是const修饰this指针,所以...
     (11)如何修改常量?
          有时候我们却不得不对类内的数据进行修改,但是我们的接口却被声明了const,那该怎么处理呢?我对这个问题的看法如下:
           1)标准用法:mutable
              class A
              {
               public:
                      A(int i=0):test(i)        { }
                      void SetValue(int i)const { test=i; }
               private:
                      mutable int test;   file://这里处理!
               };
           2)强制转换:const_cast
               class A
               {
               public:
                      A(int i=0):test(i)        { }
                      void SetValue(int i)const
                      { const_cast <int>(test)=i; }//这里处理!
               private:
                      int test;  
               };
            3)灵活的指针:int*
               class A
              {
               public:
                      A(int i=0):test(i)        { }
                      void SetValue(int i)const
                      { *test=i; }
               private:
                      int* test;   file://这里处理!
               };
            4)未定义的处理
              class A
              {
               public:
                      A(int i=0):test(i)        { }
                      void SetValue(int i)const
                      { int *p=(int*)&test; *p=i; }//这里处理!
               private:
                      int test;  
               };
                注意,这里虽然说可以这样修改,但结果是未定义的,避免使用!
             5)内部处理:this指针
              class A
              {
               public:
                      A(int i=0):test(i)        { }
                      void SetValue(int i)const
                      { ((A*)this)->test=i; }//这里处理!
               private:
                      int test;  
               };
             6)最另类的处理:空间布局
               class A
               {
                public:
                      A(int i=0):test(i),c('a') {  }
                private:
                      char c;
                      const int test;
                };
                int main()
                {
                    A a(3);
                    A* pa=&a;
                    char* p=(char*)pa;    
                    int*  pi=(int*)(p+4);//利用边缘调整
                    *pi=5;                 file://此处改变了test的值!
                    return 0;
                 }
        虽然我给出了6中方法,但是我只是想说明如何更改,但出了第一种用法之外,另外5种用法,我们并不提倡,不要因为我这么写了,你就这么用,否则,我真是要误人子弟了:)
     (12)最后我们来讨论一下常量对象的动态创建。
           既然编译器可以动态初始化常量,就自然可以动态创建,例如:
           const int* pi=new const int(10);
           这里要注意2点:
           1)const对象必须被初始化!所以(10)是不能够少的。
           2)new返回的指针必须是const类型的。
           那么我们可不可以动态创建一个数组呢?
           答案是否定的,因为new内置类型的数组,不能被初始化。
           这里我们忽视了数组是类类型的,同样对于类内部数组初始化我们也做出了这样的忽视,因为这涉及到数组的问题,我们以后再讨论。

posted @ 2007-10-26 08:31 宝杉 阅读(107) | 评论 (0)编辑 收藏

2007年9月4日

     摘要: ·                                 比...  阅读全文

posted @ 2007-09-04 10:46 宝杉 阅读(302) | 评论 (0)编辑 收藏

2007年8月30日

模板是重用机制的一种工具,可以实现类型参数化,即把类型定义为参数,实现代码可重用性。

 

F:宏定义也可以实现重用,为什么不使用宏?

Q:宏避开C++类型检查机制,两个不同类型参数之间的比较将会导致错误。

 

模板,模板函数,模板类和对象之间的关系。

模板

(函数模板和类模板)

模板函数

模板类

对象

箭头代表实例化

 

模板

(函数模板和类模板)

模板函数

模板类

对象

 

例如:

C++exams\template_max

模板形参将T实例化的参数。

函数模板是一个不完全的函数,首先需要将模板形参T实例化为确定的类型。这个类型可以是预定义的,也可以是用户自定义。

 

模板函数的异常

1 先调用顺序遵循什么约定?

1)寻找类型完全匹配的函数。

2)寻找函数模板,实例化后,产生匹配的模板函数。

3)若(1)(2)失败,尝试低一级的对函数重载的方法。例如,通过类型转换。

4)(1)(2)(3)都为匹配,则是一个错误。

 

2 先了解了这样的约定,就很容易理解以下:

用非模板函数重载函数模板,只声明非模板类函数的原型,不给出函数体,而函数体借用函数模板的函数体。

例如:

template <class T>

T max( T x, T y)

{

         return( x > y )? x : y;

}

int max( int, int);

//int max 重载了函数模板的函数体;

int i;

char c;

maxi, c;

 

 

3 定义一个完整的有函数体的非模板函数。

比如:max(char, char)时,上面的函数模板不能比较比一个字符更长的字符串了。

所以,像一般的重载函数一样定义。

char *max( char *x, char *y )

{

         return ( strcmp(x, y) > 0 ) ? x y

}

char * max重载了函数模板,当调用为:

max ( “abcd”, “efgh” ); 将执行此函数。

posted @ 2007-08-30 10:47 宝杉 阅读(96) | 评论 (0)编辑 收藏

如果不想让别人使用编译器编写构造拷贝和赋值函数,可以声明为私有:

     class A

     {

       private:

         A(const A &a);                   // 私有的拷贝构造函数

         A & operate =(const A &a);  // 私有的赋值函数

     };

 

如果有人试图编写如下程序:

     A  b(a); // 调用了私有的拷贝构造函数

     b = a;        // 调用了私有的赋值函数

编译器将指出错误,因为外界不可以操作A的私有函数。

但是怎样才能使用构造拷贝和赋值函数呢?

虚拟函数使用:C++exams\destructor

 

在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。例如:

C++exams\base_operator

posted @ 2007-08-30 10:33 宝杉 阅读(85) | 评论 (0)编辑 收藏

     摘要: 定义:      class String      {        public:          String(const char *str = NULL);  ...  阅读全文

posted @ 2007-08-30 10:31 宝杉 阅读(155) | 评论 (0)编辑 收藏

 

析构函数

构造函数初始化表:构造函数特殊的初始化方式“初始化表达式表”(简称初始化表)。

初始化表位于函数参数表之后,却在函数体 {} 之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前。

规则

u       如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数。

u       类的const常量只能在初始化表里被初始化,因为它不能在函数体内用赋值的方式来初始化。

u       类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式,这两种方式的效率不完全相同。

效率

1 内部成员:

初始化表和函数体内赋值都可以,但效率不完全相同,但后者更为清晰直观。

例子:

class F

{

 public:

    F(int x, int y);        // 构造函数

 private:

    int m_x, m_y;

    int m_i, m_j;

}

F::F(int x, int y)

 : m_x(x), m_y(y)          

{

   m_i = 0;

   m_j = 0;

}

F::F(int x, int y)

{

   m_x = x;

   m_y = y;

   m_i = 0;

   m_j = 0;

}

示例9-2(c) 数据成员在初始化表中被初始化     示例9-2(d) 数据成员在函数体内被初始化

两种方式效率区别不大。

 

2 非内部成员:

只能用初始化表,提高效率。

例子:

    class A

{…

    A(void);                // 无参数构造函数

    A(const A &other);      // 拷贝构造函数

    A & operate =( const A &other); // 赋值函数

};

 

    class B

    {

     public:

        B(const A &a); // B的构造函数

     private: 

        A m_a;         // 成员对象

};

比较与分析:

B::B(const A &a)

 : m_a(a)          

{

   …

}

B::B(const A &a)

{

m_a = a;

}

1 B类构造函数的初始化里,调用了A类的拷贝构造函数。

2 B类构造初始化里,隐藏了以下几个步骤:

先创建了a对象,调用了A类的无参数构造函数;

把a赋值给m_a,调用了A类的赋值函数;

 

深入探讨:

构造和析构的次序?

构造从最深处的基类开始的,先一层层调用基类的构造函数,然后调用成员对象的构造函数。

而析构函数严格按照构造函数相反的次序执行,该次序唯一,以便让编译器自动执行析构函数。

特别之处是,成员对象初始化次序不受构造函数初始化表次序影响,由在类中声明的次序决定。而类声明是唯一的,构造函数却可能有多个,所以有多个不同次序函数初始化表。如果按照构造函数的次序构造,那么解析函数不能得到唯一的逆序。

posted @ 2007-08-30 10:28 宝杉 阅读(94) | 评论 (0)编辑 收藏

2007年8月20日

#include <iostream>
using namespace std;
#include <stdio.h>

#define OTL_ORA7 // Compile OTL 3.1/OCI7
#include <otlv4.h> // include the OTL 4 header file

otl_connect db; // connect object

void insert()
// insert rows into table
{
 otl_stream o(50, // buffer size
              "insert into test_tab values(:f1<float>,:f2<char[31]>)",
                 // SQL statement
              db // connect object
             );
 char tmp[32];

 for(int i=1;i<=100;++i){
  sprintf(tmp,"Name%d",i);
  o<<(float)i<<tmp;
 }
}

void select()
{
 otl_stream i(50, // buffer size
              "select * from test_tab where f1>=:f<int> and f1<=:f*2",
                 // SELECT statement
              db // connect object
             );
   // create select stream
 
 float f1;
 char f2[31];

 i<<8; // assigning :f = 8
   // SELECT automatically executes when all input variables are
   // assigned. First portion of output rows is fetched to the buffer

 while(!i.eof()){ // while not end-of-data
  i>>f1>>f2;
  cout<<"f1="<<f1<<", f2="<<f2<<endl;
 }

 i<<4; // assigning :f = 4
   // SELECT automatically executes when all input variables are
   // assigned. First portion of output rows is fetched to the buffer

 while(!i.eof()){ // while not end-of-data
  i>>f1>>f2;
  cout<<"f1="<<f1<<", f2="<<f2<<endl;
 }

}

int main()
{
 otl_connect::otl_initialize(); // initialize OCI environment
 try{

  db.rlogon("scott/tiger"); // connect to Oracle

  otl_cursor::direct_exec
   (
    db,
    "drop table test_tab",
    otl_exception::disabled // disable OTL exceptions
   ); // drop table

  otl_cursor::direct_exec
   (
    db,
    "create table test_tab(f1 number, f2 varchar2(30))"
    );  // create table

  insert(); // insert records into table
  select(); // select records from table

 }

 catch(otl_exception& p){ // intercept OTL exceptions
  cerr<<p.msg<<endl; // print out error message
  cerr<<p.stm_text<<endl; // print out SQL that caused the error
  cerr<<p.var_info<<endl; // print out the variable that caused the error
 }

 db.logoff(); // disconnect from Oracle

 return 0;

}

posted @ 2007-08-20 17:01 宝杉 阅读(524) | 评论 (2)编辑 收藏

OTL stream concept

Any SQL statement, PL/SQL block or a stored procedure call is characterized by its input / output [variables].

Example 1. A SELECT statement has scalar input variables that are used in the WHERE clause of the statement. The SELECT statement also defines output columns. Potentially, the output columns are vector parameters since the SELECT statement may return multiple rows.

Example 2. An INSERT statement writes data into a table, i.e. it has input parameters. Same is true for UPDATE statemements.

Example 3. A DELETE statement deletes rows from a table. Deletion criteria needs to be entered, thus the DELETE statement has input.

Example 4. A stored procedure may have input and/or output parameters. Usually, stored procedure parameters are scalars. There is a special case, though: stored procedure returning a referenced cursor (Oracle) or a result set (MS SQL Server or Sybase).

Example 5. An arbitrary PL/SQL block may have input or/and output parameters that may be either scalars or vectors.

Industrial strength database servers have bulk (or array) operations:

  • bulk INSERT
  • bulk UPDATE
  • bulk DELETE
  • bulk SELECT

Therefore, parameters in INSERT/UPDATE/DELETE statement may be vectors if the statement is performed in bulk.

The picture is clear: any interaction with SQL or its procedural extension can be treated as a black box with input and/or output. It does not matter what the black box does inside (according to the definition of a black box). What matters is the input wires that send signals into the box and the output wires that receive signals from the box:

Some of the wires may be both input and output.

Why not combine the concept of data streams and SQL? Instead of multiplying constructs and making database API's too convoluted, why not unify and simplify them? The OTL gives an answer to those questions and the answer is the otl_stream class.

Since a SQL statement may be done in bulk, the otl_stream is a buffered stream. Conceptually, the otl_stream has two separate buffers: input and output. The input buffer is comprised of all input variables put together. Respectively, the output buffer is comprised of all output variables put together.

C++ streams are usually manipulated via operator >> and operator <<. The stream reference is on the left of the operator symbol:

   s>>variable;
s<<variable;

The double arrow shows the direction in which data goes:

  • >> -- from the stream into the data container (variable)
  • << -- from the data container (variable) into the stream

OTL streams are similar to buffered C++ streams . A SQL statement or stored procedure call is opened as an ordinary buffered stream. The logic of the OTL stream operations remains the same as the C++ stream operations with the only exception -- the OTL stream has separate input and output buffers which may overlap.

The OTL stream has a flush function for flushing its input buffer when the buffer gets full and a collection of >> and << operators for reading and writing objects of different data types. The most important advantage of the OTL streams is their unified interface to SQL statements and stored procedure call of any kind. This means that the application developer needs to remember just a few syntactical constructs and function names which he already got familiar with when he started working with C++ streams.

Inside the OTL stream there is a small parser for parsing declarations of bind variables and their data types. There is no need to declare C/C++ host variables and bind them with placeholders by special bind function calls. All necessary buffers are created dynamically inside the stream. The stream just needs to be opened for reading and writing values.

The OTL stream interface requires use of the OTL exceptions. This means that potentially any OTL stream operation can throw an exception of the otl_exception type. In order to intercept the exception and prevent the program from aborting, wrap up the OTL stream code with the corresponding try & catch block.

The functioning of the otl_stream is pretty much automatic: when all of the input variables of the stream are defined (in other words, the input buffer is filled out), it triggers the block box inside the stream to execute. The output buffer gets filled out in the process of the execution of the black box. After the execution is finished, the output values can be read from the stream. If it is a SELECT statement and it returns more rows than the output buffer can hold, after the whole output buffer is read, then the stream automatically fetches the next bacth of rows into the output buffer.

posted @ 2007-08-20 16:58 宝杉 阅读(2244) | 评论 (4)编辑 收藏

Introduction

This document describes the Oracle, Odbc and DB2-CLI Template Library, Version 4.0 (OTL 4.0). OTL 4.0 is a C++ library based on templates. It integrates all of the previous releases into one library.

OTL 4.0 was designed as a combination of a C++ template framework and OTL-adapters. The framework is a generic implementation of the concept of OTL streams. The OTL-adapters are thin wrappers around the database APIs and are used as class type parameters to be substituted into the template framework.

OTL 4.0 covers the functionality of a whole database API with just a handful of concrete classes: otl_stream, otl_connect, otl_exception, otl_long_string, and several template PL/SQL (Oracle) table container classes, generated from the template framework and the OTL-adapters.

The OTL code gets expanded into direct database API function calls, so it provides ultimate performance, reliability and thread safety in multi-processor environments as well as traditional batch programs. OTL 4.0, being a template library, is highly portable since it is self-sufficient and compact enough.

OTL 4.0 is ANSI C++ compliant (ANSI C++ typecasts, clean templatized code, etc.), tightly integrated with the Standard Template Library (STL) via so-called STL-compliant stream iterators, and natively supports the STL std::string's in otl_stream's.

The current version of the OTL supports Oracle 7 (natively via OCI7), Oracle 8 (natively via OCI8), Oracle 8i (natively via OCI8i), Oracle 9i (natively via OCI9i), Oracle 10g (natively via OCI10g), DB2 (natively via DB2 CLI), ODBC 3.x as well as ODBC 2.5 compliant data sources in MS Windows and Unix (e.g. Oracle, MS SQL Server, Sybase, Informix, MySQL, DB2, Interbase / Firebird, PostgreSQL, SQLite, SAP/DB, TimesTen, MS ACCESS, etc.). The list of supported database backends is constantly growing.

posted @ 2007-08-20 16:55 宝杉 阅读(812) | 评论 (0)编辑 收藏

2007年8月13日

内联函数:既增加安全性,又可以调用类的数据成员。

C++中,用内联取代所有宏,但在Debug版本中,assert是例外。

assert不产生任何副作用,调用函数会引起内存、代码的变动,所以assert是宏。

内联格式:

     void Foo(int x, int y);    

     inline void Foo(int x, int y)    // inline与函数定义体放在一起

     {

        

     }

用于实现的关键字,而非用于声明的关键字。

在类中的规范格式:

class A

     {

public:

         void Foo(int x, int y)

     }

     // 定义文件

     inline void A::Foo(int x, int y)

{

}

什么情况不适合使用内联?

1 内联函数代码较长,导致内存消耗较高。

2 内联函数包括循环,执行内联函数代码比调用函数的开销大。

F:为什么构造和解析函数不使用内联?

Q:因为析构函数可能“隐藏”一些行为,例如执行基类或成员对象的析构过程。

有时候编译器会自动取消一些不值得的内联,所以在声明时不写inline是合理的。

posted @ 2007-08-13 10:24 宝杉 阅读(106) | 评论 (0)编辑 收藏