Answers:
好吧,有个好老的使用来源,卢克!--- R本身具有很多可以学习的(非常有效的)C代码,并且CRAN有数百个程序包,其中一些来自您信任的作者。这提供了经过测试的真实示例,可以进行研究和调整。
但是正如乔什(Josh)所怀疑的那样,我更倾向于C ++以及Rcpp。它还有很多示例。
编辑:我发现有两本书很有帮助:
也就是说,John越来越喜欢Rcpp(并做出了贡献),因为他发现R对象和C ++对象(通过Rcpp)之间的匹配非常自然-引用类对此提供了帮助。
编辑2:关于 Hadley重新关注的问题,我强烈建议您考虑使用C ++。与C有关的样板废话太多了-非常繁琐且非常可避免。看看Rcpp简介小插图。另一个简单的示例是此博客文章,我在这里展示了,不用担心10%的差异(在Radford Neal的示例中),我们可以使用C ++ 获得八十倍的增长(当然是人为的示例)。
编辑3:复杂性在于,您可能会遇到C ++错误,总之,很难理解。但是,仅使用Rcpp而不是对其进行扩展,就几乎不需要。尽管这笔费用是不可否认的,但它的优点却因为简单的代码,更少的样板,没有PROTECT / UNPROTECT,没有内存管理等优点而黯然失色。DougBates昨天表示,他发现C ++和Rcpp更像是编写R而不是编写C ++。YMMV等。
ggplot
吗?
哈德利
您绝对可以编写类似于C代码的C ++代码。
我理解您所说的C ++比C更复杂。这是如果您想掌握所有东西:对象,模板,STL,模板元编程等……大多数人不需要这些东西,而可以依靠其他东西对此。Rcpp的实现非常复杂,但是仅仅是因为您不知道冰箱的工作原理,并不意味着您就无法打开门来抢鲜牛奶...
从您对R的众多贡献中,让我印象深刻的是,您发现R有点乏味(数据操作,图形,字符串操作等)。使用R的内部C API为更多的惊喜做好准备。这非常繁琐。
我不时阅读R-exts或R-ints手册。这会有所帮助。但是大多数时候,当我真的想了解一些东西时,我会去R源,也去看Simon等编写的软件包的源(那里通常有很多东西要学习)。
Rcpp旨在消除API的这些繁琐方面。
您可以根据一些示例,自行判断发现哪些内容比较复杂,模糊不清等。此函数使用C API创建一个字符向量:
SEXP foobar(){
SEXP ab;
PROTECT(ab = allocVector(STRSXP, 2));
SET_STRING_ELT( ab, 0, mkChar("foo") );
SET_STRING_ELT( ab, 1, mkChar("bar") );
UNPROTECT(1);
}
使用Rcpp,您可以编写与以下命令相同的函数:
SEXP foobar(){
return Rcpp::CharacterVector::create( "foo", "bar" ) ;
}
要么:
SEXP foobar(){
Rcpp::CharacterVector res(2) ;
res[0] = "foo" ;
res[1] = "bar" ;
return res ;
}
正如Dirk所说,在几个小插曲上还有其他示例。我们通常也将人们引向我们的单元测试,因为他们每个人都测试代码的非常具体的部分,并且有些自我解释。
我在这里显然有偏见,但是我建议您熟悉Rcpp而不是学习R的C API,如果不清楚或似乎无法使用Rcpp,然后进入邮件列表。
无论如何,结束销售。
我想这取决于您最终要编写哪种代码。
罗曼
@hadley:不幸的是,我没有特定的资源来帮助您开始使用C ++。我是从Scott Meyers的书中摘录的(有效的C ++,更有效的C ++等),但是这些并不是真正可以称为入门的书。
我们几乎只使用.Call接口来调用C ++代码。规则很容易:
因此,.Call函数在某些头文件中这样声明:
#include <Rcpp.h>
RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;
并在.cpp文件中这样实现:
SEXP foo( SEXP x1, SEXP x2 ){
...
}
对于要使用Rcpp的R API并没有更多的了解。
大多数人只想处理Rcpp中的数字向量。您可以使用NumericVector类来执行此操作。有几种创建数字矢量的方法:
从您从R传递过来的现有对象中:
SEXP foo( SEXP x_) {
Rcpp::NumericVector x( x_ ) ;
...
}
使用:: create静态函数给定值:
Rcpp::NumericVector x = Rcpp::NumericVector::create( 1.0, 2.0, 3.0 ) ;
Rcpp::NumericVector x = Rcpp::NumericVector::create(
_["a"] = 1.0,
_["b"] = 2.0,
_["c"] = 3
) ;
给定大小的:
Rcpp::NumericVector x( 10 ) ; // filled with 0.0
Rcpp::NumericVector x( 10, 2.0 ) ; // filled with 2.0
然后,一旦有了向量,最有用的就是从中提取一个元素。这是通过operator []和基于0的索引完成的,因此例如对数字矢量的值求和的过程如下:
SEXP sum( SEXP x_ ){
Rcpp::NumericVector x(x_) ;
double res = 0.0 ;
for( int i=0; i<x.size(), i++){
res += x[i] ;
}
return Rcpp::wrap( res ) ;
}
但是,有了Rcpp糖,我们现在可以做得更好:
using namespace Rcpp ;
SEXP sum( SEXP x_ ){
NumericVector x(x_) ;
double res = sum( x ) ;
return wrap( res ) ;
}
如前所述,这完全取决于您要编写哪种代码。查看人们在依赖Rcpp的程序包中做什么,检查小插图,进行单元测试,然后在邮件列表中返回给我们。我们总是很乐意提供帮助。
@jbremnant:是的。Rcpp类实现了一些类似于RAII模式的东西。创建Rcpp对象时,构造函数将采取适当的措施以确保基础R对象(SEXP)受垃圾回收器保护。析构函数撤消保护。在Rcpp插入插图中对此进行了说明。基础实现依赖于R API函数R_PreserveObject和R_ReleaseObject
由于C ++封装,确实确实会降低性能。我们尝试通过内联等将其保持在最低限度。代价很小,当考虑到编写和维护代码所花费的时间方面的收益时,它就不那么重要了。
从Rcpp类调用R函数比通过C api直接调用eval慢。这是因为我们采取了预防措施,并将函数调用包装到tryCatch块中,以便捕获R错误并将其提升为C ++异常,以便可以使用C ++中的标准try / catch处理它们。
大多数人都想使用向量(特别是NumericVector),而此类的惩罚很小。examples / ConvolveBenchmarks目录包含R-exts臭名昭著的卷积函数的几种变体,并且该小插图具有基准结果。事实证明,Rcpp使其比使用R API的基准代码更快。