函数式编程 - 快速指南



函数式编程 - 简介

函数式编程语言专门设计用于处理符号计算和列表处理应用程序。函数式编程基于数学函数。一些流行的函数式编程语言包括:Lisp、Python、Erlang、Haskell、Clojure等。

函数式编程语言分为两类,即:

  • 纯函数式语言 - 这些类型的函数式语言仅支持函数式范式。例如 - Haskell。

  • 非纯函数式语言 - 这些类型的函数式语言支持函数式范式和命令式编程风格。例如 - LISP。

函数式编程 - 特点

函数式编程最突出的特点如下:

  • 函数式编程语言设计在数学函数的概念上,使用条件表达式和递归来执行计算。

  • 函数式编程支持高阶函数惰性求值特性。

  • 函数式编程语言不支持像循环语句和条件语句(如If-Else和Switch语句)这样的流程控制。它们直接使用函数和函数调用。

  • 与OOP一样,函数式编程语言支持抽象、封装、继承和多态等流行概念。

函数式编程 - 优点

函数式编程提供了以下优点:

  • 无错误代码 - 函数式编程不支持状态,因此没有副作用结果,我们可以编写无错误的代码。

  • 高效的并行编程 - 函数式编程语言没有可变状态,因此没有状态更改问题。可以将“函数”编程为并行运行的“指令”。此类代码支持易于重用和可测试性。

  • 效率 - 函数式程序由可以并发运行的独立单元组成。因此,此类程序效率更高。

  • 支持嵌套函数 - 函数式编程支持嵌套函数。

  • 惰性求值 - 函数式编程支持惰性函数构造,如惰性列表、惰性映射等。

不利的一面是,函数式编程需要大量的内存空间。因为它没有状态,所以每次执行操作都需要创建新的对象。

函数式编程用于需要对同一组数据执行大量不同操作的情况。

  • Lisp用于人工智能应用,如机器学习、语言处理、语音和视觉建模等。

  • 嵌入式Lisp解释器为某些系统(如Emacs)添加了可编程性。

函数式编程与面向对象编程

下表突出显示了函数式编程和面向对象编程之间的主要区别:

函数式编程 OOP
使用不可变数据。 使用可变数据。
遵循声明式编程模型。 遵循命令式编程模型。
重点在于:“你在做什么” 重点在于“你如何去做”
支持并行编程 不适合并行编程
其函数没有副作用 其方法可能会产生严重的副作用。
流程控制通过函数调用和带递归的函数调用完成 流程控制通过循环和条件语句完成。
它使用“递归”概念来迭代集合数据。 它使用“循环”概念来迭代集合数据。例如:Java中的For-each循环
语句的执行顺序不那么重要。 语句的执行顺序非常重要。
支持“数据抽象”和“行为抽象”。 仅支持“数据抽象”。

程序代码的效率

程序代码的效率与算法效率和执行速度成正比。良好的效率确保更高的性能。

影响程序效率的因素包括:

  • 机器的速度
  • 编译器速度
  • 操作系统
  • 选择合适的编程语言
  • 程序中数据组织的方式
  • 用于解决问题的算法

可以通过执行以下任务来提高编程语言的效率:

  • 通过删除不必要的代码或导致冗余处理的代码。

  • 通过利用最佳内存和非易失性存储

  • 通过在任何适用的地方使用可重用组件。

  • 通过在程序的所有层中使用错误和异常处理。

  • 通过创建确保数据完整性和一致性的程序代码。

  • 通过开发符合设计逻辑和流程的程序代码。

高效的程序代码可以最大程度地减少资源消耗和完成时间,同时最大程度地降低对操作环境的风险。

函数概述

在编程方面,函数是一块执行特定任务的语句块。函数接受数据,处理数据并返回结果。编写函数的主要目的是支持可重用性的概念。编写函数后,可以轻松调用它,而无需一遍又一遍地编写相同的代码。

不同的函数式语言使用不同的语法来编写函数。

编写函数的先决条件

在编写函数之前,程序员必须了解以下几点:

  • 程序员应该知道函数的目的。

  • 程序员应该知道函数的算法。

  • 程序员应该知道函数数据变量及其目标。

  • 程序员应该知道用户调用的函数的数据。

函数的流程控制

当函数被“调用”时,程序“转移”控制以执行函数,其“控制流”如下:

  • 程序到达包含“函数调用”的语句。

  • 执行函数内的第一行。

  • 从上到下执行函数内的所有语句。

  • 当函数成功执行后,控制权返回到其开始执行的语句。

  • 函数计算和返回的任何数据都将用于替换原始代码行中的函数。

函数的语法

函数的一般语法如下:

returnType functionName(type1 argument1, type2 argument2, . . . ) {     
   // function body 
} 

在C++中定义函数

让我们举个例子来了解如何在C++(一种面向对象编程语言)中定义函数。以下代码包含一个添加两个数字并将其结果作为输出提供的函数。

#include <stdio.h> 

int addNum(int a, int b);     // function prototype  

int main() {    
   int sum; 
   sum = addNum(5,6);         // function call 
   printf("sum = %d",sum); 
   return 0; 
}  
int addNum (int a,int b) {    // function definition      
   int result; 
   result = a + b; 
   return result;             // return statement 
} 

它将产生以下输出:

Sum = 11

在Erlang中定义函数

让我们看看如何在Erlang(一种函数式编程语言)中定义相同的函数。

-module(helloworld).  
-export([add/2,start/0]).   

add(A,B) ->
   C = A + B,  
   io:fwrite("~w~n",[C]).  
start() ->  
   add(5,6). 

它将产生以下输出:

11

函数原型

函数原型是函数的声明,包括返回类型、函数名和参数列表。它类似于没有函数体的函数定义。

例如 - 一些编程语言支持函数原型,而一些不支持。

在C++中,我们可以像这样创建函数'sum'的函数原型:

int sum(int a, int b) 

注意 - Python、Erlang等编程语言不支持函数原型,我们需要声明完整的函数。

函数原型的作用是什么?

当调用函数时,编译器会使用函数原型。编译器使用它来确保正确的返回类型、传递的参数列表正确以及它们的返回类型正确。

函数签名

函数签名类似于函数原型,其中参数的数量、参数的数据类型和出现的顺序应按相同的顺序排列。例如:

