分享

关于类的基础介绍

 fisher60 2012-09-01
c++被称为带类的c,可见,类当之无愧是c++的核心内容。以前看c++方面的书籍也看过不少,关于c++中类的那一部分章节也都仔细的看过,看过之后,觉得比较平常。当时的理解呢,c++中的类就是一个封装,在这个封装里有着多个数据成员,这些数据成员构成了这个类的属性,但是这些属性一般我们将之定义为private,在外面的interface层面,我们定义了函数来对这些属性进行操作。从另一个角度理解,这又是一种数据抽象。标准库里的 vector是一种类(暂且这样描述吧),数组与它相比较,既不是抽象的,也不是封装的,我们可以通过指针等手段任意操纵它。可以说,这是一个危险动作,大部分c++专家都不鼓励使用指针,原因就在于它的功能过于强大,我们无法对它的使用进行一定的限制,所以连带着连数组也不是特别推荐使用,呵呵。当然了,关于类的知识还有重载以及继承等等。其实,通过最近一段时间对c++类的比较深入的学习(^_^,有点不太谦虚哦),发现以前完全低估了c++的创造者们,类其实是一个经过严格周密定义的“数据类型”,通过它,我们可以戴着脚镣跳舞,可以这样说吧,实现十分丰富的功能。这也是我之所以喜欢c++的原因,正是因为定义的规范和复杂,我们才可以清楚的知道我们在做什么并且写出完美的代码。

好,书归正传,首先,为什么要有类?在c++里,我们有着丰富的数据类型,它们构成了我们操作具体数据的基石,但是,仅有这些内置类型是远远不够的。引入类,至少带来了以下便利:一,实现封装,这点前面已经提过了,我们在掩盖了实现的代码的同时,向使用我们类代码的程序员(一般是我们自己咯)提供了一个接口,我认为,这是未来软件发展的方向,软件世界应当是一个积木结构architecture,好像COM也是类似的样子吧,以前看过一点,记不太清楚了。二,面向对象,我认为这是设计类的初衷所在,原始的c代码在处理当今世界复杂多变的问题,其不足应当是越加明显的暴露了出来,面向对象(object-oriented)成为了程序设计的主流(这也是Java现在这么热门的原因之一),在处理一个对象时,我们将可以使用一个类来完整的处理它,同时,类的作用域又使它相当具有独立性。这个类成为这种对象在程序里的抽象表示。三,安全规范,我们定义类的时候,可以定义对类所进行的操作。再有,使用了类,代码还具有了重用性以及易于更新。相信你已经想到这一点了,呵呵。在这里,我想说,编写一个类的较高境界就是使用类和使用int等数据类型一样方便,在适当情况下,甚至有可能达到更加满
意的效果。

类一般都是有成员的,要不然,要类干吗呢?成员可以分为数据成员和成员函数。定义了类之后,也就是class { };之后,这个类的成员也就不能再扩展了,当然成员函数是可以在外边定义的(已经在类里声明过了,如果在类里定义的,则一般为inline函数),这里要加上作用域符号::,在我的理解里,这个类的作用域是class { };的大括号内部以及外部定义函数的内部,其他情况一般都已经出了此类的作用域。这里需要说明的是,在对类的定义进行编译时,是首先对类的成员的声明进行编译,然后再对定义进行操作,至于其他关于作用域方面的知识,在学习谭浩强那本c是我们就学过了,呵呵。有些programmers习惯于将所有数据成员和成员函数定义为public,这样有时候或许会比较方便,但是绝对是不可取的,原因么?丧失了对类对象的保护机制。我认为一般情况下,数据成员应当是 private的,少部分成员函数也应当是private的,当然,这肯定要看到具体应用了。下面,咱们说说inline函数,它的特点就是在调用处展开,所以速度快点。除了在类内部定义默认为inline之外,我们可以显式指定inline,在声明和定义处指定都行。但是,在类的定义体外部的话呢,应当包括在类定义的头文件中,因为它必须对调用它的源文件可见,呵呵,这样一说,简单了吧。在类的定义体里呢,我们是不能定义一个类作为这个类的数据成员,怎么这么别扭呢!在这种情况下,我们可以使用引用(referance),再说一遍吧,在类里,我强烈推荐使用引用而尽量避免使用指针。关于类的成员函数的重载和函数的重载没多大差别,我就不罗索了。

在这里我想比较详细的说一下this指针,我们知道数据成员是每个对象都有的(static成员除外),而成员函数作为一种operation method,理应是公有的。但是,当我们应用一个具体的对象来调用成员函数时,c++是如何实现对本对象的数据成员进行操作的呢?其实,每个成员函数都有一个默认的this指针,指向了我们操作的这个对象,这样就可以理解了吧。知道了这一点呢,我们可以使成员函数返回一个*this, return *this;这样呢,我们就返回了一个对本对象的引用,对了,还应当将函数的类型声明为classname & memberfunctionname(){};呵呵。

