LoginSignup
4
3

More than 3 years have passed since last update.

【C/C++】構造体とポインタについて新卒エンジニアが解説してみた

Last updated at Posted at 2020-12-14

はじめに

構造体についての解説と中級者向け(?)に構造体宣言を行ったときメモリ節約の方法を解説します。

対象者

・C/C++ 初心者
・構造体の概念がつかめていない方
アロー演算子ドット演算子の違いがあやふやな方

構造体とはなんぞや

一言で言ってしまえば大きな箱です。
大きな箱の中に複数のデータを格納して管理しています。
大きな箱で管理すつことによって様々なメリットがあります。

構造体の宣言

宣言は以下のようになります。

struct data {
    int year;
    int month;
    int day;
    char name[100 + 1];
};

イメージはこんな感じ
「birthday」の箱に「年」「月」「日」「名前」を入れておく
a (1).jpg

実際に使用してみよう

宣言の仕方が理解できたところで実際に使用してみましょう。

#include <bits/stdc++.h>
using namespace std;

struct birthday {
    int year;
    int month;
    int day;
    char name[100 + 1];
};

int main() {

    struct birthday birthday;

    // 構造体初期化
    memset(&birthday,
           0x00,
           sizeof(birthday) );

    birthday.year = 1999;
    birthday.month = 10;
    birthday.day = 11;
    strncpy( birthday.name,
             "山田 太郎",
             sizeof(birthday.name) - 1 );

    cout << "名前:" << birthday.name << '\n' << "誕生日:";    // 山田 太郎
    cout << birthday.year << "年 ";      // 1999
    cout << birthday.month << "月 ";     // 10
    cout << birthday.day << "日 ";       // 11

    /* 出力結果

    名前:山田 太郎
    誕生日:1999年 10月 11日

    */
     return 0;
}

最初に構造体「birthday」を宣言
main関数の中で初期化、値を代入しています。

何が便利なの??

ここまで確認しても何が便利なのかイマイチ分からないと思います。
構造体の便利なところはここからです。

関数に引数を渡す場合は一つずつ渡しますよね。
ただ、それは扱う引数の数が少ない場合は問題ないのかもしれません。
しかし、数多くのデータを受け渡したいときにはちょっと面倒です。
そういうときに構造体が役に立ってきます。
関係性のある変数を一つの箱にまとめます。
今回の場合ですと「誕生日」の箱に4つの情報を持った変数を入れています。
こうすることによって一つ一つの変数を受け渡すことなく、値のやり取りが可能になります。
*例は以下の通りです

#include <bits/stdc++.h>
using namespace std;

struct birthday {
    int year;
    int month;
    int day;
    char name[100 + 1];
};

void mybirthday( birthday mybirthday ) {
    cout << "名前:" << mybirthday.name << '\n' << "誕生日:";    // 山田 太郎
    cout << mybirthday.year << "年 ";      // 1999
    cout << mybirthday.month << "月 ";     // 10
    cout << mybirthday.day << "日 ";       // 11
}

int main() {
    struct birthday birthday;

    // 構造体初期化
    memset(&birthday,
           0x00,
           sizeof(birthday) );

    birthday.year = 1999;
    birthday.month = 10;
    birthday.day = 11;
    strncpy( birthday.name,
             "山田 太郎",
             sizeof(birthday.name) - 1 );

    mybirthday( birthday );

   return 0;
}

main関数内で「mybirthday」を呼び出しています。
この際、4つの変数をそれぞれ引数として渡すのではなくbirthdayだけを渡してあげれば渡された関数内でそれぞれの変数を参照することができます。

ドット演算子とアロー演算子の使い分け

この2つは混乱している方も多いのではないでしょうか。
・ドットとアローの違いは?
・使い分けをする必要はあるの??
私も勉強を始めた頃は上記のような疑問を持っていました。

しかし、一度覚えてしまえば簡単です。

■ドット演算子

一言でいうと実態に使用します。
「実際に使用してみよう」で解説したプログラムはドット演算子です。(birthday.yearなど...)

void mybirthday( birthday mybirthday ) {
    mybirthday.year = 99999;
    cout << "名前:" << mybirthday.name << '\n' << "誕生日:";    // 山田 太郎
    cout << mybirthday.year << "年 ";      // 99999
    cout << mybirthday.month << "月 ";     // 10
    cout << mybirthday.day << "日 ";       // 11
}

int main() {
    struct birthday birthday;

    birthday.year = 1999;
    birthday.month = 10;
    birthday.day = 11;
    strncpy( birthday.name,
             "山田 太郎",
             sizeof(birthday.name) - 1 );

    mybirthday( birthday );

    cout << birthday.year << "年 ";      // 1999

    return 0;
}

