编程随想录
CSDN拆迁户 @2014-04-07

导航

 

索引:
 构造函数, 构造函数的初始化语法, (virtual)析构函数,inline函数, 私有构造函数私有析构函数.
 重载操作符operator,
 类的访问标示(public/private/protected), 友元
 名空间namespace.
 类的const, static成员
 「引用」类型的类成员

第1.1: 复制构造函数&构造函数
  定义格式:Thing::Thing(Thing&),Thing::Thing(),
 
  构造函数
被隐式调用的两种情况:(1)void func(Thing); (2)Thing a; 前者按值传递参数,进行实参的复制,会调用复制构造函数,后者会调用默认构造函数;
 
  编译器自动合成的复制构造函数,仅仅简单把成员复制,如果类的成员中有指针,则会出现意外的问题:两个指针指向同一个地址,这种带有"指针"成员的类要自己完成代码(而不是用编译器合成的);
  在书写一个类的代码时,如果实现复制构造函数很困难,但为了代码的健壮性仍要防止用户无意中调用到复制构造函数,则应在基类中添加一个"private的复制构造函数",这样用户一旦"无意中"调用了复制构造函数,编译器会报错;
  (*)上句中的"无意中"指如下几种调用:
        Base b; Base b_copy(b); //copy-constructor
 
  构造函数的初始化语法,以及什么时候应该使用此语法:
  假设类Thing有成员String Thing::name, int Thing::value,则Thing类的初始化语法的构造函数如下:
  Thing::Thing(String& s, int v)
  :name(s), value(v) 
  {
      //...函数体
  }
  使用初始化语法带来的性能提升,比如上面的Thing类的构造函数,如果不使用初始化语法,而是在函数体内对name成员进行赋值,这样做相当于调用了两次String类的构造函数,先是String(),然后是String(String&),即先调用默认构造,然后调用复制构造,
##但是对于内建类型的成员,"初始化语法"不会带来更高的效率.
##类型为"引用"的成员:由于"引用"的值不可改变,故"char& ref"这样的定义语句会报错,故该类型的成员必须使用初始化语法来初始化;


第1.3:  私有的构造函数/析构函数 ?
   私有的"默认构造函数"在语法上成立, 可以作为一种策略, 禁止用户调用某个类的"默认构造函数";
  (1) 私有的"默认构造函数",  将不能以"AClass  a"的方式创建实例, 也不能以"AClass* p = new AClass()"的方式创建;
  (2) 私有的"默认构造函数", 这个类的派生类无法实例化, why?
        ##派生类实例化, 会调用基类的"默认构造函数",,,
  (3) 如果"私有析构函数"... 编译可以通过, 但调用到"私有析构函数", 将会按照private成员函数的规则.
       ##这个类只能在堆上创建实例, 如果以"AClass  a"的方式在栈上创建, a生命周期结束时将调用析构函数~AClass(),  调用私有函数, 报错.
       ##如果"AClass* p = new AClass()"的方式创建, 在执行"delete p"时, 也会调用"私有的析构", error;
 
class CTest; //假设CTest有私有的析构函数;
void CTest::Destroy(){  //Destroy函数public
    delete this;
}

int main(){
    CTest *pa = new CTest(); // 不报错
    delete(pa); // 报错, 相当于main调用了~CTest()
    pa->Destroy(); // OK

    CTest  b;
  // 报错, b自动释放时会调用私有析构
}


第2.1: const/static
   const/static成员的初始化?
   const 的函数返回值/型参.
answer:
    (1) const/static类型的成员的初始化, const成员只能用"初始化语法"来初始化, 比如初始化const int A::value的代码如下:
    A::A(int v): m_value(v)
    {
        // const成员在初始化列表赋值
        // 普通成员在构造函数内赋值
    }
    static静态成员的初始化, 不应在*.H文件中, 而在cpp文件中, 就像定义全局变量一样:
          static double base::s_rate = 1.2;

    (2) 引用类型的类成员的初始化,类似const成员,需要用初始化列表。

    (2) void CThing::Add() const { ... }  // 表示这个Add()不会改变类成员的值, 如果改变, 编译器报错, 这是一种依靠编译器自我检查代码的习惯;

    (3) int func(const int & ref_var) // 函数形参声明为const, 函数内改变ref_var值的行为都会在编译期报错,  注意const只有是指针或引用时才有意义, int func(const int v)没有意义, 因为形参按值传递, 函数内的变量v只是实参的一个拷贝.

    (4) 函数返回值为const, 则"函数调用"不能是左值, 比如 const int func(int), 则语句 func(2) = 10是错误的, 

#注意,  const AClass a, 一个"不可改变"的类实例, 将不能调用"非const的"成员函数, 
#类的const 函数内只能调用const函数, why? const 的类实例, 不能调用非const的函数, why?


