《C++ Primer》导学系列:第 6 章 - 函数

news2024/10/16 16:01:21

6.1 函数基础

6.1.1 基本概念

函数是C++程序的基本组成单元,用于将代码组织成可以复用的模块。函数通过函数名进行调用,并且可以接受参数和返回值。函数的定义包括函数头和函数体,其中函数头描述了函数的接口,函数体包含了具体的实现代码。

函数的定义

函数的定义通常包括返回类型、函数名、参数列表和函数体。函数头声明了函数的返回类型和参数类型,函数体则包含实际执行的代码。

示例代码
#include <iostream>

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

int main() {
    int result = add(3, 4);  // 调用 add 函数
    std::cout << "Result: " << result << std::endl;  // 输出结果
    return 0;
}

在这个示例中,add函数接受两个整数参数,并返回它们的和。main函数调用add函数并输出结果。

6.1.2 函数声明

在使用函数之前,必须声明函数。函数声明告诉编译器函数的名称、返回类型和参数类型,但不需要具体实现。函数声明通常放在头文件中,函数定义放在源文件中。

示例代码
#include <iostream>

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

int main() {
    int result = add(3, 4);  // 调用 add 函数
    std::cout << "Result: " << result << std::endl;  // 输出结果
    return 0;
}

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

在这个示例中,add函数的声明在main函数之前,定义在main函数之后。

6.1.3 形参和实参

函数在定义时使用形参(形式参数)来指定参数类型和名称,在调用时使用实参(实际参数)来传递具体的值。形参在函数定义时声明,实参在函数调用时提供。

示例代码
#include <iostream>

// 函数定义
void printSum(int a, int b) {
    std::cout << "Sum: " << a + b << std::endl;  // 输出 a 和 b 的和
}

int main() {
    int x = 5;
    int y = 7;
    printSum(x, y);  // 调用 printSum 函数,x 和 y 是实参
    return 0;
}

在这个示例中,printSum函数接受两个整数形参ab,在调用时传递具体的实参xy

6.1.4 返回类型

函数可以返回一个值,返回类型可以是任何基本类型、指针、引用、类类型等。如果函数不返回值,则返回类型为void

示例代码
#include <iostream>

// 函数定义,返回类型为 int
int multiply(int a, int b) {
    return a * b;  // 返回 a 和 b 的乘积
}

// 函数定义,返回类型为 void
void printMessage() {
    std::cout << "Hello, World!" << std::endl;
}

int main() {
    int result = multiply(3, 4);  // 调用 multiply 函数
    std::cout << "Result: " << result << std::endl;  // 输出结果

    printMessage();  // 调用 printMessage 函数
    return 0;
}

在这个示例中,multiply函数返回两个整数的乘积,而printMessage函数不返回值。

6.1.5 函数调用

函数调用是通过函数名和参数列表来实现的。函数调用时,实参的值被传递给形参,并执行函数体中的代码。

示例代码
#include <iostream>

// 函数定义
int subtract(int a, int b) {
    return a - b;  // 返回 a 和 b 的差
}

int main() {
    int a = 10;
    int b = 3;
    int result = subtract(a, b);  // 调用 subtract 函数
    std::cout << "Result: " << result << std::endl;  // 输出结果
    return 0;
}

在这个示例中,subtract函数接受两个整数参数并返回它们的差,在main函数中调用并输出结果。

重点与难点分析

重点

  1. 函数定义与声明:掌握函数的定义和声明方法,理解函数头和函数体的组成。
  2. 形参和实参:理解形参和实参的概念及其在函数定义和调用中的作用。
  3. 返回类型:掌握函数返回类型的定义方法,理解void类型的应用场景。

难点

  1. 函数调用:初学者需要通过实践掌握函数调用的语法,理解实参与形参的对应关系。
  2. 返回类型的应用:在复杂的程序中,合理设计和使用函数的返回类型,特别是指针和引用类型的返回值。

练习题解析

练习6.1:编写一个函数,接受一个整数参数并打印该整数的平方。

    • 示例代码
#include <iostream>

void printSquare(int x) {
    std::cout << "Square: " << x * x << std::endl;
}

int main() {
    printSquare(4);
    return 0;
}

练习6.2:编写一个函数,接受一个字符参数并返回该字符的大写形式。

    • 示例代码
#include <iostream>
#include <cctype>

char toUpperCase(char c) {
    return std::toupper(c);
}

int main() {
    char c = 'a';
    char result = toUpperCase(c);
    std::cout << "Uppercase: " << result << std::endl;
    return 0;
}

练习6.3:编写一个void函数,不接受任何参数,并打印一条问候消息。

    • 示例代码
#include <iostream>

void greet() {
    std::cout << "Hello, welcome to C++ programming!" << std::endl;
}

int main() {
    greet();
    return 0;
}

总结与提高

本节总结

  1. 学习了函数的基本概念,掌握了函数的定义、声明和调用方法。
  2. 理解了形参和实参的作用,掌握了返回类型的定义和应用。
  3. 通过示例代码和练习题,加深了对函数基础知识的理解和应用。

提高建议

  1. 多练习函数的定义和调用:通过编写各种包含函数的程序,熟悉函数的定义、声明和调用方法。
  2. 深入理解形参和实参:通过实践掌握形参和实参的对应关系,确保函数调用时参数传递正确。
  3. 合理设计函数的返回类型:在编写复杂程序时,合理设计和使用函数的返回类型,提高代码的可读性和维护性。

6.2 参数传递

6.2.1 传值参数

按值传递是将实参的值复制一份传递给函数的形参。函数对形参的修改不会影响实参的值。按值传递适用于传递基本数据类型(如int, double等)和小型的结构体或类对象。

示例代码
#include <iostream>

void increment(int x) {
    ++x;  // 仅修改形参 x 的值
    std::cout << "Inside function: " << x << std::endl;
}

int main() {
    int a = 5;
    increment(a);  // 按值传递,实参 a 的值不会改变
    std::cout << "Outside function: " << a << std::endl;
    return 0;
}

在这个示例中,increment函数按值传递参数x,修改形参不会影响实参a

6.2.2 传引用参数

按引用传递是将实参的引用传递给函数的形参,函数对形参的修改将直接影响实参。按引用传递适用于需要在函数内部修改实参值的情况,或者传递较大的对象以避免不必要的复制开销。

示例代码
#include <iostream>

