c/c++ 复习随记(2)----fwqlzz love is for ever

4.1 类(Classes)

类(class)是一种将数据和函数组织在同一个结构里的逻辑方法。定义类的关键字为class ,其功能与C语言中的struct类似,不同之处是class可以包含函数,而不像struct只能包含数据元素。

类定义的形式是:

    class class_name {
        permission_label_1:
            member1;
        permission_label_2:
            member2;
        ...
    } object_name;
    

其中 class_name 是类的名称 (用户自定义的类型) ,而可选项object_name 是一个或几个对象(object)标识。Class的声明体中包含成员members,成员可以是数据或函数定义,同时也可以包括允许范围标志 permission labels,范围标志可以是以下三个关键字中任意一个:private:, public: 或 protected:。它们分别代表以下含义:

  • private :class的private成员,只有同一个class的其他成员或该class的“friend” class可以访问这些成员。
  • protected :class的protected成员,只有同一个class的其他成员,或该class的“friend” class,或该class的子类(derived classes) 可以访问这些成员。
  • public :class的public成员,任何可以看到这个class的地方都可以访问这些成员。

构造函数和析构函数 (Constructors and destructors)

对象(object)在生成过程中通常需要初始化变量或分配动态内存,以便我们能够操作,或防止在执行过程中返回意外结果。例如,在前面的例子中,如果我们在调用函数set_values( ) 之前就调用了函数area(),将会产生什么样的结果呢?可能会是一个不确定的值,因为成员x 和 y 还没有被赋于任何值。

为了避免这种情况发生,一个class 可以包含一个特殊的函数:构造函数 constructor,它可以通过声明一个与class同名的函数来定义。当且仅当要生成一个class的新的实例 (instance)的时候,也就是当且仅当声明一个新的对象,或给该class的一个对象分配内存的时候,这个构造函数将自动被调用。

析构函数Destructor 完成相反的功能。它在objects被从内存中释放的时候被自动调用。释放可能是因为它存在的范围已经结束了(例如,如果object被定义为一个函数内的本地(local)对象变量,而该函数结束了);或者是因为它是一个动态分配的对象,而被使用操作符delete释放

构造函数重载(Overloading Constructors)

像其它函数一样,一个构造函数也可以被多次重载(overload)为同样名字的函数,但有不同的参数类型和个数。记住,编译器会调用与在调用时刻要求的参数类型和个数一样的那个函数(Section 2.3, Functions-II)。在这里则是调用与类对象被声明时一样的那个构造函数。

实际上,当我们定义一个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 ,就必需要自己定义它们。

当然,如果你也可以重载class的构造函数,定义有不同的参数或完全没有参数的构造函数,见如下例子:

    // overloading class constructors
    #include <iostream.h>

    Class CRectangle {
        int width, height;
      public:
        CRectangle ();
        CRectangle (int,int);
        int area (void) {return (width*height);}
    };

    CRectangle::CRectangle () {
        width = 5;
        height = 5;
    }

    CRectangle::CRectangle (int a, int b) {
        width = a;
        height = b;
    }

    int main () {
        CRectangle rect (3,4);
        CRectangle rectb;
        cout << "rect area: " << rect.area() << endl;
        cout << "rectb area: " << rectb.area() << endl;
    }
			
rect area: 12

rectb area: 25

在上面的例子中,rectb 被声明的时候没有参数,所以它被使用没有参数的构造函数进行初始化,也就是width 和height 都被赋值为5。

注意在我们声明一个新的object的时候,如果不想传入参数,则不需要写括号():

CRectangle rectb; // right

CRectangle rectb(); // wrong! 

类的指针(Pointers to classes)

类也是可以有指针的,要定义类的指针,我们只需要认识到,类一旦被定义就成为一种有效的数据类型,因此只需要用类的名字作为指针的名字就可以了。例如:

CRectangle * prect;

是一个指向class CRectangle类型的对象的指针。

