MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++函数编程基础与进阶

2023-12-054.8k 阅读

C++ 函数基础

函数定义与声明

在 C++ 中,函数是一段完成特定任务的可重用代码块。函数的定义包括函数头和函数体。函数头包含函数返回类型、函数名和参数列表,函数体则是实现具体功能的代码。

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

在上述代码中,int 是返回类型,表示函数返回一个整数;add 是函数名;(int a, int b) 是参数列表,定义了两个整数类型的参数 ab。函数体中通过 return 语句返回 ab 的和。

函数声明是告诉编译器函数的名称、返回类型和参数列表,它不需要包含函数体。函数声明通常放在头文件中,以便其他源文件能够使用该函数。

// 函数声明
int add(int a, int b); 

声明和定义的返回类型、函数名和参数列表必须完全一致。

参数传递方式

  1. 值传递 值传递是最常见的参数传递方式。在值传递中,函数接收的是实参的副本,对形参的修改不会影响实参。
void increment(int num) {
    num++;
}

int main() {
    int value = 5;
    increment(value);
    std::cout << "Value after increment: " << value << std::endl; 
    // 输出:Value after increment: 5
    return 0;
}

increment 函数中,numvalue 的副本,num 的增加不会改变 value 的值。

  1. 指针传递 指针传递允许函数通过指针修改实参的值。
void increment(int* num) {
    (*num)++;
}

int main() {
    int value = 5;
    increment(&value);
    std::cout << "Value after increment: " << value << std::endl; 
    // 输出:Value after increment: 6
    return 0;
}

这里 increment 函数接收一个指向 int 类型的指针 num,通过解引用指针 (*num) 来修改 value 的值。

  1. 引用传递 引用传递就像是给实参起了一个别名,对形参的修改直接影响实参。
void increment(int& num) {
    num++;
}

int main() {
    int value = 5;
    increment(value);
    std::cout << "Value after increment: " << value << std::endl; 
    // 输出:Value after increment: 6
    return 0;
}

increment 函数中的 num 实际上就是 value 的引用,所以对 num 的操作等同于对 value 的操作。

函数返回值

函数通过 return 语句返回值。返回值的类型必须与函数定义的返回类型一致。

double divide(double a, double b) {
    if (b != 0) {
        return a / b;
    } else {
        std::cerr << "Division by zero error!" << std::endl;
        return -1; // 这里返回 -1 表示错误情况
    }
}

divide 函数中,如果 b 不为零,返回 a 除以 b 的结果;否则输出错误信息并返回 -1 表示错误。

一个函数只能有一个 return 语句,但可以有多个分支返回不同的值。

C++ 函数进阶

函数重载

函数重载允许在同一作用域内定义多个同名函数,但这些函数的参数列表必须不同(参数个数或参数类型不同)。编译器会根据调用函数时提供的实参来选择合适的函数版本。

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

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

int main() {
    int result1 = add(2, 3); 
    double result2 = add(2.5, 3.5); 
    std::cout << "Integer addition result: " << result1 << std::endl; 
    std::cout << "Double addition result: " << result2 << std::endl; 
    return 0;
}

在上述代码中,有两个 add 函数,一个处理整数加法,另一个处理双精度浮点数加法。编译器根据实参的类型来决定调用哪个函数。

缺省参数

缺省参数是在函数声明或定义中为参数提供默认值。如果在调用函数时没有为该参数提供值,就会使用默认值。

void printMessage(const std::string& message, int times = 1) {
    for (int i = 0; i < times; ++i) {
        std::cout << message << std::endl;
    }
}

int main() {
    printMessage("Hello"); 
    printMessage("World", 3); 
    return 0;
}

printMessage 函数中,times 参数有一个默认值 1。当调用 printMessage("Hello") 时,times 使用默认值 1;当调用 printMessage("World", 3) 时,times 使用传入的值 3。

