优秀的编程知识分享平台

网站首页 > 技术文章 正文

C/C++声明、定义,分别编译、链接的语法机制

nanyue 2024-08-16 20:00:10 技术文章 9 ℃

C/C++作为强类型语言,其重要特征就是通过类型检查特别是编译期检查确保其类型安全(最早期的语言和最底层其实是不区分类型的)。

变量和函数都要区分类型(函数通过返回值区分,同时函数的参数也需区分类型)。

C/C++考量到效率和单元测试的需要,使用“分别编译”(separate compilation)和链接的语法机制。

C++ 语言支持“分别编译”(separate compilation)。也就是说,一个程序所有的内容,可以分成不同的部分分别放在不同的 .cpp 文件里。.cpp 文件里的东西都是相对独立的,在编译(compile)时不需要与其他文件互通,只需要在编译成目标文件后再与其他的目标文件做一次链接(link)就行了。比如,在文件 a.cpp 中定义了一个全局函数 "void a(){}",而在文件 b.cpp 中需要调用这个函数。即使这样,文件 a.cpp 和文件 b.cpp 并不需要相互知道对方的存在(b.cpp只需声明一下voida();),而是可以分别地对它们进行编译,编译成目标文件之后再链接,整个程序就可以运行了。

在编译阶段,变量和函数的使用都要有类型和标识符的提前声明,以确保一致性,如:

extern int g_i;  
int add(int a, int b);
// 除了类型和标识符的引入,也表明该标识符在文件的别外或工程的其它文件中有定义

在链接阶段,变量和函数的使用都要有类型和标识符的提前定义,以确保一致性,如:

int g_i;
int add(int a, int b)
{
    return a+b;
}

在编译阶段,编译器可以检查出语法错误(Syntax errors)和类型错误(Type errors)。

在链接阶段,链接器可以检查出链接错误(也就是找不定声明的定义或声明与定义不匹配的情况)。以及运行期错误(runtime errors)和逻辑错误(logic errors),通常程序的debugging时间和testing时间大大超过coding时间。

定义如同声明一样,包括了一个类型与一个标识符的引入,定义相对于声明来说,多了一个动作,对于变量来说,定义会确保内存的分配,对于函数来说,定义要求包含函数体,同样的,在内存的代码区会对应一段内存空间。所以说定义也是声明,而声明仅仅是声明,未涉及到内存的分配。

通常将声明写在一个.cpp文件的头部或一个单独的.h文件中,再include进.cpp文件的头部。

而变量(全局变量)和函数的定义则可以写在同一工程的其它.cpp文件中。

简单地说,"定义"就是把一个符号完完整整地描述出来:它是变量还是函数,返回什么类型,需要什么参数等等。而"声明"则只是声明这个符号的存在,即告诉编译器,这个符号是在其他文件中定义的,我这里先用着,你链接的时候再到别的地方去找找看它到底是什么吧。定义的时候要按 C++ 语法完整地定义一个符号(变量或者函数),而声明的时候就只需要写出这个符号的原型了。需要注意的是,一个符号,在整个程序中可以被声明多次,但却要且仅要被定义一次。试想,如果一个符号出现了两种不同的定义,编译器该听谁的?这种机制给 C++ 程序员们带来了很多好处,同时也引出了一种编写程序的方法。考虑一下,如果有一个很常用的函数 "void f() {}",在整个程序中的许多 .cpp 文件中都会被调用,那么,我们就只需要在一个文件中定义这个函数,而在其他的文件中声明这个函数就可以了。

来看Bjarne Stoustrup对一些相关术语的定义:

bit the basic unit of information in a computer. A bit can have the value 0 or the value 1.

byte the basic unit of addressing in most computers. Typically, a byte holds 8 bits.

word a basic unit of memory in computer, usually the unit used to hold an integer or a pointer. On many machines, an object must be aligned on a word boundary for acceptable performance.

object (1) an initialized region of memory of a known type which holds a value of that type; (2) a region of memory.

