Python程序员的C/C++入门指南

10
我需要从Python转换到C/C++。你知道有没有快速的“参考教程”或类似于Numpy和Scipy教程的东西可以作为起点参考吗?我已经阅读了很多“文档”,例如:
  • C++ for dummies
  • the K&R C Programming Language
  • 许多博客和在线文档,如:http://eli.thegreenplace.net/2010/01/11/pointers-to-arrays-in-c/,
  • http://newdata.box.sk/bx/c/
  • 大量的StackOverflow问答
  • ...
但是对我来说仍然不清楚如何开始将像这样的东西移植到C/C++:
#!/usr/bin/env python

import time
import numpy as np
import tables as tb

"""Retrieve 3D positions form 1000 files and store them in one single HDF5 file.
"""

t = time.time()

# Empty array
sample = np.array([])
sample.shape = (0,3)

# Loop over the files
for i in range(0, 1000):
  filename = "mill2sort-"+str(i)+"-extracted.h5"
  print "Doing ", filename
  # Open data file
  h5f = tb.openFile(filename, 'r')
  # Stack new data under previous data
  sample = np.vstack((sample, h5f.root.data.read()))
  h5f.close()

# Create the new file
h5 = tb.openFile("mill2sort-extracted-all", 'w')
# Save the array
h5.createArray(h5.root, 'data', sample, title='mill_2_sub_sample_all')
h5.flush()
h5.close()

print "Done in ", time.time()-t, " seconds."

在C或C++中,例如我甚至不知道如何将一个3D数组传递给一个函数以找到它的维度,类似于

int getArrayDimensions(int* array, int *dimensions){
  *dimensions = sizeof(*array)/sizeof(array[0]);
  return 0;
}

使用数组时,

int array[3][3][3] = ...

感谢您提供的任何建议!:)

3
选择一种:C语言或C++。"C/C++"并不存在。我认为,相较于C语言而言,C++更容易学习。 - user1203803
3
我怀疑这一点。一旦你非常熟悉它,C++是非常高效的编程语言,但它也是最难学习的之一。 - enobayram
@daknok_t:我还没有决定是用C还是C++,所以我写了“C/C++”!但哪一个更适合我的需求是另一个问题! - brunetto
1
我刚刚将一个项目从Python移植到了C,大约有10k行代码 - 花了我4个月的时间,但我仍然不清楚如何开始做它,这真是太可怕了。也许可以用一种完全不可行的方式来重新构思问题,以使其不再想要这样做? - bph
在进行端口之前,请确保您绝对确定需要进行端口。从高级语言到低级语言的移植并不是很愉快,我个人认为。请调查其他选项,如SWIG、ctypes等。 - bph
显示剩余2条评论
3个回答

9

好的,针对这个特定的例子:

  • 您可以从标准库这里获取时间服务
  • 您可以使用eigen进行线性代数计算。它是一个很棒的库,我非常喜欢它。
  • 请查看这里以了解如何操作文件

在使用C++时,您可能会错过一些Python的功能,但实际上大部分这些功能都是由Boost库提供的。例如,使用boost.tuple库从函数返回多个值非常容易,就像这里所示。如果您不想自己处理内存管理,可以使用boost::shared_ptr。或者,如果您想继续使用Python来操作C++类,则可以使用boost.pythonBoost.parameter可帮助您定义具有命名参数的函数。还有Boost.lambda用于Lambda函数,但如果您的环境支持它,也可以使用C++11来支持Lambda函数。 Boost是一个黄金矿山,永远不要停止挖掘。只需假设它是标准库的一部分。我在许多不同的平台上开发C ++,Eigen和Boost都没有让我失望过。

这里有一份关于 C++ 最佳实践的好 FAQ。这个是一个非常重要的原则,在使用 C++ 时必须时刻记在心中。我会稍微扩展一下,认为如果你要做一些危险的事情,比如使用原始的 new 分配内存,或者索引一个原始的 C 风格数组,传递裸指针,或者进行 static_cast(更糟糕的是 reinterpret_cast)等操作,它们通常应该在专门用于它们的类中发生,并且确保它们不会造成问题的代码应该与它们非常接近,这样你可以一眼看到一切都在掌控之中。

最后,我的最爱!你想继续在 C++ 中使用生成器吗?这里有一些黑魔法。


