C/C++ 面试八股文

C++面试常见问题 - 知乎

智能指针: 

智能指针(Smart Pointers)是一种用于管理动态内存的数据结构,通常用于C++和某些其他编程语言中。它们提供了更安全和方便的内存管理方式,帮助减少内存泄漏和悬垂指针等问题。智能指针是与RAII(资源获取即初始化)编程原则紧密相关的,因此它们确保在离开作用域时自动释放分配的内存。

主要的智能指针类型包括:

  1. std::shared_ptr:共享指针,允许多个智能指针共享相同的资源。资源在最后一个引用离开作用域时释放。

  2. std::unique_ptr:唯一指针,确保只有一个指针可以访问分配的资源。资源在唯一指针离开作用域时释放。

  3. std::weak_ptr:弱指针,与shared_ptr一起使用,用于解决循环引用的问题。弱指针不增加引用计数,它只能用于监视资源的生存状态。

智能指针的好处包括:

  • 自动内存管理:它们负责在资源不再需要时自动释放内存,减少了内存泄漏的风险。

  • 减少悬垂指针:当资源被释放后,智能指针将确保不再引用它,从而避免了悬垂指针问题。

  • 简化代码:它们可以减少显式的内存管理操作,使代码更清晰和安全。

  • 支持多线程:某些智能指针类型(如std::shared_ptr)具有引用计数,可以用于多线程环境。

使用智能指针有助于提高C++程序的健壮性和可维护性。然而,开发者仍然需要小心避免循环引用,以确保资源的正确释放。

实现一个智能指针:

#include 

template 
class SmartPointer {
private:
    T* ptr;

public:
    // 构造函数
    SmartPointer(T* p = nullptr) : ptr(p) {}

    // 析构函数
    ~SmartPointer() {
        delete ptr;  // 在析构函数中释放资源
    }

    // 拷贝构造函数
    SmartPointer(const SmartPointer& other) {
        ptr = new T(*other.ptr);
    }

    // 赋值运算符
    SmartPointer& operator=(const SmartPointer& other) {
        if (this == &other) {
            return *this;
        }
        delete ptr;  // 释放当前资源
        ptr = new T(*other.ptr);
        return *this;
    }

    // 解引用操作符
    T& operator*() {
        return *ptr;
    }

    // 成员访问操作符
    T* operator->() {
        return ptr;
    }
};

int main() {
    SmartPointer sp1(new int(42));
    SmartPointer sp2 = sp1; // 使用拷贝构造函数
    SmartPointer sp3(new int(10));
    sp3 = sp2; // 使用赋值运算符

    std::cout << "Value from sp1: " << *sp1 << std::endl;
    std::cout << "Value from sp2: " << *sp2 << std::endl;
    std::cout << "Value from sp3: " << *sp3 << std::endl;

    return 0;
}

share_ptr是如何实现的

std::shared_ptr 是C++标准库中的智能指针,它允许多个std::shared_ptr共享相同的资源,并在资源不再需要时自动释放。std::shared_ptr的实现通常基于引用计数,它会记录资源被多少个std::shared_ptr共享。以下是std::shared_ptr的基本工作原理:

  1. 构造和拷贝构造:当您创建一个std::shared_ptr时,它将创建一个引用计数对象,该对象包含两部分信息:指向分配的资源的指针和一个引用计数。

  2. 引用计数:每当您拷贝一个std::shared_ptr(或将其作为参数传递给函数),引用计数会递增。当析构或赋值操作使引用计数为零时,资源会被释放。

  3. 资源释放:当std::shared_ptr的引用计数变为零,它将自动释放关联的资源。这是通过析构函数来实现的,当引用计数为零时,析构函数被调用,资源被释放。

  4. 复制和赋值std::shared_ptr支持复制和赋值操作,使多个std::shared_ptr可以共享相同的资源,而不会出现悬垂指针问题。当一个std::shared_ptr离开作用域或不再需要资源时,它的引用计数会减少,直到资源不再被引用。

  5. 循环引用std::shared_ptr存在循环引用的潜在问题。如果两个或多个std::shared_ptr相互引用,它们的引用计数将永远不会变为零,导致资源泄漏。为了解决这个问题,C++11引入了std::weak_ptr,允许弱引用资源,但不会增加引用计数,用于打破循环引用。

std::shared_ptr的实现会维护引用计数并确保在没有引用时释放资源。这是通过使用std::shared_ptr的析构函数来实现的,当最后一个std::shared_ptr离开作用域或不再需要资源时,析构函数被调用,资源得到释放。引用计数的维护是线程安全的,这使得std::shared_ptr适用于多线程环境。

实现一个完整的 shared_ptr 需要涉及引用计数、资源管理、拷贝构造、赋值运算符重载等复杂的细节。以下是一个非常基本的示例,展示了 shared_ptr 的基本思想,但不包括线程安全和其他重要功能。请注意,这只是教育目的的示例,实际使用时应使用C++标准库中的 std::shared_ptr

#include 

template 
class SharedPtr {
public:
    // 构造函数
    SharedPtr(T* ptr) : data(ptr), ref_count(new int(1)) {}

