在stackexchange有一個(gè)人提了這么一個(gè)問題黑竞,自己實(shí)現(xiàn)了一個(gè)通用隊(duì)列眨层,然后把代碼貼了上來,然后請(qǐng)大家review以下面哥,希望基于以下幾方面提一些建議:
- 1哎壳,編程風(fēng)格(My general style)
- 2,是否正確地釋放內(nèi)存以避免內(nèi)存泄漏
- 3尚卫,正確地處理了內(nèi)存分配失敗
- 4归榕,其它任何你能想得到的建議
在我看來,下面的實(shí)現(xiàn)其實(shí)已經(jīng)相當(dāng)完備了吱涉,代碼正確性刹泄,風(fēng)格外里,可讀性都相當(dāng)不錯(cuò),可是下面還有一人竟然提出了12條改進(jìn)建議特石,讓我覺得相當(dāng)驚訝盅蝗,我看了下這個(gè)人的簡(jiǎn)介,coding since 1976姆蘸,讓我覺得一個(gè)有著多年經(jīng)驗(yàn)的程序員和普通程序員之間的差別墩莫。
代碼如下:
Queue.h
#ifndef QUEUE_H_INCLUDED
#define QUEUE_H_INCLUDED
typedef struct Node
{
void *data;
struct Node *next;
}node;
typedef struct QueueList
{
int sizeOfQueue;
size_t memSize;
node *head;
node *tail;
}Queue;
void queueInit(Queue *q, size_t memSize);
int enqueue(Queue *, const void *);
void dequeue(Queue *, void *);
void queuePeek(Queue *, void *);
void clearQueue(Queue *);
int getQueueSize(Queue *);
#endif /* QUEUE_H_INCLUDED */
Queue.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Queue.h"
void queueInit(Queue *q, size_t memSize)
{
q->sizeOfQueue = 0;
q->memSize = memSize;
q->head = q->tail = NULL;
}
int enqueue(Queue *q, const void *data)
{
node *newNode = (node *)malloc(sizeof(node));
if(newNode == NULL)
{
return -1;
}
newNode->data = malloc(q->memSize);
if(newNode->data == NULL)
{
free(newNode);
return -1;
}
newNode->next = NULL;
memcpy(newNode->data, data, q->memSize);
if(q->sizeOfQueue == 0)
{
q->head = q->tail = newNode;
}
else
{
q->tail->next = newNode;
q->tail = newNode;
}
q->sizeOfQueue++;
return 0;
}
void dequeue(Queue *q, void *data)
{
if(q->sizeOfQueue > 0)
{
node *temp = q->head;
memcpy(data, temp->data, q->memSize);
if(q->sizeOfQueue > 1)
{
q->head = q->head->next;
}
else
{
q->head = NULL;
q->tail = NULL;
}
q->sizeOfQueue--;
free(temp->data);
free(temp);
}
}
void queuePeek(Queue *q, void *data)
{
if(q->sizeOfQueue > 0)
{
node *temp = q->head;
memcpy(data, temp->data, q->memSize);
}
}
void clearQueue(Queue *q)
{
node *temp;
while(q->sizeOfQueue > 0)
{
temp = q->head;
q->head = temp->next;
free(temp->data);
free(temp);
q->sizeOfQueue--;
}
q->head = q->tail = NULL;
}
int getQueueSize(Queue *q)
{
return q->sizeOfQueue;
}
TestQueue.c
#include <stdio.h>
#include <stdlib.h>
#include "Queue.h"
int main()
{
int val;
Queue q;
queueInit(&q, sizeof(int));
for(val = 0; val < 10; val++)
{
enqueue(&q, &val);
printf("The value %d has been enqueued.\n", val + 1);
}
printf("\n");
queuePeek(&q, &val);
printf("The value that is at the front of the queue is %d\n\n", val + 1);
while(getQueueSize(&q) > 0)
{
dequeue(&q, &val);
printf("%d has been dequeued.\n", val + 1);
}
return 0;
}
下面讓我看看他提的一些建議:
- 1,Pattern-less function names逞敷。對(duì)于queue.h里面一些函數(shù)的命名狂秦, Queue_Init(), Queue_GetSize() 要比 queueInit(), getQueueSize() 更能凸顯意義,更具有一致性推捐。
- 2裂问,Comments with # preprocessing may not be portable。注釋帶了#可能移植性不太好玖姑。這個(gè)沒理解愕秫,原程序中似乎也沒有這樣用注釋,哪位讀者明白可以評(píng)論里面補(bǔ)充下焰络。
// #endif /* QUEUE_H_INCLUDED */
#endif
- 3戴甩,QueueList沒有必要保存兩個(gè)head和tail兩個(gè)指針,只需要保存tail指針闪彼,并且tail結(jié)點(diǎn)的next指針域指向頭結(jié)點(diǎn)甜孤,這實(shí)際上是一個(gè)循環(huán)鏈表組成的queue,那么鏈表走到末端的判斷將會(huì)是p->next == tail->next畏腕。這樣的好處是大量使用queue的時(shí)候更節(jié)省內(nèi)存缴川,但是邏輯上復(fù)雜了些。
- 4 不夠明晰為什么隊(duì)列大小類型使用int表示描馅。int是(有符號(hào))signed類型把夸,可能表示負(fù)數(shù),也許可以使用unsigned铭污,在大多數(shù)系統(tǒng)中恋日,size_t表示的范圍要比int更加寬廣,也許使用size_t更加謹(jǐn)慎一些嘹狞。健壯的代碼應(yīng)該在enqueue函數(shù)中檢查進(jìn)隊(duì)時(shí)是否超過隊(duì)列最大長(zhǎng)度岂膳。
- 5 好的點(diǎn):聲明memSize為size_t類型,對(duì)于malloc()函數(shù)的錯(cuò)誤檢查磅网,有測(cè)試用例谈截。另外對(duì)于.h的使用有注釋說明,對(duì)于你的代碼來說.h就像是一個(gè)公共借口。
關(guān)于size_t簸喂,int和unsigned int可參考此文https://prateekvjoshi.com/2015/01/03/why-do-we-need-size_t/, size_t,size_t的取值range是目標(biāo)平臺(tái)下最大可能的數(shù)組尺寸(非負(fù)數(shù)),一些平臺(tái)下size_t的范圍小于int的正數(shù)范圍,又或者大于unsigned int毙死。為了移植性和性能考慮,有些情況下表示大小盡可能使用size_t
- 6 更加健壯的用法在queueInit()函數(shù)中檢查memSize==0娘赴,同樣對(duì)于malloc()函數(shù)應(yīng)該做以下檢查:if(newNode->data == NULL && q->memSize > 0)
- 7 要有些文檔化的解釋规哲。對(duì)于queue.h每個(gè)函數(shù)聲明前面建議由一到兩行的注釋。
- 8 對(duì)于queuePeek(Queue *q, void data)函數(shù)诽表,如果q不改變唉锌,則應(yīng)該聲明為queuePeek(const Queue *q, void *data)。這是對(duì)于q常量特性的自我文檔說明竿奏,可能對(duì)編譯器優(yōu)化也有好處袄简。另外對(duì)于函數(shù)的使用實(shí)現(xiàn)檢查也是有好處。
- 9 為了完備性泛啸,建議在clearQueue()函數(shù)中寫上q->memSize = 0 绿语。
- 10 改變#include的包含順序。把"queue.h"應(yīng)該放在第一個(gè)候址。這樣可以檢測(cè)到queue.h沒有依賴下面的三個(gè)<.h>文件吕粹。queue.h需要依賴的已經(jīng)在其本身文件中包含進(jìn)來。
#include "Queue.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// #include "Queue.h"
關(guān)于頭文件依賴順序說明可以參看:https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/headers/岗仑,
1匹耕,foo/bar.h.
2,C 系統(tǒng)文件
3荠雕,C++系統(tǒng)文件
4稳其,其他庫(kù)頭文件
5,本項(xiàng)目?jī)?nèi)頭文件,
實(shí)際上《C++編程思想》對(duì)于2,3,4,5建議的順序可能是反過來的炸卑,兩者各有利弊既鞠。但有一點(diǎn)本文件相關(guān)的頭文件必須放在第一位,可以防止在本文件(如上面的bar.h)里面少包含了必須的頭文件盖文。
- 11 結(jié)果聲明嘱蛋。對(duì)于void dequeue(Queue *q, void *data)函數(shù)中拷貝data的操作,沒有對(duì)結(jié)果的聲明五续,應(yīng)該失敗返回fasle洒敏,成功返回true。queuePeek()函數(shù)也類似返帕。
- 12 為了調(diào)試方便桐玻,釋放內(nèi)存操作free()之前最好把內(nèi)存數(shù)據(jù)置零篙挽,這樣錯(cuò)誤的代碼可能在面對(duì)原始數(shù)據(jù)對(duì)比為0的指針或者數(shù)據(jù)更快失敗荆萤,更容易調(diào)試。
- 13 建議:如果非必要,可以不用存儲(chǔ)queue的size大小链韭,在需要的時(shí)候計(jì)算即可偏竟。也許可以添加一個(gè)檢查是否為空隊(duì)列的函數(shù)bool Queue_IsEmpty(const Queue *q)。這樣可以去掉size的聲明敞峭。
另外還有一個(gè)回答建議Node結(jié)構(gòu)體的聲明可以在.c文件中定義踊谋,如果其不是對(duì)外公開的數(shù)據(jù)結(jié)構(gòu)體。
原文網(wǎng)址:
https://codereview.stackexchange.com/questions/141238/implementing-a-generic-queue-in-c