实现具备C++11现代特性的STL——vector篇(附带简单的航空订票系统实例)

实现具备C++11现代特性的STL------vector篇(附带简单的航空订票系统实例)

引言

本代码旨在练习诸如智能指针,万能引用,完美转发,可变参数模板等C++11现代特性相关语法,并复习vector迭代器的创建等重点知识,所以并未完全参照标准库的vector实现,包括但不限于,为迭代器单独创建了迭代器类等,但同时,也使用智能指针模仿了vector标准实现中由指针作为成员变量进行容器控制的思路

vector源码

cpp 复制代码
#pragma once
#include <iostream>
#include <initializer_list>
#include <memory>
#include <utility>// move forward swap等函数所在库
#include <assert.h>

namespace dzh
{
	// 前置声明
	template<class T> class MyVector;

	template<class T, class Ref, class Ptr>
	class Iterator
	{
		friend class MyVector<T>;
	public:
		Iterator() = default;
		Iterator(T* ptr, T* begin, T* end)
			:_ptr(ptr)
			,_begin(begin)
			,_end(end)
		{
		}

		Iterator& operator++()
		{
			assert(_ptr != _end);
			++_ptr;
			return *this;
		}

		Iterator operator++(int)
		{
			assert(_ptr != _end);
			Iterator mid = *this;
			++_ptr;
			return mid;
		}

		Iterator& operator--()
		{
			assert(_ptr != _begin);
			--_ptr;
			return *this;
		}

		Iterator operator--(int)
		{
			assert(_ptr != _begin);
			Iterator mid = *this;
			--_ptr;
			return mid;
		}

		bool operator==(const Iterator& other) const
		{
			return _ptr == other._ptr;
		}

		bool operator!=(const Iterator& other) const
		{
			return _ptr != other._ptr;
		}

		Ptr operator->() const
		{
			assert(_end != _ptr);
			return _ptr;
		}

		Ref operator*() const 
		{
			assert(_end != _ptr);
			return *_ptr;
		}
		
		Ref operator[](size_t pos) const
		{
			return _begin[pos];
		}

		Iterator operator-(size_t n) const
		{
			assert(_ptr - n >= _begin);
			return Iterator(_ptr - n, _begin, _end); 
		}

		Iterator operator+(size_t n) const
		{
			assert(_ptr + n < _end);
			return Iterator(_ptr + n, _begin, _end); 
		}

	private:
		T* _ptr = nullptr;
		T* _begin = nullptr;
		T* _end = nullptr;
	};
	
	template<class T>
	class MyVector
	{
		friend class Iterator<T, T&, T*>;
		friend class Iterator<T, const T&, const T*>;
		template<class U> friend std::ostream& operator<<(std::ostream& os, const MyVector<U>& v);// 在思考此处友元是否需要声明为模板重载,由于该友元目的是访问MyVector的私有,而使用<<重载时,需要能够对所有T有效,因此当然要设计为模板
	public:
		// 由于这两个迭代器类型后续需要被外部使用,所以应设为公有
		using iterator = Iterator<T, T&, T*>;
		using const_iterator = Iterator<T, const T&, const T*>;

		// 在需要创建空容器时,需要使用该构造函数,若仅有下面的双参数构造,则capacity与size会一直相同,会导致无法pushback等操作
		MyVector(size_t capacity = 4)
			:_start(std::make_unique<T[]>(capacity < 4 ? 4 : capacity))
			,_finish(_start.get())
			,_end_of_storage(_finish + (capacity < 4 ? 4 : capacity))
		{
		}

		MyVector(size_t capacity, T value)// capacity可能会被错误赋值为负数,由于初始化列表会比函数体更先执行,所以也无法断言处理,这里索性将其设置为size_t类型
			:_start(std::make_unique<T[]>(capacity < 4 ? 4 : capacity))
			,_finish(_start.get() + (capacity < 4 ? 4 : capacity))// 由于会进行值初始化,所以finish也应当指向+capacity
			,_end_of_storage(_finish)
		{
			// 由于unique/unique[]会自动进行值初始化,所以这里判断一下,若value为默认值,则无需重复遍历赋值,若不为默认值,则需要初始化为value
			if(value != T())
			{
				for(int i = 0; i < capacity; ++i)
				{
					_start[i] = value;
				}
			}
		}

		MyVector(std::initializer_list<T> i)// 想支持{}方式的vector初始化,仅需要设置一个C++11的initializer_list容器,然后在构造函数中循环push_back即可
			:_start(std::make_unique<T[]>(4))
			,_finish(_start.get())
			,_end_of_storage(_finish + 4)
		{
			for(const auto& e : i)
			{
				push_back(e);
			}
		}

		MyVector(const MyVector<T>& other)
			:_start(std::make_unique<T[]>(other._finish - other._start.get()))
			,_finish(_start.get() + (other._finish - other._start.get()))// 注意这里后面两个操作数要加小括号,因为指针和指针之间是不能进行+运算的(但可以进行-运算)
			,_end_of_storage(_finish)
		{
			for(int i = 0; i < other._finish - other._start.get(); ++i)
			{
				_start[i] = other._start[i];
			}
		}
		
