2019-04-09 C++学习笔记之类和对象(下)

参考资料:《21天学通C++》

拷贝构造函数

拷贝构造函数的作用是用一个已经存在的对象来初始化该类的新对象,用户可以根据需要定义拷贝构造函数,也可以由系统生成一个默认的拷贝构造函数。

定义拷贝构造函数

定义拷贝构造函数的形式为:

类名(类名 &对象名) {
    函数体
}

其中对象名是用来初始化另一个对象的引用。

例1 自定义拷贝构造函数

#include <iostream>
using namespace std;

class point {
private:
    double fx, fy;
public:
    point(point &p);                    //声明拷贝构造函数
    point(double x, double y);
    void showpoint();
};

point :: point(point &p) {              //定义拷贝构造函数
    fx = p.fx + 10;                     //定义拷贝构造函数的功能
    fy = p.fy + 20;
}

point :: point(double x, double y) {    //定义构造函数
    fx = x;
    fy = y;
}

void point :: showpoint() {
    cout << fx << "\t" << fy << endl;
}

int main(void) {
    point p1(1.1, 2.2);                 //用构造函数创建对象p1
    cout << "The fx and fy of p1: ";
    p1.showpoint();

    point p2(p1);                       //用拷贝构造函数创建对象p2
    cout << "The fx and fy of p2: ";
    p2.showpoint();

    return 0;
}

输出:

The fx and fy of p1: 1.1        2.2
The fx and fy of p2: 11.1       22.2

上述代码中,在第8行声明了一个拷贝构造函数,在第13~16行定义了该拷贝构造函数的功能。在main()函数中,使用了语句point p2(p1);语句创建了对象p2,其调用的是拷贝构造函数point(point &p)。

调用拷贝构造函数

一般来说,以下三种情况拷贝构造函数会被调用:

  • 用类的对象去初始化类另一个对象时
  • 函数的形参是类的对象,调用函数进行形参和实参结合时
  • 函数的返回值是类的对象,函数执行完返回调用者时

例2 调用拷贝构造函数

#include <iostream>
using namespace std;

class point {
private:
    int x, y;
public:
    point(int a = 0, int b = 0) {
        x = a;
        y = b;
    }
    
    point(point &p);                        //声明拷贝构造函数
    
    int getx() {
        return x;                           //取成员变量x的值
    }

    int gety() {
        return y;                           //取成员变量y的值
    }
    
};

point :: point(point &p) {                  //定义拷贝构造函数
        x = p.x + 10;                       //定义拷贝构造函数的功能
        y = p.y + 20;
        cout << "调用拷贝构造函数" << endl;
}

void f(point p) {
    cout << p.getx() << "\t" << p.gety() << endl;
}

point g() {
    point q(3, 5);                          //调用拷贝构造函数
    return q;
}

int main(void) {
    point p1(2, 4);                         //用构造函数创建对象p1
    point p2(p1);
    cout << p2.getx() << "\t" << p2.gety() << endl;
    f(p2);
    p2 = g();
    cout << p2.getx() << "\t" << p2.gety() << endl;
    return 0;
}

输出:

调用拷贝构造函数
12      24
调用拷贝构造函数
22      44
调用拷贝构造函数
13      25

上述代码中,定义了类point和拷贝构造函数point(point &p),在main()函数中首先用构造函数声明了对象p1,接着分别采用上述三种形式调用拷贝构造函数声明了p2,并输出了x和y的值。

默认拷贝构造函数

当用一个已经存在的对象初始化本类的新对象时,如果没有自定义拷贝构造函数,则系统会自动生成一个默认的拷贝构造函数来完成初始化工作。

析构函数

析构函数也是一类特殊的成员函数,也被声明为公有成员。析构函数作用时释放分配给对象的内存空间,并做一些“善后”工作。析构函数在声明定义和使用时需要注意:

  • 析构函数的名字必须与类名相同,但在名字前要加波浪号“~”
  • 析构函数无参数、无返回值、不能重载,在一个类中只能有一个析构函数
  • 撤销对象时,系统自动调用析构函数完成空间的释放和“善后”工作

例3 析构函数的声明和使用

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

class complex {
private:
    double real, imag;
public:
    complex(double real = 0.0, double imag = 0.0);
    ~complex();                                 //声明析构函数
    double abscomplex();
};

complex :: complex(double r, double i) {
    cout << "constructing" << endl;
    real = r;
    imag = i;
}

complex :: ~complex() {                         //定义析构函数
    cout << "destructing" << endl;
}

double complex :: abscomplex() {
    double n;
    n = real * real + imag * imag;
    return sqrt(n);
}

int main(void) {
    complex ob(1.1, 2.2);
    cout << "abs of complex ob = " << ob.abscomplex() << endl;
    return 0;
}

输出:

constructing
abs of complex ob = 2.45967
destructing

上述代码中,定义了一个类complex,在类中声明了该类的构造函数和析构函数,在类外定义了构造函数和析构函数。在main()函数中创建对象ob,调用构造函数,因此输出“constructing”的信息。程序结束后编译系统自动调用析构函数释放该对象,因此输出“destructing”的信息。

注意:

  • 每一个类必须有一个析构函数,若没有显式定义,则系统会自动生成一个默认的析构函数,它是一个空函数
  • 对于大多数类而言,默认的析构函数就可以满足需求,但如果对象在完成操作前需要做内部处理,则应该显式地定义析构函数
  • 构造函数和析构函数最常见的用法是,在构造函数中用new运算符为对象分配空间,在析构函数中用delete运算符释放空间

