C++——6.函数基础:定义、声明、参数传递(值、指针、引用)、重载.

一、函数定义

函数是一段可重复使用的代码块,用来完成特定任务。在 C++ 中,函数定义包含以下几个部分:

1.返回值类型:函数返回的数据类型,可以是内置类型、类类型、指针或引用,无返回则写 void。

2.函数名:标识函数的名称,遵循标识符命名规则。

3.参数列表:逗号分隔的形参声明,可为空(写 void 或留空)。

4.函数体:花括号内的语句,实现具体功能。

示例:计算两个整数之和的函数

cpp 复制代码
#include <iostream>
#include <vector>

int add(int a, int b);
int main() {
    int addNum = add(1, 2);
    printf("add(1, 2)的值为:%d",addNum);
    return 0;
}

int add(int a, int b) {
    return a + b;
}

二、函数声明(函数原型)

函数声明告诉编译器函数的名称、返回值类型及参数类型,不包含函数体,以分号结束。

作用:允许在定义之前调用函数,或者跨文件使用函数。通常放在头文件中。

cpp 复制代码
#include <iostream>

// 函数声明(原型)
int add(int, int);   // 参数名可省略,保留类型即可

int main() {
    std::cout << add(3, 4) << std::endl; // 在定义前调用,合法
    return 0;
}

// 函数定义
int add(int a, int b) {
    return a + b;
}

定义与声明的区别:

1.声明仅提供接口信息,不分配执行代码;

2.定义包含函数体,决定函数的行为;

3.一个函数可有多次声明,但只能有一次定义(单一定义规则 ODR)。

三、参数传递方式

C++ 函数参数的传递机制主要包括 值传递、指针传递、引用传递。理解它们的区别是写出正确、高效代码的关键。

1. 值传递

形参获得实参的副本,在函数内对形参的修改不影响外部的实参。

cpp 复制代码
#include <iostream>
#include <vector>

void SwapByValue(int x, int y);
int main() {
    int a = 10;
    int b = 20;
    SwapByValue(a, b);
    // a 仍为 10,b 仍为 20
    printf("a的值为:%d\n", a);
    printf("b的值为:%d", b);
    return 0;
}

void SwapByValue(int x, int y)
{
    int temp = x;
    x = y;
    y = temp;   // 只交换了副本
}

特点:

1.实参到形参发生拷贝,适合小型对象(如 int、double)。

2.大型对象(如 std::vector)拷贝开销大,应避免值传递。

3.函数内部对形参的修改不影响原对象,天然安全。

2. 指针传递

将实参的地址作为值传给指针形参。函数内可通过解引用操作原对象。

cpp 复制代码
#include <iostream>
#include <vector>

void SwapByPointer(int* x, int* y);

int main() {
    int a = 10;
    int b = 20;
    SwapByPointer(&a, &b);
    // a 为 20,b 为 10
    printf("a的值为:%d\n", a);
    printf("b的值为:%d", b);
    return 0;
}

void SwapByPointer(int* x, int* y) {
    int temp = *x;
    *x = *y;
    *y = temp;   // 通过地址修改了实参
}

特点:

1.传递的是地址值,仅拷贝一个地址(通常 4/8 字节),避免大对象拷贝。

2.可修改指针所指对象,也可用 const int* 指向只读对象。

3.指针可为空(nullptr),调用者必须保证指针有效,或被调函数需检查空指针。

4.适合需要"可选"参数(允许不传递对象)的场景,或与 C 风格接口交互。

3. 引用传递

引用是对象的别名,传递引用不会产生拷贝,直接操作原对象。

cpp 复制代码
#include <iostream>
#include <vector>

void SwapByReference(int& x, int& y);

int main() {
    int a = 10;
    int b = 20;
    // 无需取地址
    SwapByReference(a, b);
    // a 为 20,b 为 10
    printf("a的值为:%d\n", a);
    printf("b的值为:%d", b);
    return 0;
}


void SwapByReference(int& x, int& y) {
    int temp = x;
    x = y;
    y = temp;   // 直接修改实参
}

特点:

1.语法简洁,像值传递一样使用,没有解引用开销。

2.引用必须初始化,不能为空,通常比指针更安全。

3.对引用的操作即是对原对象的操作。

4.是传递大型对象的首选方式(尤其配合 const)。

三种传递方式对比

传递方式 是否拷贝原对象 能否修改实参 是否可能为空 典型用途
值传递 拷贝整个对象 不可能 小型不可变参数
指针传递 拷贝地址(4/8字节) 是(非 const 时) 是(可传 nullptr) C 接口、可选参数、需要改变指向
引用传递 不拷贝 是(非 const 时) 不可能 大型对象、输出参数、运算符重载