		MyVector(MyVector<T>&& other) noexcept
		{
			swap(other);
		}
		
		MyVector<T>& operator=(const MyVector<T>& other)// 使用移动语义时,不能同时具备传值传参的函数重载,会导致编译器不知如何选择正确的函数实例进行匹配,这里处理左值的函数参数类型直接写为const T&类型即可,由于也可以使用交换惯用法,所以对右值的处理,其实和原本的传值传参的效率无异
		{
			MyVector<T> newOther(other);
			swap(newOther);
			return *this;// 记得return
		}

		MyVector<T>& operator=(MyVector<T>&& other) noexcept
		{
			swap(other);
			return *this;// 记得return
		}
		
		iterator begin()
		{
			return iterator(_start.get(), _start.get(), _finish);
		}

		iterator end()
		{
			return iterator(_finish, _start.get(), _finish);
		}

		const_iterator begin() const
		{
			return const_iterator(_start.get(), _start.get(), _finish);
		}

		const_iterator end() const 
		{
			return const_iterator(_finish, _start.get(), _finish);
		}
		
		T& operator[](size_t pos)
		{
			assert(_start.get() + pos <= _finish);
			return _start.get()[pos];
		}

		const T& operator[](size_t pos) const
		{
			assert(_start.get() + pos <= _finish);
			return _start.get()[pos];
		}

		void reserve(size_t capacity)
		{
			capacity = capacity <= (_end_of_storage - _start.get()) ? (_end_of_storage - _start.get()) * 1.5 : capacity;
			MyVector newVector(capacity);	
			newVector._finish = newVector._start.get() + (_finish - _start.get());// 将新创建的vector临时对象的finish长度设置为与this的长度一致,以便[]不触发断言
			for(int i = 0; i < _finish - _start.get(); ++i)
			{
				newVector[i] = _start[i];
			}
			swap(newVector);
		}

		void resize(size_t n)
		{
			if(_start.get() + n > _end_of_storage)
			{
				reserve(n);
			}
			_finish = _start.get() + n;
		}

		iterator insert(iterator pos, const T& value)
		{
			// 这里需要创建mid变量存储源迭代器在vector中的相对位置,用于处理扩容后pos迭代器失效的问题
			size_t mid = pos._ptr - _start.get();
			if(_finish == _end_of_storage)
			{
				reserve((_end_of_storage - _start.get()) * 1.5);
			}	
			
			++_finish;
			iterator newPos(_start.get() + mid, _start.get(), _finish);// 使用前面存储的mid变量更新迭代器,需要注意,此处的更新须在++_finish之后
																	   
			for(iterator it = --end(); it != newPos; --it)
			{
				*it = std::move(*(it - 1));// 这里算是一个关键的性能优化点,看似仅仅是将一块内存的二进制cv到另一块上,但对于自定义类型,实则不然,这也是调用了赋值重载,由于当前的赋值本意是覆盖操作,也就是说,被覆盖的数据其实是没用的了,因此,可以直接将这些数据当作右值进行移动赋值调用
			}
			*newPos = value;
			return newPos;
		}

		void push_back(const T& value)
		{	
			// insert(--end(), value);// 这里有问题,当v为空时,end()就是finish也就是_start,此时--end就会触发断言,正确的做法是直接用_finish做一个迭代器当参数传递给insert
			insert(iterator(_finish, _start.get(), _finish), value);
		}
		
		template<class ...Args>
		iterator emplace(iterator pos, Args&& ...args)
		{
			size_t mid = pos._ptr - _start.get();
			if(_finish == _end_of_storage)
			{
				reserve((_end_of_storage - _start.get()) * 1.5);
			}	
			
			++_finish;
			iterator newPos(_start.get() + mid, _start.get(), _finish);// 使用前面存储的mid变量更新迭代器,需要注意,此处的更新须在++_finish之后
																	   
			for(iterator it = --end(); it != newPos; --it)// 这里的循环条件要注意,由于可能发生了扩容,所以此处不能再使用pos迭代器了,应使用newPos作为循环条件
			{
				*it = std::move(*(it - 1));// 这里的move与上述insert函数中的优化同理
			}
			*newPos = T(std::forward<Args>(args)...);// 这里也要注意,千万不能继续使用pos迭代器了,要使用newPos进行更新
			return newPos;
		}

		template<class ...Args>
		void emplace_back(Args&& ...args)
		{
			emplace(iterator(_finish, _start.get(), _finish), std::forward<Args>(args)...);
		}

		iterator erase(iterator pos)
		{
			assert(size());
			for(auto it = pos; it != --end(); ++it)// 条件为!= --end(),原因是下面的循环逻辑中要it + 1,若end - 1时还进行,就会造成end的越界访问
			{
				*it = std::move(*(it + 1));// 与上述insert的move同理,均为右值调用移动赋值的性能优化
			}
			--_finish;
			return pos;// 由于erase操作并未发生扩容/缩容,且真正的操作不过是pos后的所有元素前移,所以只要返回pos就可以,当然,为了STL的一致性,也可以重新构建一个新迭代器,两种方式都是安全的
		}

