第十章 对象和类(3)类的构造函数和析构函数

(三)类的构造函数和析构函数

    c++提供了一种特殊的成员函数,称为类构造函数,专门用于构造新对象,将值赋值给它的数据成员(初始化对象的数据成员)。

    还有一种函数是析构函数,它给出了程序在消灭对象的时候应该进行哪些处理,如果类构造函数中没有使用new运算符,那么析构函数使用默认的就可以了,否则应该为这些new出来的新空间进行释放。

1.声明和定义构造函数

(1)构造函数名称和类名相同,构造函数的原型和函数头还有一个有趣的特征,即虽然没有返回值,但没有声明为void类型,也就是说构造函数没有类型,这是构造函数特有的。

(2)构造函数原型要放在类声明的公有部分Public:部分

(3)构造函数和一般的函数的区别有,程序声明对象的时候会自动调用构造函数。我们当然可以通过类的某种成员函数来对类对象进行初始化,但是这样只能先声明对象,然后再用对象调用这些函数,但是构造函数不同,构造函数是自动调用的,也就是我们可以像声明整型变量那样在声明的时候赋值

(4)构造函数的参数,表示的不是类成员,而是赋值给类成员的值,因此,形参的名称与类成员的名称不能相同。为了不相同,一种常用的做法是在类成员前面加上m前缀,还有一种做法是在类成员后面加上_后缀。

2.使用构造函数

    c++提供了两种使用构造函数初始化类对象的方式,一种是显式地调用,另一种是隐式调用。比如:stock food=stock(“hawoiej”,2,3.0);这样为显示调用;而stock food(“jdkfjhjkh”,53,2.33);为隐式调用。每次创建对象的时候c++都使用类构造函数,使用new和构造函数创建类对象的方法stock food=new stock(howis that,23,55);。另外一点要注意的是,构造函数仅仅是用来构造新的对象的,不能用对象来调用构造函数

    使用构造函数的时候,尽量使用隐式调用,因为显式调用可能会生成临时对象(也可能不生成,取决于实现),随后会删除这个临时对象,这个过程会浪费资源开支。

    类对象的声明也是可以使用列表初始化的,也就是说c++中所有的数据声明和初始化都可以使用列表初始化,比如Stock stock1{};作用和Stock stock1是一样的,可以调用默认构造函数。再比如Stock stock2={"saa",2};和Stock stock2{"saa",2};就相当于Stock stck2=Stock("saa",2);和Stock stock2("saa",2);

3.默认构造函数

    默认构造函数是我们没有提供显式的初始值来创建对象的时候,程序用来创建对象的构造函数当且仅当没有定义任何构造函数的时候,编译器才会自动提供默认构造函数,并且这个默认构造函数是空的(比如stock(){}这样,此时对象的所有数据都是随机的)。当为类定义了构造函数,程序员必须手动给它提供默认构造函数。

    一般我们定义默认构造函数的方法有两种,一种是声明一个没有任何参数的构造函数,然后给类的数据成员加入默认的初始值。比如:stock::stock(){company=”no name”;shares=0;share_val=0.0}。第二种方法是为构造函数的所有的参数提供默认值,比如stock(const string &x=”error”,int n=0,double pr=0.0);(这是在构造函数的声明那里完成的)。当我们设计类的时候,通常要设计给所有的类成员提供隐式初始化的默认构造函数

    !!!!!注意:使用默认构造函数时,声明对象的后面不要加上括号。加上括号但不加参数,比如stock a();并不是声明了一个stock类的a对象,而是声明了一个返回值为stock类型的a函数,因此与我们的目的差别很大,这一点要留心。实际上我们的目的是stock a=stock();来声明一个a对象,因此这种形式也是可以的,这是显式使用了默认构造函数。默认构造函数的目的就是在声明对象的时候即使不初始化,对象也是默认初始化了的,这与基本数据类型不太相同,主要是为了不会生成一个不受控制的对象。比如int a;a的值是空的,不确定,但是stock a;a对象的值将是确定的(提供了默认构造函数才如此,如果使用默认的默认构造函数,那么值也会是不确定的)。

