【1】C++面向对象:《构造函数》之精讲

在一讲内容比较多,但对于C++的有关构造函数的问题都面面即到,使读者在使用不会心存顾虑

⒈【类与对象】:类是一种抽象的概念,无实体可言,定义类时,没有分配内存空间的语句(就算定义数据成员也没为其分配空间),类的定义相当于变量的声明过程,定义的最终结果是定义了一种类类型数据。对象是一种实在的概念,是有具体操作的能力。对象可以被创建和销毁,而类定义之后一直存在。

⒉【定义对象】:全局对象在主函数开始执行前首先被建立,局部对象在程序执行遇到他们的对象定义时才被建立,并分配空间。

【对象定义时程序的执行过程】:

①分配对象空间,调用构造函数,但不立即执行函数体。

②定义数据成员,用构造函数所带的实参为相应的数据成员赋值。

③执行函数体中的代码。

⒊【初始化】

数组初始化:int b[4] ={1,2,3,4};(此时数组的长度也可以不给)。

结构体初始化可以和数组一样用{}括起来的一些数据一次给结构体变量赋值,但是类的初始化不可以以结构体一样。

这是由于结构体变量的访问控制符默认是public的,无访问限制。而类的成员数据在默认情况下是private的,不能直接对其赋值。但事实上,不管是结构体还是类,只要其有非public数据成员(包括函数成员)在,就不能像数组初始化方式那样直接初始化,反正,只要没有非public数据成员在,则结构体和类都可以类似数组初始化方式进行初始化。

⒋**【给数据成员赋值的方式】:记住给对象非public成员赋值的方式只有两种**

①初始化方式,这种方式有严格要求,只能在对象定义时同时进行,在对象定义完之后不可能再通过这种方式定义。

②通过成员函数赋值方式,这种方式显然可以做到随时赋值。

切忌不要妄想通过"对象名.数据成员"的方式来企图改变非public数据成员的值,这种错误往往很容易犯!

⒌数据(变量)在定义时就分配好空间,而初始化是在定义语句的分号之前,即语句结束之前。因此若无初始化则数据(变量)为“0值”“随机值”。

初始化的方式有两种:(以int型变量为例)

int a(20) ⇔ int a=20;

赋值的方式则只有一种:

a(20) 不等价于a =20;编译器只会把a(20)解释成函数调用
⒍【构造函数】对象定义的时候自动被调用,对于类整体来说,构造函数的特点是必须与类同名,而相对于普通函数(或其他成员函数),构造函数的区别是不能有返回类型,且一般不显式调用

⒎【构造函数的定义】:

首先必须知道的概念是:无参的构造函数称为默认构造函数。

如果没有自定义构造函数,则系统会调用默认构造函数(系统提供),所以默认构造函数有两个方式,一是由系统提供,一是自己定义可以无参方式调用的(重载)构造函数。但必须记住,如果有自定义构造函数,不管该构造函数有无参数,系统都不会再自动提供默认构造函数,所以,在定义或重载构造函数时,一定要时刻注意有没有提供默认构造函数的调用方式。如果没有调用默认构造函数的方式,则就不能进行定义需要调用默认构造函数的方式对象,如不能定义对象数组

如:

class A;(定义A类)

A arr[5];(定义含有五个元素的数组,每个元素是A类的对象)

则此时,每一个元素对象定义时都依次调用无参的默认构造函数,所以,此时的类定义必须有默认构造函数。

必须注意:在创建对象时,如果调用的是默认构造函数,则在对象名后面是不能加(),即:A s1();创建对象s1在任何情况下都是错误的。即()里面不然必须有实参,不然如果没有实参,则调用的是默认构造函数,就必须满足两点①有默认构造函数可以调用②s1后面不能有括号,即A s1;。

⒏. 然而需要注意的是,并不是说不能显式的调用构造函数,而上面所的一般不显示调用是根据一般性来说的,因为对象是一个实例,创建的目的就是为了对其使用(即操作),要使用一个对象必须提供使用的方式,然而对象的使用只有两种:

①通过对象的名称来使用对象

②通过指向对象的指针来使用它。

而实际上,显示调用构造函数的作用是定义了一个无名对象,所以,在定义一语句结束之前如果:

没有复制给一个同类型的对象a,则该无名对象将占据着空间而永远不能对该对象成员进行操作,但是就算在定义语句结束之前赋值给了同类型的对象a,对对象a的使用也不是指无名对象所在的空间,对象a只是拷贝了无名对象的数据,对象a有自己的对象空间,所以这种操作必然造成多余的空间浪费,这种方式的使用只有在函数调用时,实参是无名对象而形参是对象的引用才能提现其用途。不然往往是浪费空间。

用一个指向该无名对象类型数据的指针p (A * p;)去指向它,或作为实参传给形参p,此时就可以通过指针p用箭头操作符间接使用该对象,但必须注意在该无名对象前必须加上&符。

同样需要注意:通过构造函数的显示调用创建无名对象时,构造函数名后面有()才有可能正确,即
p = &A();才有可能正确

意思是如果需要参数,就在()里输入实参,如果想调用默认构造函数创建无名对象,此时括号()也不能省,这与在创建对象时隐式调用默认构造函数实现原理是不同,显然前者无无名对象产生!

**事实上:无名对象还有一个非常重要点用途,就是可以在创建类类型数组arr时可以对元素进行初始化,这也是唯一一种方式使得类数组元素(A arr[5]; )在定义数组时可以不调用默认构造函数。下面是两种定义类数组的方式,他们是有讲究的:

A arr[5];

定义了5个元素的组,每个元素都是A类的对象。显然,这种方式没有为5个元素初始化赋值,然而,我们都知道对象创建的时候都会调用构造函数,类数组也不例外,所以,每一个数组元素都会根据下标的序号依次调用构造函数,但是要注意,这种方式显然不可能给每一个元素对象调用时传递任何参数的可能,所以就只能调用默认构造函数进而所以A必须提供默认的构造函数,否则出错。

A arr[5] = {A(£), A(£), A(£), A(£), A(£)};

其中£表示实参列表。

这种方式定义的类对象数组,可以根据自己的需要灵活的为每一个数组元素调用适合的构造函数,同样需要注意两点,①如果£为空,则A类中必须要默认构造函数可以调用,若不为空,则构造函数必须提供£对应的构造函数。②通过这种方式初始化数组,原理上也是通过创建无名对象,然后再赋值给每一个数组元素

**⒐【析构函数】:**析构函数也是特殊的成员函数,可以自定义或系统默认提供,但和构造函数不同,在名称上析构函数之前多了~符,结构上析构函数不能有形参,不能对其重载,也不能自调用,在类生命期结束后由系统自动调用。在使用的习惯上来说,只有在为对象分配资源时才会使用析构函数。

只需牢记一点,析构函数以调用构造函数相反的顺序被调用。(即先创建的对象后析构,后创建的对象先析构)。

**⒑【类定义的过程】?*①先调用构造函数,但不立即执行函数体,再为对象的数据成员分配内存空间,程序执行到构造函数名右括号后面。②执行构造函数名之后":"(称为冒号语法)符号后面的表达式。此时执行的语句其实也是为某些数据成员进行初始化,但不能对数组数据成员在这里进行初始化③之后再执行构造函数体,如进行相应的赋值操作。

如:

有类A如下:

class A

{

public:

    A(int a=0):b(a)

     {

          c = 2*a;

     }

protected:

     int a,b,c;

};

定义A的对象Aclass:

A = Aclass(3);

定义语句的执行过程为调用构造函数A(3),为数据成员a,b,c分配内存空间,把实参3赋值给形参a,执行表达式b(a),即b=a,此时b的值属于初始化得来,即b分配空间之后便获得合理的值即为初始化,此时a,c的值没有初始化,为随机值。然后继续执行函数体,为c赋值,而a依然是随机值。

【总结】:在对象的定义时,数据成员的初始化方式可以通过":"(称为冒号语法)符在构造函数名后面初始化,当数据成员是某个类的对象时,通过这种方式的初始化,是为该数据成员对象(即数据成员为类类型数据)可以不调用默认构造函数的唯一方式。意思是,如果不这样为该数据成员对象初始化,则该数据成员对象只能调用默认构造函数,所以此时必须保证该数据成员所属的类有默认构造函数。

否则如果在该构造函数里为该成员对象赋值,就会伴随无名对象的产生。

此外还有一点需要注意的是,如果一个类(类A)的对象的数据成员是另一个类(B)的对象,则类B必须在类A之前有定义,不能单单声明。

冒号":"语法的使用】:①对于一般类型的变量(基本类型变量或指针变量)一般建议不要通过冒号语法初始化,完全可以通过构造函数初始化来达到初始化目的,这也是类机制本身对于初始化的初衷。②对于类类型数据成员,建议通过冒号语法对其进行初始化,如果通过对象的构造函调用数据成员所属类的构造函数,则不叫初始化,那是赋值操作,虽然能达成目的,但是也会伴随无名对象的产生而过多的浪费空间。③对于常量数据成员和引用数据成员,则必须一定得通过冒号语法进行初始化,否则分分钟出错,因为:我们知道构造函数体只能对数据成员进行赋值操作,是对原有的变量再赋值(不管该变量是不是随机值),而常量和引用变量是只能通过初始化确定值,是不可能再赋值的。④对于数组类型数据,不管是什么类型的数组,都不能企图通过冒号语法进行数组元素的初始化。这是C++语法做决定的。

11. 【构造对象的顺序】

①局部和静态对象,以声明的顺序构造。

②函数作用域的所有变量和对象在函数开始执行时,统一定义,定义的顺序是他们声明的逻辑顺序。**即在函数被调用的时候,先统一对所有对象或变量进行定义,不管该语句在确定会执行的return语句之后。也就是函数调用会先统一先执行函数作用域变量或对象定义的代码,再从头执行其他代码。

③静态对象和静态变量一样,只会被构造一次,局部静态对象(函数作用域)在程序进入局部作用域时被构造,全局对象(包括静态)在主函数开始执行前被构造,因此必须保证全局对象不能出现致命的错误,不然程序运行不起来:检查致命问题是否是全局对象造成的可以两种方法: Ⅰ将全局对象先作为局部对象来调试。Ⅱ在全局对象的构造函数里增加输出语句,如果有输出,之后程序出现问题,这就是全局对象有问题。

④全局对象构造时无特殊顺序,即不像局部对象那样,全局对象的构造是没有控制流向来指明其顺序的,如果是单文件程序,其构造的顺序的知道的,但是对于多文件程序,往往是不可意料的,因为编译器不能控制文件链接的顺序,所以也不能决定文件中全局对象之间的构造顺序。

⑤成员以其在类中声明的顺序被构造。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值