		void pop_back()
		{
			erase(iterator(_finish - 1, _start.get(), _finish));// 这里要注意,此处的迭代器第一个参数应为finish - 1,而非finish(因为finish是end),否则会触发断言
		}

		void swap(MyVector<T>& other)
		{
			_start.swap(other._start);// 虽然unique不支持拷贝赋值,但却支持swap函数,可直接使用
			std::swap(_finish, other._finish);
			std::swap(_end_of_storage, other._end_of_storage);
		}

		size_t capacity() const 
		{
			return _end_of_storage - _start.get();
		}

		size_t size() const 
		{
			return _finish - _start.get();
		}

		bool empty() const
		{
			return size() == 0;
		}

		// 为方便容器使用,特殊设计了find函数,但由于是vector,所以查找效率很低,只有O(n),且行为是从左到右找出第一个匹配value的值,且由于vector本身没有去重的功能,所以返回类型索性设直接设计为了MyVector<>类型,便于出现重复数据时的查询,但问题也接踵而来,<T>还是<iterator>抑或是<size_t>(索引)呢?思来想去,<T>虽然只会深拷贝匹配的节点,但也是有可能对整个结构进行拷贝的,这开销就太大了,而使用<iterator>也确实存在后续迭代器失效的问题,不过按照我的使用,会加以注意,且正常STL的查询不都是返回迭代器吗,而对于第三种返回索引的方案,其实是效率最高的,但是使用会较为复杂,因此,最终选择了<iterator>的方案
		MyVector<iterator> find(const T& value)
		{
			MyVector<iterator> ret;
			for(size_t i = 0; i < size(); ++i)
			{
				if((*this)[i] == value)
				{
					ret.emplace_back(iterator(_start.get() + i, _start.get(), _finish));
				}
			}
			return ret;
		}

		MyVector<const_iterator> find(const T& value) const 
		{
			MyVector<const_iterator> ret;
			for(size_t i = 0; i < size(); ++i)
			{
				if((*this)[i] == value)
				{
					ret.emplace_back(const_iterator(_start.get() + i, _start.get(), _finish));
				}
			}
			return ret;
		}

	private:
		std::unique_ptr<T[]> _start = nullptr;
		T* _finish = nullptr;
		T* _end_of_storage = nullptr;
	};
	
	template<class T>
	void print(const MyVector<T>& v)
	{
		std::cout << v << "\n";
	}

	template<class U> 
	std::ostream& operator<<(std::ostream& os, const MyVector<U>& v)
	{
		for(const auto& e : v)
		{
			os << e;
		}
		return os;
	}
}

航空订票系统实验需求

航空订票系统源码

头文件:

cpp 复制代码
// 注:该项目为学校的实验作业,要求是练习线性表的增删查改,所以查询时也是使用了MyVector,按道理来说最好还是存储到hash中,查询效率会高很多,另外,像是乘客姓名进行查找这些,也是学校要求,按道理来说肯定要用身份证号查询的,因此,该程序仅为练习C++11语法使用,具体业务逻辑不推荐模仿
#pragma once
#include <iostream>
#include <string>
#include <queue>
#include "beginner_vector_C++11.h"

namespace dzh
{
	enum STATUS
	{
		EMPTY = 0,
		FILL = 1
	};

	struct Flight
	{
		//friend std::istream& operator>>(std::istream& is, Flight& infor);// 这里>>无需访问类的私有成员,所以无需设置友元,但值得一提的是,当声明定义文件分离 + 声明友元函数时,最好还要在类外部进行一次函数声明 
		Flight() = default;// 创建默认构造
		Flight(std::string flightId, std::string departureCity, std::string arrivalCity, std::string departureTime, std::string arrivalTime, int totalSeats);

		// 设置==重载,用于判断出发城市和抵达城市对应的航班(其实应该用航班号的,但这个是题目要求)
		bool operator==(const Flight& other);
		
		std::string _flightId;      // 航班号,如 "BJ001"
		std::string _departureCity; // 出发城市
		std::string _arrivalCity;   // 抵达城市
		std::string _departureTime; // 起飞时间
		std::string _arrivalTime;   // 降落时间
		int _totalSeats;            // 总座位数
		int _availableSeats;        // 剩余座位数
		MyVector<STATUS> _seatStatus; // 座位状态数组 (0=空, 1=已订)
		
		bool empty()
		{
			return _availableSeats == 0;
		}

		// 售票及退票需求处理
		int assignSeat();// 分配一个座位
		bool refundSeat(int seatNumber);// 释放一个座位(返回值建议为bool类型,判断输入是否为有效座位号)
										
		private:
		// 使用单变量 + 优先级队列的思路实现近似常数级别消耗的售票和退票需求(具体见同目录.log文档)
		int _nextNormalSeat = 0;
		std::priority_queue<int, std::vector<int>, std::greater<int>> _refundedSeats;// 按照需求,此处应为小堆,因此,需要给出三个模板参数
	};

	struct Passenger
	{
		Passenger() = default;
		Passenger(std::string passengerName, std::string bookedrFilghtId, int seatNumber);

