常见面试题答案


1. 介绍一下volatile关键字

参考

volatile是一个类型修饰符,用于告诉编译器对象的值可能会在编译器无法检测到的情况下被改变。


2. C++中的内联函数和宏的区别?

参考
  • 内联函数是编译器编译时函数调用替换为函数体代码的一种优化方式,它保持了函数调用的语法并进行了类型安全检查
  • 而宏是预处理器编译前预处理阶段对代码进行文本替换没有类型安全检查,且可能导致意外的副作用。

3. 自己实现一个堆栈 C++实现 + 模板

参考
#include <iostream>
#include <vector>
#include <stdexcept>

template <typename T>
class Stack {
private:
    std::vector<T> elems;   // 使用vector作为底层存储结构

public:
    // 判断栈是否为空
    bool isEmpty() const {
        return elems.empty();
    }

    // 获取栈大小
    size_t size() const {
        return elems.size();
    }

    // 入栈操作
    void push(const T& elem) {
        elems.push_back(elem);
    }

    // 出栈操作
    void pop() {
        if (isEmpty()) {
            throw std::out_of_range("Stack is empty!");
        }
        elems.pop_back();
    }

    // 获取栈顶元素
    T top() const {
        if (isEmpty()) {
            throw std::out_of_range("Stack is empty!");
        }
        return elems.back();
    }
};

4. 函数后面加const的作用是什么?

参考

在C++中,成员函数后面加上const关键字表示这个成员函数是一个常量成员函数,它不能修改调用它的对象的任何数据成员。


5. 纯虚函数和虚函数的区别?

参考

纯虚函数和虚函数的主要区别在于:

  • 纯虚函数在基类中没有默认实现,必须在派生类中实现,含有纯虚函数的类被称为抽象类,不能被实例化;
  • 而虚函数在基类中可以有默认实现,子类可以选择性地覆盖它们的实现,且含有虚函数的类可以被实例化

6. 抽象类和接口区别?

参考

在C++中,抽象类和接口的主要区别在于:

  • 抽象类是一个不能被实例化的类,通常包含至少一个纯虚函数,它可以包含普通成员变量和方法,以及构造和析构函数;
  • 而接口则更像是一个行为的规范或契约,它只声明方法但不实现,且其所有方法默认都是公开的,不包含成员变量和构造、析构函数。

C++的多继承是真的多继承,功能更强大,不过也更不好用。所以java等语言干脆不让继承多个基类了,只能继承一个基类,可以实现多个接口。


7. malloc和calloc的区别?

参考

malloccalloc都是C语言中用于动态内存分配的函数,但它们在使用和行为上有一些重要的区别。

  1. 参数的差异malloc函数接收一个参数,即要分配的字节数。而calloc函数接收两个参数,一个是元素的数量,另一个是每个元素的大小。calloc会计算总的字节数(元素数量*元素大小)并进行分配。
  2. 内存初始化的差异malloc只是简单地分配指定大小的内存,并不会初始化内存区域的内容,因此分配后的内存中的值是未知的(即它们包含的是内存中的随机值)。相反,calloc在分配内存后会自动将内存区域初始化为0。
  3. 速度的差异:由于malloc不需要初始化内存,所以它通常比calloc快一些。但是,如果你需要分配的内存区域必须被初始化为0,那么使用calloc可能会更方便,因为你不需要再手动将内存区域设置为0。然而,这种速度差异在现代计算机和编译器优化下可能并不明显。

这是两个函数的基本原型:

void *malloc(size_t size);
void *calloc(size_t num, size_t size);

在使用这两个函数时,都需要包含头文件<stdlib.h>,并且在使用完分配的内存后,应使用free()函数释放内存,以防止内存泄漏。


8. C++中的default关键字

参考

在C++中,default关键字主要用于类的特殊成员函数,如构造函数、析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符的显式默认实现,表示编译器应该为这个成员函数生成默认的实现。此外,default也可以用在switch语句中,表示当所有case都不匹配时的默认行为。


9. lock_guard和unique_guard的区别?

参考

C++11中的lock_guardunique_lock都是用于管理互斥锁(mutex)的类,但lock_guard在构造时自动加锁、析构时自动解锁,简单安全;而unique_lock提供了更灵活的锁控制,可以手动加锁解锁,且能配合条件变量使用,但相对复杂且效率稍低。


10. 介绍一下C++中的多态

参考

C++中的多态是指允许一个接口或父类引用/指针调用在其子类中重写的方法的能力,从而实现运行时行为的动态绑定


11. C语言中,static函数与普通函数的区别

参考

C语言中,static函数与普通函数的主要区别在于:

  • static函数的作用域被限制在其定义的源文件内,具有内部链接属性
  • 而普通函数可以在多个源文件间共享,具有外部链接属性

