C/C++编程:类继承中的构造函数和析构函数

#include 
#include 

struct dev_info_tag{
    int 			     camera_sdk_port= -1; //设备sdk端口
    std::string          camera_id;    //设备标识
};

class dev_abstract {
public:
    int             camera_port = 0;
    std::string     camera_id;

public:
    explicit dev_abstract(const std::shared_ptr<struct dev_info_tag>& devInfo);
};

dev_abstract::dev_abstract(const std::shared_ptr<struct dev_info_tag> &devInfo) {
    camera_id = devInfo->camera_id;
    camera_port = devInfo->camera_sdk_port;
}

class dev_dahua: public dev_abstract {
public:
    explicit dev_dahua(const std::shared_ptr<struct dev_info_tag>& devInfo);
};

dev_dahua::dev_dahua(const std::shared_ptr<struct dev_info_tag>& devInfo):dev_abstract(devInfo){

}


typedef std::shared_ptr<dev_abstract> dev_abstract_ptr;

using namespace std;

int main(){
    std::shared_ptr<struct dev_info_tag> devInfo = make_shared<dev_info_tag>();
    devInfo->camera_id = "aaaa";
    devInfo->camera_sdk_port = 8888;

    dev_abstract_ptr dev = make_shared<dev_dahua>(devInfo);
    printf("%s\n", dev->camera_id.c_str());
    return 0;
}






Virtual

virtual在英文中表示“虚”、“虚拟”的含义。c++中的关键字“virtual”主要用在两个方面:虚函数与虚基类。下面将分别从这两个方面对virtual进行介绍。

虚函数

虚函数源于c++中的类继承,是多态的一种。在c++中,一个基类的指针或者引用可以指向或者引用派生类的对象。同时,派生类可以重写基类中的成员函数。这里“重写”的要求是函数的特征标(包括参数的数目、类型和顺序)以及返回值都必须与基类中的函数一致。如下所示:

class base
{
public:
    void test(){ cout<<"基类方法!"<<endl; }
};

class inheriter:public base
{
public:
    void test(){ cout<<"派生类方法!"<<endl; }     //重写基类方法, 这个方法可以实现也可以不实现。不管实现不实现,都指向父类方法
};

可以在基类中将被重写的成员函数设置为虚函数,其含义是:当通过基类的指针或者引用调用该成员函数时,将根据指针指向的对象类型确定调用的函数,而非指针的类型。

  • 如下,是未将test()函数设置为虚函数前的执行结果:
    base *p1=new base;
    base *p2=new inheriter;
    p1->test();                                      //输出“基类方法”
    p2->test();                                      //输出“基类方法”
  • 在将test()函数设置为virtual后,执行结果如下:
#include 
#include 
using namespace  std;

class base
{
public:
    virtual void test(){ cout<<"基类方法!"<<endl; }
};

class inheriter:public base
{
public:
    void test() override { cout<<"派生类方法!"<<endl; }     //重写基类方法: 这个方法可以实现也可以不实现。不实现,指向父类方法,输出“基类方法”;实现,指向派生类方法,输出”派生类方法“
};

int main()
{

    base *p1=new base;
    base *p2=new inheriter;
    p1->test();                                      //输出“基类方法”
    p2->test();                                      //输出“派生类方法”

    return 0;
}

如此,便可以将基类与派生类的同名方法区分开,实现多态。

说明:

  • 只需要将基类中的成员函数声明为虚函数即可,派生类重写的virtual函数自动称为虚函数
  • 基类中的析构函数必修为虚函数,否则会出现对象释放错误。

多态

#include 
#include 
using namespace  std;
class Shape{
public:
    Shape() {
        cout<<"Base::Draw()"<<endl;
    }
    ~Shape() {
        cout<<"Base::Erase()"<<endl;
    }
};

class Polygon:public Shape{
public:
    Polygon() {cout<<"Polygon::Draw()"<<endl;}
    ~Polygon() {cout<<"Polygon Erase()"<<endl;}
};