就像数据机构中的情况一样,要想直接引用一个由指针指向的对象(object)中的成员,需要使用操作符 ->。这里是一个例子,显示了几种可能出现的情况:

    // pointer to classes example
    #include <iostream.h>

    class CRectangle {
        int width, height;
      public:
        void set_values (int, int);
        int area (void) {return (width * height);}
    };

    void CRectangle::set_values (int a, int b) {
        width = a;
        height = b;
    }

    int main () {
        CRectangle a, *b, *c;
        CRectangle * d = new CRectangle[2];
        b= new CRectangle;
        c= &a;
        a.set_values (1,2);
        b->set_values (3,4);
        d->set_values (5,6);
        d[1].set_values (7,8);
        cout << "a area: " << a.area() << endl;
        cout << "*b area: " << b->area() << endl;
        cout << "*c area: " << c->area() << endl;
        cout << "d[0] area: " << d[0].area() << endl;
        cout << "d[1] area: " << d[1].area() << endl;
        return 0;
    }
			
a area: 2

*b area: 12

*c area: 2

d[0] area: 30

d[1] area: 56

以下是怎样读前面例子中出现的一些指针和类操作符 (*, &, ., ->, [ ]):

  • *x 读作: pointed by x (由x指向的)
  • &x 读作: address of x(x的地址)
  • x.y 读作: member y of object x (对象x的成员y)
  • (*x).y 读作: member y of object pointed by x(由x指向的对象的成员y)
  • x->y 读作: member y of object pointed by x (同上一个等价)
  • x[0] 读作: first object pointed by x(由x指向的第一个对象)
  • x[1] 读作: second object pointed by x(由x指向的第二个对象)
  • x[n] 读作: (n+1)th object pointed by x(由x指向的第n+1个对象)

由关键字struct和union定义的类

类不仅可以用关键字class来定义,也可以用struct或union来定义。

因为在C++中类和数据结构的概念太相似了,所以这两个关键字struct和class的作用几乎是一样的(也就是说在C++中struct定义的类也可以有成员函数,而不仅仅有数据成员)。两者定义的类的唯一区别在于由class定义的类所有成员的默认访问权限为private,而struct定义的类所有成员默认访问权限为public。除此之外,两个关键字的作用是相同的。

union的概念与struct和class定义的类不同, 因为union在同一时间只能存储一个数据成员。但是由union定义的类也是可以有成员函数的。union定义的类访问权限默认为public。

4.2 操作符重载(Overloading operators)

C++ 实现了在类(class)之间使用语言标准操作符,而不只是在基本数据类型之间使用。例如:

int a, b, c;

a = b + c;

是有效操作,因为加号两边的变量都是基本数据类型。然而,我们是否可以进行下面的操作就不是那么显而易见了(它实际上是正确的):

struct { char product [50]; float price; } a, b, c;

a = b + c;

将一个类class (或结构struct)的对象赋给另一个同种类型的对象是允许的(通过使用默认的复制构造函数 copy constructor)。但相加操作就有可能产生错误,理论上讲它在非基本数据类型之间是无效的。

但归功于C++ 的操作符重载(overload)能力,我们可以完成这个操作。像以上例子中这样的组合类型的对象在C++中可以接受如果没有操作符重载则不能被接受的操作,我们甚至可以修改这些操作符的效果。以下是所有可以被重载的操作符的列表:

	+    -    *    /    =    <    >    +=   -=   *=   /=   <<   >>
	<<=  >>=  ==   !=   <=   >=   ++   --   %    &    ^    !    |
	~    &=   ^=   |=   &&   ||   %=   []   ()   new  delete
	

要想重载一个操作符,我们只需要编写一个成员函数,名为operator ,后面跟我们要重载的操作符,遵循以下原型定义:

type operator sign (parameters);

这里是一个操作符 +的例子。我们要计算二维向量(bidimensional vector) a(3,1) 与b(1,2)的和。两个二维向量相加的操作很简单,就是将两个x 轴的值相加获得结果的x 轴值,将两个 y 轴值相加获得结果的 y值。在这个例子里,结果是 (3+1,1+2) = (4,3)。

    // vectors: overloading operators example
    #include <iostream.h>

    class CVector {
      public:
        int x,y;
        CVector () {};
        CVector (int,int);
        CVector operator + (CVector);
    };

    CVector::CVector (int a, int b) {
        x = a;
        y = b;
    }

    CVector CVector::operator+ (CVector param) {
        CVector temp;
        temp.x = x + param.x;
        temp.y = y + param.y;
        return (temp);
    }

    int main () {
        CVector a (3,1);
        CVector b (1,2);
        CVector c;
        c = a + b;
        cout << c.x << "," << c.y;
        return 0;
    }
			