友元

友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。

如果友元是一般成员函数或类的成员函数,则称为友元函数;如果友元是一个类,则称为友元类,友元类的所有成员函数都是友元函数。

友元函数

友元函数不是当前类的成员函数,而是独立于当前类的外部函数,它可以是普通函数或其他类的成员函数。友元函数定以后可以访问该类的所有对象成员。

友元函数在使用前必须要在类定义时声明,声明时在其函数名前加上关键字friend,该声明可以放在公有成员中,也可以放在私有成员中。

友元函数的定义可以在类的内部进行,也可以在类的外部进行,但通常都放在类的外部。

例4 普通函数作为友元函数

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

class point {
private:
    double x, y;
public:
    point(double a = 0, double b = 0) {
        x = a;
        y = b;
    }
    
    point(point &p);                                         //重载构造函数
    
    double getx() {
        return x;
    }
    double gety() {
        return y;
    }

    friend double dist(point &p1, point &p2);                //声明友元函数
};

double dist(point &p1, point &p2) {                          //定义友元函数
    return (sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)));
}

int main(void) {
    point ob1(1, 1);
    point ob2(4, 5);
    
    cout << "The distance is: " << dist(ob1, ob2) << endl;  //调用友元函数

    return 0;
}

输出:

The distance is: 5

上述代码定义了类point,在类中声明了友元函数dist(),在类的外部对该函数进行了定义。

注意:

  • 友元函数不是成员函数,所以定义时不必加域运算符
  • 友元函数不是成员函数,所以不能直接引用对象成员的名字,也不能通过this指针引用对象的成员,必须通过作为入口的参数传递进来的对象名或对象指针来引用该对象的成员。为此,友元函数一般都有一个该类的入口参数,如dist(point &p1, point &p2)。
  • 当一个函数需要访问多个类时,应该把这个函数同时定义为这些类的友元函数

友元成员

如果一个类的成员函数是另一个类的友元函数,则称这个成员函数为友元成员。通过友元成员函数,不仅可以访问自己所在类对象中的私有和共有成员,还可以访问由关键字friend声明语句所在的类对象中的私有成员,从而使两个类可以互相访问。

例5 友元成员的应用

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

class boy;                                  //声明类boy

class girl {                                //定义类girl
private:
    char *name;
    int age;
public:
    girl(char *n, int a) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
        age = a;
    }
    void prt(boy &);                        //声明公有成员函数
    ~girl() {                               //定义析构函数
        delete name;                        //释放空间
    }
};

class boy {                                 //定义类boy
private:
    char *name;
    int age;
public:
    boy(char *n, int a) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
        age = a;
    }
    friend void girl :: prt(boy &);         //声明友元成员
    ~boy() {
        delete name;
    }
};

void girl :: prt(boy &b) {                  //定义友元成员
    cout << "girl\'s name: " << name << " age: " << age << endl;
    cout << "boy\'s name: " << b.name << " age: " << b.age << endl;
}

int main(void) {
    girl g("Stacy", 15);
    boy b1("Jim", 16);
    g.prt(b1);
}

输出:

girl's name: Stacy age: 15
boy's name: Jim age: 16

上述代码中定义了boy和girl两个类,在类boy中声明了girl类的prt()函数为其友元成员,在类外定义了该成员函数。该函数既可以访问girl类的成员变量age,又可以访问boy类的成员age。

注意:当一个类的成员函数作为另一个类的友元函数时,必须先定义成员函数所在的类,并且在声明友元函数时,要加上成员函数所在的类名和域运算符,如第33行。另外,在主函数中要创建一个类girl的对象。

友元类

当一个类作为另一个类的友元时,称这个类为友元类

当一个类成为另一个类的友元类时,这个类的所有成员函数都称为另一个类的友元函数。

C++中,友元类的声明可以放在类声明中的任何位置,这时,友元类中的所有成员函数都称为友元函数。

友元类声明的一般形式如下:

friend class 友元类名;
或
friend 友元类名;

例6 友元类的应用

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

class boy;                                  //声明类boy

class girl {                                //定义类girl
private:
    char *name;
    int age;
public:
    girl(char *n, int a) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
        age = a;
    }
    void prt(boy &);                        //声明公有成员函数
    ~girl() {                               //定义析构函数
        delete name;                        //释放空间
    }
};

class boy {                                 //定义类boy
private:
    char *name;
    int age;
    friend girl;
public:
    boy(char *n, int a) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
        age = a;
    }
    friend void girl :: prt(boy &);         //声明友元成员
    ~boy() {
        delete name;
    }
};

void girl :: prt(boy &b) {                  //定义友元成员
    cout << "girl\'s name: " << name << " age: " << age << endl;
    cout << "boy\'s name: " << b.name << " age: " << b.age << endl;
}

int main(void) {
    girl g("Stacy", 15);
    boy b1("Jim", 16);
    g.prt(b1);
}

输出:

girl's name: Stacy age: 15
boy's name: Jim age: 16

上述代码同样定义了boy类和girl类,此处在boy类的定义中将girl类声明为boy类的友元类,因此,girl类的成员可以访问boy类的任意成员和函数。

注意:友元关系是不能传递的,并且是单向的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容