C++中构造函数、析构函数和虚函数

本文介绍了C++中的构造函数、析构函数和虚函数。构造函数用于对象初始化,析构函数在对象生命周期结束时调用。虚函数则实现多态机制,通过虚函数表(V-Table)保证正确的函数调用。文章通过多个示例详细阐述了这些概念及其在继承、覆盖中的应用。

1.类

类的三个新特征:友元(friend)、可变成员和静态成员

友元:允许一个类将对其非公有成员的访问权授予指定的函数或类。

2.new与delete

new时做了三件事:①分配内存空间。②调用构造函数。③返回对象指针。

delete时做了两件事:①调用析构函数。②释放内存空间。

3.构造函数

1)实参决定使用哪个构造函数。

2)构造函数自动执行。

3)先调用基类构造函数,再调用子类构造函数。

4)常见类型(无参、拷贝、重载)及调用过程。与等号运算符重载区分

例1:

#include <iostream>
using namespace std;

class Complex
{        
private :
    double    m_real;
    double    m_imag;
    int id;
    static int counter;
public:
    //    无参数构造函数
    Complex(void)
    {
        m_real = 0.0;
        m_imag = 0.0;
        id=(++counter);
        cout<<"Complex(void):id="<<id<<endl;
    }
    //    一般构造函数(也称重载构造函数)
    Complex(double real, double imag)
    {
        m_real = real;
        m_imag = imag;        
        id=(++counter);
        cout<<"Complex(double,double):id="<<id<<endl;
    }
    //    复制构造函数(也称为拷贝构造函数)
    Complex(const Complex & c)
    {
        // 将对象c中的数据成员值复制过来
        m_real = c.m_real;
        m_imag = c.m_imag;
        id=(++counter);
        cout<<"Complex(const Complex&):id="<<id<<" from id="<<c.id<<endl;
    }            
    // 类型转换构造函数,根据一个指定的类型的对象创建一个本类的对象
    Complex(double r)
    {
        m_real = r;
        m_imag = 0.0;
        id=(++counter);
        cout<<"Complex(double):id="<<id<<endl;
    }
    ~Complex()
    {
        cout<<"~Complex():id="<<id<<endl;
    }
    // 等号运算符重载
    Complex &operator=( const Complex &rhs )
    {
        if ( this == &rhs ) {
            return *this;
        }
        this->m_real = rhs.m_real;
        this->m_imag = rhs.m_imag;
        cout<<"operator=(const Complex&):id="<<id<<" from id="<<rhs.id<<endl;
        return *this;
    }
};
int Complex::counter=0;
Complex test1(const Complex& c)
{
    return c;
}
Complex test2(const Complex c)
{
    return c;
}
Complex test3()
{
    static Complex c(1.0,5.0);
    return c;
}
Complex& test4()
{
    static Complex c(1.0,5.0);
    return c;
}
int main()
{
    Complex a,b;//各调用一次Complex(void)构造函数

    // 下面函数执行过程中各会调用几次构造函数,调用的是什么构造函数?
    Complex c=test1(a);//①test1()②拷贝构造函数
    Complex d=test2(a);//①拷贝②test2()③拷贝
	
	Complex h(c);//拷贝
	Complex i = h;   //拷贝,相当于Complex i(h);
	c=h;//等号运算符
    b = test3();//①一般②拷贝③等号
    b = test4();//①一般②等号

    Complex e=test2(1.2);//①类型转换②拷贝
    Complex f=test1(1.2);//①类型转换②拷贝
    Complex g=test1(Complex(1.2));//①类型转换②拷贝

}
注:

等号:对象已初始化;拷贝:对象未初始化

<pre name="code" class="cpp">Complex i = h;//调用拷贝;
i = h;//调用等号

<span style="background-color: rgb(255, 255, 0);">Complex c = test1(a);//调用1次构造函数</span>
上式结果等价于

<span style="background-color: rgb(255, 255, 0);">Complex c1 = test1(a);//调用1次
Complex c = c1;//调用1次,总共2次,因为多了中间变量c1.</span>

4.析构函数

