第八章:函数探幽 | C++ Primer Plus 重点带看

内联函数

内联函数是编译器会将函数代码直接嵌入到调用处,避免函数调用的开销。普通函数需要压栈、跳转、返回等过程,内联函数直接把代码"复制粘贴"到调用处。

inline int add(int a, int b) {
    return a + b;
}

int main() {
    int x = add(1, 2);  // 编译后等价于 int x = 1 + 2; 函数体直接展开到调用处
}

代价:

  • 代码膨胀,每次调用都会复制一份代码。

注意事项:

  • inline 关键字只是给编译器的建议,编译器可以选择忽略。
  • 内联函数必须定义在头文件。因为内联函数需要在编译期展开,编译器必须能看到完整定义。如果定义在 .cpp 中,其他文件 #include 头文件时看不到定义,无法内联。
  • 类的成员函数如果在类内定义是自动内联的,不会造成重复定义的错误。(普通/静态成员函数都是自动内联的)
  • 静态成员变量以前不能在类内定义,C++17 起可以用 inline 在类内定义。
class MyClass {
public:
    static inline int count = 0;        // ✅ C++17,类内定义
    static inline std::string name;     // ✅ C++17
};
// 不需要类外定义了!inline 关键字告诉编译器:这个定义可以出现在多个源文件中,链接器会合并它们,不会报重复定义。

左值与右值

左值 = 可以取地址的表达式,代表一个持久的内存位置。

右值 = 不能取地址的表达式,代表临时值,用完即销毁。

左值引用

左值引用 = 左值的别名,不分配新内存,只是给已有变量起另一个名字。

注意事项:

  • 必须初始化,引用必须绑定到一个已存在的左值,不能"空着"。
  • 一旦绑定不能改变,引用不是指针,没有"指向"的概念,绑定后就是别名。
  • 不能绑定右值。(但 const 左值引用可以绑定到右值,是一个特例)

引用与指针的区别

引用必须在声明引用变量的时候就完成初始化,不能通过赋值设置。int &a =b;相当于 int * const a = &b;

函数参数传递方式:按引用传递与按值传递

1、c 语言只有按值传递

  • 传递基本类型时,形参是实参值的副本;
  • 传递指针时,本质仍是按值传递——形参是指针变量的副本,只是这个副本存储的值是一个地址。

2、C++引用传参的绑定规则

  • 非 const 左值引用(T&)只能绑定左值,适用于需要修改实参的场景;
  • const 左值引用(const T&)可以绑定左值和右值,常用于只读访问并避免拷贝开销;
  • 右值引用(T&&,C++11 引入)只能绑定右值,用于实现移动语义资源转移
void byValue(int x) { x = 10; }        // 修改的是副本

void byPointer(int* p) { *p = 10; }    // p 是指针的副本,但指向同一对象

void byRef(int& r) { r = 10; }         // r 是实参的引用(别名)

int a = 5;
byValue(a);      // a 仍为 5
byPointer(&a);   // a 变为 10
byRef(a);        // a 变为 10

返回引用

1、绝不能返回局部变量或临时对象的引用

int& wrong() {
    int x = 10;
    return x;  // 错误:返回局部变量的引用
}  // x 已销毁,返回的引用悬空

int& also_wrong() {
    return 10;  // 错误:返回临时对象的引用
}

2、只能返回生命周期超出函数作用域的对象的引用

3、返回引用避免拷贝,效率更高,常用于运算符重载和链式调用

// 1. 返回全局变量
int global = 10;
int& getGlobal() { return global; }

// 2. 返回静态变量
int& getStatic() {
    static int s = 10;
    return s;
}

// 3. 返回引用参数(最常见)
int& getElement(int arr[], int index) {
    return arr[index];  // 实参数组生命周期在外部
}

// 4. 返回成员变量
class Person {
    string name;
public:
    string& getName() { return name; }  // 成员生命周期随对象
};

// 5. 链式调用
class StringBuilder {
    StringBuilder& append(const string& s) {
        // ...
        return *this;  // 返回自身引用
    }
};

函数的默认参数

1、默认参数只能在函数声明或定义的其中一处指定,不能同时指定。原因:编译器通过函数原型确定调用时需要传递的参数数目,定义时编译器已经知道要生成什么代码了。

// 正确:在声明中指定
void func(int a, int b = 10);  // 函数原型
void func(int a, int b) {      // 定义中不再指定
    // ...
}

// 正确:没有声明时,在定义中指定
static void localFunc(int a, int b = 10) {
    // ...
}

// 错误:重复指定
void func(int a, int b = 10);
void func(int a, int b = 10) { }  // 错误!重复定义默认参数

2、必须从右向左指定

void func(int a, int b = 10, int c = 20);  // 正确

// void func(int a = 1, int b, int c = 20);  // 错误!中间不能断开

3、实参不能跳过中间的默认参数

void func(int a, int b = 10, int c = 20);

func(1);           // 正确:a=1, b=10, c=20
func(1, 2);        // 正确:a=1, b=2, c=20
func(1, 2, 3);     // 正确:a=1, b=2, c=3
// func(1, , 3);   // 错误!不能跳过 b

函数重载(静态多态)

函数重载要求签名不同(参数个数、类型、顺序),返回类型不参与签名。编译器通过名称修饰为每个重载函数生成唯一内部名称,签名相同则修饰后冲突。注意 const 成员函数、引用限定符可构成重载,但数组和指针参数等价。

#include <iostream>
#include <string>
using namespace std;

class Data {
private:
    string value;
public:
    Data(const string& s) : value(s) {}
    
