質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

5回答

2449閲覧

自分の解答の穴を見つけて頂けると助かります。

Akihiro_O

総合スコア20

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2019/01/05 11:42

編集2019/01/10 09:03

現在、書籍「苦しんで覚えるc言語」でc言語を勉強しているプログラミング入門者です。
次のような練習問題に取り組んでいるのですが、自分なりに著者の解答を書き換えてみました。
他に著者の解答に付け足すべき箇所などありましたら、教えて頂けると幸いです。

#前提
情報を入力する「InputPeople関数」内において、正しく入力されたか確認を取るために、著者の解答を一部書き換えました。
#実現したいこと
より安全で正確な(穴のない)プログラムが書けたらと思っています。

#問題
「3人分の、名前、年齢、性別、を入力して表示するプログラムを作りなさい。
ただし、データは構造体で記憶することとし、
また、データの入力と表示はそれぞれ専用の関数を作って行うこととする。」

#著者の解答

#include<stdio.h> #include<string.h> typedef struct { char name[256]; int age; int sex; }People; void InputPeople(People *data); void ShowPeople(People data); int main(void) { People data[3]; int i; for (i = 0; i < 3; i++) { InputPeople(&data[i]); } for ( i = 0; i < 3; i++) { ShowPeople(data[i]); } return 0; } void InputPeople(People *data) { printf("名前:"); scanf_s("%s", &data->name,256); printf("年齢:"); scanf_s("%d", &data->age); printf("性別(1-男性、2-女性):"); scanf("%d", &data->sex); printf("\n"); } void ShowPeople(People data) { char sex[16]; printf("名前:%s\n",data.name); printf("年齢:%d\n",data.age); if (data.sex == 1) { strcpy_s(sex, 16,"男性"); } else { strcpy_s(sex, 16,"女性"); } printf("性別:%s\n",sex); printf("\n"); }

#自分で一部書き換えたプログラム

#include<stdio.h> #include<string.h> typedef struct { unsigned char name[256]; unsigned int age; unsigned int sex; }People; void InputPeople(People *data); void ShowPeople(People data); int main(void) { People data[3]; int i; for (i = 0; i < 3; i++) { InputPeople(&data[i]); } for (i = 0; i < 3; i++) { ShowPeople(data[i]); } return 0; } void InputPeople(People *data) { int i,j, k; char sex[16]; do { printf("名前を入力してください:"); scanf_s("%s", data->name,256); printf("%sでよろしいですか??(「合ってます」:「1」を入力、「間違いました」:「1以外」を入力)\n", data->name); scanf_s("%d", &i); } while (i != 1); do { printf("年齢を入力してください:"); scanf_s("%d", &data->age); printf("%dでよろしいですか??(「合ってます」:「1」を入力、「間違いました」:「1以外」を入力)\n", data->age); scanf_s("%d", &j); } while (j != 1); do { printf("性別を入力してください(「男性」:「1」を入力、「女性」:「2」を入力):"); scanf_s("%d", &data->sex); if (data->sex == 1) { strcpy_s(sex, 16, "男性"); } else if(data->sex == 2) { strcpy_s(sex, 16, "女性"); } else { continue; } printf("%sでよろしいですか??(「合ってます」:「1」を入力、「間違いました」:「1以外」を入力)\n", sex); scanf_s("%d", &k); } while (k != 1); printf("\n"); } void ShowPeople(People data) { char sex[16]; printf("名前:%s\n", data.name); printf("年齢:%d\n", data.age); if (data.sex == 1) { strcpy_s(sex, 16, "男性"); } else { strcpy_s(sex, 16, "女性"); } printf("性別:%s\n", sex); printf("\n"); }

**「自分で一部書き換えたプログラム」**に穴や間違いがありましたら、教えて頂けると助かります。

(本記事は、苦しんで覚えるc言語から一部引用しています。)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

LouiS0616

2019/01/05 11:50

・コードブロックを利用してください。 コードが読みづらいです。次のgif画像に従って質問を編集して下さい。 https://teratail.storage.googleapis.com/uploads/contributed_images/4c6e48a3bd0707d89f61b901fd1f8915.gif ・観点を追記してください。 どのような経緯で著者の回答を改造したのかいまいちわかりません。 改変した部分とその理由を追記してください。理由は詳しい方が良いですが、必ずしも厳密なものでなくても構いません。
bochan2

2019/01/05 11:57

質問いただきありがとうございます! 自分で行った確認作業(デバッグ等)について追記頂けると回答の役に立つと思います
guest

回答5

0

i, j, k はマズいね。その名前が意味を持たないから。
あとで読み返すことを考えた方がいい。体を表す名を与えよ。

※ ~CheckFlag は感心しない。この文脈では validName, validGender とかそんなカンジ。

投稿2019/01/12 11:09

編集2019/01/12 11:11
episteme

総合スコア16614

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Akihiro_O

2019/01/14 02:03

大変貴重なアドバイスをありがとうございます。 勉強になりました。
guest

0

ベストアンサー

入力チェック用のフラグ変数にi,j,kといった名称を使うのはお勧めしません。

nameCheckFlag, sexCheckFlagなど意味のある変数名にした方がよいでしょう。
変数や関数の名前ってソースの可読性に大きな影響がありますよ。
リーダブルコードなどの書籍を読むと勉強になると思います。

正しく入力されたか確認を取るために、

自分がやるとすると1項目ずつ確認を入力させるというやり方はとらないと思います。
プログラムを使う側の立場に立ってみるとイライラすると思いませんか?
すべての項目を入力した後にOKかどうか再入力する項目の確認を促すようにすると思います。

投稿2019/01/12 10:52

TaroToyotomi

総合スコア1430

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Akihiro_O

2019/01/14 02:04

貴重なアドバイスを有難うございます。
guest

0

手軽にチェックする方法にはつぎのものがあります。

  • cppcheck などのフリーの静的解析を使用する
  • コンパイラの警告オプションを変更する

投稿2019/01/12 10:19

fu7mu4

総合スコア1088

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

全般に冗長にしただけのイメージです。
元のコードもどうかなって気がしますが、、、気になったところを

  • 入力の確認

while (i == 2);

3 とか入れたら、どうなるでしょうか? 多分、そのまま、OKですね。 良いですか?
(他も同様)

  • 性別の判定

if (data->sex == 1) {

個々も同様です。 1,2 以外はどうしましょう?

  • 文字列コピー

strcpy_s(sex, 16, "男性");

sex の文字列は表示でしか使っていません。
char *sex; として、sex = "男性"; で十分じゃないですか?

気が付いたところで。

投稿2019/01/07 12:17

pepperleaf

総合スコア6383

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Akihiro_O

2019/01/10 09:00

ご返信有難うございます。 勉強になりました。
guest

0

入力の検証をしたいようですが、そもそもscanf系関数で数値読み込みすること自体が問題です。

C言語で安全に標準入力から数値を取得

を見てください。

あとagesexint型なのが個人的にはどうかと思うので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言語」のとあるコードをもっと安全に、より良く

投稿2019/01/05 12:37

編集2019/01/14 11:20
yumetodo

総合スコア5850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Akihiro_O

2019/01/07 10:25

回答有難うございます。大変参考になりました。
Akihiro_O

2019/01/14 03:06

有難うございます。 自習に使用させて頂きます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問