第3.1重载操作符示例:
  class String{
   
 // 赋值操作符
    String& operator=(const String&);
    String& operator=(const char*);
   // 调用方式str="abc"; 
   // 或a=b=c;
   
    // 比较操作
    bool operator==(const String&);
    bool operator==(const char*);
   
    // 下标操作
    char& operator[](int); 
    // 调用方式 if( name[n] != 'A' )
    // []操作符必须能出现在赋值操作符的左右两边, 为了能在左边出现, 操作符的返回值必须是一个左值.

    // 强制转换, 没有返回值
    operator double() ;
    // 隐式调用: a + 3.13 , 显示调用: a.operator double();
  }

#question:为什么重载操作符的返回值一般是const的引用??
#answer: 这样做可以实现这样的语句: a=b=c,  如果写出(a=b)=c这样的语句, 则报错; 


  String类的重载操作符实现:
inline String& String::operator=(const String& other) 

    if (this!=&other) 
    { 
        delete[] m_data; 
        if(!other.m_data) m_data=0; 
        else 
        { 
            m_data = new char[strlen(other.m_data)+1]; 
            strcpy(m_data,other.m_data); 
        } 
    } 
    return *this;
 //返回this的解


inline String String::operator+(const String &other)const 

    String newString; 
    if(!other.m_data) 
        newString = *this; 
    else if(!m_data) 
        newString = other; 
    else 
    { 
        newString.m_data = new char[strlen(m_data)+strlen(other.m_data)+1]; 
        strcpy(newString.m_data,m_data); 
        strcat(newString.m_data,other.m_data); 
    } 
    return newString; 


inline bool String::operator==(const String &s)     

    if ( strlen(s.m_data) != strlen(m_data) ) 
        return false; 
    return strcmp(m_data,s.m_data)?false:true; 


inline char& String::operator[](unsigned int e) 

    if (e>=0&&e<=strlen(m_data)) 
        return m_data[e]; 


ostream& operator<<(ostream& os,String& str) 

    os << str.m_data; 
    return os; 


第3.2:  重载操作符的左/右操作数,返回值,非类的成员的操作符
  对于类的双目操作符,例如string的对象 str=="cpp",实际是str.operator=("cpp"),即左操作数是调用者,右操作数是参数,符号左右对调写作"cpp"==str则会出错;
 
 ## 如果重载操作符是类成员,则可以通过this指针调用左操作数str;
 
 ## C++要求 赋值=,下标[],调用()和成员访问箭头->,操作符必须被定义为类成员, 否则编译期错误;

  对于
"非类的成员的"重载操作符,如何定义,调用? 第1/2个参数分别表示操作符的左/右值,
  bool operator==( const String &str1, const String &str2 )
  {
    if ( str1.size() != str2.size() )
        return false;
    return strcmp( str1.c_str(), str2.c_str() ) ? false : true;
  }

以操作符+为例, 说明重载操作符是成员和非成员的不同:
Sales_item& Sales_item::operator+=(const Sales_item& rhs);
Sales_item Sales_item::operator+(const Sales_item &lhs, const Sales_item &rhs);
对于类成员的操作符+, 操作符左边是类实例, 操作符右边是实参, 返回的引用是*this;
对于非类成员的操作符+, 没有this指针, 并且有两个参数, 返回值不是引用类型;

1、赋值(=),下标([ ]),调用(())和成员访问箭头(->)等操作符必须定义为成员,如果定义为非成员的话,程序在编译的时候,会发生错误。
2、和赋值操作符一样,复合赋值操作符通常定义为成员。与赋值操作符不同的是,不一定飞的这样做,如果定义为非成员,编译器不会报告错误。
3、改变对象状态或者与给定类型紧密联系的其他一些操作符,入自增,自减和解引用,通常定义为类成员
4、对称的操作符,for example:算数操作符、相等操作符、关系操作符和位操作符,最好定义为非成员函数。
5、IO操作符必须定义为非成员函数,我们不能将该操作符定义为类的成员函数,否则,左操作数将只能是该类类型的对象。



第4.1: 类的访问标识。

(1)private成员:
         1.*可以由该类的成员函数访问内以this指针直接访问在成员函数内以“类实例.成员”的方式访问。
         2.可以由其友元函数访问。 
/*类ATPString的私有成员ptr_string, 在类的成员函数内, 可以用"实例.私有成员"的方式访问*/
ATPString::ATPString(const ATPString& ref)
{
    if(this == &ref) return;
    string_length = ref.length();
    ptr_string = (char*)malloc(string_length+1);
    strcpy(ptr_string,ref.ptr_string);  // notice

    for(unsigned int i=0; i<string_length; i++)
    {
        temp = ref.get_char_at(i);
        memcpy(ptr_string+i,&temp,1);
    }
}


