面向对象程序设计问答题OOP7构造和析构函数


1. 什么是构造函数?构造函数有返回值吗?构造函数如何命名?构造函数可以重载吗?什么是缺省构造函数?什么情况下,类中会有缺省构造函数?

  • 构造函数是一种特殊的函数,用来在对象实例化的时候初始化对象的成员变量

  • 构造函数没有返回值

    A constructor is a method whose name is the same as the class name.

  • 构造函数必须与类名相同

  • 构造函数可以进行重载,每个构造函数必须有不同的函数签名

    class Date {
    int  d, m, y;
    public:
    Date(int dd, int mm, int yy);
    Date(int dd, int mm);    // today's year
    Date(int dd);        // today's month and year
    Date();        // default Date: today    
    Date(const char* p);    // date in string representation
    /* ... */
    };
    

    The default constructor is a constructor that can be invoked with no arguments.

  • 缺省构造函数($default$ $constructor$): 缺省构造函数是可以不带参数调用的构造函数。一个类中,只能有一个缺省构造函数。

    The compiler provides a public default constructor for a class with two exception:

    • If a class explicitly declare any constructor, the complier does not provide a public default constructor. In this case, the programmer must provide a public default constructor if desired.
    • If a class declares a nonpublic default constructor, the complier does not provide a public default constructor.
  • 没有定义构造函数或者定义的构造函数没有参数时,类中会有缺省构造函数。

2. 构造函数的作用是什么?什么时候会被调用?构造函数的执行顺序是什么?

(父类与子类的构造函数、类自身与其数据成员的构造函数)

  • 作用: 用来在对象实例化的时候初始化对象的成员变量
  • 调用时间: 当类被创建时,自动调用构造函数
  • 执行顺序: 先执行父类的构造函数,再执行数据成员的初始化(成员中有类,执行该类的构造函数),最后执行子类的构造函数

3. 为什么拷贝构造函数(copy constructor)的参数必须是按引用传递(by reference)而不能是按值传递(by value)?

class Base
{
public:
    Base(){}
    Base(const Base b){
        //..
    }
};

Base A;
Base B=A;
  • 值传递不可以原因: 以上述代码说明,当$B$需要调用其拷贝构造函数,需要将$A$进行值传递进入$B$的拷贝构造函数,而在进行值传递时,$B$的拷贝构造函数会生成一个该类的临时对象(假设为$C$),会执行Base C=A(初始化形参,也就是初始化函数的局部变量),这又将调用$C$的拷贝构造函数,将$A$以值传递的方式传入,如此往复,每一次都会产生新的对象,无限递归调用拷贝构造函数从而耗尽资源,产生错误.
class Base
{
public:
    Base(){}
    Base(const Base& b){
        //..
    }
};

Base A;
Base B=A;
  • 引用传递可以的原因:当上述$B$调用拷贝构造函数时,会生成一个临时的引用变量,而不是对象,会执行Base &C=A(同样是初始化形参,只不过是初始化一个引用),这不会产生新的对象,也就不会调用拷贝构造函数,只是增加了一个指向A的临时引用而已。

4. 拷贝构造函数(复制构造函数)的作用是什么?什么是浅拷贝?什么是深拷贝?

  • 拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。
  • 作用: 用来复制对象,在使用这个对象的实例来初始化这个对象的一个新的实例。
  • 浅拷贝和深拷贝: 拷贝者和被拷贝者若是同一个地址,则为浅拷贝,反之为深拷贝,深拷贝会在堆内存中另外申请空间来储存数据。默认的拷贝构造函数实现的是浅拷贝,数据成员中有指针时,必须要用深拷贝。
//深度拷贝
int a = 5;
int b = a;
//浅拷贝
int a = 8;
int *p;
p = &a;

char* str1 = "HelloWorld";
char* str2 = str1;

5. 全局对象的构造函数、析构函数分别是什么时候被调用的?自动局部对象的构造函数、析构函数分别是什么时候被调用的?静态局部对象的构造函数、析构函数分别是什么时候被调用的?

  • 全局对象($Global$ $scope$ $objects$)的构造函数在程序运行前被调用,析构函数在程序结束前最后被调用
  • 自动局部对象($Automatic$ $local$ $objects$)的构造函数在程序执行到对象定义时自动被调用,析构函数在对象离开范围时(离开定义对象的块时)被调用
  • 静态局部对象($static$ $local$ $objects$)的构造函数在程序执行到对象定义时自动被调用,析构函数在程序结束前被调用

6. 什么是初始化列表?它的作用是什么?

