C++模版编程实现Haskell的函数模式匹配特性[图]

C++模版编程实现Haskell的函数模式匹配特性[图]:
大神 Bartosz Milewski 在2009年写了一篇文章《What Does Haskell Have to Do with C++?》,使用C++实现Haskell函数式编程语言的一些特性。【传送门在文末】
其中有这样一段例子:
// code 1
1.template<int n>class fact {
2.public:

  1. staticconstint value = n * fact<n -1>::value;
    4.};
  2. 6.template<>class fact<0>{// specialization for n = 0
    7.public:
  3. staticconstint value =1;
    9.};
    注:原文中使用的是struct关键字,这里改为class并加上了public
    我猜,你没看懂。没关系,我们先跳过上面这一段有着【令人恐怖的语法】的C++模版代码。
    上面的例子想干嘛呢?其实它只是想计算n的阶乘。

    如果你在C语言里面学过递归,应该知道下面这段计算阶乘的递归函数
    // code 2
    int fact(int n){
    if(0== n )
    return1; //0阶问题答案。0! 等于1
    else
    return( n fact( n -1)); //问题降阶:n阶->n-1阶
    }
    它的效果就等于下面的代码
    // code 3
    int fact2(int n){ // 用 for 循环计算阶乘
    int p =1;
    for(int i=n; i >=1; i--)
    p
    = i;
    return p;
    }
    那么,第一段代码(code1)与第二段代码(code2)的区别在哪里呢?
    区别在于,code1是在编译时(由编译器)计算的,code2是在运行时(就是代码运行的时候)计算的。
    现在来解释一下code1 (部分根据Bartosz Milewski文中的说法)
    // code 1
    / 第1行代码声明了一个类模版 fact。
    这个模版接受一个“非类型参数”n,
    n是整数。
    /
    1.template<int n>class fact {
    2.public:
    / 第3行代码声明了一个静态整型常量
    成员 value。而 value 的值是使用
    递归模版表示的
    /
  4. staticconstint value = n * fact<n -1>::value;
    4.};
  5. / 第6行代码是“特化”类模版fact,
    也就是显式地给出某种类型参数的
    类模板的一个实例的代码,而非由
    编译器生成。
    在这里,是给出了参数n为0时模板
    fact的代码。这样,编译器不会再
    根据类模版fact生成n=0时的代码
    关于模版特化,详见文末链接
    /
    6.template<>class fact<0>{// specialization for n = 0
    7.public:
  6. static const int value = 1;
  7. };
    / 根据C++规范,模版特化的代码必须
    放到模版声明之后。
    因此上面的代码看上去好像先处理了
    由n阶到n-1阶的降阶问题,然后再给
    出了0阶的解答
    这可不像code2。code2中有if/else,
    因此可以把降阶代码与0阶解答代码调
    换先后次序(当然if条件得改)。
    /
    那么这个用模版计算阶乘的代码(类?)该怎么用呢?如下:
    cout <<"Factorial of 0 = "<< fact<0>::value << endl;
    其中,C++编译器会为“fact<0>::value”这个调用匹配最合适的模版代码,也就是code1中的第6-9行代码。
    如果用非零参数调用呢?
    cout <<"Factorial of 8 = "<< fact<8>::value << endl;
    其中,C++编译器会为“fact<8>::value”这个调用匹配code1中的第1-4行代码。
    前面blahblhaaaaaaaaaaaah讲了一大堆,其实都不是正经事儿。
    正经是下面的Haskell代码:
    //code 4
  8. fact 0=1
  9. fact n = n * fact (n -1)
    上面两行代码定义了函数fact。fact是函数名,fact的后面、等号的前面是函数的参数。等号后面是函数体,函数体的计算结果就是fact函数的返回值。
    当程序员调用【fact 8】的时候(参数是8,因为Haskell函数调用一般不像C++那样给参数加括号),Haskell会将之匹配到上面代码的第2行。谁动了我的奶酪读书笔记(http://www.simayi.net/dushubiji/6208.html)摘抄好词好句及感悟赏析,这种参数匹配,是Haskell特有的函数声明与调用方式。

    所以前面的code1中C++模版代码,就是在模仿 code4 中的Haskell代码。
    下面给出一个完整的Haskell程序
    moduleFactwhere
    importSystem.IO
    fact::Integer->Integer
    fact0=1
    fact n = n * fact (n-1)
    main::IO()
    main=do
    putStrLn $"8! = "++ show (fact 8)
    putStrLn $"88! = "++ show (fact 88)
    上面的代码输出结果是:
    8! = 40320
    88! =185482642257398439114796845645546284380220968949399346684421580986889562184028199319100141244804501828416633516851200000000000000000000
    Haskell对C++说:我能算88!,你行吗?
    C++说:你欺负人!

原文地址:http://blog.51cto.com/13868750/2146809

时间: 2024-10-16 07:45:04

C++模版编程实现Haskell的函数模式匹配特性[图]的相关文章

数据库编程1 Oracle 过滤 函数 分组 外连接 自连接

[本文谢绝转载原文来自http://990487026.blog.51cto.com] <大纲> 数据库编程1 Oracle 过滤 函数 分组 外连接 自连接 本文实验基于的数据表: winsows安装好Oracle11g之后,开始实验 SQLplus 登陆 ORacle sqlplus 退出的方式 查看用户之下有什么表 查看表的所有记录,不区分大小写 设置SQLplus行宽,页宽,列宽: 清屏命令 select as 语法 1,as别名的使用 2,没有引号带有空格的别名,无法识别: 3,带有

数据库编程2 Oracle 过滤 函数 分组 外连接 自连接

[本文谢绝转载原文来自http://990487026.blog.51cto.com] 续:数据库编程1 Oracle 过滤 函数 分组 外连接 自连接 where like模糊查询,查询员工姓名是4个字母 SQL> select * from emp where ename like '____';      EMPNO ENAME                JOB                       MGR HIREDATE          SAL       COMM    

Linux 高性能服务器编程——高级I/O函数

重定向dup和dup2函数 [cpp] view plaincopyprint? #include <unistd.h> int dup(int file_descriptor); int dup2(int file_descriptor_one, int file_descriptor_two); dup创建一个新的文件描述符, 此描述符和原有的file_descriptor指向相同的文件.管道或者网络连接. dup返回的文件描述符总是取系统当前可用的最小整数值. dup2函数通过使用参数f

c#编程基础之字符串函数

c#常用的字符串函数 例一: 获取字符串的大小写函数 ToLower():得到字符串的小写形式 ToUpper():得到字符串的大写形式 注意: 字符串时不可变的,所以这些函数都不会直接改变字符串的内容,而是把修改后的字符串通过函数返回值的形式返回. 源码如下: using System; using System.Collections.Generic; using System.Text; namespace 字符串函数学习 { class Program { static void Mai

Linux高性能server编程——高级I/O函数

 高级I/O函数 pipe函数 pipe函数用于创建一个管道,实现进程间的通信. #include <unistd.h> int pipe(int pipefd[2]); 通过pipe函数创建的文件描写叙述符fd[0]和fd[1]分别构成管道的两端,往fd[1]写入的数据能够从fd[0]读出,不能反过来.管道内部传输的数据时字节流,和TCP字节流概念同样,但有差别,管道本身拥有一个容量限制,它规定假设应用程序不将数据从管道读走的话,该管道最多能被写入多少字节的数据.管道容量阿东小默认是65

编程题:用函数实现,用户输入年月日,来计算出该日期为当年第几天?

#include<stdio.h> /*函数is_leap_year()的返回值是判断该年是否闰年*/ int is_leap_year(int year) { int leap; if(year%4==0&&year%100!=0||year%400==0) leap=1; else leap=0; return leap; } /*函数len_of_month()的返回值为某年year的某月month的天数*/ int len_of_month(int year,int m

编程题:指向函数的指针,求两个数中较大的数。

#include<stdio.h> int max(x,y) { int z; if(x>y)  z=x; else  z=y; return z; } void main() { int a,b,c; int (*p)(); p=max; scanf("%d,%d",&a,&b); c=(*p)(a,b); printf("%d,%d,max is %d\n",a,b,c); } 编程题:指向函数的指针,求两个数中较大的数.,布布

socket编程之三:socket网络编程中的常用函数

这节本来打算先给出常用函数介绍,再给两个代码实例,写着写着发现越来越长,决定把代码放在下一节. 本节内容持续更新...... 1 socket()函数 原型: int socket(int domain, int type, int protocol); 描述: 类似打开一个文件,返回一个socket描述符,唯一标识一个socket,后面相应的操作都是这用这个socket描述符. 参数: domain:协议族,常用的协议族有AF_INET.AF_INET6.AF_LOCAL.AF_ROUTE等:

linux编程中接收主函数返回值以及错误码提示

程序A创建子进程,并调用进程B,根据不调用的不同情况,最后显示结果不同. #include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <errno.h> int main() { pid_t pid, rpid; int stat; if ((pid = fork()) < 0) { perror("for