(2)protected成员:
         1.可以被“类自身”或其派生类的成员函数直接访问
       2.可以被“类自身”或其派生类的成员函数通过this指针访问,而派生类不能访问base.value,
       3.可以被类的友元访问。
         派生类只能通过派生类对象访问基类的protected成员, 派生类对其基类对象的protected成员没有访问权限.*下面的代码说明了, Base的protected成员value_protected在派生类中的访问权限:

void Derive::test(Base& b, Derive& d)
{
    cout << b.value_protected << std::endl;
 //error!
    cout << this->value_protected << std::endl; 
    cout << d.value_protected << std::endl;
    cout << value_protected << std::endl; 
}


(3)public成员:
          1. 可以被该类中的函数、2.子类的函数、3.其友元函数访问,也可以由 4.该类的对象访问。


第4.2: 类的继承后成员属性变化。
private 属性不能够被继承。
使用public继承,   基类中的protected和public属性不发生改变;
使用protected继承,基类的protected和public属性在派生类中变为protected;
使用private继承,  基类的protected和public属性在派生类中变为private;


第5.1:(虚的)析构函数
  在说明前,先看一个例子,Base,Derive(基类,派生类):
  Derive* pd = new Derive();
  pd->~Derive(); //区别delete pd;
  //解释,当pd->~DeriveItem()时,会先执行~DeriveItem(),再执行~Base(),保证基类和派生类的资源都得到释放;
 
  第二个例子,假若Base,Derive的析构都不是virtual的,
  Base* pd = new Derive();//基类指针指向派生类对象
  delete pd;
  这时候只会执行Base::~Base(),而没有执行~Derive;
所以,如果一个类要当做基类, 那么它的析构必须写为virtual的;
如果函数中定义的"栈变量", 比如BaseClass b, 在函数退出时自动执行~BaseClass();

#new delete, free malloc#
malloc和new都申请空间,但是new是强类型的分配,会调用对象的构造函数初始化对象,而malloc仅分配内存空间但是不初始化。
new 自适应类型,malloc需要强制转换.
new按类型进行分配,malloc需要指定内存大小.
对于对象来说free的确释放了对象的内存,但是不调用对象的析构函数。delete不仅释放对象的内存,并且调用对象的析构函数.
所以在对象中用free删除new创建的对象,内存就有可能泄露.
#在delete内部仍调用了free .
待续 : http://hi.baidu.com/striveforit/item/637e14dc4b269e2b38f6f78f


 
第5.2:成员的初始化顺序:
  顺序是按照Thing类声明中的顺序,而不是构造函数中赋值的顺序;
 
第6.1:函数的重载(区分重写):
  "重写"override:也叫覆盖,指virtual函数在继承中的定义改写,返回值和形参在过程中保持一致;virtual关键字仅能在函数声明中出现,(纯)虚函数的定义句中不能带有virtual关键字;

  "重载"overload:是同域内的同名函数可由形参表区分(返回值可以不同,且仅返回值不能确定重载);

  "隐藏"是非虚函数在继承中的定义改写,返回值和形参可以不一致.产生覆盖的函数仍是"静态绑定",基类指针不能调用子类函数;

const修饰对函数重载的影响:
 仅当函数的形参是指针或引用时, const关键字才对重载有影响.



第7:类设计的健壮性建议:

  1.如果复制构造函数的代码很困难写出,为了避免用户调用"编译器合成的复制构造函数",可以把此函数写为private;

  2.(纯)虚函数写为private;

  3.构造函数/析构函数写为private,这样可以...
      (1)防止此类被继承,被当做基类;
      (2)构造函数声明为virtual,编译出错,构造函数执行时还未形成vtable;
      (3)析构函数声明为virtual,这是一般性做法, 如果一个类要作为基类, 那么它的析构函数必须为virtual, why ?


  4.把需要检查值的成员写为private: 设计一个"分数"类,分子分母int num和int deom,如果把deom成员写public,作容易出现用户直接写a.deom=0这样的代码,
     #解决方案: deom设为private,新建一个set_deom(int)的成员函数,在set_deom中进行deom==0的检查.

  5.函数形参使用const,例如void func(const int*)或void func(const int&),防止函数内意外修改实参;或者类的某成员函数声明为const,"void func() const;",则此函数不能修改类的数据成员,否则编译器报错;
    下面的代码可通过:
        void func(const int*);
        int *ptr = NULL;
        func(ptr); //正常调用        
    下面的代码不可通过:
        void func(char*);
        const char* prt = "hello"; //字符串常量
        func(prt); //调用出错


posted on 2011-05-31 09:55  dos5gw  阅读(132)  评论(0编辑  收藏  举报