四、函数重载

函数重载允许在同一作用域内定义多个同名函数,只要它们的参数列表不同。

参数列表不同的含义:

1.参数个数不同

2.参数类型不同

3.参数顺序不同(针对不同类型)

返回值类型不参与重载解析,仅返回类型不同无法构成重载。

示例:不同类型的打印函数

cpp 复制代码
#include <iostream>
#include <vector>

void print(int i);
void print(double d);
void print(const std::string& s);
int main() {
    print(10);        // 调用 print(int)
    print(3.14);      // 调用 print(double)
    print("hello");   // 调用 print(const std::string&),"hello" 可转换为 string
}


void print(int i) {
    std::cout << "整数: " << i << std::endl;
}

void print(double d) {
    std::cout << "双精度: " << d << std::endl;
}

void print(const std::string& s) {
    std::cout << "字符串: " << s << std::endl;
}

重载决议:编译器根据实参类型寻找最佳匹配,规则大致为:

1.精确匹配(实参与形参类型完全相同)。

2.提升匹配(如 char → int、float → double)。

3。标准转换匹配(如 int → double、派生类指针→基类指针)。

4.用户定义转换(如转换构造函数、类型转换运算符)。

注意事项与常见陷阱:

值传递与引用传递重载可能产生歧义:

cpp 复制代码
#include <iostream>
#include <vector>

void f(int x);
void f(int& x);

int main() {
    int a = 1;
    f(a);
    f(a);
    return 0;
}
void f(int x) {
    x = 10;
    printf("f(int x):%d", x);
}
void f(int& x) {
    x = 10;
    printf("f(int& x):%d", x);
}

编译时报错:有多个 重载函数 "f" 实例与参数列表匹配

仅靠 const 区分引用参数是合法的,且常用来区分读写版本:

cpp 复制代码
#include <iostream>
#include <vector>

void g(const int& x);  // 接受 const 对象、右值
void g(int& x);        // 接受非 const 左值

int main() {
    int a = 1;
    const int b = 12;
    g(a);
    std::cout<< std::endl;
    g(b);
    return 0;
}
void g(const int& x) {
    printf("g(int x):%d", x);
}
void g(int& x) {
    printf("g(int& x):%d", x);
}

带有默认参数时,避免产生不同重载间的调用模糊,例如 void h(int a, double b = 0.0) 和 void h(int a) 同时存在,调用 h(5) 将报错。

csharp 复制代码
#include <iostream>
#include <vector>

// 带有默认参数的版本
void h(int a, double b = 0.0) {
    std::cout << "h(int, double) called: a=" << a << ", b=" << b << std::endl;
}

// 单参数重载版本
void h(int a) {
    std::cout << "h(int) called: a=" << a << std::endl;
}

int main() {
    int a = 5;
    h(a);
    return 0;
}

编译时报错:E0308 有多个 重载函数 "h" 实例与参数列表匹配。

顶层 const 不影响重载:void f(int) 和 void f(const int) 视为同一签名(因为值传递时顶层 const 被忽略)。但 const 引用或指针则属于不同签名:void f(int*) 和 void f(const int*) 是不同的。

演示"底层 const 影响重载",应该删除值传递版本,只保留指针和引用重载:

cpp 复制代码
#include <iostream>

// 指针重载:底层 const 决定不同签名
void f(int* p) {
    std::cout << "f(int*) called, *p = " << (p ? *p : 0) << std::endl;
}

void f(const int* p) {
    std::cout << "f(const int*) called, *p = " << (p ? *p : 0) << std::endl;
}

// 引用重载:底层 const 决定不同签名
void f(int& r) {
    std::cout << "f(int&) called, r = " << r << std::endl;
}

void f(const int& r) {
    std::cout << "f(const int&) called, r = " << r << std::endl;
}

int main() {
    // ---------- 指针重载调用演示 ----------
    int a = 10;
    const int b = 20;

    f(&a);                   // 传入 int*,匹配 f(int*)
    f(&b);                   // 传入 const int*,匹配 f(const int*)

    // 若需要传递空指针,必须显式转换以消除歧义
    f(static_cast<int*>(nullptr));        // 调 f(int*)
    f(static_cast<const int*>(nullptr));  // 调 f(const int*)

    // ---------- 引用重载调用演示 ----------
    int x = 100;
    const int y = 200;

    f(x);                    // x 是 int 左值,匹配 f(int&)
    f(y);                    // y 是 const int 左值,匹配 f(const int&)
    f(300);                  // 300 是右值,只能绑定到 const 引用,匹配 f(const int&)

    return 0;
}