需要注意的是,缺省参数必须从参数列表的右侧开始定义,即有默认值的参数不能出现在没有默认值的参数之前。

内联函数

内联函数是一种特殊的函数,它在编译时会将函数体嵌入到调用处,而不是像普通函数那样进行函数调用的跳转。这样可以减少函数调用的开销,提高程序的执行效率。

使用 inline 关键字来定义内联函数。

inline int square(int num) {
    return num * num;
}

int main() {
    int result = square(5); 
    std::cout << "Square of 5 is: " << result << std::endl; 
    return 0;
}

对于简单的、短小的函数,使用内联函数可以有效提高性能。不过,过度使用内联函数可能会导致代码膨胀,因为函数体被多次嵌入,增加了可执行文件的大小。

递归函数

递归函数是指在函数的定义中使用自身调用的函数。递归函数必须有一个终止条件,否则会导致无限递归,最终耗尽系统资源。

int factorial(int n) {
    if (n == 0 || n == 1) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

int main() {
    int num = 5;
    int result = factorial(num); 
    std::cout << num << "! is: " << result << std::endl; 
    return 0;
}

factorial 函数中,当 n 为 0 或 1 时,函数返回 1,这是终止条件。否则,函数通过递归调用 factorial(n - 1) 来计算 n 的阶乘。

递归函数在解决一些具有递归性质的问题时非常有效,如计算斐波那契数列、遍历树形结构等。但由于递归调用会占用栈空间,对于大型问题,可能需要考虑使用迭代方法来避免栈溢出。

函数指针

函数指针是指向函数的指针变量。它可以像普通指针一样进行赋值、传递等操作,通过函数指针可以间接调用函数。

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

int main() {
    int (*funcPtr)(int, int) = add; 
    int result = funcPtr(3, 4); 
    std::cout << "Result of addition: " << result << std::endl; 
    return 0;
}

在上述代码中,funcPtr 是一个指向 add 函数的指针。通过 funcPtr 调用 add 函数,就像直接调用 add 函数一样。

函数指针在实现回调函数、函数表等场景中非常有用。例如,在排序算法中,可以使用函数指针来指定比较函数,以实现不同的排序规则。

函数模板

函数模板是一种通用的函数定义方式,可以用来创建一个通用的函数,该函数能够处理不同类型的数据,而不需要为每种数据类型都编写一个单独的函数。

template <typename T>
T max(T a, T b) {
    return (a > b)? a : b;
}

int main() {
    int intResult = max(5, 3); 
    double doubleResult = max(5.5, 3.3); 
    std::cout << "Max of 5 and 3 is: " << intResult << std::endl; 
    std::cout << "Max of 5.5 and 3.3 is: " << doubleResult << std::endl; 
    return 0;
}

在上述代码中,template <typename T> 声明了一个模板参数 T,表示一种通用的数据类型。max 函数可以接受任何类型的参数 ab,只要该类型支持 > 运算符。编译器会根据调用 max 函数时的实参类型,自动生成相应的函数版本。

函数模板提供了一种强大的代码复用机制,使得编写通用算法变得更加容易。同时,模板元编程也是基于函数模板和类模板发展起来的一种高级编程技术,可以在编译期进行计算和代码生成。

Lambda 表达式

Lambda 表达式是 C++11 引入的一种匿名函数,它可以在需要函数对象的地方直接定义和使用。Lambda 表达式的一般形式如下:

[capture list](parameter list) -> return type { function body }

其中,capture list 用于捕获外部作用域中的变量,parameter list 是参数列表,return type 是返回类型(可以省略,由编译器自动推导),function body 是函数体。

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};

    // 使用 Lambda 表达式进行排序
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a < b;
    });

    // 使用 Lambda 表达式进行遍历输出
    for_each(numbers.begin(), numbers.end(), [](int num) {
        std::cout << num << " ";
    });
    std::cout << std::endl;

    return 0;
}

