8
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go言語】Goの概要・基本構文を押さえる

Last updated at Posted at 2024-10-14

はじめに

本記事では、Go言語を触ってみたい、基本的な構文を押さえたいと言った初学者に向けた記事となっております。
今回は、DockerでGoの環境構築を行うのでGo自体の環境構築は不要となっております。
エディタはVScodeを使用して進めていきます。

🙏 dockerコマンドが使用できる状態にしておいてください。

Go言語はどのような開発言語なのか?

  • Go言語はGoogleによって2009年にリリースされた、比較的新しいプログラミング言語の一つです。
  • Goが正式名称ですが、それだけだと別の意味に捉えられてしまうこともあり、「Go言語」や「Golang」の通称で呼ばれています。

Go言語には以下の特徴が挙げられます。

1. コードを簡潔に書くことができる
2. 高速で動作する
3. 並行処理・並列処理に優れている
4. 複雑なシステム構築に向いている
5. 静的型付け言語である
etc....

具体的に一つずつ見ていきましょう。

コードを簡潔に書くことができる

Go言語は、他の言語に比べて機能が削ぎ落とされています。
そのため、コードの記述量が少なく、シンプルで可読性が高い点が特徴といえます。
例えば、繰り返し構文はfor文しかなく、while文やdo/while文などはありません。そのため、for分しか使えないからこそ、コードの統一感が簡単に実現でき、プログラマー毎の表示のばらつきを抑え、コンパイルを高速化することができます。

高速で動作する

記述したコードをプログラムの実行前に一括で機械語に変換する「コンパイラ言語」であるため、一行ずつの変換が必要な「インタプリタ言語」(PythonやPHPなど)と比べ、処理速度が速いです。

並行処理・並列処理に優れている

Go言語は、「並行処理」と「並列処理」が得意な言語です。これも高速処理を実現する要因となっています。並行処理は、複数のコアで一定期間内に複数の処理を順不同に行うことです。対して、並列処理は、複数のCPUを効率良く使うことで、処理速度を向上させ、同時に処理を行うことが可能です。

複雑なシステム構築に向いている

Go言語はコードがシンプルかつ書き方が厳密なので、エンジニアによる癖が現れにくく、複数のエンジニアによる大規模なシステム開発に適しています。
他の人が書いたコードであっても、可読性の高さ故にスムーズに理解することができるため、チームでの開発・運用・保守に向いている言語ともいえます。

一方で、デメリットも存在します。

  • 継承ができない
    • オブジェクト指向の言語には「継承」というあるクラスの性質を別のクラスに引き継ぐと言った機能が備わっていますが、Go言語は継承がありません
    • つまり、既存のコードの使いまわしが難しく、コードの修正もやりにくいと言ったデメリットがあります
  • 例外処理がない
    • Go言語では、「例外が発生した場合は呼び出し元が処理をする」という考え方に基づいているため、例外処理の機構を用意していません
    • この理由は、Go言語がコードのシンプルさを重視しているからです

以上のことから、Go言語のシンプルさは、その機能を削ぎ落とすトレードオフの結果と言えるでしょう。

Goの環境構築をしよう

それではGo言語を実際に触ってみましょう!

まず、作業するディレクトリを作成してその中にmain.goファイルを作成してください。

Terminal
mkdir go-sample && cd go-sample 

ローカルにGo言語が入っていなくても動くようにDockerを使います。
作成し終えたら、VScode上でターミナルを開き、以下のコマンドを叩いてください。

docker run -it -v /Users/<User_name>/<directory_name>/<今回作成したdirectory_name>:/app golang 

簡単にコマンドの説明をすると、

  • docker run
    • Dockerのコンテナを起動させる
  • -it
    • コンテナ内でインタラクティブなターミナルを起動し、操作することが可能になる
  • -v /Users/<User_name>/<directory_name>/<今回作成したdirectory_name>
    • 今回作業をするディレクトリをコンテナ内にマウントする(pwdコマンドで自分が現在いるパスを出力してそれを使う)
  • /app
    • コンテナ内のディレクトリの指定
    • ホスト側のディレクトリが、コンテナ内の /app ディレクトリとして認識される
  • golang
    • 使用するGoのイメージの指定

シェルが立ち上がったらcd /appでappディレクトリに移動しましょう。
それではGoでHello World!を出力してみましょう。
main.goファイルに以下の記述をしてください。

main.go
package main

import "fmt"

func main() {
	fmt.Println("Hello World!")
}

Goはgo run <ファイル名>で実行ができます。今回だと、ファイル名にmain.goを指定してあげればOKです👍

動作確認ができたので早速、packageについてです。

