const/const T&/const 指针/inline/constexpr(2)

inline

inline 关键字用于建议编译器将函数的调用替换为函数体本身,从而减少函数调用的开销,提高程序执行效率。

inline 只是建议,现代编译器会根据优化策略决定是否真正内联。

示例:

#include <iostream>

inline int square(int x) {  // 建议内联展开
    return x * x;
}

int main() {
    std::cout << square(5) << '\n';  // 可能被展开为  std::cout << (5 * 5) << '\n';
}

可能被编译成:

std::cout << (5 * 5) << '\n';

👉 省去函数调用过程,提高运行速度。

适用于短小频繁调用的函数

适合代码少、执行频繁的小函数,比如:

数学计算(max(a, b)、square(x))

getter/setter

运算符重载

❌ 不适合大函数,因为内联会导致代码膨胀(Code Bloat)。​

方法优点缺点适用场景
inline 在头文件​避免链接错误,全局共享可能增加编译时间(函数体重复处理)小型高频使用的函数
​声明在 .h,定义在 .cpp减少编译依赖,隐藏实现细节模板无法使用普通函数,大型项目
static 在头文件​避免链接错误

每 TU 一份副本,可能浪费空间,无法用于

跨文件共享全局状态(因为 static 使函数仅在当前 TU 可见)。

极少使用,C 风格遗留代码

换句话说:将所有函数在头文件中用 inline 声明并定义是可行的,但会带来一些潜在问题,需要根据具体场景权衡利弊。

普通函数 vs. inline 函数的本质区别

1️⃣ 普通函数的情况

代码示例:

// math.h
#ifndef MATH_H
#define MATH_H

int add(int a, int b);  // 只声明

#endif
// math.cpp
#include "math.h"

int add(int a, int b) {  // 在 math.cpp 中定义
    return a + b;
}
// main.cpp
#include "math.h"
#include <iostream>

int main() {
    std::cout << add(2, 3) << std::endl;  // ✅ 这样不会报错
}

🔹 编译过程:

  1. 编译 math.cpp
    • 发现 add() 的定义,并在目标文件 (math.o) 里生成符号 add
  2. 编译 main.cpp
    • 只看到 add()声明,不会生成 add() 的实现。
  3. 链接阶段
    • 链接器在 math.o 里找到了 add() 的实现,并将其正确链接

所以,普通函数在 .cpp 里定义是没问题的,因为编译器知道它是一个“外部符号”,链接器最终会找到它的实现。

2️⃣ inline 函数的情况

错误示例:

// math.h
#ifndef MATH_H
#define MATH_H

inline int add(int a, int b);  // ❌ 仅声明,不在头文件定义

#endif
// math.cpp
#include "math.h"

inline int add(int a, int b) {  // ❌ 只在 math.cpp 里定义
    return a + b;
}

// main.cpp
#include "math.h"
#include <iostream>

int main() {
    std::cout << add(2, 3) << std::endl;  // ❌ 链接错误:undefined reference to `add(int, int)`
}

💥 问题:在 main.cpp 里,add() 的实现找不到!

🔹 发生了什么?

  1. 编译 math.cpp
    • inline int add(int a, int b) 被定义,可能会被内联,也可能不会。
  2. 编译 main.cpp
    • 只看到 inline int add(int a, int b); 声明,但编译器需要看到完整的定义,否则无法展开。
    • 由于 add()inline,编译器不会默认把它当成外部符号,也不会生成 add() 的符号表项。
  3. 链接阶段
    • main.o 里找不到 add() 的实现,导致 undefined reference 错误。

根本原因:

  • 普通函数的声明是外部声明,意味着编译器知道它的定义会在其他编译单元.cpp 文件)中找到。
  • inline 函数的本质是每个 .cpp 里都需要看到完整的定义,才能决定是否展开它。
  • main.cpp 里没有 add() 的定义,导致链接失败。

所以inline 函数无论是全局函数、类的 public 还是 private 成员函数,都应该在头文件中同时声明和实现。

constexpr和常量表达式

常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。后面将会提到,C++语言中有几种情况下是要用到常量表达式的。

我们先在global.h中声明一个全局函数返回固定大小

extern int GetSize();

在global.cpp中实现

int GetSize(){
    return 20;
}

然后我们用const定义一些常量表达式

一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定,例如

{
    //max_files是一个常量表达式
    const int max_files = 20;
    //limit是一个常量表达式
    const int limit = max_files + 10;
    //staff_size不是常量表达式,无const声明
    int staff_size = 20;
    //sz不是常量表达式,运行时计算才得知
    const int sz = GetSize();
}

尽管staff_size的初始值是个字面值常量,但由于它的数据类型只是一个普通int而非const int,所以它不属于常量表达式。

另一方面,尽管sz本身是一个常量,但它的具体值直到运行时才能获取到,所以也不是常量表达式。

在一个复杂系统中,很难(几乎肯定不能)分辨一个初始值到底是不是常量表达式。

当然可以定义一个const变量并把它的初始值设为我们认为的某个常量表达式,但在实际使用时,尽管要求如此却常常发现初始值并非常量表达式的情况。

C++11新标准

C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化:

//20是一个常量表达式
constexpr int mf = 20;
//mf+1是一个常量表达式
constexpr int limit = mf + 10;
//错误,GetSize()不是一个常量表达式,需要运行才能返回
//constexpr int sz = GetSize();

尽管不能使用普通函数作为constexpr变量的初始值,新标准允许定义一种特殊的constexpr函数。

这种函数应该足够简单以使得编译时就可以计算其结果,这样就能用constexpr函数去初始化constexpr变量了。

我们在global.h中定义一个constexpr函数

inline constexpr int GetSizeConst() {
    return 1;
}

为了避免在多个源文件中包含同一个头文件而导致的多重定义错误,可以将 constexpr 函数声明为 inline

inline 关键字允许在多个翻译单元中定义同一个函数,而不会引起链接错误。

接下来在定义一个constexpr变量就行了

constexpr int sz = GetSizeConst();

指针和constexpr

必须明确一点,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:

//p是一个指向整形常量的指针
const int * p = nullptr;
//q是一个指向整数的常量指针
constexpr int *q = nullptr;

一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。

函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。

定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针

global_i是一个全局变量

//constexpr指针只能绑定固定地址
//constexpr int *p = &mvalue;
constexpr int *p = nullptr;
//可以绑定全局变量,全局变量地址固定
constexpr  int *cp = &global_i;

可以修改constexpr指向的内容

constexpr int *p = &global_i;
//修改p指向的内容数据
*p = 1024;

问题

global_i是一个全局变量,下面这个指针是什么类型?能否修改cp指向的数据的内容(*cp = 200)?

constexpr const int * cp = &global_i;

如果 constexpr double PI_CONST = 3.14159; 需要在多个 .cpp 文件中使用,正确的做法是在头文件中声明和定义,这样每个包含这个头文件的 .cpp 文件都可以直接使用它。 

constexpr 变量不需要 extern,可以直接在头文件定义,因为它默认 inline,不会引发链接错误。
const 变量需要 extern 处理,否则会在多个 .cpp 文件中重复定义,引发链接错误

 C++17 及以后,推荐用 constexpr 代替 const 进行全局常量声明!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值