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

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

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

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

C++

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

Q&A

解決済

3回答

10550閲覧

関数内では、グローバル変数は直接使わず、引数として渡して使ったほうが良いですか?

N---------

総合スコア46

C

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

C++

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

0グッド

0クリップ

投稿2018/12/07 15:07

シューティングゲームをc言語&openglで作っています。困ったことが起きたので質問させていただきます。

以下のようなコードがあったとします。

c

1MISSILE g_missile[10] 2int g_missile_num; 3 4void setMissile(MISSILE *missile, double x, double y, double angle, double speed) { 5 missile->x = x; 6 missile->y = y; 7 missile->angle = angle; 8 missile->speed = speed; 9 10 g_missile_num++; //ミサイルの数をカウントする 11} 12 13int main(){ 14 for(i=0; i<10; i++){ 15 setMissile(&missile[g_missile_num], 0, 0, 0, 0); 16 } 17 return 1; 18}

setMissile関数では、g_countというグローバル変数にmissileが作成された数を記録しています。

しかし、「グローバル変数は関数内で使わない。使うなら引数として渡して使う」という設計思想が世の中にはありますよね(あるきがする)。

では、上のコードはどのようにするのが良いのでしょうか?

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

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

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

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

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

LouiS0616

2018/12/07 15:21

ご提示のコードの場合、ミサイル数をカウントする必然性が無いように思えます。単に簡単な例を示しただけで、本当はもう少し煩雑なのでしょうか?
N---------

2018/12/07 15:36 編集

ミサイルを画面に描画するdrawMissile(MISSILE *missile)関数というのを使うときに、g_missile_num変数をつかいます。 すべてのミサイルを画面上に描画するために、drawMissile関数をミサイルの数だけループさせたいので (こんな感じのコード→for(i=0; i<g_missile_num; i++) drawMissile(missile[i]); )、その時に必要になります
guest

回答3

0

ベストアンサー

こんにちは。

しかし、「グローバル変数は関数内で使わない。使うなら引数として渡して使う」という設計思想が世の中にはありますよね(あるきがする)。

微妙に間違ってます。「なるべくグローバル変数は使わない。」です。(C/C++では関数外でグローバル変数を使うことは初期化を除きできません。)

で、なぜ「なるべくグローバル変数は使わない。」方が良いのかですが、最大の要因はアクセスする必要ない関数がアクセスできないようにしておけば、プログラマのミスにより「間違って」アクセスすることがないのでバグが格段に減るという経験則から来ています。グローバル変数は事実上全ての関数がアクセスできるので、バグの温床になりがちなのです。

ですので、ご提示のg_missileとg_missile_numを、アクセスする必要がない関数から「間違って」アクセスできないようにすれば、別にグローバル変数のままでも大差ありません。
C言語で可能な方法としては、下記のような方法が考えられます。

①アクセスする必要がある関数の数が少なければ、引数で渡すのが理想的です。

②そこそこ多数の関数がアクセスするなら、それらの関数を全て同じソース・ファイルに収めて、g_missileとg_missile_numをstatic変数とする方法もあります。

②更に多数の関数がアクセスするなら、g_missileとg_missile_numをextern宣言したヘッダを、これらをアクセスする必要がある関数群を定義した .cファイルでのみインクルードすることも視野に入ります。

なお、ご提示のような方法でグローバル変数とした場合の問題点としてもう1つあります。
下記は1つのセットと思います。

MISSILE g_missile[10]
int g_missile_num;

もし、このようなセットを複数設けたい場合(例えば、敵も同じようにミサイルを持つ場合や、協力プレイ可能なゲームで自機が同時に2機以上存在するなどなど)には、これでは非常に面倒です。
この2つを構造体にまとめ、これらをアクセスする関数addMissile(当該構造体*)みたいなものを設けます。addMissile()へg_missile_numのインクリメントを移動し、かつ、setMissile()を呼び出すと複数のセットを設けることが簡単になり、かつ、必要性の低い依存(setMissileがg_missile_numに依存)を切り離せます。

ちなみに、この発想を推し進めていくとyumetodoさんのようなコードもゴールの1つとなります。
実際の場面で、そこまでやるべきかどうかはケースバイケースですが。
汎用性は高いですがその分複雑です。汎用性と複雑さのトレードオフをどこにおくのかも結構重要です。経験的には仕様を満たす最小限から複雑さがどんと増える手前くらいで汎用化を留めておくとトータルの工数が最低になるように感じます。

投稿2018/12/07 18:28

Chironian

総合スコア23272

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

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

N---------

2018/12/08 03:37

回答ありがとうございます!助かります!
guest

0

ミサイルの本数が固定なら、最初から定数マクロを使っても良いのですよね。

C

1#define NUM_OF_MISSILES 10 2 3int main(void){ 4 MISSILE missile[NUM_OF_MISSILES]; 5 6 for(size_t i = 0; i < NUM_OF_MISSILES; i++) { 7 setMissile(&missile[i], 0, 0, 0, 0); 8 } 9 10 for(size_t i = 0; i < NUM_OF_MISSILES; i++) { 11 drawMissile(&missile[i]); 12 } 13 14 return 0; 15}

あるいは要素数を前以て保持しておくとか。

C

1int main(void){ 2 MISSILE missile[10]; 3 const size_t num_of_missiles = sizeof(missile) / sizeof(missile[0]); 4 5 for(size_t i = 0; i < num_of_missiles; i++) { 6 setMissile(&missile[i], 0, 0, 0, 0); 7 } 8 9 for(size_t i = 0; i < num_of_missiles; i++) { 10 drawMissile(&missile[i]); 11 } 12 13 return 0; 14}

ただしミサイルの本数を動的に変化させたい場合は、
yumetodoさんの回答のような方法を選ぶのが見通しが良いと思います。

投稿2018/12/07 16:28

LouiS0616

総合スコア35660

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

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

0

c

1#include <stdlib.h> 2#include <stdbool.h> 3typedef struct MISSILE { 4 double x; 5 double y; 6 double angle; 7 double speed; 8} 9typedef struct tag_missile_vec { 10 MISSILE* data; 11 size_t size; 12 size_t capacity; 13} missile_vec; 14 15missile_vec* missile_vec_new(size_t size, size_t capacity, double x, double y, double angle, double speed) { 16 if(capacity < size) return NULL; 17 missile_vec* missile; 18 if(NULL = (missile->data = malloc(capacity * sizeof(MISSILE))) return NULL; 19 for(size_t i = 0; i < size; ++i){ 20 MISSILE* m = &missile->data[i]; 21 m->x = x; 22 m->y = y; 23 m->angle = angle; 24 m->speed = speed; 25 } 26 missile->size = size; 27 missile->capacity = capacity; 28 return missile; 29} 30bool missile_vec_push(missile_vec* missile, const MISSILE* data) 31{ 32 if(NULL == missile || NULL == data) return false; 33 if(capacity < size + 1){ 34 const size_t new_capacity = ((SIZE_MAX / 2) < missile->capacity) ? SIZE_MAX : missile->capacity * 2; 35 if(NULL == (missile->data = realloc(missile->data, capacity * sizeof(MISSILE)))) return false; 36 missile->capacity = new_capacity; 37 } 38 missile->data[missile->size] = *data; 39 ++missile->size; 40 return true; 41} 42void missile_vec_delete(missile_vec* missile) 43{ 44 if(NULL != missile) free(missile->data); 45 free(missile); 46} 47void missile_vec_draw(missile_vec* missile) 48{ 49 if(NULL == missile) return; 50 //do something 51} 52int main(){ 53 missile_vec* missile = missile_vec_new(10, 20, 0, 0, 0, 0); 54 if(NULL == missile) return 1; 55 missile_vec_draw(missile); 56 missile_vec_delete(missile); 57 return 0; 58}

投稿2018/12/07 16:02

編集2018/12/07 16:18
yumetodo

総合スコア5850

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

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

yumetodo

2018/12/08 06:09

まあここまでやるならC++のstd::vectorを使うべきである
N---------

2018/12/09 07:17

回答ありがとうございます。 Malloc関数を使ったことがないので、yumetodoさんの回答を気に使い方を調べました。 missile_vec_new関数の引数にsize_t sizeとsize_t capacityがありますが、これは何のための引数なのですか? あとに、malloc(capacity * sizeof(MISSILE))というプログラムがでてきますが、なぜsizeof(MISSILE)にcapacityをかけるのですか?
N---------

2018/12/09 15:51 編集

上の質問ですが、考えたら分かりました。失礼しました。
N---------

2018/12/09 14:20

missile_vec_draw関数やmissile_vec_delete関数でif(NULL == missile)とありますが、これは何のためにあるのですか?missile_vec_new関数で返り値がNULLだった時を考慮しているからですか?
N---------

2018/12/09 15:52

もう一つ質問です。 missile_vec構造体で、dataをポインタとして宣言するのは何故ですか?
yumetodo

2018/12/09 16:36 編集

data可変長を見越して設計したからですね。あとものすごく要素数が大きくてstackに乗せたくないような場合を考えて。普通はstackを使ってあげたほうが高速です。Chironianさんも言っているように、どこまで想定するか、抽象化するか次第です。 NULLチェックは、だいたい「これくらい自明でしょ」ってところで省いたせいで後でバグを探すのが面倒になるやつなのでかならずもれなくチェックします。もっというとNULLチェックしたくないのでC++のlvalue reference使いたいです。
N---------

2018/12/10 08:05

返信ありがとうございます!理解しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問