配列の初期化(+配列の動的確保)について
どーもどーも、久々の更新です。
今回はJOI予選中にハマってしまったことについて書かせていただこうかと。
問題文は省略。
回答作成中にint配列の全要素を-1で初期化したかったから
int array[n] = {-1};
と書いたわけですが、これが間違い。
n==5;
とすると
イメージ
array[0] == -1;
array[1] == -1;
array[2] == -1;
array[3] == -1;
array[4] == -1;
現実
array[0] == -1;
array[1] == 0;
array[2] == 0;
array[3] == 0;
array[4] == 0;
ってなるらしい。
それを知らなかったために色々と余計な部分を確認した。
入力データも処理も確認したけどおかしくはない、となると他の部分?
と思い変数の値をすべて出力するという暴挙に。(デバッグツールっておいしいの?)
その結果、
array[0] == -1;
array[1] == 0;
array[2] == 0;
array[3] == 0;
array[4] == 0;
......えー、何々どゆこと?
これは入力データに依存しないはずだぞ?
代入(初期化)ミス?
という訳で検索した結果。
初期化子リストの値の数が集約型より少ない場合、集約型の残りのメンバーまたは要素は 0 に初期化されます。
お、おぅふ......。
いやぁ、これ知らなかったせいでループ内の処理とか入力データを延々と睨み続けるハメに......。
普段一律の値で配列初期化するときは0ばっかりで
int array[n]={0};
って書いてて、てっきり値を1つしか書かなければその値が全部代入されるのかと思ってたんです(汗)
なのでここのコードを
int array[n];
for(int i=0; i<n; ++i)
array[1]=−1;
と書き換えた結果、狙い通り動いてくれた(全要素に代入してるんだから当たり前)
あ、あと配列ついでにもう一個、いわゆる配列の動的確保。
動的確保ってなんぞ?って人のために(雑に)説明すると、
・配列使いたい!!
・要素数は入力された値使いたい!!→コンパイル時には要素数未定、実行時に確定させたい!!
って時に使う技術。
(実際には入力以外の要因で使いたい場面もいっぱいあるだろうけど割愛、そういう場面でも使えるから安心してね!!)
じゃあ、ソース書くぜ!!
// array-sample1.cpp
#include <iostream>
using namespace std;
int main(){
int n;
cin >> n;
int array[n];
for(int i=0; i<n; ++i)
array[i]=0;
for(int i=0; i<n; ++i)
cout << "array[" << i << "] ==" << array[i] << endl;
return 0;
}
と書いたら(開発環境にも依るっぽいけど)コンパイルエラー、または実行時にエラーが出る。
環境によって動作の変わるプログラム→不安定のプログラム、を組むということでよろしくない。
なのでここのコードを
int array[n];
から
int *array = new int[n];
と書き換えちゃいます!!
int *arrayはint型の配列を「示す」よ〜(配列そのものじゃないよ!!)ってやつで、
そのあとの= new int[n];はint型の領域をn個確保(つまり動的確保!!)を代入するよ!!ってやつ。
......え?動的確保ってことは結局未定のまんまじゃん、って思うだろうけどそれでいいの。
newは動的確保を可能にするためのものだからこれでいいんです!!
で、注意としてはnewで確保した領域はdeleteで解放しなきゃいけないっていうのがあって、それの書き方は
delete array[ ];
これだけです笑
newの時と違ってarrayのサイズが決まってるから要素数は書かなくていいの。
というわけでさっきのコードを修正すると
// array-sample2.cpp
#include <iostream>
using namespace std;
int main(){
int n;
cin >> n;
int *array = new int[n];
for(int i=0; i<n; ++i)
array[i]=0;
for(int i=0; i<n; ++i)
cout << "array[" << i << "] ==" << array[i] << endl;
delete[ ] array;
return 0;
}
これでOK
......なんか初期化より動的確保がメインになっちゃったかも(汗)
取り敢えずここまで!!
もっと詳しく知りたいって人はここ見るといいかも→C++編(言語解説) 第12章 new/delete
次回は多次元配列の動的確保について書いてみようかな
2014/12/14 Sun.
2014/12/18 Thu.
一部修正。
new[ ]で確保した領域はdeleteではなくdelete[ ]で開放しなければなりませんでした。
私のせいで勘違いしてしまった方がいらっしゃいましたら申し訳ありませんでした。
Windows10TechnicalPreview
お久しぶりです、旧:ふくともです。
Windows Technical Preview - Windows Insider Program - Microsoft Windows
β版と聞けば気になっちゃうよね
というわけでインストールしてみた。
(と言っても機体はMBA Mid 2012だから純正じゃなくてVirtualBoxなんだけど。)
取り敢えずOSイメージください、というわけでDLしに行くと......
そ、想定内だもんね!!
イメージさえ有れば俺の勝ちですし......(震え声)
Windows Technical Preview ISO のダウンロード - Microsoft Windows
......え?
\ページの言語は日本語あるのに日本語OSは無い!!/
仕方なく英語版の64bitを導入することに。
設定は以下の通り。
一番の注意点はOSの種類をWindows8.1にすることかな。
その他はDirectXを導入する予定だからビデオメモリを最大にしたことと、ある程度動作に余裕を持たせるためプロセッサを4つ(使用率制限100%)、メインメモリを4GBに設定した、くらい。
ネットワーク設定はVirtualBoxの基本(らしい)のNATに設定し、ホストOSと同じものを利用する。
仮想ハードディスクのサイズは任意で。
俺は取り敢えず100GBにしておいた。
いざ起動。
最初はOSイメージ(今回はWindowsTechnicalPreview-x64-EN-US)から起動して仮想ハードディスクにインストール。
アカウント設定については省略。
OSのインストールが終わったら一旦再起動し、VirtualBoxのお約束Guest Additionsのインストール。
何のためのどういうものなのかについてはこちらを参照
→ Guest Additionsのインストール | VirtualBox Mania
基本的にはこれでOK
あとはアプリケーションインストールしたり、壁紙とかの外観変えたりお好きな様に。
取り敢えずこれでも自称エンジニア(見習い)なので開発環境は整える。
Windowsを最新のにしたついでに環境も最新のにしてみる。
Microsoft Visual Studio Express 2013 for Windows Desktop をインストール!!
......実はこれのインストールに時間かかりすぎて暇だったからこの記事書いてます。
というわけで今回はここまで!!(雑)
環境構築まで気になる方はこちらを参考にするとよろしいかと。
Windows 8.1 で作る 無料の VC++ 開発環境 - Planetleaf.com Lab.
Windows8.1ってなってるけど、気にしなくて大丈夫。
次回は環境整え終わってからそれでも晒そうかなぁ......
2014/12/02 Tue.
配列の宣言について。
お久しぶりです。
今回は配列の宣言について。
例によって例のごとくノベルゲームの(ryを組んでた際にエラーが発生
◯◯◯.exe の 0x00ac4bc4 でハンドルされていない例外が発生しました: 0xC0000005: 場所 0xdadad240 に書き込み中にアクセス違反が発生しました。
エラー発生時に実行されていた関数がこちら
void setScript(Script* pScript, const char* str) {
int i, start = 1, size;
const char* pStr;
for (i = 0; i < 3; ++i){
// ","を探す
pStr = strstr(&str[start], ",");
if (pStr == NULL ) // ,がない場合、行末(改行)を探す
pStr = strstr(&str[start], "\n");
size = pStr - &str[start];
char Buf[ ] = {0};
switch(i){
case 0:
strncpy(Buf, &str[start-1], size+1);
Buf[size+1] = '\0';
pScript->Name = Buf;
break;
case 1:
strncpy(Buf, &str[start-1], size+1); // ここでエラー発生
Buf[size+1] = '\0';
pScript->Message = Buf;
break;
case 2:
// キーワードに合わせて読み込む画像を変える
strncpy(Buf, &str[start-1], size+1);
Buf[size+1] = '\0';
if(strcmp(Buf, "A"))
pScript->bgGraph = LoadGraph(FILENAME_A);
else if(strcmp(Buf, "B"))
pScript->bgGraph = LoadGraph(FILENAME_B);
else if(strcmp(Buf, "C"))
pScript->bgGraph = LoadGraph(FILENAME_C);
else if(strcmp(Buf, "D"))
pScript->bgGraph = LoadGraph(FILENAME_D);
break;
default:
break;
}
start = start + size + 2;
}
}
pScriptは以下の通り
struct Script{
char* Name,Message;
int bgGraph;
}script;
Script *pScript = &script;
FILENAME_◯は画像ファイルのパス(文字列)で定義済。
switch文のcase 1:とcase 2:の違いなんて代入先だけじゃないか!!と思いつつ色々調べてみる。
すると、問題があったのは......
\ switch文の前のBufの宣言!! /
char Buf[ ] = {0};
要素数の動的確保、こいつが穴だった。
要素数を指定しなかった場合、配列の利用時に要素数が確定される。
→case 1:で確定される。== case 2:以降では既に確定している。
文字列の長さがNameに代入したい値よりも、Messageに代入したい値の方が長かった場合、配列の最終要素(書き込み禁止!!)にはみ出た分の最初の文字を代入しようとし、エラーを吐いた、ということだったようだ。
動的確保は便利だけど長さが未定で何度も使い回す場合には最初っから多めに確保したほうが良さそう。
動的に再定義とかできたら無駄がなくていいんだろうけど、そこまでの知識は俺には無いからなぁ......(´-ω-`)
これもまた調べたいことに追加して今回も終了、かな。
PS.冬コミまで日が無くなってきて焦ってます。ダレカタスケテー
2014/11/22 Sat.
【C++】ノベルゲームのスクリプト - その2.5
前回に引き続きスクリプトについて。
といっても今回はスクリプトそのものではなくその扱い方についてなんだが。
前回の記事を振り返って、無駄に気付いた。
構造体の配列を利用するということは、それだけメモリを必要としてしまうため単純計算でいくと原稿が2倍になれば必要なメモリも2倍になってしまう。
例えばDVD-ROM3枚組の某型月さんのFate/stay night。
もしその原稿を前回のに読み込ませようとした場合どれだけのメモリが必要になることやら......
というわけで
配列に詰めるのはやめよう!!
では、どうするか。
今回も検証してみないことにはわからないけども案として、
構造体を1つ生成して一回の発言分をメンバ変数にセット
描画
これでループすればいいのでは無いだろうか。
また、以前にも書いたがDxLibの利用を前提に考えているのだが、
LoadGraph()関数で画像をメモリに読み込むとDeleteGraph()関数で削除するまでメモリに残留してしまうらしい。
【C/C++】画像描画について【DxLib】 • C言語交流フォーラム ~ mixC++ ~
そこでメッセージを描画し終わったら構造体を破棄し、デストラクタでDeleteGraph関数を呼んであげればメモリ圧迫はしなくて済むのでは無いだろうか。
ただし、この手法では描画ループ内で毎回
・構造体の生成
・構造体に代入(画像や音声ロード含む)
・構造体を破棄
を行うので処理速度が落ちる。
この処理速度の落ち具合によっては構造体配列を要素数を決めて生成して、ある程度まとめて行ったほうが良いかもしれない。
いずれにしても実際に走らせてみないことにはなんとも言えないねw
時間ができれば実装して検証しようと思います。
もうひとつこれに関して気になることがあるけど、それは下のリンクで質問中
【C++/DxLib】LoadGraph()関数について|teratail
色んなとこで質問してる無知っぷりを晒しつつ、今回はこの辺で。
2014/11/15 Sat.
【C++】ノベルゲームのスクリプト - その2
前回に引き続きスクリプトについて考えてみる。
前回はデータを受け取る構造体だけを考えたので、今回はそれにデータを受け渡す方法を考えようと思う。(いちいち構造体のデータを記述するのは面倒なため)
まず初めに思いついたのはExcelファイルを読み込むこと。
横で要素数、縦でデータ内容を統一すれば管理が楽だからだ。
問題は、Microsoftさんのライセンス品であるExcelのデータを読み込むのは骨が折れそうなのと、完成後に一般公開しようと思っているため利用するときに何かの有料アプリケーションが必須という事態は避けたいというという方針に反すること。
そこでまた色々調べてみるとCSVファイルなるものがあるらしい。
曰く、Excelで言うところの横列が行・縦列はその行の中で「,」で区切って表現されるらしい。
これを利用すればデータの入力は楽になるのではないだろうか。
前回のコードを再掲
struct Script{
char* Name;
char* Message;
int BGIHandle;
int PersonHandle;
int BGMHandle;
};
Script *p = new Script;
メンバ変数は5つ。
// CSVファイルから読み込んだデータをScript構造体に設定
void SetScript(struct Script* pScript, const char* Str) {
int i = 0, Start = 1, Size;
char* pStr,pResource;
char Buff[100];
for (i = 0; i < 5; ++i) {
// ","を探す
pStr = strstr(&Str[start], "\",\"");
if (pStr == NULL )
// ","がない場合、最後の"を探す
pStr = strstr(&str[start], "\"\n");
size = pStr - &str[start];
switch (i) {
case 0:
// 発言者
strncpy(pScript->Name, &str[start], size);
break;
case 1:
// メッセージ
strncpy(pScript->Message, &str[start], size);
break;
case 2:
// 背景画像
strncpy(pResource, &str[start], size);
pScript->BGIHandle = LoadGraph(pResource);
break;
case 3:
// キャラ画像
strncpy(pResource, &str[start], size);
pScript->PersonHandle = LoadGraph(pResource);
break;
case 4:
// BGM
strncpy(pResource, &str[start], size);
pScript->BGMHandle = LoadSoundMem(pResource);
break;
default :
break;
}
start = start + size + 3;
}
}
という関数を組んで、引数に構造体配列の要素とCSVファイルを1行づつ渡して、EOFまでループしてあげればいいはず。
ファイル読み込みにはfgets()関数を使えばいいかな。
fgets()に渡すバッファサイズとかは適宜調整してくださいな。
これでCSVファイルで書かれた原稿を渡すだけでScript構造体が使えるはず。(検証自体は未済、あくまでもアイデアのメモですので。)
毎回CSVの原稿を読み込むのは面倒だし、構造体のデータを書き出せるようにして、書きだしたデータをエンジン側で読み込めればいいのかなぁ。
次回はその構造体のファイル書き出しによるデータ保持について纏めようと思う。
......ということは、これはスクリプト生成用のプログラムであってエンジンそのものには搭載されない気ががががが
まぁ、一緒にパッケージングして配布とかになるかなぁ、(俺主観でだけど)需要はありそうだし。
2014/11/14 Fri.
以下、参考サイト様
【C++】ノベルゲームのスクリプト - その1
現在ノベルゲームのエンジンを作成中。
それにあたっていちいちソースコード側でメッセージや画像を切り替えるのは汎用性に欠けるからゲーム本体に実装するための外部スクリプトを作れたらいいなぁ、と思い実装方法を考えてみた。
まず、スクリプトに必要なデータ。
・発言者
・表示メッセージ
・背景絵(のパス)
・キャラ絵(のパス)
・BGM(のパス)
・効果音(のパス)
まぁ、こんなところかな。
実際には効果音不要の場合があったり、キャラ絵にも表示位置などがあったりするからもう少し複雑になるけど、今回は構想を纏めるだけなので割愛しよう。
ふむ。
必要なデータはどのシーンでも共通だ。
となるとスクリプト構造体を定義し、その配列で管理するのがベター。
要素数は原稿の長さに依存するのでnewを利用して動的にメモリを確保する。
struct Script{
char* Name;
char* Message;
int BGIHandle;
int PersonHandle;
int BGMHandle;
};
Script *p = new Script;
こんな感じにして受け手を確保してあげればいいんじゃないかなぁ......
これでメインフレームでの描画ループのカウンタを、ロードする構造体の要素数に当てていけば運用しやすくなる(ハズ)
ただ、このままだと各要素の代入は手打ちになるから外部で作成した意義があるとは言えそうにない。
次回はこの構造体に外部ファイルからデータを読み込む方法を考えようと思う。
2014/11/13 Thu.
環境パス設定。
本日の部活であった出来事。
開発環境
・OS:Windows8(64bit)
・キーボード:JIS配列
・コンパイラ:Borland C++ Compiler 5.5
・エディタ:Tera Pad
部員T「C++でグラフィック処理したいんだけど、どうすれば出来る?」
俺「***.rcファイルで管理してwindows.h使うか、DxLib.h使うかかな。弄ってみた感じは後者」の方が簡単かな」
〜数分後〜
T「大体サイトと同じようにやったはずなんだけど何かエラー出とる......」
PC交代して作業スタート
まずソースコード
#include <stdio.h>
#include "DxLib.h"
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
if(DxLib_Init() == -1)
return -1;
else
MessageBox( NULL, "DxLib初期化完了", "Debug", MB_OK);
return 0;
}
ソース自体は改変してあるものの
・初期化に失敗したら強制終了
・初期化に成功したらメッセージボックスを表示して終了
といった仕様で特に問題はない。
続いてコンパイル
GUIアプリケーションだから -W
とりあえず警告は一旦無視するため -w-
実行。
bcc32 -W -w- dx_sample.cpp↵
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland
エラー E2209 I:\pcclub\T\prog\dx_sample.cpp 2: インクルードファイル 'DxLib.h' をオープンできない
エラー E2141 I:\pcclub\T\prog\dx_sample.cpp 4: 宣言の構文エラー
*** 2 errors in Compile ***
** error 1 ** deleting Debug\dx_sample.obj
Make End !!
なるほど。
どうやらヘッダファイルのインクルードに失敗している模様。
bcc32.cfgを確認すると
-I"I:\pcclub\T\borland\bcc\bin\Include"
-L"I:\pcclub\T\borland\bcc\bin\Lib; I:\pcclub\T\borland\bcc\bin\Lib\PSDK"
一見、問題が有るようには見えない。
そこで環境パス設定について調べてみた。
すると、
「""で囲んであればフォルダ名に空白を含んでも問題無い。」
これが実は問題だったのだ。
どうやら2行目の後半が
「I」ドライブではなく、
「 I」ドライブ(Iの前に空白有り)として読み込まれていたようだ。
というわけでbcc32.cfgを
-I"I:\pcclub\T\borland\bcc\bin\Include"
-L"I:\pcclub\T\borland\bcc\bin\Lib; I:\pcclub\T\borland\bcc\bin\Lib\PSDK"
から
-I"I:\pcclub\T\borland\bcc\bin\Include"
-L"I:\pcclub\T\borland\bcc\bin\Lib;I:\pcclub\T\borland\bcc\bin\Lib\PSDK"
に書き換えて再コンパイルしたところ無事解決。
基本的に俺はMacで開発しているが、覚えておいたほうが(というよりはいつでも思い出せるようにしておいた方が)良いかなぁ、と思い投稿した。
2014/11/11 Tue.