在上述代码中,std::sort 函数的第三个参数是一个 Lambda 表达式,用于指定排序规则。for_each 函数的第二个参数也是一个 Lambda 表达式,用于对 numbers 中的每个元素执行输出操作。

Lambda 表达式在现代 C++ 编程中广泛应用于算法库、多线程编程等领域,它提供了一种简洁、灵活的方式来定义和使用函数对象。

函数与面向对象编程

成员函数

在 C++ 的类中,成员函数是定义在类内部的函数,它可以访问类的私有成员、保护成员和公共成员。成员函数可以分为普通成员函数和静态成员函数。

class Rectangle {
private:
    int width;
    int height;

public:
    Rectangle(int w, int h) : width(w), height(h) {}

    int area() {
        return width * height;
    }

    static int compareArea(const Rectangle& rect1, const Rectangle& rect2) {
        int area1 = rect1.area();
        int area2 = rect2.area();
        if (area1 > area2) return 1;
        if (area1 < area2) return -1;
        return 0;
    }
};

int main() {
    Rectangle rect1(5, 10);
    Rectangle rect2(10, 10);

    std::cout << "Area of rect1: " << rect1.area() << std::endl; 
    std::cout << "Comparison result: " << Rectangle::compareArea(rect1, rect2) << std::endl; 
    return 0;
}

Rectangle 类中,area 是普通成员函数,它可以访问类的私有成员 widthheightcompareArea 是静态成员函数,它不依赖于具体的对象实例,可以通过类名直接调用。静态成员函数只能访问静态成员变量和其他静态成员函数。

虚函数与多态

虚函数是在基类中声明为 virtual 的函数,它允许在派生类中被重写,以实现多态行为。多态是面向对象编程的重要特性之一,它使得通过基类指针或引用调用函数时,能够根据实际对象的类型来调用相应的函数版本。

class Shape {
public:
    virtual double area() {
        return 0;
    }
};

class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    double area() override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width;
    double height;

public:
    Rectangle(double w, double h) : width(w), height(h) {}

    double area() override {
        return width * height;
    }
};

int main() {
    Shape* shapes[2];
    shapes[0] = new Circle(5);
    shapes[1] = new Rectangle(4, 6);

    for (int i = 0; i < 2; ++i) {
        std::cout << "Area of shape " << i + 1 << " is: " << shapes[i]->area() << std::endl; 
    }

    for (int i = 0; i < 2; ++i) {
        delete shapes[i];
    }

    return 0;
}

在上述代码中,Shape 类中的 area 函数被声明为虚函数。CircleRectangle 类继承自 Shape 类,并分别重写了 area 函数。通过 Shape 类型的指针数组,能够根据实际对象的类型(CircleRectangle)调用相应的 area 函数版本,从而实现多态。

纯虚函数与抽象类

纯虚函数是在基类中声明为 virtual 且赋值为 0 的函数,即 virtual return_type function_name(parameter_list) = 0;。包含纯虚函数的类称为抽象类,抽象类不能实例化对象,只能作为其他类的基类。

class Shape {
public:
    virtual double area() = 0;
};

class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    double area() override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width;
    double height;

public:
    Rectangle(double w, double h) : width(w), height(h) {}

    double area() override {
        return width * height;
    }
};

在这个例子中,Shape 类是抽象类,因为它包含纯虚函数 areaCircleRectangle 类继承自 Shape 类,并实现了 area 函数,所以它们可以实例化对象。纯虚函数和抽象类常用于定义接口,使得派生类必须实现特定的功能,从而实现更严格的代码规范和多态性。

通过深入理解和掌握 C++ 函数编程的基础与进阶知识,开发者能够编写出更加高效、灵活和可维护的代码。无论是在面向过程编程还是面向对象编程中,函数都是实现复杂功能的关键组成部分。在实际应用中,需要根据具体需求选择合适的函数特性,以达到最佳的编程效果。