void Sum(int a, int b, int c);         // function 1  
void Sum(float a, float b, float c);   // function 2  
void Sum(float a, float b, float c);   // function 3 

Function1和Function2具有不同的签名。Function2和Function3具有相同的签名。

注意 - 我们将在后续章节中讨论的函数重载和函数重写基于函数签名的概念。

  • 当一个类有多个名称相同但签名不同的函数时,函数重载是可能的。

  • 当派生类函数与基类具有相同的名称和签名时,函数重写是可能的。

函数式编程 - 函数类型

函数分为两种类型:

  • 预定义函数
  • 用户定义函数

在本章中,我们将详细讨论函数。

预定义函数

这些是内置于语言中用于执行操作的函数,并存储在标准函数库中。

例如 - C++中的'Strcat'和Haskell中的'concat'用于追加两个字符串,C++中的'strlen'和Python中的'len'用于计算字符串长度。

在C++中打印字符串长度的程序

以下程序演示了如何使用C++打印字符串的长度:

#include <iostream> 
#include <string.h> 
#include <stdio.h> 
using namespace std;  

int main() {     
   char str[20] = "Hello World"; 
   int len; 
   len = strlen(str); 
   cout<<"String length is: "<<len; 
   return 0; 
} 

它将产生以下输出:

String length is: 11

在Python中打印字符串长度的程序

以下程序演示了如何使用Python(一种函数式编程语言)打印字符串的长度:

str = "Hello World"; 
print("String length is: ", len(str)) 

它将产生以下输出:

('String length is: ', 11)

用户定义函数

用户定义函数由用户定义以执行特定任务。有四种不同的模式来定义函数:

  • 没有参数且没有返回值的函数
  • 没有参数但有返回值的函数
  • 有参数但没有返回值的函数
  • 有参数且有返回值的函数

没有参数且没有返回值的函数

以下程序演示了如何在C++中定义一个没有参数且没有返回值的函数:

#include <iostream> 
using namespace std; 

void function1() { 
   cout <<"Hello World"; 
}  
int main() { 
   function1(); 
   return 0; 
} 

它将产生以下输出:

Hello World 

以下程序演示了如何在Python中定义一个类似的函数(没有参数且没有返回值):

def function1():    
   print ("Hello World") 
    
function1() 

它将产生以下输出:

Hello World 

没有参数但有返回值的函数

以下程序演示了如何在C++中定义一个没有参数但有返回值的函数:

#include <iostream> 
using namespace std; 
string function1() { 
   return("Hello World"); 
}  

int main() { 
   cout<<function1(); 
   return 0; 
}

它将产生以下输出:

Hello World 

以下程序演示了如何在Python中定义一个类似的函数(没有参数但有返回值):

def function1(): 
   return "Hello World" 
res = function1() 
print(res) 

它将产生以下输出:

Hello World 

有参数但没有返回值的函数

以下程序演示了如何在C++中定义一个有参数但没有返回值的函数:

#include <iostream> 
using namespace std; 
void function1(int x, int y) {    
   int c; 
   c = x+y;  
   cout<<"Sum is: "<<c; 
}  

int main() { 
   function1(4,5); 
   return 0; 
}

它将产生以下输出:

Sum is: 9 

以下程序演示了如何在Python中定义一个类似的函数:

def function1(x,y): 
   c = x + y 
   print("Sum is:",c) 
function1(4,5)

它将产生以下输出:

('Sum is:', 9)

有参数且有返回值的函数

以下程序演示了如何在C++中定义一个没有参数但有返回值的函数:

#include <iostream> 
using namespace std; 
int function1(int x, int y) {    
   int c; 
   c = x + y;  
   return c;    
} 

int main() {  
   int res; 
   res = function1(4,5); 
   cout<<"Sum is: "<<res; 
   return 0; 
}

它将产生以下输出:

Sum is: 9 

以下程序演示了如何在Python中定义一个类似的函数(有参数且有返回值):

def function1(x,y): 
   c = x + y 
   return c  

res = function1(4,5) 
print("Sum is ",res) 

它将产生以下输出:

('Sum is ', 9)

函数式编程 - 按值调用

定义函数后,我们需要向其中传递参数以获得所需的输出。大多数编程语言支持按值调用按引用调用方法将参数传递给函数。

在本章中,我们将学习“按值调用”如何在面向对象编程语言(如C++)和函数式编程语言(如Python)中工作。

在按值调用方法中,原始值不能更改。当我们将参数传递给函数时,函数参数会在栈内存中本地存储它。因此,值仅在函数内部更改,并且不会对函数外部产生影响。

C++中的按值调用

以下程序演示了按值调用如何在C++中工作:

#include <iostream> 
using namespace std; 

void swap(int a, int b) {    
   int temp; 
   temp = a; 
   a = b; 
   b = temp; 
   cout<<"\n"<<"value of a inside the function: "<<a; 
   cout<<"\n"<<"value of b inside the function: "<<b; 
}  
int main() {     
   int a = 50, b = 70;   
   cout<<"value of a before sending to function: "<<a; 
   cout<<"\n"<<"value of b before sending to function: "<<b; 
   swap(a, b);  // passing value to function 
   cout<<"\n"<<"value of a after sending to function: "<<a; 
   cout<<"\n"<<"value of b after sending to function: "<<b; 
   return 0;   
}  

它将产生以下输出:

value of a before sending to function:  50 
value of b before sending to function:  70 
value of a inside the function:  70 
value of b inside the function:  50 
value of a after sending to function:  50 
value of b after sending to function:  70 

Python中的按值调用

以下程序演示了按值调用如何在Python中工作:

def swap(a,b): 
   t = a; 
   a = b; 
   b = t; 
   print "value of a inside the function: :",a 
   print "value of b inside the function: ",b 

# Now we can call the swap function 
a = 50 
b = 75 
print "value of a before sending to function: ",a 
print "value of b before sending to function: ",b 
swap(a,b) 
print "value of a after sending to function: ", a 
print "value of b after sending to function: ",b 

它将产生以下输出:

value of a before sending to function:  50 
value of b before sending to function:  75 
value of a inside the function: : 75 
value of b inside the function:  50 
value of a after sending to function:  50 
value of b after sending to function:  75 

函数式编程 - 按引用调用

