為什麼寫鏈表的時候總是停止工作?
初學鏈表,寫的時候總是會出現下面的情況。可能是什麼原因呢?PS:不是說只是寫這次而已,而是經常出現這種情況。感謝前輩們的回答~
下面找出一例,代碼寫的很差,大神們見笑求輕噴~原題要求可以輸入七個人的相應信息,並按照生日順序輸出。
#include&
using namespace std;
struct member{
char name[4];
int year;
int month;
member *next;
};void Insert(member* pHead, member* pNode){
if(pHead == NULL){
pHead = pNode;
return;
}
if(pNode-&>year &< pHead-&>year || (pNode-&>year == pHead-&>year pNode-&>month &< pHead-&>month)){
pNode-&>next = pHead;
pHead = pNode;
return;
}
member *p = new member;
member *q = new member;
p = pHead;
q = p-&>next;
while(p-&>next != NULL){
p = q;
if((pNode-&>year &> p-&>year || (pNode-&>year == p-&>year pNode-&>month &> p-&>month))
(pNode-&>year &< p-&>next-&>year || (pNode-&>year == p-&>next-&>year pNode-&>month &<= p-&>next-&>month))){
pNode-&>next = p-&>next;
p-&>next = pNode;
return;
}
q = q-&>next;
}
q-&>next = pNode;
}
void Input(member* head){
member *pNode = new member;
cin &>&> pNode-&>name &>&> pNode-&>year &>&> pNode-&>month;
Insert(head, pNode);
return;
}
void Output(member* head){
member *p = head;
for(int i = 0; i &< 7; i++) cout &<&< p-&>name &<&<" "&<&< p-&>year &<&<"."&<&< p-&>month &<&
看了你的代碼, 我想說, 這代碼這麼複雜, 有什麼難為情的.
-----
昨晚看的倉促,測試的數據不好,沒看出潛在的問題。現更新答案。有兩個地方錯誤:- 第一個: 明確幾個邏輯點,pHead一直指向鏈表的頭,p和q正好是一前一後兩個游標。如果pNode的值正好在p和q之間,則插到中間,return了。如果比q還大,那麼p和q向後平移。循環上面的比較。如果到頭了,表示pNode是最大的了,恰好p代表此刻鏈表最後一個節點,用p指向pNode即可。(游標p,q不需要new出新空間,請注意。)
member *p = pHead;
member *q = p-&>next;
while(q != NULL){
if(pNode-&>year &< q-&>year || (pNode-&>year == q-&>year pNode-&>month &< q-&>month)) {
pNode-&>next = q;
p-&>next = pNode;
return;
}
p = q;
q = q-&>next;
}
p-&>next = pNode;
- 第二個: 你的 OutPut 函數有明顯錯誤, 按代碼看, 只能輸出鏈表的第一節. 試試改成下面:
void Output(member* head){
member *p = head;
for(int i = 0; i &< 7; i++) {
cout &<&< p-&>name &<&<" "&<&< p-&>year &<&<"."&<&< p-&>month &<&
}
}
- 第三個,承蒙 @bombless 指出,就是野指針的問題。需要在Input函數中將pNode-&>next置空。否則在第一個問題代碼中的 while(q != NULL) 的判斷會有問題。明明 q是邏輯上的空指針,但如果你不置空,q 就是不等於 NULL的, 是一個野指針。
void Input(member* head){
member *pNode = new member;
pNode-&>next = NULL; // 這裡要置空,否則出現野指針。
cin &>&> pNode-&>name &>&> pNode-&>year &>&> pNode-&>month;
Insert(head, pNode);
return;
}
member *t = head;
建議刪掉.
完整代碼請見: 回答問題 http://www.zhihu.com/question/26165061 (已更新)輸出結果, 重新截圖:學習鏈表, 有個基本的方法就是畫圖, 把鏈表的結構畫出來. 然後每一個指針指向哪裡, 如何變化, 自己推演一遍. 多試幾次, 就不會覺得麻煩了.
調試的時候最好用 gdb 跟一下,如果嫌麻煩,可以用Visual Studio 的Debug工具看看。-----看見好多都在 po 代碼,看題意也沒說非要用鏈表,我要自己解決這個問題會這麼寫:#include &
#include &
#include &
class Member
{
public:
Member(){}
friend std::istream operator&>&>(std::istream in, Member m) { in &>&> m.name &>&> m.bYear &>&> m.bMonth; return in; }
friend std::ostream operator&<&<(std::ostream out, const Member m) { out &<&< m.name &<&< " " &<&< m.bYear &<&< "." &<&< m.bMonth; return out; }
bool operator&<(const Member m) const { return bYear &< m.bYear || (bYear == m.bYear bMonth &< m.bMonth);}
private:
std::string name;
int bYear;
int bMonth;
};
int main()
{
std::set&
for (int i=0; i!=7; ++i)
{
Member m;
std::cin &>&> m;
s.insert(m);
}
for (auto m : s)
std::cout &<&< m &<&< std::endl;
return 0;
}
用C++,卻不用STL,豈不是自討苦吃。這樣貌似也比較符合 @王月 提到的抽象了。^_^
不管題主程序水平怎麼樣。代碼是非常好看的!
鼓勵一下!
(摺疊我吧~#include &
using namespace std;
struct Member
{
//struct 名字第一個字母大寫
char name[4];
int year;
int month;
Member *next;
};
//其實最好把這個寫到Member裡面
int compare(Member* left, Member* right)
{
//這裡寫你的compare邏輯,比如如果left &< right,return -1,等於return 0,大於return 1;
//注意處理null的情況
if (left == NULL right == NULL)
{
return 0;
}
if (left == NULL)
{
return 1;
}
if (right == NULL)
{
return -1;
}
if (left-&>year == right-&>year)
{
return left-&>month == right-&>month ? 0 : left-&>month &< right-&>month ? -1 : 1;
}
return left-&>year &< right-&>year ? -1 : 1;
}
Member* Insert(Member* pHead, Member* pNode)
{
//個人認為這裡沒必要傳指針的引用。
//引用主要是為了函數內部修改值後作為output的一部分,或者是為了避免函數調用時copy參數。
//pHead可以作為函數返回值,pNode的值是不是被修改你也不care
if (pHead == NULL)
{
return pNode;
}
if (pNode-&>year &< pHead-&>year
|| (pNode-&>year == pHead-&>year pNode-&>month &< pHead-&>month)){
//上麵條件改成compare(pNode, pHead) &< 0
pNode-&>next = pHead;
return pNode;
//你這裡這麼處理完全沒有任何問題。或者也可以生成一個dummy head,不存任何信息,只是作為固定的鏈表頭
//這樣就不需要在插入新的node時修改head了。不修改head的好處就是如果程序的其他地方存儲了head,即使
//你插入新的更小的節點也不需要更新。而且copy指針非常cheap
}
Member *p = pHead;
while (true)
{
if (p-&>next == NULL)
{
p-&>next = pNode;
return pHead;
}
if (compare(pNode, p-&>next) &< 0){
pNode-&>next = p-&>next;
p-&>next = pNode;
return pHead;
}
p = p-&>next;
}
}
Member* Input(Member* head)
{
Member *pNode = new Member();
cin &>&> pNode-&>name &>&> pNode-&>year &>&> pNode-&>month;
head = Insert(head, pNode);
return head;
}
void Output(Member* head)
{
Member *p = head;
while (p != NULL)
{
cout &<&< p-&>name &<&< " " &<&< p-&>year &<&< "." &<&< p-&>month &<&< endl;
p = p-&>next;
}
}
int main(int argc, CHAR* argv[])
{
Member *head = NULL;
for (int i = 0; i &< 7; i++)
{
//永遠都加大括弧是好習慣
head = Input(head);
}
Output(head);
return 0;
}
好吧,就謝邀答一下吧。
初看問題還是覺得好基礎的問題,若不是受邀都沒有答的慾望。後來看了一下邀請人來自清華大學,還是誠惶誠恐,畢竟是自己曾失之交臂的地方。答案很簡單,99%都是內存溢出,野指針,未初始化的指針變數等等。
那麼再多寫一點。想到哪兒寫到哪兒吧,抱歉了。
1、寫指針的東西,感覺很像做幼兒園的這種:
找到底下8根繩子的一頭和上面8根繩子的一頭的對應關係。看起來很亂,但是仔細做好了成就感也不小(當然是說幼兒園的時候)。指針的操作也需要實時跟蹤每一個內存分配地址,腦子裡有一個完整的想像。很累。亂。2、但沒有指針就不是C/C++了。指針簡直是我見過的最美好的東西之一。自由。儘管這種自由感覺就像和惡魔做的交易,自由的飛翔,但是隨時一不小心就會折翼。第一次做JAVA的時候,直到理解到裡面那些變數的引用其實都是指針以後我才敢開始敲鍵盤。
3、鏈表自然是所有指針操作里最基本的。基礎。然後後面的樹、圖都只是鏈表的升級形式罷了。學號鏈表太重要。我一直覺得,學好了鏈表才能說自己會C。
4、無休無止的bug。崩潰簡直是最好處理的bug之一了。單步跟蹤一下,立刻找到哪個指針寫錯了,變成了野指針,或者沒有初始化。分段加斷點,把程序切割開來用排除法二分法debug簡直是常識。要是是在某個循環當中崩潰的,又找不到野指針,多半考慮內存溢出了,malloc/new的內存沒釋放掉,趕緊查查這段循環里申請了什麼內存忘釋放了。
最怕的還是編譯0 error 0 warning,鏈接無誤,運行無誤,就是結果錯了。這還得在debug的時候一個一個核對變數值,是不是和手算的一致。5、指針的bug遇到多線程我都不知道怎麼搞。只好黑盒子debug。沒法好好看內存里的值。得寫進代碼,在運行過程當中輸出一些關鍵變數的值來判斷。日誌文件之類。
6、也只有指針造成的bug還比較典型,要是其他方面的,沒有指針這個關鍵詞,沒有總是崩潰這個描述,多半知乎給的回答只能是說沒有給出任何程序癥狀,就問怎麼出錯,簡直是耍流氓。
7、指針越寫越系統,腦子裡對每個指針的跟蹤想像肯定不會是真的每一個,而是抽象出數學關係那樣,腦子裡的每一個對應著真實程序里的一大堆相同類型相同功能的指針。比如最近寫的一個程序,估計裡面90%以上的變數都是指針,當然這只是一個罕見的例子,寫的很自由,debug的很辛苦,腦子裡麻繩題根本做不過來了。
8、都知道1小時寫的代碼,花10小時debug都是常有的事。多半都是指針惹的禍。當然另一方面,題主說程序總是崩潰,怕是我們這種寫了5、6年代碼的程序猿(雖然我真正在寫代碼的日子加起來肯定不超過3個月),除非連續三天甚至一周,經歷各種調試,運行成百遍程序還在崩潰的,才會用上【總是】兩字吧。否則大多數情況估計就是【哎,今天程序崩潰了1整天了,幸好半夜找到了問題,沒有花太多時間。】
末了:唉,我自己也就個三腳貓功夫,懇請指正。以上。#include &
#include &
struct Member
{
int _year;
int _month;
std::string _name;
Member* _next;
bool operator&<=(const Member rhs)
{
return _year != rhs._year ? _year &< rhs._year : _month &< rhs._month;
}
};
void Insert(Member* head, Member* node)
{
if (head == NULL || *node &<= *head) {
node-&>_next = head;
head = node;
} else {
Member* curr = head;
while (curr-&>_next != NULL *curr-&>_next &<= *node) {
curr = curr-&>_next;
}
node-&>_next = curr-&>_next;
curr-&>_next = node;
}
}
void Input(Member* head)
{
Member* node = new Member;
std::cin&>&>node-&>_name&>&>node-&>_year&>&>node-&>_month;
Insert(head, node);
}
void Output(Member* head)
{
Member* node = head;
while (node != NULL) {
std::cout&<&
}
}
int main()
{
Member* head = NULL;
for (int i = 0; i &< 7; i++) {
Input(head);
}
Output(head);
while (true);
return 0;
}
來湊勒個熱鬧。
不足之處在於Input裡面cin可能輸入錯誤沒有處理,其他地方可能有錯誤。
牽扯到內存這種東西,錯了人家一般不給你解釋
1、Talk is cheap, show me the code.2、估計是你沒有malloc或者用new申請空間。
推薦閱讀: