面试题小品

81 阅读9分钟

sizeof运算符和strlen函数的区别是什么

  • 使用时机sizeof 在编译时确定大小,而 strlen 在运行时计算字符串长度。
  • 用途sizeof 用于获取内存占用大小,strlen 用于获取字符串长度。
  • 对象sizeof 可以用于任何数据类型,而 strlen 只能用于以 null 结尾的字符串。
  • 返回值sizeof 返回的是字节数,而 strlen 返回的是字符数。
  • 性能sizeof 没有运行时开销,因为它在编译时就已经确定;strlen 需要遍历整个字符串直到找到 null 字符,因此有运行时开销。

strcpy,memcpy

strcpy函数将源字符串(包括结尾的 null 字符 \0)复制到目标字符串中。

  1. 重叠字符串:如果源字符串和目标字符串有重叠,strcpy 可能会导致未定义的行为。在这种情况下,应使用 strncpy 函数。
  2. 安全替代:为了更安全地复制字符串,可以使用 strncpy 函数,它允许指定最大复制的字符数。但是,即使使用 strncpy,也需要确保正确处理字符串的结尾。

memcpy是一个内存复制函数,它将指定数量的字节从源内存区域复制到目标内存区域。

memcpy 不会停止复制操作来检查源或目标内存区域中的 null 字符。 它可以用于复制任意类型的数据,包括结构体和数组。

malloc和new

前者只分配内存,不会调用任何构造函数。- 返回 void* 类型的指针,需要显式地转换为需要的指针类型。

  • 需要手动调用 free 来释放内存。

后者分配内存,并调用对象的构造函数。- 返回正确类型的指针,无需显式类型转换。

  • 需要使用 delete 来释放单个对象的内存,使用 delete[] 来释放数组的内存。

虚函数与纯虚函数

  1. 虚函数(Virtual Function)

    • 虚函数是一个在基类中声明为 virtual 的函数,可以在派生类中被重写。
    • 虚函数可以有实现(即函数体),也可以没有实现(即只有声明)。
    • 一个类如果包含至少一个虚函数,它就被称为多态类(polymorphic class)。
    • 虚函数的调用在运行时动态绑定,即根据对象的实际类型来调用相应的函数。
  2. 纯虚函数(Pure Virtual Function)

    • 纯虚函数是一个在基类中声明为 virtual 并且没有实现的函数,其声明的末尾有两个关键字 = 0
    • 包含至少一个纯虚函数的类被称为抽象类(abstract class),抽象类不能被实例化
    • 纯虚函数的主要目的是作为接口,强制派生类提供特定的实现。
    • 派生类必须重写所有纯虚函数,除非派生类也是抽象类

示例

cpp
class Base {
public:
    virtual void func1() { /* 实现 */ } // 虚函数
    virtual void func2() = 0; // 纯虚函数
};

class Derived : public Base {
public:
    void func1() override { /* 重写实现 */ }
    void func2() override { /* 实现 */ } // 必须提供实现
};

区别

  • 实现:虚函数可以有实现,而纯虚函数没有实现。
  • 实例化:包含虚函数的类可以被实例化,而包含纯虚函数的类不能被实例化,它们是抽象类。
  • 目的:虚函数用于实现多态性,允许在基类指针或引用上调用函数时动态绑定到派生类的实现。纯虚函数用于定义接口,确保派生类实现某些函数。
  • 重写:派生类可以不重写虚函数(除非需要改变行为),但必须重写纯虚函数(除非派生类也是抽象类)。

goto语句作用

在 C++ 中,goto 语句是一种无条件的跳转语句,它允许程序的控制流跳转到同一作用域内的某个标签处。goto 通常用于跳出深层嵌套的循环或跳出复杂的代码块。

语法

goto 标签名;

示例

    // 外层循环
    for (i = 0; i < 5; ++i) {
        // 中层循环
        for (j = 0; j < 5; ++j) {
            // 内层循环
            for (k = 0; k < 5; ++k) {
                if (i * j * k > 10) {
                    std::cout << "Jumping out of loops!" << std::endl;
                    goto end; // 跳转到 end 标签
                }
            }
        }
    }
    
    // 如果没有跳转,执行到这里
    std::cout << "This will not be printed." << std::endl;

end:
    std::cout << "This will be printed after the goto statement." << std::endl;

    return 0;
}

注意事项

  1. 标签定义:标签是由标识符和冒号组成的,它必须位于 goto 语句之前,且在同一个作用域内。

  2. 跨函数跳转goto 不能跳转到另一个函数中的标签。

  3. 跨作用域跳转goto 不能跳转到不同作用域的标签,例如不能从函数内部跳转到函数外部。

  4. 跳过初始化:使用 goto 可能会跳过变量的初始化,这可能导致未定义的行为。

goto 在某些情况下可能有用,但一般建议尽量避免使用它,除非没有更好的替代方案

nullptr

用于声明一个指向特定类型的空指针

可以被隐式转换为任何指针类型,但不会转换为整数类型;