function a named unit of code that can be invoked (called) from different parts of a program; a logical unit of computation.

value a set of bits in memory interpreted according to a type.

declaration the specification of a name with its type in a program.

definition a declaration of an entity that supplies all information necessary to complete a program using the entity. Simplified definition: a declaration that allocates memory.

type something that defines a set of possible values and a set of operations for an object.

name equence of letters and digits started by a letter, used to identify ("name") user-defined entities in program text. An underscore is considered a letter. Names are case sensitive. The standard imposes no upper limit on the length of names.

operation something that can perform some action, such as a function and an operator.

parameter a declaration of an explicit input to a function or a template. When called, a function can access the arguments passed through the names of its parameters.

argument a value passed to a function or a template, in which it is accessed through a parameter.

expression combination of operators and names producing a value .

const a attribute of a declarations that makes the entity to which it refers readonly.

header a file containing declarations used to share interfaces between parts of a program.

interface a declaration or a set of declarations specifying how a piece of code (such as a function or a class) can be called.

声明通常都写在头文件中,对于库文件来说,头文件是库设计者和使用者的接口。

.h与.cpp如何include?

一个原则就是,需要某标识符时,就包含有这一标识符的声明(不管是库还是自己写的.h,但是要可能避免在.h文件中include)。

// main.cpp

int main() // 操作系统调用main()
{
    
    while(1);
    return 0;
}

需要使用std::cout、std::endl时,就需要包含其所在头文件(一个声明集):

// main.cpp
#include <iostream>

int main() // 操作系统调用main()
{
    std::cout<<"hello!"<<std::endl;
    while(1);
    return 0;
}

再增加一个add()函数:

// main.cpp
#include <iostream>
int add(int a,int b) // 定义也是声明
{
    return a+b;
}
int main()
{
    std::cout<<add(3,4)<<std::endl;
    while(1);
    return 0;
}

分别写到不同的文件中:

// main.cpp
#include <iostream>
#include "head.h"

int main()
{
    std::cout<<add(3,4)<<std::endl;
    while(1);
    return 0;
}
// head.h
#ifndef __HEAD_H__
#define __HEAD_H__
int add(int a,int b);
#endif
// implementation.cpp
int add(int a,int b)
{
    return a+b;
}

再增加一个函数:

// main.cpp
#include <iostream>
#include "head.h"
using namespace std; // 也可省略,因为在head.h文件中有写

int main()
{
    std::cout<<add(3,4)<<std::endl;
    string dst = "abcdefghidef";
    replaceAll(dst,"def","123");
    std::cout<<dst<<std::endl;
    while(1);
    return 0;
}
// head.h
#ifndef __HEAD_H__
#define __HEAD_H__

int add(int a,int b);

#include <string>
using namespace std;

string& replaceAll(string& context,const string& from,const string& to);

#endif
// implementation.cpp
//#include <iostream>
//using namespace std;
#include "head.h"

int add(int a,int b)
{
    return a+b;
}

string& replaceAll(string& context,const string& from,const string& to) 
{
    size_t lookHere = 0;
    size_t foundHere;
    while((foundHere = context.find(from, lookHere))   != string::npos) 
    {
        context.replace(foundHere, from.size(), to);
        lookHere = foundHere + to.size();
    }
    return context;
}

如果在implementation.cpp中include头文件head.h是一种更便捷的写法,因为在head.h中的声明和已有的包含,很大可能也是implementation.cpp所需要的,特别是关系到函数的嵌套调用时。如果不包含head.h,就需要在implementation.cpp中单独声明所需要的标识符。至于main.cpp如何找到函数的定义,是因为在链接阶段需要将全部工程中的文件链接到一起的,linker.exe会寻找声明的定义,如果找不到,会报告一个链接错误。

ref

C/C++|头文件、源文件分开写的源起及作用

C++|头文件使用源由及可以包含什么、不能包含什么

-End-

Tags:

最近发表
标签列表