在引用传递中,原始值会被改变,因为我们传递了参数的引用地址。实际参数和形式参数共享相同的地址空间,因此函数内部的任何值更改都会反映在函数内部和外部。

C++ 中的引用传递

以下程序演示了按值调用如何在C++中工作:

#include <iostream> 
using namespace std; 

void swap(int *a, int *b) {    
   int temp; 
   temp = *a; 
   *a = *b; 
   *b = temp; 
   cout<<"\n"<<"value of a inside the function: "<<*a; 
   cout<<"\n"<<"value of b inside the function: "<<*b; 
}  
int main() {     
   int a = 50, b = 75;   
   cout<<"\n"<<"value of a before sending to function: "<<a; 
   cout<<"\n"<<"value of b before sending to function: "<<b; 
   swap(&a, &b);  // passing value to function 
   cout<<"\n"<<"value of a after sending to function: "<<a; 
   cout<<"\n"<<"value of b after sending to function: "<<b; 
   return 0;   
}

它将产生以下输出:

value of a before sending to function:  50 
value of b before sending to function:  75 
value of a inside the function:  75 
value of b inside the function: 50 
value of a after sending to function:  75 
value of b after sending to function:  50 

Python 中的引用传递

以下程序演示了按值调用如何在Python中工作:

def swap(a,b): 
   t = a; 
   a = b; 
   b = t; 
   print "value of a inside the function: :",a 
   print "value of b inside the function: ",b 
   return(a,b) 
    
# Now we can call swap function 
a = 50 
b =75 
print "value of a before sending to function: ",a 
print "value of b before sending to function: ",b 
x = swap(a,b) 
print "value of a after sending to function: ", x[0] 
print "value of b after sending to function: ",x[1] 

它将产生以下输出:

value of a before sending to function:  50 
value of b before sending to function:  75 
value of a inside the function:  75 
value of b inside the function:  50 
value of a after sending to function:  75 
value of b after sending to function:  50  

函数重载

当我们有多个函数名称相同但参数不同的函数时,我们就说这些函数被重载了。此技术用于增强程序的可读性。

重载函数有两种方法,即 -

  • 具有不同数量的参数
  • 具有不同的参数类型

当我们需要对不同数量或类型的参数执行单个操作时,通常会进行函数重载。

C++ 中的函数重载

以下示例演示了如何在 C++(一种面向对象编程语言)中进行函数重载 -

#include <iostream> 
using namespace std;  
void addnum(int,int); 
void addnum(int,int,int); 

int main() {     
   addnum (5,5); 
   addnum (5,2,8); 
   return 0; 
} 

void addnum (int x, int y) {     
   cout<<"Integer number: "<<x+y<<endl; 
} 

void addnum (int x, int y, int z) {     
   cout<<"Float number: "<<x+y+z<<endl; 
}

它将产生以下输出:

Integer number: 10 
Float number: 15 

Erlang 中的函数重载

以下示例演示了如何在 Erlang(一种函数式编程语言)中执行函数重载 -

-module(helloworld).  
-export([addnum/2,addnum/3,start/0]).   

addnum(X,Y) ->  
   Z = X+Y,  
   io:fwrite("~w~n",[Z]).  
    
addnum(X,Y,Z) ->  
   A = X+Y+Z,  
   io:fwrite("~w~n",[A]).  
  
start() ->
   addnum(5,5),     addnum(5,2,8). 

它将产生以下输出:

10 
15 

函数重写

当基类和派生类具有完全相同名称、相同返回类型和相同参数列表的成员函数时,我们就说它是函数重写。

使用 C++ 的函数重写

以下示例演示了如何在 C++(一种面向对象编程语言)中进行函数重写 -

#include <iostream> 
using namespace std;  

class A {  
   public: 
   void display() {   
      cout<<"Base class"; 
   } 
}; 

class B:public A {  
   public: 
   void display() {   
      cout<<"Derived Class"; 
   } 
}; 

int main() {  
   B obj; 
   obj.display(); 
   return 0;  
}

它将产生以下输出

Derived Class 

使用 Python 的函数重写

以下示例演示了如何在 Python(一种函数式编程语言)中执行函数重写 -

class A(object): 
   def disp(self): 
      print "Base Class"  
class B(A): 
   def disp(self): 
      print "Derived Class"  
x = A() 
y = B()  
x.disp() 
y.disp() 

它将产生以下输出:

Base Class 
Derived Class 

函数式编程 - 递归

一个调用自身的函数称为递归函数,这种技术称为递归。递归指令会持续执行,直到另一个指令阻止它。

C++ 中的递归

以下示例演示了递归如何在 C++(一种面向对象编程语言)中工作 -

#include <stdio.h> 
long int fact(int n);  

int main() { 
   int n; 
   printf("Enter a positive integer: "); 
   scanf("%d", &n); 
   printf("Factorial of %d = %ld", n, fact(n)); 
   return 0; 
} 
long int fact(int n) { 
   if (n >= 1) 
      return n*fact(n-1); 
   else 
      return 1; 
} 

它将产生以下输出

Enter a positive integer: 5 
Factorial of 5 = 120 

Python 中的递归

以下示例演示了递归如何在 Python(一种函数式编程语言)中工作 -

def fact(n): 
   if n == 1: 
      return n 
   else: 
      return n* fact (n-1)  

# accepts input from user 
num = int(input("Enter a number: "))  
# check whether number is positive or not 

if num < 0: 
   print("Sorry, factorial does not exist for negative numbers") 
else: 
   print("The factorial of " + str(num) +  " is " + str(fact(num))) 

它将产生以下输出:

Enter a number: 6
The factorial of 6 is 720   

高阶函数

高阶函数 (HOF) 是至少满足以下条件之一的函数 -

  • 将一个或多个函数作为参数
  • 返回一个函数作为其结果

PHP 中的 HOF

以下示例演示了如何在 PHP(一种面向对象编程语言)中编写高阶函数 -

<?php  
$twice = function($f, $v) { 
   return $f($f($v)); 
};  

$f = function($v) { 
   return $v + 3; 
}; 

echo($twice($f, 7));

它将产生以下输出:

13

Python 中的 HOF

以下示例演示了如何在 Python(一种面向对象编程语言)中编写高阶函数 -

def twice(function): 
   return lambda x: function(function(x))  