4,3

如果你迷惑为什么看到这么多遍的 CVector,那是因为其中有些是指class名称CVector ,而另一些是以它命名的函数名称,不要把它们搞混了:

   CVector (int, int);            // 函数名称 CVector (constructor)
   CVector operator+ (CVector);   // 函数 operator+ 返回CVector 类型的值
   

Class CVector的函数 operator+ 是对数学操作符+进行重载的函数。这个函数可以用以下两种方法进行调用:

c = a + b;

c = a.operator+ (b);

注意:我们在这个例子中包括了一个空构造函数 (无参数),而且我们将它定义为无任何操作:

CVector ( ) { };

这是很必要的,因为例子中已经有另一个构造函数,

CVector (int, int);

因此,如果我们不像上面这样明确定义一个的话,CVector的两个默认构造函数都不存在。

这样的话,main( )中包含的语句

CVector c;

将为不合法的。

尽管如此,我已经警告过一个空语句块 (no-op block)并不是一种值得推荐的构造函数的实现方式,因为它不能实现一个构造函数至少应该完成的基本功能,也就是初始化class中的所有变量。在我们的例子中,这个构造函数没有完成对变量x 和 y 的定义。因此一个更值得推荐的构造函数定义应该像下面这样:

CVector ( ) { x=0; y=0; };

就像一个class默认包含一个空构造函数和一个复制构造函数一样,它同时包含一个对赋值操作符assignation operator (=)的默认定义,该操作符用于两个同类对象之间。这个操作符将其参数对象(符号右边的对象) 的所有非静态 (non-static) 数据成员复制给其左边的对象。当然,你也可以将它重新定义为你想要的任何功能,例如,只拷贝某些特定class成员。

重载一个操作符并不要求保持其常规的数学含义,虽然这是推荐的。例如,虽然我们可以将操作符+定义为取两个对象的差值,或用==操作符将一个对象赋为0,但这样做是没有什么逻辑意义的。

虽然函数operator+ 的原型定义看起来很明显,因为它取操作符右边的对象为其左边对象的函数operator+的参数,其它的操作符就不一定这么明显了。以下列表总结了不同的操作符函数是怎样定义声明的 (用操作符替换每个@):

Expression Operator (@) Function member Global function
@a + - * & ! ~ ++ -- A::[email protected]( ) [email protected](A)
[email protected] ++ -- A::[email protected](int) [email protected](A, int)
[email protected] + - * / % ^ & | < > == != <= >= << >> && || , A::[email protected](B) [email protected](A, B)
[email protected] = += -= *= /= %= ^= &= |= <<= >>= [ ] A::[email protected](B) -
a(b, c...) ( ) A::operator()(B, C...) -
a->b -> A::operator->() -

* 这里a 是class A的一个对象,b 是 B 的一个对象,c 是class C 的一个对象。

从上表可以看出有两种方法重载一些class操作符:作为成员函数(member function)或作为全域函数(global function)。它们的用法没有区别,但是我要提醒你,如果不是class的成员函数,则不能访问该class的private 或 protected 成员,除非这个全域函数是该class的 friend (friend 的含义将在后面的章节解释)。

关键字 this

关键字this 通常被用在一个class内部,指正在被执行的该class的对象(object)在内存中的地址。它是一个指针,其值永远是自身object的地址。

它可以被用来检查传入一个对象的成员函数的参数是否是该对象本身。例如:

    // this
    #include <iostream.h>

    class CDummy {
      public:
        int isitme (CDummy& param);
    };

    int CDummy::isitme (CDummy& param) {
        if (&param == this) return 1;
        else return 0;
    }

    int main () {
        CDummy a;
        CDummy* b = &a;
        if ( b->isitme(a) )
            cout << "yes, &a is b";
        return 0;
    }
			
yes, &a is b