在这里,this是一个const指针。这个有什么用处呢?我举个例子,cout<<""<<"";明白了吧?就是可以这样实现串连式的调用,当然这里是运算符的重载,不过应该差不多咯!说到const,那么,假如我们想将一个成员函数设计为const呢,只需在成员函数声明和定义后面加一个const就行了,这就表示这个成员函数是没法修改数据成员的(当然咯,它也不能调用非const函数)。下面呢,我们说个虽然简单但是内涵却很丰富的例子:
classconst& displayconst() const
    {
        cout<<num1<<num2<<endl;
        return *this;
    }//classconst is the name of the class;
这段代码是无法compile的。为什么?想过再接着往下看。原因一点就破,因为this 返回的是引用型的,所以可以作为左值改变,这便影响到调用的类是const型(一般是这样的)的情形,所以原因提示为cannot convert from 'const class classconst' to 'class classconst &'。亲爱的朋友,你明白了吗?你可以参照引用的用法来理解这段代码,因为不是重点,我这里就不讲了。至于修改的办法,前面加个 const,OK。这里呢又出现了一个问题,就是,还挺招人烦的,返回的对象不能再像cout那样调用非const型成员函数了。怎么办?两个答案:一,就不再调用,因为我觉得不必实现这么无聊的code,二 ,对这个const 函数进行非const重载。一般说来,Non-const function is preferred when called for a non-const object. For a const object non-const function cannot be called。有些十分BT的情况,数据必须是可变的,即使类对象被声明为const也是可变的,这是就需要mutable来修饰这个极为BT的成员函数了。在这里我想说说我对const的理解,那就是说,这个对象或者数据(const int a=8)一经初始化(必须进行初始化)就不能再改变了,当然,mutable就是一个特例,因为它mutable嘛。声明定义初始化是C的基础,我在这里讲讲吧,也是为了引出下面的构造函数,本来打算先讲讲static类型成员函数的,只好改变咯。

先说一下内置数据成员(曾经和一个同学说到控制台程序,他嫌我拐了一个大弯,说话太术语了,呵呵),就是int,char,float什么的,如果是全局变量的话,一般不初始化的话,编译器赋值为0。但是,如果是局部的,那么就不太一样,我测了一下,是这个样子,int和float型是随即的,但是char是-52,所以是无法cout的,这里我不太清楚是为什么,朋友们有知道的一定要给我留言啊,先谢谢了。const和referance必须初始化,并且一经初始化(赋值的形式)就不可再变了,point就不一样了。其实,说到这些内置类型,差别是不算什么的,一般也就是毛毛细雨,但是对于类类型来说,差别那可是非常大滴!下面我们就来讨论一下构造函数,以及复制构造,赋值运算以及析构函数,是不是有点晕那?其实现在我也比较晕,就让我们在“晕”中继续我们的c++学习之路,呵呵。

在这之前,我先写两个例子,int a; int a=0; int a(0);我就不说什么了。请你先思考一下,这个问题后面会有讲到。构造函数相当于给了你一个机会去初始化,就像你去开会,去是必须去的,当然可能你去了什么都没有干。一会儿回过头看这个比喻可能会有更好的理解。每个类的对象都必须执行构造函数即使是这种形式 classname classobject;也就是说这种形式是类似于int a的,但是它执行了构造函数,这个构造函数我们称之为默认构造函数(default constructor)。给default constructor下个定义吧,就是你不提供任何参数(也就是对象名后面没有括号那种)依然能够执行的构造函数,它一般分为两种吧:一 classname () {....};二 classname ( p1=...,p2=...){......};其中第二种中每个形参都有默认值,这样也是可以的哟!下面我说一下我对构造函数的理解吧:它在定义每个类的时候自动运行,这是强制性的,但是你可以选择什么都不干,让编译器默认去做。默认构造函数一般就是这样子,就像不要依赖不确定行为,也尽量不要这样子简略的初始化,除非特
别简单的情形。说到数据成员,内置类型就是那样子去做咯,至于类,再次调用它的构造函数。这里有两个需要注意的地方,首先是构造函数初始化列表,不知道大家听说过没有,它大概是这个样子的,classname(...):dm1(..),dm2(..),dm3(..){};(注意,它只是出现在构造函数的定义中)。存在得有个理由吧(就像咱们活着总得给这个社会做点贡献吧,呵呵),我们知道有些数据必须初始化必须初始化,前面也讲到
了。如果这些数据类型作为数据成员出现呢??这里需要明确一点(在构造函数的大括号里面进行的是赋值,是赋值而不是初始化),初始化列表,物如其名。第二个是隐式类型转换,啥意思呢?就是说,假如一个函数的参数要求是一个类,但我们类代码的使用者却不小心提供了一个其他的类型,比如string类型,而且碰巧呢,有这么一个构造函数,它的构造函数只要求你提供一个string类型,那么,编译器就将悄悄调用这个构造函数来实现一个临时对象,来完成这个函数的调用,要阻止这种情况的出现,便是在那些只有一个形参的构造函数前面加一个explicit,就行了。如果还想实现原来的功能的话,可以使用classname(stringobject),这是一个无名对象,执行完这个语句,就死了。

