C++ Primer Plus 第8章:函数探幽

8.1 C++ 内联函数(深入)

8.1.1 内联函数 vs 宏定义

内联函数是对 C 语言宏(#define)的改进,解决了宏的诸多问题。

cpp 复制代码
// inline_vs_macro.cpp -- 内联函数与宏的对比
#include <iostream>

// C风格宏(有副作用!)
#define SQUARE_MACRO(x) ((x) * (x))

// C++内联函数(安全)
inline double square(double x)
{
    return x * x;
}

inline int myMax(int a, int b)
{
    return (a > b) ? a : b;
}

int main()
{
    using namespace std;

    int n = 5;

    // 宏的副作用:n++被展开两次!
    // SQUARE_MACRO(n++) → ((n++) * (n++)) → 5*6=30,n变为7
    cout << "宏 SQUARE_MACRO(n++):" << SQUARE_MACRO(n++) << endl;
    cout << "n 变为:" << n << endl;   // 7(被加了两次!)

    n = 5;
    // 内联函数:n++只执行一次,结果正确
    cout << "\n内联 square(n++):" << square(n++) << endl;
    cout << "n 变为:" << n << endl;   // 6(只加了一次)

    // 内联函数有类型检查,宏没有
    double d = 3.14;
    cout << "\nsquare(3.14) = " << square(d) << endl;   // 9.8596

    return 0;
}

宏 vs 内联函数对比:

特性 #define inline 函数
类型检查 ❌ 无 ✅ 有
调试支持 ❌ 难以调试 ✅ 可调试
副作用 ❌ 参数可能多次求值 ✅ 参数只求值一次
作用域 ❌ 全局 ✅ 遵循作用域规则
递归 ❌ 不支持 ✅ 支持(但不会内联)

8.1.2 内联函数的使用场景

cpp 复制代码
// inline_usage.cpp -- 内联函数适用场景
#include <iostream>
#include <cmath>

// ✅ 适合内联:函数体短小,频繁调用
inline double toRadians(double degrees)
{
    return degrees * 3.14159265358979 / 180.0;
}

inline bool isPositive(double x) { return x > 0; }
inline int  clamp(int val, int lo, int hi)
{
    return (val < lo) ? lo : (val > hi) ? hi : val;
}

// ❌ 不适合内联:函数体过长
// inline void longFunction() { /* 100行代码 */ }

int main()
{
    using namespace std;

    cout << "30度 = " << toRadians(30) << " 弧度" << endl;
    cout << "sin(30°) = " << sin(toRadians(30)) << endl;

    cout << boolalpha;
    cout << "isPositive(3.14) = " << isPositive(3.14) << endl;
    cout << "isPositive(-1.0) = " << isPositive(-1.0) << endl;

    // clamp:将值限制在[0, 100]范围内
    cout << "clamp(150, 0, 100) = " << clamp(150, 0, 100) << endl;  // 100
    cout << "clamp(-10, 0, 100) = " << clamp(-10, 0, 100) << endl;  // 0
    cout << "clamp(75,  0, 100) = " << clamp(75,  0, 100) << endl;  // 75

    return 0;
}

8.2 引用变量(深入)

8.2.1 引用的本质与规则

cpp 复制代码
// reference_deep.cpp -- 引用深入理解
#include <iostream>

int main()
{
    using namespace std;

    int a = 10;
    int& ref = a;   // ref 是 a 的别名

    // 规则1:引用必须在声明时初始化
    // int& r;   // ❌ 错误

    // 规则2:引用一旦绑定,不能改变绑定对象
    int b = 20;
    ref = b;    // 这是赋值(a=20),不是改变绑定!
    cout << "a = " << a << endl;    // 20
    cout << "ref = " << ref << endl; // 20(ref仍然是a的别名)

    // 规则3:引用和原变量共享地址
    cout << "&a   = " << &a   << endl;
    cout << "&ref = " << &ref << endl;   // 地址相同

    // 规则4:不能有引用的引用,不能有指向引用的指针
    // int&& rr = ref;   // 这是右值引用(C++11),不同概念
    int* p = &ref;      // p 指向 a(通过ref取地址)
    cout << "*p = " << *p << endl;   // 20

    return 0;
}

8.2.2 引用参数的三种使用场景

cpp 复制代码
// reference_scenarios.cpp -- 引用参数的使用场景
#include <iostream>
#include <string>

// 场景1:修改调用者的变量(需要修改时用引用)
void increment(int& n, int step = 1)
{
    n += step;
}

// 场景2:避免复制大对象(只读时用const引用)
void printString(const std::string& s)
{
    std::cout << "字符串:" << s
              << "(长度:" << s.size() << ")" << endl;
}

// 场景3:返回多个值(通过引用参数"返回")
void minMax(const int arr[], int size, int& minVal, int& maxVal)
{
    minVal = maxVal = arr[0];
    for (int i = 1; i < size; i++)
    {
        if (arr[i] < minVal) minVal = arr[i];
        if (arr[i] > maxVal) maxVal = arr[i];
    }
}

int main()
{
    using namespace std;

    // 场景1
    int x = 5;
    increment(x);       // x = 6
    increment(x, 10);   // x = 16
    cout << "x = " << x << endl;

    // 场景2
    string longStr = "这是一个很长的字符串,通过const引用传递避免复制";
    printString(longStr);

    // 场景3
    int data[] = {3, 1, 4, 1, 5, 9, 2, 6};
    int minV, maxV;
    minMax(data, 8, minV, maxV);
    cout << "最小值:" << minV << " 最大值:" << maxV << endl;

    return 0;
}

8.2.3 引用与临时变量

cpp 复制代码
// reference_temp.cpp -- 引用与临时变量
#include <iostream>

// const引用可以绑定到临时变量(右值)
double refCube(const double& x)
{
    return x * x * x;
}

// 非const引用不能绑定到临时变量
// double badFunc(double& x) { return x; }  // 不能接受字面量

int main()
{
    using namespace std;

    // const引用可以接受字面量和表达式
    cout << refCube(3.0)   << endl;   // ✅ 字面量
    cout << refCube(3)     << endl;   // ✅ int自动转换为double临时变量
    cout << refCube(2.0+1) << endl;   // ✅ 表达式结果

    // 非const引用只能接受左值(有地址的变量)
    // badFunc(3.0);   // ❌ 编译错误

    double d = 3.0;
    // badFunc(d);     // ✅ 变量可以

    return 0;
}

💡 关键规则

  • 非const引用 :只能绑定到左值(有名字、有地址的变量)
  • const引用:可以绑定到左值、右值(字面量、临时变量、表达式)

8.2.4 返回引用

cpp 复制代码
// return_ref.cpp -- 返回引用的正确与错误用法
#include <iostream>
#include <string>

struct Student {
    std::string name;
    int score;
};

// ✅ 正确:返回成员的引用(对象生命周期足够长)
std::string& getName(Student& s)
{
    return s.name;
}

// ✅ 正确:返回静态变量的引用
std::string& getVersion()
{
    static std::string version = "v1.0.0";
    return version;
}

// ❌ 错误:返回局部变量的引用(悬空引用!)
// int& badReturn()
// {
//     int local = 42;
//     return local;   // local在函数结束后销毁!
// }

int main()
{
    using namespace std;

    Student s = {"张三", 90};

    // 返回引用可以作为左值
    getName(s) = "李四";   // 直接修改s.name
    cout << "修改后姓名:" << s.name << endl;   // 李四

    // 链式操作
    getVersion() = "v2.0.0";
    cout << "版本:" << getVersion() << endl;

    return 0;
}

8.3 默认参数(深入)

cpp 复制代码
// default_params_deep.cpp -- 默认参数深入
#include <iostream>
#include <string>

// 默认参数在原型中声明(不在定义中重复)
void createUser(std::string name,
                int age       = 18,
                std::string role = "普通用户",
                bool active   = true);

int main()
{
    using namespace std;

    // 各种调用方式
    createUser("张三");                          // 全部使用默认值
    createUser("李四", 25);                      // 指定age
    createUser("王五", 30, "管理员");            // 指定age和role
    createUser("赵六", 22, "VIP用户", false);   // 全部指定

    return 0;
}

void createUser(std::string name, int age,
                std::string role, bool active)
{
    using namespace std;
    cout << "用户:" << name
         << " 年龄:" << age
         << " 角色:" << role
         << " 状态:" << (active ? "激活" : "禁用")
         << endl;
}

输出:

复制代码
用户:张三 年龄:18 角色:普通用户 状态:激活
用户:李四 年龄:25 角色:普通用户 状态:激活
用户:王五 年龄:30 角色:管理员 状态:激活
用户:赵六 年龄:22 角色:VIP用户 状态:禁用

⚠️ 默认参数规则

  • 默认参数必须从右向左连续设置
  • 一旦某个参数有默认值,其右边所有参数都必须有默认值
  • 默认参数通常在函数原型中声明,定义中不重复

8.4 函数重载(深入)

8.4.1 重载的匹配规则

cpp 复制代码
// overload_deep.cpp -- 函数重载深入
#include <iostream>

// 重载函数组
void show(int x)
{
    std::cout << "show(int): " << x << std::endl;
}

void show(double x)
{
    std::cout << "show(double): " << x << std::endl;
}

void show(int x, int y)
{
    std::cout << "show(int,int): " << x << ", " << y << std::endl;
}

void show(const char* s)
{
    std::cout << "show(const char*): " << s << std::endl;
}

int main()
{
    using namespace std;

    show(42);           // show(int)
    show(3.14);         // show(double)
    show(42, 100);      // show(int, int)
    show("Hello");      // show(const char*)

    // 类型提升
    short s = 10;
    show(s);            // show(int):short提升为int

    float f = 1.5f;
    show(f);            // show(double):float提升为double

    // 二义性错误示例(注释掉以避免编译错误)
    // show('A');       // ❌ 二义性:char→int 还是 char→double?

    return 0;
}

8.4.2 重载与 const 参数

cpp 复制代码
// overload_const.cpp -- const与重载
#include <iostream>

// const版本和非const版本可以重载
void process(int& x)
{
    std::cout << "非const引用版本:" << x << std::endl;
    x *= 2;
}

void process(const int& x)
{
    std::cout << "const引用版本:" << x << std::endl;
}

int main()
{
    int a = 5;
    const int b = 10;

    process(a);   // 调用非const版本(a是可修改的左值)
    process(b);   // 调用const版本(b是const)
    process(3);   // 调用const版本(字面量是右值,绑定到const引用)

    std::cout << "a = " << a << std::endl;   // 10(被修改了)

    return 0;
}

8.4.3 何时不能重载

cpp 复制代码
// 以下情况不构成重载(会导致编译错误):

// ❌ 仅返回类型不同,不构成重载
// int    getValue() { return 1; }
// double getValue() { return 1.0; }   // 错误!

// ❌ 参数名不同,不构成重载
// void func(int a) {}
// void func(int b) {}   // 错误!重定义

// ✅ 参数类型不同,构成重载
void func(int a)    {}
void func(double a) {}

// ✅ 参数数量不同,构成重载
void func(int a, int b) {}

8.5 函数模板

8.5.1 什么是函数模板?

函数模板允许编写与类型无关的通用函数,编译器根据调用时的参数类型自动生成具体函数。

复制代码
模板定义格式:
template <typename T>
返回类型 函数名(T 参数, ...)
{
    // 使用T作为类型
}
cpp 复制代码
// template_basic.cpp -- 函数模板基础
#include <iostream>

// 函数模板:T是类型参数
template <typename T>
T myMax(T a, T b)
{
    return (a > b) ? a : b;
}

template <typename T>
void swap(T& a, T& b)
{
    T temp = a;
    a = b;
    b = temp;
}

template <typename T>
void printArray(const T arr[], int size)
{
    for (int i = 0; i < size; i++)
        std::cout << arr[i] << " ";
    std::cout << std::endl;
}

int main()
{
    using namespace std;

    // 编译器根据参数类型自动实例化
    cout << myMax(3, 5)       << endl;   // int版本:5
    cout << myMax(3.14, 2.71) << endl;   // double版本:3.14
    cout << myMax('a', 'z')   << endl;   // char版本:z

    int a = 10, b = 20;
    swap(a, b);
    cout << "交换后:a=" << a << " b=" << b << endl;

    double da = 1.1, db = 2.2;
    swap(da, db);
    cout << "交换后:da=" << da << " db=" << db << endl;

    int    iarr[] = {1, 2, 3, 4, 5};
    double darr[] = {1.1, 2.2, 3.3};
    printArray(iarr, 5);
    printArray(darr, 3);

    return 0;
}

输出:

复制代码
5
3.14
z
交换后:a=20 b=10
交换后:da=2.2 db=1.1
1 2 3 4 5
1.1 2.2 3.3

8.5.2 多类型参数模板

cpp 复制代码
// multi_type_template.cpp -- 多类型参数模板
#include <iostream>

// 两个类型参数
template <typename T1, typename T2>
void printPair(T1 first, T2 second)
{
    std::cout << "(" << first << ", " << second << ")" << std::endl;
}

// 返回类型也是模板参数
template <typename T>
T sumArray(const T arr[], int size)
{
    T sum = T();   // 默认初始化(0 for int/double)
    for (int i = 0; i < size; i++)
        sum += arr[i];
    return sum;
}

int main()
{
    using namespace std;

    printPair(1, "hello");       // (1, hello)
    printPair(3.14, true);       // (3.14, 1)
    printPair("name", "Alice");  // (name, Alice)

    int    iarr[] = {1, 2, 3, 4, 5};
    double darr[] = {1.1, 2.2, 3.3, 4.4};

    cout << "整数数组和:" << sumArray(iarr, 5) << endl;   // 15
    cout << "浮点数组和:" << sumArray(darr, 4) << endl;   // 11

    return 0;
}

8.5.3 模板的显式实例化与具体化

cpp 复制代码
// template_specialization.cpp -- 模板具体化
#include <iostream>
#include <cstring>

// 通用模板
template <typename T>
bool isEqual(T a, T b)
{
    return a == b;
}

// 模板具体化(针对特定类型提供特殊实现)
// 对 const char* 类型,用strcmp比较字符串内容
template <>
bool isEqual<const char*>(const char* a, const char* b)
{
    return strcmp(a, b) == 0;
}

// 显式实例化(告诉编译器生成特定类型的版本)
template bool isEqual<int>(int, int);       // 显式实例化int版本
template bool isEqual<double>(double, double); // 显式实例化double版本

int main()
{
    using namespace std;
    cout << boolalpha;

    cout << "isEqual(1, 1)     = " << isEqual(1, 1)     << endl;   // true
    cout << "isEqual(1, 2)     = " << isEqual(1, 2)     << endl;   // false
    cout << "isEqual(1.0, 1.0) = " << isEqual(1.0, 1.0) << endl;   // true

    // 使用具体化版本比较字符串内容
    const char* s1 = "hello";
    const char* s2 = "hello";
    const char* s3 = "world";
    cout << "isEqual(\"hello\",\"hello\") = " << isEqual(s1, s2) << endl; // true
    cout << "isEqual(\"hello\",\"world\") = " << isEqual(s1, s3) << endl; // false

    return 0;
}

8.5.4 模板函数的重载

cpp 复制代码
// template_overload.cpp -- 模板与普通函数重载
#include <iostream>

// 模板函数
template <typename T>
T add(T a, T b)
{
    std::cout << "[模板] ";
    return a + b;
}

// 普通函数(优先级高于模板)
int add(int a, int b)
{
    std::cout << "[普通函数] ";
    return a + b;
}

// 模板重载(不同参数数量)
template <typename T>
T add(T a, T b, T c)
{
    std::cout << "[三参数模板] ";
    return a + b + c;
}

int main()
{
    using namespace std;

    cout << add(1, 2)         << endl;   // 普通函数:3
    cout << add(1.5, 2.5)     << endl;   // 模板(double):4
    cout << add(1, 2, 3)      << endl;   // 三参数模板:6
    cout << add(1.1, 2.2, 3.3)<< endl;   // 三参数模板:6.6

    return 0;
}

函数匹配优先级:

复制代码
1. 完全匹配的普通函数(最高优先级)
2. 完全匹配的模板函数
3. 需要类型转换的普通函数

8.6 模板的局限性与解决方案

cpp 复制代码
// template_limits.cpp -- 模板局限性
#include <iostream>
#include <string>

// 问题:对某些类型,模板的行为可能不正确
template <typename T>
T myMax(T a, T b)
{
    return (a > b) ? a : b;
}

// 解决方案1:模板具体化
template <>
const char* myMax<const char*>(const char* a, const char* b)
{
    return (strcmp(a, b) > 0) ? a : b;
}

// 解决方案2:使用string类(已重载>运算符)
// myMax<string> 可以直接使用

int main()
{
    using namespace std;

    cout << myMax(3, 5)         << endl;   // 5
    cout << myMax(3.14, 2.71)   << endl;   // 3.14

    // C风格字符串:使用具体化版本
    const char* s1 = "banana";
    const char* s2 = "apple";
    cout << myMax(s1, s2) << endl;   // banana(字典序更大)

    // string类:直接使用模板
    string str1 = "banana", str2 = "apple";
    cout << myMax(str1, str2) << endl;   // banana

    return 0;
}

8.7 decltype 关键字(C++11)

decltype 用于推断表达式的类型,常与模板配合使用。

cpp 复制代码
// decltype_demo.cpp -- decltype关键字
#include <iostream>

template <typename T1, typename T2>
// 问题:T1+T2的结果类型不确定(int+double=double)
// 解决:用decltype推断返回类型
auto addValues(T1 a, T2 b) -> decltype(a + b)
{
    return a + b;
}

int main()
{
    using namespace std;

    // decltype推断变量类型
    int    x = 5;
    double y = 3.14;

    decltype(x)   a = 10;      // a 是 int 类型
    decltype(y)   b = 2.71;    // b 是 double 类型
    decltype(x+y) c = x + y;   // c 是 double 类型(int+double=double)

    cout << "a = " << a << " (int)"    << endl;
    cout << "b = " << b << " (double)" << endl;
    cout << "c = " << c << " (double)" << endl;

    // 模板中使用decltype
    cout << "\naddValues(3, 4.5)   = " << addValues(3, 4.5)   << endl;  // 7.5
    cout << "addValues(1.5, 2.5) = " << addValues(1.5, 2.5) << endl;  // 4
    cout << "addValues(2, 3)     = " << addValues(2, 3)     << endl;  // 5

    return 0;
}

8.8 综合示例:通用排序与查找

cpp 复制代码
// generic_algorithms.cpp -- 综合示例:通用算法
#include <iostream>
#include <string>

// ===== 通用排序(模板 + 函数指针)=====
template <typename T>
void bubbleSort(T arr[], int size, bool (*compare)(T, T))
{
    for (int i = 0; i < size - 1; i++)
        for (int j = 0; j < size - i - 1; j++)
            if (!compare(arr[j], arr[j+1]))
                std::swap(arr[j], arr[j+1]);
}

// 比较函数
template <typename T>
bool ascending(T a, T b)  { return a <= b; }

template <typename T>
bool descending(T a, T b) { return a >= b; }

// ===== 通用查找(模板)=====
template <typename T>
int linearSearch(const T arr[], int size, const T& target)
{
    for (int i = 0; i < size; i++)
        if (arr[i] == target)
            return i;
    return -1;   // 未找到
}

// ===== 通用打印(模板)=====
template <typename T>
void printArray(const T arr[], int size,
                const std::string& label = "")
{
    if (!label.empty())
        std::cout << label << ":";
    for (int i = 0; i < size; i++)
        std::cout << arr[i] << " ";
    std::cout << std::endl;
}

// ===== 通用统计(模板)=====
template <typename T>
struct Stats {
    T    minVal;
    T    maxVal;
    double sum;
    double average;
};

template <typename T>
Stats<T> calcStats(const T arr[], int size)
{
    Stats<T> s;
    s.minVal = s.maxVal = arr[0];
    s.sum = arr[0];

    for (int i = 1; i < size; i++)
    {
        if (arr[i] < s.minVal) s.minVal = arr[i];
        if (arr[i] > s.maxVal) s.maxVal = arr[i];
        s.sum += arr[i];
    }
    s.average = s.sum / size;
    return s;
}

int main()
{
    using namespace std;

    // 整数数组
    int iArr[] = {64, 34, 25, 12, 22, 11, 90};
    int iSize  = 7;

    printArray(iArr, iSize, "原始整数数组");

    bubbleSort(iArr, iSize, ascending<int>);
    printArray(iArr, iSize, "升序排序后");

    bubbleSort(iArr, iSize, descending<int>);
    printArray(iArr, iSize, "降序排序后");

    int idx = linearSearch(iArr, iSize, 25);
    cout << "查找 25:位置 " << idx << endl;

    auto iStats = calcStats(iArr, iSize);
    cout << "最小值:" << iStats.minVal
         << " 最大值:" << iStats.maxVal
         << " 平均值:" << iStats.average << endl;

    // 浮点数组
    cout << "\n";
    double dArr[] = {3.14, 1.41, 2.71, 1.73, 0.57};
    int dSize = 5;

    printArray(dArr, dSize, "原始浮点数组");
    bubbleSort(dArr, dSize, ascending<double>);
    printArray(dArr, dSize, "升序排序后");

    auto dStats = calcStats(dArr, dSize);
    cout << "最小值:" << dStats.minVal
         << " 最大值:" << dStats.maxVal
         << " 平均值:" << dStats.average << endl;

    // 字符串数组
    cout << "\n";
    string sArr[] = {"banana", "apple", "cherry", "date", "elderberry"};
    int sSize = 5;

    printArray(sArr, sSize, "原始字符串数组");
    bubbleSort(sArr, sSize, ascending<string>);
    printArray(sArr, sSize, "字典序排序后");

    int sIdx = linearSearch(sArr, sSize, string("cherry"));
    cout << "查找 cherry:位置 " << sIdx << endl;

    return 0;
}

输出:

复制代码
原始整数数组:64 34 25 12 22 11 90
升序排序后:11 12 22 25 34 64 90
降序排序后:90 64 34 25 22 12 11
查找 25:位置 3
最小值:11 最大值:90 平均值:36.8571

原始浮点数组:3.14 1.41 2.71 1.73 0.57
升序排序后:0.57 1.41 1.73 2.71 3.14
最小值:0.57 最大值:3.14 平均值:1.912

原始字符串数组:banana apple cherry date elderberry
字典序排序后:apple banana cherry date elderberry
查找 cherry:位置 2

📝 第8章知识点总结

知识点 核心要点
内联函数 比宏更安全(有类型检查、无副作用),适合短小频繁调用的函数
引用深入 必须初始化,不能改变绑定;const引用可绑定临时变量
引用参数场景 修改变量用引用,只读大对象用const引用,返回多值用引用参数
返回引用 不能返回局部变量的引用,可返回成员/静态变量的引用
默认参数 从右向左设置,在原型中声明,调用时可省略右侧参数
函数重载 同名不同参数,返回类型不同不构成重载,const版本可重载
函数模板 template <typename T> 定义通用函数,编译器自动实例化
模板具体化 template <> 为特定类型提供特殊实现
模板重载 模板可以重载,普通函数优先级高于模板
decltype C++11新特性,推断表达式类型,常用于模板返回类型
相关推荐
William_wL_1 小时前
【C++】模板进阶
c++
MC皮蛋侠客8 小时前
Google Test 单元测试指南
c++·单元测试·google test
方也_arkling9 小时前
【Java-Day08】static / final / 枚举
java·开发语言
艾莉丝努力练剑9 小时前
【Linux:文件】Ext系列文件系统进阶
linux·运维·服务器·c++·文件系统·文件io·ext
风吹夏回9 小时前
Python 全局异常处理:从“满屏 try-except”到优雅兜底
开发语言·python
Chengbei119 小时前
一站式源码安全检测工具、云安全 / APP / 小程序源码敏感信息递归多层目录扫描AK、JWT、手机号、身份证等敏感信息
java·开发语言·安全·web安全·网络安全·系统安全·安全架构
llz_1129 小时前
web-第一次课后作业
java·开发语言·idea
小熊Coding10 小时前
Python爬取当当网二手图书项目实战!
开发语言·爬虫·python·beautifulsoup·requests·二手图书
秋910 小时前
Java项目运行5天左右自动宕机:系统性定位与解决方案
java·开发语言·python