		// 此重载用于完成题目要求中的通过姓名区分乘客信息
		bool operator==(const Passenger& other);

		std::string _passengerName;    // 乘客姓名
		std::string _bookedrFlightId;  // 已订航班号
		int _seatNumber;		  // 座位号
	};

	struct PassengerInfo// 用于题目中要求的通过客户姓名查出客户信息,由于仅仅比passenger类的信息多了一个起降时间,所以先开始准备练习一下继承来降低冗余度,不过按道理来说,更推荐使用组合,所以这里就先使用组合了
	{
		PassengerInfo() = default;
		PassengerInfo(MyVector<Passenger>::iterator pit, const std::string& departureTime, const std::string& arrivalTime);

		MyVector<Passenger>::iterator _pit;// 为了避免查询时需要深拷贝Passenger也为了和find系列函数返回迭代器保持一致性,这里的成员变量类型选择了迭代器类型
		std::string _departureTime; // 起飞时间
		std::string _arrivalTime;   // 降落时间
	};
	
	// 流输出重载函数
	std::ostream& operator<<(std::ostream& os, const Flight& infor);
	std::ostream& operator<<(std::ostream& os, const Passenger& infor);
	std::ostream& operator<<(std::ostream& os, const PassengerInfo& infor);
	std::istream& operator>>(std::istream& is, Flight& infor);
	std::istream& operator>>(std::istream& is, Passenger& infor);

	class FlightBookingSystem
	{
	public:
		FlightBookingSystem() = default;
		FlightBookingSystem(const MyVector<Flight>& flights, const MyVector<Passenger>& passengers);

		MyVector<MyVector<Flight>::iterator> queryFlights(std::string departureCity, std::string arrivalCity); // 根据出发城市和抵达城市查询航班信息
		MyVector<PassengerInfo> queryPassenger(const std::string& name); // 根据乘客姓名查询乘客信息
		void addFlight(const Flight& flight);           // 添加航班
	    void bookTicket(const std::string& name, const std::string& flightId);          // 订票
   	    void refundTicket(const std::string& name);        // 退票
		void print();
	private:
		MyVector<Flight> _flights;
		MyVector<Passenger> _passengers;

		// 售票退票的子函数,只返回bool,不输出cout信息
	    bool _bookTicket(const std::string& name, const std::string& flightId);          // 订票
   	    bool _refundTicket(const std::string& name);        // 退票
	};

	void testf();
	void tests();
}

cpp文件:

cpp 复制代码
#include <unordered_map>
#include <functional>
#include <sstream>
#include <limits>
#include "system.h"

namespace dzh
{
	Flight::Flight(std::string flightId, std::string departureCity, std::string arrivalCity, std::string departureTime, std::string arrivalTime, int totalSeats)
		:_flightId(flightId)
		,_departureCity(departureCity)
		,_arrivalCity(arrivalCity)
		,_departureTime(departureTime)
		,_arrivalTime(arrivalTime)
		,_totalSeats(totalSeats)
		,_availableSeats(totalSeats)
		,_seatStatus(MyVector<STATUS>(totalSeats <= 0 ? 4 : totalSeats, dzh::EMPTY))
		,_nextNormalSeat(0)
	{
	}

	Passenger::Passenger(std::string passengerName, std::string bookedrFlightId, int seatNumber)
		:_passengerName(passengerName)
		,_bookedrFlightId(bookedrFlightId)
		,_seatNumber(seatNumber)
	{
	}

	PassengerInfo::PassengerInfo(MyVector<Passenger>::iterator pit, const std::string& departureTime, const std::string& arrivalTime)
		:_pit(pit)
		,_departureTime(departureTime)
		,_arrivalTime(arrivalTime)
	{
	}

	bool Flight::operator==(const Flight& other)
	{
		return (other._departureCity == _departureCity && other._arrivalCity == _arrivalCity) || other._flightId == _flightId;// 这里写了两个判断条件,目的均为了find函数的逻辑,由于实验要求找出所有出发城市和抵达城市相同的航班,也就有了第一条判断,而又由于要求通过乘客信息确定起飞和降落时间,也就需要能通过航班号进行判断
	}

	bool Passenger::operator==(const Passenger& other)
	{
		return other._passengerName == _passengerName;
	}

	std::ostream& operator<<(std::ostream& os, const Flight& infor)
	{
		os << "航班号:" << infor._flightId << " 出发城市:" << infor._departureCity << " 抵达城市:" << infor._arrivalCity << " 起飞时间:" << infor._departureTime << " 降落时间:" << infor._arrivalTime << " 总座位数:" << infor._totalSeats << " 剩余座位数:" << infor._availableSeats <</* " 座位状态数组:" << infor._seatStatus <<*/ "\n";
		return os;
	}

	std::istream& operator>>(std::istream& is, Flight& infor)
	{
		is >> infor._flightId >> infor._departureCity >> infor._arrivalCity >> infor._departureTime >>  infor._arrivalTime >>  infor._totalSeats;
		infor._availableSeats = infor._totalSeats;
		infor._seatStatus = MyVector<STATUS>(infor._totalSeats, dzh::EMPTY);
		return is;
	}