4.析构函数

(1)如果构造函数使用new来分配内存,那么析构函数要用delete来释放这些内存。如果没有使用new,那么实际析构函数什么都不要做即可,我们只需要让编译器自动生成一个什么都不需要做的隐式析构函数即可

(2)析构函数的名称,是在类名前加上~。比如:stock::~stock(){}。如果我们没有显式定义析构函数,编译器会自动生成一个隐式的析构函数

(3)类对象过期的时候,析构函数将自动被调用,!!!!!不需要在代码中显式调用析构函数(只有极特殊的情况要显式调用析构函数,比如用new创建类对象,对象中还有new创建的指针,而我用了free来释放类对象的内存而不是用了delete,虽然这其实是不符合规定的,但还是会有这样使用的情况)。

(4)如果我没有提供析构函数,那么编译器会自动为类生成一个默认的析构函数。默认析构函数将类中的所有数据成员释放,但是不会释放数据成员指向的内存。因此如果构造函数中有用new创建的对象,那么析构函数要手动重载,并用delete来释放掉这些对象。

5.const成员函数

    const成员函数是将this指针设定为const指针的成员函数,因此保证不会修改调用对象,如果调用对象被设定为const,那么只有const函数才可以使用,非const成员函数不能保证不修改调用对象,因此使用的时候会出差错。所以,如果函数不修改原对象,应该将成员函数设定为const成员函数(在函数后面加上const来修饰)。

6.构造函数和析构函数小结

(1)将头文件名放在双引号而不是尖括号中意味着编译器将在同一个文件夹中(与.cpp文件相同的文件夹)来搜寻该文件而不是从标准库中搜寻,搜索完本文件夹后再搜索标准库。

(2)可以将对象赋值给同类型的另一个对象,就像同类型的结构可以赋值给另一个结构一样

(3)构造函数初始化一个新的对象的方法有两种,一种是stock stock1(tom,222);,这种类型的初始化将会使stock1内的数据成员的值与参数列表中的值相同;另一种初始化方法是stock stock1=stock(Lee,35);这种类型的初始化过程取决于实现,有可能跟第一种完全一样,也有可能会构建一个临时对象,然后将临时对象的值赋值给stock1。一般来说我们使用第一种初始化的方式来创建对象,因为它的效率更高。

(4)构造函数不但可以用来初始化一个新的对象,还可以给对象赋值。比如stock1=stock(“adjfkla”,1.2,33);stock1是一个已经存在的对象,我们用构造函数生成一个临时变量,然后赋值给stock1,再摧毁这个临时变量。但要注意,初始化跟赋值是两个不同的概念。

(5)类对象也可以使用列表初始化,只要函数参数相匹配就可以了,这和调用构造函数来初始化并没有什么不同。比如:stock stock2{“him”,399};但是c++11提供了initializer_list类,可以提供类型相同的列表初始化(注意类型相同或者可以转换为相同),如果使用大括号形式的构造函数和以initializer_list对象为参数的构造函数相冲突,那么会调用以initializer_list对象参数形式的构造函数,而不是普通的构造函数。

(6)!!!!!const成员函数,只要类方法不修改调用对象,我们就将其声明为const成员函数。声明的方法是在()后面加const,也就是在函数声明和定义后面加上const。比如声明:void show() const;定义:void stock::show() const{}。

(7)如果构造函数只有一个参数,那么如果将对象初始化为一个与参数类型相同的值的时候,这个构造函数将被调用,来创建临时对象,并将临时对象赋值给需要初始化的对象。比如有map类,构造函数为map(int);那么map m=3;就将使用这个特性,将3作为参数调用构造函数,然后将对象初始化。这个功能可以关闭,以免带来不必要的麻烦,方法是使用explicit关键字

(8)如果构造函数使用了new,那么析构函数中必须要用delete来对内存进行释放

(9)默认构造函数可以没有任何参数如果有,那么必须给所有的参数提供默认值


你可能感兴趣的:(第十章 对象和类(3)类的构造函数和析构函数)