void increment(int &x) {
    ++x;  // 修改形参 x 的值,会影响实参
    std::cout << "Inside function: " << x << std::endl;
}

int main() {
    int a = 5;
    increment(a);  // 按引用传递,实参 a 的值会改变
    std::cout << "Outside function: " << a << std::endl;
    return 0;
}

在这个示例中,increment函数按引用传递参数x,修改形参会影响实参a

6.2.3 const形参和实参

为了防止函数修改传递的实参,可以使用常量引用(const reference)。常量引用既避免了按值传递的复制开销,又保证了实参在函数内部不被修改。

示例代码
#include <iostream>

void printValue(const int &x) {
    std::cout << "Value: " << x << std::endl;
    // 不能修改 x 的值
}

int main() {
    int a = 5;
    printValue(a);  // 按常量引用传递,实参 a 的值不会改变
    return 0;
}

在这个示例中,printValue函数按常量引用传递参数x,保证形参不会修改实参a

6.2.4 数组形参

在C++中,数组作为函数参数时,会退化为指针。这意味着传递的是数组首元素的地址,而不是整个数组的值。

示例代码
#include <iostream>

void printArray(int arr[], int size) {
    for (int i = 0; i < size; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
}

int main() {
    int myArray[] = {1, 2, 3, 4, 5};
    int size = sizeof(myArray) / sizeof(myArray[0]);
    printArray(myArray, size);  // 按指针传递数组
    return 0;
}

在这个示例中,printArray函数接受一个数组指针和数组大小,并打印数组的元素。

6.2.5 main函数的形参

main函数可以接受两个参数,通常用于命令行参数的传递。参数argc表示参数的个数,参数argv是一个字符指针数组,包含传递的参数。

示例代码
#include <iostream>

int main(int argc, char *argv[]) {
    std::cout << "Number of arguments: " << argc << std::endl;
    for (int i = 0; i < argc; ++i) {
        std::cout << "Argument " << i << ": " << argv[i] << std::endl;
    }
    return 0;
}

在这个示例中,main函数接受命令行参数并打印出来。

6.2.6 含有可变参数的函数

C++提供了可变参数模板(variadic templates)来处理参数数量不确定的情况。通过使用省略号(...)表示可变参数列表。

示例代码
#include <iostream>
#include <cstdarg>

void printValues(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    while (*fmt != '\0') {
        switch (*fmt++) {
            case 'i': // int
                std::cout << va_arg(args, int) << " ";
                break;
            case 'd': // double
                std::cout << va_arg(args, double) << " ";
                break;
            case 'c': // char
                std::cout << static_cast<char>(va_arg(args, int)) << " ";
                break;
        }
    }
    va_end(args);
    std::cout << std::endl;
}

int main() {
    printValues("icd", 42, 'a', 3.14);
    return 0;
}

在这个示例中,printValues函数使用可变参数列表处理不同类型和数量的参数。

重点与难点分析

重点

  1. 按值传递和按引用传递:掌握按值传递和按引用传递的基本概念和区别,理解它们在参数传递中的应用场景。
  2. const形参和实参:理解常量引用的用法和优势,特别是在防止修改实参时的应用。
  3. 数组形参:掌握数组作为函数参数时的传递方式,理解数组退化为指针的概念。
  4. main函数的形参:理解main函数接受命令行参数的机制及其应用。
  5. 含有可变参数的函数:掌握可变参数函数的定义和使用方法,理解可变参数模板的应用。

难点

  1. 参数传递方式的选择:初学者需要通过实践掌握不同参数传递方式的选择,根据具体情况选择最合适的传递方式。
  2. 数组形参的应用:在使用数组形参时,理解数组退化为指针的影响及其在函数中的应用。
  3. 可变参数函数的设计:在设计可变参数函数时,确保参数类型和数量的正确处理,避免潜在的错误。

练习题解析

  1. 练习6.5:编写一个函数,按值传递一个整数参数,并尝试修改该参数的值。
    • 示例代码
#include <iostream>

void modifyValue(int x) {
    x = 10;
    std::cout << "Inside function: " << x << std::endl;
}

int main() {
    int a = 5;
    modifyValue(a);  // 按值传递,实参 a 的值不会改变
    std::cout << "Outside function: " << a << std::endl;
    return 0;
}
  1. 练习6.6:编写一个函数,按引用传递一个整数参数,并修改该参数的值。
    • 示例代码
#include <iostream>

void modifyValue(int &x) {
    x = 10;
    std::cout << "Inside function: " << x << std::endl;
}

int main() {
    int a = 5;
    modifyValue(a);  // 按引用传递,实参 a 的值会改变
    std::cout << "Outside function: " << a << std::endl;
    return 0;
}
  1. 练习6.7:编写一个函数,按常量引用传递一个字符串参数,并尝试修改该参数的值。
    • 示例代码
#include <iostream>
#include <string>

void printString(const std::string &str) {
    std::cout << "String: " << str << std::endl;
    // str = "New String";  // 错误:不能修改常量引用
}

int main() {
    std::string s = "Hello, World!";
    printString(s);  // 按常量引用传递,实参 s 的值不会改变
    return 0;
}
  1. 练习6.8:编写一个函数,按值传递指针参数,修改指针指向的值。
    • 示例代码
#include <iostream>

void modifyPointer(int *p) {
    *p = 10;
    std::cout << "Inside function: " << *p << std::endl;
}

int main() {
    int a = 5;
    modifyPointer(&a);  // 按值传递指针,实参 a 的值会改变
    std::cout << "Outside function: " << a << std::endl;
    return 0;
}
  1. 练习6.9:编写一个函数,按引用传递指针参数,修改指针的地址。
    • 示例代码
#include <iostream>

void setPointer(int *&p, int *newAddress) {
    p = newAddress;
}

int main() {
    int a = 5;
    int b = 10;
    int *ptr = &a;
    setPointer(ptr, &b);  // 修改 ptr 使其指向 b
    std::cout << "Pointer now points to: " << *ptr << std::endl;
    return 0;
}
  1. 练习6.10:编写一个main函数,接受命令行参数并输出这些参数。
    • 示例代码
#include <iostream>

int main(int argc, char *argv[]) {
    std::cout << "Number of arguments: " << argc << std::endl;
    for (int i = 0; i < argc; ++i) {
        std::cout << "Argument " << i << ": " << argv[i] << std::endl;
    }
    return 0;
}
  1. 练习6.11:编写一个含有可变参数的函数,接受不同类型的参数并输出它们的值。
    • 示例代码
#include <iostream>
#include <cstdarg>

void printValues(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    while (*fmt != '\0') {
        switch (*fmt++) {
            case 'i': // int
                std::cout << va_arg(args, int) << " ";
                break;
            case 'd': // double
                std::cout << va_arg(args, double) << " ";
                break;
            case 'c': // char
                std::cout << static_cast<char>(va_arg(args, int)) << " ";
                break;
        }
    }
    va_end(args);
    std::cout << std::endl;
}

int main() {
    printValues("icd", 42, 'a', 3.14);
    return 0;
}

总结与提高

本节总结

  1. 学习了参数传递的多种方式,包括按值传递、按引用传递、常量引用、数组形参、main函数的形参以及含有可变参数的函数。
  2. 通过示例代码和练习题,深入理解了不同参数传递方式的应用场景和实现方法。
  3. 掌握了参数传递中的关键概念和技术,能够在实际编程中灵活运用这些知识。

提高建议

  1. 多练习不同方式的参数传递:通过编写各种包含不同参数传递方式的程序,熟悉按值传递、按引用传递和常量引用的用法。
  2. 深入理解数组形参和可变参数:通过实践掌握数组形参和可变参数的使用方法,确保在实际应用中正确处理这些参数。
  3. 合理选择参数传递方式:在编写复杂程序时,根据具体情况选择最合适的参数传递方式,提高代码的效率和可读性。

6.3 返回类型和return语句

6.3.1 返回类型

函数的返回类型是函数在执行完毕后返回给调用者的值的类型。C++允许函数返回基本类型、指针、引用、类类型、数组(通过指针)等。如果函数不返回值,其返回类型为void

基本语法
returnType functionName(parameters) {
    // 函数体
    return value;  // 返回值
}

6.3.2 返回值的基本类型

函数可以返回基本数据类型(如int, double等)。在函数体中,使用return语句将值返回给调用者。

示例代码
#include <iostream>

int add(int a, int b) {
    return a + b;  // 返回整数
}

double multiply(double x, double y) {
    return x * y;  // 返回浮点数
}

int main() {
    int sum = add(3, 4);
    double product = multiply(2.5, 4.0);

    std::cout << "Sum: " << sum << std::endl;
    std::cout << "Product: " << product << std::endl;
    return 0;
}

在这个示例中,add函数返回两个整数的和,multiply函数返回两个浮点数的乘积。

6.3.3 返回引用类型

函数可以返回引用类型。当函数返回引用时,调用者接收到的实际上是原对象的引用,而不是一个新的对象。这对于避免拷贝大对象和实现链式调用很有用。

示例代码
#include <iostream>

int& getElement(int arr[], int index) {
    return arr[index];  // 返回数组元素的引用
}

int main() {
    int myArray[5] = {1, 2, 3, 4, 5};
    getElement(myArray, 2) = 10;  // 修改数组第三个元素的值

    for (int i = 0; i < 5; ++i) {
        std::cout << myArray[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,getElement函数返回数组元素的引用,允许调用者直接修改数组元素的值。

6.3.4 返回指针类型

函数可以返回指针类型。返回指针时,需要注意指针的生命周期,确保指针指向的内存在函数返回后仍然有效。

示例代码
#include <iostream>

int* findMax(int* a, int* b) {
    return (*a > *b) ? a : b;  // 返回指向较大值的指针
}

int main() {
    int x = 10, y = 20;
    int* max = findMax(&x, &y);

    std::cout << "Max value: " << *max << std::endl;
    return 0;
}

在这个示例中,findMax函数返回指向较大值的指针。

6.3.5 返回void类型

如果函数不需要返回值,可以将返回类型定义为void。这样的函数通常用于执行一些操作而不返回结果。

示例代码
#include <iostream>

void printMessage() {
    std::cout << "Hello, World!" << std::endl;  // 不返回值
}

int main() {
    printMessage();
    return 0;
}

在这个示例中,printMessage函数不返回值。

重点与难点分析

重点

  1. 返回类型的选择:掌握基本类型、引用类型、指针类型和void类型的返回方法及其适用场景。
  2. return语句的使用:理解return语句的作用,掌握在不同返回类型函数中的使用方法。

难点

  1. 返回引用类型的安全性:初学者需要理解返回引用类型时的安全性问题,避免返回局部变量的引用。
  2. 返回指针类型的生命周期管理:掌握返回指针类型时的生命周期管理,确保返回的指针在函数返回后仍然有效。

练习题解析

  1. 练习6.12:编写一个函数,返回两个整数中的较大值。
    • 示例代码
#include <iostream>

int max(int a, int b) {
    return (a > b) ? a : b;
}

int main() {
    int x = 10, y = 20;
    std::cout << "Max: " << max(x, y) << std::endl;
    return 0;
}
  1. 练习6.13:编写一个函数,返回字符串的引用,并修改该字符串的值。
    • 示例代码
#include <iostream>
#include <string>

std::string& modifyString(std::string &str) {
    str += " modified";
    return str;
}

int main() {
    std::string s = "Original";
    std::cout << modifyString(s) << std::endl;
    return 0;
}
  1. 练习6.14:编写一个函数,返回指向数组元素的指针。
    • 示例代码
#include <iostream>

int* findElement(int arr[], int size, int value) {
    for (int i = 0; i < size; ++i) {
        if (arr[i] == value) {
            return &arr[i];
        }
    }
    return nullptr;
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    int* elem = findElement(arr, size, 3);

    if (elem) {
        std::cout << "Element found: " << *elem << std::endl;
    } else {
        std::cout << "Element not found." << std::endl;
    }

    return 0;
}
  1. 练习6.15:编写一个void函数,打印一个消息,并在满足特定条件时提前返回。
    • 示例代码
#include <iostream>

void checkEven(int x) {
    if (x % 2 == 0) {
        std::cout << x << " is even." << std::endl;
        return;
    }
    std::cout << x << " is odd." << std::endl;
}

int main() {
    checkEven(10);
    checkEven(7);
    return 0;
}

总结与提高

本节总结

  1. 学习了函数的返回类型,掌握了返回基本类型、引用类型、指针类型和void类型的方法。
  2. 理解了return语句的作用,掌握了在不同返回类型函数中的使用方法。
  3. 通过示例代码和练习题,加深了对函数返回类型和return语句的理解和应用。

提高建议

  1. 多练习不同返回类型的函数:通过编写各种包含不同返回类型的函数,熟悉返回基本类型、引用类型、指针类型和void类型的方法。
  2. 深入理解返回引用和指针的安全性:通过实践掌握返回引用和指针的安全性,避免返回局部变量的引用,确保返回的指针在函数返回后仍然有效。
  3. 合理使用return语句:在编写函数时,合理使用return语句,提高代码的可读性和维护性。

6.4 函数重载

6.4.1 基本概念

函数重载是指在同一作用域内定义多个具有相同名称但参数列表不同的函数。重载函数的参数列表必须在参数的数量或类型上有所不同,以使编译器能够根据调用时提供的实参来选择合适的函数。函数重载使程序设计更加灵活和易于维护。

6.4.2 定义重载函数

重载函数是通过定义多个同名但参数列表不同的函数来实现的。编译器在调用重载函数时,根据传递的参数类型和数量来决定调用哪个版本的函数。

示例代码
#include <iostream>

// 重载函数定义
void print(int a) {
    std::cout << "Integer: " << a << std::endl;
}

void print(double b) {
    std::cout << "Double: " << b << std::endl;
}

void print(const std::string &c) {
    std::cout << "String: " << c << std::endl;
}

int main() {
    print(10);           // 调用第一个 print 函数
    print(3.14);         // 调用第二个 print 函数
    print("Hello");      // 调用第三个 print 函数
    return 0;
}

在这个示例中,定义了三个名为print的重载函数,分别接受intdoublestd::string类型的参数。

6.4.3 参数类型转换

在函数重载中,编译器可以进行参数类型转换,以找到最匹配的函数。默认参数和类型提升(如从intdouble)也会影响重载函数的选择。

示例代码
#include <iostream>

void display(int a) {
    std::cout << "Integer: " << a << std::endl;
}

void display(double b) {
    std::cout << "Double: " << b << std::endl;
}

int main() {
    display(10);    // 调用 display(int)
    display(3.14);  // 调用 display(double)
    display('A');   // 类型转换,调用 display(int)
    return 0;
}

在这个示例中,字符'A'被转换为int类型,因此调用了display(int)函数。

6.4.4 默认参数与重载

在定义重载函数时,可以为某些参数指定默认值。如果某个重载函数与带默认参数的函数存在二义性,编译器将无法确定调用哪个函数。

示例代码
#include <iostream>

void show(int a, int b = 10) {
    std::cout << "a: " << a << ", b: " << b << std::endl;
}

void show(double a) {
    std::cout << "a: " << a << std::endl;
}

int main() {
    show(5);      // 调用 show(int, int),使用默认参数
    show(3.14);   // 调用 show(double)
    return 0;
}

在这个示例中,show(5)调用了带有默认参数的show(int, int)函数,而show(3.14)调用了show(double)函数。

6.4.5 函数重载的限制

函数重载有一些限制条件,例如,不能仅通过返回类型的不同来重载函数。函数签名(函数名和参数列表)必须在参数的数量或类型上有所区别。

错误示例
#include <iostream>

// 错误:仅通过返回类型不同来重载函数
int getValue() {
    return 10;
}

double getValue() {
    return 3.14;
}

int main() {
    std::cout << getValue() << std::endl;  // 编译错误
    return 0;
}

在这个示例中,两个getValue函数仅通过返回类型不同来区分,违反了函数重载的规则,导致编译错误。

6.4.6 重载与命名空间

在不同的命名空间中,可以定义同名的重载函数。命名空间提供了一个机制,用于组织代码并避免命名冲突。

示例代码
#include <iostream>

namespace FirstNamespace {
    void func() {
        std::cout << "Inside FirstNamespace" << std::endl;
    }
}

namespace SecondNamespace {
    void func() {
        std::cout << "Inside SecondNamespace" << std::endl;
    }
}

int main() {
    FirstNamespace::func();  // 调用 FirstNamespace 中的 func
    SecondNamespace::func(); // 调用 SecondNamespace 中的 func
    return 0;
}

在这个示例中,不同命名空间中的同名函数不会冲突,可以通过命名空间限定符来调用。

重点与难点分析

重点

  1. 函数重载的基本概念和实现方法:掌握如何通过定义参数列表不同的同名函数来实现函数重载。
  2. 参数类型转换与重载选择:理解编译器在函数重载中进行的参数类型转换和选择机制。
  3. 默认参数与重载:了解在重载函数中使用默认参数时可能产生的二义性问题。

难点

  1. 参数类型转换的影响:初学者需要通过实践理解参数类型转换如何影响函数重载的选择,避免意外的重载函数调用。
  2. 函数重载的限制:掌握函数重载的规则,避免违反规则导致编译错误。

总结与提高

本节总结

  1. 学习了函数重载的基本概念,掌握了通过定义参数列表不同的同名函数来实现函数重载的方法。
  2. 理解了参数类型转换、默认参数与重载的关系,能够避免重载函数中的二义性问题。
  3. 通过示例代码和练习题,加深了对函数重载的理解和应用。

提高建议

  1. 多练习函数重载的定义与调用:通过编写各种包含重载函数的程序,熟悉重载函数的定义和调用方法。
  2. 深入理解参数类型转换的影响:通过实践掌握参数类型转换在重载

函数选择中的影响,确保调用的是预期的重载函数。
3. 避免重载函数中的二义性问题:在编写重载函数时,合理设计参数列表,避免因默认参数或类型转换导致的二义性问题。

6.5 特殊用途语言特性

6.5.1 默认实参

默认实参允许我们在函数声明中为形参指定一个默认值。如果调用函数时未提供对应的实参,则使用默认值。默认实参使函数调用更加灵活,简化了代码。

示例代码
#include <iostream>

void print(int a = 10, int b = 20) {
    std::cout << "a: " << a << ", b: " << b << std::endl;
}

int main() {
    print();          // 使用默认实参
    print(30);        // b 使用默认实参
    print(40, 50);    // 不使用默认实参
    return 0;
}

在这个示例中,print函数定义了两个默认实参,在调用时可以选择性地提供参数。

6.5.2 constexpr函数

constexpr函数是在编译时求值的函数,用于常量表达式。constexpr函数的返回值和所有形参都必须是字面值类型,且函数体中只能包含单一的return语句或其他常量表达式。constexpr函数的优点在于提高编译时计算效率,减少运行时的计算开销。

示例代码
#include <iostream>

constexpr int factorial(int n) {
    return (n <= 1) ? 1 : (n * factorial(n - 1));
}

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

在这个示例中,factorial函数是一个constexpr函数,在编译时计算5的阶乘。

6.5.3 内联函数

内联函数(inline function)建议编译器将函数体插入到每个函数调用点,而不是进行一次函数调用。这可以减少函数调用的开销,适用于频繁调用的小型函数。使用inline关键字声明内联函数。

示例代码
#include <iostream>

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

int main() {
    int sum = add(3, 4);
    std::cout << "Sum: " << sum << std::endl;
    return 0;
}

在这个示例中,add函数是一个内联函数,建议编译器将其内联展开。

6.5.4 调试支持

调试支持特性包括assert和调试信息的输出。assert宏用于在调试期间检测程序中的逻辑错误。调试信息的输出可以使用条件编译和预处理宏。

示例代码
#include <iostream>
#include <cassert>

void checkPositive(int x) {
    assert(x > 0 && "Number is not positive");
    std::cout << x << " is positive." << std::endl;
}

int main() {
    checkPositive(10);
    // checkPositive(-5);  // 会触发断言失败
    return 0;
}

在这个示例中,assert宏用于检查x是否为正数,如果条件不满足则会终止程序并输出错误信息。

NDEBUG预处理变量

在定义了NDEBUG预处理变量后,assert宏将被禁用。通常在发布版本中定义NDEBUG以移除调试断言。

示例代码
#include <iostream>
#include <cassert>

// #define NDEBUG  // 取消注释以禁用 assert

void checkPositive(int x) {
    assert(x > 0 && "Number is not positive");
    std::cout << x << " is positive." << std::endl;
}

int main() {
    checkPositive(10);
    // checkPositive(-5);  // 如果定义了 NDEBUG,不会触发断言失败
    return 0;
}

在这个示例中,如果定义了NDEBUG,则assert宏将被禁用,不会进行检查。

6.5.5 使用递归

递归函数是指在其自身内部调用自己的函数。递归常用于解决那些可以分解为更小的同类子问题的问题。递归需要定义一个基准情况,以避免无限递归。

示例代码
#include <iostream>

int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    int result = fibonacci(10);
    std::cout << "Fibonacci of 10 is " << result << std::endl;
    return 0;
}

在这个示例中,fibonacci函数使用递归计算第10个斐波那契数。

尾递归是递归的一种特殊形式,在递归调用是函数体中最后一个操作。尾递归可以被编译器优化为迭代,从而减少调用栈的开销。C++不强制要求尾递归优化,但许多编译器都支持这种优化。

示例代码
#include <iostream>

int factorial(int n, int accumulator = 1) {
    if (n <= 1) return accumulator;
    return factorial(n - 1, n * accumulator);
}

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

在这个示例中,factorial函数使用尾递归计算阶乘,递归调用是函数体中的最后一个操作。

重点与难点分析

重点

  1. 默认实参:理解默认实参的定义和应用场景,掌握默认实参的使用方法。
  2. constexpr函数:掌握constexpr函数的定义和用法,理解其在编译时计算中的应用。
  3. 内联函数:理解内联函数的作用和定义方法,了解其在减少函数调用开销中的应用。
  4. 调试支持:掌握assert宏的用法,理解条件编译和预处理宏在调试信息输出中的应用,特别是NDEBUG预处理变量的使用。
  5. 递归与尾递归优化:理解递归函数和尾递归优化的概念及其应用场景。

难点

  1. 默认实参的二义性问题:初学者需要理解在重载函数中使用默认实参可能产生的二义性问题。
  2. constexpr函数的限制:初学者需要理解constexpr函数的限制条件,确保其符合编译时计算的要求。
  3. 尾递归优化的实现:掌握尾递归优化的实现方法,理解其在减少调用栈开销中的作用。

练习题解析

  1. 练习6.25:编写一个函数,使用默认实参计算两个数的和。
    • 示例代码
#include <iostream>

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

int main() {
    std::cout << add(3) << std::endl;    // 使用默认实参 b = 5
    std::cout << add(3, 7) << std::endl; // 不使用默认实参
    return 0;
}
  1. 练习6.26:编写一个constexpr函数,计算一个数的平方。
    • 示例代码
#include <iostream>

constexpr int square(int x) {
    return x * x;
}

int main() {
    constexpr int result = square(5);
    std::cout << "Square of 5 is " << result << std::endl;
    return 0;
}
  1. 练习6.27:编写一个内联函数,计算两个数的最小值。
    • 示例代码
#include <iostream>

inline int min(int a, int b) {
    return (a < b) ? a : b;
}

int main() {
    int result = min(3, 7);
    std::cout << "Min: " << result << std::endl;
    return 0;
}
  1. 练习6.28:编写一个使用assert宏的函数,检查一个数是否为负数,并使用NDEBUG预处理变量禁用assert
    • 示例代码
#include <iostream>
#include <cassert>

// #define NDEBUG  // 取消注释以禁用 assert

void checkNegative(int x) {
    assert(x < 0 && "Number is not negative");
    std::cout << x << " is negative." << std::endl;
}

int main() {
    checkNegative(-10);
    // checkNegative(5);  // 如果定义了 NDEBUG,不会触发断言失败
    return 0;
}
  1. 练习6.29:编写一个递归函数,计算一个数的阶乘,并实现尾递归优化。
    • 示例代码
#include <iostream>

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int factorialTail(int n, int accumulator = 1) {
    if (n <= 1) return accumulator;
    return factorialTail(n - 1, n * accumulator);
}

int main() {
    int result = factorial(5);
    std::cout << "Factorial of 5 is " << result << std::endl;

    int tailResult = factorialTail(5);
    std::cout << "Tail Factorial of 5 is " << tailResult << std::endl;

    return 0;
}

总结与提高

本节总结

  1. 学习了默认实参、constexpr函数、内联函数、调试支持(包括assertNDEBUG)、递归和尾递归优化的基本概念和应用。
  2. 掌握了这些特殊用途的语言特性的定义和用法,理解其在提高编译时计算效率、减少运行时开销和调试程序中的作用。
  3. 通过示例代码和练习题,加深了对这些语言特性的理解和应用。

提高建议

  1. 多练习默认实参和内联函数的使用:通过编写各种包含默认实参和内联函数的程序,熟悉其定义和应用场景。
  2. 深入理解constexpr函数和调试支持特性:通过实践掌握constexpr函数和调试支持特性的使用方法,提高编译时计算效率和程序的调试效率。
  3. 掌握递归与尾递归优化:在编写递归函数时,合理设计基准情况和递归步骤,掌握尾递归优化的实现方法,提高递归函数的效率。

6.6 函数匹配

6.6.1 函数匹配概述

函数匹配(Function Matching)是指在函数调用时,根据提供的实参类型和数量选择最合适的重载函数。当编译器遇到多个同名函数时,它会根据函数匹配规则确定哪个函数最适合被调用。如果没有找到合适的匹配,或者存在二义性,编译器将报错。

6.6.2 重载决议

重载决议(Overload Resolution)是编译器选择最匹配的重载函数的过程。编译器通过检查函数的形参列表和调用时提供的实参列表,按照以下规则进行匹配:

  1. 精确匹配:实参类型与形参类型完全相同。
  2. 标准类型转换:如从int转换到double,或从char转换到int
  3. 用户定义的类型转换:如通过构造函数或转换运算符进行的类型转换。
  4. 省略默认实参:当实参数量少于形参数量时,编译器会使用默认实参补足。
示例代码
#include <iostream>

void print(int a) {
    std::cout << "Integer: " << a << std::endl;
}

void print(double b) {
    std::cout << "Double: " << b << std::endl;
}

void print(const std::string &c) {
    std::cout << "String: " << c << std::endl;
}

int main() {
    print(10);           // 精确匹配 print(int)
    print(3.14);         // 精确匹配 print(double)
    print("Hello");      // 精确匹配 print(const std::string &)
    return 0;
}

在这个示例中,编译器根据实参的类型选择最合适的重载函数。

6.6.3 最佳匹配

当存在多个候选函数时,编译器会选择“最佳匹配”的函数。最佳匹配是指实参类型和形参类型之间的转换代价最小的匹配。如果没有唯一的最佳匹配,编译器将报错,指出存在二义性。

示例代码
#include <iostream>

void func(int a) {
    std::cout << "Integer: " << a << std::endl;
}

void func(double b) {
    std::cout << "Double: " << b << std::endl;
}

void func(float c) {
    std::cout << "Float: " << c << std::endl;
}

int main() {
    func(10);      // 调用 func(int)
    func(3.14);    // 调用 func(double)
    func(2.5f);    // 调用 func(float)
    return 0;
}

在这个示例中,编译器选择与实参类型最匹配的函数进行调用。

6.6.4 二义性

如果存在多个同等匹配的重载函数,编译器将无法确定调用哪个函数,从而报二义性错误。为了避免这种情况,重载函数的形参类型应尽量明确。

示例代码
#include <iostream>

void ambiguous(int a) {
    std::cout << "Integer: " << a << std::endl;
}

void ambiguous(double b) {
    std::cout << "Double: " << b << std::endl;
}

void ambiguous(long c) {
    std::cout << "Long: " << c << std::endl;
}

int main() {
    // ambiguous(10L);  // 错误:二义性,long 可匹配 int 和 double
    return 0;
}

在这个示例中,10L可以匹配intdouble,编译器无法确定调用哪个重载函数,导致二义性错误。

6.6.5 默认实参与重载

使用默认实参时,需要小心避免重载函数之间的二义性问题。默认实参与重载函数结合使用时,应确保每个重载版本都具有唯一的匹配。

示例代码
#include <iostream>

void display(int a, int b = 10) {
    std::cout << "a: " << a << ", b: " << b << std::endl;
}

void display(double a) {
    std::cout << "a: " << a << std::endl;
}

int main() {
    display(5);     // 调用 display(int, int),使用默认参数
    display(3.14);  // 调用 display(double)
    return 0;
}

在这个示例中,display(5)调用了display(int, int)并使用默认参数,而display(3.14)调用了display(double)

6.6.6 特殊匹配情况

一些特殊情况可能影响函数匹配,包括使用模板函数和函数指针等。模板函数的匹配规则更加复杂,编译器会根据模板实参推断和匹配规则进行选择。

示例代码
#include <iostream>

template <typename T>
void templateFunc(T a) {
    std::cout << "Template: " << a << std::endl;
}

void templateFunc(int a) {
    std::cout << "Non-template: " << a << std::endl;
}

int main() {
    templateFunc(10);    // 调用非模板函数
    templateFunc(3.14);  // 调用模板函数
    return 0;
}

在这个示例中,templateFunc(10)调用了非模板函数,而templateFunc(3.14)调用了模板函数。

重点与难点分析

重点

  1. 函数匹配规则:理解函数匹配的基本规则,包括精确匹配、标准类型转换和用户定义的类型转换。
  2. 最佳匹配:掌握编译器选择最佳匹配的过程,理解如何避免二义性。
  3. 默认实参与重载:了解默认实参与重载结合使用时可能产生的问题及其解决方法。

难点

  1. 二义性问题:初学者需要通过实践理解二义性问题的产生原因及其解决方法,避免重载函数之间的冲突。
  2. 模板函数匹配:掌握模板函数的匹配规则,理解模板实参推断和匹配的复杂性。

总结与提高

本节总结

  1. 学习了函数匹配的基本规则,掌握了重载决议、最佳匹配和二义性问题的解决方法。
  2. 理解了默认实参与重载结合使用时的注意事项,避免二义性问题的产生。
  3. 通过示例代码和练习题,加深了对函数匹配和

模板函数匹配的理解和应用。

提高建议

  1. 多练习函数匹配的定义与调用:通过编写各种包含重载函数的程序,熟悉函数匹配的规则和重载决议的过程。
  2. 深入理解二义性问题:通过实践掌握二义性问题的产生原因及其解决方法,避免重载函数之间的冲突。
  3. 掌握模板函数的匹配规则:在编写模板函数时,合理设计模板参数,理解模板实参推断和匹配的复杂性,提高代码的灵活性和可维护性。

6.7 函数指针

6.7.1 函数指针概述

函数指针是指向函数的指针。通过函数指针,我们可以间接调用函数,并且可以将函数作为参数传递给其他函数。这为编写灵活和可重用的代码提供了便利。

6.7.2 定义和初始化函数指针

函数指针的定义包括返回类型和参数列表,形式如下:

returnType (*pointerName)(parameterList);

要初始化函数指针,可以将其指向一个具有相同签名的函数。

示例代码
#include <iostream>

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

int main() {
    // 定义函数指针
    int (*funcPtr)(int, int) = add;
    // 通过函数指针调用函数
    int result = funcPtr(3, 4);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

在这个示例中,funcPtr是一个函数指针,指向add函数,通过该指针调用add函数并获取结果。

6.7.3 函数指针作为参数

函数指针可以作为参数传递给其他函数,从而实现对不同函数的灵活调用。

示例代码
#include <iostream>

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

int subtract(int a, int b) {
    return a - b;
}

// 接受函数指针作为参数的函数
int compute(int (*func)(int, int), int x, int y) {
    return func(x, y);
}

int main() {
    // 调用 compute 函数,传递 add 和 subtract 函数指针
    int sum = compute(add, 5, 3);
    int difference = compute(subtract, 5, 3);
    std::cout << "Sum: " << sum << std::endl;
    std::cout << "Difference: " << difference << std::endl;
    return 0;
}

在这个示例中,compute函数接受一个函数指针作为参数,并调用传递的函数。

6.7.4 函数指针作为返回值

函数指针也可以作为函数的返回值,从而实现对不同函数的动态选择。

示例代码
#include <iostream>

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

int subtract(int a, int b) {
    return a - b;
}

// 返回函数指针的函数
int (*getOperation(char op))(int, int) {
    if (op == '+') {
        return add;
    } else {
        return subtract;
    }
}

int main() {
    // 获取函数指针并调用函数
    int (*operation)(int, int) = getOperation('+');
    int result = operation(10, 5);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

在这个示例中,getOperation函数根据输入字符返回相应的函数指针,通过该指针调用相应的函数。

6.7.5 使用typedef简化函数指针

使用typedef可以简化函数指针的定义和使用,使代码更加可读和易于维护。

示例代码
#include <iostream>

// 使用 typedef 定义函数指针类型
typedef int (*operationFunc)(int, int);

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

int subtract(int a, int b) {
    return a - b;
}

// 接受函数指针作为参数的函数
int compute(operationFunc func, int x, int y) {
    return func(x, y);
}

int main() {
    // 使用 typedef 定义的函数指针类型
    operationFunc op = add;
    int result = compute(op, 7, 3);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

6.7.6 使用using简化函数指针

using关键字是C++11引入的一种新方式,用于定义类型别名。相对于typedefusing语法更加直观和易于理解,特别是在处理复杂类型时。

示例代码
#include <iostream>

// 使用 using 定义函数指针类型
using operationFunc = int(*)(int, int);

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

int subtract(int a, int b) {
    return a - b;
}

// 接受函数指针作为参数的函数
int compute(operationFunc func, int x, int y) {
    return func(x, y);
}

int main() {
    // 使用 using 定义的函数指针类型
    operationFunc op = add;
    int result = compute(op, 7, 3);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

在这个示例中,使用using定义了operationFunc类型,使函数指针的定义和使用更加简洁。

6.7.7 指向成员函数的指针

指向成员函数的指针不同于普通函数指针。定义指向成员函数的指针时,需要指定类的作用域。

示例代码
#include <iostream>

class Calculator {
public:
    int add(int a, int b) const {
        return a + b;
    }

    int subtract(int a, int b) const {
        return a - b;
    }
};

int main() {
    Calculator calc;
    // 定义指向成员函数的指针
    int (Calculator::*funcPtr)(int, int) const = &Calculator::add;
    // 通过指针调用成员函数
    int result = (calc.*funcPtr)(10, 5);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

在这个示例中,funcPtr是一个指向Calculator类成员函数的指针,通过该指针调用add函数。

重点与难点分析

重点

  1. 函数指针的定义和初始化:掌握函数指针的定义方法,并能够初始化和调用函数指针。
  2. 函数指针作为参数和返回值:理解如何将函数指针作为参数传递和返回,提高代码的灵活性。
  3. 使用typedefusing简化函数指针:掌握使用typedefusing简化函数指针定义的方法,提高代码的可读性。
  4. 指向成员函数的指针:理解指向成员函数的指针的定义和使用,掌握其与普通函数指针的区别。

难点

  1. 函数指针的语法:初学者需要通过实践掌握函数指针的语法,避免常见的语法错误。
  2. 指向成员函数的指针:掌握指向成员函数的指针的定义和使用方法,理解其与普通函数指针的不同。

总结与提高

本节总结

  1. 学习了函数指针的定义和初始化方法,掌握了如何通过函数指针调用函数。
  2. 理解了函数指针作为参数和返回值的应用,能够编写灵活和可重用的代码。
  3. 掌握了使用typedefusing简化函数指针定义的方法,提高代码的可读性。
  4. 理解了指向成员函数的指针的定义和使用,能够通过该指针调用成员函数。

提高建议

  1. 多练习函数指针的定义与调用:通过编写各种包含函数指针的程序,熟悉函数指针的语法和应用场景。
  2. 深入理解指向成员函数的指针:通过实践掌握指向成员函数的指针的定义和使用方法,理解其与普通函数指针的不同。
  3. 使用typedefusing提高代码可读性:在编写复杂函数指针时,使用typedefusing简化定义,提高代码的可读性和可维护性。

本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“iShare爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1844005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot+Vue电影推荐系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝1W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还…

Python 绘制圆欠采样时的数学图形

Python 绘制圆欠采样时的数学图形 正文end_radian 190end_radian 180end_radian 170end_radian 130end_radian 120 正文 今天在绘制圆形的时候遇到了意外&#xff0c;发现了一个有意思的现象&#xff0c;这里特来记录一下。 end_radian 190 import numpy as np import…

【因果推断python】45_估计量1

目录 问题设置 目标转换 到目前为止&#xff0c;我们已经了解了如何在干预不是随机分配的情况下对我们的数据进行纠偏&#xff0c;这会导致混淆偏差。这有助于我们解决因果推理中的识别问题。换句话说&#xff0c;一旦单位是可交换的&#xff0c;或者 &#xff0c;就可以学习…

H3C防火墙抓包(图形化)

一.报文捕获 &#xff0c;然后通过wireshark查看报文 二.报文示踪 &#xff0c; 输入源目等信息&#xff0c; 查看报文的详情

JavaWeb之JSON、AJAX

JSON 什么是JSON&#xff1a;JSON: JavaScript Object Notation JS对象简谱 , 是一种轻量级的数据交换格式(JavaScript提供) 特点 [{"name":"周珍珍", "age":18},{"name":"李淑文","age":20}]数据是以键值对形式…

SpingBoot快速入门下

响应HttpServietResponse 介绍 将ResponseBody 加到Controller方法/类上 作用&#xff1a;将方法返回值直接响应&#xff0c;如果返回值是 实体对象/集合&#xff0c;将会自动转JSON格式响应 RestController Controller ResponseBody; 一般响应 统一响应 在实际开发中一般…

SpringBoot | 实现邮件发送

运行环境&#xff1a; IntelliJ IDEA 2022.2.5 (Ultimate Edition) (注意&#xff1a;idea必须在2021版本以上&#xff09;JDK17 项目目录&#xff1a; 该项目分为pojo,service,controller,utils四个部分&#xff0c; 在pojo层里面写实体内容&#xff08;发邮件需要的发件人邮…

浅谈配置元件之HTTP Cookie管理器

浅谈配置元件之HTTP Cookie管理器 在进行Web测试时&#xff0c;处理Cookies是非常关键的一环&#xff0c;因为Cookies常用于存储用户会话信息、登录状态等。JMeter中的“HTTP Cookie管理器”&#xff08;HTTP Cookie Manager&#xff09;正是为此设计的配置元件&#xff0c;它…

后端学习笔记:Python基础

后端学习笔记&#xff1a;Python基础 数据类型&#xff1a; Python中主要有以下几种常用的基本数据类型&#xff1a; String 字符串类型&#xff0c;用单引号或者双引号引用Number 数字类型&#xff0c;包括浮点数&#xff0c;整数&#xff0c;长整数和复数List 列表项&…

24年计算机等级考试22个常见问题解答❗

24年9月计算机等级考试即将开始&#xff0c;整理了报名中容易遇到的22个问题&#xff0c;大家对照入座&#xff0c;避免遇到了不知道怎么办&#xff1f; 1、报名条件 2、报名入口 3、考生报名之后后悔了&#xff0c;不想考了&#xff0c;能否退费&#xff1f; 4、最多能够报多少…

【MySQL】(基础篇十五) —— 增删改数据

增删改数据 本文介绍如何利用SQL的INSERT语句将数据插入表中。以及如何利用UPDATE和DELETE语句进一步操纵表数据。 数据插入 INSERT是用来插入&#xff08;或添加&#xff09;行到数据库表的。插入可以用几种方式使用 插入完整的行&#xff1b;插入行的一部分&#xff1b;插…

目标检测技术学习

最近公司做一个目标检测相关的项目&#xff0c;对目标检测以及相关的深度学习知识有一些了解。这里整理一下。 一、目标检测定义 什么是目标检测&#xff1f;最近还碰到一个朋友做的项目&#xff0c;是无人机目标检测的&#xff0c;很有意思&#xff08;据说还是军事用途&…

SpringBoot的冬奥会科普平台 LW +PPT+源码

3 平台分析 3.1 平台可行性分析 3.1.1 经济可行性 由于本平台是作为毕业设计平台&#xff0c;且平台本身存在一些技术层面的缺陷&#xff0c;并不能直接用于商业用途&#xff0c;只想要通过该平台的开发提高自身学术水平&#xff0c;不需要特定服务器等额外花费。所有创造及工…

python离线安装第三方库、及其依赖库(单个安装,非批量移植)

文章目录 1.外网下载第三方库、依赖库2.内网安装第三方库3.补充附录内网中离线安装python第三方库,这时候只能去外网手动下载第三方库,再传回内网进行安装。 问题是python第三方库往往有其前置依赖包,你很难清楚某个第三方库依赖的是哪些依赖包,更难受的是依赖包可能还有其…

谷歌个人号14天封闭测试,又添新要求?怎么提高封测通过率?

相信不少在Google Play上架应用的开发者们&#xff0c;对谷歌个人号的20人连续14天的封闭测试感到头疼。谷歌对个人号提出周期长达14天的封测要求&#xff0c;主要是希望开发者们能上架质量更好、体验感更好的app到谷歌商店。 而随着谷歌行业的发展&#xff0c;这项政策要求的执…

双网卡设置路由网络不通原因之一:静态ip设置失败

1.主要现象&#xff1a; 外网通&#xff0c;内网不通 外网IP设置 内网IP设置 路由表设置 内网不通 2.主要原因&#xff1a;在适配器中设置的内网静态IP没有成功 设置静态IP失败 在命令行使用ipconfig命令看到内网适配器的静态IP为192.168.0.55&#xff0c;并不是我们设置的1…

密码学及其应用——GMP库在密码学中的应用

GMP&#xff08;GNU Multiple Precision arithmetic library&#xff0c;GNU多精度算术库&#xff09;是一个针对大整数运算的库。这个库提供了许多针对多种多精度类型的计算函数&#xff1a; - 大整数&#xff1a;Z - 大有理数&#xff1a;Q - 大浮点数&#xff1a;R 1. 密码学…

Unity URP简单烘焙场景步骤

Unity URP简单烘焙场景步骤 前言项目场景布置灯光模型Lighting设置环境设置烘焙前烘焙后增加角色 前言 项目中要烘焙一个3D场景&#xff0c;用的URP渲染管线&#xff0c;简单记录一下。 项目 场景布置 灯光 因为场景中有能动的东西&#xff0c;需要一部分实时光照&#xf…

vuejs3+elementPlus后台管理系统,左侧菜单栏制作,跳转、默认激活菜单

默认激活菜单,效果&#xff1a; 默认激活菜单&#xff0c;效果1&#xff1a; 默认激活菜单&#xff0c;效果2&#xff1a; 跳转链接效果&#xff1a; 制作&#xff1a; <script setup> import {useUserStore} from "/stores/userStore.js"; import {ref} fr…

FKA总结

问题点&#xff1a; 原因分析: 工作状态下进入充电模式&#xff0c;程序检测到的电压会降低&#xff0c;比如8.07V的电池电压&#xff0c;它处于充满的临界状态&#xff0c;开机情况下程序检测的电压会降到7.98V&#xff0c;然后进入充电模式显示红灯 关机状态下进入充电模式…