|
这些题目的考察风格偏向细节与边界,常常把多个知识点糅合在一个程序里,让你在看似正确的代码中找出隐蔽错误或推导准确输出。下面按照 C++ 的学习顺序,重新梳理这些套路和陷阱。
函数重载与默认参数
重载的判定只看参数列表,与返回值无关。参数个数和类型相同、仅返回值不同的两个函数不构成重载,编译器会报错。
带默认参数的重载很容易引发二义性。比如一个无参函数和一个有默认参数的有参函数同时存在,调用 f() 时编译器无法选择。
许多人以为“返回值不同就能重载”,这是经典误解。类似陷阱还有:默认参数写在声明与定义中不一致、重载函数用于毫无相干的语义,以及重载运算符时忘记返回引用导致连续运算失败。
类与对象基础
构造函数和析构函数都没有返回值,析构函数不能带参数,因此一个类只能有一个析构函数,它不能被重载。
复制构造函数必须用引用传递。当类中有指针成员时,必须手工实现深拷贝,否则默认的浅拷贝会让两个对象的指针指向同一块内存,析构时重复释放导致崩溃。
派生类构造时,如果没有显式调用基类构造,基类部分会使用默认构造,可能让成员初始化为意想不到的值。
const 成员必须用初始化列表赋值,且之后不可修改。
创建对象数组要求类有默认构造函数;如果自定义了带参构造却没提供无参构造,new T[n] 就会失败。
类里的 const 成员函数和非 const 成员函数可以重载:const 对象只能调用 const 版本,非 const 对象优先调用非 const 版本。
继承与多态
只有通过基类的指针或引用调用虚函数,多态才会生效。如果用基类对象直接接收派生类对象,会发生对象切片,丢掉派生类特性。
派生类覆盖虚函数时,函数签名必须完全一致——函数名、参数列表、const 修饰符一个都不能差。一旦 const 不一致,就不再是覆盖,而变成隐藏,此时通过基类指针调用的仍是基类版本,多态失效。
强烈建议使用 override 关键字,让编译器帮你检查签名错误。
含有纯虚函数的类是抽象类,不能实例化。如果派生类没有实现所有纯虚函数,它仍然是抽象类。
静态成员函数没有 this 指针,因此不能是虚函数,也无法实现多态。
友元函数可以访问私有成员,但虚函数必须是成员函数。
派生类覆盖虚函数时可以省略 virtual,函数依然是虚的。
访问控制上,private 成员对派生类不可见,想让派生类直接访问但又对外隐藏,必须用 protected。
运算符重载
单目运算符重载为成员函数时不需要形参,重载为友元时需要一个形参。
后置自增必须有一个 int 占位参数以区别于前置版本,后置返回的是修改前的副本(按值返回),前置返回的是修改后的引用。
赋值运算符通常返回 *this 的引用,以支持连续赋值。
重载 operator+ 时,语法上允许返回任意类型,但习惯上返回类类型以便链式调用。题目可能会故意给出返回 int 的例子,它语法合法,但破坏了链式运算的习惯。
下标运算符常重载为普通引用和 const 引用两个版本,分别用于读写和只读。
不能重载的运算符是常考选择题:::、.、.*、?: 等。
模板与异常
类模板的成员函数在类外定义时,必须带上 template<typename T> 和 类名<T>::。
try-catch 必须搭配使用,throw 可以抛出任意类型的对象,catch 按类型匹配。异常对象可以是简单枚举值。
静态成员变量必须在类外单独定义,否则链接报错。静态成员函数不能是虚函数,也没有 this。
这套题的风格就是在细微处做文章:不断在函数签名、const 一致性、初始化顺序、默认构造、深拷贝、覆盖条件等容易被忽略的地方挖掘错误。很多代码看起来能运行,实际却藏了切片、隐藏或未定义行为。应对的关键是深刻理解编译器的匹配规则——重载决议、覆盖与隐藏的判定、访问权限检查。类似的坑还有:析构函数是否该写 virtual、基类指针删除派生对象是否安全、赋值运算符是否检查自赋值、移动构造与移动赋值、explicit 防止隐式转换、初始化列表与构造体内赋值的区别等。扎扎实实掌握这些底层规则,才能写出安全可靠的 C++ 代码。
|