# 遍历与提前退出:查找首个匹配文件
学习目标:在文件列表的线性扫描中,理解提前退出、短路求值与未命中结果的跨语言表达。
前置知识与范围:循环、切片/数组与条件判断。本篇只遍历已加载的内存字符串列表,不执行真实磁盘 I/O。
# 问题与边界
给定一个文件名字符串列表和一个后缀 ext(如 ".log"):
- 按列表原始顺序依次检查,一旦遇到以
ext结尾的文件名,立即返回该文件名并终止后续遍历。 - 若列表为空,或遍历完整列表后仍无任何项匹配,显式返回“未找到”,不得抛出未捕获异常或访问越界内存。
边界用例:
["app.log", "data.csv", "trace.log"]找".log":返回"app.log"(证明按序且短路,不返回后面的"trace.log")。["data.csv", "image.png"]找".log":返回未命中。- 空列表
[]找".log":返回未命中。
# 关键差异
| 语言 | 循环与提前退出 | 未命中表示 | 空列表行为 |
|---|---|---|---|
| Rust | for in 配合 return Some(item) |
Option<&'a str> |
循环不执行,返回 None |
| Go | for _, f := range files 配合 return f, true |
多返回值 (string, bool) |
循环不执行,返回 "", false |
| Java | 增强 for 配合 return Optional.of(f) |
Optional<String> |
返回 Optional.empty() |
| JavaScript | for...of 配合 return f |
undefined 或 null |
循环不执行,返回 null |
| TypeScript | 沿用 JS 运行时,签名标注 string | null |
联合类型 string | null |
返回 null |
| Kotlin | for (f in files) 配合 return f |
可空类型 String? |
循环不执行,返回 null |
| Python | for f in files: 配合 return f |
Optional[str](None) |
循环不执行,返回 None |
| C++ | 范围 for 配合 return f |
std::optional<std::string_view> |
返回 std::nullopt |
# 各语言实现
# Rust
Rustfn find_first<'a>(files: &'a [&'a str], ext: &str) -> Option<&'a str> {
for &file in files {
if file.ends_with(ext) {
return Some(file);
}
}
None
}
fn main() {
let files = ["app.log", "data.csv", "trace.log"];
assert_eq!(find_first(&files, ".log"), Some("app.log"));
assert_eq!(find_first(&files, ".txt"), None);
assert_eq!(find_first(&[], ".log"), None);
match find_first(&files, ".log") {
Some(name) => println!("found: {name}"),
None => println!("not found"),
}
}
Rust 中借用生命周期 'a 确保返回的字符串切片与入参列表有效绑定;未命中通过 Option::None 表达,迫使调用方在解出值前处理缺失情况。
# Go
Gopackage main
import (
"fmt"
"strings"
)
func findFirst(files []string, ext string) (string, bool) {
for _, f := range files {
if strings.HasSuffix(f, ext) {
return f, true
}
}
return "", false
}
func main() {
files := []string{"app.log", "data.csv", "trace.log"}
if match, ok := findFirst(files, ".log"); ok {
if match != "app.log" {
panic("expected app.log")
}
fmt.Printf("found: %s\n", match)
} else {
panic("expected match")
}
if _, ok := findFirst(files, ".txt"); ok {
panic("expected not found")
}
if _, ok := findFirst(nil, ".log"); ok {
panic("expected not found for empty")
}
}
Go 惯用多返回值 (string, bool)(comma-ok 惯用法),不依靠魔数空串 "" 来指示查找失败。参见 Go 循环与多返回值规范。
# Java
Javaimport java.util.List;
import java.util.Optional;
public class Main {
public static Optional<String> findFirst(List<String> files, String ext) {
for (String file : files) {
if (file.endsWith(ext)) {
return Optional.of(file);
}
}
return Optional.empty();
}
public static void main(String[] args) {
List<String> files = List.of("app.log", "data.csv", "trace.log");
Optional<String> match = findFirst(files, ".log");
assert match.isPresent() && "app.log".equals(match.get());
assert findFirst(files, ".txt").isEmpty();
assert findFirst(List.of(), ".log").isEmpty();
match.ifPresentOrElse(
name -> System.out.println("found: " + name),
() -> System.out.println("not found")
);
}
}
Java 标准库提供 Optional 作为显式容器避免未受保护的 null 访问。
# JavaScript
JavaScriptconst assert = {
equal: (a, b) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
};
function findFirst(files, ext) {
for (const file of files) {
if (file.endsWith(ext)) {
return file;
}
}
return null;
}
const files = ["app.log", "data.csv", "trace.log"];
assert.equal(findFirst(files, ".log"), "app.log");
assert.equal(findFirst(files, ".txt"), null);
assert.equal(findFirst([], ".log"), null);
const result = findFirst(files, ".log");
if (result !== null) {
console.log(`found: ${result}`);
} else {
console.log("not found");
}
JavaScript 中使用 null 显式表示“未命中”。for...of 在条件达成时通过 return 立即退出函数与循环,后续元素不会被遍历。
# TypeScript
TypeScriptconst assert = {
equal: (a: unknown, b: unknown) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
};
function findFirst(files: readonly string[], ext: string): string | null {
for (const file of files) {
if (file.endsWith(ext)) {
return file;
}
}
return null;
}
const files: readonly string[] = ["app.log", "data.csv", "trace.log"];
assert.equal(findFirst(files, ".log"), "app.log");
assert.equal(findFirst(files, ".txt"), null);
assert.equal(findFirst([], ".log"), null);
const result = findFirst(files, ".log");
if (result !== null) {
console.log(`found: ${result}`);
} else {
console.log("not found");
}
TypeScript 返回类型为 string | null,调用方在访问字符串属性前必须进行 result !== null 的类型收窄。
# Kotlin
Kotlinfun findFirst(files: List<String>, ext: String): String? {
for (file in files) {
if (file.endsWith(ext)) {
return file
}
}
return null
}
fun main() {
val files = listOf("app.log", "data.csv", "trace.log")
val match = findFirst(files, ".log")
check(match == "app.log")
check(findFirst(files, ".txt") == null)
check(findFirst(emptyList(), ".log") == null)
if (match != null) {
println("found: $match")
} else {
println("not found")
}
}
Kotlin 的类型系统原生区分可空引用 String? 与不可空引用 String,在 if (match != null) 块内自动智能转换(smart cast)。
# Python
Pythonfrom typing import Sequence, Optional
def find_first(files: Sequence[str], ext: str) -> Optional[str]:
for f in files:
if f.endswith(ext):
return f
return None
if __name__ == "__main__":
files = ["app.log", "data.csv", "trace.log"]
assert find_first(files, ".log") == "app.log"
assert find_first(files, ".txt") is None
assert find_first([], ".log") is None
result = find_first(files, ".log")
if result is not None:
print(f"found: {result}")
else:
print("not found")
Python 函数在循环未触发 return 时自然执行末尾的 return None。调用方通过 is not None 进行判断。
# C++
C++#include <iostream>
#include <vector>
#include <string_view>
#include <optional>
#include <cassert>
std::optional<std::string_view> find_first(const std::vector<std::string_view>& files, std::string_view ext) {
for (const auto& file : files) {
if (file.size() >= ext.size() && file.compare(file.size() - ext.size(), ext.size(), ext) == 0) {
return file;
}
}
return std::nullopt;
}
int main() {
std::vector<std::string_view> files = {"app.log", "data.csv", "trace.log"};
assert(find_first(files, ".log") == std::optional<std::string_view>{"app.log"});
assert(find_first(files, ".txt") == std::nullopt);
assert(find_first({}, ".log") == std::nullopt);
if (auto match = find_first(files, ".log")) {
std::cout << "found: " << *match << "\n";
} else {
std::cout << "not found\n";
}
}
C++17 引入了 std::optional,配合 std::string_view 避免多余的堆分配拷贝。
# 迁移误区与适用限制
- 不要把未找到与空字符串
""混淆:空文件名在部分上下文中是合法或特殊值,使用 sentinel value(如"")容易导致假性命中。 - 循环退出与短路:若改为
filter或列表推导式全量收集后再取第一项,在长列表或懒加载流中会造成无谓的全部遍历与内存开销。 - 本例中比较的是后缀字面量;若要匹配复杂通配符或正则表达式,应明确预编译模式的开销。
# 运行与验证
语言与版本基线见示例基线。 所有程序运行后控制台输出均为单行:
纯文本found: app.log
所有断言保证边界内、边界外及空集合均能正确提前短路并返回预期结构。