class Rectangle:public Polygon{
public:
    Rectangle() {cout<<"Rectangle::Draw()"<<endl;}
    ~Rectangle() {cout<<"Rectangle Erase()"<<endl;}
};

C++类继承中的构造函数和析构函数

在C++的类继承[无论以什么方式继承]中,
建立对象时,首先调用基类的构造函数,然后在调用下一个派生类的构造函数,依次类推;
析构对象时,其顺序正好与构造相反;

第一个例子

int main()
{
    Polygon p;
/*    Base::Draw()
    Polygon::Draw()
    Polygon Erase()
    Base::Erase()*/
    return 0;
}
int main()
{
    Polygon *p = new Polygon();
/*    Base::Draw()
    Polygon::Draw()*/
    return 0;
}
int main()
{
    auto *p = new Polygon(); //==  Polygon *p = new Polygon();   // auto *会自动识别为Polygon

/*    Base::Draw()
    Polygon::Draw()
*/
    delete p;
    /*Polygon Erase()
    Base::Erase()*/
    return 0;
}
  • 只析构了父类,子类并没有析构执行
int main()
{
    Shape *p = new Polygon();
/*    Base::Draw()
    Polygon::Draw()
*/
    delete p; // 
    /*Base::Erase()*/
    return 0;
}

第二个例子

#include 
using namespace std;
class Person{
public:
    Person(int Age,string Name):m_nAge(Age),m_strName(Name){
        cout<<"Person:基类的代参构造函数"<<endl;
    }
    ~Person(){  //
        cout<<"Person:基类的析构构造函数"<<endl;
    }
private:
    int m_nAge;
    string m_strName;
};

class Student: protected Person{
public:

    Student(int Age,string Name,int Num):Person(Age,Name),m_nNum(Num){
        cout<<"Student:子类的代参构造函数"<<endl;
    }
    ~Student(){  //
        cout<<"Student:子类的析构构造函数"<<endl;
    }
    Student(const Student&stu):Person(stu),m_nNum(stu.m_nNum){
        cout<<"Student:子类的拷贝构造函数"<<endl;
    }
    Student& operator= (const Student&stu){ // 子类的拷贝赋值
        if(this != &stu)
        {
            Person::operator= (stu);
            m_nNum = stu.m_nNum;
        }
        return *this;
    }
private:
    int m_nNum;
};

使用:

int main()
{
    Student *student = new Student(11, "aaa", 11);
    /*
     * Person:基类的代参构造函数
     * Student:子类的代参构造函数
     * */
    return 0;
}

int main()
{
    Student *student = new Student(11, "aaa", 11);
    delete student;
    /*
Person:基类的代参构造函数
Student:子类的代参构造函数
Student:子类的析构构造函数
Person:基类的析构构造函数
     * */
    return 0;
}

C++多态性中的静态绑定和动态绑定

静态绑定和动态绑定是C++多态性的一种特性。

对象的静态类型和动态类型:

  • 对象的静态类型:对象在声明是采用的类型,在编译期确定;

  • 对象的动态类型:当前对象所指的类型,在运行期决定,对象的动态类型可以更改,但静态类型无法更改。

int main()
{
    Polygon *p = new Polygon(); // p的静态类型是它声明的类型Polygon*,动态类型也是Polygon*
    Shape *p1 = new Polygon(); // p1的静态类型是它声明的类型Shape*,动态类型p1是所指的对象Polygon*
    Rectangle *pr = new Rectangle();
    p1 = pr; // pB的动态类型可以改变,现在它的动态类型为Rectangle*

    return 0;
}

其他

如果一个类被继承,同时定义了基类以外的成员对象,而且基类析构函数不是virtual修饰的,那么当基类指针或者引用指向派生类对象并析构(例如自动对象在函数作用域结束时;或者通过delete)时,会调用基类的析构函数而导致派生类定义的成员没有被析构,产生内存泄露等问题。虽然把析构函数定义成virtual的可以解决这个问题,但是当其他成员函数都不是virtual函数时,会在基类和派生类中引入vtable,实例引用入vptr造成运行时的性能损失。

