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

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

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

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

Q&A

解決済

3回答

2013閲覧

可変配列においてのメモリ取得、解放について

yowashi

総合スコア19

C

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

0グッド

0クリップ

投稿2017/07/12 08:30

###前提・実現したいこと
お世話になります・・・
いくつかの数字を入力して、隣り合う二数が連続した数であるならばその二数をぬいてその処理がなくなるまで行い、結果を出す。というプログラムを考えています。
構造体を勉強したため、何気なくで組んでみましたが、うまく動作せず、見にくいコードになってしまいました。
###発生している問題・エラーメッセージ
隣り合う二数の比較に構造体のメンバをそのまま使っているのですがわかりにくいコードであること。
処理後つなぎ合わせているため、メモリ解放するためにもう一度一からつながなければならないのか。

###該当のソースコード

C

1 2#include<stdio.h> 3#include<stdlib.h> 4#define MAXLINE 11 5 6void get_num(char ary[]); 7 8 9typedef struct _number 10{ 11 short int num; //数字格納 12 struct _number *prev; //ひとつ前の構造体へのポインタを格納 13 struct _number *next; //次の構造体へのポインタを格納 14}array; 15 16 17int main() 18{ 19 int i; //配列の添え字 20 array *p, *start; //アドレスのp、構造体の先頭 21 array *q; //解放のアドレス 22 char num[MAXLINE]; //getchar()の数字を格納 23 short int cnt = 1; //数列整理のカウンタ 24 25 for (i = 0; i < 10; i++) 26 { 27 if (i == 0) 28 { 29 p = malloc(sizeof(array)); //構造体のメモリを確保 30 if (p == NULL) 31 { 32 printf("memory allocattion error\n"); 33 return EXIT_FAILURE; 34 } 35 start = p; //基準、始のアドレスを残しておく 36 p->prev = NULL; 37 38 get_num(num); 39 } 40 else 41 { 42 p->next = malloc(sizeof(array)); //次の構造体用のメモリ確保 43 if (p == NULL) 44 { 45 printf("memory allocattion error\n"); 46 return EXIT_FAILURE; 47 } 48 p->next->prev = p; //次の構造体の前へのポインタに現在のアドレス 49 p = p->next; 50 } 51 p->num = num[i]; //構造体の数字格納メンバに数字を格納 52 } 53 p->next = NULL; //終端NULL 54 55/*数列の隣り合う要素を比較して連続数ならその二つの要素をとばす*/ 56 p = start; 57 while (cnt != 0) 58 { 59 if (p->next==NULL) 60 p = start; 61 cnt = 0; 62 if ((p->num + 1 == p->next->num) || (p->num == p->next->num + 1)) 63 { 64 if (p == start) { 65 p = p->next->next->next; 66 cnt++; 67 } 68 else { 69 p->prev->next = p->next->next; 70 cnt++; 71 } 72 } 73 p = p->next; 74 } 75 76 p = start; 77 while (1) 78 { 79 printf(" %d", p->num); 80 p = p->next; 81 if (p->next == NULL) 82 break; 83 } 84 85 86 87 /*動的確保したメモリの解放*/ 88 p = start; //保存していた基準のアドレスに 89 while (p != NULL) //終端NULLになるまで 90 { 91 q = p; //解放用でまわすqにp(pのメモリを消す為) 92 p = p->next; //次に移動 93 free(q); //解放 94 95 } 96 97 return 0; 98} 99 100 101void get_num(char ary[]) //数字の文字列を格納する関数 102{ 103 short int k, c; 104 for (k = 0; k <10 && (c = getchar()) != '\r'; k++) 105 ary[k] = c - '0'; 106 ary[k] = '\0'; 107 108 while (getchar() != '\n'); 109} 110 111

今回聞きたくて該当になる部分はここになります。

/*数列の隣り合う要素を比較して連続数ならその二つの要素をとばす*/ p = start; while (cnt != 0) { if (p->next==NULL) p = start; cnt = 0; if ((p->num + 1 == p->next->num) || (p->num == p->next->num + 1)) { if (p == start) { p = p->next->next->next; cnt++; } else { p->prev->next = p->next->next; cnt++; } } p = p->next; } p = start; while (1) { printf(" %d", p->num); p = p->next; if (p->next == NULL) break; }