def f(x): 
   return x + 3 
g = twice(f)
print g(7)      

它将产生以下输出:

13

函数式编程 - 数据类型

数据类型定义了对象可以具有的值类型以及可以在其上执行的操作。在使用数据类型之前,应首先声明它。不同的编程语言支持不同的数据类型。例如,

  • C 支持 char、int、float、long 等。
  • Python 支持字符串、列表、元组等。

从广义上讲,数据类型有三种 -

  • 基本数据类型 - 这些是程序员直接使用的预定义数据类型,用于根据需要仅存储一个值,即整数类型、字符类型或浮点类型。例如 - int、char、float 等。

  • 派生数据类型 - 这些数据类型是使用内置数据类型派生的,由程序员设计用于根据其需要存储相同类型多个值。例如 - 数组、指针、函数、列表等。

  • 用户定义数据类型 - 这些数据类型是使用内置数据类型派生的,这些数据类型被封装到单个数据类型中,用于根据需要存储相同类型或不同类型或同时存储两种类型的多个值。例如 - 类、结构体等。

C++ 支持的数据类型

下表列出了 C++ 支持的数据类型 -

数据类型 大小 范围
char 1 字节 -128 到 127 或 0 到 255
unsigned char 1 字节 0 到 255
signed char 1 字节 -128 到 127
int 4 字节 -2147483648 到 2147483647
unsigned int 4 字节 0 到 4294967295
signed int 4 字节 -2147483648 到 2147483647
short int 2 字节 -32768 到 32767
unsigned short int 2 字节 0 到 65,535
signed short int 2 字节 -32768 到 32767
long int 4 字节 -2,147,483,648 到 2,147,483,647
signed long int 4 字节 -2,147,483,648 到 2,147,483,647
unsigned long int 4 字节 0 到 4,294,967,295
float 4 字节 +/- 3.4e +/- 38 (~7 位)
double 8 字节 +/- 1.7e +/- 308 (~15 位)
long double 8 字节 +/- 1.7e +/- 308 (~15 位)

Java 支持的数据类型

Java 支持以下数据类型 -

数据类型 大小 范围
byte 1 字节 -128 到 127
char 2 字节 0 到 65,536
short 2 字节 -32,7688 到 32,767
int 4 字节 -2,147,483,648 到 2,147,483,647
long 8 字节 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
float 4 字节 -2147483648 到 2147483647
double 8 字节 +9.223*1018
Boolean 1 位 True 或 False

Erlang 支持的数据类型

在本节中,我们将讨论 Erlang(一种函数式编程语言)支持的数据类型。

数字

Erlang 支持两种类型的数字字面量,即整数浮点数。请查看以下示例,该示例演示了如何添加两个整数值 -

-module(helloworld).
-export([start/0]). 
start() -> 
   io:fwrite("~w",[5+4]). 

它将产生以下输出 -

9

原子

