【校招面试 之 C/C++】第5题 C++各种构造函数的写法

构造函数 ,是一种特殊的方法 。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载

1、最基本的构造函数

class Base
 {
  public:
      Base(int var) : m_Var(var)
      {
      }
  private:
      int m_Var;
  };

2、拷贝构造函数

   class Base
   {
   public:
       Base(int var) : m_Var(var)
       {
       }
       //拷贝构造函数
       Base(Base &ref) : m_Var(ref.m_Var)
       {
       }
   private:
       int m_Var;
   };

为什么拷贝构造函数的参数只能用引用呢?

首先以下几种情况都会自动调用拷贝构造函数:

1)用一个已有的对象初始化一个新对象的时候

2)将一个对象以值传递的方式传给形参的时候

3)函数返回一个对象的时候

所以当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环直至栈溢出除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。

拷贝构造函数,一般不需要自己编写,系统默认的拷贝构造函数就能抗住了,但是有些情况需要在构造的时候开辟空间,这时候就需要拷贝构造函数了:

  class String
   {
   public:
       String(const char *str = NULL); // 普通构造函数
       String(const String &other);    // 拷贝构造函数
       ~ String(void);                 // 析构函数
   private:
       char *m_data; // 用于保存字符串
   };
   // String 的析构函数
   String::~String(void) 
   {
       delete [] m_data;
       // 由于m_data 是内部数据类型,也可以写成 delete m_data;
   }
   
   // String 的普通构造函数
   String::String(const char *str) 
   {
       if(str==NULL)
       {
           m_data = new char[1]; // 若能加 NULL 判断则更好
           *m_data = '';
       }
       else
       {
           int length = strlen(str);
           m_data = new char[length+1]; // 若能加 NULL 判断则更好
           strcpy(m_data, str);
       }
   }
   // 拷贝构造函数
   String::String(const String &other) 
   {
       int length = strlen(other.m_data);
       m_data = new char[length+1]; // 若能加 NULL 判断则更好
       strcpy(m_data, other.m_data);
   }

3、普通派生类构造函数的写法

   class Base
   {
   public:
       Base(int b) : m_b(b)
       {
       }
   private:
       int m_b;
   };
 
   class Derived : public Base
   {
   public:
       //普通派生类构造函数的写法
       Derived(int b, int d) : Base(b), m_d(d)
       {
       }
   private:
       int m_d;
   };

再写一个多继承的示例:

   class Base1
   {
   public:
       Base1(int b1) : m_b1(b1)
       {
       }
   private:
       int m_b1;
   };
 
   class Base2
   {
   public:
       Base2(int b2) : m_b2(b2)
       {
       }
   private:
       int m_b2;
   };
 
   class Derived : public Base1, public Base2
   {
   public:
       Derived(int b1, int b2, int d) : Base1(b1), Base2(b2), m_d(d)
       { //注意冒号语法后面的顺序无所谓,创造基类是按照上面的继承声明顺序来进行的...
       }
   private:
       int m_d;
   };

  

4、含有虚继承的派生类构造函数的写法

(1)虚基类存在的意义:

解释:

在继承中产生歧义的原因有可能是继承类继承了基类多次,如图,子类C最后会接受分别来自A和B的同一个或多个相同拷贝,从而产生了多个拷贝,即不止一次的通过多个路径继承类在内存中创建了基类成员的多份拷贝。而这些是A和B从父类继承而来,所以C类该继承A还是B传下来的还是都接受呢?这样就产生歧义,虚基类的基本原则是在内存中只有基类成员的一份拷贝。这样,通过把基类继承声明为虚拟的,就只能继承基类的一份拷贝,从而消除歧义。用virtual限定符把基类继承说明为虚拟的。
        父类
  子类A        子类B
        子类C

需要解决的问题:当派生类从多个基类派生,而这些基类又有共同的基类(“菱形结构”),则在访问此共同基类中的成员时,将产生冗余,并且可能因为冗余带来不一致性的问题。主要作用是来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题。即为最远的派生类提供唯一的基类成员,而不重复产生多次复制。语法:

class B1 : vartual public B  注意:需要在第一级继承时就要将共同基类设计成为虚基类

虚继承底层实现原理与编译器相关,一般通过虚基类指针虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。

 实际上,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间。

https://blog.csdn.net/xiejingfa/article/details/48028491

关于虚基类下面举例说明:

例1:

#include<iostream>
using namespace std;
 
class A  //大小为4
{
public:
	int a;
};
class B :virtual public A  //大小为12,变量a,b共8字节,虚基类表指针4
{
public:
	int b;
};
class C :virtual public A //与B一样12
{
public:
	int c;
};
class D :public B, public C //24,变量a,b,c,d共16,B的虚基类指针4,C的虚基类指针
{
public:
	int d;
};
 
int main()
{
	A a;
	B b;
	C c;
	D d;
	cout << sizeof(a) << endl;
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
	cout << sizeof(d) << endl;
	system("pause");
	return 0;
}

输出:

例2:

#include<iostream>
using namespace std;
 
class A
{
public:
	int a;
};
class B : public A 
{
public:
	int b;
};
class C : public A 
{
public:
	int c;
};
class D :public B, public C
{
public:
	int d;
};
 
int main()
{
	A a;
	B b;
	C c;
	D d;
	cout << sizeof(a) << endl;
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
	cout << sizeof(d) << endl;
	system("pause");
	return 0;
}

输出:

例3:虚继承的构造函数的写法

虚基类的成员是由最远派生类的构造函数通过调用基类的构造函数进行初始化的;

在整个继承结构中,直接或间接继承虚函数的所有派生类,都必须在构造函数的成员初始化列表中为虚函数的构造函数列出参数。如果未列出则表示调用该虚基类的默认构造函数;

在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。(调用忽略,但是需要在初始化列表中写出来)

#include<iostream>
using namespace std;

class A{
private:
	int a;
public:
	A(int a):a(a){}
};

class B:virtual public A{
private:
	int b;
public:
	B(int a, int b):A(a), b(b){}			// 类B不会再调用A的构造函数
};

class C:virtual public A{
private:
	int c;
public:
	C(int a, int c):A(a), c(c){}		// // 类C不会再调用A的构造函数
};

class D: public B,public C{
private:
	int d;
public:
	D(int a, int b, int c, int d):A(a), B(a,b), C(a,c),d(d){}
};
int main(){
	
	D d(1, 2, 3, 4);
	system("pause");
	return 0;
}

5、虚析构函数

下面举例使用到虚析构函数的情形:

#include<iostream>
using namespace std;

class Base{
public:
	virtual ~Base(){						// 如果基类的析构函数不是虚函数,则子类的析构函数将不会被执行
		cout<<"Base Destructor"<<endl;
	}
};

class Derived: public Base{
public:
	virtual ~Derived(){
		cout<<"Derived Destructor"<<endl;
		delete p;
	}
	Derived(){
		p = new int(1);
	}
private:
	int *p;
};

void fun(Base *b){
	delete b;
}
int main(){
	
	Base *b = new Derived();
	fun(b);
	system("pause");
	return 0;
}

输出:

对于析构函数:

众所周知,c++中的每个类都会有一个析构函数,当这个类的对象被销毁的时候,对象会自动调用析构函数。那么什么情况下对象的析构函数会被自动调用呢?其实这个问题也可以换种方式问,什么情况下对象会被自动销毁。

    我们跟据对象的声明方式分两种情况来讲:
    1、动态声明的对象
    这种声明方式下系统会自动销毁不再使用的对象,对应的对象的析构函数也会被调用。例如classname object;这样声明的对象,当程序运行到了对象作用域之外或者程序退出,对象都会被销毁,当然析构函数也会被调用。
    2、静态声明的对象(new等)
    这种声明方式下系统不会主动帮你销毁对象,对应的析构函数也不会被主动调用,除非你的程序显式地调用delete等函数。这种情况下只要你不去delete,对象的析构函数永远不会调用,即便这个对象的内存空间已经泄露或者程序退出。
原文地址:https://www.cnblogs.com/xuelisheng/p/9332221.html