C++源文件与程序开发:从编译到实践
立即解锁
发布时间: 2025-08-22 00:47:45 阅读量: 2 订阅数: 16 


C++编程语言精髓与实践
### C++源文件与程序开发:从编译到实践
在C++编程领域,源文件和程序的组织与管理是构建高效、可维护代码的关键。本文将深入探讨C++源文件的编译、链接、头文件的使用以及程序的初始化和终止等重要概念,并通过具体示例和实用建议帮助你更好地理解和应用这些知识。
#### 1. 分离编译与链接
在传统的文件系统中,文件既是存储单元,也是编译单元。将一个完整的程序放在一个文件中通常是不现实的,特别是对于标准库和操作系统代码,它们一般不会以源代码形式作为用户程序的一部分。对于实际规模的应用程序,将所有代码放在一个文件中既不实际也不方便。
分离编译的过程如下:
- 用户将源文件提交给编译器。
- 源文件进行预处理,包括宏处理和`#include`指令引入头文件,预处理的结果称为翻译单元。
- 编译器对翻译单元进行编译。
- 链接器将各个翻译单元链接在一起形成可执行程序。
链接器的作用是检测并解决不同翻译单元之间的不一致性问题。链接可以在程序运行前完全完成,也可以在程序运行时动态链接新的代码。
程序的物理结构是指将程序组织成源文件的方式,它应该由程序的逻辑结构来指导。但逻辑结构和物理结构并不一定完全相同,例如可以使用多个源文件存储一个命名空间的函数,或者将一个命名空间的定义分散在多个文件中。
#### 2. 链接
在所有翻译单元中,函数、类、模板、变量、命名空间、枚举和枚举器的名称必须一致使用,除非它们被明确指定为局部的。程序员需要确保每个命名空间、类、函数等在每个使用它的翻译单元中都有正确的声明,并且所有引用同一实体的声明都是一致的。
以下是一些链接相关的概念和示例:
- **外部链接和内部链接**:可以在定义它的翻译单元之外使用的名称具有外部链接,只能在定义它的翻译单元中引用的名称具有内部链接。
- **`extern`关键字**:用于声明一个变量或函数,但不进行定义。例如:
```cpp
// file1.c:
int x = 1;
int f() { /* do something */ }
// file2.c:
extern int x;
int f();
void g() { x = f(); }
```
- **`inline`函数**:必须在每个使用它的翻译单元中具有相同的定义。例如:
```cpp
// file1.c:
inline int f(int i) { return i; }
// file2.c:
inline int f(int i) { return i + 1; } // 错误
```
- **`const`和`typedef`**:默认情况下,`const`和`typedef`具有内部链接。可以通过显式声明为`extern`来为`const`赋予外部链接。
- **未命名命名空间**:可以用于使名称在编译单元内局部化,其效果类似于内部链接。
#### 3. 头文件
头文件是实现不同翻译单元之间声明一致性的一种简单方法。通过`#include`指令将包含接口信息的头文件引入到包含可执行代码和/或数据定义的源文件中。
##### 3.1 头文件的内容
头文件可以包含以下内容:
- 命名命名空间
- 类型定义
- 模板声明和定义
- 函数声明
- 内联函数定义
- 数据声明
- 常量定义
- 枚举
- 名称声明
- `#include`指令
- 宏定义
- 条件编译指令
- 注释
头文件不应包含以下内容:
- 普通函数定义
- 数据定义
- 聚合定义
- 未命名命名空间
- 导出模板定义
##### 3.2 标准库头文件
标准库的功能通过一组标准头文件提供,标准库头文件不需要后缀,使用`#include <...>`语法引入。对于每个C标准库头文件`<X.h>`,都有一个对应的C++标准头文件`<cX>`。
##### 3.3 单一定义规则(ODR)
一个给定的类、枚举和模板等在程序中必须有且仅有一个定义。两个类、模板或内联函数的定义被认为是同一个唯一定义,当且仅当:
- 它们出现在不同的翻译单元中。
- 它们的标记完全相同。
- 这些标记在两个翻译单元中的含义相同。
以下是违反ODR的示例:
```cpp
// file1.c:
struct S1 { int a; char b; };
struct S1 { int a; char b; }; // 错误:重复定义
// file1.c:
struct S2 { int a; char b; };
// file2.c:
struct S2 { int a; char bb; }; // 错误:成员名称不同
// file1.c:
typedef int X;
struct S3 { X a; char b; };
// file2.c:
typedef char X;
struct S3 { X a; char b; }; // 错误:名称含义不同
```
#### 4. 与非C++代码的链接
C++程序通常包含用其他语言编写的部分,或者C++代码片段被用作其他语言程序的一部分。不同语言和不同编译器之间的合作可能会有困难,例如在参数使用的机器寄存器、栈帧布局、字符、整数、浮点数和字符串的布局、编译器传递给链接器的名称形式以及链接器所需的类型检查量等方面可能存在差异。
为了帮助解决这些问题,可以在`extern`声明中指定链接约定。例如:
```cpp
extern "C" char* strcpy(char*, const char*);
```
`extern "C"`指令指定了链接约定,但不影响函数调用的语义。可以使用链接块将一组声明指定为具有相同的链接约定,也可以使用条件编译来创建C和C++共享的头文件。
#### 5. 函数指针的链接
在混合C和C++代码时,有时需要将一种语言中定义的函数指针传递给另一种语言中定义的函数。如果两种语言的实现共享链接约定和函数调用机制,那么传递函数指针就很简单。但通常情况下,需要确保函数以其期望的方式被调用。
以下是一个函数指针链接的示例:
```cpp
typedef int (*FT)(const void*, const void*); // FT具有C++链接
extern "C" {
typedef int (*CFT)(const void*, const void*); // CFT具有C链接
void qsort(void* p, size_t n, size_t sz, CFT cmp); // cmp具有C链接
}
void isort(void* p, size_t n, size_t sz, FT cmp); // cmp具有C++链接
void xsort(void* p, size_t n, size_t sz, CFT cmp); // cmp具有C链接
extern "C" void ysort(void* p, size_t n, size_t sz, FT cmp); // cmp具有C++链接
int compare(const void*, const void*); // compare()具有C++链接
extern "C" int ccmp(const void*, const void*); // ccmp()具有C链接
void f(char* v, int sz) {
qsort(v, sz, 1, &compare); // 错误
qsort(v, sz, 1, &ccmp); // 正确
isort(v, sz, 1, &compare); // 正确
isort(v, sz, 1, &ccmp); // 错误
}
```
#### 6. 使用头文件
为了说明头文件的使用,我们将介绍两种将桌面计算器程序拆分为文件的方法。
##### 6.1 单头文件组织
单头文件组织的方法是将定义放在适当数量的`.c`文件中,将它们通信所需的类型声明放在一个`.h`文件中,每个`.c`文件都`#include`这个头文件。
以下是计算器程序的单头文件示例:
```cpp
// dc.h:
namespace Error {
struct ZeroDivide { };
struct SyntaxError {
const char* p;
SyntaxError(const char* q) { p = q; }
};
}
#include <string>
namespace Lexer {
enum TokenValue {
NAME,
NUMBER,
END,
PLUS = '+',
MINUS = '-',
MUL = '*',
DIV = '/',
PRINT = ';',
ASSIGN = '=',
LP = '(',
RP = ')'
};
extern TokenValue curr_tok;
extern double number_value;
extern std::string string_value;
TokenValue get_token();
}
namespace Parser {
double prim(bool get);
double term(bool get);
double expr(bool get);
using Lexer::get_token;
using Lexer::curr_tok;
}
#include <map>
extern std::map<std::string, double> table;
namespace Driver {
extern int no_of_errors;
extern std::istream* input;
void skip();
}
```
对应的`.c`文件示例:
```cpp
// lexer.c:
#include "dc.h"
#include <iostream>
#include <cctype>
Lexer::TokenValue Lexer::curr_tok;
double Lexer::number_value;
std::string Lexer::string_value;
Lexer::TokenValue Lexer::get_token() { /* ... */ }
// parser.c:
#include "dc.h"
double Parser::prim(bool get) { /* ... */ }
double Parser::term(bool get) { /* ... */ }
double Parser::expr(bool get) { /* ... */ }
// table.c:
#include "dc.h"
std::map<std::string, double> table;
// main.c:
#include "dc.h"
#include <sstream>
#include <iostream>
int Driver::no_of_errors = 0;
std::istream* Driver::input =
```
0
0
复制全文
相关推荐