它还经常被用在成员函数operator= 中,用来返回对象的指针(避免使用临时对象)。以下用前面看到的向量(vector)的例子来看一下函数operator= 是怎样实现的:

   CVector& CVector::operator= (const CVector& param) {
       x=param.x;
       y=param.y;
       return *this;
   }

实际上,如果我们没有定义成员函数operator=,编译器自动为该class生成的默认代码有可能就是这个样子的。

静态成员(Static members)

一个class 可以包含静态成员(static members),可以是数据,也可以是函数。

一个class的静态数据成员也被称作类变量"class variables",因为它们的内容不依赖于某个对象,对同一个class的所有object具有相同的值。

例如,它可以被用作计算一个class声明的objects的个数,见以下代码程序:

    // static members in classes
    #include <iostream.h>

    class CDummy {
      public:
        static int n;
        CDummy () { n++; };
        ~CDummy () { n--; };
    };

    int CDummy::n=0;

    int main () {
        CDummy a;
        CDummy b[5];
        CDummy * c = new CDummy;
        cout << a.n << endl;
        delete c;
        cout << CDummy::n << endl;
        return 0;
    }
			
7

6

实际上,静态成员与全域变量(global variable)具有相同的属性,但它享有类(class)的范围。因此,根据ANSI-C++ 标准,为了避免它们被多次重复声明,在class的声明中只能够包括static member的原型(声明),而不能够包括其定义(初始化操作)。为了初始化一个静态数据成员,我们必须在class之外(在全域范围内),包括一个正式的定义,就像上面例子中做法一样。

因为它对同一个class的所有object是同一个值,所以它可以被作为该class的任何object的成员所引用,或者直接被作为class的成员引用(当然这只适用于static 成员):

cout << a.n;

cout << CDummy::n;

以上两个调用都指同一个变量:class CDummy里的static 变量 n 。

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

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

时间: 2024-10-11 17:32:19

c/c++ 复习随记(2)----fwqlzz love is for ever的相关文章

c/c++ 复习随记----fwqlzz love is for ever

标识(Identifiers) 有效标识由字母(letter),数字(digits)和下划线 ( _ )组成.标识的长度没有限制,但是有些编译器只取前32个字符(剩下的字符会被忽略). 空格(spaces),标点(punctuation marks)和符号(symbols) 都不可以出现在标识中. 只有字母(letters),数字(digits) 和下划线(_)是合法的.并且变量标识必须以字母开头.标识也可能以下划线(_)开头,但这种标识通常是保留给为外部连接用的.标识不可以以数字开头. 必须注

暴力英语学习法 + 严格的目标管理 = 成功快速靠谱的学好英语

Updated: 留下邮件的同学,我已经将链接发到你们邮件了,没有留邮箱的同学,你们就自己到下面拿链接和密码下载吧.:) 园子里时不时就吹起一阵学英语的浪潮,不少同鞋表示一直想学,或者一直在学,就是效果不明显(你躺枪了么?)相信自己或者身边的人都或多或少吃了英语弱的当(你懂的,我们重点在说薪水的问题:).而各种英语成功学,方法论,版本是一个接一个层出不穷.今天我们不说为什么要学好英语,好处太多而且已经广为流传了,我们主要结合目标管理来讨论一下如何坚定不移的,快速的学好英语.以我自己的亲身经历作样

【转】暴力英语学习法 + 严格的目标管理 = 成功快速靠谱的学好英语

园子里时不时就吹起一阵学英语的浪潮,不少同鞋表示一直想学,或者一直在学,就是效果不明显(你躺枪了么?)相信自己或者身边的人都或多或少吃了英语弱的当(你懂的,我们重点在说薪水的问题:).而各种英语成功学,方法论,版本是一个接一个层出不穷.今天我们不说为什么要学好英语,好处太多而且已经广为流传了,我们主要结合目标管理来讨论一下如何坚定不移的,快速的学好英语.以我自己的亲身经历作样板,以下情况全部属实,绝无虚构. 先说说我在开始学英语之前的情况: 时间:2012年2月,已工作4.5年 词汇量:小于15

巧学教育公益速记课第十期第一组作业2016年3月31号