    // 拷贝构造函数
    SharedPtr(const SharedPtr& other) : data(other.data), ref_count(other.ref_count) {
        (*ref_count)++;
    }

    // 析构函数
    ~SharedPtr() {
        if (--(*ref_count) == 0) {
            delete data;
            delete ref_count;
        }
    }

    // 赋值运算符
    SharedPtr& operator=(const SharedPtr& other) {
        if (this == &other) {
            return *this;
        }

        // 减少当前对象的引用计数
        if (--(*ref_count) == 0) {
            delete data;
            delete ref_count;
        }

        data = other.data;
        ref_count = other.ref_count;
        (*ref_count)++;

        return *this;
    }

    // 解引用操作符
    T& operator*() {
        return *data;
    }

    // 成员访问操作符
    T* operator->() {
        return data;
    }

private:
    T* data;
    int* ref_count;
};

int main() {
    SharedPtr sp1(new int(42));
    SharedPtr sp2 = sp1;
    SharedPtr sp3(new int(10));
    sp3 = sp2;

    std::cout << "Value from sp1: " << *sp1 << std::endl;
    std::cout << "Value from sp2: " << *sp2 << std::endl;
    std::cout << "Value from sp3: " << *sp3 << std::endl;

    return 0;
}

这个示例演示了一个非线程安全的 SharedPtr 类,它能够跟踪引用计数并在引用计数减为零时释放资源。在实际应用中,您需要考虑线程安全、更复杂的功能,以及更多的边界情况,以确保正确和高效的资源管理。最好的选择是使用C++标准库中的 std::shared_ptr,因为它已经经过充分测试和优化。

 shared_ptr使用方法:

1. 构造函数std::shared_ptr 可以使用多种构造函数创建,其中包括从原始指针、另一个 std::shared_ptr、或者其他智能指针类型创建。

std::shared_ptr sp1(new int(42)); // 使用原始指针创建
std::shared_ptr sp2 = std::make_shared(42); // 使用 make_shared 创建
std::shared_ptr sp3 = sp1; // 使用拷贝构造函数创建

拷贝构造函数:用于创建一个新的 std::shared_ptr,共享相同的资源。

std::shared_ptr sp1(new int(42));
std::shared_ptr sp2 = sp1; // 使用拷贝构造函数

赋值操作符:用于将一个 std::shared_ptr 赋值给另一个,共享相同的资源。

std::shared_ptr sp1(new int(42));
std::shared_ptr sp2;
sp2 = sp1; // 使用赋值操作符

reset 方法:用于重置 std::shared_ptr,它可以释放资源并指向新的资源。

std::shared_ptr sp1(new int(42));
sp1.reset(new int(10)); // 重置 shared_ptr,释放旧资源

use_count 方法:用于获取 std::shared_ptr 的引用计数。

std::shared_ptr sp1(new int(42));
int count = sp1.use_count(); // 获取引用计数

get 方法:用于获取 std::shared_ptr 内部的原始指针。

std::shared_ptr sp1(new int(42));
int* rawPtr = sp1.get(); // 获取原始指针

*operator 和 operator->**:允许通过 *-> 操作符来访问资源。

std::shared_ptr sp1(new int(42));
int value = *sp1; // 解引用操作符
int* rawPtr = sp1.get();
int value2 = *rawPtr; // 也可以通过原始指针访问

operator bool:用于检查 std::shared_ptr 是否为空(未指向任何资源)。

std::shared_ptr sp1;
if (!sp1) {
    // shared_ptr 为空
}

这些方法和操作符使 std::shared_ptr 可以方便地管理资源和共享资源的所有权,同时自动处理引用计数和资源释放。

C++ 中的构造函数有几种:

在C++中,构造函数有几种不同的类型,主要分为以下几类:

  1. 默认构造函数(Default Constructor)

    • 默认构造函数没有参数,用于创建对象的实例。
    • 如果您没有为类定义构造函数,编译器会为您自动生成一个默认构造函数,但如果您自定义了任何构造函数,编译器将不再提供默认构造函数。
  2. 参数化构造函数(Parameterized Constructor)

    • 参数化构造函数接受一个或多个参数,用于初始化对象的成员变量。
    • 它允许您在创建对象时传递参数,以自定义对象的初始化。
  3. 拷贝构造函数(Copy Constructor)

    • 拷贝构造函数接受同一类型的对象作为参数,用于创建一个新对象,新对象的值与原对象相同。
    • 它在对象复制时被调用,通常在对象传递给函数或通过赋值操作时使用。
  4. 移动构造函数(Move Constructor)(C++11及以后):

    • 移动构造函数用于将资源从一个对象“移动”到另一个对象,而不是进行复制。这提高了性能,特别是在处理动态分配内存等资源时。
  5. 复制构造函数和移动构造函数可以重载的版本

    • 复制构造函数和移动构造函数可以有多个版本,根据参数的不同来重载。例如,可以有一个接受常量引用和一个接受非常量引用的版本。
  6. 析构函数(Destructor)

    • 析构函数没有参数,用于对象生命周期结束时清理资源。通常用于释放动态分配的内存、关闭文件、释放锁等操作。
  7. 委托构造函数(Delegating Constructor)(C++11及以后):

    • 委托构造函数是一个构造函数调用另一个构造函数,以减少冗余代码。这允许您在一个构造函数中调用另一个构造函数来执行实际的初始化工作。

