zoukankan      html  css  js  c++  java
  • 【C++】C++中的分离式编译

    在C++中随着程序越来越复杂,我们希望把程序的各个部分分别储存在不同的文件中。C++支持的分离式编译(separate compilation)允许我们把程序分割到几个文件中去,每个文件独立编译。

    头文件以.h为后缀,主要包含类和函数的声明;实现文件以.cpp为后缀。可以这样理解,头文件中包含就是一些接口声明,而实现文件就是对这些接口进行定义。
    例如:

    文件:Num.h

    class Num{
        private:
            int num;
        public:
            Num();
            Num(int);
            int getNum();
    };

    文件:Num.cpp

    #include "Num.h"
    
    Num::Num() : num(0){}
    Num::Num(int n) : num(n){}
    int Num::getNum(){
    return num;
    }

    文件:NumTest.cpp

    #include <iostream>
    #include "Num.h"
    
    using namespace std;
    
    int main(int argc,char **argv){
        Num n(20);
        cout << n.getNum() << endl;
    return 0;
    }

    然后使用如下命令进行编译:

    g++ NumTest.cpp Num.cpp -o NumTest

    注意:必须在任何使用Num类的地方添加上 #include "Num.h" ,上面的案例中 NumTest.cpp 和 Num.cpp 文件都使用到了Num类,应此都必须在文件顶部引入 Num.h 文件。

    #ifndef

    有时我们可能会多次包含同一个文件,在C++中出现重复声明是不允许的。在上面的案例中 Num.cpp 和 NumTest.cpp 文件都包含了Num.h文件,但Num.cpp和NumTest.cpp是分开编译的,所以不会出现重复定义。
    为了演示该错误,我们重新定义一个文件。

    文件:Foo.h

    #include "Num.h"
    
    class Foo{
     public:
      Num n;
    };

    注意:这里没有Foo.cpp文件,因为Foo.h头文件没有什么需要被实现的。

    接下来进行测试

    文件:NumFooTest.cpp

    #include <iostream>
    #include "Num.h"
    #include "Foo.h"
    
    using namespace std;
    
    int main(int argc,char **argv){
        Num n(20);
        cout << n.getNum() << endl;
    
        Foo f;
        cout << f.n.getNum() << endl;
    return 0;
    }

    当笔者试图使用 g++ NumFooTest.cpp Num.cpp -o NumFooTest 命令进行编译时,出现如下的错误信息:

    In file included from Foo.h:1:0,
     from NumFooTest.cpp:3:
    Num.h:1:7: error: redefinition of ‘class Num’
    In file included from NumFooTest.cpp:2:0:
    Num.h:1:7: error: previous definition of ‘class Num’
    main.cpp: In function ‘int main()’:
    main.cpp:13:13: error: ‘class Foo’ has no member named ‘num’ 

    从错误信息中可以看出错误原因是Num类重复定义了,为了解决这种问题可以使用#ifndef,#ifndef的功能可描述为如下:“如果宏语句未定义语句1则执行程序2,否则执行程序3”。
    例如:

    #ifndef NUM_H
    #define NUM_H
    <define class or whatever else>
    #endif 

    当首次编译时 NUM_H 未被定义,所以 #ifndef NUM_H 条件为真,然后会执行 #define NUM_H 定义 NUM_H 并且执行我们定义的其他操作。若其后再次编译到该文件,由于 NUM_H 已经被定义了,所以 #ifndef NUM_H 条件为假,也就不会执行 #ifndef 到 #endif 间的任何代码。在知道了 #ifndef 的原理后,我们知道只需要将 #ifndef 语句应用到上面的 Num.h 文件,则会解决重复定义的问题。

    文件:Num.h

    #ifndef NUM_H
    #define NUM_H
    class Num{
        private:
            int num;
        public:
            Num();
            Num(int);
            int getNum();
    };
    #endif

    #pragma once

    除了是使用#ifndef语句避免重复定义,还可以将#pragma once添加到文件的开头,也可以完成同样的功能。Visual Studio默认使用的就是#pragma once。

    文件:Num.h

    #pragma once
    class Num{
        private:
            int num;
        public:
            Num();
            Num(int);
            int getNum();
    };

    分离编译

    完成分离式编译的步骤:
    1.将.cpp文件编译为对象文件,该对象文件包含.cpp文件的机器码。
    2.将对象文件链接到可执行文件。

    将.cpp编译为对象文件,可以使用g++加上命令行选项 -c

    g++ -c NumFooTest.cpp Num.cpp

    这行命令会产生 NumFooTest.o 和 Num.o 对象文件。

    然后将它们链接为可执行文件,我们再次使用g++命令:

    g++ NumFooTest.o Num.o -o NumFooTest

    这样就会产生可以执行文件NumFooTest。

    如果改变了NumFooTest.cpp文件,我们只需要重新编译NumFooTest.cpp文件就行了

    g++ -c NumFooTest.cpp

    得到NumFooTest.o对象文件。

    然后链接为可执行文件:

    g++ NumFooTest.o Num.o -o NumFooTest

    只编译变动过的文件可以为我们节约编译时间,大多数的IDE会自动帮助我们完成这一部分功能。

    make命令

    在一个很大的项目中很难去手动编译很多文件。如果你使用IDE的话,这些命令可以由IDE代劳。但如果未使用IDE的话,那么必须手动去完成。在大型项目中,使用 g++ 手动链接成百的文件,这是不可能的。
    这时可以使用make命令,首先创建一个文件Makefile,然后在其中添加我们编译需要依赖的信息。

    文件:Makefile

    CFLAGS = -O
    CC = g++
    NumTest: NumTest.o Num.o
        $(CC) $(CFLAGS) -o NumTest NumTest.o Num.o
    NumTest.o: NumTest.cpp
        $(CC) $(CFLAGS) -c NumTest.cpp
    Num.o: Num.cpp
        $(CC) $(CFLAGS) -c Num.cpp
    clean:
        rm -f core *.o

    然后输入 make 命令,Linux就会自动去解析 Makefile 文件。

    关于Makefile文件需要注意两点:

    第一,Makefile 文件的名称是固定的(不能改变,没有后缀);

    第二,缩进是Tab不是space。

    接下来解析一下上面的命令:

    NumTest: NumTest.o Num.o 

    表示 NumTest 由 NumTest.o 和 Num.o 生成。

    $(CC) $(CFLAGS) -o NumTest NumTest.o Num.o

    其中 $(CC) 会被替换为 g++ , $(CFLAGS) 会被替换为 -0 ,替换后的命令会生成 NumTest 文件。文件中的其他命令也是类似的道理,最后的clean表示清理文件, rm -f core *.o 会删除所有以o结尾的文件(也就是生成 NumTest 的所有中间文件——对象文件)。

    上面的文件内容依然比较复杂,如果换成如下的命令,除了可以使用上面那种格式,还可以在Makefile中使用下面这种格式:

    CFLAGS = -O
    CC = g++
    SRC = NumTest.cpp Num.cpp
    OBJ = $(SRC:.cpp = .o)
    NumTest: $(OBJ)
      $(CC) $(CFLAGS) -o NumTest $(OBJ)
    clean:  
      rm
    -f core *.o


    参考文章:C++ Separate Header and Implementation Files

  • 相关阅读:
    图像的分离合并
    图像旋转与格式转换
    图像的剪切和粘贴
    缩放图像
    遮罩混合
    透明度混合
    Anaconda安装jieba、snownlp等外部包
    anaconda3 中pip安装模块方法
    PHP读取文本文件内容并随机输出任意一行
    php读取在线远程txt文档内容到数组并遍历
  • 原文地址:https://www.cnblogs.com/HDK2016/p/10591690.html
Copyright © 2011-2022 走看看