我有一个ToDo List项目,使用链表作为其数据结构。我在头文件的addTask((中遇到问题,行为:
if (head != NULL)
{
current = head;
while (current->link != NULL)
{
current = current->link; //error happens here
}
current->link = n; //another error here
}
- '='无法从'list*'转换为'list::taskPTR'
- "="无法从"list::task*"转换为"list*">
^^^是发生的错误。我不明白我的错误以及如何修复它。
这是完整的头文件:
#ifndef linkedList
#define linkedList
#include <iostream>
using namespace std;
class list
{
private:
struct task
{
string taskDue;
string taskDesc;
string taskCourse;
string day;
string mon;
string year;
list* link;
};
typedef struct task* taskPtr;
taskPtr head;
taskPtr current;
taskPtr temp;
public:
list();
~list();
void printByDue();
void printByCourse();
void printByStatus();
void addTask(string addTaskDue, string addTaskDesc, string addTaskCourse, string addDay, string addMon, string addYear);
void delTask(string delTaskDue, string delTaskDesc, string delTaskCourse, string delDay, string delMon, string delYear);
};
list::list() //constructor
{
head = NULL;
current = NULL;
temp = NULL;
}
void list::addTask(string addTaskDue, string addTaskDesc, string addTaskCourse, string addDay, string addMon, string addYear)
{
struct task* n = new task;
n->link = NULL;
n->taskDue = addTaskDue;
n->taskDesc = addTaskDesc;
n->taskCourse = addTaskCourse;
n->day = addDay;
n->mon = addMon;
n->year = addYear;
if (head != NULL)
{
current = head;
while (current->link != NULL)
{
current = current->link;
}
current->link = n;
}
else
{
head = n;
}
}
void menu()
{
char option;
cout << " Welcom to the ToDo List Program by ACGR";
cout << "n" << endl;
cout << "What would you like to do? n" << endl;
cout << "t[a]dd a new task" << endl;
cout << "t[c]omplete a task" << endl;
cout << "t[d]isplay tasks" << endl;
cout << "t[q]uit" << endl;
cout << "nEnter choice here: ";
}
#endif // !linkedList.h
有很多东西需要解压,但可能的简单修复方法是更改以下行:
list* link;
// Should be
task* link;
你的任务不应该指向列表(它们在列表中(,它们应该指向列表中的下一个任务。
我要说的是,所有这些可能会揭示更多的错误,包括逻辑和语法错误。
但是,如果让您带着该代码上路,我会感到很难过。你发布的内容不太好,原因有很多。我本以为,在分配链表时,我要展示的几乎所有东西都已经教过了;它在我的课程中。
以下是对发布内容的迷你代码审查:
#include <iostream>
// Where is <string> ? Include what you use.
using namespace std; // Bad practice, **especially** in a header
class list
{
private:
struct task // A task has nothing to do with a list
{
string taskDue;
string taskDesc;
string taskCourse;
string day;
string mon;
string year;
list* link; // This has nothing to do with a task.
// And now you've forced yourself to include Rule of 5 in your task.
};
typedef struct task* taskPtr; // Subjective; I prefer `using taskPtr = task*;`
// More subjective; I wouldn't do this at all.
// taskPtr is longer than task*, I fail to see the point.
taskPtr head; // Prefer default member initialization.
taskPtr current; // Likely part of the todo list, which has no bearing on a list data structure
taskPtr temp; // ??? Why should a temp get permenant residence as class data?
public:
list();
~list();
void printByDue(); // Should be getters. Let the caller print if and how they want.
void printByCourse();
void printByStatus();
// If the task and list were separate from each other, this could be simplified quite a bit.
void addTask(string addTaskDue, string addTaskDesc, string addTaskCourse, string addDay, string addMon, string addYear);
void delTask(string delTaskDue, string delTaskDesc, string delTaskCourse, string delDay, string delMon, string delYear);
};
// Use the initializaton section of constructors, or better yet default member initialization.
list::list() //constructor
{
head = NULL;
current = NULL;
temp = NULL;
}
//
// Where is the destructor? This shouldn't be compiling at all
//
void list::addTask(string addTaskDue, string addTaskDesc, string addTaskCourse, string addDay, string addMon, string addYear)
{
struct task* n = new task;
n->link = NULL;
n->taskDue = addTaskDue;
n->taskDesc = addTaskDesc;
n->taskCourse = addTaskCourse;
n->day = addDay;
n->mon = addMon;
n->year = addYear;
if (head != NULL)
{
current = head;
while (current->link != NULL)
{
current = current->link;
}
current->link = n;
}
else
{
head = n;
}
}
// This function has no business being here at all.
void menu()
{
char option;
cout << " Welcom to the ToDo List Program by ACGR";
cout << "n" << endl;
cout << "What would you like to do? n" << endl;
cout << "t[a]dd a new task" << endl;
cout << "t[c]omplete a task" << endl;
cout << "t[d]isplay tasks" << endl;
cout << "t[q]uit" << endl;
cout << "nEnter choice here: ";
}
对你帮助最大的是把你的顾虑分开。待办事项列表和链接列表是独立的实体,应按此处理。链表使用节点以列表类型的方式将数据链接在一起。待办事项列表是一个任务列表,虽然我在那里使用了单词列表,但我可以使用数组、堆栈、队列甚至哈希图来编写待办事项列表。根据列表所需的功能,底层数据结构可能非常灵活。
因此,写下链接列表,然后将其合并到待办事项列表中。这使得测试更容易,集中代码,而且是更好的实践。
对于这项任务,最难的部分是列表。这里有一个快速而肮脏的:
#ifndef SINGLY_LINKED_LIST
#define SINGLY_LINKED_LIST
#include <utility> // std::swap
template <typename T>
class SList {
public:
SList() = default;
SList(const SList& other);
SList(SList&& other) noexcept;
~SList();
void add(T val);
void erase(T val);
// Employing copy-swap idiom; that's why the parameter is taken by value
SList& operator=(SList rhs);
friend void swap<>(SList<T>& lhs, SList<T>& rhs);
// This function is for convenience and demonstration; I don't want to be
// bothered implementing the minimal iterator required for a range-based
// for loop.
void print() const;
private:
struct Node {
T data;
Node* next = nullptr;
Node(T d) : data(d) {}
};
Node* head = nullptr;
Node* tail = nullptr;
// Helper
std::pair<Node*, Node*> find_node_and_before(T val);
};
// Implementation
template <typename T>
SList<T>::SList(const SList& other) {
Node* walker = other.head;
while (walker) {
add(walker->data);
walker = walker->next;
}
}
template <typename T>
SList<T>::SList(SList&& other) noexcept : SList() {
swap(*this, other);
}
template <typename T>
SList<T>::~SList() {
while (head) {
Node* tmp = head;
head = head->next;
delete tmp;
}
tail = nullptr;
}
template <typename T>
void SList<T>::add(T val) {
if (!head) {
head = new Node(val);
tail = head;
return;
}
tail->next = new Node(val);
tail = tail->next;
}
template <typename T>
void SList<T>::erase(T val) {
auto toBeErased = find_node_and_before(val);
if (toBeErased.second == head) {
Node* tmp = head;
head = head->next;
delete tmp;
return;
}
(toBeErased.first)->next = (toBeErased.second)->next;
if (toBeErased.second == tail) {
tail = toBeErased.first;
}
delete toBeErased.second;
}
template <typename T>
SList<T>& SList<T>::operator=(SList rhs) {
swap(*this, rhs);
return *this;
}
template <typename T>
void swap(SList<T>& lhs, SList<T>& rhs) {
using std::swap;
swap(lhs.head, rhs.head);
swap(lhs.tail, rhs.tail);
}
template <typename T>
void SList<T>::print() const {
Node* walker = head;
while (walker) {
std::cout << walker->data << ' ';
walker = walker->next;
}
std::cout << 'n';
}
template <typename T>
std::pair<typename SList<T>::Node*, typename SList<T>::Node*>
SList<T>::find_node_and_before(T val) {
if (head->data == val) {
return {nullptr, head};
}
Node* walker = head;
while (walker->next) {
if (walker->next->data == val) {
return {walker, walker->next};
}
walker = walker->next;
}
return {nullptr, nullptr};
}
#endif
请注意,待办事项列表或其中的任务无关。它只是一个链表。它可以是SList<int>
、SList<std::string>
或任何其他类型。
其他类型也有依赖关系,比如operator<<()
,但这主要是由于print((函数通常根本不存在。它作为一个黑客来展示列表的工作原理。
我把一个超级简单的";任务列表";也
#include <iostream>
#include <string>
#include "slist.hpp"
struct task {
std::string name;
task() = default;
task(const char* t) : name(t) {}
};
bool operator==(const task& lhs, const task& rhs) {
return lhs.name == rhs.name;
}
std::ostream& operator<<(std::ostream& sout, const task& t) {
return sout << """ << t.name << """;
}
int main() {
SList<task> one;
one.add("trash");
one.add("dishes");
one.add("walk the dog");
one.add("tidy up");
one.print();
one.erase("dishes");
one.print();
one.erase("tidy up");
one.print();
one.erase("trash");
one.print();
}
输出:
"trash" "dishes" "walk the dog" "tidy up"
"trash" "walk the dog" "tidy up"
"trash" "walk the dog"
"walk the dog"
当任何人都准备好编写链表时,我的代码中应该没有什么是新的。链表是一个";致密的";作业要求学习者能够正确地运用到目前为止所学的几乎每一种技术和原则。我认为他们毕业于;初学者"-级别C++。
缺少并可以添加的两个主要原则包括迭代器和完美转发(用于编写模板函数(。
如果没有自制列表,代码会是什么样子:
#include <chrono>
#include <list>
#include <string>
// using namespace std; <== NO
// a task is just that, it isn't the linked list so should not have any list info in it, like a next pointer
struct task final
{
task() = default;
task(const std::string& new_due, const std::string& new_desc, const& std::string new_course, const std::chrono::system_clock::time_point& new_time) :
due{ new_due },
desc{ new_desc },
course{ new_course },
time{ new_time }
{
}
~task() = default;
task(const task&) = default; // default copyable
task& operator=(const task&) = default; // default copy assignable
task(task&& task) = default; // default movable
task& operator=(task&&) = default; // default move assignable
std::string due; // no need to repeat name of struct in members
std::string desc;
std::string course;
std::chrono::system_clock::time_point time; // use chrono for times
};
// print by due etc will need to be free functions, it is not the task of a task
// to know how to print itself. And if you have a list, you can use std::sort(tasks.begin(),task.end(),sort_function) to sort in any order you like
// before doing a print.
int main()
{
std::list<task> tasks;
// this is how easy it can be to add a task to a list
tasks.emplace_back("new due", "new desc", "new course", std::chrono::system_clock::now()); // will call constructor for task.
}
current
的类型为taskPtr
,current->link
的类型为list*
。不能将一个分配给另一个。一个可能的解决方案是将成员变量link
的类型更改为task*
,如@Fareanor所述。
除非这个项目的目标是学习如何实现自己的链表,否则我强烈建议您避免这样做,而是使用std::list
:
struct task {
string taskDue;
...
string year;
};
std::list<task> tasks;
然后添加一个任务很简单:
tasks.emplace_back(addTaskDue, addTaskDesc, ...);
还要注意,将日期存储为字符串不是很有效。请考虑使用std::chrono
中合适的类型。
C++标准库中有很多有用的东西;花一些时间熟悉它是值得的,这样你就不会一直在重新发明轮子,这样你可以专注于实现todo列表的更高级别的细节。