说来好笑,刚才忽然想到一点,一个类可以有多个默认构造函数吗?尝试了一下,答案是否定的。 warning C4520: 'myclass' : multiple default constructors specified   &   error C2668: 'myclass::myclass' : ambiguous call to overloaded function。呵呵!下面我们就说一下赋值构造函数,赋值运算符和析构函数。把这三个放在一块儿是有原因的,如我们没有显式给出它们的话,那么编译器会给出精炼的默认版本,c++将这三个合称为复制控制(Copy Control)。
前面说到过int a; int a (0); int a=0; 如果换成类的形式的话,第二个和第三个就分别对应复制构造函数和赋值运算了。文章也写了老长了,这里我就拣要点说说吧。这两个我认为没有什么本质的差别,虽然一个属于构造函数,一个属于运算符的重载。归总的说,它们基本上实现了对应元素的copy。这里有一个问题,就是如果有指针数据成员怎么办的问题,答案是你自己code咯。你看,其实就这么简单。就像前几天聊天是说得,现在这名次吓死人,象什么“多目标预测控制”,其实原理也就是那样,呵呵。至于析构函数嘛,就是释放资源的,明白?我想指出的是,你自己编的析构函数是掩盖不了编译器的析构函数的,当你的运行完后,编译器那个会再运行的。由于析构并不象构造用的那么多,

我就不罗唆了,*^_^*。

下面再讲两点就OVER。

友元:友元就相当于这个类首肯或者默许的一个小管家,经过了主人(类)的同意(在类的定义体里friend一下),就取得了对私有数据的操作权,就像自家人一样了,呵呵。什么可以成为友元呢,类,类的成员函数,重载的运算符,以及全局函数都行的。这里,类和全局函数不用先定义就能声明为朋友,类成员函数的话,就得先定义类了。据我所知,cout的<<一般就是friend的,呵呵。

static类型成员:什么意思呢?就是说,它是这个类型的对象所共有的一个数据成员或者成员函数。从网上搜到的一点知识,比较全面,共享

一下咯:
1、静态数据成员
  
特点:
  A、内存分配:在程序的全局数据区分配。
  B、初始化和定义:
   a、静态数据成员定义时要分配空间,所以不能在类声明中定义。
   b、为了避免在多个使用该类的源文件中,对其重复定义,所在,不能在类的头文件中
    定义。   
   c、静态数据成员因为程序一开始运行就必需存在,所以其初始化的最佳位置在类的内部实现。
  C、特点
   a、对相于   public,protected,private   关键字的影响它和普通数据成员一样,   
   b、因为其空间在全局数据区分配,属于所有本类的对象共享,所以,它不属于特定的类对象,在没产生类对象时其作用域就可见,即

在没有产生类的实例时,我们就可以操作它。
  
  D、访问形式
   a、   类对象名.静态数据成员名
   b、   类类型名::   静态数据成员名
  
  E、静态数据成员,主要用在类的所有实例都拥有的属性上。比如,对于一个存款类,账号相对   于每个实例都是不同的,但每个实

例的利息是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局区的内存,所以节省存贮空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了,因为它们实际上是共用一个东西。
  
2、静态成员函数
  
特点:
  A、静态成员函数与类相联系,不与类的对象相联系。
  B、静态成员函数不能访问非静态数据成员。原因很简单,非静态数据成员属于特定的类实例。
  
作用:
  主要用于对静态数据成员的操作。
调用形式:
  A、类对象名.静态成员函数名()
  B、类类型名::   静态成员函数名()

针对上面说的,我补充几句:我觉得static成员函数没啥用处。如果static数据成员是static类型的,就可以在类定义体内初始化,但是还是

得在外边定义一次。最后再说一点,static成员是类这个层次得,普通成员是对象层次得,这个用多了就有体会了,例如,构造函数得默认参

数就能用static数据成员,就不能用普通数据成员。我用的也不多,呵呵,象品一杯香茗一样,好好品位吧

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多