入力の検証をしたいようですが、そもそもscanf系関数で数値読み込みすること自体が問題です。
C言語で安全に標準入力から数値を取得
を見てください。
あとage
とsex
がint
型なのが個人的にはどうかと思うのでenumとかunsignedな何かに書き換えたいt頃です。
暇ができたので全面書き直ししました。他にもいろいろ思うところがあったので。
c
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <limits.h>
5#include <errno.h>
6#include <stdbool.h>
7#include <ctype.h>
8#include <float.h>
9#include <assert.h>
10#ifndef static_assert
11# define NO_STDC_STATIC_ASSERT
12# define static_assert(...)
13#endif
14#if defined(_MSC_VER) || defined(__cplusplus)
15# define restrict
16#endif
17/**
18 * @brief 文字列が文字を持っているか調べます。
19 * @param str 対象文字列へのポインタ
20 * @return false: nullptrか空白文字のみの文字列 true:それ以外
21 */
22static inline bool str_has_char(const char *str)
23{
24 if (NULL == str) return false;
25 bool ret = false;
26 for (; !ret && *str != '\0'; str++) ret = (*str != ' ');
27 return ret;
28}
29/**
30 * @brief 文字列が文字を持っているか調べます。
31 * @param io 書き換えるbool型変数へのポインタ、呼び出し後はポインタが指す変数にnew_valueが代入される
32 * @param new_value 新しい値
33 * @return ioが指すbool変数がもともと持っていた値
34 */
35static inline bool exchange_bool(bool* restrict const io, const bool new_value)
36{
37 const bool tmp = *io;
38 *io = new_value;
39 return tmp;
40}
41/**
42 * @brief fgetsで失敗したときにストリームをクリアしてループする関数
43 * @param s ストリームから読み取った文字列を格納するための領域へのポインタ
44 * @param buf_size ストリームから読み取った文字列を格納するための領域の大きさ
45 * @param stream FILE構造体へのポインタかstdin
46 * @param message_on_error エラー時に表示してループする
47 * @return 成功時は0, new line at the end of fileのときは-1
48 */
49static inline int fgets_wrap(char* restrict const s, size_t buf_size, FILE* restrict const stream, const char* restrict message_on_error)
50{
51 size_t i = 0;
52 for (bool first_flg = true; i < 100 && NULL == fgets(s, buf_size, stream); ++i) {
53 if (feof(stdin)) return -1;
54 if (!exchange_bool(&first_flg, false)) puts((message_on_error) ? message_on_error : "再入力してください");
55 }
56 if (100u == i) exit(1);//無限ループ防止
57 if (feof(stdin)) return 0;
58 //改行文字が入力を受けた配列にない場合、入力ストリームにごみがある
59 const size_t len = strlen(s);
60 //短すぎる入力
61 if (0 == len || (1 == len && '\n' == s[0])) return 1;
62 //長過ぎる入力
63 if ('\n' != s[len - 1]) {
64 //入力ストリームを掃除
65 while (fgetc(stream) != '\n');
66 return 2;
67 }
68 return 0;
69}
70
71/**
72 * @brief 標準入力から文字列の入力を受ける
73 * @param s ストリームから読み取った文字列を格納するための領域へのポインタ
74 * @param buf_size ストリームから読み取った文字列を格納するための領域の大きさ
75 * @param message 入力を受ける前にputsに渡す文字列。表示しない場合はnullptrか空白文字のみで構成された文字列へのポインタを渡す
76 * @param message_on_error エラー時に表示してループする
77 */
78static inline void input_str(char* restrict const s, size_t buf_size, const char* message, const char* restrict message_on_error)
79{
80 if (str_has_char(message)) puts(message);
81 size_t i = 0;
82 for (; i < 100u; ++i) {
83 //長過ぎる入力以降の無限ループ防止にerrnoをクリアする
84 errno = 0;
85 switch (fgets_wrap(s, buf_size, stdin, message_on_error)) {
86 case -1: return;//EOF
87 case 1://短すぎる入力
88 case 2://長過ぎる入力
89 continue;
90 default: goto after_loop;
91 }
92 }
93 if (100u == i) exit(1);//無限ループ防止
94after_loop:
95 {
96 char* const lf = strchr(s, '\n');
97 if(NULL != lf) lf[0] = '\0';
98 }
99}
100
101/**
102 * @brief 標準入力から入力を受け、unsigned int型に変換する
103 * @details fgetsしてstrtodしている。max, minの条件に合わないかエラー時はループ
104 * @details errnoの値を書き換える
105 * @param message 入力を受ける前にputsに渡す文字列。表示しない場合はnullptrか空白文字のみで構成された文字列へのポインタを渡す
106 * @param message_on_error エラー時に表示してループする
107 * @param max 入力値を制限する。最大値を指定
108 * @param min 入力値を制限する。最小値を指定
109 * @return 入力した数字、EOFのときは0
110 */
111static inline unsigned int input_uint(const char* message, const char* restrict message_on_error, const unsigned int max, const unsigned int min)
112{
113 if (str_has_char(message)) puts(message);
114 char s[30];
115 static_assert(sizeof(unsigned int) < 8, "err");
116 unsigned long t = 0;
117 size_t i = 0;
118 for (char* endptr = s; ((0 == t && endptr == s) || 0 != errno || t < min || max < t) && i < 100u; ++i) {
119 //長過ぎる入力以降の無限ループ防止にerrnoをクリアする
120 errno = 0;
121 switch (fgets_wrap(s, sizeof(s), stdin, message_on_error)) {
122 case -1: return 0;//EOF
123 case 1://短すぎる入力
124 case 2://長過ぎる入力
125 endptr = s;//ループ制御フラグとして流用
126 continue;
127 default: break;
128 }
129 t = strtoul(s, &endptr, 10);
130 }
131 if (100 == i) exit(1);//無限ループ防止
132 return ((unsigned int)(t));
133}
134#ifdef NO_STDC_STATIC_ASSERT
135# undef static_assert
136# undef NO_STDC_STATIC_ASSERT
137#endif
138
139#ifndef _countof
140#define _countof(arr) (sizeof(arr) / sizeof(*arr))
141#endif
142
143typedef struct
144{
145 char name[256];
146 unsigned int age;
147 unsigned int sex;
148}People;
149
150void InputPeople(People *data)
151{
152 do {
153 input_str(data->name, _countof(data->name), "名前を入力してください", "不正な長さの入力です、もう一度入力してください");
154 printf("%sでよろしいですか??(「合ってます」:「1」を入力、「間違いました」:「1以外」を入力)\n", data->name);
155 } while (!feof(stdin) && 1 != input_uint(NULL, NULL, UINT_MAX, 0));
156 if (feof(stdin)) exit(1);
157 do {
158 data->age = input_uint("年齢を入力してください", NULL, UINT_MAX, 0);
159 printf("%dでよろしいですか??(「合ってます」:「1」を入力、「間違いました」:「1以外」を入力)\n", data->age);
160 } while (!feof(stdin) && 1 != input_uint(NULL, NULL, UINT_MAX, 0));
161 if (feof(stdin)) exit(1);
162 do {
163 data->sex = input_uint("性別を入力してください(「男性」:「1」を入力、「女性」:「2」を入力)", NULL, 2, 1);
164 printf("%sでよろしいですか??(「合ってます」:「1」を入力、「間違いました」:「1以外」を入力)\n", (1 == data->sex) ? "男性" : "女性");
165 } while (!feof(stdin) && 1 != input_uint(NULL, NULL, UINT_MAX, 0));
166 if (feof(stdin)) exit(1);
167 putchar('\n');
168}
169void ShowPeople(const People* data)
170{
171 printf(
172 "名前:%s\n"
173 "年齢:%d\n"
174 "性別:%s\n",
175 data->name, data->age, (data->sex == 1) ? "男性" : "女性"
176 );
177 putchar('\n');
178}
179
180int main(void)
181{
182 People data[3];
183 for (size_t i = 0; i < 3; i++) {
184 InputPeople(&data[i]);
185 }
186 for (size_t i = 0; i < 3; i++) {
187 ShowPeople(&data[i]);
188 }
189 return 0;
190}
https://wandbox.org/permlink/NuxCyJqbMdnrsPCc
追記:
Qiitaに記事書きました、上記コードに誤りがあった場合はQiitaのほうのみ修正することにします。
「苦しんで覚えるc言語」のとあるコードをもっと安全に、より良く