码迷,mamicode.com
首页 > 编程语言 > 详细

C++ Primer 学习笔记_18_类与数据抽象(4)_构造函数、析构函数、explicit关键字、赋值与初始化、类成员的显式初始化

时间:2015-07-28 01:01:44      阅读:140      评论:0      收藏:0      [点我收藏+]

标签:c++   c++ primer   类与数据抽象   

引言:

    构造函数确保每个对象在创建时自动调用,以确保每个对象的数据成员都有合适的初始值。


一、构造函数、默认构造函数

1、构造函数

——构造函数是特殊的成员函数
——构造函数是为了保证对象的每个数据成员都被正确初始化

——函数名和类名完全相同
——不能定义构造函数的类型(返回类型),也不能使用void
——通常情况下构造函数应声明为公有函数,一般被隐式地调用。
——构造函数被声明为私有有特殊的用途,比如单例模式。

(1)、构造函数可以被重载

    一般而言,不同的构造函数允许用户指定不同的方式来初始化数据成员。构造函数可以有任意类型和任意个数的参数,一个类可以有多个构造函数(重载)。

(2)、实参决定使用哪个构造函数

<span style="font-size:14px;">Sales_item();      //Sales_item empty;
Sales_item(const string &);     //Sales_item Primer_3td_Ed("0-201-82470-1");
Sales_item(std::istream &);     //Sales_item Primer_4th_Ed(cin);</span>

(3)、构造函数自动执行

创建类类型的新对象,编译器自动会调用构造函数

(4)、用于const对象的构造函数

创建类类型的const对象时,运行一个普通构造函数就可以初始化该const对象。构造函数的工作就是初始化对象,不管对象是否为const,都用一个构造函数来初始化对象

class Sales_item
{
public:
    //构造函数不能是const
    Sales_item() const; //Error
};

2、默认构造函数

——不带参数的构造函数
——只有当一个类没有定义构造函数时,则系统自动产生出一个默认构造函数,是空函数

——一个类哪怕只是定义了一个构造函数(包括拷贝构造函数),编译器也不会再生成默认构造函数。


(1)回顾

在C++中类型大致可以分为三种

第一种、内置类型

如int, char, float, unsigned等。内置类型是最基本的类型。

第二种、复合类型

复合类型:使用其它类型定义的类型。有三种复合类型:引用,指针,数组。

第三种、类类型

就是类。比如string以及自己定义的类。


(2)若使用编译器自动生成的默认构造函数(或自己定义一个未进行任何操作的默认构造函数),则类中的每个成员,使用与初始化变量相同的规则来进行初始化。

——类类型成员:运行该类型的默认构造函数来初始化

——内置或符合类型的成员:依赖于对象的作用域,在局部作用域中这些成员不被初始化,而在全局作用域中他们被初始化为0。

例子:

下面代码中a,b的各个成员变量值时多少?

class Student
{
public:
    Student(){}
    void show();
private:
    string name;
    int number;
    int score;
};
Student a;
int main()
{
    Student b;
}

解答:a与b的name都调用string类的默认构造函数初始化,a是全局对象,故a的number与score初始化为0;而b是局部对象,故b的number与score不被初始化,为垃圾值。


【例子】

#include <iostream>
using namespace std;

class Test
{
public:
    // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的   
    Test();// 默认的构造函数
    Test(int num);
    void Display();

    Test &operator=(const Test &other);

    ~Test();
private:
    int num_;
};

// 不带参数的构造函数称为默认构造函数
Test::Test()
{
    num_ = 0;
    cout << "Initializing Default" << endl;
}

Test::Test(int num)
{
    num_ = num;
    cout << "Initializing " << num_ << endl;
}

Test::~Test()
{
    cout << "Destroy " << num_ << endl;
}

void Test::Display()
{
    cout << "num=" << num_ << endl;
}

Test &Test::operator=(const Test &other)
{
    cout << "Test::operator=" << endl;
    if (this == &other)
        return *this;

    num_ = other.num_;
    return *this;
}

int main(void)
{
    Test t;
    t.Display();

    Test t2(10);
    t2.Display();

    Test *t3 = new Test(20);    // new operator
    t3->Display();

    delete t3;
    return 0;
}


运行结果:

Initializing Default
num=0
Initializing 10
num=10
Initializing 20
num=20
Destroy 20
Destroy 10
Destroy 0