原子是一个其值不能更改的字符串。它必须以小写字母开头,并且可以包含任何字母数字字符和特殊字符。当原子包含特殊字符时,应将其括在单引号 (') 内。请查看以下示例以更好地理解。

-module(helloworld). 
-export([start/0]). 

start()-> 
   io:fwrite(monday). 

它将产生以下输出:

monday

注意 - 尝试将原子更改为以大写“M”开头的“Monday”。程序将产生错误。

Boolean

此数据类型用于将结果显示为truefalse。请查看以下示例。它演示了如何比较两个整数。

-module(helloworld). 
-export([start/0]). 

start() -> 
   io:fwrite(5 =< 9). 

它将产生以下输出:

true

位字符串

位字符串用于存储未类型化内存区域。请查看以下示例。它演示了如何将位字符串的 2 位转换为列表。

-module(helloworld). 
-export([start/0]). 

start() -> 
   Bin2 = <<15,25>>, 
   P = binary_to_list(Bin2), 
   io:fwrite("~w",[P]). 

它将产生以下输出:

[15,25]

元组

元组是一种复合数据类型,具有固定数量的项。元组的每一项都称为元素。元素的数量是元组的大小。以下示例演示了如何定义一个包含 5 项的元组并打印其大小。

-module(helloworld).  
-export([start/0]).  

start() ->  
   K = {abc,50,pqr,60,{xyz,75}} ,  
   io:fwrite("~w",[tuple_size(K)]). 

它将产生以下输出:

5 

映射

映射是一种复合数据类型,具有可变数量的键值关联。映射中的每个键值关联都称为关联对。对的部分称为元素。关联对的数量称为映射的大小。以下示例演示了如何定义一个包含 3 个映射的映射并打印其大小。

-module(helloworld).  
-export([start/0]).  
 
start() ->  
   Map1 = #{name => 'abc',age => 40, gender => 'M'},  
   io:fwrite("~w",[map_size(Map1)]). 

它将产生以下输出:

3 

列表

列表是一种复合数据类型,具有可变数量的项。列表中的每一项都称为元素。元素的数量称为列表的长度。以下示例演示了如何定义一个包含 5 个项目的列表并打印其大小。

-module(helloworld).  
-export([start/0]).  

start() ->  
   List1 = [10,15,20,25,30] ,  
   io:fwrite("~w",[length(List1)]). 

它将产生以下输出:

5 

注意 - Erlang 中未定义“字符串”数据类型。

函数式编程 - 多态性

在编程方面,多态性意味着多次重用单个代码。更具体地说,它是一种程序能够根据其数据类型或类以不同方式处理对象的能力。

多态性分为两种 -

  • 编译时多态性 - 此类型的多态性可以通过方法重载来实现。

  • 运行时多态性 - 此类型的多态性可以通过方法重写和虚函数来实现。

多态性的优点

多态性提供以下优点 -

  • 它帮助程序员重用代码,即,一旦编写、测试和实现的类可以根据需要重用。节省大量时间。

  • 单个变量可用于存储多种数据类型。

  • 易于调试代码。

多态数据类型

可以使用泛型指针实现多态数据类型,泛型指针仅存储字节地址,而不存储该内存地址处存储的数据类型。例如,

function1(void *p, void *q) 

其中pq是泛型指针,可以将int、float(或任何其他)值作为参数。

C++ 中的多态函数

以下程序演示了如何在 C++(一种面向对象编程语言)中使用多态函数。

#include <iostream> 
Using namespace std: 

class A {  
   public: 
   void show() {    
      cout << "A class method is called/n"; 
   } 
}; 

class B:public A { 
   public: 
   void show() {   
      cout << "B class method is called/n"; 
   } 
};  

int main() {   
   A x;        // Base class object 
   B y;        // Derived class object 
   x.show();   // A class method is called 
   y.show();   // B class method is called 
   return 0; 
} 

它将产生以下输出:

A class method is called 
B class method is called 

Python 中的多态函数

以下程序演示了如何在 Python(一种函数式编程语言)中使用多态函数。

class A(object): 
   def show(self): 
      print "A class method is called" 
  
class B(A): 
   def show(self): 
      print "B class method is called" 
  
def checkmethod(clasmethod): 
   clasmethod.show()  

AObj = A() 
BObj = B() 
  
checkmethod(AObj) 
checkmethod(BObj) 

它将产生以下输出:

A class method is called 
B class method is called 

函数式编程 - 字符串

字符串是一组字符,包括空格。我们可以说它是一个字符的一维数组,以 NULL 字符('\0')结尾。字符串也可以被视为大多数编程语言(如 C、C++、Java、PHP、Erlang、Haskell、Lisp 等)支持的预定义类。

下图显示了字符串“Tutorial”在内存中的外观。

Strings

在 C++ 中创建字符串

以下程序是一个示例,演示了如何在 C++(一种面向对象编程语言)中创建字符串。

#include <iostream> 
using namespace std; 

int main () {    
   char greeting[20] = {'H', 'o', 'l', 'i', 'd', 'a', 'y', '\0'}; 
   cout << "Today is: "; 
   cout << greeting << endl; 
   return 0; 
} 

它将产生以下输出:

Today is: Holiday 

Erlang 中的字符串

以下程序是一个示例,演示了如何在 Erlang(一种函数式编程语言)中创建字符串。

-module(helloworld).  
-export([start/0]).   
start() -> 
   Str = "Today is: Holiday",  
   io:fwrite("~p~n",[Str]). 

它将产生以下输出:

"Today is: Holiday" 

C++ 中的字符串操作

不同的编程语言支持字符串的不同方法。下表显示了 C++ 支持的一些预定义字符串方法。

序号 方法和描述
1

Strcpy(s1,s2)

它将字符串 s2 复制到字符串 s1 中

2

Strcat(s1,s2)

它将字符串 s2 添加到 s1 的末尾

3

Strlen(s1)

它提供字符串 s1 的长度

4

Strcmp(s1,s2)

当字符串 s1 和 s2 相同时,它返回 0

5

Strchr(s1,ch)

它返回指向字符串 s1 中字符 ch 首次出现的指针

6

Strstr(s1,s2)

它返回字符串 s1 中字符串 s2 的第一次出现的指针。

以下程序展示了如何在 C++ 中使用上述方法 -

#include <iostream> 
#include <cstring> 
using namespace std; 

int main () {   
   char str1[20] = "Today is "; 
   char str2[20] = "Monday"; 
   char str3[20]; 
   int  len ;  
   strcpy( str3, str1); // copy str1 into str3 
   cout << "strcpy( str3, str1) : " << str3 << endl;  

   strcat( str1, str2); // concatenates str1 and str2 
   cout << "strcat( str1, str2): " << str1 << endl;  

   len = strlen(str1);  // String length after concatenation 
   cout << "strlen(str1) : " << len << endl; 
   return 0; 
}    

它将产生以下输出:

strcpy(str3, str1)   :  Today is 
strcat(str1, str2)   :  Today is Monday 
strlen(str1)         :  15 

Erlang 中的字符串操作

下表显示了 Erlang 支持的预定义字符串方法列表。

序号 方法和描述
1

len(s1)

返回给定字符串中的字符数。

2

equal(s1,s2)

当字符串 s1 和 s2 相等时返回 true,否则返回 false。

3

concat(s1,s2)

它将字符串 s2 添加到字符串 s1 的末尾。

4

str(s1,ch)

它返回字符 ch 在字符串 s1 中的索引位置。

5

str (s1,s2)

它返回 s2 在字符串 s1 中的索引位置。

6

substr(s1,s2,num)

此方法根据起始位置和从起始位置开始的字符数,从字符串 s1 中返回字符串 s2。

7

to_lower(s1)

此方法返回小写字符串。

以下程序展示了如何在 Erlang 中使用上述方法。

-module(helloworld).  
-import(string,[concat/2]).  
-export([start/0]).  
   start() ->  
   S1 = "Today is ",  
   S2 = "Monday",  
   S3 = concat(S1,S2),  
   io:fwrite("~p~n",[S3]). 

它将产生以下输出:

"Today is Monday" 

函数式编程 - 列表

列表是函数式编程语言中最通用的数据类型,用于存储一系列相似的数据项。这个概念类似于面向对象编程中的数组。列表项可以写在方括号中,用逗号分隔。将数据写入列表的方式因语言而异。

在 Java 中创建数字列表的程序

列表不是 Java/C/C++ 中的数据类型,但我们有替代方法在 Java 中创建列表,即使用ArrayListLinkedList

以下示例展示了如何在 Java 中创建列表。这里我们使用 LinkedList 方法来创建数字列表。

import java.util.*; 
import java.lang.*; 
import java.io.*;  

/* Name of the class has to be "Main" only if the class is public. */ 

public class HelloWorld { 
   public static void main (String[] args) throws java.lang.Exception { 
      List<String> listStrings = new LinkedList<String>(); 
      listStrings.add("1"); 
      listStrings.add("2"); 
      listStrings.add("3"); 
      listStrings.add("4"); 
      listStrings.add("5"); 
  
      System.out.println(listStrings); 
   } 
} 

它将产生以下输出:

[1, 2, 3, 4, 5] 

在 Erlang 中创建数字列表的程序

-module(helloworld).  
-export([start/0]).   

start() ->  
   Lst = [1,2,3,4,5],  
   io:fwrite("~w~n",[Lst]). 

它将产生以下输出:

[1 2 3 4 5]

Java 中的列表操作

在本节中,我们将讨论一些可以在 Java 中对列表执行的操作。

向列表中添加元素

方法 add(Object)、add(index, Object)、addAll() 用于向列表中添加元素。例如,

ListStrings.add(3, “three”) 

从列表中删除元素

方法 remove(index) 或 removeobject() 用于从列表中删除元素。例如,

ListStrings.remove(3,”three”)

注意 - 要删除列表中的所有元素,请使用 clear() 方法。

从列表中检索元素

get() 方法用于从指定位置的列表中检索元素。getFirst() 和 getLast() 方法可以在 LinkedList 类中使用。例如,

String str = ListStrings.get(2) 

更新列表中的元素

set(index,element) 方法用于使用指定元素更新指定索引处的元素。例如,

listStrings.set(2,”to”)

对列表中的元素进行排序

方法 collection.sort() 和 collection.reverse() 用于按升序或降序对列表进行排序。例如,

Collection.sort(listStrings) 

在列表中搜索元素

以下三种方法根据需要使用 -

布尔型 contains(Object) 方法如果列表包含指定的元素则返回true,否则返回false

int indexOf(Object) 方法返回列表中指定元素第一次出现的索引,如果找不到该元素则返回 -1。

int lastIndexOf(Object) 返回列表中指定元素最后一次出现的索引,如果找不到该元素则返回 -1。

Erlang 中的列表操作

在本节中,我们将讨论一些可以在 Erlang 中对列表执行的操作。

添加两个列表

append(listfirst, listsecond) 方法用于通过添加两个列表来创建一个新列表。例如,

append(list1,list2)

删除元素

delete(element, listname) 方法用于从列表中删除指定的元素,并返回新列表。例如,

delete(5,list1) 

从列表中删除最后一个元素

droplast(listname) 方法用于从列表中删除最后一个元素并返回一个新列表。例如,

droplast(list1) 

搜索元素

member(element, listname) 方法用于在列表中搜索元素,如果找到则返回 true,否则返回 false。例如,

member(5,list1) 

获取最大值和最小值

max(listname) 和 min(listname) 方法用于查找列表中的最大值和最小值。例如,

max(list1) 

对列表元素进行排序

方法 sort(listname) 和 reverse(listname) 用于按升序或降序对列表进行排序。例如,

sort(list1) 

添加列表元素

sum(listname) 方法用于将列表的所有元素加起来并返回它们的和。例如,

sum(list1)

使用 Java 按升序和降序对列表进行排序

以下程序展示了如何使用 Java 按升序和降序对列表进行排序 -

import java.util.*; 
import java.lang.*; 
import java.io.*;  

public class SortList { 
   public static void main (String[] args) throws java.lang.Exception { 
      List<String> list1 = new ArrayList<String>(); 
      list1.add("5"); 
      list1.add("3"); 
      list1.add("1"); 
      list1.add("4"); 
      list1.add("2"); 
  
      System.out.println("list before sorting: " + list1); 
  
      Collections.sort(list1); 
  
      System.out.println("list in ascending order: " + list1); 
      Collections.reverse(list1); 
  
      System.out.println("list in dsending order: " + list1); 
   } 
} 

它将产生以下输出:

list before sorting     : [5, 3, 1, 4, 2] 
list in ascending order : [1, 2, 3, 4, 5] 
list in dsending order  : [5, 4, 3, 2, 1] 

使用 Erlang 按升序对列表进行排序

以下程序展示了如何使用 Erlang(一种函数式编程语言)按升序和降序对列表进行排序 -

-module(helloworld).  
-import(lists,[sort/1]).  
-export([start/0]).   

start() ->  
   List1 = [5,3,4,2,1],  
   io:fwrite("~p~n",[sort(List1)]), 

它将产生以下输出:

[1,2,3,4,5] 

函数式编程 - 元组

元组是一种复合数据类型,具有固定数量的项。元组中的每个项称为元素。元素的数量是元组的大小。

在 C# 中定义元组的程序

以下程序展示了如何使用 C#(一种面向对象编程语言)定义一个包含四个项的元组并打印它们。

using System; 
public class Test { 
   public static void Main() { 
      var t1 = Tuple.Create(1, 2, 3, new Tuple<int, int>(4, 5));   
      Console.WriteLine("Tuple:" + t1);    
   } 
} 

它将产生以下输出:

Tuple :(1, 2, 3, (4, 5)) 

在 Erlang 中定义元组的程序

以下程序展示了如何使用 Erlang(一种函数式编程语言)定义一个包含四个项的元组并打印它们。

-module(helloworld).  
-export([start/0]).   

start() -> 
   P = {1,2,3,{4,5}} ,  
   io:fwrite("~w",[P]). 

它将产生以下输出:

{1, 2, 3, {4, 5}} 

元组的优点

元组提供以下优点 -

  • 元组本质上是固定大小的,即我们不能向元组添加/删除元素。

  • 我们可以在元组中搜索任何元素。

  • 元组比列表快,因为它们具有恒定的值集。

  • 元组可以用作字典键,因为它们包含不可变的值,如字符串、数字等。

元组与列表

元组 列表
元组是不可变的,即我们不能更新其数据。 列表是可变的,即我们可以更新其数据。
元组中的元素可以是不同类型。 列表中的所有元素都是同一类型。
元组用圆括号表示元素。 列表用方括号表示元素。

元组上的操作

在本节中,我们将讨论一些可以在元组上执行的操作。

检查插入的值是否为元组

方法is_tuple(tuplevalues)用于确定插入的值是否为元组。当插入的值为元组时,它返回true,否则返回false。例如,

-module(helloworld).  
-export([start/0]).  

start() ->  
   K = {abc,50,pqr,60,{xyz,75}} , io:fwrite("~w",[is_tuple(K)]). 

它将产生以下输出:

True

将列表转换为元组

方法list_to_tuple(listvalues)将列表转换为元组。例如,

-module(helloworld).  
-export([start/0]).  

start() ->  
   io:fwrite("~w",[list_to_tuple([1,2,3,4,5])]). 

它将产生以下输出:

{1, 2, 3, 4, 5} 

将元组转换为列表

方法tuple_to_list(tuplevalues)将指定的元组转换为列表格式。例如,

-module(helloworld).  
-export([start/0]).  

start() ->  
   io:fwrite("~w",[tuple_to_list({1,2,3,4,5})]). 

它将产生以下输出:

[1, 2, 3, 4, 5] 

检查元组大小

方法tuple_size(tuplename)返回元组的大小。例如,

-module(helloworld).  
-export([start/0]).  

start() ->  
   K = {abc,50,pqr,60,{xyz,75}} ,  
   io:fwrite("~w",[tuple_size(K)]). 

它将产生以下输出:

5

函数式编程 - 记录

记录是一种用于存储固定数量元素的数据结构。它类似于 C 语言中的结构体。在编译时,其表达式会被翻译成元组表达式。

如何创建记录?

关键字“record”用于创建记录,并指定记录名称及其字段。其语法如下 -

record(recodname, {field1, field2, . . fieldn})

将值插入记录的语法为 -

#recordname {fieldName1 = value1, fieldName2 = value2 .. fieldNamen = valuen}

使用 Erlang 创建记录的程序

在以下示例中,我们创建了一个名为student的记录,它有两个字段,即snamesid

-module(helloworld).  
-export([start/0]).  
-record(student, {sname = "", sid}).   

start() ->  
   S = #student{sname = "Sachin",sid = 5}. 

使用 C++ 创建记录的程序

以下示例展示了如何使用 C++(一种面向对象编程语言)创建记录 -

#include<iostream> 
#include<string>
using namespace std; 

class student {
   public: 
   string sname; 
   int sid; 
   15 
}; 

int main() {    
   student S;  
   S.sname = "Sachin"; 
   S.sid = 5; 
   return 0;  
} 

使用 Erlang 访问记录值的程序

以下程序展示了如何使用 Erlang(一种函数式编程语言)访问记录值 -

-module(helloworld).  
-export([start/0]).  
-record(student, {sname = "", sid}).   

start() ->  
   S = #student{sname = "Sachin",sid = 5},  
   io:fwrite("~p~n",[S#student.sid]),  
   io:fwrite("~p~n",[S#student.sname]). 

它将产生以下输出:

5 
"Sachin"

使用 C++ 访问记录值的程序

以下程序展示了如何使用 C++ 访问记录值 -

#include<iostream> 
#include<string> 
using namespace std; 

class student {     
   public: 
   string sname; 
   int sid; 
}; 

int main() {     
   student S;  
   S.sname = "Sachin"; 
   S.sid = 5; 
   cout<<S.sid<<"\n"<<S.sname;  
   return 0; 
} 

它将产生以下输出:

5 
Sachin 

可以通过将值更改为特定字段,然后将该记录分配给新的变量名来更新记录值。请查看以下两个示例,了解如何在面向对象和函数式编程语言中完成此操作。

使用 Erlang 更新记录值的程序

以下程序展示了如何使用 Erlang 更新记录值 -

-module(helloworld).  
-export([start/0]).  
-record(student, {sname = "", sid}).   

start() ->  
   S = #student{sname = "Sachin",sid = 5},  
   S1 = S#student{sname = "Jonny"},  
   io:fwrite("~p~n",[S1#student.sid]),  
   io:fwrite("~p~n",[S1#student.sname]). 

它将产生以下输出:

5
"Jonny" 

使用 C++ 更新记录值的程序

以下程序展示了如何使用 C++ 更新记录值 -

#include<iostream> 
#include<string> 
using namespace std;  

class student {    
   public: 
   string sname; 
   int sid; 
};  

int main() {     
   student S;  
   S.sname = "Jonny"; 
   S.sid = 5; 
   cout<<S.sname<<"\n"<<S.sid; 
   cout<<"\n"<< "value after updating"<<"\n"; 
   S.sid = 10; 
   cout<<S.sname<<"\n"<<S.sid; 
   return 0; 
}

它将产生以下输出:

Jonny 
5 
value after updating 
Jonny 
10 

函数式编程 - Lambda 演算

Lambda 演算是一个由 Alonzo Church 在 1930 年代开发的框架,用于研究使用函数进行计算。

  • 函数创建 - Church 引入了λx.E符号来表示函数,其中“x”是形式参数,“E”是函数体。这些函数可以是无名函数和单参数函数。

  • 函数应用 - Church 使用E1.E2符号来表示将函数E1应用于实际参数E2。并且所有函数都是单参数函数。

Lambda 演算的语法

Lambda 演算包括三种不同类型的表达式,即:

E :: = x(变量)

| E1 E2(函数应用)

| λx.E(函数创建)

其中λx.E称为 Lambda 抽象,E 称为 λ-表达式。

评估 Lambda 演算

纯 Lambda 演算没有内置函数。让我们评估以下表达式 -

(+ (* 5 6) (* 8 3)) 

在这里,我们不能以“+”开头,因为它只对数字进行运算。有两个可约表达式:(* 5 6) 和 (* 8 3)。

我们可以先减少其中一个。例如 -

(+ (* 5 6) (* 8 3)) 
(+ 30 (* 8 3)) 
(+ 30 24) 
= 54

β-归约规则

我们需要一个归约规则来处理 λs。

(λx . * 2 x) 4 
(* 2 4) 
= 8

这称为 β-归约。

形式参数可以使用多次 -

(λx . + x x) 4 
(+ 4 4) 
= 8

当有多个项时,我们可以按如下方式处理它们 -

(λx . (λx . + (− x 1)) x 3) 9 

内部x属于内部λ,外部 x 属于外部λ

(λx . + (− x 1)) 9 3 
+ (− 9 1) 3 
+ 8 3 
= 11

自由变量和绑定变量

在一个表达式中,变量的每次出现要么是“自由的”(对 λ),要么是“绑定的”(对 λ)。

(λx . E) y 的 β-归约将E中每个自由出现的x替换为y。例如 -

Bound Variables

Alpha 归约

Alpha 归约非常简单,可以在不改变 lambda 表达式含义的情况下完成。

λx . (λx . x) (+ 1 x) ↔ α λx . (λy . y) (+ 1 x) 

例如 -

(λx . (λx . + (− x 1)) x 3) 9 
(λx . (λy . + (− y 1)) x 3) 9 
(λy . + (− y 1)) 9 3 
+ (− 9 1) 3 
+ 8 3 
11 

Church-Rosser 定理

Church-Rosser 定理陈述如下 -

  • 如果 E1 ↔ E2,则存在一个 E 使得 E1 → E 且 E2 → E。“以任何方式进行归约最终都可以产生相同的结果。”

  • 如果 E1 → E2,且 E2 是范式,则 E1 到 E2 的正则顺序归约存在。“如果存在范式,则正则顺序归约将始终产生范式。”

函数式编程 - 惰性求值

惰性求值(Lazy evaluation)是一种求值策略,它延迟表达式求值,直到需要其值时才进行计算。它避免了重复计算。Haskell就是一个很好的例子,这门函数式编程语言的基础就是建立在惰性求值之上的。

惰性求值被用于Unix的map函数,通过只加载磁盘上需要的页面来提高性能。剩余的页面不会分配内存。

惰性求值 - 优点

  • 它允许语言运行时丢弃与表达式最终结果没有直接关联的子表达式。

  • 它通过丢弃临时计算和条件判断来减少算法的时间复杂度。

  • 它允许程序员在初始化数据结构的组件后以任意顺序访问它们,只要它们没有循环依赖关系。

  • 它最适合加载那些很少访问的数据。

惰性求值 - 缺点

  • 它迫使语言运行时通过创建thunk(延迟对象)来保存子表达式的求值,直到最终结果需要它。

  • 有时它会增加算法的空间复杂度。

  • 很难确定它的性能,因为它包含了在执行之前表达式的thunk。

使用Python实现惰性求值

Python中的range方法遵循惰性求值的理念。它节省了较大范围的执行时间,并且我们不需要一次获取所有值,因此也节省了内存消耗。请看下面的例子。

r = range(10) 
print(r) 
range(0, 10) 
print(r[3]) 

它将产生以下输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 
3 

文件I/O操作

当程序终止时,我们需要文件来存储程序的输出。使用文件,我们可以使用不同语言中的各种命令访问相关信息。

以下是可以在文件上执行的一些操作列表:

  • 创建新文件
  • 打开现有文件
  • 读取文件内容
  • 在文件中搜索数据
  • 写入新文件
  • 更新现有文件的内容
  • 删除文件
  • 关闭文件

写入文件

要将内容写入文件,首先需要打开所需的文件。如果指定的文件不存在,则会创建一个新文件。

让我们看看如何使用C++将内容写入文件。

示例

#include <iostream> 
#include <fstream> 
using namespace std;  

int main () {   
   ofstream myfile; 
   myfile.open ("Tempfile.txt", ios::out); 
   myfile << "Writing Contents to file.\n"; 
   cout << "Data inserted into file"; 
   myfile.close(); 
   return 0; 
} 

注意 -

  • fstream是用于控制文件读写操作的流类。

  • ofstream是用于将内容写入文件的流类。

让我们看看如何使用Erlang(一种函数式编程语言)将内容写入文件。

-module(helloworld).  
-export([start/0]).   

start() ->
   {ok, File1} = file:open("Tempfile.txt", [write]),  
   file:write(File1,"Writting contents to file"), 
   io:fwrite("Data inserted into file\n"). 

注意 -

  • 要打开文件,我们必须使用open(filename,mode)

  • 写入文件内容的语法:write(filemode,file_content)

输出 - 当我们运行这段代码时,“Writing contents to file”将被写入文件Tempfile.txt中。如果文件有任何现有内容,则将被覆盖。

从文件读取

要从文件读取,首先必须以读取模式打开指定的文件。如果文件不存在,则其相应的方法返回NULL。

以下程序演示了如何在C++中读取文件内容:

#include <iostream> 
#include <fstream> 
#include <string> 
using namespace std;  

int main () {
   string readfile; 
   ifstream myfile ("Tempfile.txt",ios::in); 
   
   if (myfile.is_open()) {     
      while ( getline (myfile,readfile) ) {       
         cout << readfile << '\n'; 
      } 
      myfile.close(); 
   } else  
      cout << "file doesn't exist";  
   return 0; 
} 

它将产生以下输出:

Writing contents to file 

注意 - 在此程序中,我们使用“ios::in”以读取模式打开了一个文本文件,然后将其内容打印到屏幕上。我们使用了while循环,使用“getline”方法逐行读取文件内容。

以下程序演示了如何使用Erlang执行相同的操作。在这里,我们将使用read_file(filename)方法读取指定文件中的所有内容。

-module(helloworld).  
-export([start/0]).   

start() ->  
   rdfile = file:read_file("Tempfile.txt"),  
   io:fwrite("~p~n",[rdfile]). 

它将产生以下输出:

ok, Writing contents to file 

删除现有文件

我们可以使用文件操作删除现有文件。以下程序演示了如何使用C++删除现有文件:

#include <stdio.h> 

int main () {   
   if(remove( "Tempfile.txt" ) != 0 ) 
      perror( "File doesn’t exist, can’t delete" ); 
   else 
      puts( "file deleted successfully " ); 
   return 0; 
}   

它将产生以下输出:

file deleted successfully 

以下程序演示了如何在Erlang中执行相同的操作。在这里,我们将使用delete(filename)方法删除现有文件。

-module(helloworld).  
-export([start/0]).   

start() ->  
   file:delete("Tempfile.txt"). 

输出 - 如果文件“Tempfile.txt”存在,则将其删除。

确定文件大小

以下程序演示了如何使用C++确定文件的大小。这里,函数fseek将与流关联的位置指示器设置为新位置,而ftell返回流中的当前位置。

#include <stdio.h> 

int main () {  
   FILE * checkfile; 
   long size; 
   checkfile = fopen ("Tempfile.txt","rb"); 
   
   if (checkfile == NULL)  
      perror ("file can’t open"); 
   else {   
      fseek (checkfile, 0, SEEK_END);    // non-portable 
      size = ftell (checkfile); 
      fclose (checkfile); 
      printf ("Size of Tempfile.txt: %ld bytes.\n",size); 
   } 
   return 0; 
}    

输出 - 如果文件“Tempfile.txt”存在,则将显示其大小(以字节为单位)。

以下程序演示了如何在Erlang中执行相同的操作。在这里,我们将使用file_size(filename)方法确定文件的大小。

-module(helloworld).  
-export([start/0]).   

start() ->  
   io:fwrite("~w~n",[filelib:file_size("Tempfile.txt")]). 

输出 - 如果文件“Tempfile.txt”存在,则将显示其大小(以字节为单位)。否则,将显示“0”。

广告