下面展示 顺序表的底层实现
。
#pragma once
#include
using namespace std;
//1、试用顺序表表示集合,并确定合适的约定,在此基础上编写算法以实现集合的交、并、差等运算,并分析各算法的时间性能。(3*)
enum Error_code {
Success, Range_error, Overflow, Underflow };
template<class T>
class Sequence_List {
public:
Sequence_List(int maxlen = 10); //构造函数初始化
Sequence_List(const Sequence_List<T> &SL); //拷贝构造
void copy(const Sequence_List<T>& L); //复制函数(不改变容量,只将L的元素装入此类)
bool empty(); //判断表是否为空
bool full(); //判断表是否为满
unsigned int size() const; //求顺序表中的元素数
unsigned int capacity() const; //求顺序表中的容量
int find(const T &x) const; //按值查找元素并返回元素位置
Error_code get_element(const int i, T &x) const; //获取i号位置的元素,并传入x来接受(传入的x被i号赋值,要被改变,故用&)
Error_code insert(const int i, const T x); //将元素x插入到i位置
Error_code delete_element(const int i); //删除i号位置元素
//Sequence_List Union(Sequence_List &A,Sequence_List &B); //求两表的并集
//Sequence_List Intersection(Sequence_List &A, Sequence_List &B); //求两表的交集
//Sequence_List Difference_Set(Sequence_List &A, Sequence_List &B); //求两表的差集
void Union(Sequence_List<T> &A, Sequence_List<T> &B); //求两表的并集
void Intersection(Sequence_List<T> &A, Sequence_List<T> &B); //求两表的交集
void Difference_Set(Sequence_List<T> &A, Sequence_List<T> &B); //求两表的差集
void removeDuplicate(); //去重
void print(); //打印表
~Sequence_List(); //析构函数
private:
T *data;
unsigned int count; //元素数
int m_capacity; //数组的容量
};
template<class T>
inline Sequence_List<T>::Sequence_List(int maxlen)// :m_capacity(maxlen) //初始化列表给m_capacity赋初值
{
//若重名
//this->m_capacity = maxlen;
data = new T[maxlen];
m_capacity = maxlen;
count = 0;
}
template<class T>
inline Sequence_List<T>::Sequence_List(const Sequence_List<T>& SL)
{
m_capacity = SL.m_capacity;
count = SL.count;
data = new T[m_capacity];
for (int i = 0; i < count; i++)
data[i] = SL.data[i];
}
//仅仅是拷贝其他的值,没改变空间与容量
template<class T>
inline void Sequence_List<T>::copy(const Sequence_List<T>& SL)
{
for (int i = 0; i < SL.size(); i++)
data[i] = SL.data[i];
count = SL.size();
}
template<class T>
inline bool Sequence_List<T>::empty()
{
if (count == 0) return true;
return false;
}
template<class T>
inline bool Sequence_List<T>::full()
{
if (count == m_capacity) return true;
return false;
}
template<class T>
inline unsigned int Sequence_List<T>::size() const
{
return count;
}
template<class T>
inline unsigned int Sequence_List<T>::capacity() const
{
return m_capacity;
}
template<class T>
inline int Sequence_List<T>::find(const T & x) const
{
for (int i = 0; i < count; i++)
if (data[i] == x) return (i + 1);
return -1;
}
template<class T>
inline Error_code Sequence_List<T>::get_element(const int i, T & x) const
{
if (i<1 || i>count)return Range_error;
x = data[i - 1];
return Success;
}
template<class T>
inline Error_code Sequence_List<T>::insert(const int i, const T x)
{
if (count == m_capacity) return Overflow;
if (i<1 || i>count + 1) return Range_error;
//j指向待移走线性表中元素序号
for (int j = count; j >= i; j--) data[j] = data[j - 1];
data[i - 1] = x;
count++;
return Success;
}
template<class T>
inline Error_code Sequence_List<T>::delete_element(const int i)
{
if (count == 0) return Underflow;
if (i<1 || i>count) return Range_error;
for (int j = i + 1; j <= count; j++)
data[j - 2] = data[j - 1];
count--;
return Success;
}
template<class T>
//inline Sequence_List Sequence_List::Union(Sequence_List &A, Sequence_List &B)
inline void Sequence_List<T>::Union(Sequence_List<T> &A, Sequence_List<T> &B)
{
if (data != NULL)
{
//先把原来释放干净
delete[]data;
data = NULL;
}
//深拷贝
m_capacity = A.m_capacity+B.m_capacity;
data = new T[m_capacity];
for (int i = 0; i < count; i++)
data[i] = A.data[i];
count = A.size();
for (int i = 0; i < B.size(); i++) {
if (this->find(B.data[i]) == -1)
this->insert(this->size() + 1, B.data[i]);
}
}
//p.s.形参列表传入类,相当于是值传递(不过这个类中的数组是地址传递),相当于再建了一个形参的类(调用了拷贝和析构)
//因此一般用引用传递来减少时间和空间
//即传递对象值会引起拷贝构造和析构,增加空间开销
template<class T>
//inline Sequence_List Sequence_List::Intersection(Sequence_List &A, Sequence_List &B)
inline void Sequence_List<T>::Intersection(Sequence_List<T> &A, Sequence_List<T> &B)
{
//注意:成员函数的参数为类对象时,可以通过类对象直接访问其私有成员
//因为函数Sequence_List::Intersection(Sequence_List A, Sequence_List B)是Sequence_List类的成员函数,函数处于类的内部,
//因此对于相同类的对象,是可以通过对象名.私有成员来调用的。若是在类的外部,如在main()中使用对象名.私有成员是一定会报错的。
//Sequence_List C;
/*for (int i = 0; i < A.count; i++) {
for (int j = 0; j < B.count; j++) {
if (A.data[i] == B.data[j]) {
C.insert(1, A.data[i]);
}
}
}*/
/*for (int i = 0; i < A.count; i++) {
if (B.find(A.data[i]) != -1)
C.insert(C.count+1, A.data[i]);
}
return C;*/
for (int i = 0; i < A.size(); i++) {
if (B.find(A.data[i]) != -1)
this->insert(this->count + 1, A.data[i]);
}
}
template<class T>
//inline Sequence_List Sequence_List::Difference_Set(Sequence_List &A, Sequence_List &B)
inline void Sequence_List<T>::Difference_Set(Sequence_List<T> &A, Sequence_List<T> &B)
{
this->copy(A);
for (int i = 0; i < this->count; i++)
this->delete_element(this->find(this->data[i]));
}
template<class T>
inline void Sequence_List<T>::removeDuplicate()
{
/*for (int i = 0; i < count; i++) {
while (data[i] == data[i + 1])
delete_element(i+1);
}*/
/*你的数组在内存中占据着一片连续空间
看似删除i处的操作,实际上是i后面所有的数往前挪了一下
同时,由于你做了count的限制,真正挪动的区间只从i+1~count
但是你的数组在定义时的大小是maxlen,一定大于count,也就是说,count~maxlen-1这一段区间,实际上是合法区间,只不过对于此时大小为count的数组是没意义的
删除操作本质就是挪动区间的操作
那么抛开具体的例子,假设我往前挪动一个单位的[i,j]闭区间
那么[i,j]上的数据到了[i-1, j-1]
[i,j]上的数据到了[i-1, j-1],换句话说,i-1处的数据被覆盖了
那么请问,此时的[j]处的数据是什么?
是不是从未变过
所以上面的区间[i,j]中的j实际上就是9所在的位置
也就是说,当你多次挪动区间,9被多次拷贝且不未被覆盖
因为没有数在他后面
所以当你完成去重操作,你的数组就会是12345678999999……(一直到18个位置)
*/
//错误算法:原因分析:while特点,一直判断,直到条件不满足才会退出。
//当i为7,count为10时,此时1234567889 data[i]=8,data[i+1]=8 delete第八号,覆盖data[7]
//当i为7,count为9时,此时123456789(9) data[i]=8,data[i+1]=9 i++
//当i为8,count为9,此时123456789(9) data[i]=9,data[i+1]=? 答案还是9,不过这个9不在delete管控范围内,因此删(覆盖)不掉
//从而导致一直while循环成立,但delete每次Range_errror跳出(陷入死循环)
//启示:不要做越界(其实也不算叫越界)
for (int i = 1; i < count; i++) {
while (data[i - 1] == data[i])
delete_element(i);
}
}
template<class T>
inline void Sequence_List<T>::print()
{
for (int i = 0; i < count; i++)
cout << data[i] << " ";
cout<<endl;
}
template<class T>
inline Sequence_List<T>::~Sequence_List()
{
if (data!=NULL)
delete[] data;
}
其中 交集
。
template<class T>
inline void Sequence_List<T>::Intersection(Sequence_List<T> &A, Sequence_List<T> &B)
{
/
for (int i = 0; i < A.size(); i++) {
if (B.find(A.data[i]) != -1)
this->insert(this->count + 1, A.data[i]);
}
}
其中 并集
。(扩充了容量为A,B之和)
template<class T>
inline void Sequence_List<T>::Union(Sequence_List<T> &A, Sequence_List<T> &B){
if (data != NULL){
//先把原来释放干净
delete[]data;
data = NULL;
}
//深拷贝
m_capacity = A.m_capacity+B.m_capacity;
data = new T[m_capacity];
for (int i = 0; i < count; i++)
data[i] = A.data[i];
count = A.size();
for (int i = 0; i < B.size(); i++) {
if (this->find(B.data[i]) == -1)
this->insert(this->size() + 1, B.data[i]);
}
}
其中 差集
。
template<class T>
inline void Sequence_List<T>::Difference_Set(Sequence_List<T> &A, Sequence_List<T> &B)
{
this->copy(A);
for (int i = 0; i < this->count; i++)
this->delete_element(this->find(this->data[i]));
}
测试代码
#include"Linked_List.hpp"
int main() {
//假设链表A、B分别表示两个集合,设计算法以求解C = A∪B,并分析算法的时间复杂度。 (3*)
//假设递增有序链表A、B分别表示一个集合,设计算法以求解C= A∩B,并分析算法的时间复杂度。
Linked_List<int> C;
for (int i = 0; i < 5; i++)
C.insert(C.size() + 1, i);
C.print();
Linked_List<int> D;
for (int i = 3; i < 8; i++)
D.insert(D.size() + 1, i);
D.print();
Linked_List<int> E;
E.Union(C, D);
E.print();
Linked_List<int> F;
F.Intersection(C, D);
F.print();
return 0;
}
输出结果为
0 1 2 3 4
3 4 5 6 7
0 1 2 3 4 7 6 5
3 4
以上是我改正之后的代码
但在此之前,我的这三个分别是这样的
template<class T>
inline Sequence_List<T> Sequence_List<T>::Union(Sequence_List<T> &A, Sequence_List<T> &B)
{
Sequence_List<T> C(A);
for (int i = 0; i < B.count; i++) {
if (C.find(B.data[i]) == -1)
C.insert(C.count + 1, B.data[i]);
}
return C;
/*this->copy(A);
for (int i = 0; i < B.size(); i++) {
if (this->find(B.data[i]) == -1)
this->insert(this->size() + 1, B.data[i]);
}*/
}
template<class T>
inline Sequence_List<T> Sequence_List<T>::Intersection(Sequence_List<T> &A, Sequence_List<T> &B)
//inline void Sequence_List::Intersection(Sequence_List &A, Sequence_List &B)
{
Sequence_List<T> C;
for (int i = 0; i < A.count; i++) {
if (B.find(A.data[i]) != -1)
C.insert(C.count+1, A.data[i]);
}
return C;
/*for (int i = 0; i < A.size(); i++) {
if (B.find(A.data[i]) != -1)
this->insert(this->count + 1, A.data[i]);
}*/
}
template<class T>
inline Sequence_List<T> Sequence_List<T>::Difference_Set(Sequence_List<T> &A, Sequence_List<T> &B)
//inline void Sequence_List::Difference_Set(Sequence_List &A, Sequence_List &B)
{
Sequence_List<T> D(A);
Sequence_List<T> C = C.Intersection(A, B);
for (int i = 0; i < C.count;i++)
D.delete_element(D.find(C.data[i]));
return D;
/*this->copy(A);
for (int i = 0; i < this->count; i++)
this->delete_element(this->find(this->data[i]));*/
}
即类中的这三个成员函数并不是对这个类对象本身进行操作的
而是又创建了另一个对象,对其进行操作后再返回
因此,测试代码需要进行相应的改变,如下:
#include"Sequence_List.hpp"
int main() {
Sequence_List<int> A(12);
A.insert(A.size() + 1, 1);
A.insert(A.size() + 1, 2);
A.insert(A.size() + 1, 4);
A.print();
Sequence_List<int> B(6);
B.insert(B.size() + 1, 3);
B.insert(B.size() + 1, 2);
B.insert(B.size() + 1, 4);
B.print();
Sequence_List<int> C;
C=C.Intersection(A, B);
C.print();
Sequence_List<int> D ;
D= D.Union(A, B);
D.print();
Sequence_List<int> E ;
E = E.Difference_Set(A, B);
E.print();
Sequence_List<int> F ;
F = F.Difference_Set(B, A);
F.print();
return 0;
}
看着仿佛没有什么问题,但系统,显示已触发了一个断点,这是为什么呢?
其实,因为此顺序表开辟的是动态数组,在堆区开辟了空间,自然此表的析构函数会去释放堆区空间
template<class T>
inline Sequence_List<T>::~Sequence_List()
{
if (data!=NULL)
delete[] data;
}
这样的析构函数,我们暂且称为深析构,这时候往往要注意浅拷贝带来的堆区内存重复释放问题。即系统中提供的默认拷贝构造函数往往只会提供浅拷贝的操作,即简单的值拷贝
template<class T>
inline Sequence_List<T>::Sequence_List(const Sequence_List<T>& SL)
{
m_capacity = SL.m_capacity;
count = SL.count;
data = SL.data;
}
关键问题就出在
data=SL.data;
此时数组中的data是原先在堆区new出的一块内存,data为指针。
若仅仅做如上操作,相当于把此对象this->data指针指向了要拷贝的SL.data指针指向的内存,即两个指针指向同一块内存,这样我对此对象进行的操作就会对SL产生影响,两者共同对这块区域有操作权,但这不是我们希望的,我们不希望对SL产生影响,而且程序结束时,此对象this先释放(执行析构函数),把SL在堆区new出来的内存已经还给了系统,然后SL再释放(析构)此时已经没有内存可以还了,出现堆区内存重复释放问题。因此,我们在这里要做一个深拷贝的操作。
template<class T>
inline Sequence_List<T>::Sequence_List(const Sequence_List<T>& SL)
{
m_capacity = SL.m_capacity;
count = SL.count;
data = new T[m_capacity];
for (int i = 0; i < count; i++)
data[i] = SL.data[i];
}
重新给this对象开辟一片与SL相同长度的空间,并把其中的值拷贝到this对象的新开辟的data空间中。
但明明已经做了深拷贝,为什么还会出现已经释放了的内存重复删除的情况呢?我们对如下代码进行分析
//功能实现
template<class T>
inline Sequence_List<T> Sequence_List<T>::Union(Sequence_List<T> &A, Sequence_List<T> &B)
{
Sequence_List<T> C(A);
for (int i = 0; i < B.count; i++) {
if (C.find(B.data[i]) == -1)
C.insert(C.count + 1, B.data[i]);
}
return C;
}
//测试代码
Sequence_List<int> D ;
D= D.Union(A, B);
D.print();
分析这个并集功能实现,首先先将A拷贝给了C(深拷贝),再将遍历B,将B中C没找到的元素插入C末端。最后返回了C(其实是C的临时拷贝)。
下面我们来具体的分析一下这个函数的构造与析构情况,首先,因为A,B传入的不是值,是引用,故在形参不调用析构,然后在函数体中创建了C,调用了一次拷贝构造,这个局部变量C,在return之前被析构掉了,但因为此时return的是C的临时拷贝的类(即在C被析构之前还调用了一次拷贝构造创建了一个临时的类),然后这个临时类在将其一些属性值返回后就立即释放了。
问题在这个时候浮现了
D= D.Union(A, B);
对这一过程,临时类被返回给了D.Union(A, B);然后被赋值给了D,赋值之后立马释放了(析构),但注意对两个类的=,按道理说是无法进行了,但系统会给你提供一个默认的=运算符重载,不过这个重载的=运算符,仅做了浅拷贝,故D只得到了这个临时类的count,capacity和data的地址,注意仅仅是将D的data指针指向了这个临时类的data空间(共用一块空间),但由于这个临时类做完=浅拷贝操作后就立马析构掉了(将其data中new出来堆区的空间还给了系统),故此时D中data指针指向的地址是一片已经还给系统的非法空间,后期对D进行打印的时候,访问了非法空间。
那如何修改呢?
直接在构造交并差类的时候,返回拷贝C.Intersection(A, B), D.Union(A, B); E.Difference_Set(A, B);的临时变量,在构造这个类的时候系统调用的不是默认的重载=运算符,而是你写的拷贝构造函数。
Sequence_List<int> C=C.Intersection(A, B);
//C=C.Intersection(A, B);
C.print();
Sequence_List<int> D = D.Union(A, B);
//D= D.Union(A, B);
D.print();
Sequence_List<int> E = E.Difference_Set(A, B);
//E = E.Difference_Set(A, B);
E.print();
Sequence_List<int> F = F.Difference_Set(B, A);
//F = F.Difference_Set(B, A);
F.print();
return 0;
}
p.s.对调用构造函数的方式的回顾
void test02()
{
//1、括号法 (建议使用)
Person2 p11; //默认构造函数的调用,不要加()
Person2 p12(10);//有参构造函数调用
Person2 p13(p12);//拷贝构造函数的调用
//注意事项
//调用默认构造函数时,不要加()
//因为下面这行代码,编译器会认为是一个函数的声明(在一个函数体内部允许写另一个函数声明)
//Person2 p4();
//void func();类比
cout << "p12的年龄为:" << p12.age << endl;
cout << "p13的年龄为:" << p13.age << endl;//拷贝年龄
//2、显示法
Person2 p21; //默认构造函数的调用,不要加()
Person2 p22 = Person2(10); //有参构造函数调用(相当于给匿名对象取名)
Person2 p23 = Person2(p22);//拷贝构造函数的调用
Person2(10);//匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
//即下面那行aaaaaaa的代码在匿名对象的析构函数之后执行
//即匿名对象在下一行代码执行之前已被销毁
cout << "aaaaaaa" << endl;
//注意事项2
//不要利用拷贝构造函数 初始化匿名对象
//编译器会认为 Person (p3) == Person p3;(对象的声明)
//Person2(p23); 错误 这样相当于一个无参构造而上面已经有一个p23了
//3、隐式转换法
Person2 p32 = 10;//相当于 写了 Person p32 = Person2(10); 有参构造
Person2 p33 = p32;//拷贝构造
}
在临时变量还没被析构时即输出
Sequence_List<int> C(A);
C.Intersection(A, B).print();
Sequence_List<int> D;
D.Union(A, B).print();
Sequence_List<int> E;
E.Difference_Set(A, B).print();
Sequence_List<int> F;
F.Difference_Set(B, A).print();
template<class T>
inline Sequence_List<T>& Sequence_List<T>::operator=(Sequence_List<T> & SL)
{
//编译器是提供浅拷贝
//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (data != NULL)
{
//先把原来释放干净
delete data;
data = NULL;
}
//深拷贝
m_capacity = SL.m_capacity;
count = SL.count;
data = new T[m_capacity];
for (int i = 0; i < count; i++)
data[i] = SL.data[i];
//返回对象本身
return *this;
}
p.s.我不知道为什么vs识别不出=
但vc可以正确运行
跪求大佬解释为啥vc可以编译,vs不行!
C++中拷贝构造函数调用的时机通常有三种情况:
Person p2(p1);
void doWork(Person p)//值传递{
}
void test2(){
Person p0;//调用默认构造
doWork(p0);//调用这个函数的时候是用值传递的方法
//值传递的本质是会拷贝一个临时的副本出来(调用一个拷贝构造函数,在创建一个新的东西)
//实参在传给形参的时候会调用一个拷贝构造函数
//形参p会按照实参p拷出一个新的数据
//所以,现在操纵形参不会影响实参
}
实参p0传递给了形参p,在形参列表中,调用拷贝构造函数构造了一个局部变量p,在函数中对p操作一般不会改变p0(除非p0的成员变量中有指针)
这个p在函数执行完后析构
以值方式返回局部对象
//在一个函数里定义的对象叫局部对象
//局部对象有一个特点在函数执行完之后就会被释放掉
Person doWork2(){
Person p1;//创建局部对象 //会调用默认构造函数
cout << (int*)&p1 << endl;
return p1;//但返回的不是p1本身
//是用值方式来返回,这里面会根据p1来创建(拷贝)一个新的对象,然后返回给test
}
//在doWork2执行完后原p1被释放,产生析构函数,在这个过程之前,return产生了一个拷贝函数
void test(){
Person p = doWork2();
cout << (int*)&p << endl;//两地址不一样说明返回的p1(外侧用p接收的p1)与原p1不是同一个对象
}
运行结果:
Person的默认构造函数的调用 (创建p1)
00EFF6F4 (p1的地址)
Person的拷贝构造函数调用 (临时类对p1的拷贝)
Person 析构函数的调用 (局部对象p1的析构)
00EFF7EC (临时对象把地址传给了p,打印p的地址)
Person 析构函数的调用 (p的析构)
但是很奇怪的是,为什么临时类没有析构函数,p没有拷贝构造函数?
其实像下面这样将Person p = doWork2()分开写就有了。
(为了更好判断,在调用前写上this)
class Person9{
public:
Person9(){
cout<<this << "Person3的默认构造函数的调用" << endl;
}
Person9(int age){
cout<<this<< "Person3的有参构造函数调用" << endl;
m_Age = age;
}
Person9(const Person9 & p){
cout<<this<< "Person3的拷贝构造函数调用" << endl;
m_Age = p.m_Age;
}
~Person9(){
cout << this << "Person3 析构函数的调用" << endl;
}
int m_Age;
};
Person9 doWork2()
{
Person9 p1;//创建局部对象 //会调用默认构造函数
cout << &p1 << endl;//p1为对象指针
return p1;//但返回的不是p1本身
//是用值方式来返回,这里面会根据p1来创建(拷贝)一个新的对象,然后返回给test9
}
//在doWork2执行完后原p1被释放,产生析构函数,在这个过程之前,return产生了一个拷贝函数
void test9()
{
Person9 p;
p=doWork2();
cout <<(int*) &p << endl;//强行把对象指针转化为整型指针(int)&p才是转化为十进制
//两地址不一样说明返回的p1(外侧用p接收的p1)与原p1不是同一个对象
}
输出结果:
0056F974 Person3的默认构造函数的调用 (test9中p的构造)
0056F86C Person3的默认构造函数的调用 (doWork2()中p1的构造)
0056F86C
0056F8A8 Person3的拷贝构造函数调用
(根据p1来创建(拷贝)一个新临时的对象,然后返回给test9)
0056F86C Person3 析构函数的调用 (doWork2()中p1的析构)
0056F8A8 Person3 析构函数的调用
(在完成对p的赋值doWork2()中临时对象的析构)
0056F974
0056F974 Person3 析构函数的调用 (test9中p的析构)
分析:为什么分开写就会有临时类析构函数,p构造函数,合在一起写没有呢?(相当于显示法调用拷贝构造)对于Person2(p22)的匿名对象拷贝析构省略。(可能说的不太严谨)
//2、显示法
Person2 p21; //默认构造函数的调用,不要加()
Person2 p22 = Person2(10); //有参构造函数调用(相当于给匿名对象取名)
Person2 p23 = Person2(p22);//拷贝构造函数的调用
Person2(10);//匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
//即下面那行aaaaaaa的代码在匿名对象的析构函数之后执行
//即匿名对象在下一行代码执行之前已被销毁
cout << "aaaaaaa" << endl;