nullptr 可以用作任何指针类型的初始值,但它必须被转换为相应的指针类型。

STRUCT和CLASS的区别

  1. 默认访问权限

    • struct 的成员默认是 public,这意味着在没有指定访问修饰符的情况下,struct 的成员可以被任何访问该 struct 的代码访问。
    • class 的成员默认是 private,这意味着在没有指定访问修饰符的情况下,class 的成员只能被类的内部以及友元函数访问。
  2. 继承访问权限

    • 当一个 struct 继承自另一个 struct 或 class 时,基类的 private 成员在派生 struct 中仍然是 private基类的 protected 成员在派生 struct 中变为 public
    • 当一个 class 继承自另一个 class 或 struct 时,基类的 private 成员在派生 class 中仍然是 private基类的 protected 成员在派生 class 中仍然是 protected
  3. 设计意图

    • struct 通常用于表示简单的数据集合,它强调数据的聚合,而不是数据的封装和抽象。
    • class 通常用于表示复杂的数据类型和对象的封装,它强调数据的隐藏和操作数据的函数的封装。
  4. 布局

    • 在某些编译器实现中,struct 和 class 在内存布局上可能有所不同,尽管这并不是标准规定的行为。
  5. 历史和习惯用法

    • 在 C++ 的早期版本中,struct 没有方法和继承,只能用来定义纯数据结构。随着 C++ 的发展,struct 获得了与 class 相同的功能。
    • 在 C 语言中,struct 只能包含数据,不能包含函数。在 C++ 中,struct 和 class 都可以包含数据和函数。

智能指针和指针的区别

  1. 内存管理

    • 智能指针:自动管理内存,当智能指针对象的生命周期结束时,会自动释放它所指向的内存。
    • 普通指针:需要程序员手动使用 new 和 delete 操作符来分配和释放内存。
  2. 类型

    • 智能指针:实际上是一个模板类的对象,例如 std::unique_ptrstd::shared_ptr 和 std::weak_ptr
    • 普通指针:是直接指向某个数据类型的内存地址的变量。
  3. 所有权

    • 智能指针

      • std::unique_ptr 表示独占所有权,不允许复制,只能移动。
      • std::shared_ptr 使用引用计数机制,表示多个指针可以共享同一个对象。
    • 普通指针:没有所有权的概念,需要程序员自己确保资源的正确释放。

  4. 异常安全

    • 智能指针:通常提供异常安全保证,即使在抛出异常时也能确保内存被释放。
    • 普通指针:如果异常发生时没有正确释放内存,可能会导致内存泄漏。
  5. 使用场景

    • 智能指针:用于管理动态分配的内存,避免手动管理内存的错误。
    • 普通指针:用于各种场景,包括但不限于指向静态分配、栈分配或动态分配的内存。
  6. 性能开销

    • 智能指针:有一定的性能开销,特别是在 std::shared_ptr 中,因为需要维护引用计数。
    • 普通指针:没有额外的性能开销。
  7. 兼容性

    • 智能指针:不能直接与 C 语言 API 一起使用,因为 C++ 的智能指针对于 C 语言来说是不透明的。
    • 普通指针:可以与 C 语言 API 无缝交互。
  8. 功能

    • 智能指针:除了基本的指针功能外,还提供了额外的功能,如自动内存管理、引用计数、自定义删除器等。
    • 普通指针:只有基本的指针功能。

线程之间的通信方式

  1. 互斥量(Mutex)

    • 用于保护共享数据,确保同一时间只有一个线程可以访问临界区。
  2. 锁(Locks)

    • 例如 std::lock_guardstd::unique_lock,它们使用 Mutex 来管理对共享资源的访问。
  3. 条件变量(Condition Variables)

    • 允许线程等待某些条件成立,当其他线程更改了这些条件时,条件变量可以被用来唤醒等待的线程。
  4. 线程安全队列

    • 一种线程安全的队列实现,可以在生产者-消费者场景中使用。
  5. 原子操作(Atomic Operations)

    • std::atomic 提供了一种机制,使得单个变量的操作是原子的,即不可中断的。
  6. 信号量(Semaphores)

    • 用于控制对共享资源的访问数量。
  7. 屏障(Barriers)

    • 确保所有线程都到达某个点后再继续执行。
  8. Future 和 Promise

    • std::future 和 std::promise 允许线程之间传递值或异常。
  9. 线程局部存储(Thread-Local Storage, TLS)

    • 使用 thread_local 关键字声明的变量,每个线程都有其独立的实例。
  10. 共享内存

    • 直接通过共享内存地址来通信,需要配合同步机制使用。
  11. 消息传递接口

    • 如 POSIX 消息队列,可以用来在不同线程或进程之间传递消息。
  12. 管道(Pipes)

    • 可以用于线程之间的简单通信。
  13. 套接字(Sockets)

    • 在分布式系统中,线程可以通过网络套接字进行通信。
  14. 文件映射(Memory-Mapped Files)

    • 允许多个线程通过文件系统访问同一块内存区域。