我支持这个观点。使用C++(或者更好的C++11),而不是C语言,因为它更接近Python。使用Boost来完成你在Python中习惯的所有操作,比如lambda函数。永远不要使用裸指针 - 使用std::string,std::vector和shared_ptr。 - Gurgeh
但是好的程序员会尽量避免难以调试的代码。代码本身让我怀疑作者是否真的是一个好的程序员:使用公共成员存储私有状态?明确地将已经默认为公共的派生类设置为公共? - Sebastian Mach
我知道它看起来非常可怕,一开始我也持怀疑态度,但它真的非常强大,并且还能产生非常高效的代码。不要将生成器视为常规类,并在心中以此评估它。真正难以调试的是你编写的代码,以绕过C++中缺少生成器的限制。使用这种方法,我最终编写了5-10行生成器代码,这取代了原本需要几十行的代码。你认为哪一个更容易出现错误? - enobayram
你必须亲眼看到它与调试器的完美配合,才能相信。调试器的行为就像所有这些伪关键字都是语言特性一样。至于生成器代码是否为玩具,我已经用它来实现许多复杂的算法。我认为我们不需要争论生成器通常有多有用。你只需要使用它们,就会知道你需要它们的程度。就像编程中的任何其他东西一样。 - enobayram
有一个容易出错的地方,我认为只有这一个。如果在生成器函数体中声明局部变量,你会得到令人惊讶的结果。但我认为在编写10-20-30行代码时,你可以记住这一点。这对于C++来说并不新鲜。 - enobayram
显示剩余47条评论

5

好的,现在先从C语言开始。

void readH5Data(FILE *file, int ***sample);   // this is for you to implement
void writeH5Data(FILE *file, int ***sample);  // this is for you to implement

int main(int argc, const char *argv[])
{
#define width 3
#define height 3
#define depth 3

    time_t t = time(NULL);

    int ***sample = calloc(width, sizeof(*sample));

    for (int i = 0; i < width; i++)
    {
        sample[i] = calloc(height, sizeof(**sample));
        for (int j = 0; j < height; j++)
        {
            sample[i][j] = calloc(depth, sizeof(***sample));
        }
    }

    for (int i = 0; i < 1000; i++)
    {
        char *filename[64];
        sprintf(filename, "mill2sort-%i-extracted.h5", i);

        // open the file
        FILE *filePtr = fopen(filename, "r");

        if (filePtr == NULL || ferror(filePtr))
        {
            fprintf(stderr, "%s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
        readH5Data(filePtr, sample);

        fclose(filePtr);
    }

    char filename[] = "mill2sort-extracted-all";

    FILE *writeFile = fopen(filename, "w");

    if (writeFile == NULL || ferror(writeFile))
    {
        fprintf(stderr, "%s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    writeH5Data(writeFile, sample);

    fflush(writeFile);
    fclose(writeFile);

    printf("Done in %lli seconds\n", (long long int) (time(NULL) - t));

    for (int i = 0; i < width; i++)
    {
        for (int j = 0; j < width; j++)
        {
             free(sample[i][j]);
        }

        free(sample[i]);
    }

    free(sample);
}

只要您记得您的数组是3x3x3,您就不会在“writeH5Data”方法中越界出现问题。

1
我想指出Richard调用了三次 calloc() : sample 是一个指针数组(外部 calloc),每个指针又指向另一组指针的数组 (中间 calloc),而每个指针数组又指向一个 int 型数组 (内部 calloc)。当我开始学习 C 语言时,我到处都是纸张,画着用来解决指针指向问题的内存块示意图。此外,如果你真的需要数组的大小,在大多数情况下,你将不得不在创建数组时自己跟踪它。通常,C 不知道数组何时“结束”,所以 sizeof() 无法使用。 - Sam Britt
2
谢谢您的回答,我会研究它,但我正在寻找一个“参考”,类似于https://www.cfa.harvard.edu/~jbattat/computer/python/science/idl-numpy.html。好吧,不是真正的“转换表”,而是像“在C/C++中,以这种方式管理数组,以这种方式传递它们,如果需要做<经常需要的某事>,则应该这样做”这样的东西! - brunetto
2
更明确地说,我正在寻找一个“数字参考”(但不是“数字配方”)。在阅读了大量理论之后,我仍然不知道如何开始变得有成效,我没有关于如何执行简单的,“标准的”,日常操作,例如操作多维数组并将它们传递给函数的参考。在C/C++编程世界中这么多年后,我认为应该有一个“标准”的共享知识,建议“要执行<每天的操作>,您应该这样做”,这样我就不必重新发明轮子,进行数组操作等等。我正在寻找像这样的东西! :) - brunetto

5

显然,这个问题已经被修复了。 - cedbeu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接