解释:可以看到构造函数是被自动调用的,且构造函数可以被重载调用;栈上的对象生存期到了会自动调用析构函数;而new operator 做了两件事,一个是创建了对象内存,一个是调用构造函数;堆上的内存需要delete 释放,做了两件事,一是调用析构函数,二是释放内存。


——我们不能调用一个构造函数但没有提供参数(实例化对象),如A a();
——因为是有歧义的,我们也可以看成是声明了一个没有参数的函数a,返回值是类型A的一个对象。但在函数传参的时候往往可以这样写: A()  // 即定义一个无名对象。


还有一个注意点,全局对象的构造先于main函数执行,如下:

#include <iostream>

using namespace std;

class Test
{
public:

    // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的   

    Test();// 默认的构造函数
    Test(int num);
    void Display();
    Test &operator=(const Test &other);
    ~Test();

private:
    int num_;
};


// 不带参数的构造函数称为默认构造函数
Test::Test()
{
    num_ = 0;
    cout << "Initializing Default" << endl;
}

Test::Test(int num)
{
    num_ = num;
    cout << "Initializing " << num_ << endl;
}

Test::~Test()
{
    cout << "Destroy " << num_ << endl;
}

void Test::Display()
{
    cout << "num=" << num_ << endl;
}


Test &Test::operator=(const Test &other)
{
    cout << "Test::operator=" << endl;
    if (this == &other)
        return *this;

    num_ = other.num_;
    return *this;
}

Test t(10);

int main(void)
{
    cout << "Entering main ..." << endl;
    cout << "Exiting main ..." << endl;
    return 0;
}

运行结果:

Initializing 10
Entering main ...
Exiting main ...
Destroy 10

解释:在return 0 时全局变量的生存期也到了,故也会自动调用析构函数。


二、析构函数

    在构造函数中分配了资源之后,需要一个对应操作自动回收或释放资源。析构函数就是这样的一个特殊函数,它可以完成所需的资源回收,作为类构造函数的补充。


1、何时调用析构函数

    撤销类对象时会自动调用析构函数:动态分配的对象只有在指向该对象的指针被删除时才撤销,如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象就会一直存在,从而导致内存泄漏,而且,对象内部使用的任何资源也不会释放


2、默认析构函数

如果没有定义析构函数,编译器会自动生成一个默认析构函数,其格式如下:
类名::~默认析构函数名( )
{
}
默认析构函数是一个空函数


3、如何编写析构函数

函数名和类名相似(前面多了一个字符“~”)
没有返回类型
没有参数
析构函数不能被重载


4、何时编写显式析构函数
     析构函数并不仅限于用来释放资源,一般而言,析构函数还可以执行任意操作,该操作是类设计者希望在该类对象的使用完毕之后执行的

    实际上,构造函数和析构函数都是可以被显式调用的,只是很少这样做。


【例子】
#include <iostream>
using namespace std;

class Test
{
public:
    // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的   
    Test();// 默认的构造函数
    Test(int num);
    void Display();

    Test &operator=(const Test &other);

    ~Test();
private:
    int num_;
};

// 不带参数的构造函数称为默认构造函数
Test::Test()
{
    num_ = 0;
    cout << "Initializing Default" << endl;
}

Test::Test(int num)
{
    num_ = num;
    cout << "Initializing " << num_ << endl;
}

Test::~Test()
{
    cout << "Destroy " << num_ << endl;
}

void Test::Display()
{
    cout << "num=" << num_ << endl;
}

Test &Test::operator=(const Test &other)
{
    cout << "Test::operator=" << endl;
    if (this == &other)
        return *this;

    num_ = other.num_;
    return *this;
}

int main(void)
{
    Test t[2] = {10, 20};

    Test *t2 = new Test(2);
    delete t2;

    Test *t3 = new Test[2];
    delete[] t3;

    return 0;
}
运行结果:

Initializing 10
Initializing 20
Initializing 2
Destroy 2
Initializing Default
Initializing Default
Destroy 0
Destroy 0
Destroy 20
Destroy 10

解释:注意  Test t[2] = {1020}; 中10,20是当作参数传递给每个对象的构造函数的,如果没有对应的构造函数,比如只有2个参数的构造函数,那么编译是失败的。



三、转换构造函数

为了定义到类类型的隐式转换,需要定义合适的构造函数。

    可以使用单个实参来调用的构造函数定义了从形参类型到该类型的一个隐式转换

——单个参数的构造函数不一定是转换构造函数
——将其它类型转换为类类型
——类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型

【例子】

#include <iostream>

using namespace std;

