ページ 1 / 1
構造体配列
Posted: 2014年2月22日(土) 23:22
by turasan
構造体配列のデータに対して、そのデータを更新、追加、削除ができるプログラムを作っているのですが、
1ヶ月かけて悩んでいます...。
コード:
typedef struct {
char* name; /* 名前 */
int height; /* 身長 */
int weight; /* 体重 */
} List;
List members[30] = /*構造体配列*/
{
{"A",180,60},
{"B",170,50},
{"C",165,45}
};
としたときに、更新(データの変更)はポインタを使ってうまくいくのですが、
追加、削除のプログラムがうまくいきません。
悩みは、
①上記の構造体配列において、配列の要素がまず3つあり、残りの27個が余っていますよね、、。この余った27個のところにデータを追加していくにはどのような手段が考えられるでしょうか。
②また、削除するにはどうしたらよいでしょうか。
です。
よろしくお願いいたします。
Re: 構造体配列
Posted: 2014年2月22日(土) 23:26
by みけCAT
要素数が30くらいでしたら、「今何個の要素が入っているか」という情報を変数で管理し、
追加は単純にデータが埋まっている場所の後ろに書き込み、要素数の変数をインクリメントする : O(1)
削除は削除する要素の後ろの要素すべてを1個前に移動し、要素数の変数をデクリメントする : O(N)
という方法がいいと思います。
要素数がもっと多くなった(1万~10万程度)場合は、平衡二分探索木を利用するのがいいと思います。
Re: 構造体配列
Posted: 2014年2月22日(土) 23:53
by turasan
初学者で、初歩的な質問になりますがすみません。
「データが埋まっている場所の後ろに書き込む」場合、
たとえば単純に、
コード:
members[3] = {"D", 176, 54};
と書くと、エラーになるのですが、どのようにすればよいでしょうか。
Re: 構造体配列
Posted: 2014年2月23日(日) 00:08
by みけCAT
仕様です。素直に1個ずつ書き込んでください。
コード:
members[3].name="D";
members[3].height=176;
members[3].weight=54;
または、「代入」処理を関数化したり、
コード:
void assignValueToList(List* lst,char* name,int height,int weight) {
lst->name=name;
lst->height=height;
lst->weight=weight;
}
assignValueToList(&members[3],"D",176,54);
値を指定できるコンストラクタを作るのもいいでしょう。
コード:
typedef struct list_t {
char* name; /* 名前 */
int height; /* 身長 */
int weight; /* 体重 */
list_t(){}
list_t(char* n,int h,int w):name(n),height(h),weight(w){}
} List;
/* members配列の宣言と初期化は同様でよい */
members[3]=List("D",176,54);
Re: 構造体配列
Posted: 2014年2月23日(日) 01:07
by turasan
作ってみたのですが、うまくいかない部分があります。
削除の関数はまだ作っていません。
少し長いです、申し訳ありません。
AddDataの関数のところです。
最後の要素の次の場所に、新たな要素を追加したいのですが、うまくいきません。if文をどうかけば、その場所を指定できるのでしょうか。
NULLが入っていると思い、NULLと書いたのですが..。
コード:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct {
char* name; /* 名前 */
int height; /* 身長 */
int weight; /* 体重 */
} List;
/*関数プロトタイプ宣言*/
void Display(char *inputname, List *members); //ユーザーが指定した人のデータを表示
void Change(char *inputname, List *pmembers); //データの更新
void AddData(List *pmembers); //データを追加
void AllDisplay(List *pmembers); //データをすべて表示
int kazu = 3; //現在の要素数
int main(void)
{
List members[100] = /*構造体配列*/
{
{"A",190,40},
{"B",167,61},
{"C",187,45}
};
char inputname[10]; /*ユーザー入力保存用*/
int select = 0;
List *pmembers;
pmembers = members; /*構造体配列の最初のアドレスを入れる*/
while(1){
printf("操作を選択してください:1.追加 2.更新 3.削除 4.終了\n");
scanf("%d", &select);
if(select == 1){
AddData(pmembers);
} else if(select == 2){
printf("更新したい人の名前を入力してください\n");
scanf("%s", inputname); /*ユーザーが入力*/
Display(inputname, pmembers);
Change(inputname, pmembers);
}else if(select == 3){
AllDisplay(pmembers);
}else if(select == 4){
printf("\n終了します\n");
break;
}
}
return 0;
}
void Display(char *inputname, List *pmembers) /*ユーザーが入力した人物のデータを表示*/
{
while(1){
if(strcmp(pmembers->name, inputname) == 0){ /*ユーザー入力と構造体配列の名前が一致したら*/
printf("%sの身長は%d,体重は%dです。\n", pmembers->name, pmembers->height, pmembers->weight);
break;
}
pmembers++; /*一致しなければ次の配列へ*/
}
return;
}
void AllDisplay(List *pmembers)
{
int i;
for (i = 0; i < kazu; i++){
printf("名前:%s 身長:%d 体重:%d\n", pmembers->name, pmembers->height, pmembers->weight);
pmembers++;
}
return ;
}
void Change(char *inputname, List *pmembers)
{
int shincho = 0;
int taiju = 0;
printf("身長は何にしますか。:");
scanf("%d", &shincho);
printf("体重は何にしますか。:");
scanf("%d", &taiju);
while(1){
if(strcmp(pmembers->name, inputname) == 0){ /*ユーザー入力と構造体配列の名前が一致したら*/
pmembers->height = shincho; /*その人物の身長を更新*/
pmembers->weight = taiju; /*その人物の体重を更新*/
break;
}
pmembers++; /*一致しなければ次の配列へ*/
}
printf("更新されました。\n");
return;
}
void AddData(List *pmembers) /*データを追加*/
{
char addname[20];
int addheight = 0;
int addweight = 0;
while(1){
if(pmembers->name = NULL){ /*←ここがうまくいきません*/
printf("追加したい人の名前/身長/体重を入力してください\n");
printf("名前:"); scanf("%s", addname);
printf("身長:"); scanf("%d", &addheight);
printf("体重:"); scanf("%d", &addweight);
pmembers->name = addname;
pmembers->height = addheight;
pmembers->weight = addweight;
printf("追加しました\n");
kazu++;
break;
}
pmembers++;
}
return;
}
Re: 構造体配列
Posted: 2014年2月23日(日) 08:52
by box
turasan さんが書きました:
NULLが入っていると思い、
そうとは限りません。明示的に初期化していない場合、初期値はゴミです。
何が入っているかは不定です。
turasan さんが書きました:
コード:
if(pmembers->name = NULL){ /*←ここがうまくいきません*/
このコードでは「NULLを代入」していますが、大丈夫ですか?
Re: 構造体配列
Posted: 2014年2月23日(日) 09:31
by たいちう
> そうとは限りません。明示的に初期化していない場合、初期値はゴミです。
> 何が入っているかは不定です。
初期化しているでしょ。
コード:
List members[100] = /*構造体配列*/
{
{"A",190,40},
{"B",167,61},
{"C",187,45}
};
配列の最初の3つがデータを与えられていて、残りの97個は0で初期化されます。
nameはNULLになっているはず。(if文でNULLを代入しているのは当然誤りですが)
解決方法についてですが、
AddDataのローカル変数であるaddnameのアドレスを構造体に代入しても意味がありません。
配列の4つめ以降のnameはNULLで初期化されていて、文字列を保持するための領域がありません。
mallocで領域を確保して、そこにstrcpyで代入する必要があります。
構造体を変更しても良い場合、文字数の制約はありますが、次にように定義すると、
mallocは必要なくなります。
コード:
typedef struct {
char name[20]; /* 名前 */
int height; /* 身長 */
int weight; /* 体重 */
} List;
Re: 構造体配列
Posted: 2014年2月23日(日) 13:01
by turasan
mallocで次々と領域を確保していった場合、
追加はできますが、一つ一つ任意のデータを削除できるものなのでしょうか。
mallocで新たな領域を確保した場合、それは
コード:
S_MEMBERS members[100] = /*構造体配列*/
{
{"A",180,60},
{"B",170,50},
{"C",165,45}
};
における、
残り97個の配列とは別に新たな領域を確保しているんですよね、、。
とりあえず、AddData関数でmallocを使用して作ってみました。
コード:
void AddData()
{
char addname[20];
int addheight = 0;
int addweight = 0;
printf("追加したい人の名前/身長/体重を入力してください\n");
printf("名前:"); scanf("%s", addname);
printf("身長:"); scanf("%d", &addheight);
printf("体重:"); scanf("%d", &addweight);
S_MEMBERS *heap;
heap = (S_MEMBERS*)malloc(sizeof(S_MEMBERS));
strcpy(heap[0].name, addname);
heap[0].height = addheight;
heap[0].weight = addweight;
printf("追加しました\n");
kazu++;
return;
}
このようにAddData関数を書き換えました。
AddData関数をmainで呼び出すたびに領域を一つ確保し、そこにユーザーが入力したデータを挿入したいという意図なのですが、うまくいきません..。どこが間違っているのでしょうか。
Re: 構造体配列
Posted: 2014年2月23日(日) 13:20
by みけCAT
heapの情報をどこにも保存していないので、
せっかく確保したメモリの位置を投げ捨てることになってしまっています。(メモリリーク)
Re: 構造体配列
Posted: 2014年2月23日(日) 13:59
by turasan
一度書き直しました。
void AllDisplay(S_MEMBERS members[])によって、すべての入力したデータを表示させたいのですが、
このコードをコンパイルすると、
void AddData()内において、mallocによって確保した領域に挿入したデータを、AllDisplayで表示させることができません。どのような原因が考えられますでしょうか。質問攻めですみません。
コード:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct {
char name[20]; /* 名前 */
int height; /* 身長 */
int weight; /* 体重 */
} S_MEMBERS;
/*関数プロトタイプ宣言*/
void Display(char *inputname, S_MEMBERS *members);
void Change(char *inputname, S_MEMBERS *pmembers);
void AddData();
void AllDisplay(S_MEMBERS members[]);
int kazu = 3;
int main(void)
{
S_MEMBERS members[100] = /*構造体配列*/
{
{"A",180,60},
{"B",170,50},
{"C",165,45}
};
char inputname[10]; /*ユーザー入力保存用*/
int select = 0;
S_MEMBERS *pmembers;
pmembers = members; /*構造体配列の最初のアドレスを入れる*/
while(1){
printf("操作を選択してください:1.追加 2.更新 3.すべてのデータを表示 4.終了\n");
scanf("%d", &select);
if(select == 1){
AddData();
} else if(select == 2){
printf("更新したい人の名前を入力してください\n");
scanf("%s", inputname); /*ユーザーが入力*/
Display(inputname, pmembers);
Change(inputname, pmembers);
}else if(select == 3){
AllDisplay(members[);
}else if(select == 4){
printf("\n終了します\n");
break;
}
}
return 0;
}
void Display(char *inputname, S_MEMBERS *pmembers) /*ユーザーが入力した人物のデータを表示*/
{
while(1){
if(strcmp(pmembers->name, inputname) == 0){ /*ユーザー入力と構造体配列の名前が一致したら*/
printf("%sの身長は%d,体重は%dです。\n", pmembers->name, pmembers->height, pmembers->weight);
break;
}
pmembers++; /*一致しなければ次の配列へ*/
}
return;
}
void AllDisplay(S_MEMBERS members[])
{
int i;
for (i = 0; i < kazu; i++){
printf("名前:%s 身長:%d 体重:%d\n", members[i].name, members[i].height, members[i].weight);
}
return ;
}
void Change(char *inputname, S_MEMBERS *pmembers)
{
int shincho = 0;
int taiju = 0;
printf("身長は何にしますか。:");
scanf("%d", &shincho);
printf("体重は何にしますか。:");
scanf("%d", &taiju);
while(1){
if(strcmp(pmembers->name, inputname) == 0){ /*ユーザー入力と構造体配列の名前が一致したら*/
pmembers->height = shincho; /*その人物の身長を更新*/
pmembers->weight = taiju; /*その人物の体重を更新*/
break;
}
pmembers++; /*一致しなければ次の配列へ*/
}
printf("更新されました。\n");
return;
}
void AddData()
{
char addname[20];
int addheight = 0;
int addweight = 0;
printf("追加したい人の名前/身長/体重を入力してください\n");
printf("名前:"); scanf("%s", addname);
printf("身長:"); scanf("%d", &addheight);
printf("体重:"); scanf("%d", &addweight);
S_MEMBERS *heap;
heap = (S_MEMBERS*)malloc(sizeof(S_MEMBERS));
strcpy(heap[0].name, addname);
heap[0].height = addheight;
heap[0].weight = addweight;
printf("追加しました\n");
free(heap);
kazu++;
return;
}
Re: 構造体配列
Posted: 2014年2月23日(日) 14:03
by みけCAT
freeによりメモリリークは解消しましたが、せっかく読み込んだデータ(の領域(の有効性))をすぐに消してしまっているので、
AllDisplayで表示できないのはアタリマエです。
Re: 構造体配列
Posted: 2014年2月23日(日) 14:21
by turasan
できました!!!
次、削除関数つくります...。すみません
Re: 構造体配列
Posted: 2014年2月23日(日) 18:25
by turasan
ご助力お願いします。
構造体配列の要素に対する、削除用の関数を作りました。
自分の意図としては、ユーザーが削除したい人の名前を入力したら、
その要素を抜かした構造体を作り、その構造体をまるまる元の構造体にコピーしようというものです。
for文のなかにある、if文において、
その人の要素に対し、+1の次の要素を挿入することで、
その人の要素を削除しようとしています。
この操作を次の要素にも次々おこなうことで、copymembersという構造体配列は、削除したい人を抜かした構造体となっているはずなのですが、うまくいってないようです。
うまく行っていない原因はどこにあるでしょうか、、。
※kazu はグローバル変数でkazu = 3;です。
コード:
void Delete(S_MEMBERS members[])
{
char deletename[20];
S_MEMBERS copymembers[30];
int k;
int c;
printf("誰を削除しますか?");
printf("名前:");
scanf("%s", &deletename);
for (k = 0; k < kazu; k++){
strcpy(copymembers[k].name, members[k].name);
copymembers[k].height = members[k].height;
copymembers[k].weight = members[k].weight;
if(strcmp(members[k].name, deletename) == 0){
for(c = k; c < kazu - 1; c++ ){
strcpy(copymembers[c].name, members[c+1].name);
copymembers[c].height = members[c+1].height;
copymembers[c].weight = members[c+1].weight;
}
break;
}
kazu--;
members = copymembers;
return ;
}
}
Re: 構造体配列
Posted: 2014年2月23日(日) 18:26
by みけCAT
配列を=演算子でコピーすることはできない気がします。
素直にmembers配列を直接書き換えるべきだと思います。
Re: 構造体配列
Posted: 2014年2月23日(日) 19:23
by dic
いろいろbreak や return で試行錯誤されたようですね。
kazu = 3 ととても小さい数なので、削除したい構造体のインデックスを
0~2と代入して、一回一回紙にかいてみるとか
ブレークポイントとかを使うとかしてみて、一個ずつ解決してください。
一応できたものを残しておきます。
► スポイラーを表示
コード:
void Delete(S_MEMBERS members[])
{
char deletename[20];
S_MEMBERS copymembers[30]; // <- main関数ではサイズが100なのでサイズが異なる
int k;
int c;
printf("誰を削除しますか?");
printf("名前:");
scanf("%s", &deletename);
for (k = 0; k < kazu; k++){
copymembers[k] = members[k];
if(strcmp(members[k].name, deletename) == 0){
for(c = k; c < kazu; c++ ){
copymembers[c] = members[c+1];
}
// break;
for( int i=0; i<kazu; i++ )
members[i] = copymembers[i];
kazu--;
}
else
{
if( k == kazu - 1 )
printf( "指定した名前はありません\n" );
}
}
}
コード:
strcpy(copymembers[k].name, members[k].name);
copymembers[k].height = members[k].height;
copymembers[k].weight = members[k].weight;
は
コード:
copymembers[k] = members[k];
でOKです。
Re: 構造体配列
Posted: 2014年2月23日(日) 19:48
by turasan
完成しました!!一応、目的達成です!
ありがとうございます、、。みなさんのおかげです
泣きそうです。。
いくつか疑問あるので質問させてください。
以下が僕の完成版のコードなのですが、
コード:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct {
char name[20]; /* 名前 */
int height; /* 身長 */
int weight; /* 体重 */
} S_MEMBERS;
/*関数プロトタイプ宣言*/
void Display(char *inputname, S_MEMBERS *members); /*特定の人のデータを表示*/
void Change(char *inputname, S_MEMBERS *pmembers); /*データを更新*/
void AddData(S_MEMBERS members[]); /*データを追加*/
void AllDisplay(S_MEMBERS members[]); /*すべてのデータを表示*/
void Delete(S_MEMBERS members[]); /*データを削除*/
int kazu = 3; /*構造体配列の現在の要素数*/
int main(void)
{
S_MEMBERS members[100] = /*構造体配列*/
{
{"A",180,60},
{"B",170,50},
{"C",165,45}
};
char inputname[10]; /*ユーザー入力保存用*/
int select = 0; /*メニュー選択用*/
S_MEMBERS *pmembers;
pmembers = members; /*構造体配列の最初のアドレスを入れる*/
while(1){
printf("---------------------------------------------------------------------------\n");
printf("操作を選択してください:1.追加 2.更新 3.すべてのデータを表示 4.削除 5.終了\n"); /*メニュー*/
printf("---------------------------------------------------------------------------\n");
scanf("%d", &select);
if(select == 1){ /*入力が1ならデータを追加*/
AddData(members);
} else if(select == 2){ /*入力が2なら任意のデータを更新*/
printf("更新したい人の名前を入力してください\n");
scanf("%s", inputname); /*ユーザーが入力*/
Display(inputname, pmembers); /*まず更新したい人の現在のデータを表示させて*/
Change(inputname, pmembers); /*そのあと更新*/
}else if(select == 3){ /*入力が3ならすべてのデータを表示させる*/
AllDisplay(members);
}else if(select == 4){ /*入力が4なら任意のデータを削除*/
Delete(members);
}else if(select == 5){ /*入力が5なら終了*/
printf("\n終了します\n");
break;
}
}
return 0;
}
void Display(char *inputname, S_MEMBERS *pmembers) /*特定の人のデータを表示*/
{
while(1){
if(strcmp(pmembers->name, inputname) == 0){ /*ユーザー入力と構造体配列の名前が一致したら*/
printf("%sの身長は%d,体重は%dです。\n", pmembers->name, pmembers->height, pmembers->weight); /*その人物の名前を表示*/
break;
}
pmembers++; /*一致しなければ次の配列へ*/
}
return;
}
void AllDisplay(S_MEMBERS members[]) /*すべてのデータを表示*/
{
int count;
for (count = 0; count < kazu; count++){
printf("名前:%s 身長:%d 体重:%d\n", members[count].name, members[count].height, members[count].weight);
}
putchar('\n');
return ;
}
void Change(char *inputname, S_MEMBERS *pmembers) /*データを更新*/
{
int shincho = 0; /*体重*/
int taiju = 0; /*身長*/
printf("身長は何にしますか。:");
scanf("%d", &shincho);
printf("体重は何にしますか。:");
scanf("%d", &taiju);
while(1){
if(strcmp(pmembers->name, inputname) == 0){ /*ユーザー入力と構造体配列の名前が一致したら*/
pmembers->height = shincho; /*その人物の身長を更新*/
pmembers->weight = taiju; /*その人物の体重を更新*/
break;
}
pmembers++; /*一致しなければ次の配列へ*/
}
printf("更新されました。\n\n");
return;
}
void AddData(S_MEMBERS members[]) /*データを追加*/
{
char addname[20]; /*名前*/
int addheight = 0; /*身長*/
int addweight = 0; /*体重*/
printf("追加したい人の名前/身長/体重を入力してください\n");
printf("名前:"); scanf("%s", addname);
printf("身長:"); scanf("%d", &addheight);
printf("体重:"); scanf("%d", &addweight);
S_MEMBERS *heap;
heap = (S_MEMBERS*)malloc(sizeof(S_MEMBERS)); /*名前用にmallocで領域を確保*/
strcpy(heap[0].name, addname); /*確保した領域に名前を入れておく*/
strcpy(members[kazu].name, heap[0].name); /*名前、身長、体重を構造体配列に追加*/
members[kazu].height = addheight;
members[kazu].weight= addweight;
printf("追加しました\n\n");
free(heap); /*確保した領域は解放*/
kazu++; /*データを追加したら次の要素にインクリメント*/
return;
}
void Delete(S_MEMBERS members[]) /*データを削除*/
{
char deletename[20]; /*削除したい人の名前*/
int deletecount1;
int deletecount2;
printf("誰を削除しますか?\n");
printf("名前:");
scanf("%s", &deletename);
for (deletecount1 = 0; deletecount1 < kazu; deletecount1++){ /*要素数分だけループ*/
if(strcmp(members[deletecount1].name, deletename) == 0){ /*もし削除したい人の名前が見つかったら*/
for(deletecount2 = deletecount1; deletecount2 < kazu; deletecount2++){ /*そのあとの配列をすべて一つデクリメントしてずらす*/
members[deletecount2] = members[deletecount2+1];
}
}
}
printf("削除されました。\n\n");
kazu--; /*削除したので要素数はひとつ減る*/
return ;
}
疑問はいくつかあります。
①一番最初の構造体配列で、char name[20]; と宣言していますが、これをchar *nameと 宣言するとコンパイルエラーになるのはどうしてなのでしょうか。。
②今僕が書いたコードはリスト構造で実現可能でしょうか。自己参照構造体を使うアルゴ リズムです。
③それから僕が書いたコードはもっとスマートにできるでしょうか。。自分で見ていてもっとスマートに書ける気がするのですが、他の書き方がないか、自分ならこうするとか、不自然な点をご指摘してくれるとありがたいです。。
Re: 構造体配列
Posted: 2014年2月23日(日) 21:13
by たいちう
> ①一番最初の構造体配列で、char name[20]; と宣言していますが、
> これをchar *nameと 宣言するとコンパイルエラーになるのはどうしてなのでしょうか。。
コンパイルエラーではなく、実行時エラーですよね。
136行目のstrcpyで、コピー先の領域が存在しないためエラーになります。
その前のmallocで用意しているのは、構造体の領域。
nameはその構造体のメンバで、ポインタ。
このポインタは初期化されていないので、あさっての方向を指していて、
そこに無理やり書き込もうとしているので実行時エラーです。
構造体の宣言をchar name[20];としておくと、
構造体の領域をmallocで確保したときに、nameの20バイトも確保されるので、
このようなエラーはおきません。
文字の配列と、文字列へのポインタ(正確には文字列の先頭文字へのポインタ)の違いです。
> ②今僕が書いたコードはリスト構造で実現可能でしょうか。
> 自己参照構造体を使うアルゴ リズムです。
可能です。特に削除については、データを詰める処理が不要になります。
> ③それから僕が書いたコードはもっとスマートにできるでしょうか。。
> 自分で見ていてもっとスマートに書ける気がするのですが、他の書き方がないか、
> 自分ならこうするとか、不自然な点をご指摘してくれるとありがたいです。。
一番気になるのが、不要なmallocです。
必要な数(100個)の構造体を配列として用意しているのに、
わざわざmallocで新しい構造体を作っています。
未使用の構造体に直接書き込んだらいいのに、と思います。
また、pmembersという変数の存在も必要ありません。membersがそのまま使えます。
Re: 構造体配列
Posted: 2014年2月23日(日) 21:37
by turasan
ありがとうございます、、。
人に見てもらうと自分の気づかないことが多々あり、
大変勉強になりました。
指摘されたこと、今後に活かしたいと思います。
みなさんありがとうございました。
Re: 構造体配列
Posted: 2014年2月23日(日) 21:38
by turasan
ありがとうございました
Re: 構造体配列
Posted: 2014年2月23日(日) 22:23
by たいちう
> 指摘されたこと、今後に活かしたいと思います。
「今」活かしてもらえると思って指摘したのですが、、、
時間が許すならば、ですけどね。