###試したこと
当初は配列で考えたのですが、処理後配列の長さが変わるということで可変型が言いと思い、構造体にしました。
このコードで 0123456789 と入力すると 01236 となるため、まだ変数の扱いが間違っているのは把握してます。

うまい表現方法などがあれば教えていただけると幸いです。

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

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

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

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

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

guest

回答3

0

当初は配列で考えたのですが、処理後配列の長さが変わるということで可変型が言いと思い、構造体にしました。

この文では読んだ人は「???」となります、正しくは構造体ではなくリスト構造ですね。

やりたい事は連続した数を抜くということなので、データとしては増える事はなく減る一方の処理ですね。
それならばリスト構造ではなく配列(文字列)で扱った方がシンプルになると思います。

このプログラムではリスト構造を用いる必要もなければ、メリットもありません。
配列(文字列)のまま扱うべきでしょう。


書いてみました。

C

1#include<stdio.h> 2#include<stdlib.h> 3#define MAXLINE 11 4 5int main() 6{ 7 int i, j; 8 char num[MAXLINE]; 9 char work[MAXLINE]; 10 11 strcpy(num, "2563579015"); 12 13 while(1){ 14 j = 0; 15 for(i = 0; i <= strlen(num); i++) { 16 if( num[i+1] == '\0' || num[i] + 1 != num[i+1]) { 17 work[j] = num[i]; 18 j += 1; 19 } else { 20 i += 1; 21 } 22 } 23 work[j] = '\0'; 24 if(strcmp(num, work) == 0) break; 25 strcpy(num, work); 26 } 27 28 printf(num); // 5795 29}

投稿2017/07/12 09:56

編集2017/07/13 03:06
pashango2

総合スコア930

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

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

yowashi

2017/07/13 00:08

回答ありがとうございます。そうですね。言われて、調べて確認しました。リスト構造体というものですね。 配列で扱ってみたいのですが、配列は要素を削ったりすることができるのでしょうか。 それとも配列(文字列)とおっしゃられている意味というのは、数字の並びを文字列と考えstr○○関数を駆使して分解、再構成するということでしょうか。 無知で申し訳ないですが、足りない部分がありましたらそのテーマ、単元でも教えていただけると幸いです。
pashango2

2017/07/13 01:36

要素を削るというのは適当ではありません、配列の範囲内ならば増減はできます。 文字列の範囲はヌルターミネートといって\0がきたら終了となります、配列の範囲内(MAX_VALUE)であれば要素を削る(正確には配列の範囲内で終端を短くする)ことはできます。
pashango2

2017/07/13 01:37

ちなみに私がやるのであればstr○○関数は一つも使いません。
yowashi

2017/07/13 01:54

なるほど、配列で確保した範囲の増減ですね。 その方法のときに、メモリ管理に重きを置く場合、たとえば要素数が千以上などの膨大な桁になった時、使用メモリは大きくなりますよね、処理速度はおそらく配列の方が速いのだと思いますが、処理とメモリを並べたときに、その使用メモリの多さというのは目をつぶれる範疇にあるのでしょうか。 うまく伝えられずにすみません。
pashango2

2017/07/13 02:02

まず要素をどのようにアクセスするかによります。 シーケンシャルなアクセスであれば、リスト構造でも良いでしょう。 ランダムアクセスが何回も発生する場合はリスト構造は非常に遅くなります。 処理に見合ったデータ構造を選択することが重要です。 銀の弾丸はありませんので、メリットデメリットを熟知している技術者が判断を下すべきです。 コード組むだけなら誰でも出来ます、逆に言えばその判断のバランス感覚こそが技術者の手腕といっても良いでしょう。
pashango2

2017/07/13 02:19 編集

あと普通にリスト構造の作成時にバグがありますよ。 p->next = malloc(sizeof(array)); //次の構造体用のメモリ確保 とありますが、この後の if (p == NULL) は意味がありません。 コピペをしているからだと思いますが、あまり良くないコードですね。
yowashi

2017/07/13 02:25