这些构造函数类型提供了不同的初始化和对象创建方式,根据您的需要,可以选择适合的构造函数类型。

C++ 的特点:

C++ 是一种多范式的编程语言,它继承了 C 语言的基本特性,同时引入了面向对象编程和泛型编程的概念。以下是 C++ 的主要特点:

  1. 面向对象编程(OOP):C++ 支持面向对象编程,允许将数据和操作封装在类中,提供了封装、继承和多态等面向对象的概念。

  2. 泛型编程:C++ 支持泛型编程,通过模板(template)实现,允许编写通用的、参数化的代码,使代码更灵活和可重用。

  3. 高性能:C++ 允许直接访问内存,提供了指针和引用,使得可以编写高性能的代码,适用于系统级编程、游戏开发和嵌入式系统等领域。

  4. 标准库:C++ 标准库包括丰富的数据结构和算法,使程序员能够更轻松地进行常见任务,例如容器、字符串处理、文件操作和输入/输出等。

  5. 多线程支持:C++11 引入了多线程支持,通过标准库中的线程和互斥机制,使并发编程更容易。

  6. 强类型:C++ 是一种强类型语言,要求严格的类型检查,有助于避免类型相关的错误。

  7. 内存管理:C++ 允许手动管理内存,但也提供了智能指针和 RAII(资源获取即初始化)的机制,帮助减少内存泄漏和悬垂指针问题。

  8. 运算符重载:C++ 允许运算符重载,使程序员能够自定义类的行为,例如重载 + 运算符用于自定义类的加法操作。

  9. 低级访问能力:C++ 允许直接访问硬件和内存,适用于系统编程和嵌入式系统开发。

  10. 兼容性:C++ 是 C 的超集,允许在 C++ 程序中使用 C 代码,同时 C++ 编译器也能编译 C 代码。

  11. 广泛应用:C++ 在游戏开发、嵌入式系统、高性能计算、科学计算和金融领域等多个领域得到广泛应用。

  12. 标准化:C++ 有一个国际标准,由 ISO/IEC 维护,定期更新并增加新特性,以保持现代性。

需要注意的是,C++ 的复杂性也导致它的学习曲线相对陡峭,程序员需要深入了解语言的各个方面以避免一些潜在的陷阱。

C++ 与 C 的区别:

C++ 是 C 语言的超集,它继承了 C 的基本特性,并引入了许多新的概念和特性。以下是 C++ 和 C 之间的一些主要区别:

  1. 面向对象编程

    • C++ 支持面向对象编程(OOP),允许使用类和对象的概念,实现封装、继承和多态等特性。C 并没有这些概念。
  2. 泛型编程

    • C++ 引入了模板(template),支持泛型编程,可以编写通用代码以适应不同的数据类型。C 没有泛型支持。
  3. 标准库

    • C++ 包括标准模板库(STL),提供了丰富的数据结构和算法,如容器、迭代器、算法、字符串处理等,使开发更加高效。
  4. 函数重载

    • C++ 允许函数重载,可以定义多个具有相同名称但不同参数的函数,根据参数的类型或数量来区分它们。C 不支持函数重载。
    • C++ 允许定义类,其中包含数据成员和成员函数,以实现封装和抽象数据结构。C 没有类的概念。
  5. 构造函数和析构函数

    • C++ 允许定义构造函数和析构函数,用于对象的初始化和清理。C 中没有这些特性。
  6. 运算符重载

    • C++ 允许运算符重载,使开发者能够自定义类的操作符行为,如 +- 等。C 不支持运算符重载。
  7. 引用

    • C++ 引入了引用,允许创建别名,用于参数传递和操作变量。C 中没有引用的概念。
  8. 命名空间

    • C++ 引入了命名空间,用于组织和避免命名冲突。C 没有命名空间。
  9. 异常处理

    • C++ 支持异常处理,允许程序员编写异常安全的代码。C 不具备内置的异常处理机制。
  10. bool 类型

    • C++ 引入了 bool 类型,用于表示布尔值 truefalse。C 使用整数值 0 和非零值来表示真假。
  11. 类型检查

    • C++ 是一种强类型语言,要求严格的类型检查,有助于避免类型相关的错误。C 中类型检查相对宽松。
  12. 多线程支持

    • C++11 引入了多线程支持,包括 std::threadstd::mutex 等,使并发编程更容易。C 没有内置多线程支持。

需要注意的是,C++ 仍然允许编写 C 风格的代码,因此可以在 C++ 程序中包含 C 代码,并且现有的 C 代码通常可以无需修改地与 C++ 集成。C++ 和 C 之间的选择通常取决于项目需求和编程风格。

你可能感兴趣的:(面试,职场和发展)