1. 不能重载的运算符

在 C++ 里,并非所有运算符都能被重载,不能重载的运算符及其原因如下:

  • .(成员访问运算符):用于访问对象的成员,若重载可能导致对象成员访问方式混乱,破坏封装性,编译器也无法确保访问的正确性。
  • .*(成员指针访问运算符):用于通过成员指针访问对象成员,逻辑复杂且与对象内存布局紧密相关,重载可能引发内存访问错误。
  • ::(作用域运算符):用于区分不同作用域的标识符(如类、命名空间),其功能是编译器的作用域解析,不涉及运行时操作,重载无实际意义。
  • ? :(条件运算符):是三目运算符,语法结构特殊(需要三个操作数),且运算逻辑与流程控制相关,重载会导致语法解析困难,破坏代码可读性。
  • sizeof(长度运算符):用于计算类型或变量的大小,其结果在编译期确定,与具体对象无关,重载会改变基本类型长度的计算规则,导致系统级错误。
  • typeid(类型识别运算符):用于获取变量或表达式的类型信息,是 C++ 运行时类型识别(RTTI)的核心机制,重载会破坏类型系统的一致性。

2. 函数重载判断依据

在 C++ 中,能作为函数重载判断依据的有:

  • 参数类型:例如void func(int x)void func(double x),因参数类型不同可构成重载。
  • const:例如void func(int* ptr)void func(const int* ptr),因const修饰符不同可构成重载。
  • 参数个数:例如void func(int x)void func(int x, int y),因参数个数不同可构成重载。
    而返回类型不能作为函数重载的判断依据,因为仅返回类型不同的函数,编译器无法在调用时区分。

3. list 删除元素与迭代器失效

在 C++ 中,list删除任意一个元素不会导致其他迭代器失效。因为list是双向链表结构,被删除元素两边的元素的迭代器指针会自动更新,其他迭代器不受影响。