(提示:一般数据成员的初始化、常成员的初始化,对象成员构选函数的选择、父类构造函数的选等)

  • 初始化列表($Initialization$ $Sections$):与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
class foo{
public:
     // 初始化列表
    foo(string s, int i): name(s), id(i){} ;
private:
    string name ;
    int id ;
};
  • 作用:
  1. 初始化一般数据成员(如上例)
  2. 初始化常量成员(原因:常量只能初始化不能赋值,所以必须放在初始化列表中)
  3. 初始化引用类型(原因:引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面)
  4. 初始化没有缺省构造函数的类类型(原因:使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化)
    举例:类A的对象a是类B的数据成员
#include <iostream> 
using namespace std; 

class A { 
    int i; 
public: 
    A(int ); 
}; 

A::A(int arg) { 
    i = arg; 
    cout << "A's Constructor called: Value of i: " << i << endl; 
} 

// Class B contains object of A 
class B { 
    A a; 
public: 
    B(int ); 
}; 

B::B(int x):a(x) { //Initializer list must be used 
    cout << "B's Constructor called"; 
} 

int main() { 
    B obj(10); 
    return 0; 
} 
/* OUTPUT: 
    A's Constructor called: Value of i: 10 
    B's Constructor called 
*/
  1. 初始化基类成员(父类成员)
#include <iostream> 
using namespace std; 

class A { 
    int i; 
public: 
    A(int ); 
}; 

A::A(int arg) { 
    i = arg; 
    cout << "A's Constructor called: Value of i: " << i << endl; 
} 

// Class B is derived from A 
class B: A { 
public: 
    B(int ); 
}; 

B::B(int x):A(x) { //Initializer list must be used 
    cout << "B's Constructor called"; 
} 

int main() { 
    B obj(10); 
    return 0; 
} 
  1. 当构造函数的参数名称与数据成员相同,必须使用this指针或初始化列表初始化数据成员。
#include <iostream> 
using namespace std; 

class A { 
    int i; 
public: 
    A(int ); 
    int getI() const { return i; } 
}; 

A::A(int i):i(i) { } // Either Initializer list or this pointer must be used 
/* The above constructor can also be written as 
A::A(int i) { 
    this->i = i; 
} 
*/

int main() { 
    A a(10); 
    cout<<a.getI(); 
    return 0; 
} 
/* OUTPUT: 
    10 
*/

7. 什么是析构函数?析构函数有返回值吗?析构函数如何命名?析构函数可以重载吗?

  • 析构函数也是一个在类中跟构造函数类似的特殊功能的成员函数,作用与构造函数相反,是在对象的声明周期结束的时候会被自动调用。
  • 析构函数没有返回值
  • 在C++中析构函数的名字跟类名相同,并在前面带上一个取反的符号~,表达的意思也跟构造函数的过程相反

    Because there is no parameter in a destructor, the destructor can not be overloaded. That is, a class has only one destructor.

  • 析构函数不可以重载,析构函数在类中仅有一个

8. 析构函数的作用是什么?什么时候会被调用?为什么析构函数通常是虚函数,如果不是虚函数,会如何?

  • 作用: 清空并释放对象先前创建或者占用的内存资源
  • 调用时间: 析构函数对象消亡时自动被调用
  • 原因: 如果析构函数不被声明成虚函数,则编译器采用的绑定方式是静态绑定,在删除基类指针时,只会调用基类析构函数,而不调用派生类析构函数,这样就会导致基类指针指向的派生类对象析构不完全。
  • 如何: 若是将析构函数声明为虚函数,不管派生类的析构函数前是否加$virtual$(可以理解为编译器优化),都构成重写。基类的指针指向派生类的对象,然后调用重写虚函数——析构函数,构成了多态,而多态与类型无关,只与对象有关,所以就能够调用的就是派生类的析构函数了。

9. 如果要编写一段程序,跟踪类A所创建的实例的个数,请叙述编写程序的大体思路。

使用静态数据成员,构造函数时$+1$,析构函数时$-1$.

class A {
public:
  A( ) 
  { 
     ++count;
     //… 
   } 
  ~A( ) 
  { - -count;
    //… 
   }
private:
  static unsigned count;        // class data member
};
unsigned A:: count = 0;

10. 什么是C++中的三大函数(The Big Three)?

  • 析构函数: ~S()
  • 拷贝构造函数: S(const S& s)
  • 赋值函数: operator=

文章作者: 保底不歪抽早柚
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 保底不歪抽早柚 !
评论
  目录