字节跳动C++开发一面

1. C++中的智能指针有哪些?它们的区别和使用场景是什么?

答案:

三种智能指针:

1. unique_ptr(独占所有权)

  • 独占资源,不可复制,只能移动
  • 开销最小,性能最好
  • 使用场景:明确单一所有者的资源管理
std::unique_ptr<int> p1(new int(10));
std::unique_ptr<int> p2 = std::move(p1);  // 转移所有权

2. shared_ptr(共享所有权)

  • 引用计数,多个指针共享同一资源
  • 最后一个指针销毁时释放资源
  • 线程安全(引用计数操作)
  • 使用场景:多个对象共享资源
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::shared_ptr<int> p2 = p1;  // 引用计数+1

3. weak_ptr(弱引用)

  • 不增加引用计数,不影响对象生命周期
  • 解决shared_ptr循环引用问题
  • 使用前需要lock()转换为shared_ptr
std::weak_ptr<int> wp = sp;
if(auto p = wp.lock()) {  // 检查对象是否存在
    // 使用p
}

对比表:

所有权

独占

共享

不拥有

可复制

引用计数

不增加计数

开销

最小

较大

2. 虚函数的实现原理是什么?虚函数表在内存中如何布局?

答案:

实现原理:

  • 每个包含虚函数的类都有一个虚函数表(vtable)
  • 每个对象有一个**虚函数指针(vptr)**指向虚函数表
  • 通过vptr查表实现动态绑定

内存布局:

class Base {
    int a;
    virtual void func1() {}
    virtual void func2() {}
};
// 对象内存:[vptr][a]
// vtable: [&Base::func1][&Base::func2]

class Derived : public Base {
    int b;
    void func1() override {}  // 覆盖
};
// 对象内存:[vptr][a][b]
// vtable: [&Derived::func1][&Base::func2]

关键点:

  • vptr通常位于对象内存的开头
  • 虚函数表在编译期生成,存储在只读数据段
  • 构造函数中会设置vptr
  • 多继承时可能有多个vptr

性能影响:

  • 额外的指针开销(8字节)
  • 函数调用需要两次间接寻址
  • 无法内联优化

3. 左值和右值的区别?什么是移动语义?

答案:

左值(lvalue)vs 右值(rvalue):

  • 左值:有名字,可取地址,持久存在 变量、数组元素、返回左值引用的函数
  • 右值:临时对象,不可取地址,即将销毁 字面量、临时对象、返回值
int a = 10;      // a是左值,10是右值
int b = a + 1;   // b是左值,a+1是右值

移动语义(Move Semantics):

  • C++11引入,避免不必要的拷贝
  • 通过"窃取"资源而非复制来转移所有权
  • 使用右值引用&&实现

移动构造函数:

class String {
    char* data;
public:
    // 拷贝构造(深拷贝)
    String(const String& s) {
        data = new char[strlen(s.data) + 1];
        strcpy(data, s.data);
    }
    
    // 移动构造(转移所有权)
    String(String&& s) noexcept {
        data = s.data;      // 窃取资源
        s.data = nullptr;   // 置空源对象
    }
};

std::move的作用:

  • 将左值转换为右值引用
  • 不移动任何东西,只是类型转换
String s1("hello");
String s2 = std::move(s1);  // 调用移动构造

使用场景:

  • 容器元素的插入/删除
  • 返回局部对象
  • 资源管理类(unique_ptr)

4. const关键字的作用?const成员函数能修改成员变量吗?

答案:

const的多种用法:

1. 修饰变量

const int a = 10;        // 常量
int const b = 20;        // 同上
const int* p1;           // 指向常量的指针(不能改*p1)
int* const p2;           // 常量指针(不能改p2)
const int* const p3;     // 都不能改

2. 修饰函数参数

void func(const string& s);  // 避免拷贝,防止修改

3. 修饰成员函数

class A {
    int value;
public:
    int getValue() const {  // 承诺不修改成员变量
        return value;
    }
};

const成员函数能否修改成员变量?

一般情况:不能

void func() const {
    value = 10;  // 编译错误
}

例外:mutable关键字

class Cache {
    mutable int access_count;  // 可在const函数中修改
public:
    int getData() const {
        access_count++;  // 允许
        return data;
    }
};

const对象只能调用const成员函数:

const A obj;
obj.getValue();   // OK
obj.setValue(5);  // 错误(如果setValue不是const)

5. 什么是RAII?如何实现一个线程安全的单例模式?

答案:

RAII(Resource Acquisition Is Initialization):

  • 资源获取即初始化
  • 利用对象生命周期管理资源
  • 构造函数获取资源,析构函数释放资源

典型应用:

// 智能指针
std::unique_ptr<int> p(new int(10));  // 自动释放

// 互斥锁
std::lock_guard<std::mutex> lock(mtx);  // 自动解锁

// 文件操作
std::ifstream file("data.txt");  // 自动关闭

线程安全的单例模式:

方法1:C++11局部静态变量(推荐)

class Singleton {
private:
    Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    
public:
    static Singleton& getInstance() {
        static Singleton instance;  // C++11保证线程安全
        return instance;
    }
};

方法2:双重检查锁(DCLP)

class Singleton {
private:
    static std::atomic<Singleton*> instance;
    static std::mutex mtx;
    
public:
    static Singleton* getInstance() {
        Singleton* tmp = instance.load(std::memory_order_acquire);
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            tmp = instance.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton();
                instance.store(tmp, std::memory_order_release);
            }
        }
        return tmp;
    }
};

为什么C++11局部静态变量是线程安全的?

  • 编译器保证初始化时的互斥
  • 只初始化一次
  • 简洁高效

6. vector和list的区别?什么时候用vector,什么时候用list?

答案:

底层实现:

  • vector:动态数组,连续内存
  • list:双向链表,非连续内存

性能对比:

随机访问

O(1)

O(n)

头部插入

O(n)

O(1)

尾部插入

O(1)均摊

O(1)

中间插入

O(n)

O(1)

内存占用

大(额外指

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++八股文全集 文章被收录于专栏

本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。

全部评论
c++和java面向对象差不多
点赞 回复 分享
发布于 昨天 22:49 河南

相关推荐

评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务