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
-End-