	std::ostream& operator<<(std::ostream& os, const Passenger& infor)
	{
		os << "乘客姓名:" << infor._passengerName << " 已订航班号:" << infor._bookedrFlightId << " 座位号:" << infor._seatNumber << "\n";
		return os;
	}

	std::istream& operator>>(std::istream& is, Passenger& infor)
	{
		is >> infor._passengerName >> infor._bookedrFlightId;
		return is;
	}

	std::ostream& operator<<(std::ostream& os, const PassengerInfo& infor)
	{
		os << *(infor._pit) << " 起飞时间:" << infor._departureTime << " 降落时间:" << infor._arrivalTime << "\n";
		return os;
	}
	

	// 分配一个座位
	int Flight::assignSeat()// 存在由于无空座导致分配失败的问题,但可以凭借上层调用来筛选,也可以将该函数的返回类型设置为pair,不过这里选择了最简单的assert
	{
		assert(_availableSeats);
		int nextSeat = 0;
		// 若优先级队列为空,则说明当前_nextNormalSeat即为所求,判断该航班是否满员,若未满,直接返回即可
		if(_refundedSeats.empty())
		{
			if(_nextNormalSeat == _totalSeats)
			{
				return -1;
			}
			nextSeat = _nextNormalSeat;
			++_nextNormalSeat;
		}
		// 若优先级队列不为空,则说明队列的top为所求,返回top即可
		else
		{
			nextSeat = _refundedSeats.top();
			_refundedSeats.pop();
		}
		_seatStatus[nextSeat] = FILL;
		--_availableSeats;// 记得维护剩余座位数
		return nextSeat;
	}
	// 释放一个座位(返回值建议为bool类型,判断输入是否为有效座位号)
	bool Flight::refundSeat(int seatNumber)
	{
		// 当传入的参数>=size或<0或下标所在位置值为0时,说明为错误数据,直接返回false即可
		if(seatNumber >= _seatStatus.size() || seatNumber < 0 || _seatStatus[seatNumber] == EMPTY)
		{
			return false;
		}
		// 当数据正确,则进行相应处理
		else
		{
			_seatStatus[seatNumber] = EMPTY;
			_refundedSeats.emplace(seatNumber);
			++_availableSeats;
			return true;
		}
	}
	FlightBookingSystem::FlightBookingSystem(const MyVector<Flight>& flights, const MyVector<Passenger>& passengers)
		:_flights(flights)
	{
		// 这里要注意,参数中拿到的passengers是为了构造函数中订票做准备的,由于订票函数会自动将新数据插入_passengers,所以上面不要用参数直接进行初始化列表
		for(const auto& e : passengers)
		{
			bookTicket(e._passengerName, e._bookedrFlightId);
		}
	}

	MyVector<MyVector<Flight>::iterator> FlightBookingSystem::queryFlights(std::string departureCity, std::string arrivalCity)// 这里再看真的会迟疑一会,本质是类中成员变量_flights调用了_flights自身的find函数,而find函数内部为了避免批量深拷贝符合要求的数据,返回的其实是MyVector<迭代器>,那么迭代器的类型自然就是MyVector<Flight>::iterator,简而言之,就是在vector的某个函数中返回了vector的迭代器数组,也就是在类模板内部就使用了类模板,其实也没有那么奇怪,只要经过了构造函数,那对象本身就是完备的,其他的成员函数不过是方法而已
	{
		return _flights.find(Flight("", departureCity, arrivalCity, "", "", -1));// 由于MyVector的find函数中会调用==重载进行比较判断,而根据上面设计的==重载逻辑,传递判断用的对象时,仅需初始化出发和抵达城市或航班号即可,其他信息不会影响==的判断
	}

	MyVector<PassengerInfo> FlightBookingSystem::queryPassenger(const std::string& name)
	{
		MyVector<MyVector<Passenger>::iterator> fit = _passengers.find(Passenger(name, " ", -1));
		MyVector<PassengerInfo> ret;
		for(const auto& e : fit)
		{
			auto vit = _flights.find(Flight(e->_bookedrFlightId, "", "", "", "", -1));
			// 判定查找是否成功,若查找失败,则直接continue即可,避免解引用end()迭代器
			if(vit.empty())
			{
				continue;
			}
			ret.emplace_back(PassengerInfo(e, vit[0]->_departureTime, vit[0]->_arrivalTime));// 由于这里是依靠航班号进行find函数的查找的,所以仅会查出一个结果,该结果被存放在v[0]处,因此,直接v[0]进行迭代器的访问即可
		}

		return ret; 
	}

	void FlightBookingSystem::addFlight(const Flight& flight)
	{
		_flights.emplace_back(flight);
	}


	void FlightBookingSystem::bookTicket(const std::string& name, const std::string& flightId)          // 订票
	{
		if(_bookTicket(name, flightId))
		{
			std::cout << "售票成功!" << "\n";
		}
		else
		{
			std::cout << "售票失败!" << "\n";
		}
	}

