1、除非已显式声明为局部名字,否则函数名、类名、模板名、变量名、名字空间名、枚举名以及枚举值名的使用必须跨所有编译单元保持一致。
2、如果全局作用域中或名字空间中的变量定义不带初始值,则该变量会使用默认初始值。非static局部变量或创建在自由存储上的对象则不会使用默认初始值。
3、关键字static及关键字const、constexpr暗示默认使用内部链接,如果希望具有外部链接,就需要在其定义前加上extern。
4、inline函数在其应用的所有编译单元中都必须有完全等价的定义。为了确保一致性,应该将别名、const对象、constexpr对象和inline函数放置在头文件中。
5、当需要使用全局变量时,应限制他们只在单一源文件中使用,有两种方法实现这种限制:
- 将声明放在无名名字空间中。
- 声明实体时使用static。
6、单一定义规则(ODR),即一个类、模板或内联函数的两个可被接受、被认为是相同唯一定义,当且仅当如下条件成立:它们出现在不同的编译单元中,且它们的源码逐单词对应,完全一样,且这些单词在两个编译单元中的含义完全一样。
7、每个C标准库头文件<X.h>都有一个对应的标准C++头文件<cX>。例如,#include <cstdio>提供了#include <stdio.h>的功能。
8、在extern声明中使用链接规范,可以帮助我们声明使用的函数链接符合何种规范。
extern "C" char* strcpy(char*, const char*); //符合C实现规范
//链接块构造
extern "C"{
char* strcpy(char*, const char*);
int strcmp(const char*, const char*);
int strlen(const char*);
//...
}
9、将一个程序划分为多个文件的最简单的方法就是将定义放在适当数量的.cpp文件中,而将所需的类型、函数、类等的声明放在单一的.h文件中,每个.cpp文件都#include它。
//dc.h:
#include <map>
#include <string>
#include <iostream>
namespace Parser{
double expr(bool);
double term(bool);
double prim(bool);
}
namespace Lexer{
enum class Kind:char{
name, number, end,
plus='+', minus='-', mul='*', div='/', print=';', assign='=', lp='(', rp=')'
};
struct Token{
Kind kind;
string string_value;
double number_value;
};
class Token_stream{
public:
Token(istream& is):ip{&s}, owns(false), ct{Kind::end}{}
Token(istream* p):ip{p}, owns{true}, ct{Kind::end}{}
~Token(){close();}
Token get();
Token& current();
void set_input(istream& s){close(); ip=&s; owns=false;}
void set_input(istream* p){close(); ip=p; owns = true;}
private:
void close(){if(owns) delete ip;}
istream* ip;
bool owns;
Token ct{Kind::end};
};
extern Token_stream ts;
}
namespace Table{
extern map<string, double> table;
}
namespace Error{
extern int no_of_errors;
double error(const string& s);
}
namespace Driver{
void calculate();
}
在编写实现文件的时候,我们使用显示限定(如下例子Lexer::),而不是简单地将他们都放在名字空间中:
//lexer.cpp
#include "dc.h"
#include <cctype>
#include <iostream>
Lexer::Token_stream ts;
Lexer::Token Lexer::Token_stream::get(){/*...*/}
Lexer::Token& Lexer::Token_stream::current(){/*...*/}
10、包含保护,可以避免包含冗余的类定义或内联函数头文件在相同的编译单元中被#include两次带来的错误。
//error.h
#ifndef CALC_ERROR_H
#define CALC_ERROR_H
namespace Error{
//...
}
#endif //CALC_ERROR_H
11、程序终止时,如果使用标准库函数exit()则会调用已构造的静态对象的析构函数。但是如果程序是使用标准库函数abort()终止的,析构函数就不会被调用。这意味着,exit()不会立即终止程序。在一个析构函数中调用exit()会导致无限递归。函数quick_exit()与exit()类似,区别在于它不调用任何析构函数。