第118集:函数类型检查

核心知识点讲解

参数类型匹配

函数调用时,实参的类型必须与函数声明中的形参类型匹配。参数类型匹配需要考虑以下几点:

  1. 参数数量匹配

    • 实参数量必须与形参数量相同(除非函数支持可变参数)
    • 某些语言允许默认参数,此时对应的实参可以省略
  2. 参数类型兼容

    • 每个实参的类型必须与对应的形参类型兼容
    • 允许隐式类型转换(如int到float)
    • 某些语言支持参数类型的协变和逆变
  3. 参数传递方式

    • 值传递:实参的值被复制给形参
    • 引用传递:形参引用实参的内存位置
    • 指针传递:形参是指向实参的指针
    • 不同传递方式对类型检查有不同影响

返回类型检查

函数的返回值类型必须与函数声明中的返回类型匹配。返回类型检查规则:

  1. 返回表达式类型

    • return语句中的表达式类型必须与函数返回类型兼容
    • 允许从较窄类型到较宽类型的隐式转换
  2. void函数

    • 没有返回值的函数(void)不能有return语句带表达式
    • 可以有不带表达式的return语句
  3. 构造函数和析构函数

    • 构造函数没有返回类型
    • 析构函数也没有返回类型
  4. 返回语句位置

    • 所有函数执行路径都必须有返回语句(除非返回类型为void)
    • 某些语言(如C)允许函数末尾没有return语句,但行为未定义

函数重载

函数重载是指在同一作用域内定义多个同名函数,但参数列表不同。函数重载的类型检查规则:

  1. 重载决议

    • 根据实参的数量和类型选择最合适的重载函数
    • 考虑隐式类型转换的代价
    • 选择转换代价最小的重载版本
  2. 重载条件

    • 函数名相同
    • 参数列表不同(参数数量、类型或顺序不同)
    • 返回类型不同不足以构成重载
  3. 模糊调用

    • 如果多个重载版本都同样适合,会产生模糊调用错误
    • 需要通过显式类型转换消除模糊性
  4. 名字查找

    • 函数重载与名字查找规则密切相关
    • 不同作用域的同名函数不会重载,而是隐藏

实用案例分析

函数声明和定义的类型检查

def check_function_declaration(self, func_decl):
    """检查函数声明的类型"""
    # 检查函数名是否已存在
    existing_func = self.symbol_table.lookup(func_decl.name)
    if existing_func:
        # 检查是否为重载
        if self.is_function_overload(existing_func, func_decl):
            # 是重载,允许
            pass
        else:
            self.errors.append(f"Type error: function {func_decl.name} already declared with same signature")
            return False
    
    # 检查参数类型
    param_types = []
    for param in func_decl.params:
        if not param.type:
            self.errors.append(f"Type error: parameter {param.name} has no type")
            return False
        param_types.append(param.type)
    
    # 创建函数类型
    func_type = {
        "return_type": func_decl.return_type,
        "param_types": param_types,
        "is_function": True
    }
    
    # 将函数信息插入符号表
    self.symbol_table.insert(func_decl.name, func_type)
    return True

def check_function_definition(self, func_def):
    """检查函数定义的类型"""
    # 首先检查函数声明
    if not self.check_function_declaration(func_def):
        return False
    
    # 进入函数作用域
    self.symbol_table.enter_scope()
    
    # 将参数插入符号表
    for param in func_def.params:
        self.symbol_table.insert(param.name, param.type)
    
    # 检查函数体
    for stmt in func_def.body:
        self.check_statement(stmt)
    
    # 检查返回语句
    if func_def.return_type != "void":
        # 检查是否所有路径都有返回值
        if not self.has_return_statement(func_def.body):
            self.errors.append(f"Type error: function {func_def.name} must return a value")
    
    # 退出函数作用域
    self.symbol_table.exit_scope()
    return True

函数调用的类型检查

def check_function_call(self, call_expr):
    """检查函数调用的类型"""
    # 查找函数
    func_symbol = self.symbol_table.lookup(call_expr.func_name)
    if not func_symbol:
        self.errors.append(f"Type error: undefined function {call_expr.func_name}")
        return "error"
    
    # 检查是否是函数
    if not func_symbol.get("is_function", False):
        self.errors.append(f"Type error: {call_expr.func_name} is not a function")
        return "error"
    
    # 检查参数数量
    expected_params = len(func_symbol["param_types"])
    actual_params = len(call_expr.args)
    if expected_params != actual_params:
        self.errors.append(f"Type error: function {call_expr.func_name} expects {expected_params} parameters, got {actual_params}")
        return "error"
    
    # 检查参数类型
    for i, (arg, expected_type) in enumerate(zip(call_expr.args, func_symbol["param_types"])):
        arg_type = self.check_expression(arg)
        if arg_type == "error":
            return "error"
        
        if not self.can_implicit_convert(arg_type, expected_type):
            self.errors.append(f"Type error: parameter {i+1} of {call_expr.func_name} expects {expected_type}, got {arg_type}")
            return "error"
    
    # 返回函数的返回类型
    return func_symbol["return_type"]

函数重载的类型检查

def is_function_overload(self, existing_func, new_func):
    """检查是否是函数重载"""
    if not existing_func.get("is_function", False):
        return False
    
    # 检查参数数量
    if len(existing_func["param_types"]) != len(new_func.params):
        return True
    
    # 检查参数类型
    for existing_type, new_param in zip(existing_func["param_types"], new_func.params):
        if existing_type != new_param.type:
            return True
    
    # 参数列表相同,不是重载
    return False

