Rumah > Soal Jawab > teks badan
ArrayList.h
#pragma once
template <class T>
class ArrayList
{
public:
ArrayList(int size);
~ArrayList(void);
private:
T* arrayList;
int maxSize;
};
ArrayList.cpp
#include "ArrayList.h"
template <class T>
ArrayList<T>::ArrayList(const int size)
{
maxSize = size;
arrayList = new T[maxSize];
}
template <class T>
ArrayList<T>::~ArrayList(void)
{
delete [] arrayList;
}
源.cpp
#include <iostream>
#include "ArrayList.h"
using namespace std;
int main()
{
ArrayList<int> list(2);
system("pause");
return 0;
}
出现的错误是
将三个文件写到一个文件里后就没有错误了
#include <iostream>
using namespace std;
template <class T>
class ArrayList
{
public:
ArrayList(int size);
~ArrayList(void);
private:
T* arrayList;
int maxSize;
};
template <class T>
ArrayList<T>::ArrayList(int size)
{
maxSize = size;
arrayList = new T[maxSize];
}
template <class T>
ArrayList<T>::~ArrayList(void)
{
delete [] arrayList;
}
int main()
{
ArrayList<int> list(2);
system("pause");
return 0;
}
请问这是为什么
怪我咯2017-04-17 13:06:54
简单答案是“模板要写在.h文件里”。
但我想这不是题主想要的。
要搞清楚这问题,你得先明白分开.h和.cpp的意义。
平常我们分开.cpp和.h,为的并不是模块化。
模块化本身体现在“把不同的东西分别放在不同的文件”,但.cpp和.h却是“把相同的东西分别放在两个文件”。
事实上,你大可以完全不写.h档,只写.cpp,然后#include "bla.cpp",一样可以通过编译。
(只要.cpp里的函数/变量/类等的名字没撞车……)
分开.h和.cpp的最大原因,在于把各.cpp分开编译。
在建构程序时,我们把所有.cpp(源码文件)编译成.o(目标文件),然后把.o整合成为可执行文件(或者库)。
假设我们的软件项目里,有100个.cpp文件,而在上次建构之后,我们只改了其中一个。
那么最好的情况是只有一个.o文件需要重新编译,其余99个可以直接用来整合可执行文件,省下编译时间。
这对于建构大型软件来说很有用,总不能你改了Office的一行代码,就把数十GB的源码都重新编译吧……
然后我们有另一个问题:
.cpp全部都分开了,互相不知道彼此的存在,那如果a.cpp要用到b.cpp里的东西,怎么办?
a.cpp需要知道b.cpp提供了甚么功能,但不需要知道那些功能是怎么实现的。
而“展示所提供的功能”就是.h文件的作用:我提供了名为BlaBlaBla的类,里面有某某变量某某方法,还有个叫做doSomething的函数,它接受两个int作为参数,……
于是所有.cpp只需要引入.h,看看能做些甚么就行了。
它们不需要管.h里说的那些功能是怎样实现的,反正只要知道最终整合为可执行文件事,这些事都能完成,就够了。
这就实现了依赖分离,从而让各.cpp可以分开编译。
但模板不同:模板没办法分开编译。
为甚么?看看以下的例子:
template <typename T>
T* makeArray(size_t n) {
return new T[n];
}
编译器看到了这行代码,它应该做甚么?
一般情况下,假如编译器看到一个类似的东西:
SomeClass* makeArray(size_t n) {
return new SomeClass[n];
}
它应该在.o里写下类似这样的二进制代码:
分配一段足够长的空间(n * sizeof(SomeClass))
找到SomeClass对应的构造函数
对空间里的每一个sizeof(SomeClass)大小的位置,执行一遍SomeClass的构造函数
返回空间的地址
但在模板的例子里,它办不到:它根本不知道T是甚么鬼,sizeof(T)不明,无法编译!
另一个问题在于,模板有些特性,让你不到实际知道T之前,根本不知道编译出来的代码长甚么样子。
其中一个叫做偏特化:
template<typename T>
int getValue(T t) {
return t.value;
}
template<>
int getValue(int t) {
return 0;
}
这东西怎么编译?在不知道T是甚么的情况下,你根本不知道该用上面的版本还是下面的版本……
因此,模板代码无法被编译成.o文件。
其他.cpp要使用这段模板,只有直接把它复制贴上到自己的代码里,也就是#include干的事。
事实上,这也体验了“模板”这个名字:我只是个源码的模板而已,是用来方便你复制贴上然后修改的,不是用来编译的!
黄舟2017-04-17 13:06:54
我来告诉题主你终极的答案。
其实这个模板写cpp里面的事情早就有人propose了。世界上有一个最牛逼的公司叫EDG,负责写正确的C++编译器,然后告诉标准委员会他们的看法,顺便卖一下他们的C++/Java/Fortran的编译器前端。他们花了两年终于把这件事情给做出来了,里面包括如何把模板代码的AST序列化进obj/lib以便于在链接的时候再展开重新编译一边等超级复杂的工作。最后得出一个结论:
这么牛逼的功能,只有我们知道怎么做,你们是做不出来的。
所以全世界就作罢了,再也不提这件事情。
因此其它的答案说了一大堆道理其实都是不正确的,因为并没有什么理由阻止C++编译器在链接的时候才编译模板的源代码,这全部都是工程上的取舍。做这件事情,好处又几乎没有,但是编译器的复杂度又上升了一大截。所以没有什么意义。
伊谢尔伦2017-04-17 13:06:54
如果不用#pragma once
改成用这个试试看呢
#ifndef ARRAY_LIST_H
#define ARRAY_LIST_H
#endif