4. C++ 中的类型转换

  • static_cast(基础转换):用于基础类型之间的转换,如double d = static_cast<double>(42);(整数转双精度浮点数)。
  • dynamic_cast(多态下行转换):用于多态场景下的下行转换,需类有虚函数,例如:
    class Base { virtual void f() {} }; 
    class Derived : public Base {}; 
    Base* b = new Derived; 
    Derived* d = dynamic_cast<Derived*>(b);
    
  • const_cast(去除const:用于去除指针或引用的const属性,例如const int* cptr = new int(10); int* ptr = const_cast<int*>(cptr);
  • reinterpret_cast(底层二进制转换):用于底层二进制位的重新解释转换,例如int* p = new int(5); long addr = reinterpret_cast<long>(p);(指针转数值表示存储地址)。

5. 引用的本质

引用的本质是对指针的封装,底层是通过const的指针实现的,因此本身引用具有const语义,不能重新绑定。引用本质如int &ref = a;等价于int * const ptr = &a;

6. 非静态成员函数与this指针

在 C++ 中每个非静态成员函数都隐含一个指向当前对象的this指针,其类型由成员函数是否为const决定。非const成员函数的this指针类型为类类型* const,可以修改类的成员变量;而const成员函数的this指针类型为const 类类型* const,不能修改非mutable修饰的类内变量,只能修改用mutable修饰的变量(const必须放在函数的后面,且只有类内的函数可以有这个const)。例如:

class MyClass {
private:
    int a; // 非mutable成员(受const函数限制)
    mutable int b; // mutable成员(不受const函数限制)
public:
    MyClass(int a_, int b_) : a(a_), b(b_) {}
    // 非const成员函数,this指针类型为 MyClass* const
    void modifyNonConst() {
        a = 100; // 合法,通过非const this修改非mutable成员
        b = 200; // 合法,修改mutable成员
    }
    // const成员函数,this指针类型为 const MyClass* const
    void modifyConst() const {
        // a = 300; // 非法,const this不能修改非mutable成员
        b = 400; // 合法,mutable成员不受const限制
    }
    void print() const {
        std::cout << "a = " << a << ", b = " << b << std::endl;
    }
};

7. 万能引用与完美转发

万能引用是指用&&从模板参数推导T&&创造的右值引用或左值引用的类型。当使用完美转发时,T&&会折叠为左值引用(当传入左值时)或右值引用(当传入右值时)。例如:

#include <iostream>
using namespace std;
// 模板函数中的万能引用(能推导的类型)
template <typename T>
void func(T&& x) { // x 是万能引用
    cout << "确定成功" << endl;
}
int main() {
    int a = 30;
    func(a); // 万能引用绑定左值a,正确
    func(30); // 万能引用绑定右值30(临时对象),正确
    func(a + 7); // 万能引用绑定右值(表达式结果是临时对象),正确
    return 0;
}

8. 类的大小计算

class A为例,在 64 位机中,类的大小计算需考虑成员变量的大小、内存对齐以及是否有虚函数(虚函数会带来虚函数表指针,占 8 字节)。

  • 不含virtual的类
    • 类中成员变量按声明顺序,遵循内存对齐规则排列,填充字节用于满足对齐要求。静态成员变量和非虚成员函数不占类的实例内存。
    • 以示例类为例,int64_t A占 8 字节(地址 0 - 7),short B占 2 字节(地址 8 - 9),char C占 1 字节(地址 10),之后为满足void* p(占 8 字节,需 8 字节对齐),填充 5 字节(地址 11 - 15),void* p占 8 字节(地址 16 - 23),最终类大小需根据各成员及填充情况,按最大对齐数(此处为 8)调整为整数倍。
  • virtual的类
    • 会额外增加一个vptr(虚函数表指针,8 字节),通常位于类内存最开始位置。
    • 同样按成员声明顺序和内存对齐规则计算,vptr占 8 字节(地址 0 - 7),之后int64_t A占 8 字节(地址 8 - 15),short B占 2 字节(地址 16 - 17),char C占 1 字节(地址 18),为满足void* p(8 字节对齐),填充 5 字节(地址 19 - 23),void* p占 8 字节(地址 24 - 31),总大小按最大对齐数(8)调整,最终为 32 字节。

9. 多线程相关说法判断

下面的说法是否正确以及为什么?

1.使用condition_variable必须搭配互斥锁来保持临界区,通常配合lock_guard

2.当线程被notifiy_one或者notify_all唤醒,会立即重新获取锁,且wait的解锁、阻塞、唤醒、加锁是原子性的

3.调用wait(lock,predicate)时,必须先持有锁。该函数会检查predicate的条件,日光返回true则继续执行,否则就释放锁并阻塞线程直到被唤醒。

4.wait函数底层实现是一个if

  • 错误。condition_variable 需搭配 unique_lock<mutex>,而非 lock_guard。因 wait 需临时释放锁再重新获取,lock_guard 不支持手动解锁 / 加锁,无法满足需求。

  • 正确。wait 的 “解锁→阻塞” 是原子操作,避免通知丢失;被唤醒后会立即重新获取锁,保证线程安全。

  • 正确。调用 wait(lock,predicate) 需先持有锁,若条件为 true 则继续;否则释放锁并阻塞,直到被唤醒后重新检查。

  • 错误。wait 底层用 while 循环而非 if,以处理虚假唤醒,确保条件满足才继续执行。例如:

  • while (!predicate()) {
        cv.wait(lock);
    }
    

16. 模板的定义位置

在 C++ 中,模板的定义通常需要放在头文件中,而非源文件中。因为模板是在编译期根据使用的类型进行实例化的,若放在源文件中,其他源文件无法看到模板的完整定义,会导致链接错误。例如将模板add的定义放在头文件template_example.h中:

// template_example.h
#ifndef TEMPLATE_EXAMPLE_H
#define TEMPLATE_EXAMPLE_H
template <typename T>
T add(T a, T b) {
    return a + b;
}
#endif

main.cpp中包含该头文件并实例化模板:

#include "template_example.h"
#include <iostream>
int main() {
    std::cout << add(2, 3) << std::endl; // 实例化int版本
    std::cout << add(2.5, 3.5) << std::endl; // 实例化double版本
    return 0;
}

17. vectorreserveresize

  • reserve(100)的作用reserve(n)的功能是预先分配至少能容纳n个元素的内存空间,执行后v.capacity()的值必然≥100(若原容量小于 100,则扩容至 100;若原容量已≥100,则不改变)。
  • resize(50)的作用resize(n)的功能是调整容器中实际元素的数量为n(若n小于当前元素数,则删除多余元素;若n大于当前元素数,则添加默认构造的元素)。但resize不会改变容器的容量(capacity),更不会减小容量。

https://github.com/0voice

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