例えば、mybirthday関数内でyearの値を99999へ書き換えました。
書き換えられた値はmybirthday関数内では99999に。
main関数内では1999のままです。

これはmainからmybirthdayへ構造体をコピーしているため値を書き換えている関数、書き換えていない関数で出力されてる値が異なります。
ドット演算子はイメージがしやすいのではないでしょうか。

■アロー演算子

アロー演算子(->)はポインタが指す構造体のメンバへアクセスするために使用します。
アロー演算子を使用すると値を変更した関数内だけではなく使用する関数全体に影響を及ぼすことが可能です。


void mybirthday( birthday *mybirthday ) {
    mybirthday->year = 9999;
    cout << "名前:" << mybirthday->name << '\n' << "誕生日:";    // 山田 太郎
    cout << mybirthday->year << "年 ";      // 9999
    cout << mybirthday->month << "月 ";     // 10
    cout << mybirthday->day << "日 ";       // 11

    //----------------------//
    //  アロー演算子の略記法
    //----------------------//
    cout << ( *mybirthday ).name << '\n' << "誕生日:"; 
    cout << ( *mybirthday ).year << "年 ";      // 9999
    cout << ( *mybirthday ).month << "月 ";     // 10
    cout << ( *mybirthday ).day << "日 ";       // 11
}

int main() {
    struct birthday birthday;

    // 構造体初期化
    memset(&birthday,
           0x00,
           sizeof(birthday) );

    birthday.year = 1999;
    birthday.month = 10;
    birthday.day = 11;
    strncpy( birthday.name,
             "山田 太郎",
             sizeof(birthday.name) - 1 );

    mybirthday( &birthday );

    cout << birthday.year << "年 ";      // 9999

    return 0;
}

main関数内でmybirthdayを呼び出していますが、この際に引数へ&をつけることによって呼び出し先へアドレスを受け渡すことができます。
呼び出し先関数内で値を変更すると呼び出し元・先の両方で値が変更されます。

また、アロー演算子は以下の略記法になります。
( *mybirthday ).name
この記法とアロー演算子(->)は全く同じ意味を持ちます。

詳しくは【C/C++】苦手なポインタについて新卒エンジニアが解説してみたの「値渡し」「ポインタ渡し」で解説しているのでご参照ください。

構造体宣言をちょっとの工夫でメモリ節約!

structのオブジェクトはメンバが宣言順に格納されています。
例えば、以下の例だとこのようなイメージになります。

struct data {
   char aaa;
   int bbb;
   char ccc; 
};

aaa.png

この場合、sizeof( data )でサイズを取得すると6...ではなく12となる。

しかし、以下のようにメンバを大きい順から宣言をするだけで、無駄なメモリ領域を抑えることができます。

struct data {
   int bbb;
   char aaa;
   char ccc; 
};

イメージはこんな感じ
bbb.png
これをsizeof( data )でサイズを取得すると8になります。
これでもサイズが6にならないのは、2バイトの未使用領域が残されているからです。

今回の例ではメンバは3つで例えたのでそこまで大きな差は生まれませんでしたが、それでも4バイトの節約に成功しました。
実際のプログラミングではまず第一に可読性を優先して、最適化が必要だと判断した場合に大きさ順に並び替えるといいのかもしれません。

まとめ

今回は構造体とポインタについてざっくり解説してみました。
投稿するにあたって改めて構造体について勉強するとても良い機会となりました。
今まで当たり前に使用していた機能でも中身まで詳しくすることで人に説明するときや、実際にプログラムを書くときに理解度が全然違ってきますね。

今後も気づきや発見があったときには投稿してみようと思います。
何よりアウトプットすることで自分自身のレベルアップに直結するのがいいですね。

最後に

伝える内容について十分留意しておりますが、当方初心者のため誤った記載箇所がありましたらご一報いただけると幸いです。

参考文献/参考サイト

・ビャーネ・ストラウストラップ[柴田望洋 訳] プログラミング言語C++ [第4版]
・新・明解 C++入門 (著)柴田望洋
・苦しんで覚えるC言語( https://9cguide.appspot.com/
・だえうホームページ( https://daeudaeu.com/arrow/
・ほぷしぃ( http://www.isl.ne.jp/pcsp/beginC/C_Language_14.html
https://www.cc.kyoto-su.ac.jp/~yamada/programming/struct.html
http://www.ss.cs.meiji.ac.jp/CCP055.html

4
3
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3