在C语言中,static关键字可以用于修饰函数,这种被static修饰的函数称为静态函数。静态函数与普通函数之间有一些重要的区别,主要体现在其作用域和链接属性上。

  1. 作用域

    • 普通函数:普通函数的作用域通常是从其定义开始到文件结束。如果函数在一个源文件中定义,并且在另一个源文件中使用,那么通常需要通过头文件进行声明。
    • 静态函数:静态函数的作用域被限制在其定义的源文件内。这意味着,即使其他源文件包含了该静态函数的声明(假设通过某种方式获得了声明),它们也不能链接到这个静态函数。静态函数对于其定义的文件是私有的。
  2. 链接属性

    • 普通函数:普通函数具有外部链接属性,这意味着它们可以在多个源文件之间共享。只要在其他源文件中正确声明了这些函数,就可以调用它们。
    • 静态函数:静态函数具有内部链接属性,也就是说,它们只能在定义它们的源文件内部被调用。这种特性使得静态函数非常适合用于实现一些辅助函数或工具函数,这些函数只需要在单个文件内部使用,而不需要暴露给其他文件。
  3. 命名冲突

    • 由于静态函数的作用域仅限于其定义的源文件,因此在不同的源文件中可以定义同名的静态函数,而不会发生命名冲突。每个源文件中的静态函数都是独立的。
    • 相反,普通函数如果在多个源文件中定义且没有使用static修饰,那么会导致链接错误,因为链接器会发现多个相同的函数定义。
  4. 用途

    • 静态函数通常用于封装和模块化设计,它们隐藏了实现细节,只向外部提供必要的接口。这有助于减少全局命名空间的污染,并提高代码的可维护性和可读性。
    • 普通函数则更常用于那些需要在多个源文件之间共享和调用的功能。

下面是一个简单的示例来说明静态函数和普通函数的区别:

// file1.c
#include <stdio.h>

static void static_function() {
    printf("This is a static function in file1.c\n");
}

void regular_function() {
    printf("This is a regular function in file1.c\n");
}

// file2.c
#include <stdio.h>

extern void regular_function(); // 可以声明并调用file1.c中的普通函数
// extern void static_function(); // 错误!不能声明并调用file1.c中的静态函数

int main() {
    regular_function(); // 正确调用
    // static_function(); // 错误!无法调用file1.c中的静态函数
    return 0;
}

12. C++类中静态常量的初始化

参考

在C++中,类的静态常量成员需要在类外部进行定义和初始化。这通常是在类的实现文件(通常是.cpp文件)中完成的。

下面是一个简单的示例,展示了如何在类中声明静态常量成员,并在类外部进行定义和初始化:

// MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
    // 声明静态常量成员
    static const int kStaticConst;
};

#endif // MYCLASS_H

// MyClass.cpp
#include "MyClass.h"

// 定义和初始化静态常量成员
const int MyClass::kStaticConst = 42;

在这个示例中,MyClass 类声明了一个静态常量成员 kStaticConst。在 MyClass.cpp 文件中,我们使用 const int MyClass::kStaticConst = 42; 来定义和初始化这个静态常量成员。

注意,静态常量成员在类外部进行定义和初始化时,需要使用类名和作用域解析运算符 :: 来指定它们所属的类。此外,由于这是一个常量,所以我们必须使用 const 关键字进行定义,并确保其值在编译时是已知的。

另外,从C++11开始,如果静态常量成员是整型或枚举类型,并且其值在编译时是已知的,那么你也可以在类内部进行初始化:

// MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
    // 在类内部初始化静态常量成员(C++11及更高版本)
    static const int kStaticConst = 42;
};

#endif // MYCLASS_H

在这种情况下,你不需要在类外部进行额外的定义和初始化。但是,如果你的静态常量成员不是整型或枚举类型,或者其值在编译时不是已知的,那么你还是需要在类外部进行定义和初始化。


13. C++类中静态成员函数

参考

C++类中的静态成员函数是一种不依赖于类实例、可直接通过类名调用的成员函数,它只能访问类的静态成员。


14. 父类指针如何调用子类的虚函数

参考

基类如果存在虚函数,那么在子类对象中,除了成员函数与成员变量外,编译器会自动生成一个指向该类的虚函数表(这里是类Derived)的指针,叫作虚函数表指针。通过虚函数表指针,父类指针即可调用该虚函数表中所有的虚函数


15. 构造函数能不能为虚函数?

参考

构造函数不能为虚函数。

  • 虚函数的实现是通过虚表(vtable)管理被重写了的函数,通过虚表指针(vptr)指向虚表。
  • 虚表指针是与对象相联系的,只有创建了对象,才产生虚表指针。
  • 虚表指针位于对象的第一个指针长度的位置,它会根据实际情况指向对象类型对应的虚表。
  • 构造函数调用结束之前,对象还未产生,此时的对象是不完整的,虚表指针未必生成,多态机制无法使用,因此不能在构造函数结束之前调用虚函数。

16. 析构函数能不能为虚函数?

参考

析构函数可以为虚函数
当基类中存在虚函数时,基类的析构函数要写成虚函数

  • 如果基类的析构函数不写成虚函数时,使用父类指针delete,会调用不到基类的析构函数,基类内存未释放,产生内存泄漏
  • 析构函数不能写成纯虚函数,析构函数是虚函数意味着在delete时调用基类的析构,而纯虚的析构函数没有实现体,因此不能调用

17. 什么是虚函数表、虚函数表指针

参考

具有虚函数的类及其派生的类会在编译时创建虚函数表,简称虚表(vtbl),虚表是虚函数指针的数组。 具有虚函数的类对象有一个虚表指针(vfptr),是编译器生成的指针,在对象构造时初始化。虚表指针vfptr指向虚表的第一个虚函数指针(即vfptr的值是虚表第一个虚函数指针的地址)。


posted @   guanyubo  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示