勉強になります。ケースバイケースを念頭においてどのアルゴリズムをするか考えるようにします。 コードについては、こちらでまだまともに動いていなくバグがあるのは承知です。 なるほど、確かに 次の なのにpがNULLなはずがないですね。 p->next = malloc(sizeof(array))でp->nextのarray構造体の箱がとられて、そのメンバのprevに現在のpの値が入ると思っていたのですが、まだ値が決まっていないんですね。
pashango2

2017/07/13 02:43 編集

すみません、質問なのですが0123456789 と入力されたら正しい答えは何ですか? 012345678と入力したときの答えもお願いします。
yowashi

2017/07/13 02:46

問題定義があやふやですみません。隣り合う二数を比較して連続なら抜く、この作業を繰り返して、この作業がなくなったら表示。というものなので、 自分の想定している答えは0123456789ですと、何も表示されずに終了。です。 もうひとつ、2563579015ですと、5795 と表示されて終了するものを作成するつもりです。
yowashi

2017/07/13 02:51

012345678ですと8と表示で終了の予定です。
pashango2

2017/07/13 03:00

あまり答えは書くべきではないかと思いましたが、リスト構造をやめるとこんなにスッキリするという事で・・・ 処理にあったデータ構造にすることが肝心です。
yowashi

2017/07/13 03:10

解答ありがとうございます。すごいスッキリするんですね。 ただ、今回自分の中で標準入力からの不特定な数字であること、構造体を理解するということを自己課題にしてやっているので、もう少し自分で考えて見ます。
guest

0

連続する二整数が現れたら,現れた時点でリストを繋ぎなおして,その後すぐにfree()してしまうべきでしょうね。
このままではメモリリークを起こしてしまいます。
一度リストから外れた要素は(別に記録しておかない限りは)もう二度と参照する方法がありませんから,後でまとめて削除することもできません。

また,このようなリストの場合,敢えて先頭にはデータを持たせないでダミーとするのもありかもしれません。
今は先頭を削除するときはstartを書き換えなければならないので先頭と途中とで場合分けをしなければなりませんが,もし先頭をダミーの要素にしておけば,データの先頭も途中と同じように見なせます。表示するときはデータは start->next から読み始めればよいですし。

それで質問に関係のない部分なのですが少し。

main() 内でのfor文が10回回されていることから考えて,入力は10文字固定なんですよね。

  • 9文字しか来ない等の不正な入力を考えなくてよいならば,

get_num() 関数内では単純に for を 10 回回せばよいと思います。getchar()を使って判定する必要はないかと。
ついでに,配列をNULL終端させる意味はないと思います。

  • 不正な入力を考えるならば,

get_num() 関数の中の (c = getchar()) != '\r'に関してですが,(バイナリファイルなら別として)入力は勝手にCRLF(\r\n)LF(\n)に変換してくれる場合がほとんどだと思うので,チェックしたいなら(c = getchar()) != '\n'にするべきなのではないでしょうか?
また,配列を初期化しておかないと,10文字に満たなかった場合に,配列の後ろの残りが不正値になります。
さらに,チェックを'\n'に変更した場合は,最後のwhile(getchar() != '\n')を常に実行させると困ったことが起こります。10文字に満たなかった場合に,入力の'\n'がfor文の中で消費されてしまっていますので,もう一度改行を入力する羽目になります。

また,最後に表示させている部分の while なのですが,このままではリストの中身が全て無くなってしまった場合に落ちます。

投稿2017/07/13 04:12

Eki

総合スコア429

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

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

yowashi

2017/07/13 04:32

なるほど、ダミーをいれるのはしっくりきました。試してみます。 そうですね、getchar()の判定はLFでした。おっしゃるとおり、while文でまた入力することになったため消しました。 残りの配列部分は¥0で終端決めて、と考えていましたがfor文の10回固定では初期化必要ですね。考え直します。 表示させているwhileもおっしゃるとおり落ちました、startの値を適宜変えて、要素がない時、nullの時以外の実行でまわすようにします。
guest

0

自己解決

処理の後にそれぞれfreeで解放することでおそらくメモリ解放は解決すると思います。
処理後のstartのポインタの中身等、まだ穴だらけではありますが、考えていきます。

ケースバイケースを改めて知ることができました。ありがとうございます。

投稿2017/07/13 03:13

yowashi

総合スコア19

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問