	void FlightBookingSystem::refundTicket(const std::string& name)        // 退票
	{
		if(_refundTicket(name))
		{
			std::cout << "退票成功!" << "\n";
		}
		else
		{
			std::cout << "退票失败!" << "\n";
		}
	}	

	bool FlightBookingSystem::_bookTicket(const std::string& name, const std::string& flightId)
	{	
		auto vit = _flights.find(Flight(flightId, "", "", "", "", -1));
		// 当未查到该航班(返回的迭代器数组为空)或当前航班已满座时返回false
		if(vit.empty() || (vit[0]->_availableSeats == 0))
		{
			return false;
		}
		// 满足条件时,将客人入vector
		else
		{
			int seat = vit[0]->assignSeat();
			_passengers.emplace_back(Passenger(name, flightId, seat));
			return true;
		}
		
	}

   	bool FlightBookingSystem::_refundTicket(const std::string& name)
	{
		auto vit = _passengers.find(Passenger(name, "", -1));
		if(vit.empty())
		{
			return false;
		}

		for(auto& e : vit)
		{
			auto fit = _flights.find(Flight(e->_bookedrFlightId, "", "", "", "", -1));
			if(fit.empty())
			{
				return false;
			}
			else
			{

				if(!fit[0]->refundSeat(e->_seatNumber))// refundSeat函数中会检验座位号是否正确,如打算降低耦合度,这里也可以检验一次,不过相应的代码冗余度就会提高,这里就不单独检验了
				{
					return false;// 若refundSeat函数返回false,则说明退票失败
				}
				_passengers.erase(e);
			}
		}
		return true;
	}

	void FlightBookingSystem::print()
	{
		std::cout << _flights << "\n" << _passengers << "\n";
	}

	void testf()
	{	
		// 初始化航班数据
		MyVector<dzh::Flight> initFlights;
		
		// 添加航班信息
		initFlights.emplace_back("CA1234", "北京", "上海", "08:00", "10:30", 150);
		initFlights.emplace_back("MU5678", "上海", "广州", "14:00", "16:30", 200);
		initFlights.emplace_back("CZ9012", "北京", "深圳", "09:30", "12:00", 180);
		initFlights.emplace_back("HU3456", "成都", "西安", "11:00", "12:30", 120);
		initFlights.emplace_back("MF7890", "厦门", "杭州", "13:15", "14:45", 1);

		// 初始化乘客数据
		MyVector<dzh::Passenger> initPassengers;
		
		// 添加乘客信息(姓名,已订航班号,座位号)
		initPassengers.emplace_back("张三", "CA1234", 15);
		initPassengers.emplace_back("李四", "MU5678", 28);
		initPassengers.emplace_back("王五", "CA1234", 32);
		initPassengers.emplace_back("赵六", "CZ9012", 5);
		initPassengers.emplace_back("钱七", "HU3456", 78);

		FlightBookingSystem fbs(initFlights, initPassengers);
		fbs.print();

		// 第1组:常规预订
		fbs.bookTicket("孙八", "MU5678");
		fbs.bookTicket("周九", "CZ9012");
		fbs.bookTicket("吴十", "HU3456");
		fbs.bookTicket("郑十一", "MF7890");
		fbs.bookTicket("王十二", "CA1234");
		
		// 第2组:同一航班多乘客测试
		fbs.bookTicket("测试乘客A", "CA1234");
		fbs.bookTicket("测试乘客B", "CA1234");
		fbs.bookTicket("测试乘客C", "CA1234");
		
		// 第3组:不同航班测试
		fbs.bookTicket("外籍乘客", "MU5678");
		fbs.bookTicket("VIP客户", "CZ9012");
		fbs.bookTicket("普通乘客", "HU3456");
		
		// 第4组:边界测试
		fbs.bookTicket("最后一位", "MF7890");  // 测试满座前最后一个座位
		fbs.bookTicket("重复乘客", "CA1234");  // 测试同名乘客但不同航班
		fbs.bookTicket("最后一位", "MF7890");  // 测试满座前最后一个座位
		fbs.addFlight(Flight("MF7790", "厦门", "杭州", "13:15", "14:45", 1));
		fbs.print();
	}