如果确定不需要直接而是只通过派生类对象使用基类,可以把析构函数定义为protected(这样会使得基类和派生类外使用自动对象和delete时的错误,因为访问权限禁止调用析构函数),就不会导致以上问题。

将构造和析构都声明为protected的用法

从语法上来讲,一个函数被声明为protected或者private,那么这个函数就不能从“外部”直接被调用了。
对于protected的函数,子类的“内部”的其他函数可以调用之。
而对于private的函数,只能被本类“内部”的其他函数说调用。
通常使用的场景如下:

如果你不想让外面的用户直接构造一个类(假设这个类的名字为A)的对象,而希望用户只能构造这个类A的子类

那你就可以将类A的构造函数/析构函数声明为protected,而将类A的子类的构造函数/析构函数声明为public。例如:

class A
{ 
protected: 
	A(){}
public:.
};
class  B : public A
{ 
public: 
	B(){}.
};

A a; // error
B b; // ok

单例模式

如果将构造函数/析构函数声明为private,那只能这个类的“内部”的函数才能构造这个类的对象了。这里所说的“内部”不知道你是否能明白,下面举个例子吧。

class A
{
private:
A(){ }
~A(){ }
public:
	void Instance()//类A的内部的一个函数
	{
		A a;
	}
};

上面的代码是能通过编译的。上面代码里的Instance函数就是类A的内部的一个函数。Instance函数体里就构建了一个A的对象。
但是,这个Instance函数还是不能够被外面调用的。为什么呢?
如果要调用Instance函数,必须有一个对象被构造出来。但是构造函数被声明为private的了。外部不能直接构造一个对象出来。

A aObj; // 编译通不过
aObj.Instance();

但是,如果Instance是一个static静态函数的话,就可以不需要通过一个对象,而可以直接被调用。如下:

class A
{
private:
	A():data(10){ cout << “A” << endl; }
	~A(){ cout <<~A” << endl; }
public:
	static A& Instance()
	{
	    static A a;
	    return a;
	}

	void Print()
	{
	    cout << data << endl;
	}
private:
	int data;
};

A& ra = A::Instance();
ra.Print();

保证对象只生成在堆上

当我们想禁止在栈中产生对象时,如何来实现呢?

将构造函数设为private行吗?no!这样对象虽然不能在栈中产生,但同样也不能在堆中产生了。

将析构函数设为private行吗?bad!这样虽然限制了栈对象,但同时也限制了继承。

将析构函数设为protected行吗?yes!

例如:

class A
{  
protected:   
           A() { }
    ~A() { }  
public:   
         static A* create()
  {    return new A() ;//调用保护的构造函数   }   
         void destroy()   
         {    delete this ;//调用保护的析构函数   }
};

我们可以这样来使用它:

A* pa= A::create();
pa->destroy() ;

纯虚析构函数

有的时候,你想要一个类成为抽象类,但是刚好有没有任何纯虚函数。怎么办?因为抽象类是准备被用作基类的,基类必须要有一个虚析构函数,纯虚函数会产生抽象类。:在想要成为抽象类的类里声明一个纯虚析构函数。

 class awov {
  public:
  virtual ~awov() = 0; // 声明一个纯虚析构函数
  };

这个类有一个纯虚函数,所以它是抽象的,而且它有一个虚析构函数,所以不会产生析构函数问题。但这里还有一件事:必须提供纯虚析构函数的定义:

  awov::~awov() {} // 纯虚析构函数的定义

这个定义是必须的,因为虚析构函数工程的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类,编译器也要产生对~awov的调用,所以要保证为它提供函数体。如果不这么做,链接器就会检测出来,最后还是得回去把它添上。

附上面试例题:
1、c++中如何阻止一个类被实例化?
2、一般什么时候构造函数被声明private?
3、什么时候编译器会生成默认的复制构造函数?

答:
1、当类中又被声明纯虚函数;将构造函数、析构函数声明private
2、当不想让用户在类外构造类的对象,阻止编译器产生默认复制构造函数。
3、用户未定义,且程序需要

你可能感兴趣的:(C++,学习,c++)