package について

  • Goのコードは全て何らかのパッケージに属します
    • 今回は、main.goのみを扱うので登場するのは、main package のみです
  • パッケージはimportして使えます
    • 上記のコードでは、"fmt"(フォーマット)というGoが標準で提供してくれているパッケージを使用しています
    • 自分でパッケージを開けながら開発をするめるときもこのようにインポートして使います
    • 複数インポートするときは()で囲みます
    • main.go
        package main
      
        import (
              "fmt"
              //このパッケージも標準搭載
              "math/rand"
                )
                
       func main() {
            fmt.Println("Hello World!".rand.Intn(10))
       }
      
    • Goはディレクトリがパッケージになります
      • ファイル間で要素を移動してもビルド時には差はないです

基本構文について

ここからは基本的な構文を見ていきましょう!

変数について

変数の宣言方法は主に3パターンあります。
1 . 宣言だけしておいて値を後から代入

  • main.go
     package main
     
     import "fmt"
     
     func main() {
     	var i int
     	i = 1
     	fmt.Println(i)
     }
    
    • var 変数名 型 このように宣言できます

2 . 宣言と初期化を同時にやる

  • main.go
    
    package main
    
    import "fmt"
      
    func main() {
         var i = 1
         fmt.Println(i)
     }
    

3 .短縮した宣言方法(最もよく使われます)

  • main.go
    package main
    
    import "fmt"
    
    func main() {
      i := 1
      fmt.Println(i)
     }
    
    • varでの型宣言は、関数外でも宣言できますが、ショートデクラレーション(:=)は関数内でしか宣言できません
    • ただ、使える場所ではどんどん短縮形式:=を使用するべきです!
    • 関数外のパッケージ変数の場合は短縮形式が使えないためvarを使用します

    複数の変数を同時に初期化することも可能!

    main.go
     package main
    
     import "fmt"
    
     func main() {
        i, j := 1, "hoge"
        fmt.Println(i, j)
     }
    

    ループ処理について

    前述した通り、Goではforしかありあません。しかし、whileのような書き方をすることは可能です。

  • main.go
      package main
      
      import "fmt"
      
      func main() {
         sum := 0
         	for i := 0; i < 10; i++ {
          	sum += i
          	}
          	fmt.Println(sum)
      }
    

    これは0〜9までの和を計算するコードになります。なので45が帰ってきたらOKです!

    次は、whileのような書き方を書いてみましょう。

    main.go
      package main
      
      import "fmt"
      
      func main() {
         sum := 1
         for sum < 10 {
      	   sum += sum
        }
        fmt.Println(sum)
      }
    

    これは変数sumが10より小さい範囲で足し算されていきます。

    続いて、配列をループさせて配列の中身を取り出す処理を書いてみましょう。

    main.go
    package main
    
    import "fmt"
    
    func main() {
       arr := []int{1, 2, 3, 4, 5}
       for _, num := range arr {
    	    fmt.Println(num)
      }
    }
    
    • 配列の宣言方法:[] 型 {要素1, 要素2, ...}
    • 一つ目の変数_は配列のインデックスを指します。今回は使用しないでの_にしています
    • 二つ目の変数numにスライスの各要素が代入されます
    • rangeはスライスの各要素を1つずつ取り出し、左辺の変数に代入します

条件分岐

配列のループで使ったコードを再利用してifを使ってみましょう。

  • main.go
     package main
     
     import "fmt"
     
     func main() {
        arr := []int{1, 2, 3, 4, 5}
        for _, num := range arr {
            if num % 2 == 0{
     	       fmt.Printf("%d は偶数です。\n", num)
             } 
         }
     }
    

条件分岐に関しては他の言語とあまり変わらない気がしますね。

関数について

それでは先ほどの条件分岐を関数にしてみましょう。

  • main.go
    package main
    
    import (
    	"fmt"
    )
    
    func isEven(num int, message string) {
    	if num%2 == 0 {
    		fmt.Printf("%d %s \n", num, message)
    	}
    }
    
    func main() {
    	arr := []int{1, 2, 3, 4, 5}
    	for _, num := range arr {
    		isEven(num, "は偶数だ!")
    	}
    }
    
    • func 関数名(引数1, 引数2)戻り値の型{ return 戻り値 }
    • 今回は第一引数にint型のnum、第二引数にstring型のmessageを指定します
    • 関数の処理の中身は先ほど使用した条件分岐を書きます
    • 動的型付け言語と違い、関数定義時に戻り値の型を指定できるので、実行せずともある程度どの型が戻り値かが分かります

構造体について

スクリプト言語しか触ったことがない方は構造体って何?と感じると思うので
まず、構造体とは何かについて話したいと思います。

構造体とは、プログラムを理解しやすくするための小さな構成要素にプログラムを分解する役割を担うものです。

構造体の特徴は以下の3点が挙げられます。

  • フィールドをまとめることが可能
  • 他の構造体を埋め込むことができるが、アップキャスト1やダウンキャスト2はできない
  • 関数の中や式の中でも動的に定義することができる

