发新话题
打印

[原创] 类(class)及其相关学习笔记(c++)

类(class)及其相关学习笔记(c++)

复制内容到剪贴板
代码:
    最近我们学习VC++了,不过,一位前辈说先把C++学好比较好.于是我又去学C++.C++原先叫做"带类的C"(c with class),可见类在C++中占有很重要的地位.加上我又有些C语言的基础,(其实有人说,它们是完全不同的两种语言)所以决定从类开始学起.
       我看了一下参考书,除了和C相似的那些以外,可以分成这么几个部分:一.类,包括相关的许多东西;二.指针,包括容器等相关;三.算法,包括相关算法;四.高级应用,包括模板及相关.
       要说的是,其实这个笔记没有太多自己的东西在里边,基本上是把<C++ primer>(中文第四版)第十二章和第十三章以及相关章节抄一遍而已.但是包含了自己的理解在里边,而且我想,抄一遍也许有意义,会理解它更深一些.如此,必然有许多错误在,希望各位发现了可以指给我.谢.
      一个完整的类类型在定义中包含了构造函数,复制控制,成员函数和成员数据.其中复制控制包括构造函数,赋值函数和析构函数.简单一些的话,可以只要成员函数和成员数据.当然,最简单的是什么也没有的空类,但它没有什么实际用处.只包含成员函数和成员数据的类如下:
class Sales_item
{
  public:
        double avg_price() const;
        bool same_isbn(const Sales_item &rhs) const
          {
           return isbn==rhs.isbn;
           }
  private:
        std::string isbn;
        unsigned units_sold;
        double   revenue;
};
double Sales_item::avg_price() const
{
if(units_sold)
return revenue/units_sold;
else
return 0;
}
       首先说一下,C语言中有一个结构,用关键字struct,在C++中,它和class是一样的,不同之处在于,如果用struct关键字表示类,则默认成员是公有的,而class则是私有的.
       因为默认是私有的,所以开始有一个public关键字,类成员函数在类体中定义和之外定义并没有不同,当然会和inline有些关系,我是说,功能并没有不同.当然,其中会有一个分号结束符和一个作用域操作符,以及在声明时和定义时都要有const关键字,这些细节也要注意.
       把函数定义为const的意思是它不能改变它的数据,当然,后面会说一个mutable关键字,会有不同.这儿声明一下,这个笔记名义上是要和大家交流一下,其实还是我一个人的笔记,所以对我来说,如果遇到对我来说的新东西,我就会说一下,如果是以前就明白的,我就会略过了,所以如果你觉得它比较省略或是啰嗦,包涵.
       下面说构造函数,构造函数就是当定义一个类对象时,给其中的成员数据的初使值.比如在上面的定义中的public中加上一行:Sales_item(): units_sold(0),revenue(0.0){}.类的构造函数必须和类同名,后面有一冒号,接下来是对成员数据的初使化,没有初使化string类型的isbn是因为类类型的数据有它自己的构造函数.其实如果不定义构造函数,编译器会自动合成一个,但是合成的构造函数不会初使化局部作用域的内置类型数据,所以,大多数情况下还是自己定义一个构造函数比较好.另外,构造函数可以重载,由实参决定使用哪个.对于const函数,不用const型的构造函数,普通的可以.在构造函数体中对数据赋值同样也可以初使化,不同的是,如果数据是const型的,就必须在初使化列表中进行初使化.还有一个要说的是,如果定义了一个构造函数,就必须同时定义一个默认的无参构造函数,因为那时编译器不会再自动定义了.
       还有函数的重载和inline函数,没有什么要说的.
       接下来是隐含的*this指针.再看另一个类:
class Screen
{
public:  //something.....
private:
       string contents;
       string::size_type cursor;
       string::size_type height,width;
};
      我们想有一个set操作和一个move操作,前者表示将特定字符写到光标指定处,后者表示将光标移到新位置.我们可能会希望这么来使用这两个函数(Screen myScreen):myScreen.move(4,0).set('#').它等价于:myScreen.move(4,0);myScreen.set('#').很明显,这个操作必须返回一个引用,指向执行操作的那个对象.即:
public:
      Screen& move(string::size_type r,string::size_type c);
      Screen& set(char);
      Screen& set(string::size_type,string::size_type,char);
这样,函数可以这样写:
      Screen& Screen::set(char c)
        {
         contens[cursor]=c;
         return *this;
         }
这样看起来似乎很完美,然而每个事物可能都不象它们表面看起来那么简单,比如说这次.如果我们再增加一个display函数.它是const型的.也就是说:display() const.*this本身是一个指向类类型的const指针.这就有问题了.因为这样以来,它返回的类型就变成了一个指向const类型的指针,而这个指针本身也是const类型的.也就是说,既不能改变指针的值,也不能改变指针指向的对象的值.下面的用法是错误的:
     myScreen.display().set('#');
因为函数会返回一个本身是const的指向const的指针,不能用set来改变它所指向的值.
解决方法是写一个重载函数,分为const和非const两种情况.如下:
{
public:
       Screen& display(std::ostream &os)
            {
             do_display(os);
             return *this;
            }
       Screen& display(std::ostream &os) const
            {
             do_display(os);
             return *this;
            }
private:
        void do_display(std::ostream &os) const
            {
            oss<<contents;
            }
};
      最后说一下友元,其实这个很简单,要说的是,重载函数必须在友元声明中全部声明.另外还有const static类型的成员,我觉得这个太过于技巧化,不适于初学者,就没有看.
       上面的都比较简单,这说明下面的可能就不会再简单了.事实上我认为下面的这些非常难,看了三遍才算是有些眉目了.因为它里面包含了指针,老实说,我感觉,在程序设计中,不管什么地方,只要遇到了指针,就准没好事儿.因为指针的隐藏的危险性,Andrew Koenig,Barbara Moo夫妇在给想成为更好的C++程序员的最重要的三条建议的第一条中就说:避免使用指针.我是这么理解这个建议的:在你完全理解了指针之后,避免使用指针.否则的话,更好的C++程序员就是在学习C++的时候不学习指针,这样有些搞笑吧.
       言归正传.
本帖最近评分记录
  • 刹那芳华 威望 +2 原创? 好象是吧 2007-10-11 10:49
The Return Of The Lord

TOP

复制内容到剪贴板
代码:
  为了集中精力来看一下最难的部分,我先说一下析构函数,本来这是最后才说的.撤销类对象时会运行析构函数:
        Salse_item *p=new Sales_item;
       {
        Salse_item item(*p);
        delete p;
       };
动态分配的对象只有在指向该对象的指针被删除时才撤销.如果没有删除该指针,不会运行析构函数,对象就会一直存在.有时候会需要编写一个析构函数.还有就是关于继承的构造函数和析构函数,我觉得没有什么新东西和新问题,学习它可以帮助更好地理解这两个函数.
       人在完成许多了工作之后,就会有一种想休息的感觉,接下来做事就会不再认真,以至于在后面做的总是不如前面,而如果后面的恰好就是比较重要的事的话,结果可能会比较让人不愉快,所以在一件重要的事情开始之前,我总是会问自己:你准备好了吗?其实我是想说,也许你也有这种情况.
        说了这么多,我还是没有看出来哪一个类是需要一个构造函数或是析构函数,当然,Lippman这个老头比咱们想象的要细致.他为我们准备了这么一个例子,相信你和我一样早已跃跃欲试了:
        假设有一个Message类和一个Folder类,前者表示消息,后者表示它出现的目录.Message中的Save和Remove函数,分别在指定Folder中保存和删除消息.对每个Message,它都保存一个指针集,(set)set中的指针指向该Message所在的Folder.而每个Folder中也有一个指针集,指向它所包含的Message.
        创建一个新的Message时,指定它的内容但不指定Folder,调用save将Message放入一个Folder.复制一个Message时,复制它的内容和指针集,并给指向源Message的每个Folder增加一个指向该Message的指针.赋值时,现在左Message中的每个Folder中删除指针,再将右边的指针赋值给它.并给所处的Folder增加一个指向该Message的指针.代码如下:
class Message
{
public:
       Message(const string &str=""):contents(str){}
       Message(const Message&);
       Message& operator=(const Message&);
       ~Message();
       void save(Folder&);
       void remove(Folder&);
private:
       srting contents;
       set<Folder*>folders;
       void put_Msg_in_Folders(const std::set<Folder*>&);
       void remove_Msg_from_Folders();
};
Message::Message(const Message &m)
{
put_Msg_in_Folders(folders);
}
void Message::put_Msg_in_Folders(const set<Folder*> &rhs)
{
for(set<Folder*>::const_iterator beg=rhs.begin();beg!=rhs.end();++beg)
            (*beg)->addMsg(this);
}
Message& Message::operator=(const Message &rhs)
{
if(rhs!=this)
{
  remove_Msg_from_Folders();
  contents=rhs.contents;
  folders=rhs.folders;
  put_Msg_in_Folders(rhs.folders);
}
return *this;
}
void Message::remove_Msg_from_Folders()
{
for(set<Folder*>::const_iterator beg=folders.begin();beg!=folders.end();++beg)
(*beg)->remMsg(this);
}
Message::~Message()
{
remove_Msg_from_Folders();
}
Folder类等我学完了容器之后应该可以写出来了,以后再说.
       还有一种情况没有说,就是类成员指针指向类成员.这是和个比较麻烦的事.比如一个名为"CStudent"的类,其中有一个指针指向该对象的父母的姓名.如果遇上一对双胞胎,我们就可以复制一下.因为两个人父母姓名相同.但同时你也要明白,后面一个对象的指针指向的内容是前者的父母姓名,因为两人是双胞胎,所以是正确的.但是如果那个指针是指向该对象的女朋友的姓名,那就坏了,虽然是亲兄弟,但也不能这样啊.如果要复制,不但要复制指针和指针指向的对象,而且还要修改后者指向的对象.下面的情况就是这样的.
     看这个例子:
class HasPtr
{
public:
       HasPtr(int *p,int i):prt(p),val(i){}
       int *get_ptr() const{return ptr;}
       int get_int() const{return val;}
       void  set_ptr(int *p){ptr=p;}
       void  set_int(int i){val=i;}
       int  get_ptr_val() const{return *ptr;}
       void set_ptr_val(int val)const{*ptr=val;}
private:
       int *ptr;
       int val;
}
类很简单,但是如下操作将出现问题:
int obj=0;
HasPtr ptr1(&obj,42);
HasPtr ptr2(ptr1);
复制之后,两个指针指向同一个对象,两个整形独立.这样,如果用ptr1删除了指针指向的对象,ptr2就不能再用了,但是我们并没有办法知道这个事.
解决办法是用一个计数类.如下:
class U_Ptr
{
friend class HasPtr;
int *p;
size_t use;
U_Ptr(int *p):ip(p),use(1){}
~U_Ptr(){delep ip;}
};
创建一个对象时该计数器加一,删除一个指针时减一,到零时删除对象.
还有一种情况就是可以在复制时同时复制指针和它指向的值,这样,两者就不会相互影响了.具体实现如下:
class HasPtr
{
public:
       HasPtr(const int &p,int i):ptr(new int(p)),val(i){}
       HasPtr(const HasPtr &orig):ptr(new int(*orig.ptr)),val(orig.val){}
       HasPtr& operator=(const HasPtr&);
       ~HasPtr(){delet ptr;}
       int get_ptr_val() const{return *ptr;}
       int get_int()const{return val;}
       void set_ptr(int *p){ptr=p;}
       void set_int(int i){val=i;}
       int *get_ptr()const{return ptr;}
       int set_ptr_val(int p)const{*ptr=p;}
private:
       int *ptr;
       int val;
};
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
*ptr=*rhs.ptr;
val=rhs.val;
return *this;
}
本帖最近评分记录
The Return Of The Lord

TOP

发新话题