1.6 C/C++ 函数

一、请写个函数在 main 函数执行前或者后执行

#include <stdio.h>
void before() __attribute__((constructor));//设置函数属性
void after() __attribute__((destructor));
void before() {
    printf("this is function %s\n",__func__);
    return;
} 
void after(){
    printf("this is function %s\n",__func__);
    return;
} 
int main(){
    printf("this is function %s\n",__func__);
    return 0;
} /
/ 输出结果
// this is function before
// this is function main
// this is function after

二、为什么析构函数必须是虚函数?

当基类指针指向派生类的时候,在释放基类指针的时候,会先执行派生类的析构函数,再执行基类的析构函数。如果基类析构函数不用 virtual 修饰,则只会调用基类的析构函数,可能会造成内存泄露。因为派生类的析构函数不是虚函数,当派生类继承父类的虚函数表的时候,没有对父类的虚析构函数进行替换。

三、为什么 C++ 默认的析构函数不是虚函数?

因为虚函数需要额外的虚函数指针虚函数表,会占用额外的内存。

四、静态函数和虚函数的区别?

静态函数地址在编译的时候就已经确定,调用的时候会直接跳转到该地址运行;虚函数调用的时候才会进行动态绑定。

静态函数属于类所有,不存在继承和覆盖的概念;虚函数支持继承和覆盖以及重写。

五、重载

重载是一种多个函数之间的平行关系。有 运算符重载、函数模版重载。

规则:

1)方法名必须相同,返回类型可以相同也可以不同,如果函数签名完全相同将导致汇编生成的标号一样,产生链接错误。(特例:除非是 const 成员函数重载)

2)参数列表必须不同,要么数量不同,要么顺序不同,要么类型不同,总之不能产生歧义。

3)在同一个作用域内。

#include <iostream>
using namespace std;

class Foo {
public:
    void bar() const {
        cout << "bar() const" << endl;
    }

    void bar() {
        cout << "bar()" << endl;
    }
};

int main () {
    Foo foo;
    const Foo foo_const;

    foo.bar();  // 输出 bar()
    foo_const.bar();  // 输出 bar() const
    return 0;
}

六、重写

函数重写发生在继承关系中,是一种垂直关系。子类重新实现父类的方法,方法名和参数都必须一样,但实现逻辑不同。

规则: 1)必须有继承关系,没有父子关系就不叫重写。

2)父类方法必须是 virtual。

3)方法名、参数列表、返回值类型都必须相同,需要一摸一样。

4)子类建议使用 override 关键字,可以增强读感。

5)基类虚构函数最好声明为 virtual,避免只调用基类析构函数而造成内存泄漏。

七、 三个陷阱

陷阱一:子类同名函数隐藏父类同名非虚函数

如果子类有和父类同名的函数,则不管参数列表是否相同,父类的函数都会隐藏掉。要想使用父类的同名方法需要用 using 关键字(using 父类 :: 方法名)。

class Base {
public:
    void foo(int x) { cout << "Base::foo(int)" << endl; }
    void foo(double x) { cout << "Base::foo(double)" << endl; }
};

class Derived : public Base {
public:
    void foo(const char* s) { cout << "Derived::foo(const char*)" << endl; }
};

int main() {
    Derived d;
    d.foo("hello");  // ✅ 调用 Derived::foo(const char*)
    d.foo(42);       // ❌ 编译错误:Base::foo(int) 被隐藏
    d.foo(3.14);     // ❌ 编译错误:Base::foo(double) 被隐藏
}



class Derived : public Base {
public:
    using Base::foo; // 引入 Base 的所有 foo 重载
    void foo(const char* s) { cout << "Derived::foo(const char*)" << endl; }
};

int main() {
    Derived d;
    d.foo("hello");  // ✅ Derived::foo(const char*)
    d.foo(42);       // ✅ Base::foo(int)
    d.foo(3.14);     // ✅ Base::foo(double)
}

陷阱二:非虚函数的伪重写

如果父类的函数不是虚函数,通过父类指针调用时,永远调用的是父类版本。

class Base {
public:
    void bar() { cout << "Base::bar()" << endl; } // 非虚函数
};

class Derived : public Base {
public:
    void bar() { cout << "Derived::bar()" << endl; } // 不是真正的重写
};

int main() {
    Derived d;
    Base* pb = &d;

    d.bar();    // ✅ 输出 "Derived::bar()"(子类对象调用子类版本)
    pb->bar();  // ❌ 输出 "Base::bar()"(父类指针调用父类版本)
}

陷阱三:const 成员函数重载,const 也能构成重载条件

规则:非 const 对象优先调用非 const 版本,如果没有则调用 const 版本;const 对象只能调用 const 成员函数版本

八、重载和覆盖(重写)有什么区别?

1、重载是同一个类的同名函数(只是函数特征标不同)之间的关系,是一种水平关系;覆盖是父类和子类的关系,是一种垂直关系。

2、所有重载函数处于同一个作用域内;覆盖是发生在基类和派生类两个不同的作用域内。

3、派生类中重写的函数必须与基类的虚函数具有相同的函数名、参数列表和返回类型。否则,派生类的同名函数将隐藏掉基类的同名函数。

4、重载:采用静态绑定(也叫早绑定),在编译阶段,编译器就会根据调用函数时传递的实参类型和数量,生成一个唯一的标号,确定要调用的具体重载函数。覆盖:使用动态绑定(也叫晚绑定),在运行时,根据对象的实际类型来决定调用哪个函数。这就需要通过基类的指针或引用调用虚函数才能实现。

特征

函数重载(Overload)

函数重写(Override)

发生位置

同一个类内

父子类之间

方法名

必须相同

必须相同

参数列表

必须不同

必须相同

返回值类型

可以不同

必须相同

决定时机

编译时决定

运行时决定

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

C++/嵌入式开发 秋招面经 文章被收录于专栏

一名985硕,在25年秋招中斩获多个C++/嵌入式开发Offer。本专栏将分享我的面经,涵盖C/C++、操作系统、计算机网络、ARM体系与架构、Linux应用/驱动开发、Qt、通信协议及开发工具链等核心内容。

全部评论
求构造析构细节
点赞 回复 分享
发布于 04-02 20:50 河北
欢迎订阅专栏《C++/嵌入式开发 秋招面经》 :https://www.nowcoder.com/creation/manager/columnDetail/MKaoll
点赞 回复 分享
发布于 03-30 17:05 河北

相关推荐

评论
6
3
分享

创作者周榜

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