该系列主要用于收集我每天在各种地方不论是刷题还是面经,看到的一些小的,易忘的或者还没见过的知识点。

今日所有全是牛客C++选择题

[TOC]

1. x & (x-1)

来自牛客选择题:

1
2
3
4
5
6
7
8
9
10
11
int` `fun(``int` `x){
``int` `count = ``0``;
``while``(x){
``count++;
``x = x &(x-``1``);
``}
``return` `count;
}
int` `main(){
``cout << ``"fun(2015)="` `<< fun(``2015``)<<endl;
}

x & (x-1) 是一个经典的位运算技巧,它能将整数 x 的二进制表示中最低位的 1 变成 0。原理是:x-1 会把最低位的 1 变成 0,并且其后的所有 0 变成 1;两者进行按位与操作后,最低位的 1 及其后的位都变为 0,而更高位保持不变。这个操作每次可消除一个二进制中的 1,因此常用于高效统计二进制中 1 的个数(popcount)、判断一个数是否是 2 的幂(因为 2 的幂只有一个 1,执行一次该操作会得到 0),以及计算汉明距离等场景,是位运算优化中一个基础且高效的工具。

3. 易混的str家族

  • strcmp
    C++ 中兼容的 C 标准库函数(位于 头文件)。
    功能:按字典序比较两个 C 风格字符串(const char* 类型)。
    返回值:若两字符串相等返回 0;若第一个小于第二个返回负值;若大于则返回正值。
    注意:在 C++ 中,更推荐使用 std::string 的运算符(如 ==、<)或 compare() 成员函数进行字符串比较。

  • strstr
    C++ 中兼容的 C 标准库函数(位于 头文件)。
    功能:在一个 C 风格字符串中查找另一个子串的首次出现位置。
    参数:两个 const char* 参数,分别表示被查找字符串和待查找子串。
    返回值:找到时返回指向该位置的指针(类型为 const char* 或 char*);找不到则返回 nullptr。
    注意:C++ 中更常用 std::string 的 find() 成员函数完成类似功能。

  • strcat
    C++ 中兼容的 C 标准库函数(位于 头文件)。
    功能:将源 C 风格字符串拼接到目标字符串的末尾。
    重要风险:可能引发缓冲区溢出,调用时必须确保目标字符数组有足够的剩余空间。
    在 C++ 中,更安全的做法是使用 std::string 的 += 运算符或 append() 成员函数进行字符串拼接。

  • strfind
    这不是 C++ 标准库中的有效函数名。

4. const对函数重载的影响

关于成员函数末尾的 const 限定符
在 C++ 中,成员函数后面加的 const 表示这个函数不会修改对象的数据成员(除了被 mutable 修饰的)。这个 const 实际上改变的是隐式 this 指针的类型——普通成员函数的 thisA* 类型,而 const 成员函数的 thisconst A* 类型。由于参数类型不同,void f()void f() const 被视为两个不同的函数签名,因此可以构成重载。调用时,const 对象会自动选择 const 版本,非 const 对象优先选择非 const 版本。

关于参数中的顶层 const
const 出现在函数参数类型中(如 const int),这个 const 作用的是参数变量本身,表示函数内部不能修改这个参数。但编译器在判断函数签名时,会忽略这种“顶层 const”——也就是说,void f(int)void f(const int) 在编译器看来参数类型完全相同,都是 int。既然参数列表没有差异,就不能构成重载,否则会导致调用时无法决定使用哪个函数的歧义。

两者的本质区别
关键区别在于 const 修饰的对象不同:成员函数末尾的 const 修饰的是隐式的 this 指针,改变了函数的接口特征;而参数中的 const 修饰的只是那个参数变量,属于函数内部实现的限制,不改变对外接口。前者影响函数签名,后者不影响,这就决定了前者能重载而后者不能。

5. 谁可以是虚函数

虚函数依赖于对象的虚函数表(vtable),通过对象的 vptr(虚函数表指针)在运行时动态绑定。
因此,任何无法或不必通过对象调用、或调用时机早于对象完整构造的函数,都不能是虚函数。

为什么构造函数不能是虚函数
构造函数的作用是在对象创建时进行初始化,此时对象本身还未完全形成。虚函数的调用依赖于对象的虚函数表指针(vptr),但这个指针是在构造函数执行过程中才被初始化的。如果构造函数是虚函数,就需要通过 vptr 来查找该调用哪个构造函数,而 vptr 此时还不存在,形成了逻辑矛盾。此外,构造哪个类的对象在代码编写时(如 new Derived)已经明确,不需要运行时多态来决定。

为什么静态成员函数不能是虚函数
静态成员函数属于类本身,而不是类的任何一个对象。它没有隐藏的 this 指针,因此无法访问对象的虚函数表。虚函数的动态绑定机制必须通过具体对象的虚函数表来实现,而静态函数的调用是直接通过类名进行的,与对象无关,这就和虚函数的基本原理冲突。C++ 语法也明确禁止使用 virtual 修饰静态成员函数。

为什么内联函数通常不能作为有效的虚函数
内联的目的是在编译时将函数体直接展开到调用处以提高效率,这要求函数实现在编译时确定。而虚函数的核心是运行时动态绑定,具体调用哪个函数要在运行时通过查虚函数表才能决定。这两个机制本质冲突。虽然你可以在语法上同时写 virtualinline,但编译器会忽略内联请求,仍然将其作为普通虚函数处理(通过虚函数表调用),因此内联特性实际无效。

6. 因为作为类的成员函数进行运算符重载时,只能有一个参数。

A. Value operator-(Value, Value) 错误。双目运算符作为成员函数重载时,左操作数隐含为this指针,所以只需要一个参数。正确写法应该是Value operator-(Value)。

B. Value operator+() 正确。这是一个一元运算符重载,表示正号运算符重载。

C. Value operator*(int) 正确。这是乘法运算符重载,参数为int类型。

D. Value operator/(Value) 正确。这是除法运算符重载,参数为Value类型。

如果需要使用双参数的运算符重载,应该定义为友元函数或全局函数,写法为:
friend Value operator-(const Value&, const Value&)

Value operator-(const Value&, const Value&)

7. vector的迭代器失效问题

对 vector 使用 push_back 后,其迭代器是否失效取决于容量是否足够:如果插入后元素数量未超过当前容量(即不需要重新分配内存),则除原 end() 外的迭代器保持有效;一旦容量不足导致内存重分配,则所有迭代器、指针和引用都会失效。因此,不能一概认为 push_back 后迭代器必然失效,必须结合 vector 的容量状态具体判断,但为保证安全,通常建议插入后重新获取迭代器。