色々考慮して書き換えたらこうなりました。
C
1#ifdef _WIN32
2#ifdef __GNUC__ // GCC or Clang
3// msvcrtのprintfではzuが使用できないため
4#define __USE_MINGW_ANSI_STDIO 1
5#elif _MSC_VER // VC++
6// VLA無効
7#define __STDC_NO_VLA__ 1
8// strcpy等の警告無効(わかってやっている)
9#define _CRT_SECURE_NO_WARNINGS 0
10// 0サイズ配列が構造体に最後に来る場合の警告無効
11#pragma warning(disable : 4200)
12#endif // __GNUC__ or _MSC_VER
13#endif // _WIN32
14
15#include <stdbool.h>
16#include <stddef.h>
17#include <stdint.h>
18#include <stdio.h>
19#include <stdlib.h>
20#include <string.h>
21
22#define BUFF_SIZE 1024
23
24struct word {
25 size_t len;
26 char str[];
27};
28
29// 行の読み込み
30char *get_line(void);
31// 英単語の個数読み取り
32size_t input_kosu(void);
33// 英単語の読み取り
34struct word *input_word(size_t no);
35// 長さ比較
36int word_len_compare(const void *a, const void *b);
37
38int main(void)
39{
40 // 英単語の個数を取得する。
41 size_t number = input_kosu();
42 if (number == 0) {
43 fprintf(stderr, "%s\n", "取得できませんでした。");
44 exit(1);
45 }
46
47 // 英単語の配列
48#ifdef __STDC_NO_VLA__
49 struct word **words =
50 (struct word **)malloc(number * sizeof(struct word *));
51 if (words == NULL) {
52 fprintf(stdout, "%s\n", "メモリ割り当てに失敗しました。");
53 exit(1);
54 }
55#else
56 struct word *words[number];
57#endif // __STDC_NO_VLA__
58
59 // 繋げたときの長さ
60 size_t total_len = 0;
61 for (size_t i = 0; i < number; i++) {
62 words[i] = input_word(i);
63 if (words[i] == NULL) {
64 fprintf(stderr, "%s\n", "取得できませんでした。");
65 exit(1);
66 }
67 // ついでに長さを追加
68 total_len += words[i]->len;
69 }
70
71 // 長さ順にソート(安定ソートではない)
72 qsort(words, number, sizeof(words[0]), word_len_compare);
73
74 // 連結した文字列
75 char *renketsu = (char *)malloc(total_len + 1);
76 renketsu[0] = '\0';
77 for (size_t i = 0; i < number; i++) {
78 strcat(renketsu, words[i]->str);
79 // ついでに解放
80 free(words[i]);
81 }
82 printf("%s: %s\n", "連結結果", renketsu);
83 free(renketsu);
84#ifdef __STDC_NO_VLA__
85 free(words);
86#endif // __STDC_NO_VLA__
87
88 return 0;
89}
90
91char *get_line(void)
92{
93 char *line = NULL;
94 size_t line_size = BUFF_SIZE;
95 size_t ptr_size = 0;
96 while (line_size <= SIZE_MAX) {
97 char *new_line = (char *)realloc(line, line_size);
98 if (new_line == NULL) goto error;
99 line = new_line;
100 char *ptr = line + ptr_size;
101 // line_size - ptr_size が intの範囲を超えることはないはず。
102 char *result = fgets(ptr, (int)(line_size - ptr_size), stdin);
103 if (result == NULL) {
104 if (line == ptr) goto error;
105 return line;
106 }
107 size_t read_size = strlen(ptr);
108 if (read_size == 0) return line;
109 if (ptr[read_size - 1] == '\n') return line;
110 // next
111 ptr_size += read_size;
112 line_size += BUFF_SIZE;
113 }
114error:
115 free(line);
116 return NULL;
117}
118
119size_t input_kosu(void)
120{
121 while (true) {
122 printf("%s: ", "英単語何個ですか?");
123 fflush(stdout);
124 char *line = get_line();
125 if (line == NULL) return 0;
126 char **line_end = NULL;
127 long long num = strtoll(line, line_end, 10);
128 free(line);
129 if (0 < num && num <= SIZE_MAX) {
130 return (size_t)num;
131 }
132 printf("%s\n", "1以上の正の整数を入力してください。");
133 }
134}
135
136struct word *input_word(size_t no)
137{
138 while (true) {
139 printf("%zu%s: ", no, "個目");
140 fflush(stdout);
141 char *line = get_line();
142 if (line == NULL) return NULL;
143 // 単語にするために空白や改行で区切る
144 for (char *ptr = line; *ptr != '\0'; ptr++) {
145 if (strchr(" \t\r\n\f\v", *ptr) != NULL) {
146 *ptr = '\0';
147 break;
148 }
149 }
150 size_t len = strlen(line);
151 if (len == 0) {
152 free(line);
153 continue;
154 }
155 printf("%zu\n", len);
156 printf("%s\n", line);
157 struct word *word_p =
158 (struct word *)malloc(sizeof(struct word) + len + 1);
159 if (word_p == NULL) {
160 free(line);
161 return NULL;
162 }
163 word_p->len = len;
164 word_p->str[0] = '\0';
165 strcpy(word_p->str, line);
166 free(line);
167 return word_p;
168 }
169}
170
171int word_len_compare(const void *a, const void *b)
172{
173 const struct word *a_w = *(const struct word *const *)a;
174 const struct word *b_w = *(const struct word *const *)b;
175 ptrdiff_t diff = a_w->len - b_w->len;
176 // diffはintの幅を超える可能性が一応ある、たぶん
177 if (diff > 0) {
178 return 1;
179 } else if (diff < 0) {
180 return -1;
181 } else {
182 return 0;
183 }
184}
Visual C++(Visual Studioの一部としてインストール)、MinGW-w64、Clang(Windows)、BCC32C、WSL上UbuntuのGCCで動作確認しています。GCCやClangでは"-std=c11"を付けてください。それぞれ最新バージョンです。特に古いVC++では未対応のためエラーになる場合があります。
- VLAは使える環境と使えない環境がある。
VLAはC11ではオプション機能であり、環境によって使えません。C11準拠のコンパイラでは__STDC_NO_VLA__
が有るかどうかで判断してください。ただし、Visual C++はVLAが使えないにもかかわらず__STDC_NO_VLA__
は設定されていません(これはVisual C++がC11に準拠していないからです)。自分で足しておいてください。
VLAが使えない環境では素直にmalloc()
等でメモリを確保しましょう。free()
を忘れずに。
scanf()
で数値を読み取る場合は、整数型の範囲を超える場合に動作が未定義である(ここら辺は実はよくわかっていない、仕様としてはstrtol()
と同じように動作と書いてあったりもする)という問題があります。strtoll()
など型の範囲外の場合でも動作が未定義とならない関数で取得してください。
今回は面倒だったのでget_line
で1行全てを取得するようにしています。
scanf()
で文字列を読み取る場合は、サイズを指定、つまり、"%s"ではなく"%256s"のように取得する大きさを必ず指定しなければなりません。"%s"のような書き方はgets
と同じで、バッファオーバーフローを防ぐ方法がありません。
※ 取得サイズを別途指定するscanf_s()
(オプション)を使用する方法もあります。
- アルゴリズムに拘りがなければソートには
qsort()
を使いましょう。ただ、安定ソートとは限らないため、安定ソートである必要がある場合は一工夫必要になります。
- 配列のサイズには
size_t
を使いましょう。ただ、printf()
で出力は%zu
になるのですが、MinGW GCCが標準で利用するC標準ライブラリ(Windows標準のmsvcrt)や古いVisual C++では対応していません。__USE_MINGW_ANSI_STDIO
でC標準に準拠したprint()
を使うようにしてください。
- 英単語の構造体を作って長さを入れているのは長さを英単語と一緒に保存するためです。
strlen()
はO(n)必要になるため、呼び出し回数が多くなると重くなります。特に、ソートの中で呼び出してしまうと、顕著な速度低下を招きます。
- 構造体の最後が大きさ0の配列(大きさ未指定)にする機能はCの標準機能なのですが、Visual C++はMicrosoftの独自拡張だと警告を出します。標準機能のはずなのですが、よくわかりません。
- 色々チェックしていないところがあります(途中で面倒になった)。安全性を見込むならassert等で真面目にチェックする必要があるでしょう。
- 適度に関数に分けてください。上のコード例ですら、分け方が足りないと思っています。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/07/16 00:26
2018/07/16 02:29
2018/07/16 02:29
2018/07/16 02:46