2

C++ 中的常见做法是将.h(or .hpp) 中的声明和实现分离到.cpp.

我知道两个主要原因(也许还有其他原因):

  1. 编译速度(当您只更改一个文件时,您不必重新编译所有内容,您可以make从预编译.o文件中链接它)
  2. 前向声明有时是必要的(当实现class A取决于class B和实现class Bon 时class A )......但我没有经常遇到这个问题,通常我可以解决它。

在面向对象编程的情况下,它看起来像这样:

QuadraticFunction.h

class QuadraticFunc{
    public:
    double a,b,c;
    double eval ( double x );
    double solve( double y, double &x1, double &x2 );
};

QuadraticFunction.cpp

#include <math.h>
#include "QuadraticFunc.h"

double QuadraticFunc::eval ( double x ){ return c + x * (b + x * a ); };

double QuadraticFunc::solve( double y, double &x1, double &x2 ){ 
    double c_ = c - y;
    double D2 = b * b - 4 * a * c_;
    if( D2 > 0 ){
        double D = sqrt( D2 );
        double frac = 0.5/a;
        x1 = (-b-D)*frac;
        x2 = (-b+D)*frac;
    }else{  x1 = NAN; x2 = NAN; }
};

main.cpp

#include <math.h>
#include <stdio.h>

#include "QuadraticFunc.h"

QuadraticFunc * myFunc;

int main( int argc, char* args[] ){

    myFunc = new QuadraticFunc();
    myFunc->a = 1.0d; myFunc->b = -1.0d; myFunc->c = -1.0d;

    double x1,x2;
    myFunc->solve( 10.0d, x1, x2 );
    printf( "soulution %20.10f %20.10f \n", x1, x2 );

    double y1,y2;
    y1 = myFunc->eval( x1 ); 
    y2 = myFunc->eval( x2 );
    printf( "check     %20.10f %20.10f \n", y1, y2 );

    delete myFunc;
}

然后像这样编译它makefile

FLAGS  = -std=c++11 -Og -g -w
SRCS   = QuadraticFunc.cpp main.cpp
OBJS   = $(subst .cpp,.o,$(SRCS))

all: $(OBJS)
    g++ $(OBJS) $(LFLAGS) -o program.x

main.o: main.cpp QuadraticFunc.h
    g++ $(LFLAGS) -c main.cpp

QuadraticFunc.o: QuadraticFunc.cpp QuadraticFunc.h
    g++ $(LFLAGS) -c QuadraticFunc.cpp

clean:
    rm -f *.o *.x

但是,我发现它经常很不方便

尤其是当您大量更改代码时(例如,在开发的初始阶段,当您还不确定整个项目的整体结构时)。

  1. .cpp在对类结构进行重大更改时,您必须始终在.h部分代码之间来回切换。
  2. 您在编辑器和项目文件夹中有两倍的文件,这令人困惑。
  3. 你必须写一些信息(比如函数头或QuadraticFunc::)两次,在那里你可以做很多错别字和不一致,所以编译器总是抱怨(我经常犯这样的错误)
  4. 每次添加/删除/重命名某个类时,您都必须编辑Makefile,在这些错误中,您会犯很多其他难以从编译器输出中跟踪的错误(例如,我经常忘记编写 Makefile 以便代码重新编译我的每个依赖项编辑 )

从这个角度来看,我更喜欢 Java 的工作方式。出于这个原因,我只是通过将所有代码(包括实现)放入.h. 像这样:

#include <math.h>
class QuadraticFunc{
    public:
    double a,b,c;
    double eval ( double x ){ return c + x * (b + x * a ); }
    double solve( double y, double &x1, double &x2 ){ 
        double c_ = c - y;
        double D2 = b * b - 4 * a * c_;
        if( D2 > 0 ){
            double D = sqrt( D2 );
            double frac = 0.5/a;
            x1 = (-b-D)*frac;
            x2 = (-b+D)*frac;
        }else{  x1 = NAN; x2 = NAN; }
    };
};

