【面经】C++八股文(地平线C++一面)

一、C++11的新特性都有哪些?

1.1 自动类型推断 (auto)

auto 关键字允许编译器自动推断变量的类型,从而简化代码的书写。

cpp 复制代码
auto num = 5;        // int
auto pi = 3.14;     // double
auto str = "Hello"; // const char*

1.2 范围 for 循环

范围 for 循环可以简化容器(如数组和 STL 容器)的遍历。

cpp 复制代码
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto& i : vec) {
    std::cout << i << " "; // 输出 1 2 3 4 5
}

1.3 Lambda 表达式和函数闭包

Lambda 表达式提供了一种简洁的方式来定义匿名函数,并允许捕获外部变量。

cpp 复制代码
int x = 10;
auto lambda = [x](int y) { return x + y; };
std::cout << lambda(5); // 输出 15

1.4 右值引用和移动语义

右值引用允许对临时对象进行高效的资源管理,移动语义使得资源的转移比复制更高效。

cpp 复制代码
#include <utility>

class MyClass {
public:
    MyClass() { /* 资源分配 */ }
    MyClass(MyClass&& other) { /* 资源转移 */ }
};

MyClass createMyClass() {
    return MyClass(); // 返回一个右值
}

MyClass obj = createMyClass(); // 通过移动构造

1.5 可变参数模板

可变参数模板允许函数接受任意数量的参数。

cpp 复制代码
template<typename... Args>
void printAll(Args... args) {
    (std::cout << ... << args) << std::endl; // C++17 折叠表达式
}
printAll(1, 2, 3.5, "Hello"); // 输出: 1 2 3.5 Hello

1.6 初始化列表

初始化列表提供了一种更方便的方式来初始化容器和自定义类型。

cpp 复制代码
std::vector<int> vec = {1, 2, 3, 4}; // 列表初始化
std::array<int, 3> arr = {1, 2, 3};   // std::array 初始化

1.7 强类型枚举

强类型枚举(enum class)提供了更强的类型安全性和作用域。

cpp 复制代码
enum class Color { Red, Green, Blue };
Color c = Color::Red;
// c = 1; // 错误,不能隐式转换

1.8 智能指针如 std::unique_ptrstd::shared_ptr

智能指针帮助管理动态分配的内存,避免内存泄漏。

cpp 复制代码
#include <memory>

std::unique_ptr<int> p1 = std::make_unique<int>(5); // 独占型智能指针
std::shared_ptr<int> p2 = std::make_shared<int>(10); // 共享型智能指针

1.9 空指针关键字 (nullptr)

nullptr 是一个类型安全的空指针常量,取代了 NULL

cpp 复制代码
int* p = nullptr; // p 是一个空指针

1.10 线程库支持

C++11 引入了 <thread> 头文件,使得多线程编程变得更加简单。

cpp 复制代码
#include <thread>

void threadFunction() {
    std::cout << "Hello from thread!" << std::endl;
}

std::thread t(threadFunction);
t.join(); // 等待线程结束

1.11 新容器如 std::arraystd::unordered_map

C++11 引入了一些新的标准容器。

  • std::array 是一个固定大小的数组,可以在编译时确定大小。
  • std::unordered_map 是一个基于哈希表的关联容器,提供常量时间复杂度的插入和查找。
cpp 复制代码
#include <array>
#include <unordered_map>

std::array<int, 5> arr = {1, 2, 3, 4, 5};
std::unordered_map<std::string, int> umap = {{"one", 1}, {"two", 2}};

这些特性极大地增强了 C++ 的功能和可用性,使得编写现代 C++ 代码更加简洁、易于维护和高效。

二、了解什么设计模式?单例了解吗?

单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实
例。

在C++中实现单例模式通常需要以下步骤:

私有化构造函数和析构函数:这样可以防止外部代码直接创建或销毁该类的实例。

提供一个私有的静态指针变量:该指针用于存储唯一实例的地址。

提供一个公共的静态方法:这个方法用于返回唯一实例,并确保实例在第一次调用时被创建。

cpp 复制代码
#include <iostream>
#include <mutex>

class Singleton {
private:
    // 私有构造函数
    Singleton() {
        std::cout << "Singleton instance created!" << std::endl;
    }
    
    // 私有析构函数
    ~Singleton() {
        std::cout << "Singleton instance destroyed!" << std::endl;
    }