牢记析构函数的调用顺序(与构造函数的调用顺序相反)

什么时候调用析构函数?

1)对象生命周期结束,被销毁时

2)delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;

3)对象i是对象o的成员,o的析构函数被调用时,对象的析构函数也被调用。

例2:

class A
{
public:
	A()
	{
		cout<<"constructing A"<<endl;
	}
	~A()
	{
		cout<<"destructing A"<<endl;
	}
private:
	int a;
};

class B:public A
{
public:
	B()
	{
		cout<<"constructing B"<<endl;
	}
	~B()
	{
		cout<<"destructing B"<<endl;
	}
private:
	int b;
};

void main()
{
	B b;
}
输出结果:

说明:先调用父类构造方法,再调用子类构造方法。

上述代码说明了一件事:析构函数的调用顺序与构造函数的调用顺序相反

例3:

main()方法改为:

void main()
{
	A *a = new B;
	delete a;
}
输出结果:


例4:

class A
{
public:
	A()
	{
		cout<<"constructing A"<<endl;
	}
	virtual ~A()
	{
		cout<<"destructing A"<<endl;
	}
private:
	int a;
};

class C
{
public:
 C()
 {
  cout<<"constructing C"<<endl;
 }
 ~C()
 {
  cout<<"destructing C"<<endl;
 }
 private:
  int c;
};

class B:public A
{
public:
	B()
	{
		cout<<"constructing B"<<endl;
	}
	~B()
	{
		cout<<"destructing B"<<endl;
	}
private:
	int b;
	C c;
};

void main()
{
	B b;
}
输出结果:


5.虚函数-virtual function

多态、虚函数表(内存中实例、最前面、地址表、父子类在内存中函数位置)、

5.1 定义

C++中虚函数的作用主要是实现了多态的机制。多态,简言之就是用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法

5.2 虚函数表

虚函数(virtual function)是通过一张虚函数表(virtual table)来实现的,简称V-Table。该表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其能真实地反映实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中。

C++中,编译器必须要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

代码如下:

#include <iostream>
using namespace std;

class Base{
public:
	virtual void f(){cout<<"f()"<<endl;}
	virtual void g(){cout<<"g()"<<endl;}
	virtual void h(){cout<<"h()"<<endl;}
};

void main()
{
	Base b;
}
内存结构如下:

一般覆盖(无虚函数覆盖)

假设有如下所示的一个继承关系:

在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:对应实例:Derive d;的虚函数表如下:

可看到下面几点:

1)虚函数按照其声明顺序存在于表中。

2)父类的虚函数在子类的虚函数前面

一般继承(有虚函数覆盖)

虚函数如下所示:

从表中可看到以下几点

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置

2)没有被覆盖的函数依旧。

多重继承(无虚函数覆盖)

从上面可看到以下几点:

1)每个父类都有自己的虚表

2)子类的成员函数被放到了第一个父类的表中

多重继承(有虚函数覆盖)



注:

一、::是运算符中等级最高的,表示作用域和所属关系,它分为三种:

1)global scope(全局作用域符),用法(::name)

2)class scope(类作用域符),用法(class::name)

3)namespace scope(命名空间作用域符),用法(namespace::name)

例5:类作用域

#include <iostream>
using namespace std;

class First{
public:
	int memi;
	double memd;
};
class Second{
public:
	int memi;
	double mend;
}

void main()
{
	First f;
	Second s = f;
}

提示错误:不存在用户定义的从"First"到"Second"的适当转换。每个类都定义了自己的新作用域和唯一的类型。两个不同的类具有两个不同的类作用域。即使两个类具有完全相同的成员列表,它们也是不同的类型。每个类的成员不同于任何其他类的成员。


结束语

C++这门语言是一门 Magic的语言,对于程序员来说,我们似乎永远摸不清楚 这门语言背着我们在干什么。需要熟悉这门语言,我们就必须了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己的脚的编程语言。

参考:

http://www.cnblogs.com/xkfz007/archive/2012/05/11/2496447.html

http://blog.csdn.net/weizhee/article/details/562833

http://blog.csdn.net/wuchuanpingstone/article/details/6742465

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值