C++类的理解(二):函数重载和多个构造函数,以及析构函数

一、函数的重载
函数重载并不属于类的特性,是众多高级语言都有的一种函数特性,比如我有下面的函数:

//函数1:
int add(int a,int b)
{
    return a+b;
}

这个函数接受两个整型变量,返回他们的和,但如果我还要一些功能,比如两个double类型的和,一个整型和100的和,并且我也想用add这个函数名怎么办?
函数重载的概念就是用来解决这个问题的,我们把这些函数都写上:

//函数2:
double add(double a,double b)
{
    return a+b;
}

//函数3:
int add(int x)
{
    return x+100;
}

以上三个函数就是互为重载函数,他们拥有相同的函数名,但拥有不同的形参表。在代码中调用add函数,系统会根据情况智能的选择最恰当的函数来执行,而选择的标准就是参数表的匹配,举个例子:

int a=10,b=20;
double x=10,y=20;
cout<<add(a,b);  //由于传入参数是两个int,所以系统自动匹配函数1进行调用
cout<<add(x,y);  //由于传入的是两个double,所以系统自动匹配函数2进行调用
cout<<add(a);    //由于传入的是一个int,与函数三的参数表匹配,所以调用函数3
cout<<add(x);    //由于传入的是一个double,没有与之匹配的参数表,
//但函数3也是一个参数,系统就会尝试将double类型转换成int类型,然后调用函数3,但我们现在的这个情况是转换不了的,因为double编程int会丢失精度,所以该条语句会报错。

当然如果是下面这种情况却是没有问题的:

char x=10;
cout<<add(x);  //因为char是8位整型,int是32位整型,系统将char转变成int时不会有问题,所以会返回int型的110;

这就是函数重载,有两个要求:
1、函数名一致
2、参数表不一致(包括参数数量和参数类型)
参数表完全匹配才可以调用,不匹配系统会擅自尝试类型转换以匹配,转换成功还好,不成功就报错。所以写代码时如果需要类型转换的话,最好也由程序员来自己转换,不要太依靠编译器系统。

函数重载有两个易犯的错误,其实都很简单,都是因为歧义而出的问题:
第一个:带默认值的函数,比如:

int add(int x,int y=1)    //y带默认值
{
    return x+y;
}
int add(int x)
{
    return x+100;
}

所以当有如下语句则报错:

cout<<add(10);

系统会说它匹配了第一个函数(相当于没有给y参数,所以y参数用默认值),也匹配了第二个函数,所以他也不晓得你到底要用哪个函数。。。这个错叫:多个匹配的重载函数。

第二个错也是多个匹配的重载函数:

//函数1:
int add(int x,int y)
{
    return x+y;
}
//函数2:
double add(double x, double y)
{
    return x+y
}

然后来了个下面的语句:

char x=10,y=20;
cout<<add(x,y);

由于传的参数是两个char,没有这样的函数匹配,系统就自己开始类型转换了,他发现可以转成int,然后用函数1,也可以转成double用函数2……于是又晕了,报错:多个匹配的重载函数。

二、类的构造函数(初始化函数)重载

是个函数一般都是可以重载的,类里的函数也不意外,我们来说说构造函数的重载。
举个例子:

class Point
{
public:
    int x;
    int y;
public:
    Point()
    {
        this->x=0;
        this->y=0;
    }
    Point(int x,int y)
    {
        this->x=x;
        this->y=y;
    }
};

这两个构造函数互为重载函数,一个不带参数,一个带两个参数。
然后有如下对象定义的话:

Point t;
Point tt(2,5);

第一个t,就是自动调用了没有参数的构造函数,被初始化成:t.x==0, t.y==0;
第二个tt,就自动调用了带两个整型的构造函数,有t.x==2, t.y==5;

然后再来看昨天一个问题:

class Circle
{
public:
    Point center;
    int r;
public:
    Circle(int x,int y, int r):center(x,y)
    {
        this->r=r;
    }
    /* 以及: Circle(int x,int y,int r) { this->center.x=x; this->center.y=y; this->r=r; } */
}

