从零到一学习c++(基础篇--筑基期十-函数)

  从零到一学习C++(基础篇) 作者:羡鱼肘子

温馨提示1:本篇是记录我的学习经历,会有不少片面的认知,万分期待您的指正。

 温馨提示2:本篇会尽量用更加通俗的语言介绍c++的基础,用通俗的语言去解释术语,但不会再大白话了哦。常见,常看,常想,渐渐的就会发现术语也是很简单滴。

 温馨提示3:看本篇前可以先了解前篇的内容,知识体系会更加完整哦。

从零到一学习c++(基础篇--筑基期九-语句)-优快云博客

温馨提示4:本篇想尝试一下双线穿插的方式来学习,在理解是什么的基础上再学习专业的表述,也许会有更好的学习效果

 函数

一、函数基础

函数是什么呢?(函数就是“做事的工具”)

1. 函数的本质

  • 作用:把一段代码打包成一个独立工具,可以重复使用。

  • 打个比方:就像厨房里的电饭煲,你只需要按按钮(调用函数),它就能自动煮饭(执行代码)。

  • // 定义一个“加法工具”
    int add(int a, int b) { // a和b是原料(参数)
        return a + b;       // 返回做好的饭菜(结果)
    }
    
    // 使用工具
    int result = add(3, 5); // 把3和5放进去,得到8
    

1. 函数声明与定义

  • 声明:指定函数名称、参数列表和返回类型(可省略形参名)。

  • 定义:实现函数体,必须与声明一致。

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

// 定义
int add(int a, int b) {
    return a + b;
}

如何去构建函数这个工具呢?( 函数的三大要素)

(1) 参数传递:如何给工具“送原料”

  • 值传递:复制一份原料给工具,工具内修改不影响原数据。

    void change(int x) { x = 10; } // 修改的是副本
    int a = 5;
    change(a); // a还是5,没变!
  • 引用传递:直接操作原数据(贴标签)。

    void change(int& x) { x = 10; } // 操作原数据
    int a = 5;
    change(a); // a变成10!
  • 指针传递:告诉工具原料的位置(地址)。(目前了解就好了哦)

    void change(int* x) { *x = 10; } // 通过地址修改原数据
    int a = 5;
    change(&a); // a变成10!

(2) 返回值:工具“给结果”

  • 直接返回:把结果交给调用者。

    int add(int a, int b) { return a + b; }
  • 返回引用:返回原数据的“标签”(需确保数据生命周期)。

    int& getMax(int& a, int& b) { return a > b ? a : b; }
    int x = 3, y = 5;
    getMax(x, y) = 10; // y被改为10

(3) 函数重载:同一个工具名,不同功能

  • 条件:函数名相同,但参数类型或数量不同。

    // 重载1:处理整数加法
    int add(int a, int b) { return a + b; }
    
    // 重载2:处理浮点数加法
    double add(double a, double b) { return a + b; }

2. 参数传递

  • 值传递:拷贝实参,函数内修改不影响原值。

  • 引用传递:直接操作实参,避免拷贝开销。

void swap(int& x, int& y) { // 引用传递
    int temp = x;
    x = y;
    y = temp;
}

3. 返回类型

  • 基础类型:直接返回。

  • 返回引用:避免拷贝,但需确保返回对象生命周期。

const std::string& getLonger(const std::string& s1, const std::string& s2) {
    return s1.size() > s2.size() ? s1 : s2;
}

4. 函数重载

  • 同一作用域内,函数名相同但参数列表不同。

void print(int i) { /*...*/ }
void print(const std::string& s) { /*...*/ }

5. 内联函数

  • 通过 inline 建议编译器内联展开,减少调用开销。

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

什么是内联函数呢?

内联函数(Inline Function) 是C++中一种优化程序性能的特性,旨在减少函数调用的开销。通过将函数体直接插入到每个调用处,避免频繁的函数跳转和栈操作,从而提升运行效率。


一、核心概念

1. 什么是内联函数?

  • 定义:使用 inline 关键字修饰的函数。

  • 作用:建议编译器将函数代码直接“展开”到调用位置,省去函数调用的开销。

  • 打个比方:就像将菜谱步骤直接写在烹饪流程图中,省去翻页查找菜谱的时间。

2. 普通函数 vs 内联函数