では、実際にコードを見ていきましょう!
まずプロパティだけ持った構造体の宣言をします。

  • main.go
      package main
      
      import (
          "fmt"
      )
      
      // Animalという構造体を定義
      type Animal struct {
          name   string
          species string
          age    int
      }
      
      func main() {
          // Animal型のインスタンスを作成
          a := Animal{name: "Gopher", species: "mouse", age: 5}
          
          fmt.Println(a)
          
          // 個々のフィールドを出力
          fmt.Println("Name:", a.name)
          fmt.Println("Species:", a.species)
          fmt.Println("Age:", a.age)
      }
    
    • 構造体は type 構造体名 struct {プロパティ1 型, プロパティ2 型}このように書きます

次は、このAnimal構造体にメソッドの追加をしてみましょう!

構造体+メソッド

Goは関数型の言語なのでメソッドの仕方が独特です。
宣言方法は、関数とほとんど同じです。

  • main.go
      package main
      
      import (
      	"fmt"
      )
      
      type Animal struct {
      	name    string
      	species string
      	age     int
      }
      
      // Animalにメソッドを追加
      func (a Animal) Describe() string {
      	return fmt.Sprintf("%s is a %d-year-old %s.", a.name, a.age, a.species)
      }
      
      func main() {
      	a := Animal{name: "Gopher", species: "mouse", age: 5}
      
      	fmt.Println(a)
      
      	fmt.Println("Name:", a.name)
      	fmt.Println("Species:", a.species)
      	fmt.Println("Age:", a.age)
      
      	// Describeメソッドを呼び出して動物の説明を出力
      	fmt.Println(a.Describe())
      }
    
    • func(レシーバ レシーバの型)メソッド名(引数 型)戻り値の型{return 戻り値}でメソッドの宣言が可能です

    • 上記のコードではDescribeというメソッドの追加をして、動物の説明を出力します

    • メソッドをもう少しクラスっぽく説明すると以下のようになりそうです

    • main.ts
        class Animal {
            readonly name: string;  
            species: string;        
            age: number;            
        
            constructor(name: string, species: string, age: number) {
                this.name = name;   
                this.species = species;
                this.age = age;
            }
            
            describe(): string {
                 return `${this.name} is a ${this.age}-year-old ${this.species}.`;
        }
      }
      
    • AnimalクラスにDescribeメソッドがありプロパティを持っている感じです

ポインターについて

ポインターとは、メモリ上の特定のアドレスを示す際に使用します。
ポインターの重要性は次の点から理解できます。

  • プログラムはメモリ上のデータを読み書きすることで機能します。そのため、データがメモリ上のどの位置(アドレス)にあるのか、それがどの範囲にわたっているかを把握する必要があります。
  • さらに、変数の型を指定することで、そのポインタが指すメモリ領域のサイズやデータの種類を知ることができます。

また、Go言語のポインターは他の言語(C/ C++)などと比べると、ガベージコレクション3があり、メモリリーク4を防ぐことができます。
ただし、ポインター演算はGoにはなく、安全性やシンプルさゆえ無い機能かなと思います。
メモリの直接操作に関する安全性とシンプルさが確保されているとも言えるでしょう。

それではコードを見ていきましょう!

  • main.go
       package main
      
       import (
      	"fmt"
      )
      
       func main() {
      	i := 10
        // iのアドレスを取得
      	p := &i
      	fmt.Println(*p)
      }
    
  • p
    • ポインター変数です
  • &変数
    • メモリ上のアドレスが取得できます
  • *ポインター変数
    • *をポインター変数の前につけることで、ポインター変数に入っているアドレスに格納されている値にアクセスできます

さらに、*p = 値をポインター変数pの後に書くと書き換えができます。

  • main.go
      package main
     
      import (
     	"fmt"
      )
     
      func main() {
          i := 10
          p := &i
    
         // 15に書き換える
          *p = 15
       	fmt.Println(*p)
         }   
    

ポインター変数もただの変数なので宣言は変数でした時と変わりません。

  • main.go
     package main
     
     import "fmt"
    
     func main() {
        var p *int
        i := 10
        p = &i
    
       fmt.Println(i)
    
    • var ポインタ変数 *型で宣言ができます
    • ただ、ポインター変数を使うべきでない場面もあります
  • 小さい構造体や不変のデータには値渡しを使うとき
  • ポインタを使うと複雑さが増すとき
  • ポインタを適切に管理できず、ガベージコレクションの効率が低下するとき

この3点に気をつけてポインター変数を使いましょう!

さいごに

今回は、Go言語の基本的な概念や構文について軽く触れました。しかし、本記事で紹介した内容以外にも、deferclosureといったGo言語の重要な機能があります。これらはGoを効果的に活用するために欠かせない要素です。
Go言語の基礎を固めるにはA Tour Of Goが個人的にオススメです。興味のある方は是非試してみてください!

参考

  1. アップキャスト:具体的な型をインターフェース型に代入すること

  2. ダウンキャスト:インターフェイス型を具体的な型に代入すること

  3. ガベージコレクション:不要になったメモリを解放する機能

  4. メモリリーク:使用していないメモリを開放することなく確保し続けてしまうこと

8
14
0

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
8
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?