内联函数

函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数,主调函数就会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回到主调函数,主调函数根据刚才的状态继续往下执行。

函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。

如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两条语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就就不容忽视。

为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数。

指定内联函数的方法很简单,只需要在函数定义处增加 inline 关键字

注意,要在函数定义处添加 inline 关键字,在函数声明处添加 inline 关键字虽然没有错,但这种做法是无效的,编译器会忽略函数声明处的 inline 关键字。

 

当函数比较复杂时,函数调用的时空开销可以忽略,大部分的 CPU 时间都会花费在执行函数体代码上,所以我们一般是将非常短小的函数声明为内联函数。
由于内联函数比较短小,我们通常的做法是省略函数原型,将整个函数定义(包括函数头和函数体)放在本应该提供函数原型的地方。

内联函数在编译时会将函数调用处用函数体替换,编译完成后函数就不存在了,所以在链接时不会引发重复定义错误。这一点和宏很像,宏在预处理时被展开,编译时就不存在了。从这个角度讲,内联函数更像是编译期间的宏。

inline 关键字可以只在函数定义处添加,也可以只在函数声明处添加,也可以同时添加;但是在函数声明处添加 inline 关键字是无效的,编译器会忽略函数声明处的 inline 关键字。也就是说,inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。

尽管大多数教科书中在函数声明和函数定义处都增加了 inline 关键字,但我认为 inline 关键字不应该出现在函数声明处。这个细节虽然不会影响函数的功能,但是体现了高质量 C++ 程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。

更为严格地说,内联函数不应该有声明,应该将函数定义放在本应该出现函数声明的地方,这是一种良好的编程风格。

内联函数不应该有声明,应该将函数定义放在本应该出现函数声明的地方,这是一种良好的编程风格。

在多文件编程时,我建议将内联函数的定义直接放在头文件中,并且禁用内联函数的声明(声明是多此一举)。

一般直接在类声明中定义内联函数。

class Student{
public:
  //成员变量
  char *name;
  int age;
  float score;

  //内联函数定义
  inline void say(){
    cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
  }
};

在C++中,获取成员变量的函数(通常称为“getter”函数)通常写成内联成员函数。以下是原因和具体做法:

  (1)为什么写成内联函数?
    性能优化:Getter函数通常非常简单(直接返回成员变量的值),适合内联展开,避免函数调用的开销。

    代码简洁:内联函数可以直接在类定义中实现,代码更紧凑。

    惯例:C++标准库和许多代码库中,简单的getter函数通常以内联方式实现。

  (2) 如何实现内联getter函数?
  在类定义中直接实现getter函数,编译器会自动将其视为内联函数。

class MyClass {
public:
  // 内联getter函数
  int getValue() const {
    return value;
  }

  // 内联setter函数
  void setValue(int newValue) {
    value = newValue;
  }

private:

  int value;
};

 

  (3)内联函数的注意事项
    编译器决定:inline关键字只是对编译器的建议,最终是否内联由编译器决定。

    复杂逻辑不适合内联:如果getter函数包含复杂逻辑(如条件判断、循环等),不建议内联。

    头文件中定义:内联函数通常定义在头文件中,因为编译器需要在每个使用它的地方看到其定义。

  (4)何时写成普通成员函数?

    如果getter函数需要执行复杂操作(如计算、验证等),或者不希望内联展开(例如在动态库中避免代码膨胀),可以将其实现为普通成员函数。

// MyClass.h
class MyClass {
public:
  int getValue() const; // 声明

private:
  int value;
};

// MyClass.cpp
int MyClass::getValue() const {
  // 可能的复杂逻辑
  return value;
}

  (5)总结
  简单getter函数:通常写成内联成员函数,直接在类定义中实现。

  复杂getter函数:可以写成普通成员函数,在类外实现。

 

 

 

 
posted @ 2025-03-05 17:18  孤情剑客  阅读(272)  评论(0)    收藏  举报