def resolve_overload(self, func_name, arg_types):
    """解析函数重载"""
    # 查找所有同名函数
    candidates = []
    for symbol in self.symbol_table.get_all_symbols():
        if symbol.name == func_name and symbol.get("is_function", False):
            candidates.append(symbol)
    
    if not candidates:
        return None
    
    # 筛选可行的重载版本
    viable_candidates = []
    for candidate in candidates:
        param_types = candidate["param_types"]
        if len(param_types) != len(arg_types):
            continue
        
        # 检查每个参数是否可转换
        viable = True
        conversion_cost = 0
        for arg_type, param_type in zip(arg_types, param_types):
            if arg_type == param_type:
                # 精确匹配,成本0
                pass
            elif self.can_implicit_convert(arg_type, param_type):
                # 隐式转换,成本1
                conversion_cost += 1
            else:
                viable = False
                break
        
        if viable:
            viable_candidates.append((candidate, conversion_cost))
    
    if not viable_candidates:
        return None
    
    # 选择转换成本最低的
    viable_candidates.sort(key=lambda x: x[1])
    best_candidate = viable_candidates[0][0]
    
    # 检查是否有多个成本相同的最佳候选
    if len(viable_candidates) > 1 and viable_candidates[0][1] == viable_candidates[1][1]:
        # 模糊调用
        self.errors.append(f"Type error: ambiguous call to function {func_name}")
        return None
    
    return best_candidate

返回语句的类型检查

def check_return_statement(self, stmt):
    """检查返回语句的类型"""
    # 获取当前函数的返回类型
    current_func = self.get_current_function()
    if not current_func:
        self.errors.append("Type error: return statement outside function")
        return
    
    return_type = current_func["return_type"]
    
    if stmt.expr:
        # 有返回表达式
        if return_type == "void":
            self.errors.append("Type error: void function cannot return a value")
            return
        
        # 检查返回表达式的类型
        expr_type = self.check_expression(stmt.expr)
        if expr_type == "error":
            return
        
        if not self.can_implicit_convert(expr_type, return_type):
            self.errors.append(f"Type error: cannot return {expr_type} from function returning {return_type}")
    else:
        # 无返回表达式
        if return_type != "void":
            self.errors.append(f"Type error: function returning {return_type} must return a value")

# 检查函数体是否所有路径都有返回值
def has_return_statement(self, statements):
    """检查语句块是否在所有路径上都有返回值"""
    # 简单实现:检查是否有return语句
    # 更复杂的实现需要进行控制流分析
    for stmt in statements:
        if stmt.type == "return":
            return True
        elif stmt.type == "if":
            # 检查if和else分支
            if self.has_return_statement(stmt.then_body):
                if stmt.else_body and self.has_return_statement(stmt.else_body):
                    return True
        elif stmt.type == "loop":
            # 循环体可能不执行,所以不能依赖循环内的return
            pass
    return False

实用案例分析

函数类型检查示例

函数声明和定义

// 函数声明
int add(int a, int b);
float add(float a, float b);  // 重载:参数类型不同

// 函数定义
int add(int a, int b) {
    return a + b;  // 正确:返回int
}

float add(float a, float b) {
    return a + b;  // 正确:返回float
}

// 错误的函数定义
int add(int a, int b) {
    // 错误:缺少返回语句
}

void print_message() {
    return "Hello";  // 错误:void函数不能返回值
}

函数调用

// 正确的函数调用
int result1 = add(10, 20);       // 调用int版本
float result2 = add(10.5, 20.5);  // 调用float版本

// 类型错误的函数调用
int result3 = add(10, 20.5);  // 错误:参数类型不匹配
int result4 = add(10);         // 错误:参数数量不匹配

// 隐式类型转换
float result5 = add(10, 20.5f);  // 正确:int转换为float

函数重载解析

// 函数重载
void func(int x);
void func(float x);
void func(int x, int y);

// 函数调用
func(10);       // 调用void func(int x)
func(10.5f);    // 调用void func(float x)
func(10, 20);   // 调用void func(int x, int y)

// 模糊调用
// func(10.5);  // 错误:double可以转换为int或float,产生模糊调用
func((float)10.5);  // 正确:显式转换消除模糊性

复杂函数类型检查

带默认参数的函数

// 带默认参数的函数
void func(int x, int y = 10);

// 函数调用
func(5);       // 正确:使用默认参数y=10
func(5, 20);   // 正确:提供所有参数

带可变参数的函数

// 带可变参数的函数
int sum(int count, ...);

// 函数调用
sum(3, 1, 2, 3);  // 正确:3个可变参数

函数指针

// 函数指针类型
int (*func_ptr)(int, int);

// 赋值
func_ptr = add;  // 正确:add函数与func_ptr类型匹配

// 调用
int result = func_ptr(10, 20);  // 正确:通过函数指针调用

小结

函数类型检查是编译器语义分析的重要组成部分:

  • 参数类型匹配:确保实参数量和类型与形参兼容,允许适当的隐式类型转换
  • 返回类型检查:确保return语句中的表达式类型与函数返回类型兼容
  • 函数重载:根据实参类型选择最合适的重载版本,处理模糊调用
  • 控制流分析:确保函数的所有执行路径都有适当的返回值
  • 特殊情况:处理默认参数、可变参数、函数指针等特殊情况

通过正确实现函数类型检查,可以在编译时发现许多潜在的函数调用错误,提高程序的正确性和可靠性。在实际编译器开发中,还需要考虑更多特殊情况和语言特性,如泛型函数、Lambda表达式等。

« 上一篇 语句类型检查 下一篇 » 类型推导