类的quiz

类的quiz

99. quiz

1. 编译器会为C++的类自动生成什么函数?

编译器会为C++的类自动生成以下六种特殊的成员函数:

  1. 默认构造函数(如果没有定义任何构造函数)
  2. 析构函数(如果没有定义)
  3. 拷贝构造函数(如果没有定义并且某些条件满足)
  4. 拷贝赋值运算符(如果没有定义并且某些条件满足)
  5. 移动构造函数(如果没有定义并且某些条件满足,C++11及以后)
  6. 移动赋值运算符(如果没有定义并且某些条件满足,C++11及以后)

这些函数被称为特殊成员函数。它们是编译器为了支持一些基本操作(如初始化、拷贝、移动、销毁对象)而自动为类生成的。但是,如果你为类定义了自己的版本,编译器就不会再生成这些函数。

2. 什么条件下不会自动生成?

  • 拷贝构造函数和拷贝赋值运算符
    • 如果你没有显式定义它们,且类中的所有成员都可以被拷贝,编译器就会为你生成这两个函数。但是,如果类中有不能被拷贝的成员(例如,std::unique_ptr或者定义了删除的拷贝构造函数的类型的成员),编译器就不会生成这两个函数。
  • 移动构造函数和移动赋值运算符 从C++11开始,如果你没有显式定义它们,且类中的所有成员都可以被移动,编译器就会为你生成这两个函数。但是,如果类中有不能被移动的成员(例如,定义了删除的移动构造函数的类型的成员),或者你已经定义了拷贝构造函数、拷贝赋值运算符或析构函数,编译器就不会生成这两个函数。

这些规则的目的是为了保证对象的正确拷贝和移动,避免因为编程错误导致的问题,例如内存泄漏或者浅拷贝。

3. 这六种特殊成员函数的函数签名是什么?

这六种特殊成员函数的函数签名如下:

  1. 默认构造函数:T()
  2. 析构函数:~T()
  3. 拷贝构造函数:T(const T& other)
  4. 拷贝赋值运算符:T& operator=(const T& other)
  5. 移动构造函数:T(T&& other)
  6. 移动赋值运算符:T& operator=(T&& other)

其中,T是类的名称,other是另一个同类型的对象。在拷贝构造函数和拷贝赋值运算符中,other是一个对常量的引用,表示这个函数不会修改other。在移动构造函数和移动赋值运算符中,other是一个右值引用,表示这个函数可能会修改other,通常是通过“窃取”其资源。

4. 为什么这六种特殊的成员函数是这样子设定的?

  1. 默认构造函数:T()。这是最基本的构造函数,如果用户没有定义任何构造函数,编译器会提供一个。它不接受任何参数,用于创建类的一个新实例。

  2. 析构函数:~T()。这个函数在对象被销毁时调用,用于清理对象可能拥有的资源。如果用户没有定义,编译器会提供一个默认的析构函数。

  3. 拷贝构造函数:T(const T& other)。这个函数用于创建一个新对象,该对象是一个已存在对象的副本。const T&表示这个函数接收一个对已存在对象的引用,且不会修改这个对象。

  4. 拷贝赋值运算符:T& operator=(const T& other)。这个函数用于将一个已存在对象的状态赋值给另一个对象。const T&表示这个函数接收一个对已存在对象的引用,且不会修改这个对象。

  5. 移动构造函数:T(T&& other)。这个函数用于创建一个新对象,该对象“窃取”了一个已存在对象的资源。T&&表示这个函数接收一个对已存在对象的右值引用,这个对象可能会被修改。

  6. 移动赋值运算符:T& operator=(T&& other)。这个函数用于将一个已存在对象的资源“窃取”给另一个对象。T&&表示这个函数接收一个对已存在对象的右值引用,这个对象可能会被修改。

这些函数的设定方式主要是为了支持对象的创建、拷贝、移动和销毁,同时尽可能地避免不必要的对象复制和资源浪费。

简单来说,以拷贝构造函数为例,即使不使用const T& other作为形参,而是使用T& other也是可以声明拷贝构造函数的。但一般来说,我们需要override编译器默认生成的拷贝构造函数,而且const T& other会比T& other好,因为使用const T& other作为参数,可以从任何对象(包括const对象和临时对象)中拷贝。

临时对象(也称为右值)是不能绑定到非const的左值引用上的,但可以绑定到const的左值引用上。这是C++语言规则的一部分,主要是为了保护临时对象不被意外修改。

除此,如果不是&引用类型的话,也会有问题。

  1. 性能:当参数是值时,会创建该对象的一个新的副本,这涉及到对象的复制操作,可能会消耗大量的时间和内存.特别是当对象较大时,复制的代价会非常高.而当参数是常量引用时,只需要传递引用,不需要复制对象,因此性能更高.

  2. 避免无限递归:如果拷贝构造函数的参数是值,那么在调用拷贝构造函数时,需要先创建一个新的对象,这又需要调用拷贝构造函数,从而形成无限递归,导致程序崩溃.而当参数是常量引用时,不会触发拷贝构造函数,因此可以避免无限递归.

所以,拷贝构造函数的参数通常是常量引用,而不是值.

5. 为什么移动构造函数一般情况下会比拷贝构造函数快?

6. 移动的时候如果不将原来的置空会怎样?

在编程中,”移动”通常指的是移动语义,这是C++11引入的一个新特性。移动语义允许资源(如动态分配的内存)从一个对象转移到另一个对象,这样可以避免不必要的临时对象的复制。

如果在移动操作后不将原对象置空,可能会导致一些问题。例如,如果原对象是一个拥有动态分配内存的对象,那么当原对象和新对象都试图释放同一块内存时,就会发生双重删除,这会导致程序崩溃。

因此,通常在执行移动操作后,我们会将原对象置空或设置为一个安全的状态,以防止这种问题的发生。

7. 一个类B,它有一个A*类型的成员变量.并写出它的拷贝构造函数.

class A {
    // A的定义
};

class B {
public:
    B() : ptr(new A()) {}  // 默认构造函数
    B(const B& other) : ptr(new A(*(other.ptr))) {}  // 拷贝构造函数
    ~B() { delete ptr; }  // 析构函数

private:
    A* ptr;  // A*类型的成员变量
};