    // 私有静态指针,指向唯一实例
    static Singleton* instance;
    static std::mutex mutex_; // 线程安全的互斥锁

public:
    // 禁止拷贝构造和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 公共静态方法获取唯一实例
    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mutex_); // 确保线程安全
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }
};

// 初始化静态指针
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;

int main() {
    Singleton* singleton1 = Singleton::getInstance();
    Singleton* singleton2 = Singleton::getInstance();

    // 验证两者是同一个实例
    if (singleton1 == singleton2) {
        std::cout << "Both are the same instance." << std::endl;
    }

    return 0;
}

三、TCP和UDP的区别?

3.1 连接

  • TCP(传输控制协议)

    • **TCP 是面向连接的协议,这意味着在实际数据传输之前,必须先建立一个连接。**这是通过三次握手(Three-Way Handshake)完成的,确保双方都准备好进行通信。
    • 三次握手的过程如下:
      1. 客户端发送一个 SYN(同步)包到服务器。
      2. 服务器回应一个 SYN-ACK(同步-确认)包。
      3. 客户端再发送一个 ACK(确认)包来确认连接建立。
  • UDP(用户数据报协议)

    • **UDP 是无连接的协议,发送方和接收方之间不需要建立连接。**数据可以立即发送,适合需要快速传输且对连接不敏感的应用。

3.2 可靠性

  • TCP

    • **TCP 提供可靠的数据传输,确保数据的完整性和按顺序到达。**它使用序列号来跟踪每个字节的发送和接收,采用重传机制来处理丢失的数据包,并通过校验和来检测数据错误。
  • UDP

    • **UDP 不提供可靠性保证,数据包可能会丢失、重复或乱序到达。**由于没有重传机制和流控制,UDP 适用于对实时性要求高的应用,比如视频会议、在线游戏等。

3.3 速度

  • TCP

    • **因为 TCP 需要进行连接建立、数据确认、错误检测和重传等操作,所以相对较慢。**它适合需要高可靠性的数据传输,像文件传输、电子邮件等。
  • UDP

    • **UDP 传输速度较快,因为它没有连接建立过程和确认机制,适用于速度要求高且可以容忍数据丢失的场景,**例如 DNS 查询、实时视频和音频传输。

3.4 数据流

  • TCP

    • **TCP 是面向字节流的协议,它将数据视为一个连续的字节流。**应用程序发送的数据会被分割成多个数据包,接收方再将这些数据包重新组装成完整的数据流。
  • UDP

    • **UDP 是面向消息的协议,每个数据包(称为数据报)都是独立的。**数据报的大小有限,UDP 不会将多个数据报组合成一个流,需要应用程序自己处理消息的边界。

四、左值和右值引用?

左值引用是对可寻址的、可重复使用的对象 (左值)的引用。它使用传统的单个&符号

右值引用是对临时对象(右值) 的引用,使用双&&符号。右值引用允许实现移动语义和完美转发,它可以将资源从一个(临时的)对象转移到另一个对象,提高效率,避免不必要的复制。

五、能说说static关键字吗?

5.1 static关键字有几个用途:

  1. 当在类的数据成员前使用 static 时,表示该成员属于类本身,而不是任何特定的实例。所有实例共享这一成员。
cpp 复制代码
class MyClass {
public:
    static int count; // 静态数据成员
    MyClass() {
        count++; // 每创建一个实例,count 增加
    }
};

// 静态成员定义和初始化
int MyClass::count = 0;

int main() {
    MyClass obj1;
    MyClass obj2;
    std::cout << MyClass::count; // 输出 2,表明创建了两个实例
    return 0;
}

2.当在类的成员函数前使用 static 时,可以在不创建类实例的情况下直接调用该函数。这通常用于那些不依赖于类的实例的功能。

cpp 复制代码
class MyClass {
public:
    static void display() {
        std::cout << "Hello from static function!" << std::endl;
    }
};

int main() {
    MyClass::display(); // 直接调用静态成员函数
    return 0;
}

3.在函数内部使用static定义局部变量,使得该变量的值在函数调用间持久存在,而不是每次调

用时都重新创建。

cpp 复制代码
#include <iostream>

void counter() {
    static int count = 0; // 静态局部变量
    count++;
    std::cout << "Count: " << count << std::endl; // 每次调用显示当前计数
}