一号:刘健凤 总学时52分钟 1.数字编码1-10制作完 2.完成36计1-5 表扬自己:坚持早起晨读和运动,不但拥有一个健康的体魄,同时拥有一个高大的灵魂!表扬别人:我们住院部主任做事雷厉风行,但是对病人如沐春风,对病人像家人一样,凡事从病人角度出发,是真正的医者仁心!我要多跟主任学习专业知识和做为医者父母心的高尚情操 3.学习后的感受:发现速记采用图像后,真的很快.以后学习可以节省好多时间了.自己不足之处是绘画技巧不足,相信以后多加练习会越来越好的,能跟大家一起学习很开心,感恩! 二号蒋文静

暴力英语学习法(转载)

暴力英语学习法 园子里时不时就吹起一阵学英语的浪潮,不少同鞋表示一直想学,或者一直在学,就是效果不明显(你躺枪了么?)相信自己或者身边的人都或多或少吃了英语弱的当(你懂的,我们重点在说薪水的问题:).而各种英语成功学,方法论,版本是一个接一个层出不穷.今天我们不说为什么要学好英语,好处太多而且已经广为流传了,我们主要结合目标管理来讨论一下如何坚定不移的,快速的学好英语.以我自己的亲身经历作样板,以下情况全部属实,绝无虚构. 先说说我在开始学英语之前的情况: 时间:2012年2月,已工作4.5年

科学方法 + 目标管理 = 学好英语

科学方法 + 目标管理 = 学好英语 园子里时不时就吹起一阵学英语的浪潮,不少同鞋表示一直想学,或者一直在学,就是效果不明显(你躺枪了么?)相信自己或者身边的人都或多或少吃了英语弱的当(你懂的,我们重点在说薪水的问题:).而各种英语成功学,方法论,版本是一个接一个层出不穷.今天我们不说为什么要学好英语,好处太多而且已经广为流传了,我们主要结合目标管理来讨论一下如何坚定不移的,快速的学好英语.以我自己的亲身经历作样板,以下情况全部属实,绝无虚构. 先说说我在开始学英语之前的情况: 时间:2012年

关于Java构造类与对象的思考

简单记录一下Java构造类与对象时的流程以及this和super对于特殊例子的分析. 首先,接着昨天的问题,我做出了几个变形: Pic1.原版: Pic2.去掉了T.foo方法中的this关键字: Pic3.在原版的基础上抹掉了B.foo方法: Pic4.抹掉了原版的B.foo方法,同时去掉了T.foo方法中的this关键字: 这四个图全方位地对this和super两个关键字的有无进行了测试,测试结果表明:这些版本的程序的运行结果都一样... 在这里,还是插一张流程图: 首先说一下super关键

21天战拖记——Day6:复习阶段性完成!(2014-05-09)

今天上完了英语的最后一堂课,看了一场电影<两个小洛特>:两个长得一模一样的姑娘,由于父母离婚,她们被抓散了.为了争取自己的幸福,她们想出一个大胆的冒险计划:姐姐代替妹妹,妹妹代替姐姐,回到离异的父母身边,由此产生了一连串奇特有趣的故事,经过种种曲折和磨难,她们终于破镜重圆. 这是我学生生涯的最后一堂课了,之后应该不会再上课了,下课后去了西院图书馆,果断复习<随机过程>嘛.今天终于将整个的书本的内容看完了,明天可以看试卷了. 学习<小强升职记(升级版)>记录: "

21天战拖记——Day5:今天继续复习《随机过程》!(2014-05-08)

本系列文章在作者的个人博客.博客园.CSDN同步连载,如有转载,请注明作者和原始链接. 又是8:30起床的,本来自己的闹钟是7:00可是它居然没有响,而小伙伴的闹钟是7:20,可是今天没有课呀,更重要的是他没醒,而闹钟一直响,把我吵醒了,我下床去关了,再回来睡觉,一觉醒来,8:30,你真准时. 又呆了一天的自习室,不知怎么的,老是将最重要的事情放到最后做,前面不是看小说.刷微博,就是看杂志,无心学习.而看书的时间很少,又没有去实验室,不过还是紧赶慢赶把今天要看的东西看完了,第3章概念+例题+习题