    // 1. const 成员函数重载
    string& get() {
        cout << "非const版本: ";
        return value;  // 可修改
    }
    
    const string& get() const {
        cout << "const版本: ";
        return value;  // 只读
    }
    
    // 2. 引用限定符重载(C++11)
    void process() & {
        cout << "左值对象调用: " << value << endl;
    }
    
    void process() && {
        cout << "右值对象调用: " << value << endl;
        // 可以安全地移动资源
    }
};

int main() {
    // const 成员函数重载
    Data d1("hello");
    const Data d2("world");
    
    d1.get() = "modified";  // 非const对象 → 调用非const版本
    cout << d1.get() << endl;
    
    // d2.get() = "error";  // 错误:const对象返回const引用
    cout << d2.get() << endl;  // const对象 → 调用const版本
    
    cout << "\n--- 引用限定符 ---\n";
    
    // 引用限定符重载
    Data d3("left");
    d3.process();              // 左值对象 → 调用 & 版本
    
    Data("right").process();   // 右值对象 → 调用 && 版本
    
    std::move(d3).process();   // 转为右值 → 调用 && 版本
}

const 参数与重载

1、❌ 顶层 const 不能重载。值传递和指针本身的 const 是顶层 const,会被忽略。值传递时形参是副本,const 只影响函数内部,调用者无法区分。

void f(int a);
void f(const int a);        // 错误!重复定义,顶层const被忽略

void g(int* p);
void g(int* const p);       // 错误!重复定义,const修饰指针本身

// 原因:任何 int 值都能赋给 int 或 const int 变量,没有限制。
// int a = 5;
// const int b = 10;

// int x = a;       // ✓ 正确
// int y = b;       // ✓ 正确,const 被忽略

// const int z = a; // ✓ 正确
// const int w = b; // ✓ 正确

2、✓ 底层 const 可以重载。指针指向的对象或引用绑定的对象的 const 是底层 const。根本原因是:底层 const 决定了指针/引用能否修改实参内存,因此不同底层const的指针和引用能接受/绑定的实参范围不同,可构成重载。

void f(int* p);             // 指向非const对象
void f(const int* p);       // 指向const对象,可以重载

void g(int& r);             // 绑定非const对象
void g(const int& r);       // 绑定const对象,可以重载

// 原因:int& 和 const int& 能绑定的范围不同,它们是不同的类型。
// int a = 5;
// const int b = 10;

// int& r1 = a;        // ✓ 正确
// int& r2 = b;     // ✗ 错误!const int 不能绑定到 int&

// const int& r3 = a;  // ✓ 正确
// const int& r4 = b;  // ✓ 正确

模板函数、模板函数的显示具体化、模板函数的显示实例化

优先级——非模板版本>显示具体化>显示实例化>模板函数(隐式实例化)。注:模板只是提供方案,并非真正的定义函数,定义要等到编译到具体的调用时才生成。

// 模板定义:提供通用方案
template<typename T>
void func(T a) { cout << "模板版本: " << a << endl; }

// 显式具体化:为特定类型提供专门实现
template<>
void func<int>(int a) { cout << "int具体化: " << a << endl; }

// 显式实例化:强制编译器生成特定类型的实例
template void func<double>(double a);  // 声明式, 注意:显式实例化只是告诉编译器"请用模板生成这个类型的函数",不能单独给实现。

// 非模板版本:普通函数
void func(int a) { cout << "非模板版本: " << a << endl; }

func(10);       // 调用非模板版本(最高优先级)
func(3.14);     // 调用隐式实例化的模板版本
func<>(10);     // 强制使用模板 → 调用int具体化
func<int>(10);  // 显式指定模板参数 → 调用int具体化

返回类型后置

主要用于解决模板中返回类型依赖参数类型的情况。

template<typename T>
auto add(T a, T b) -> decltype(a + b) {
    return a + b;
}

// 例如:a和b都是int,a+b是int,返回int
// 例如:a和b都是double,a+b是double,返回double

为什么需要后置?

// 错误:a 和 b 还未声明,编译器不知道它们是什么
template<typename T>
decltype(a + b) add(T a, T b) { return a + b; }

// 正确:后置返回类型,此时 a 和 b 已声明
template<typename T>
auto add(T a, T b) -> decltype(a + b) { return a + b; }

C++14 简化

// C++14起,可直接用auto推导返回类型
template<typename T>
auto add(T a, T b) {
    return a + b;  // 根据return语句自动推导
}
C++ Primer Plus 文章被收录于专栏

C++ Primer Plus 精读|从入门到面试,重点内容全程带看。 本专栏以《C++ Primer Plus》为蓝本,逐章提炼必考知识点、易错点、面试高频考点,跳过冗余示例,直击语法本质与工程实践,帮你高效吃透 C++ 基础,夯实底层开发必备能力。

全部评论
const左值引用特例
1 回复 分享
发布于 04-01 23:11 河北
接好运
点赞 回复 分享
发布于 04-02 20:50 河北
欢迎订阅专栏《C++/嵌入式开发-秋招面经》:https://www.nowcoder.com/creation/manager/columnDetail/MKaoll
点赞 回复 分享
发布于 04-01 17:27 河北

相关推荐

想踩缝纫机的小师弟练...:不理解你们这些人,要放记录就把对方公司名字放出来啊。不然怎么网暴他们
点赞 评论 收藏
分享
04-28 10:14
门头沟学院 Java
点赞 评论 收藏
分享
评论
5
2
分享

创作者周榜

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