int main() {
    counter(); // 输出 Count: 1
    counter(); // 输出 Count: 2
    counter(); // 输出 Count: 3
    return 0;
}

4.在文件、函数或区域前使用static,可以限制该变量或函数的链接可见性,令其只在定义它的

文件或作用域内部可见。

cpp 复制代码
// 文件 A.cpp
static void helper() {
    std::cout << "This is a static function." << std::endl;
}

void publicFunction() {
    helper(); // 可以调用
}

// 文件 B.cpp
// void anotherFunction() {
//     helper(); // 错误,无法访问 A.cpp 中的 static 函数
// }

2. static 关键字在 C++ 中具有多种用途:

用于定义类的静态数据成员和静态成员函数。

用于保持局部变量在函数调用之间的状态。

用于限制变量和函数的可见性,确保它们仅在定义它们的文件或作用域内可见。

六、进程间通信的方式有哪些?线程间的数据交互?

6.1 进程间通信方式包括:

1.管道(Pipes)

2.命名管道(FIFO)

3.信号(Signals】

4.消息队列(Message Queues)

5.共享内存(Shared Memory)

6.信号量(Semaphores)

7.套接字(Sockets)】

8.内存映射文件(Memory-mapped files)

6.2 线程间的数据交互可以通过:

1.锁(如互斥锁Mutex)

2.条件变量(Condition Variables)

3.信号量(Semaphores)

4.线程局部存储(Thread-local Storage,TLS)

5.全局变量(通过使用同步机制以避免并发访问问题)

七、用过什么容器,map和unordered_map了解吗?vector的底层原理是什么?vector的扩容机制了解多少?map的底层原理能说说吗?

7.1 std::map

  • 底层实现std::map 是基于红黑树实现的,这是一种自平衡的二叉搜索树。
  • 特性
    • 元素根据键值自动排序。
    • 插入、删除和查找操作的时间复杂度为 O(log n)。

7.2 std::unordered_map

  • 底层实现std::unordered_map 采用哈希表作为底层实现。
  • 特性
    • 元素不会自动排序,存储顺序取决于哈希值。
    • 平均情况下,插入、删除和查找操作的时间复杂度为 O(1),但在最坏情况下(如哈希冲突严重时)可能会达到 O(n)。

7.3 std::vector

1. 底层原理

  • std::vector 是基于动态数组实现的,允许随机访问。
  • 它在连续的内存空间中存储元素,这使得访问速度非常快(O(1) 时间复杂度)。

2. 扩容机制

  • **当向 vector 添加元素超过当前容量时,会创建一个更大的动态数组(通常是当前容量的两倍),并将现有元素复制到新数组中,然后释放旧数组的内存。**这种扩容策略有助于减少频繁内存分配的开销,从而提高性能。

7.4 std::map 的底层原理

  • std::map 是基于红黑树实现的,自平衡的特性帮助确保在最坏情况下也能保持 O(log n) 的时间复杂度。
  • 红黑树的特点包括:
    • 每个节点都有一个颜色(红色或黑色),并遵循特定规则以确保树的高度保持在对数级别。
    • 通过键值对自动排序,保证了数据结构的稳定性和高效操作。

八、std::move??

std::move 是 C++11 中引入的一个标准库函数,其主要作用是将其参数转换为右值引用。这样做可以让程序利用移动语义,以高效地转移资源而不进行不必要的复制。

九、C++转型操作符?

9.1 static_cast

  • 用途用于基本数据类型转换和安全的向上转型。
  • 特点
    • 可以转换内置类型(如 intfloat)。
    • 支持向上转型,即将派生类的对象或指针转换为基类。
    • 不能进行不安全的向下转型,如果需要向下转型,则需要使用 dynamic_cast

9.2 dynamic_cast

  • 用途用于类层次结构中的安全向下转型及运行时类型检查。
  • 特点
    • 需要 RTTI(运行时类型信息)支持。
    • 当进行向下转型时,可以确保转换的安全性。如果转换不成功,dynamic_cast 会返回 nullptr(对于指针类型)或抛出异常(对于引用类型)。
    • 常用于多态类,确保在运行时检查对象的真实类型。

9.3 const_cast

  • 用途用于移除对象的 constvolatile 属性。
  • 特点
    • 允许对常量对象进行修改(需谨慎使用,因为这可能导致未定义行为)。
    • 主要用于在需要非常量引用或指针的情况下,如与某些 API 交互时。

9.4 reinterpret_cast

  • 用途用于低级别的类型转换,重新解释底层位模式。
  • 特点
    • 可以将一个指针转换为任何其他类型的指针。
    • 这种转换是非常不安全的,因为它不进行任何类型检查,可能导致未定义行为。
    • 通常用于特定的低级编程场景,如系统编程和硬件交互。

十、智能指针了解吗?weak_ptr?

智能指针是 C++11 引入的一个重要概念,主要用于管理动态分配的内存,避免手动管理内存带来的问题。std::weak_ptr 是智能指针中的一种,专门用于解决循环引用的问题

10.1std::weak_ptr 详细说明

  1. 定义

    std::weak_ptr 是一种智能指针,用于观察由 std::shared_ptr 管理的对象。它并不拥有该对象,因此不会增加对象的引用计数。

  2. 目的

    主要用于解决循环引用问题。例如,当两个对象相互持有 shared_ptr,如果使用 shared_ptr,这会导致引用计数永远不为零,从而造成内存泄漏。使用 weak_ptr 可以避免这种情况,因为它不会影响引用计数。

  3. 使用方式

    当需要访问 weak_ptr 指向的对象时,可以调用 lock() 方法,该方法会尝试返回一个 shared_ptr

(1)如果原始的 shared_ptr 仍然存在,lock() 将返回一个有效的 shared_ptr,并增加引用计数。

(2)如果原始的 shared_ptr 已经被销毁,lock() 将返回一个空的 shared_ptr

10.2 示例代码:

cpp 复制代码
#include <iostream>
#include <memory>

class Node {
public:
    Node(int value) : value(value) {
        std::cout << "Node " << value << " created.\n";
    }
    ~Node() {
        std::cout << "Node " << value << " destroyed.\n";
    }
    int value;
    std::shared_ptr<Node> next;
};

int main() {
    std::shared_ptr<Node> node1 = std::make_shared<Node>(1);
    std::shared_ptr<Node> node2 = std::make_shared<Node>(2);
    
    // 产生循环引用
    node1->next = node2;
    node2->next = node1;

    // 使用 weak_ptr 解决循环引用
    std::weak_ptr<Node> weakNode1 = node1;
    std::weak_ptr<Node> weakNode2 = node2;

    // 检查 weak_ptr 是否有效
    if (auto shared1 = weakNode1.lock()) {
        std::cout << "Weak pointer is valid, value: " << shared1->value << "\n";
    }

    if (auto shared2 = weakNode2.lock()) {
        std::cout << "Weak pointer is valid, value: " << shared2->value << "\n";
    }

    // 让 node1 和 node2 超出作用域
    node1.reset();
    node2.reset(); // 这时 node1 和 node2 被销毁

    // 检查 weak_ptr 是否有效
    if (auto shared1 = weakNode1.lock()) {
        std::cout << "Weak pointer is valid, value: " << shared1->value << "\n";
    } else {
        std::cout << "Weak pointer is no longer valid.\n";
    }

    return 0;
}
相关推荐
迷迭所归处15 分钟前
C++ —— 关于vector
开发语言·c++·算法
架构文摘JGWZ43 分钟前
Java 23 的12 个新特性!!
java·开发语言·学习
leon62544 分钟前
优化算法(一)—遗传算法(Genetic Algorithm)附MATLAB程序
开发语言·算法·matlab
CV工程师小林44 分钟前
【算法】BFS 系列之边权为 1 的最短路问题
数据结构·c++·算法·leetcode·宽度优先
Navigator_Z1 小时前
数据结构C //线性表(链表)ADT结构及相关函数
c语言·数据结构·算法·链表
Aic山鱼1 小时前
【如何高效学习数据结构:构建编程的坚实基石】
数据结构·学习·算法
white__ice2 小时前
2024.9.19
c++
天玑y2 小时前
算法设计与分析(背包问题
c++·经验分享·笔记·学习·算法·leetcode·蓝桥杯
锦亦之22332 小时前
QT+OSG+OSG-earth如何在窗口显示一个地球
开发语言·qt
我是苏苏2 小时前
Web开发:ABP框架2——入门级别的增删改查Demo
java·开发语言