다음 연습을 구현했습니다.
find(struct table*, const char*)
,insert(struct table*, const char*,int)
및remove(struct table*, const char*)
와 같은 작업이 포함 된 조회 테이블. 테이블 표현은struct
쌍의 배열 또는 배열 쌍 (const char*[]
및 ); 원하는 경우 함수의 반환 유형도 선택하세요.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #define ARR_SIZE 10 struct Pair { const char* word; int val; }; struct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; }; struct Pair* make_pair(const char* word, int val) { assert(word); struct Pair* pair = (struct Pair*)malloc(sizeof(struct Pair)); pair->val = val; pair->word = word; return pair; } void table_empty(struct Table* tbl) { assert(tbl); size_t i = 0; for (i = 0; i < tbl->sz; ++i) { free(tbl->pairs[i]); tbl->pairs[i] = NULL; } tbl->sz = 0; } int search_word(struct Table* tbl, const char* word) { assert(tbl); assert(word); size_t i = 0; for (i = 0; i < tbl->sz; ++i) { //printf("%s %i\n", tbl->pairs[i]->word,i); if (strcmp(tbl->pairs[i]->word, word) == 0) { return i; } } return -1; // error } void table_insert(struct Table* tbl, const char* word, int val) { assert(tbl); assert(word); int i = search_word(tbl, word); if (i != -1) { // replace val tbl->pairs[i]->val = val; } else { // add new pair struct Pair* pair = make_pair(word, val); tbl->pairs[tbl->sz] = pair; // add pair at the last position ++tbl->sz; } } int table_find(struct Table* tbl, const char* word, int* return_val) { assert(tbl); assert(word); assert(return_val); int i = search_word(tbl, word); if (i != -1) { *return_val = tbl->pairs[i]->val; return 0; } return -1; // error not found } int table_remove(struct Table* tbl, const char* word) { assert(tbl); assert(word); int i = search_word(tbl, word); if (i == -1) return -1; free(tbl->pairs[i]); // free value at current pos tbl->pairs[i] = tbl->pairs[tbl->sz - 1]; // put address of last word at the pos of the current --tbl->sz; // "erase" last word return 0; } void table_print(struct Table* tbl) { assert(tbl); printf("\n"); printf("table size = %i\n", tbl->sz); for (int i = 0; i < tbl->sz; ++i) printf("%s %i\n", tbl->pairs[i]->word, tbl->pairs[i]->val); fflush(stdout); } void print_search_result(struct Table* tbl, const char* word) { assert(tbl); assert(word); int val = 0; if (table_find(tbl, word, &val) == 0) printf("%s %i\n",word, val); else printf("%s not found in table\n", word); printf("\n"); fflush(stdout); } void print_remove_result(struct Table* tbl, const char* word) { assert(tbl); assert(word); if (table_remove(tbl, word) == -1) printf("%s not deleted\n", word); else printf("%s deleted\n", word); printf("\n"); fflush(stdout); } int main() { struct Table table = { 0 }; int val = 0; table_insert(&table, "Hello", 10); table_insert(&table, "Test", 15); table_insert(&table, "Hello", 18); // testing overrite val table_insert(&table, "What", 5); table_insert(&table, "is", 3); table_insert(&table, "going", 4); table_insert(&table, "on", 77); table_print(&table); print_search_result(&table, "Hello"); print_search_result(&table, "Test"); print_search_result(&table, "keyword"); print_remove_result(&table, "Hello"); print_remove_result(&table, "Hello"); // double delete == false print_remove_result(&table, "What"); print_remove_result(&table, "going"); print_remove_result(&table, "is"); print_remove_result(&table, "on"); print_remove_result(&table, "Test"); table_print(&table); table_insert(&table, "Hello", 10); table_insert(&table, "Test", 15); table_insert(&table, "Hello", 18); // testing overrite val table_insert(&table, "What", 5); table_insert(&table, "is", 3); table_insert(&table, "going", 4); table_insert(&table, "on", 77); table_print(&table); table_empty(&table); table_print(&table); getchar(); }
개선 된 사항에 대해 자유롭게 의견을 보내주세요. 이 작업을 수행하는 더 좋은 방법이 있습니까?
또한 한 가지 구체적인 질문이 있습니다. assert
사용이 적절합니까?
댓글
답변
struct
개인적으로 사용하여 올바른 선택을했다고 생각합니다. iv id =의 2 개 배열이 아닌 char*
및 int
가있는 struct
각각 “eccc22e211”>
및int
.Pair
는 앱에있는 데이터의 개념적 단위이므로 함께 결합하는 것이 좋습니다. 두 개의 개별 어레이가 있다면 동기화가 해제되기 쉽고 왜 그런 일이 발생했는지 디버깅하기가 어렵습니다. 잘하셨습니다.
const
및 함수를 매크로보다 선호
매크로를 사용하여 배열 크기를 정의했습니다. 이렇게하면 프로그래밍하는 동안 단점이 있습니다. 값에 대한 유형 정보를 제거했습니다. 만들면 :
const int ARR_SIZE = 10;
타입 안전성을 얻을 수 있습니다. 편집 : “그렇지 않은 C ++-ism”입니다. t는 C에서 일한다. 나의 나쁜! 그러나 다음 단락의 나머지 조언은 내가 아는 한 정확합니다.
매개 변수를 사용하는 매크로를 사용하면 매크로가 예기치 않은 방식으로 사용되어 문제를 디버깅하기 어렵게 될 위험이 있습니다. 다행히 여기서는 그렇게하지 않았습니다.하지만 일반적으로 매크로에 도달하는 경우 상수 또는 함수를 사용하는 것이 더 나은지 자문 해보십시오. 거의 항상 그럴 것입니다 (상수를 사용할 수 있다고 가정하면 원하는 방식으로).
오류
코드에 오류가 있습니다. make_pair()
에서는 확인하지 않습니다. malloc()
성공한 경우. 실패하면 메모리가 할당되지 않고 pair
는 NULL
를 가리 킵니다. pair->val
또는 pair->word
를 할당하려고하면 충돌이 발생합니다.
수정 한 경우 table_insert()
는 먼저 NULL
인지 확인하지 않고 make_pair()
의 결과를 사용합니다. t 즉시 충돌이 발생합니다. “tbl->pairs[tbl->sz]
에서 pair
의 값을 갖도록 배열 포인터를 할당하고 있기 때문입니다. 다른 항목을 검색, 인쇄 또는 삽입하려고하면 “테이블의 해당 항목을 반복하고 작업을 수행 할 때 충돌이 발생합니다.
이 오류를 불가능하게 만들 수 있습니다. 배열 항목을 동적으로 할당합니다. 배열을 포인터가 아닌 Pair
구조체의 배열로 만들기 만하면됩니다.
이름 지정
많은 이름이 정말 좋습니다. . Pair
및 Table
는이 작업에 대해 적절하고 읽기 쉬운 이름입니다. make_pair()
, table_insert()
등은 정보를 제공합니다. 하지만 일부는 개선 될 수 있습니다. tbl
는 table
보다 많은 입력을 절약하지 않습니다. 전체 단어를 사용하십시오. sz
대 size
와 동일합니다. i
는 루프 변수 이름으로 허용되지만 더 설명적인 경우 더 좋습니다. 예 : entry_index
또는 pair_index
. 반복되는 내용을 설명해야합니다.
댓글
- 정의를
const int ARR_SIZE = 10;
struct Table { struct Pair* pairs[ARR_SIZE]; size_t sz; };
에서 ARR_SIZE가 const가 아니라는 오류가 표시됩니다. 그렇다면이 경우 어떻게 사용합니까? - 죄송합니다. ‘ C ++-ism입니다.이 질문이 C ++ 질문이라고 생각했습니다. 내 문제입니다. ‘ 내 답변을 업데이트하겠습니다.
- 나는 ‘ 설명 변수 이름의 확고한 지지자이지만이 프로그램의 루프의 단순함으로 인해 변수 이름은 잘 작동합니다.
-
malloc()
가 실패하면pair->val
또는pair->word
운이 좋다면 충돌 할 수 있습니다 . 계속해서 잘못된 결과를 내거나 완전히 예상치 못한 일을 할 수 있습니다. 이것이 ‘ 정의되지 않은 동작의 기쁨입니다!
답변
-
malloc
의 결과를 전송하지 마십시오.malloc
는void *
, C에서는void *
를 임의의 포인터로 변환하는 것이 유효합니다. 정수에서 포인터로의 변환에 대한 경고를 표시하지 않도록 캐스트하면 컴파일러에 “malloc
프로토 타입이 없으며 기본값이 (고대 C 규칙). 이제int
와 포인터의 크기가 다른 경우 malloc 반환 값이 잘리고 모든 불쾌한 결과가 발생합니다.
p>
sizeof(type)
는 권장하지 않습니다. sizeof(expression)
를 선호합니다. 귀하의 경우에는 다음을 고려하십시오.
struct Pair * pair = malloc(sizeof(*pair));
table_insert
전체 테이블에 맹목적으로 삽입합니다.
그렇지 않은 경우 오류 표시를 반환합니다.
실제 삽입
struct Pair* pair = make_pair(word, val); tbl->pairs[tbl->sz] = pair; // add pair at the last position
실제로 한 줄이어야합니다.
tbl->pairs[tbl->sz] = make_pair(word, val);
댓글
- malloc을 캐스트하는 것이 좋지 않은 이유는 무엇입니까? C ++에서는 심지어 컴파일러 오류가 발생합니까?
- @Sa ndro4912 질문은 C로 태그가 지정됩니다. C ++는 특히이 점에서 다른 언어입니다. 이제 C 컴파일러가 불평하면
malloc
프로토 타입이 누락되어 심각한 문제가 발생할 수 있습니다. - 예, c입니다. 왜 여기에서 캐스트로 유형 변경을 나타내는 것이 좋지 않은지 궁금했습니다.
- @ Sandro4912 편집 참조
- @ Sandro4912
pair = malloc(sizeof *pair)
는 처음에는 올바르게 코딩하기 쉽고 검토 및 유지 관리가 더 쉽습니다.
답변
모두 활성화 컴파일러 경고
잘 활성화 된 컴파일러를 사용하면 for (int i = 0; i < tbl->sz; ++i)
가 i
및 tbl->sz
및 범위. 시간을 절약하고 모든 경고를 활성화하고 for (size_t i = 0; i < tbl->sz; ++i)
를 사용합니다.
일반적으로 int,size_t
를 거의 같은 의미로 사용하면 코드가 엉망이됩니다. . 다시 설계하고 size_t
만 사용했습니다.
얕은 카피와 딥 카피의 혼합 사용
make_pair(const char* word, int val)
는 완전히 새로운 struct Pair
(전체 복사)를 할당하고 구성하지만 word
가 가리키는 내용은 복사하지 않습니다.
p>
아마
// pair->word = word; pair->word = strdup(word);
const
는 *tbl
를 수정하지 않으므로 const to convey that. Same for
table_find () ,
table_print () ,
print_search_result ()`.
// int search_word(struct Table* tbl, const char* word) int search_word(const struct Table* tbl, const char* word)
이름 지정
코드 const char* word;
를 사용하지만 단어가 아니라 strcmp()
에서 사용되는 문자열 입니다. p>
—– 추가 사항
계약 위반?
“…와 같은 작업으로 조회 테이블 구현”요구 사항에 const char*
는 문자열 에 대한 포인터입니다. 따라서 strcmp()
는 명시되지 않은 요구 사항이없는 한 의심 스럽습니다. char *
로서 코드는 간단한 비교를 사용할 수 있습니다.
// if (strcmp(tbl->pairs[i]->word, word) == 0) if (tbl->pairs[i]->word == word)
assert()
내가 assert를 사용하는 것이 적절합니까?
추가 / 검색을위한 char *
포인터가 문자열 으로 지정되지 않았습니다. assert(word);
는 word == NULL
는 유효하지 않은 것으로 알려져 있습니다.
table_find(struct Table* tbl, const char* word, int* return_val)
div의 assert(return_val)
>는 괜찮지 만 return_val == NULL
if (i != -1) { // add if (return_val) { *return_val = tbl->pairs[i]->val; } return 0; }
를 허용하도록 다시 디자인하겠습니다.
*
를 권장합니다. 변수 이름 옆에 입력해야합니다 (예 :const char *word
). C 유형 구문은 사용을 모방하도록 설계되었습니다. 예를 들어 ‘ d 유형*word
는 요소. 또한int *a, b;
에있는b
유형이 더 명확 해집니다.