1. 效率分析
template <typename T>
class SeqList : public List<T>
{
public:
bool insert(int i,const T& e);//O(n)
bool remove(int i); //O(n)
bool set(int i,const T& e); //O(1)
bool get(int i,T& e) const; //O(1)
int length() const; //O(1)
void clear(); //O(1)
T& operator[] (int i); //O(1)
T operator[] (int i) const; //O(1)
virtual int capacity() const = 0;
};
insert 函数最耗时的地方时for循环,但是不同情况效率一致嘛?
举个例子:
SeqList<int> s1;
SeqList<string> s2;
s1.insert(0,1);
s2.insert(0,"DragonLib");
假设容量都是20,那么很显然小效率是不一致的,s1速度很快,但是s2相当于一个字符一个字符地挪动,效率很低!
结论:效率不只是看时间复杂度,顺序存储的线性表不适合类类型的数据存储
2. 代码分析
2.1 StaticList中的问题
StaticList<int*,5> s1;
StaticList<int*,5> s2;
for(int i=0;i<s1.capacity();i++)
{
s1.insert(0,new int(i));
}
s2 = s1; //copy assignment
for(int i =0;i<s1.length();i++)
{
delete s1[i];
delete s2[i];
}
s2 = 21;会发生什么呢?
指针两个数组指向同一个区域。然而在进行delete s1[i];delete s2[i];时,相当于执行了两遍清空操作,这是未定义的,危险行为。
2.2 DynamicList中的问题
void func()
{
DynamicList<int> d1(5);
DynamicList<int> d2 = d1; //copy construct
for(int i=0;i<d1.capacity();i++)
{
d1.insert(i,i);
d2.insert(i,i*i);
}
for(int i=0;i<d1.length();i++)
{
cout << d1[i] << endl;
}
}
m_array指向同一片区域,析构时,会被释放两次!
3. 如何解决?
3.1 分析
对于容器类型的类,可以考虑禁用拷贝构造和赋值操作。(容器类型的类:存放数据元素的类,不知道这样理解对不对?没学过STL…)
template <typename T>
class List : public Object
{
protected:
List(const List&);
List& operator= (const List&);
public:
List()
{
}
//...
};
我们可以在List.h中添加拷贝构造和赋值操作符重载(protected属性),编译会报错,因为我们自己写了构造函数,所以编译器不会提供了!我们需要在public:后面加上List(){},这样就不会报错了!
我们可以在main函数里实验一下是否可以拷贝构造和赋值操作:
int main()
{
DynamicList<int> L(5);
DynamicList<int> n =L;
return 0;
}
编译报错!
赋值呢?
int main()
{
DynamicList<int> l(5);
DynamicList<int> n(5);
n = l;
return 0;
}
还是报错!
3.2 下面代码正确嘛?
int main()
{
StaticList<int,5> list;
for(int i=0;i<list.capacity();i++)
{
list[i] = i*i;
}
return 0;
}
编译会报错,线性表必须先插入元素,才能使用操作符[ ]访问元素。
问题分析:
4. 小结
- 顺序存储线性表的插入和删除操作存在重大效率隐患
- 线性表作为容器类,应该避免拷贝构造和拷贝赋值
- 顺序存储线性表可能被当成数组误用
- 工程开发中可以考虑使用数组类代替原生数组使用