火龙信奥
  • 首页
  • 课程
  • 题库
  • 打卡
    • 代码对战
    • 快速对战
  • 题单
  • 团队
  • 荣誉墙
  • 商城
  • 登录 / 注册

第五章:函数

作者: 作者的头像   huolong , 时间:2025-09-01 15:07:59 , 所有人可见, 阅读  5

第五章:函数 (Funktionen)

1. 课程概览
  • 函数的定义与调用
  • 参数传递方式:值传递、指针传递、引用传递
  • 数组作为参数
  • vector 作为参数
  • 返回引用
  • 指针与引用的选择

2. 函数的定义与调用
  • 目的:将代码分解为可重用的模块,避免重复,提高代码可读性和可维护性。
  • 语法: cpp Rückgabetyp Funktionsname(Parameterliste) { // 函数体 // ... return Wert; // 如果返回类型不是 void }
  • 示例:

```cpp // 定义一个计算平方的函数 int square(int x) { return x * x; }

int main() { int a = 3; cout << square(a) << endl; // 调用函数,输出 9 return 0; } ```


3. 参数传递方式

3.1 值传递 (Übergabe eines Wertes)

  • 机制:将实参的副本传递给函数。函数内部对参数的修改不会影响原始变量。
  • 特点:安全,但大对象的复制效率低。
  • 示例:

```cpp void tausche_wert(int a, int b) { int h = a; a = b; b = h; cout << "Fkt.:" << a << " " << b << endl; // 输出: Fkt.: 11 3 }

int main() { int a = 3, b = 11; cout << "main:" << a << " " << b << endl; // 输出: main: 3 11 tausche_wert(a, b); cout << "main:" << a << " " << b << endl; // 输出: main: 3 11 (未改变) } ```

  • 结论:此方法无法交换 main 函数中的 a 和 b。

3.2 指针传递 (Übergabe eines Zeigers)

  • 机制:将变量的地址(指针)传递给函数。函数通过解引用 * 操作符来访问和修改原始变量。
  • 特点:可以修改原始数据,效率高(只传递地址)。
  • 示例:

```cpp void tausche_ptr(int pa, int pb) { int h = pa; pa = pb; pb = h; cout << "Fkt.:" << pa << " " << pb << endl; // 输出: Fkt.: 11 3 }

int main() { int a = 3, b = 11; cout << "main:" << a << " " << b << endl; // 输出: main: 3 11 tausche_ptr(&a, &b); // 传递 a 和 b 的地址 cout << "main:" << a << " " << b << endl; // 输出: main: 11 3 (已交换) } ```

  • 结论:通过传递指针,成功交换了 main 函数中的 a 和 b。

3.3 引用传递 (Übergabe einer Referenz)

  • 机制:将变量的别名(引用)传递给函数。函数内部对参数的操作直接作用于原始变量。
  • 特点:语法简洁,避免了指针的复杂性,可以修改原始数据。
  • 示例:

```cpp void tausche_ref(int& a, int& b) { int h = a; a = b; b = h; cout << "Fkt.:" << a << " " << b << endl; // 输出: Fkt.: 11 3 }

int main() { int a = 3, b = 11; cout << "main:" << a << " " << b << endl; // 输出: main: 3 11 tausche_ref(a, b); // 传递变量本身,函数参数是其别名 cout << "main:" << a << " " << b << endl; // 输出: main: 11 3 (已交换) } ```

  • 结论:引用传递同样能成功交换变量,且调用语法更直观。

4. 数组作为函数参数
  • 核心事实:数组名在作为参数传递时,会自动退化为指向其首元素的指针。
  • 问题:函数内部无法知道数组的实际大小,容易导致越界访问。

4.1 问题示例

void inkrement(int b[5]) { // [5] 是误导性的!编译器会忽略
    for(int k = 0; k < 5; k++)
        b[k]++;
    cout << "sizeof(b):" << sizeof(b) << endl; // 输出: 8 (指针大小,非数组大小)
}

int main(){
    int a[5] = {2, 4, 6, 8, 10};
    cout << "sizeof(a):" << sizeof(a) << endl; // 输出: 20 (数组实际大小)
    inkrement(a); // a 被当作指针传递
    for(int i = 0; i < 5; i++)
        cout << a[i] << endl; // 输出: 3 5 7 9 11 (a 被修改了)
}
  • sizeof(b) 返回的是指针的大小(64 位系统上通常为 8 字节),而不是数组的大小。

4.2 正确做法:传递大小

  • 建议:将数组的大小作为额外的参数传递给函数。
  • 代码:

```cpp void inkrement(int b[], int const n) { // 或 int* b for(int k = 0; k < n; k++) b[k]++; }

int main(){ int constexpr n = 5; // 使用 constexpr 确保大小在编译时确定 int a[n] = {2, 4, 6, 8, 10}; inkrement(a, n); // 传递数组和大小 for(int i = 0; i < n; i++) cout << a[i] << endl; // 输出: 3 5 7 9 11 } ```

  • 优点:避免了硬编码的数组大小,减少了越界错误的风险。

4.3 二维数组作为参数

  • 规则:传递二维数组时,列数必须作为常量指定。
  • 原因:编译器需要列数来计算元素的地址。
  • 示例:

```cpp void inkrement(int const zeilen, int b[][3]) { // 列数 3 必须指定 for(int i = 0; i < zeilen; i++) for(int j = 0; j < 3; j++) b[i][j]++; }

int main(){ int a[][3] = {{2, 4, 6}, {9, 7, 5}}; inkrement(2, a); // 传递行数和数组 // 输出: 3 5 7 // 10 8 6 } ```


5. vector 作为函数参数
  • 问题:如果直接传递 vector,会复制整个容器,效率低下且无法修改原 vector。
  • 解决方案:使用引用传递。
  • 示例:

```cpp // 错误:值传递,复制整个 vector void plusEinsAusgabe(vector v) { for(int i = 0; i < v.size(); ++i) { ++v[i]; // 修改的是副本 cout << v[i] << " "; } cout << endl; } // v 在此处被销毁 // 输出: 2 3 4 5 6 // 原 vector 未变

// 正确:引用传递,可修改原 vector void plusEinsAusgabe(vector& v) { for(int i = 0; i < v.size(); ++i) { ++v[i]; // 直接修改原 vector cout << v[i] << " "; } cout << endl; } // 输出: 2 3 4 5 6 // 原 vector 被修改为 {2,3,4,5,6}

// 最佳:常量引用传递,不修改原 vector void ausgabe(const vector& v) { for(int i : v) { cout << i << " "; // 可读,不可写 } cout << endl; } ```


6. 返回引用 (Rückgabe einer Referenz)
  • 用途:实现链式调用,或需要返回一个可修改的对象。
  • 示例:

```cpp struct Konto { char name[20]; double wert; };

// 返回更富有账户的引用 Konto& reicher(Konto& k1, Konto& k2) { if(k1.wert > k2.wert) return k1; return k2; }

int main(){ Konto Anton{"Anton", 100}; Konto Berta{"Berta", 200}; cout << reicher(Anton, Berta).name << " hat mehr Geld" << endl; // 输出: Berta hat mehr Geld } ```

  • 严重警告:绝对不要返回局部变量的引用! cpp Konto& verdoppeln(Konto& k) { Konto lokal = k; // 局部变量 lokal.wert *= 2; return lokal; // 错误!返回对已销毁对象的引用 // 导致未定义行为! }
  • lokal 在函数结束时被销毁,返回的引用指向无效内存。

7. 指针 vs. 引用
特性 指针 (Zeiger) 引用 (Referenz)
初始化 可以先声明后赋值 必须在声明时初始化
重新赋值 可以改变指向(pa = &y;) 一旦绑定,不能改变绑定的对象
层级 可以有指针的指针(int**) 只有一层,不能有“引用的引用”
空值 可以为 nullptr 必须绑定到一个有效的对象
本质 存储地址的变量 变量的别名

建议:尽可能使用引用。当无法使用引用时(如需要 nullptr 或重新赋值),再使用指针。

示例对比:

// 使用引用
void square(int& x) { x = x * x; }

// 使用指针
void square(int* px) { *px = *px * *px; }

// 引用更简洁安全

8. 附录:确定性有限自动机 (DEA)
  • 目的:设计一个自动机来检查不带括号的正整数算术表达式(如 3+1-)的正确性。
  • 状态:S = {-1, 0, 1, 2}
  • 起始状态:s0 = 0
  • 接受状态:F = {1, 2}
  • 输入字母表:Σ = {Z, 0, op}
  • Z = {1,2,3,4,5,6,7,8,9}
  • op = {+, -}
  • 转移函数 δ: | δ | Z | op | 0 | |---|---|---|---| | -1 | -1 | -1 | -1 | -1 | | 0 | 1 | -1 | 2 | | 1 | 1 | 0 | 1 | | 2 | -1 | 0 | -1 |

  • 示例:30-0

  • 起始状态 0
  • 读 3 (Z) → 状态 1
  • 读 0 (0) → 状态 1
  • 读 - (op) → 状态 0
  • 读 0 (0) → 状态 2
  • 状态 2 属于 F → 接受该字符串。

—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com

©2026加盟我们 | 关于我们 | ACM课程 | 常见问题 | 成果墙 | 评测记录 | 浙ICP备2021013995号
在线画图 | OI WIki | 打字练习
火龙信奥
请输入登录信息


请完成安全验证
验证码底图 滑块
向右拖动滑块完成验证
请输入用户名 / 绑定的手机号码



请输入注册信息(手机号验证码注册)





验证码5分钟有效,60秒内不可重复获取,每日最多3次

微信登录

微信登录二维码

正在生成二维码...

账号已过期,请续期。
去续期

绑定手机号

📱

为了更好地保护您的账号安全,享受完整的平台服务

请您尽快绑定手机号码