	void tests()
	{
		std::string data;
		std::cout << "接下来请输入初始化系统需要的数据(输入多行,每行是一个航班的数据,输入END结束)" << "\n";
		MyVector<dzh::Flight> initFlights;
		MyVector<dzh::Passenger> initPassengers;
		std::cout << "请输入航班信息:(格式为航班号 出发地 目的地 出发时间 到达时间 座位总数)" << "\n";
		Flight initFlight;
		while(true)
		{
			getline(std::cin, data);
			std::stringstream ss(data);
			if(data == "END" || data == "")
			{
				break;
			}
			ss >> initFlight;
			initFlights.emplace_back(initFlight);
		}
		data.clear();
		std::cin.clear();
		std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');		
		std::cout << "接下来请输入初始化系统需要的数据(输入多行,每行是一个航班的数据,输入END结束)" << "\n";
		std::cout << "请输入客户信息:(格式为姓名 航班号)" << "\n";
		Passenger initPassenger;
		while(true)
		{
			getline(std::cin, data);
			std::stringstream ss(data);
			if(data == "END" || data == "")
			{
				break;
			}
			ss >> initPassenger;
			initPassengers.emplace_back(initPassenger);
		}
		
		FlightBookingSystem fbs(initFlights, initPassengers);

		std::unordered_map<int, std::function<void()>> um;
		um[1] = [&fbs](){
			fbs.print();
		};

		um[2] = [&fbs](){
			std::string departureCity;
			std::string arrivalCity;
			std::cout << "请输入出发城市和抵达城市:\n";
			std::cin >> departureCity;
			std::cin >> arrivalCity;
			auto vit = fbs.queryFlights(departureCity, arrivalCity);
			if(vit.empty())
			{
				std::cout << "查找失败!" << "\n";
				return;
			}
			for(const auto& e : vit)
			{
				std::cout << *e << "\n";
			}
		};
		
		um[3] = [&fbs](){
			std::cout << "请输入乘客的姓名:\n";
			std::string name;
			std::cin >> name;
			auto vit = fbs.queryPassenger(name);
			if(vit.empty())
			{
				std::cout << "查找失败!" << "\n";
				return;
			}
			for(const auto& e : vit)
			{
				std::cout << e << "\n";
			}
		};
		
		um[4] = [&fbs](){
			std::cout << "请输入航班信息:(格式为航班号 出发地 目的地 出发时间 到达时间 座位总数)" << "\n";
			Flight newFlight;
			std::cin >> newFlight;		
			fbs.addFlight(newFlight);
		};
		
		um[5] = [&fbs](){
			std::cout << "请输入客户信息:(格式为姓名 航班号)" << "\n";
			Passenger newPassenger;
			std::cin >> newPassenger;
			fbs.bookTicket(newPassenger._passengerName, newPassenger._bookedrFlightId);
		};

		um[6] = [&fbs](){
			std::cout << "请输入乘客的姓名:\n";
			std::string name;
			std::cin >> name;
			fbs.refundTicket(name);
		};
		
		auto display = [](){
			std::cout << "0. 退出小程序\n";
			std::cout << "1. 打印全部信息\n";
			std::cout << "2. 根据出发城市和抵达城市查询航班信息\n";
			std::cout << "3. 根据客人姓名查询其航班号以及座位号\n";
			std::cout << "4. 添加新航班\n";
			std::cout << "5. 订票\n";
			std::cout << "6. 退票\n";
		};
		int choice = 0;
		do{
			display();
			std::cin >> choice;
			if(um.count(choice) != 0)
			{
				um[choice]();
			}
			else if(choice == 0)
			{
				std::cout << "已退出程序!\n";
				break;
			}
			else
			{
				std::cout << "输入错误,请重新输入!\n";
			}
		}while(choice != 0);
	}
}

收获与总结