class Test
{
public:
    // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的   
    Test();// 默认的构造函数
    Test(int num);
    void Display();
    Test &operator=(const Test &other);
    ~Test();

private:
    int num_;
};

// 不带参数的构造函数称为默认构造函数
Test::Test()
{
    num_ = 0;
    cout << "Initializing Default" << endl;
}

Test::Test(int num)
{
    num_ = num;
    cout << "Initializing " << num_ << endl;
}

Test::~Test()
{
    cout << "Destroy " << num_ << endl;
}

void Test::Display()
{
    cout << "num=" << num_ << endl;
}

Test &Test::operator=(const Test &other)
{
    cout << "Test::operator=" << endl;
    if (this == &other)
        return *this;

    num_ = other.num_;
    return *this;
}

int main(void)
{
    Test t(10);     // 带一个参数的构造函数,充当的是普通构造函数的功能
    t = 20;         // 将20这个整数赋值给t对象    
    // 1、调用转换构造函数将20这个整数转换成类类型 (生成一个临时对象)   
    // 2、将临时对象赋值给t对象(调用的是=运算符)
    Test t2;

    return 0;
}

运行结果:

Initializing 10
Initializing 20
Test::operator=
Destroy 20
Initializing Default
Destroy 0
Destroy 20


解释:可以看到初始化了一个临时对象,传递参数20,然后调用赋值运算符operator=,接着释放临时对象,最后释放的对象是已经被更改过的t 。赋值运算符的格式为:Test& Test::operator=(const Test& other);事实上如果没有自己实现,编译器也会实现一个默认的赋值运算符,做的事情跟我们现在实现的函数一样。


四、explicit 关键字(抑制由构造函数定义的隐式转换)

——只提供给类的构造函数使用的关键字,来防止在需要隐式转换的上下文中使用构造函数
——编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象。

——假设在Test 类的构造函数Test(int num); 前面加上explicit 关键字,那么Test t = 10; 或者 t = 20; 这种语句都是编译不通过的,因为不允许隐式转换。


五、赋值与初始化的区别

在初始化语句中的等号不是运算符。

#include <iostream>

using namespace std;

class Test
{
public:
    // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的   
    Test();// 默认的构造函数
    Test(int num);
    void Display();
    Test &operator=(const Test &other);
    ~Test();

private:
    int num_;
};


// 不带参数的构造函数称为默认构造函数
Test::Test()
{
    num_ = 0;
    cout << "Initializing Default" << endl;
}

Test::Test(int num)
{
    num_ = num;
    cout << "Initializing " << num_ << endl;
}

Test::~Test()
{
    cout << "Destroy " << num_ << endl;
}


void Test::Display()
{
    cout << "num=" << num_ << endl;
}


Test &Test::operator=(const Test &other)
{
    cout << "Test::operator=" << endl;
    if (this == &other)
        return *this;

    num_ = other.num_;
    return *this;
}

int main(void)
{
    Test t = 10;        // 等价于Test t(10); 这里的=不是运算符,表示初始化。
    t = 20;             // 赋值操作
    Test t2;
    t = t2;             // 赋值操作 t.operator=(t2);

    return 0;
}
运行结果:

Initializing 10
Initializing 20
Test::operator=
Destroy 20
Initializing Default
Test::operator=
Destroy 0
Destroy 0


解释:第一条语句是初始化,后面是赋值操作,参照上面临时对象的创建销毁,赋值运算符的调用可以理解输出。


六、类成员的显式初始化

    对于没有定义构造函数并且其全体数据成员均为public的类,可以采用与初始化数组元素相同的方式初始化其成员:

缺点:

    1)要求类的全体数据成员都是public

    2)将初始化每个对象的每个成员的负担放在程序员身上。这样的初始化是乏味且易于出错的,因为容易遗忘初始化式或提供不适当的初始化式。

    3)如果增加或删除一个成员,必须找到所有的初始化并正确更新



参考:

C++ primer 第四版
Effective C++ 3rd
C++编程规范

http://blog.csdn.net/zjf280441589/article/details/24735147

http://blog.csdn.net/jnu_simba/article/details/9164887

http://blog.csdn.net/zjf280441589/article/details/24830899


版权声明:本文为博主原创文章,未经博主允许不得转载。

C++ Primer 学习笔记_18_类与数据抽象(4)_构造函数、析构函数、explicit关键字、赋值与初始化、类成员的显式初始化

标签:c++   c++ primer   类与数据抽象   

原文地址:http://blog.csdn.net/keyyuanxin/article/details/47093223

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!