类的使用-----构造函数

最近开始学习了类,rock老师告诉我说,以往的编程是面向过程的,从今以后 就要开始面向对象进行编程了。

首先 关于定义一个Human对象

class Human {
public : //共有的对外的方法
    Human();//手动自定义的默认构造函数
    Human(string name,int age);//自定义的带有参数的构造函数
    Human(const Human &other);
    void eat();
    void sleep();
    void run();
    void study();
    string getName() const; //在函数的末尾加上const防止在函数内部对数据成员进行更改
    int getAge();
    int getSalary();
    void description();
    void setAddr(const char*newAddr);
    const char*getAddr();

private://私有的内部的方法和数据
    string name;
    int age;
    int salary;
    char *addr;
};

我这里定义了一个Human对象,里面有各种方法和数据。为了对里面的数据进行一个初始化,我们就需要用到构造函数来初始化。构造函数有四类

一.默认构造函数

所谓默认的构造函数,就是我们啥也不用干,编译器自动为这个类定义的一个默认构造函数。

1)如果数据成员使用了“类内初始值”,就使用这个值来初始化数据成员。

2)否则,就使用默认初始化(实际上,不做任何初始化)

实际上,在没有使用类内初始值的情况下,这个默认构造函数是完全不可以信任的。因为默认构造函数会把数据给初始化成无效的非法数据。

二.自定义的构造函数

class Human {
public : //共有的对外的方法
    Human();//手动自定义的默认构造函数

这里手动声明并定义了一个默认构造函数,定义如下

Human::Human(){
    name = "bossy";
    age = 12;
    salary = 22;
}

构造函数中的初始值会覆盖类内初始值。

这里再声明一个自带参数的默认构造函数

Human(string name,int age);//自定义的带有参数的构造函数

//定义
Human::Human(string name,int age){
    this->name = name;
    this->age = age;
    salary = 123456;
}

通过这个自带参数的默认构造函数(重载构造函数) 我们就可以通过改变形参来随心所欲地改变数据值。

三.拷贝构造函数

先说明一个调用拷贝构造函数的时机:

1.调用函数时,实际参数是一个对象,形式参数不是引用类型(如果函数的形参是引用类型则不会调用拷贝构造函数)。

2.函数的返回类型是类,而不是引用类型。

3.对象数组的初始化列表中使用类。

下面来一一解释一下这三种时机:

1.比方说 我们有这么一个函数showMsg();

void showMsg(Human man){
    cout<<man.getName()<<"的基本信息";
    man.description();
}

很容易理解 当我们调用函数showMsg(Bossy);时,首先执行的时Human man = Bossy;  这将会使用一个拷贝构造函数。

如果函数的形式参数是一个引用类型

void showMsg(const Human &man){
    cout<<man.getName()<<"的基本信息";
    man.description();
}

这就不会 使用拷贝构造函数因为我们传进去的就是我们希望展示的对象的别名,而不会在函数内部再创建一个对象从而增加了开销。再Human前面加上一个const防止对对象内部信息进行改变。

2.定义一个函数来比较两个对象的薪资

Human getBetterman(const Human &man1,const Human &man2){
    if(man1.getSalary() >=man1.getSalary())
        return man1;
    else 
        return man2;
}

在返回函数函数值的时候返回了一个临时对象:better man = getBetterman(man1,man2);自然也会调用一个拷贝构造函数。

3.显然会调用一个拷贝构造函数。比如Human man[4] = {f1,f2,f3,f4};将会连续调用四次拷贝构造函数。

 

下面是关于拷贝构造函数中的浅拷贝(未拷贝)与深拷贝的东西。

举个例子:

我们看到了在对象有一个数据成员addr是一个指向char类型的指针。我们先定义一个对象man1,再用拷贝构造函数来定义一个对象man2.

int main(void){
    Human man1;//定义一个对象
    Human man2 = man1;//调用拷贝构造函数
    showMsg(man1);
    showMsg(man2);
    man1.setAddr("China");
    showMsg(man1);
    showMsg(man2);
    system("pause");
    return 0;
}

这里会有一个问题,就是数据成员addr是char*类型的,是一个指针,man1.addr和man2.addr指向的是同一块内存,当其中一块内存上的值发生变化,另外一个也会相应的变化,称之为“浅拷贝”这显然不是我们想要的。解决办法是在拷贝构造函数里面重新分配一块内存空间,代码如下:

Human::Human(const Human &other){
    cout<<"调用拷贝构造函数"<<endl;
    name = other.name;
    age = other.age;
    salary = other.salary;
    addr = new char[64];
    strcpy_s(addr,64,other.addr);
}

这样子就实现了所谓的深拷贝,解决了问题。

4.赋值构造函数.

这个东西也很好理解,赋值构造函数就是用一个对象对另外一个对象里面的数据进行初始化,与拷贝构造函数要区分开。

    Human man3;
    man3 = man1;

这就使用了赋值构造函数

复制构造函数的声明和定义如下

Human &operator=(const Human &man);//函数声明

Human &Human::operator=(const Human &man){
    if(this == &man){
        return *this;//检测是否对自己赋值,例如h1 = h1;
    }
    this->age = man.age;
    this->salary = man.salary;
    this->name = man.name;
    //释放掉之前的动态内存(如果有必要的话)深拷贝
    delete this->addr;
    this->addr = new char[64];
    this->addr = man.addr;
    // 返回该对象本身的引用, 以便做链式连续处理,比如 a = b = c;
    return *this;
}

以上就是我对于构造函数的学习理解,如有欠缺,望不吝赐教。

 

 

 

上一篇:TypeScript高级用法详解


下一篇:多态、编译时多态、运行时多态