以下是在本次代码编写过程中遇到的问题及解决办法与思路总结:

  1. 首先,遇到的第一个问题是,vector实现时,私有成员使用了三个指针,我困惑于智能指针不支持+x操作,但其实可以通过get函数获取智能指针中的原始指针
  2. 其次,我直接将三个指针都定义为了unique_ptr,这是大错特错的,原因是,智能指针的功能是管理堆区空间,使用三个智能指针指向同一空间不同位置的操作,会导致智能指针类析构(delete)时造成错误,其实这里仅需将_start设置为unique_ptr,其他两指针设计为T*即可
  3. 智能指针中,shared_ptr是支持指针拷贝的,unique_ptr是不支持拷贝的,在vector的模拟实现中,由于不会发生浅拷贝,也就不会用到指针的拷贝,因此,_start推荐使用unique指针
  4. 注意unique智能指针仅支持移动赋值,想要重置一个unique智能指针中变量的推荐方式有两种,第一种是使用reset函数,该函数可以接收裸指针和unique指针类型,第二种是使用移动赋值
  5. 在声明友元类模板时,遇到了模板参数遮蔽的问题(shadowing->遮蔽),即"在外层类模板中使用T作为模板参数,而内层进行友元模板的声明时,使用了template",正确做法是,若需要改为所有种类的内部都可以是外部类的友元,就将模板参数换为其他(如template <class U>),若仅需要内外部T均为同类型时,可使用friend class 类名<T>进行特定类型的友元设置
  6. 莫名出现了在私有成员写到类模板最后时,编译时,报构造函数找不到初始化列表中私有成员的错误,真正的原因是构造函数的初始化列表中出现了一个小括号不匹配,更改以后,私有放到后面也没问题了,需要说明的是,私有/成员变量具体是放到前面还是后面并没有硬性规定
  7. 使用move函数对insert,emplace以及erase函数的for循环中的=操作进行了性能优化,原理是,移动这个行为对于内置类型的确就是将一块内存的二进制数据复制到另一内存,但对于自定义类型而言,其实这个行为是会调用赋值重载的,而由于for的操作本身就是覆盖,也就是说,源数据完全没用了,因此,可以将源数据设置为右值,直接调用移动赋值,进一步提升效率,其实这里可以发现一个不太容易注意到的点,即,容器中存储的所有元素均为左值(因为容器中存储的它们都是可以取到地址的)
  8. 根据题目要求,查询二需要根据乘客姓名查询相应的信息(客户姓名,航班号,座位号,起降时间),可以发现,题目所要求的查询信息其实就是客户信息多了个起降时间,那创建这个类时,为了降低代码冗余度,就可以采取继承或组合的方式了,按照在此处的情况,推荐使用组合,降低耦合度
  9. 然后关于类模板的<<重载创建:首先,对于类的<<的创建,需要将该<<重载设置为类的友元,以可以访问类的私有成员,对于类模板当然也是如此,关键是,对于类模板,<<重载进行友元声明时,不再是声明<<重载函数了,而是必须声明为<<重载函数模板,原因是:友元的本质就是获取访问私有成员的权限,而类是否为模板其实和友元没有任何关系,因此,想要确保<<重载能够作用于类模板的所有实例,<<重载就也必须写成函数模板的形式
  10. 为方便容器使用,特殊设计了find函数,但由于是vector,所以查找效率很低,只有O(n),且行为是从左到右找出第一个匹配value的值,且由于vector本身没有去重的功能,所以返回类型索性设直接设计为了MyVector<>类型,便于出现重复数据时的查询,但问题也接踵而来,还是抑或是<size_t>(索引)呢?思来想去,虽然只会深拷贝匹配的节点,但也是有可能对整个结构进行拷贝的,这开销就太大了,而使用也确实存在后续迭代器失效的问题,不过按照我的使用,会加以注意,且正常STL的查询不都是返回迭代器吗,而对于第三种返回索引的方案,其实是效率最高的,但是使用会较为复杂,因此,最终选择了的方案
  11. 再次出现了const迭代器问题,不过这次解决的很快,原因是忘记给迭代器类模板中的函数使用const修饰,导致const类型的this调用不了这些函数,加上修饰后大部分问题都自动解决了
  12. 注意,指针与指针之间虽然可以使用-运算,但不能进行+运算,会导致编译报错!(这里的错误源自拷贝构造函数计算_finish时,直接使用三个指针进行了计算,后两个参数没有使用小括号,导致出现了指针 + 指针)
  13. 对于迭代器类模板,建议通过default关键字使编译器自动生成默认构造,避免在后续使用该迭代器作为某个类的成员变量且需要默认构造时没有,继而引发MyVector的默认构造申请capacity个空间时自动调用成员变量的默认构造引发缺少该函数的错误
  14. 关于售票和退票需求,实验要求每次售票将编号最小的座位号安排出去,而会影响这个售票需求的其实就是退票操作了,若仅仅设置一个变量用于指向当前的第一个空座下标,那若该下标前面的座位被退了,按道理来说下次售票应该安排前面被退的座位(下标更小) ,因此,单单使用一个变量存储当前被分配的下标并进行线性查找的思路很显然不行,那么,也就想到了优先级队列,只要使用优先级队列存储所有的空位,就能O(1)找出所求座位,不过单个变量排序时需要O(logn),但其实,因为正常情况下飞机不会乱退票的(有违约金),也就是说,退票的人其实是常数级别,因此可以设置优先级队列储存退票的人,再设置一个变量存储正常售票的剩余座位,这样,售票时,当优先级队列空时,就售出当前变量所表示的下标,优先级队列不空就售出优先级队列的top,这样优化,时间复杂度就从原来的O(nlogn)优化为了O(1),而空间复杂度也从原来的O(n)变为了O(1)
  15. 出现了堆区申请失败的问题,还好使用了异常捕获机制,迅速找到了问题所在,原因是为了优化Flight的构造函数,我缩减了参数个数,直接使用总座位数作为状态数组申请的个数,而find函数中,参数需要为Flight对象,但比较时仅需比较特定的几个成员变量,因此,我直接传递了匿名对象,座位总数干脆直接给了-1,然后-1转换为size_t类型作为capacity进行空间申请,这才导致更新构造函数后为状态数组申请了极大空间造成申请失败,解决方案是在Flight的构造函数的初始化列表中使用三元运算符检验负数部分问题都自动解决了`
相关推荐
云泽8081 小时前
C++ 模板进阶全解析:非类型模板参数、模板特化与分离编译详解
开发语言·c++
沐知全栈开发1 小时前
FastAPI 安装指南
开发语言
2501_930707782 小时前
使用C#代码在 Word 中删除页眉或页脚
开发语言·c#·word
坚持学习前端日记2 小时前
后台管理系统文档
java·开发语言·windows·spring boot·python·spring
凯哥Java2 小时前
MaxKB4J:基于Java的高效知识库问答系统与工作流智能解决方案
java·开发语言
我是小疯子662 小时前
C++ODB实战指南:高效ORM开发
c++
悟能不能悟2 小时前
Postman Pre-request Script 详细讲解与高级技巧
java·开发语言·前端
txinyu的博客2 小时前
Reactor 模型全解析
java·linux·开发语言·c++
IMPYLH2 小时前
Lua 的 Package 模块
java·开发语言·笔记·后端·junit·游戏引擎·lua