函数与函数式交互题
函数与函数式交互题
Java、Python党可以直接跳过下面指针这一部分
由于 C++ 强烈依靠指针进行内存管理,在学习函数之前需要提前学习一下指针的概念。虽然 Java 和 Python 隐藏了指针的细节,但理解指针有助于更深入理解这些语言中的引用机制,所以也可以简单看一看。
深入 C++ 的"地址魔法"—— 指针
指针是什么?—— 内存地址的"门牌号"
类比: 如果变量是存放数据的"房间",那么:
- 变量的值是"房间里的东西"
- 变量的地址是"房间的门牌号"
- 指针就是专门用来"存放门牌号"的变量
指针的三个核心操作:
-
取地址(
&
): 获取变量的内存地址int num = 10; int* ptr = # // ptr存放num的地址
-
声明指针(
*
): 创建一个指针变量int* ptr; // 声明一个指向整数的指针 double* dptr; // 声明一个指向浮点数的指针
-
解引用(
*
): 通过地址访问对应的值int num = 10; int* ptr = # *ptr = 20; // 通过ptr修改num的值 cout << num << endl; // 输出20
完整示例:
#include <iostream>
using namespace std;
int main() {
int num = 10; // 普通整型变量
int* ptr = # // ptr指向num
cout << "num的值: " << num << endl;
cout << "num的地址: " << &num << endl;
cout << "ptr存储的地址: " << ptr << endl;
cout << "ptr指向的值: " << *ptr << endl;
*ptr = 20; // 通过指针修改num的值
cout << "修改后num的值: " << num << endl;
return 0;
}
指针的重要用法
指针与数组
在 C++ 中,数组名实际上是指向数组第一个元素的指针:
int numbers[5] = {10, 20, 30, 40, 50};
int* ptr = numbers; // ptr指向numbers[0]
cout << *ptr << endl; // 输出10
cout << *(ptr + 1) << endl; // 输出20
cout << *(ptr + 2) << endl; // 输出30
// 通过指针遍历数组
for(int i = 0; i < 5; i++) {
cout << *(ptr + i) << " "; // 输出 10 20 30 40 50
}
空指针
空指针表示"不指向任何地址",用于初始化或重置指针:
int* ptr = nullptr; // C++11标准
// int* ptr = NULL; // 旧标准,但仍然常用
if(ptr == nullptr) {
cout << "这是一个空指针" << endl;
}
// 危险操作!不要解引用空指针
// *ptr = 10; // 程序会崩溃
指针陷阱与注意事项
野指针(未初始化指针)
int* ptr; // 未初始化,指向随机位置!
*ptr = 10; // 可能覆盖重要数据或导致程序崩溃
正确做法: 始终初始化指针!
int* ptr = nullptr; // 安全的初始化
// 或
int num = 10;
int* ptr = # // 指向明确的变量
悬挂指针
指针指向已被释放或已失效的内存:
int* createAndReturn() {
int local = 10;
return &local; // 危险!函数结束后local不再存在
}
int main() {
int* ptr = createAndReturn();
cout << *ptr << endl; // 不确定的行为,可能崩溃
return 0;
}
内存泄漏
使用new
分配的内存必须用delete
释放:
// 正确的内存管理
int* ptr = new int; // 分配内存
*ptr = 10;
// 使用ptr...
delete ptr; // 释放内存
ptr = nullptr; // 避免悬挂指针
// 内存泄漏
int* leakPtr = new int;
leakPtr = new int; // 第一次分配的内存无法访问,造成泄漏
函数(function)
想象一下,如果你要在程序中多次计算两个数的和,不用函数你可能需要这样:
// 第一次计算 5+3
int result1 = 5 + 3;
cout << result1 << endl;
// 第二次计算 10+20
int result2 = 10 + 20;
cout << result2 << endl;
// 第三次计算 7+8
int result3 = 7 + 8;
cout << result3 << endl;
// ...可能还有更多次计算
// 第一次计算 5+3
int result1 = 5 + 3;
System.out.println(result1);
// 第二次计算 10+20
int result2 = 10 + 20;
System.out.println(result2);
// 第三次计算 7+8
int result3 = 7 + 8;
System.out.println(result3);
// ...可能还有更多次计算
# 第一次计算 5+3
result1 = 5 + 3
print(result1)
# 第二次计算 10+20
result2 = 10 + 20
print(result2)
# 第三次计算 7+8
result3 = 7 + 8
print(result3)
# ...可能还有更多次计算
如果使用函数,代码会变成这样:
// 定义一个计算两数之和的函数
int add(int a, int b) {
return a + b;
}
// 使用这个函数多次
cout << add(5, 3) << endl;
cout << add(10, 20) << endl;
cout << add(7, 8) << endl;
// 定义一个计算两数之和的函数
public static int add(int a, int b) {
return a + b;
}
// 使用这个函数多次
System.out.println(add(5, 3)); // 输出8
System.out.println(add(10, 20)); // 输出30
System.out.println(add(7, 8)); // 输出15
# 定义一个计算两数之和的函数
def add(a, b):
return a + b
# 使用这个函数多次
print(add(5, 3)) # 输出8
print(add(10, 20)) # 输出30
print(add(7, 8)) # 输出15
看出区别了吗?使用函数让代码更简洁,而且如果计算方式需要修改(比如改为乘法),只需要修改函数定义处的一行代码,而不是到处寻找并修改。
函数就像是手机里的应用: 比如计算器 App,你只需要知道如何使用它(输入两个数字),它就能给你结果(计算结果),而不需要知道它内部是如何工作的。
编程世界的"小模块"
函数的英文是 function
,这个词在这里应该被翻译为功能
,但是由于各种历史遗留问题,大家都采用了更有逼格的数学翻译函数
。实际上,功能
这个翻译似乎更能反映函数的作用: 实现一个功能。
函数可以将复杂问题分解成小模块,就像把大象装进冰箱分三步走:
- 打开冰箱门
- 把大象放进去
- 关上冰箱门
每一步都可以是一个函数,让程序清晰易读,便于多人协作。
函数的基础
函数的定义与声明
函数就像一个"食谱",需要有:
- 食谱名(函数名)
- 需要的食材(参数)
- 制作步骤(函数体)
- 最终成品(返回值)
函数定义的基本结构
返回值类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...) {
// 函数体(一系列语句)
return 返回值; // 可选,如果函数返回值类型不是void
}
// 例如
int add(int a, int b) {
return a + b;
}
// 通常在类中定义函数(在Java中称为方法)
访问修饰符 [static] 返回值类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...) {
// 函数体
return 返回值; // 可选,如果返回值类型不是void
}
// 例如
public static int add(int a, int b) {
return a + b;
}
def 函数名(参数名1, 参数名2, ...):
# 函数体
return 返回值 # 可选,如果不写则返回None
# 例如
def add(a, b):
return a + b
函数的调用与参数传递
函数调用基础
调用函数时,需要写出函数名和传递的参数,格式很简单:
函数名(参数1, 参数2, ...)
例如:
int result = add(5, 3); // 调用add函数,传递参数5和3
参数与实参的区别
- 参数(Parameter): 函数定义时使用的变量名,相当于"占位符"
- 实参(Argument): 函数调用时传递的具体值
例如在
int add(int a, int b)
中,a
和b
是参数;而在add(5, 3)
中,5
和3
是实参。
参数传递方式
这里是新手最容易混淆的概念,也是面试中经常考察的点。三种语言的参数传递机制有很大不同!
C++ 的参数传递
C++ 有三种传递方式,影响"食材"(参数)是否会被函数修改:
按值传递 | func(int a) |
函数内修改不影响原变量(复印件) | void increase(int x) { x++; } |
按指针传递 | func(int* a) |
函数内可通过解引用修改原变量 | void increase(int* x) { (*x)++; } |
按引用传递 | func(int& a) |
函数内修改会影响原变量(直接操作) | void increase(int& x) { x++; } |
示例:
#include <iostream>
using namespace std;
void byValue(int x) {
x = x + 10; // 只修改复印件
cout << "函数内 x = " << x << endl;
}
void byPointer(int* x) {
*x = *x + 10; // 通过地址修改原值
cout << "函数内 *x = " << *x << endl;
}
void byReference(int& x) {
x = x + 10; // 直接修改原值
cout << "函数内 x = " << x << endl;
}
int main() {
int a = 5;
cout << "原始值 a = " << a << endl;
byValue(a);
cout << "按值传递后 a = " << a << endl; // a 不变
byPointer(&a); // 传递a的地址
cout << "按指针传递后 a = " << a << endl; // a 变了
byReference(a);
cout << "按引用传递后 a = " << a << endl; // a 变了
return 0;
}
Java 的参数传递
Java 只有一种传递方式:按值传递,但对于对象类型和基本类型表现不同:
基本类型(int, float 等) | 值的副本 | 函数内修改不影响原变量 |
对象类型(数组,类实例等) | 对象引用的副本 | 可通过引用修改对象内容,但不能更改引用指向 |
示例:
public class ParameterDemo {
public static void changeBasic(int x) {
x = x + 10; // 修改的是副本
System.out.println("函数内 x = " + x);
}
public static void changeArray(int[] arr) {
arr[0] = 99; // 修改数组内容有效
System.out.println("函数内 arr[0] = " + arr[0]);
arr = new int[]{-1, -2, -3}; // 修改引用无效(外部看不到)
System.out.println("函数内新数组 arr[0] = " + arr[0]);
}
public static void main(String[] args) {
int a = 5;
System.out.println("原始值 a = " + a);
changeBasic(a);
System.out.println("函数调用后 a = " + a); // a 不变
int[] numbers = {1, 2, 3};
System.out.println("原始数组 numbers[0] = " + numbers[0]);
changeArray(numbers);
System.out.println("函数调用后 numbers[0] = " + numbers[0]); // 变为99
}
}
Python 的参数传递
Python 采用按对象引用传递机制:
不可变类型(数字,字符串,元组) | 函数内"修改"实际是创建新对象,原对象不变 |
可变类型(列表,字典,集合) | 函数内修改会影响原对象 |
示例:
def change_number(x):
x = x + 10 # 创建新对象
print(f"函数内 x = {x}")
def change_list(lst):
lst.append(4) # 修改原列表
print(f"函数内增加元素后 lst = {lst}")
lst = [9, 9, 9] # 创建新列表,不影响外部
print(f"函数内新列表 lst = {lst}")
a = 5
print(f"原始值 a = {a}")
change_number(a)
print(f"函数调用后 a = {a}") # a 不变
my_list = [1, 2, 3]
print(f"原始列表 my_list = {my_list}")
change_list(my_list)
print(f"函数调用后 my_list = {my_list}") # 变成[1,2,3,4]
参数传递的统一理解: 所有语言中,传递的都是"值",区别在于传递的是"数据的值"还是"引用/地址的值"。理解这一点对笔面试题至关重要!
函数的返回值
函数可以(也可以不)给调用者返回一个结果:
返回值的基本用法
int multiply(int a, int b) {
return a * b; // 返回两数乘积
}
void printHello() {
cout << "Hello!" << endl;
// 无需return语句(也可以写return;)
}
public static int multiply(int a, int b) {
return a * b;
}
public static void printHello() {
System.out.println("Hello!");
// 无需return语句
}
def multiply(a, b):
return a * b
def print_hello():
print("Hello!")
# 可以不写return(默认返回None)
返回值的注意事项
- C++/Java: 返回值类型必须与函数声明中的类型匹配
- 如型果函数声明了返回类型(非 void/None),必须有
return
语句 - 可以返回多种类的数据:基本类型、数组、对象等
- Python 可通过元组返回多个值:
return a, b, c
- Java 和 C++ 可使用数组、对象或特殊的容器类返回多个值
函数的作用域与生命周期
作用域基础
作用域决定了变量在哪些区域可见、可用:
1. 局部变量:
- 在函数内部定义的变量
- 只在定义它的函数内部可见
- 函数执行结束后变量消失
2. 全局变量:
- 在所有函数外部定义的变量
- 程序中大部分地方都可访问(有一些限制)
- 程序运行期间一直存在
示例:
#include <iostream>
using namespace std;
int globalVar = 10; // 全局变量
void testScope() {
int localVar = 20; // 局部变量
cout << "在函数内部:" << endl;
cout << "全局变量值: " << globalVar << endl;
cout << "局部变量值: " << localVar << endl;
globalVar = 15; // 修改全局变量
}
int main() {
cout << "函数调用前:" << endl;
cout << "全局变量值: " << globalVar << endl;
// cout << localVar << endl; // 报错!局部变量在这里不可见
testScope();
cout << "函数调用后:" << endl;
cout << "全局变量值: " << globalVar << endl; // 变为15
// cout << localVar << endl; // 报错!局部变量在这里不可见
return 0;
}
变量生命周期
生命周期是指变量从创建到销毁的整个过程:
局部变量 | 从定义处开始,到包含它的代码块结束 |
全局变量 | 从程序开始到程序结束 |
静态局部变量(C++) | 从第一次执行到该变量的代码开始,到程序结束 |
C++静态局部变量例子:
void counter() {
static int count = 0; // 静态局部变量,只初始化一次
count++;
cout << "函数被调用了 " << count << " 次" << endl;
}
int main() {
counter(); // 输出:函数被调用了 1 次
counter(); // 输出:函数被调用了 2 次
counter(); // 输出:函数被调用了 3 次
return 0;
}
变量作用域的常见问题
1. 变量屏蔽:
int x = 10; // 全局变量
void demo() {
int x = 20; // 局部变量,屏蔽了全局变量
cout << x << endl; // 输出20(局部变量)
// 在C++中,可以使用::运算符访问被屏蔽的全局变量
cout << ::x << endl; // 输出10(全局变量)
}
2. 修改全局变量:
在 Python 中需要使用global
关键字:
total = 0 # 全局变量
def add_to_total(value):
global total # 声明要使用全局变量
total += value # 修改全局变量
add_to_total(5)
print(total) # 输出: 5
在 C++/Java 中直接使用即可(但 Java 中通常不推荐使用全局变量)。
搞定函数式交互题(核心代码题)
什么是"核心代码模式"?
在笔试或在线编程平台,常见的一种考试模式是给你一个或多个函数的框架,你只需填写核心逻辑:
// 题目提供的函数框架
class Solution {
public:
/**
*
* 实现求两个参数的和
* @param Integer1 int整型
* @param Integer2 int整型
* @return int整型
*/
int addTwoInteger(int Integer1, int Integer2) {
// 在这里填写代码
}
};
通用解题步骤("四步法")
这里以 例题:A+B(函数) 为例。
步骤 1:读懂题意,明确目标
认真阅读题目描述,理解:
- 这个函数要实现什么功能?
- 输入是什么?(参数类型、取值范围)
- 输出是什么?(返回类型、格式要求)
步骤 2:分析接口,理解参数与返回值
仔细研究函数接口:
class Solution {
public:
/**
*
* 实现求两个参数的和
* @param Integer1 int整型
* @param Integer2 int整型
* @return int整型
*/
int addTwoInteger(int Integer1, int Integer2) {
// 在这里填写代码
}
};
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 实现求两个参数的和
* @param Integer1 int整型
* @param Integer2 int整型
* @return int整型
*/
public int addTwoInteger(int Integer1, int Integer2) {
// 在这里填写代码
}
}
# 实现求两个参数的和
# @param Integer1 int整型
# @param Integer2 int整型
# @return int整型
#
class Solution:
def addTwoInteger(self, Integer1: int, Integer2: int) -> int:
# 在这里填写代码
我们不难分析出:
- 函数名为
addTwoInteger
- 第一个参数是整数一
- 第二个参数是整数二
- 函数需要返回的是一个整数
步骤 3:设计算法,构思逻辑
先用自然语言或伪代码思考解决方案,为之后代码实现作准备
步骤 4:编写代码,实现函数
在提供的框架内填写代码:
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 实现求两个参数的和
* @param Integer1 int整型
* @param Integer2 int整型
* @return int整型
*/
int addTwoInteger(int Integer1, int Integer2) {
return Integer1 + Integer2;
}
};
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 实现求两个参数的和
* @param Integer1 int整型
* @param Integer2 int整型
* @return int整型
*/
public int addTwoInteger(int Integer1, int Integer2) {
return Integer1 + Integer2;
}
}
# 实现求两个参数的和
# @param Integer1 int整型
# @param Integer2 int整型
# @return int整型
#
class Solution:
def addTwoInteger(self, Integer1: int, Integer2: int) -> int:
return Integer1 + Integer2
这样我们就完美的解决了函数交互题啦。
调试技巧
在做核心代码题时,你通常没有机会运行完整的程序。以下是一些有用的调试技巧:
-
边界检查: 特别注意函数开头处理特殊情况
-
手动模拟: 用纸笔跟踪简单测试用例的执行过程
-
打印调试: 某些平台允许在代码中添加打印语句
def find_max(nums): print(f"输入: {nums}") # 调试信息 # 实现...
课后习题
汗牛充栋,学海无涯。<br/> 内含算法知识点讲解,以及牛客题库精选例题。<br/> 学习算法,从牛栋开始。