C++花式报错大赏
引言
写作这篇文档的目的是记录下我自己在C++编程过程中,不断成(犯)长(傻)的历程,防止自己忘记这些宝贵的经验。文档中每一处细节都经过百(随)般(便)打(写)磨(写),有解决问题的想法和具体代码,希望帮(别)助(误)到(导)有需要的人。
预处理报错
编译器报错
C++ requires a type specifier for all declarations
不管是定义变量也好,定义函数也罢,一定要在最前面指出这是一个什么类型的变量,这个申明的变量类型被称为type specifier。举例来说,在申明友元类的时候,需要这样写
class A
{
public:
A();
}
class B
{
public:
B();
friend class A;
}
这里class 就是type specifier。
链接时报错
duplicate symbol
在典型的三文件格式下(header.h, definition.cpp, main.cpp),如果不用模版类,头文件只能包含声明,不能包含实现。这是因为两个.cpp文件都会include头文件,所以函数的实现会在两个cpp文件中都出现,如果在header.h中定义了全局变量,那么在链接的时候就出现了redifinition的情况。特别需要注意的是,即使在头文件里加入宏,也不能挽救链接时的重复定义,因为宏只能通过预处理保证单个文件在编译的时候不出现重复定义的情况,而这个报错是出现在链接时候,不在同一个层次上。解决的办法是把全局变量拿出来,放在一个单独的头文件里,比如global.h,规定只能在main.cpp文件里include这个global.h文件,其他地方如果要使用global.h中的全局变量,就不能直接include,而是要用extern关键字,在链接时读入数据,而不能在编译时读入。
undefined symbols
在某个文件里include一个文件A.h(没有用模版),那么链接的时候就要把A.cpp 当作参数传入g++ 命令当中,否则只有申明,没有定义,就会发生这个错误。
运行时报错
不会吧不会吧,不会真的有人以为程序在链接结束之后,就一定可以运行吧。除了在编译过程中出错,无法生成二进制文件外,就算生成了二进制文件,也不一定能够成功运行。这里99%的原因都是访问不在程序控制范围内的内存,换句话说,C++不提供下标检查功能,在编译器根本不知道有没有哪些内存能够访问而哪些不行。
segmentation fault
这是最常见的错误,原因是臭名昭著的下标越界
C++不为人知的小技巧
在类中构造不定长数组
目前探索出来的操作是在头文件对应的类里声明一个指针,然后在构造函数中,根据输入的参数,用new得到特定长度数组的地址。比如
class Mesh_1
{
private:
Interval *interval;
int n_grids;
Interval *elements;
public:
Mesh_1(Point_1 *_a, int _n_nodes);
Mesh_1(const Interval *_a , int _n_grids);
~Mesh_1();
const Interval& get_interval() const;
int get_n_grids();
int get_n_nodes();
const Interval& get_element(int _i);
};
在这个例子中,我想要私有成员elements指向一组Interval类的实例对象,但是具体有多少个这样的对象只有在类初始化的时候才知道;此外还有一个私有成员interval是一个指向一个特定Interval实例对象指针。在声明部分中,这两个意义不同的指针并没有任何区别,区别的实现是在构造函数中,代码如下
Mesh_1::Mesh_1(Point_1 *_a, int _n_nodes)
{
interval = new Interval(_a[0],_a[_n_nodes-1]);
elements = new Interval[_n_nodes-1];
for (int i=0; i < _n_nodes; i++)
{
_a[i].set_index(i);
}
for (int i=0; i< _n_nodes-1; i++)
{
elements[i] = Interval(_a[i], _a[i+1]);
}
n_grids = _n_nodes - 1;
};
可以看到针对interval的初始化,是直接在new后面跟着一个Interval构造函数,而针对elements的初始化,在new后面跟着的一个Interval数组!数组的长度由Mesh_1的构造函数的参数决定,并没有调用任何构造函数,区别就在这里。