使用通用默认生成文件,如下所示:

FLAGS  = -std=c++11 -Og -g -w

all : $(OBJS)
    g++ main.cpp $(LFLAGS) -w -o program.x

main.cpp保持不变)

然而,现在当我开始编写更复杂的程序时,编译时间开始变得相当长,因为我必须一直重新编译所有内容。

有什么方法可以利用make(更快的编译时间)的优势,并且仍然以类似 Java 的方式组织程序结构(所有内容都在类主体中,而不是单独的.h.cpp),我觉得这更方便?

4

4 回答 4

5

但是,现在当我开始编写更复杂的程序时,编译时间开始变得相当长,因为我必须一直重新编译所有内容。

分离头文件和类文件的最佳点之一是您不必编译所有内容。

当您有 class1.h、class1.cpp、class2.h、class2.cpp、...、classN.h 和 classN.cpp 时,这些头文件仅包含在每个类的编译对象中。因此,如果您的函数在 class2 中的逻辑发生变化但您的标头没有,您只需将 class2 编译为目标文件。然后,您将对生成实际可执行文件的所有目标文件进行链接。链接速度很快。

如果您正在构建大型而复杂的程序并发现编辑标题是问题,请考虑在编写之前设计您的应用程序。

于 2015-12-28T08:41:35.880 回答
1

简短的回答:没有。

长答案:仍然没有。

要么将所有代码放在头文件中,要么使用两个文件,其中包含头文件并自行编译源文件。

我个人对使用两个文件没有问题。大多数编辑器都支持“两个文件视图”——其中大多数还支持“跳转到定义”。

将所有函数放在类声明中还有另一个副作用,那就是所有函数都标记为内联,如果多个源文件中包含相同的头文件,这可能导致函数在输出二进制文件中多次生成。

虽然根据我的经验,编译时间不是解析的结果,而是编译的代码生成部分——通常是 .cpp 文件——所以如果你包含几个大的头文件很可能不会那么重要.

当然,使用make[或类似的] 和正确定义的依赖项来构建您的项目。

于 2015-12-28T08:40:40.263 回答
1

C++ 是 C++,Java 是 Java。将源代码拆分为 .h- 和 .cpp 文件是 C++ 语言概念的一部分。如果你不喜欢它,你不应该使用它。

将所有内容放在一个头文件中实际上与包含 .cpp 文件相同(这有效,但非常不合适)。在以下情况下您不应该这样做:

  • 编写标准的类,函数,...
  • 在较大的程序中多次使用代码部分(否则,当您在 main.cpp 中包含所有内容时会导致重新定义错误)
  • 您想将程序的一部分外包到静态/动态库中。几乎每个可用的库都以这种方式工作。

示例: WindowsAPI (COM!!)、SFML、Boost(部分)(以及更多

您可以在以下情况下执行此操作:

  • 该代码做了非常简单的事情,例如位移操作(制作颜色代码),字符串分析,......

示例:提升(部分)

在以下情况下您必须这样做:

  • 在编译运行时创建模板类或函数。这是 .h/.ccp 概念的主要和讨论最多的缺点之一,因此您不是第一个对此感到疑惑的人。

示例: STL(C++ 标准模板库)

于 2015-12-28T09:13:50.700 回答
0

首先,由于可读性,您必须在单独的文件中编写实现和定义。可以将它们放入同一个文件中,但不可读。编写代码时,请为下一个需要理解您的代码的贡献者编写代码。这很重要,因为下一个可能是你 :) 第二个问题是 makefile 。Make是为了使编译过程更容易而不是更快。因此,如果您对任何文件进行更改,则无需更改 ant make 文件。感谢make,您不需要一次又一次地按顺序编译文件。一次编写文件,每次使用。但是,如果您添加了影响编译过程的新代码文件,是的,您必须在 makefile 中进行更改。您可以了解有关可读性的更多信息,

于 2015-12-28T09:02:44.180 回答