首页 > 其他分享 >learncpp-20 函数

learncpp-20 函数

时间:2024-07-22 22:30:46浏览次数:11  
标签:20 函数 int learncpp 类型 return 函数指针 lambda

20 函数

20.1 函数指针

  • 函数和变量一样,也是在内存中被分配了一块地址。因此,函数指针就是一个保存函数的内存地址的变量
  • 函数也是有类型的,例如foo()这个函数的类型就是返回整数且不带参数
int foo(){return 5;}
  • <<操作符不知道如何输出函数指针(因为有无数种可能的函数指针),因此foo会被转换成bool,并且由于foo这个函数的指针是非void,所以应该被转换成true
std::cout<<foo(); // 输出5
std::cout<<foo; // 输出1,也就是true
  • int (*p)()表示p是一个指向没有参数、返回类型为int的任何函数的指针;而int* p()表示p是一个没有参数、返回类型为int*的函数
  • 要创建一个常量函数指针,const放在*后面:int (*const p)()
  • 如何解析一个复杂的声明:螺旋法则(https://c-faq.com/decl/spiral.anderson.html)
  • 函数指针的类型(参数类型和返回类型)必须和函数的类型一致
int foo();
double goo();
int hoo(int x);

int (*p1)(){&foo}; // ok
int (*p2)(){&goo}; // 编译报错:different return type
double (*p3)(){&goo}; // ok
int (*p4)(){&hoo}; // 编译报错:different number of parameters
int (*p5)(int){&hoo}; // ok
  • 与基本类型不同,C++会隐式地将函数转换为函数指针,如果获取函数地址时可以省略&;但是函数指针不会转换为void指针,反之亦然
int foo();

int (*p1)(){foo}; // ok
void* p2{foo}; // 编译报错(可能有些编译器会允许)
  • 函数指针可以初始化或者赋值为nullptr
int (*p)(){nullptr}; // ok
  • 通过函数指针调用函数的两种方式:
    • 通过显式解引用
    • 通过隐式解引用
int foo(int x){return x;}

int (*p)(int){&foo};
(*p)(5); // return 5
p(7); // return 7
  • 在调用函数指针之前校验是否为nullptr,对一个空的函数指针解引用会导致未定义的行为
  • 使用函数指针调用函数时,函数的默认参数将失效

当编译器遇到对含有默认参数的函数的正常调用时,会重写函数调用以包含默认参数,这个过程发生在编译期间
但是通过函数指针调用函数的过程是在运行期间被解析的,因此这种调用没有被编译器重写以包含默认参数

  • 函数指针可以将函数作为参数传递给另一个函数,这个作为参数的函数有时称为回调函数
  • 当函数的参数是函数类型时,会被转换成函数指针类型
// 这两种方式是等价的
void selectionSort(int* array, int size, bool comparisonFcn(int,int))
void selectionSort(int* array, int size, bool (*comparisonFcn)(int,int))
  • 使用类型别名简化函数指针的语法
using vp = void(*)(int);
vp ptr; // ptr是一个函数指针,指向一个void (int)类型的函数
  • 定义和存储函数指针的另一种方式是std::function
int foo() {
    return 5;
}

int (*p1)(){&foo};
std::cout << (*p1)() << std::endl; // 5

std::function<int()> p2{&foo};
std::cout << p2() << std::endl; // 5 
std::cout << (*p2)() << std::endl; // 编译报错:通过std::function生成的函数指针只能通过隐式解引用调用函数,不能使用显式解引用

std::function p3{&foo}; // 可以使用类型推断,无需指定类型参数
std::cout << p3() << std::endl; // 5
  • 使用类型别名时必须指定类型参数
using functionPtr = std::function<bool(int,int)>
  • 可以使用auto关键字来推断函数指针的类型,但是会隐藏函数的参数类型和返回类型
  • 推荐使用std::function来声明函数指针

如果只需要使用一次函数指针的类型(例如单个参数或返回值),可以直接使用std::function
如果需要多次函数指针的类型,最好为std::function设置别名

20.6 lambda表达式(匿名函数)

  • lambda表达式语法:
[captureClause] (parameters) -> returnType
{
  statements;
}

returnType是可选的,如果returnType被省略了,则会使用auto来推断返回类型
如果未指定returnType并且没有参数,则整个参数列表可以省略

  • 当需要一个一次性的函数作为参数传递给其他函数时,优先使用lamba表达式
  • 可以使用lambda定义来初始化一个lambda变量,以便后续使用
  • lambda变量是一种特殊的对象,它们的类型是functor。functor类有一个成员函数对操作符()进行了重载,这使得functor类型的对象可以像函数一样被调用。(因此这些对象也称为函数对象)
auto isEven{
  [](int i){
    return (i%2)==0;
  }
};
std::cout << isEven(3) << std::endl; // 0
std::cout << isEven(4) << std::endl; // 1
  • 如果捕获子句为空,则可以使用普通函数指针类型存储lambda表达式;如果捕获子句不为空,则可以使用std::functino类型或者使用auto进行类型推断
// 普通指针类型(适用于捕获子句为空)
int (*addFunc1)(int,int){
  [](int a,int b){
    return a+b;
  }
};
std::cout << addFunc1(1, 2) << '\n'; // 3
// std::function(捕获子句可以不为空)
std::function addFunc2{
  [](int a, int b) {
    return a + b;
  }
};
std::cout << addFunc2(3, 4) << '\n'; // 7
// auto(捕获子句可以不为空)
auto addFunc3{
  [](int a, int b) {
    return a + b;
  }
};
std::cout << addFunc3(5, 6) << '\n'; // 11

如果想要使用lambda变量的实际类型,只能通过auto

  • 假设我们需要把一个lambda变量传递给一个函数,有以下四种形参的形式:
auto lambda = [](int a) {
  std::cout << a << ' ';
};
// 1.std::function
void repeat1(int repetitions, std::function<void(int)> fn) {
    for (int i = 0; i < repetitions; ++i) {
        fn(i);
    }
}
repeat1(3,lambda); // 输出0 1 2
// 2.模板参数
template<typename T>
void repeat2(int repetitions, T fn) {
    for (int i = 0; i < repetitions; ++i) {
        fn(i);
    }
}
repeat2(3,lambda); // 输出0 1 2
// 3.简化的函数模板语法(C++20)
void repeat3(int repetitions, auto fn)
{
    for (int i{ 0 }; i < repetitions; ++i)
        fn(i);
}
repeat(3,lambda); // 输出0 1 2
// 4.普通函数指针(lambda的捕获子句必须为空)
void repeat4(int repetitions, void (*fn)(int)) {
    for (int i = 0; i < repetitions; ++i) {
        fn(i);
    }
}
repeat4(3,lambda); // 输出0 1 2
  • 当lambda表达式有一个或多个auto参数(也叫泛型lambda)时,编译器将从对lambda的调用中推断出所需的参数类型
[](auto& a, auto& b){
  return a[0]==b[0];
}

auto用于lambda中时,它只是模板参数的简写

  • 从C++17开始,如果lambda的结果满足常量表达式的要求,则lambda是隐式的constexpr。这通常需要满足两个条件:
    • lambda必须没有捕获子句或者所有捕获子句都为constexpr
    • lambda调用的函数必须是constexpr
  • auto解析为的每种不同类型都会对应生成唯一的lambda表达式(因此包含独立的静态局部变量)
auto print{
  [](auto value) {
      static int callCount{0};
      std::cout << callCount++ << ": " << value << '\n';
  }
};
print("hello");// 0: hello
print("world");// 1: world
print(1);// 0: 1
print(2);// 1: 2
print("ding dong");// 2: ding dong

上述代码会生成void(const char*)void(int)两种类型的lambda表达式,静态局部变量callCount不会在这两个lambda表达式中共享

  • 当使用auto存储lambda表达式时,变量的返回类型将根据lambda表达式里的return语句进行类型推断(lambda表达式里的所有return语句必须返回相同的类型)
auto divide{
  [](int x, int y, bool intDivision) {
    if (intDivision) {
      return x / y;
    } else {
      return static_cast<double >(x) / y;// 编译报错:Return type 'double' must match previous return type 'int' when lambda expression has unspecified explicit return type
    }
  }
}

上述报错有两种解决方案:
1.进行显式转换使得return语句的返回类型都相同
2.为lambda表达式指定返回类型,让编译器做隐式转换

auto divide{
  [](int x, int y, bool intDivision) -> double {
    if (intDivision) {
      return x / y;// 编译器会对返回结果进行隐式转换
    } else {
      return static_cast<double >(x) / y;
    }
  }
}
  • 对于常见的操作(加法、取反、比较),不需要自己编写lambda表达式,标准库中提供了许多基本的可调用对象(函数对象),它们在<function>头文件中定义

标签:20,函数,int,learncpp,类型,return,函数指针,lambda
From: https://www.cnblogs.com/akongogogo/p/18317126

相关文章

  • learncpp-21 操作符重载
    21操作符重载21.12重载赋值运算符拷贝构造器和拷贝赋值运算符的作用几乎相同,都是将一个对象复制到另一个对象。但是拷贝构造器初始化新的对象,而赋值运算符替换已有对象的内容如果在拷贝前必须创建一个新对象,则使用拷贝构造器(包括按值传递和按值返回)如果在拷贝前无需创......
  • 2024牛客暑期多校训练营2(部分题目题解)
    2024牛客暑期多校训练营2(部分题目题解)C.RedWalkingonGrid题意:给定只有红白的2*n个格子,只能走红色各自且只能上下左右走,问最多可以走多少红色格子。题解:左右走:dp[0][i]=dp[0][i-1]+1;上下走:intk1=dp[0][i];intk2=dp[1][i];dp[0][i]=max(dp[0][i],k2+......
  • learncpp-1 C++基础
    1C++基础1.1语句和程序结构语句是一条让计算机执行某个动作的指令,是C++语言中最小的独立计算单元在高级语言(例如C++)中,一条语句可能编译成多条机器指令大多数语句以;结尾声明语句跳转表达式语句复合语句选择语句(条件语句)迭代语句(循环语句)try代码块函......
  • 2024杭电钉耙2-1003 HDOJ7447 绝对不模拟的简单魔方
    欢迎您来我的网站看这篇题解!Problem有一个魔方可能被拧了不超过三次,同时还弄丢了一个角块上的两个贴纸。现在把这两个贴纸贴回去,请问有没有贴错?只可能拧侧面,不会拧中间层,且每次只能拧\(90^\circ\)。魔方用一个9行12列的字符型矩阵表示:初始魔方的展开图如下图:\(1\leT......
  • learncpp-2 函数和文件
    2函数和文件2.9命名冲突和命名空间两个(或多个)同名函数(或全局变量)被引入到属于同一程序的不同文件中,这将导致链接器错误。两个(或多个)同名函数(或全局变量)被引入到同一个文件中。这将导致编译器错误。不同的作用域(例如命名空间)中可以有相同的标识符只有声明和定义可以出现......
  • 2048小游戏【C语言版】单文件编写
    设计思路游戏地图和初始设置:使用一个4x4的二维数组map来表示游戏地图。初始时,所有位置的值均为0。score记录玩家得分,move_num记录移动次数。随机生成数字:在地图上随机选择一个空位置生成2或4。只有在地图发生变化时才会生成新数字。游戏菜单:使用m......
  • AIGC-DynamiCrafter: Animating Open-domain Images with Video Diffusion Priors-ECC
    论文:https://arxiv.org/pdf/2310.12190代码:https://github.com/Doubiiu/DynamiCrafter?tab=readme-ov-fileMOTIVATIONTraditionalimageanimationtechniquesmainlyfocusonanimatingnaturalsceneswithstochasticdynamics(e.g.cloudsandfluid)ordom......
  • 2024年Java高级开发工程师面试准备
    20240722前三步因为是在20年找工作的时候已经充分学习过,所以现在基本只需要读一遍即可第一步:Java基础(CYC2018[2.1-2.4]+JavaGuide[第二章])Java基础+JVM+多线程+Java集合第二步:计算机基础(算法和设计模式靠积累,计算机网络和操作系统读一遍:CYC2018[3.1-3.2]+JavaGuide[......
  • 20240722题解
    孩子们,我回来了......
  • Visual Studio 2019使用SVN管理源代码
    原文链接:https://blog.csdn.net/g313105910/article/details/119964508第一章、下载安装VisualSVNforVisualStudio2019https://www.visualsvn.com/CSDN下载地址https://download.csdn.net/download/g313105910/21698281安装 完成 VisualStudio2019中已经包含了A......