这两种写法是有区别的,第一种没有加注释的写法,是在初始化center的时候,被传了值center(x,y),所以是调用了Point 的有参数的构造函数直接初始化的。
第二种加了注释的写法,是构造center的时候,没有给参数,①:所以调用了Point 没有参数的构造函数,将x,y初始化成了0,②:然后再在Circle构造函数中把center的x,y重新赋了值的。
只有Point自己有不带参的构造函数,注释里的写法才是合法的。

构造函数的重载允许我们用各种方法来初始化我们的对象,很是方便的。

三、拷贝构造函数

这是一个特殊的构造函数,用于在一个同类型对象的基础上生成一个新的对象,他的函数形参表有固定的写法,比如以下二叉树节点的类:

class Node
{
public:
    int index;
    Node* left;
    Node* right;
public:
    Node(int i)   //构造函数,带一个参数
    {
        index=i;
        left=0;
        right=0;
    }
    Node()    //构造函数,不带参数
    {
        index=0;
        left=0;
        right=0;
    }
    Node(const Node& t)  //拷贝构造函数的特点是参数表是一个(const className&),t是自定义的变量名
    {
        this->index=t.index;
        left=0;
        right=0;
    }
}

拷贝构造函数以自己的一个同类对象为参数,生成一个新的对象,比如:

Node t(10); //调用带一个参数的构造函数
Node x(t);  //调用拷贝构造函数

//另外当我们使用赋值号生成新的对象时,系统也是调用拷贝构造函数
Node y=t;  //等价于Node y(t);

所以再强调一遍吧,任何用自身同类对象生成新对象时,都会调用拷贝构造函数。

那么有一个问题,我刚刚说用赋值号的时候,系统会调用拷贝构造函数,那我没写拷贝构造函数,我难道不能用赋值号吗?
答:如果没写拷贝构造函数,系统免费送一个默认的。这个默认的构造函数就是简单的把各个属性都对应复制一遍。

这种默认的拷贝构造函数,大部分情况下都没有问题,除非:成员变量里有指针!!!
举个例子:

class Node
{
public:
    int* number
public:
    Node(const Node& t)   //默认的拷贝构造函数就这么干的
    {
        this->number=t.number;
    }

看看它干了啥!!!他把 t 的number里存的地址赋给了新对象的number,这意味着什么? 意味着新对象的numbert.number指着同一个空间,我在新对象里改了number所指空间的值,那么t.number指的空间值也就变了,这一般不是我们想要的,我们一般想要两个对象互不干扰,所以系统默认给的那种只是简单拷贝了指针地址的这种行为,我们叫浅拷贝,解决方法叫深拷贝,如下:

......
    Node(const Node& t)
    {
        number=new int();
        *number=*(t.number);
    }
......

也就是手动开辟一个新的空间给number,然后把t的number值拷贝进去,这样他们才是互相独立了。

所以当我们的类里存在指针成员变量的时候,又有要用到拷贝构造函数的时候,就要自己动手实现深拷贝。

四、析构函数
析构函数是对象被销毁时,系统最后调用的一个函数,一般用于扫尾工作。析构函数和构造函数一样,没有个话,系统免费送一个,自己定义的话就如下:

class Node
{
public:
    int index;
    Node* left;
    Node* right;
public:
    Node(int i)   //构造函数,带一个参数
    {
        index=i;
        left=0;
        right=0;
    }
    ~Node()  //析构函数
    {
        if (left)
            delete left;
        if (right)
            delete right;
    }

析构函数没有返回值,且不带参数。一般成员变量里有指针时要写,因为这些指针指的空间一般是用new运算符分配的,不会随着对象的销毁而释放,因为系统只会自己释放那个指针用来存地址的空间,它指的那个空间很可能就孤立了,别人也再也指不过去了,所以要手动释放。

以上就是今天的全部内容啦,有不懂的继续问我吧。

    原文作者:成成赐我力量
    原文地址: https://blog.csdn.net/qq_28581077/article/details/81660383
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