普通函数内联函数
每次调用需跳转执行,存在栈开销。代码直接插入调用处,无跳转开销。
适合复杂或低频调用的函数。适合简单且高频调用的小函数。

二、如何使用内联函数?

1. 基本语法

inline int add(int a, int b) {
    return a + b;
}
  • 注意inline 是给编译器的“建议”,实际是否内联由编译器决定。

2. 对比一下

  • 普通函数调用

    int result = add(3, 5); // 跳转到add函数执行

    编译后伪代码

    push 3, 5 到栈 -> call add -> 执行add -> 返回结果 -> 清理栈
  • 内联函数调用

    int result = add(3,5);//等效int result = 3 + 5;
    // 直接替换为函数体

    编译后效果:无跳转,直接计算。


三、内联函数的优缺点

优点

  1. 减少调用开销:适合频繁调用的小函数(如简单计算、访问类成员)。

  2. 避免宏的缺陷:相比宏(#define),内联函数有类型检查,更安全。

     

    // 宏的问题:无类型检查,易出错
    #define ADD(a, b) ((a) + (b))
    ADD("hello", 5); // 编译通过但逻辑错误
    
    // 内联函数:类型安全
    inline int add(int a, int b) { return a + b; }
    add("hello", 5); // 编译报错

缺点

  1. 代码膨胀:若函数体较大或调用次数过多,会导致可执行文件变大。

  2. 编译器可能忽略:复杂函数(如含循环、递归)或调试模式下,编译器可能拒绝内联。


四、适用场景

  1. 短小函数:函数体简单(通常1-5行代码)。

    inline int getX() const { return x; } // 类成员访问
  2. 高频调用:如循环内的数学运算。

    for (int i = 0; i < 1e6; i++) {
        sum += add(i, i+1); // 内联优化显著
    }
  3. 替代宏:需要类型安全的场景。


五、使用小建议

  1. 定义在头文件中:内联函数需在调用处可见,通常将定义放在头文件。

    // math_utils.h
    inline int add(int a, int b) { return a + b; }
  2. 避免滥用:复杂函数内联可能导致性能下降。

  3. 编译器自主权:最终是否内联由编译器决定(可通过编译器选项干预)。


六、与宏的对比

特性宏(#define)内联函数
类型安全❌ 无类型检查,易出错。✅ 有类型检查,安全。
调试支持❌ 替换后难以跟踪。✅ 可调试,保留函数语义。
代码展开预处理器简单文本替换。编译器智能生成代码。
适用场景简单代码替换(如常量、简单表达式)。需要类型安全和逻辑封装的场景。

七、一个小例子

场景:游戏中的伤害计算

#include <iostream>

// 内联函数:计算伤害
inline int calculateDamage(int attack, int defense) {
    return (attack * 2) - defense;
}

int main() {
    int playerAttack = 50;
    int enemyDefense = 30;
    
    // 高频调用内联函数
    for (int i = 0; i < 1000; i++) {
        int damage = calculateDamage(playerAttack, enemyDefense);
        std::cout << "造成伤害:" << damage << std::endl;
    }
    return 0;
}

优化效果:函数体被直接嵌入循环,省去千次函数调用开销。


小结一下

  • 内联函数是优化高频调用小函数的利器,但需谨慎使用。

  • 适用场景:短小、高频、需类型安全的代码。

  • 避免场景:复杂函数或可能导致代码膨胀的情况。

二、现代C++函数特性增强(目前了解就行)

 一些想说的话:常顾常新,让我们不断发掘学过的知识和新知识间的联系

1. 返回类型后置(C++11)

  • 使用 auto 和 -> 后置返回类型,便于推导复杂类型。

auto divide(double a, double b) -> double { 
    return a / b; 
}

2. constexpr 函数(C++11/14)(第三遍哦,还记得const吗?)

  • 在编译期求值的函数,用于常量表达式。

constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}
int arr[factorial(5)]; // 编译期计算数组大小

3. Lambda表达式(C++11/14/17)(第二次见啦,是不是熟悉了一些呢)

  • 匿名函数,支持捕获局部变量。

std::vector<int> vec = {1, 2, 3};
std::for_each(vec.begin(), vec.end(), [](int x) {
    std::cout << x << " "; // 输出: 1 2 3
});

// C++14 泛型Lambda
auto adder = [](auto a, auto b) { return a + b; };

4. noexcept 说明符(C++11)

  • 声明函数不抛出异常,优化性能。

void safe_function() noexcept { /* 保证不抛异常 */ }

5. 右值引用与移动语义(C++11)

  • 优化资源管理,避免不必要的拷贝。

class Data {
public:
    Data(Data&& other) noexcept { // 移动构造函数
        ptr_ = other.ptr_;//偷资源
        other.ptr_ = nullptr;
    }
private:
    int* ptr_;
};

右值引用与移动语义

( 强化理解,因为之前有学过,这里就只是提一下核心的内容)

右值引用与移动语义的核心思想

1. 什么是右值?

  • 右值:临时值,用完就丢的东西。比如:

    • 字面量:42"hello"

    • 表达式结果:a + bfunc() 的返回值。

    • 临时对象:函数返回的临时对象。

2. 什么是右值引用?

  • 右值引用:用 && 声明的引用,专门绑定到右值。

    int&& r = 42; // r 是右值引用,绑定到字面量 42

3. 什么是移动语义?

  • 移动语义:将资源(如内存、文件句柄)从一个对象“偷”到另一个对象,避免不必要的拷贝。

  • 打个比方:搬家时,直接把家具搬到新家,而不是重新买一套

std::move 的作用

1. 什么是 std::move

  • 作用:将左值强制转换为右值引用,表示资源可以被“偷”。

  • 就像给搬家工人一个信号:“这些东西可以搬走”。

2. 示例

DynamicArray arr1(10);
DynamicArray arr2 = std::move(arr1); // 将 arr1 的资源“偷”给 arr2

3. 温馨小贴士

  • std::move 不移动资源:它只是告诉编译器“这个对象可以被移动”。

  • 移动后对象状态:被移动的对象处于有效但未定义状态(通常置空)。

用一句话去记住

  • 右值引用是“偷资源”的工具,移动语义是“偷资源”的行为,std::move是“偷资源”的信号。

6. 可变参数模板(C++11/17)

  • 接受任意数量和类型的参数。

template <typename... Args>
void log(Args&&... args) {
    (std::cout << ... << args) << std::endl; // C++17 折叠表达式
}
log("Error:", 404, "at line", __LINE__);

7. if constexpr(C++17)

  • 编译期条件分支,简化模板代码。

template <typename T>
auto process(T value) {
    if constexpr (std::is_arithmetic_v<T>) {
        return value * 2;
    } else {
        return value.size();
    }
}

8. consteval 函数(C++20)

  • 强制函数在编译期执行。

consteval int square(int n) { return n * n; }
constexpr int x = square(5); // 编译期计算

三、看个小小栗子

场景:实现一个安全的资源管理函数

#include <memory>
#include <vector>

// 使用智能指针管理动态数组(C++11)
std::unique_ptr<int[]> createArray(int size) {
    auto arr = std::make_unique<int[]>(size);
    for (int i = 0; i < size; ++i) {
        arr[i] = i * i;
    }
    return arr; // 移动语义自动转移所有权
}

// 结合Lambda和算法(C++11/14)
void processData(const std::vector<int>& data) {
    int sum = 0;
    std::for_each(data.begin(), data.end(), [&sum](int x) {
        sum += x; // 捕获sum的引用
    });
    std::cout << "Sum: " << sum << std::endl;
}

// 使用可变参数模板记录日志(C++17)
template <typename... Args>
void log(Args&&... args) {
    (std::cout << ... << args) << "\n"; // 折叠表达式
}

int main() {
    auto arr = createArray(5); // 返回 unique_ptr
    std::vector<int> vec = {1, 3, 5, 7};
    processData(vec);
    log("Array created with size:", 5); // 输出: Array created with size:5
    return 0;
}

四、现代C++函数设计的一些小建议

  1. 优先使用RAII:用智能指针(unique_ptr/shared_ptr)管理资源,替代裸new/delete

  2. 利用移动语义:减少拷贝,提升性能(如返回容器或大型对象)。

  3. 编译期计算:使用constexprconsteval优化常量表达式。

  4. 异常安全:用noexcept声明不抛异常的函数,结合智能指针避免资源泄漏。

  5. 泛型编程:模板函数与可变参数模板实现灵活接口。

 呼🤣终于学习完函数了,真的好难啊。下一篇,就开始学习类

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愚戏师

多谢道友

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值