# 泛型函数:保留查找对象的类型
学习目标:用同一个查找算法处理整数和字符串,区分参数化类型、动态调用和相等能力。
前置知识:循环、列表、返回值;Rust 的 Option 只需理解 Some/None 两个分支。
# 问题与边界
输入有限、非空元素的整数或字符串序列及同类型目标,返回第一个相等元素的位置。重复元素取最左侧;空序列或未命中不是异常。Rust/C++ 用可选位置,其余用 -1 表示未命中;调用方必须先分支再访问元素。本题不定义跨类型数值比较、NaN 或任意对象的深相等。返回位置而不复制元素,适合大型记录查找。
# 关键差异
| 机制 | 本题含义 | 不保证什么 |
|---|---|---|
| Rust PartialEq、Go comparable | 约束函数体可以比较 | 不保证业务主键相等 |
| Java/Kotlin 泛型 | 元素与目标类型关联 | 不阻止推断共同父类型 |
| C++ 模板 | 实例化时检查比较表达式 | C++17 没有本例的显式 concept |
| TS/Python 类型参数 | 描述输入关系 | 不在运行时校验外部数据 |
| JS 动态函数 | 同一函数处理多类值 | 不建立静态泛型契约 |
# 各语言实现
# Rust
成功时预期输出为 "not found\n"(\n 表示换行)。
Rustfn locate<T: PartialEq>(items: &[T], target: &T) -> Option<usize> {
items.iter().position(|item| item == target)
}
fn main() {
assert_eq!(locate(&[4, 2, 4], &4), Some(0));
assert_eq!(locate(&["a", "b"], &"b"), Some(1));
assert_eq!(locate::<i32>(&[], &4), None);
match locate(&[4, 2], &9) {
Some(i) => panic!("unexpected index: {i}"),
None => println!("not found"),
}
}
&[T] 借用切片,查找不取得元素所有权。PartialEq 才是调用 == 的依据;泛型参数本身不附送比较能力。Rust 泛型。
# Go
成功时预期输出为 "not found\n"(\n 表示换行)。
Gopackage main
import "fmt"
func locate[T comparable](items []T, target T) int {
for i, item := range items { if item == target { return i } }
return -1
}
func main() {
if locate([]int{4, 2, 4}, 4) != 0 || locate([]string{"a", "b"}, "b") != 1 || locate([]int{}, 4) != -1 { panic("lookup") }
if i := locate([]int{4, 2}, 9); i == -1 { fmt.Println("not found") } else { panic(i) }
}
comparable 允许 ==,但不能据此对切片内容逐项比较。若以接口类型实例化,动态值可能仍不可比较;本文只用具体整数和字符串。Go comparable。
# Java
成功时预期输出为 "not found\n"(\n 表示换行)。
Javaimport java.util.List;
public class Main {
static <T> int locate(List<T> items, T target) {
for (int i = 0; i < items.size(); i++) {
if (items.get(i).equals(target)) return i;
}
return -1;
}
public static void main(String[] args) {
if (locate(List.of(4, 2, 4), 4) != 0 || locate(List.of("a", "b"), "b") != 1 || locate(List.<Integer>of(), 4) != -1) throw new AssertionError();
int i = locate(List.of(4, 2), 9);
if (i == -1) System.out.println("not found"); else throw new AssertionError(i);
}
}
泛型类型参数不能直接用 int,这里使用装箱后的 Integer。equals 而非引用 == 才符合字符串内容查找。Java 泛型方法。
# JavaScript
成功时预期输出为 "not found\n"(\n 表示换行)。
JavaScriptfunction locate(items, target) {
for (let i = 0; i < items.length; i++) {
if (items[i] === target) return i;
}
return -1;
}
if (locate([4, 2, 4], 4) !== 0 || locate(["a", "b"], "b") !== 1 || locate([], 4) !== -1) throw new Error("lookup");
const i = locate([4, 2], 9);
if (i === -1) console.log("not found"); else throw new Error("unexpected index");
严格相等对本例整数、字符串满足契约,但两个内容相同的独立对象并不严格相等。ECMAScript 严格相等。
# TypeScript
TypeScriptfunction locate<T>(items: readonly T[], target: T): number {
for (let i = 0; i < items.length; i++) {
if (items[i] === target) return i;
}
return -1;
}
if (locate([4, 2, 4], 4) !== 0 || locate(["a", "b"], "b") !== 1 || locate([], 4) !== -1) throw new Error("lookup");
const i = locate([4, 2], 9);
if (i === -1) console.log("not found"); else throw new Error("unexpected index");
readonly 限制通过此参数修改数组,并不冻结运行时对象;类型参数也不将 === 改成深相等。TypeScript 泛型。
# Kotlin
成功时预期输出为 "not found\n"(\n 表示换行)。
Kotlinfun <T> locate(items: List<T>, target: T): Int {
for ((i, item) in items.withIndex()) if (item == target) return i
return -1
}
fun main() {
check(locate(listOf(4, 2, 4), 4) == 0)
check(locate(listOf("a", "b"), "b") == 1)
check(locate(emptyList<Int>(), 4) == -1)
val i = locate(listOf(4, 2), 9)
if (i == -1) println("not found") else error("unexpected index")
}
Kotlin 的 == 使用结构相等语义,与 Java 的引用 == 不同。这里没有要求可空元素,但 Kotlin 的相等操作本身能处理 null。Kotlin 相等性。
# Python
成功时预期输出为 "not found\n"(\n 表示换行)。
Pythonfrom typing import Sequence, TypeVar
T = TypeVar("T")
def locate(items: Sequence[T], target: T) -> int:
for i, item in enumerate(items):
if item == target:
return i
return -1
assert locate([4, 2, 4], 4) == 0
assert locate(["a", "b"], "b") == 1
assert locate([], 4) == -1
i = locate([4, 2], 9)
if i == -1:
print("not found")
else:
raise AssertionError(i)
TypeVar 供静态检查器关联输入,不影响解释器执行。Python 中 -1 是有效的倒数索引,所以漏掉未命中分支尤其危险。typing 泛型。
# C++
成功时预期输出为 "not found\n"(\n 表示换行)。
C++#include <cassert>
#include <iostream>
#include <optional>
#include <string>
#include <vector>
template<class T>
std::optional<std::size_t> locate(const std::vector<T>& items, const T& target) {
for (std::size_t i = 0; i < items.size(); ++i) if (items[i] == target) return i;
return std::nullopt;
}
int main() {
assert(locate(std::vector<int>{4, 2, 4}, 4) == 0);
assert(locate(std::vector<std::string>{"a", "b"}, std::string("b")) == 1);
assert(!locate(std::vector<int>{}, 4));
if (auto i = locate(std::vector<int>{4, 2}, 9)) { return 1; }
else { std::cout << "not found\n"; }
}
const& 避免复制整个容器;std::string("b") 使模板推断的两个 T 一致,而不是要求推断自动把字符数组转换成 string。模板表达式在实例化时接受检查。C++ 工作草案:模板是持续更新资料,不作为 C++17 最低版本证明。
# 迁移误区与适用限制
- 将泛型理解成“任意类型都能比较” → 没有相等操作的 C++ 类型实例化失败;选择所需能力,或显式传入比较函数。
- 将 JS 对象比较照搬到 Java
equals→ 两者不具有统一的深相等规则;记录查找应定义主键或比较器。 - 让未命中返回 0 → 与首元素命中冲突;保留可选结果或独立哨兵,并在调用边界消费。
- 生产中优先考虑已有
position、indexOf、std::find;这里手写循环用于看清类型契约,不是替换标准库的建议。
# 运行与验证
每种语言在独立目录按上节文件名和命令执行。Rust/C++ 的可执行程序在 PowerShell 中用 .\main.exe,POSIX shell 中用 ./main。基线见主题首页,实际执行证据集中记入验证记录。本文给出预期语义与检查,不把编写或阅读核对宣称为运行通过;各程序应由验证流程执行,类型错误反例的文字说明不等于已运行负向编译测试。