C 構造体

提供:yonewiki
2022年9月26日 (月) 10:43時点におけるYo-net (トーク | 投稿記録)による版 (ページの作成:「C++へ戻る ※このページではC言語にも存在していたという意味で記事タイトルがC 構造体になっていますが、<br /> C++でも同様です。C++だけの機能がある場合は明記します。<br /> <br /> == '''構造体''' == 簡単に考えると、変数を一塊(ひとかたまり)にするためのものと言っていいですが、変数の中にポ…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

C++へ戻る


※このページではC言語にも存在していたという意味で記事タイトルがC 構造体になっていますが、
C++でも同様です。C++だけの機能がある場合は明記します。

構造体

簡単に考えると、変数を一塊(ひとかたまり)にするためのものと言っていいですが、変数の中にポインタ変数も含めることが出来るし、関数だって組み込める。C++においてはクラスとよく似ている使い方もできるので、簡単なようで、なんとも奥深い仕組みです。あきらかに構造体を使うべきだと思うケースって、いわゆる二分木を使ったバイナリーツリーとかバイナリーサーチツリーとかという考え方に基づいてソート処理をしたり、検索処理をしたりするときのリスト構造やら、ヒープ、マルチウェイツリーを組むときとか、そんなときだったり、このデータを使うときはひとまとめにするわなってときなんです。


なんか、難しい言葉を並べましたけど、難しいことをするときと、単純にひとまとめにするときに使うということが伝われば、まぁそれでよしとします。自分みたいなアホなプログラマは構造体なんて使うとあとで、関数のデータの引数として使うときに面倒だったりするんで、安易にはあんまり使わないのは自分流ですね。良い子のプログラマは真似しちゃいけないと思う。バラバラに分けていると訳わからんくなるという負の効果も抜群です。


C++ではクラスと構造体には対した差はありません。敢えて言うならば、デフォルトのアクセス指定子が構造体ではpublicになっていて、クラスはprivateになっているところです。似ていますが、構造体を使うパターンというのは継承とかを使わない構造がシンプルなものになるのが一般的なように思います。なので、細かい説明はクラスに譲るとして、よく使うパターンとしての構造体についてのみ、この項目で触れたいと思います。


構造体の基本は簡単です。 以下のようにして日付構造体を自分で作ったりできます。

int main() {

    struct stDate{
    int year;
    int month;
    int day;
    };
    //これでstDateという構造体を定義したことになる。

    stDate stDateTest;//stDateTestっていう名前でstDate構造体変数を作った。

    //構造体変数への数値の代入
    stDateTest.year = 2015;
    stDateTest.month = 2;
    stDateTest.day = 3;


    int nYear;
    nYear = stDateTest.year; //使うときも同じ


    return 0;
}

っていうのが基本らしい。んで、これだけで構造体の勉強を終えてしまうと、痛い目に合うらしい。


構造体変数名.構造体の中の要素の名前


ってな具合にドットで繋ぐような感じで指定する。こういうのを選択演算子って呼ぶ。「構造体の中の要素の名前」って表現したけど、構造体の要素の変数はメンバ名っていうし、関数の場合はメンバ関数って呼ぶ。この辺は後で出てくるクラスって奴と同じかな。クラスの事を少し知っている人にはなじみやすいし、あとでクラスを勉強するときにもわかりやすいね。


複数の変数をひとまとめにした構造体を定義して、定義した構造体名の変数を作って利用する。構造体の定義と同時に変数を作ることもできるし、構造体の変数をポインタとしたものを使うこともできる。先に述べたように、構造体の定義の中には、複数の変数に限らず、ポインタ変数や関数、そして、構造体そのものを含めることができる。構造体の中で構造体を再帰的に保有することができる。その再帰的な構造体の実体を作り続ければ、無限に再帰することになるので、再帰的に構造体を定義する場合は、構造体の中の構造体変数で、最終的には実体を持たないような構造体になっているような、再帰の終点に関する工夫が必要となることは言うまでもなし。もちろん再帰的ではなく、まったく別の構造体を含めて、構造体として定義することもできる。


という具合に…


本気で使うとなるともっと複雑で難しくなっていきます。数学と一緒で基本は簡単なのに、わかったつもりで次のページをめくっていくとわからなくなるというそういう要素を含んでいるのが構造体だと思います。オレは使わないからあんまり関係ねぇとか、舐(な)めてると怪我します。


なんのために構造体を使うのか、どういう具合に使うものなのか?そこまで理解してこそのプログラミング技術。そんなスタンスで謙虚に学ぼうという姿勢を崩さないのが大事。一流のプログラマ(例えば数人で起業したGoogleの人たちみたいな)になるには、道は果てしなく遠く、ちょっとパソコンで簡単なプログラミングができるレベルの人がわんさかいる現状を考えれば、基本の技術をいかに応用できるのか?そういうところが大事なのです。自分も構造体はいまだに使いこなせていないと思っていますし…。謙虚に。謙虚に。構造体で実現されているpairとかのソースを見ると、あぁコレってすげぇなって感じると思う。


まずは自分で定義した構造体の配列を何かの関数に引き渡すサンプルを示します。

#pragma once
#include <iostream>

using namespace std;

//構造体を定義
struct stDate{
  int year;
  int month;
  int day;
};
//定義した構造体を引数とする関数のプロトタイプ宣言。関数の本体はメイン関数の後くらいに記述。
void stDatePrint(stDate* pstDateOutput,int nSize);

int _tmain(int argc, _TCHAR* argv[]){

  int nSize = 3;
  stDate* pstDateArr;
  //定義した構造体のポインタ変数を作成

  pstDateArr = new stDate[nSize];
  //構造体ポインタ変数で配列を実体化。例ではnSize=3なので配列要素0~2まで。

  //初期化 コツコツVer。ここから
  (*(pstDateArr)).year = 2015;
  (*(pstDateArr + 1)).year = 2015;
  (*(pstDateArr + 2)).year = 2015;

  (*(pstDateArr)).month = 2;
  (*(pstDateArr + 1)).month = 2;
  (*(pstDateArr + 2)).month = 2;

  (*(pstDateArr)).day = 3;
  (*(pstDateArr + 1)).day = 4;
  (*(pstDateArr + 2)).day = 5;
  //ここまで

  //引数に構造体ポインタ変数とその大きさをとる構造体出力関数を呼び出す。
  stDatePrint(pstDateArr,nSize);

  delete[] pstDateArr;

  return 0;
}
//構造体出力関数 引数には定義した構造体ポインタ変数と構造体配列要素数を受け取る
void stDatePrint(stDate* pstDateOutput,int nSize){
  for(int i = 0;i < nSize;i++){
      printf("%04d-%02d-%02d\n",(*(pstDateOutput + i)).year,(*(pstDateOutput + i)).month,(*(pstDateOutput + i)).day);
  }
}

出力結果

2015-02-03
2015-02-04
2015-02-05

上記のように構造体で定義された3つの変数をまとめて、関数に引き渡せるし、構造体を配列にしてしまえば、3つの変数をまとめて配列の要素の数だけ受け渡しできる。サンプルでは配列要素は3つなので、3変数×3要素で、一気に9つの変数を引数として渡すことができている。


どうでしょう?おわかりになられたでしょうか。(*(構造体 + 配列要素数)).構造体変数名という表記が理解できないかたは、このWikiのポインタから勉強をする必要があるでしょう。


13行目および46行目の構造体のポインタを引数としているところは、配列の表現を使っても良いので

…
void stDatePrint(stDate* pstDateOutput,int nSize);
…
void stDatePrint(stDate* pstDateOutput,int nSize){
  …
}

の部分は

…
void stDatePrint(stDate pstDateOutput[],int nSize);
…
void stDatePrint(stDate pstDateOutput[],int nSize){
  …
}

として、表記しても良いですし、ポインタで配列をnew演算子によって生成する部分も配列の変数を使っても良いです。もちろん選択演算子のドット記号をアロー演算子(->)に置き換えた表現にしても良いです。


構造体を使ったデータ構造プログラミングの例やらは複雑な話になりそうなので、回避したいところですが、それではあんまりなので、もうちょっと踏み込んだ話は、また後日にでも記述します。(2015/02/06書きかけの記事。いつ書くのかは誰の知る由もない。なんなら逃げ出す可能性もあります。しばらくは他サイトのプログラムとデータ構造あたりで検索したり、クイックソート、バブルソート、二分木、リスト構造、ヒープ、バイナリーツリー、バイナリーサーチツリー、マルチウェイツリー、線形探索、循環リスト、双方向リストといったキーワードとCサンプルプログラムあたりでひっかかるサイトを参照して下さい。例によって計算量O(Order)の話や対数についての話題が登場することになりますが、地道に勉強するという遠回りも時間があればやってみて欲しいですね。クイックソートは万能なように思えますが、既にソート済の要素を処理すると遅くなりますので、改良型クイックソートの話について触れている優良な記事に出会えると良いなぁと心配したりします。だったらおまえが説明しろや!って言う声もなんとなく想像しますが…。って言い訳しすぎだろオレ。)


C++へ戻る