C# -> Golang
まえがき
C#とGo言語を比べつつ、Go言語を説明します。
C#についての詳しい説明は割愛します。
言語概要
- Google が開発したプログラミング言語です。「Go言語」や「Golang」と表記されます。
- UNIX、B言語(C言語の元)、UTF-8の開発者ケン・トンプソンや、UNIX、Plan 9、UTF-8の開発者ロブ・パイクによって設計されました。
- 静的型付け、メモリ安全性、ガベージコレクションを備えるコンパイル言語です。
- シンプル、高速、メモリ効率が良い、メモリ破壊が無い、並行処理が得意などの特徴を備えています。
- メモリ破壊が無く、並行処理を得意とする、進化したC言語という側面があります。
- Linux、Mac OS X、Windows、Android、iOS で動作します。
言語仕様
セミコロン
C系統の言語と違い、基本的にセミコロンは必要ない
int hoge = 123;
int fuga = 456;
// セミコロンは各行に書く必要は無いが、
// 書いてもコンパイル自体に影響はない
var hoge int = 123;
// 次のように、一行で記述する際
// 区切りとして利用する場合がある
hoge := 123; fuga := 456
インデント
C++と同じインデント
インデントや波括弧の位置を正しく記述しないと、構文として認識されずにコンパイルエラーが起こる場合がある
public static void Main()
{
Console.WriteLine("Hello World");
}
func main() {
fmt.Println("Hello World")
}
未使用の変数
Go言語では未使用の変数があると、コンパイルエラーが起こる
ブランク変数 _
宣言する必要はあるが、値を使いたくない場合には
ブランク変数 _ を利用することで、未使用でもコンパイルエラーが起こらない
var hoge = 10 // 未使用だとコンパイルエラー
var _ = 0 // 未使用でも問題ない
命名規則
言語仕様による命名規則として、
小文字からスタートするのは private
大文字からスタートするのは public
となっている
インポート
C#でいうusing
using System;
using UnityEngine;
// 文字列で指定する
import "fmt"
// 括弧で括ると一気にimportできる
import (
// 改行の際に何もいらない
"os"
"fmt"
)
型
C#とだいたい同じ
bool 真偽値(true or false)
int8/int16/int32/int64 nビット整数
uint8/uint16/uint32/uint64 nビット非負整数
float32/float64 nビット浮動小数点数
complex64/complex128 nビット虚数
byte 1バイトデータ(uint8と同義)
rune 1文字(int32と同義)
uint uint32 または uint64
int int32 または int64
uintptr ポインタを表現するのに十分な非負整数
string 文字列
C#との大きな違い
float
float32 / float64 と明示的に書く必要がある (int型は必要ない)
rune
char型とは中身が微弱に違う
string
C#のようにstring[n] で1文字指定して取得することはできない
Goの場合、string[n]だとbyte単位、for文で取り出すとrune単位
変数宣言
Go言語は動的型付け言語なので構文が特殊です。
varステートメント を変数 (Variables**)** 宣言に利用します。
int hoge = 123;
// C#での型推論 見やすいね
var piyo = 456;
// 省略無しの変数宣言(初期値は0)
var hoge int = 123
// 初期値により型名が明白な場合、
// 型名を省略できる
var hoge2 = 123
// 初期値を指定する場合に = ではなく、
// := を用いるとvarも省略可能
hoge3 := 123
一括変数宣言と代入
// C#は一括で宣言は行えるが
// 初期値を指定することはできない
int a, b;
// 同じ値を代入する場合は=で繋げることができる
a = b = 3;
// 丸括弧で括ると一括で宣言を行える
var (
hoge int
hoge2 int = 456
// ここで初期値も指定しているので、
// 型名も省略できる
hoge3 = 789
)
// 複数個の変数に対しても型名の省略と
// 代入を行える
piyo, piyo2, piyo3 := 1, 2, 3
定数 (const)
// C#のconst
const int hoge = 334;
// c# 10.0 からは文字列補完でも、
// {}の中身がconst文字列の場合に限り、
// 補完結果もconstにできる
const string hoge = "hoge";
const string fuga = "fuga";
const string piyo = $"{hoge} {fuga}";
// piyoには "hoge fuga" が代入されている
// 次のように中身が文字列ではない場合エラーになる
const int hage1 = 334;
const string hage2 = $"{hage1}"; // エラー
// 型無し定数 (untyped constant)
// 指定された値によって型推論する
const hoge = 334
// 明示的に型を指定していないので
// 次のように異なるデータ型変数に代入できる
var fuga int = hoge
var piyo float64 = hoge
// 複数の定数宣言も行える
const (
hage1 = 123
hage2 = 456
)
// 型付き定数 (typed constant)
// 型を明示的に指定して宣言する
const hoge int = 334
// 型が明示されているので
// 同じ型の変数にしか代入できない
var fuga int = hoge // これはOK
var piyo float64 = hoge // エラー
// 代入する場合には明示的なキャストが必要
var piyo float64 = float64(hoge)
// 複数の宣言でも型無し定数と
// 型付き定数を宣言できる
const (
hoge = 0
piyo int = 1
)
(Goのみ)定数への値の自動割り当て
定数の宣言時に iotaキーワード を割り当てると、0から順番に数値の連番を割り当てる
const (
hoge = iota // 0
fuga // 1
piyo // 2
hage // 3
)
// iotaに値を割り当てると、その数基準に連番となる
const (
hoge2 = iota + 10 // 10
fuga2 // 11
piyo2 // 12
hage2 // 13
)
標準入出力
Go言語では標準入出力の種類が多め
標準入力 (fmt ライブラリ)
// 改行またはスペースまで読み込む
// 型は事前に定義しておく
var str string
fmt.Scan(&str)
// スペース区切りの数が分かっている入力を受け取る
var arr [3]int
fmt.Scan(&arr[0], &arr[1], &arr[2], &arr[3])
標準入力 (bufio ライブラリ)
// 入力はstring型で受け取る
// 標準入力を受け付けるスキャナ
var scanner *bufio.Scanner = bufio.NewScanner(os.Stdin)
// 1行分のスキャンを行い、スキャナ内に格納する
scanner.Scan()
// スペース区切りで分割する
var inputs []string = strings.Split(scanner.Text(), " ")
標準出力 (fmt ライブラリ)
var name string = "hoge"
var age int = 24
// 改行無しで与えられた引数をスペース区切りで標準書式(%v)で出力
fmt.Print("Name:", name, " Age:", age) // 出力: Name:hoge Age:24
// 改行有りで与えられた引数をスペース区切りで標準書式で出力
fmt.Println("Name:", name, "Age:", age) // 出力: Name:hoge Age:24 (\n)
// 書式指定子を利用して、値を出力する
fmt.Printf("Name: %s Age: %d", name, age) // 出力: Name: hoge Age: 24
明示的なキャスト
Go言語はキャストする際に変数名を丸括弧でくくる
int hoge = 123;
float fuga = (float)hoge;
var hoge int = 123
var fuga float64 = float64(hoge)
配列 (array)
Go言語における配列とは、コンパイル時にサイズが決まっていて変更不可のもの
途中でサイズを変更することはできない
int[] hoge = new int[n];
var hoge = new int[n];
int[] hoge = new[] {n1, n2, n3};
int[] hoge = {n1, n2, n3};
// C# 12からはコレクション式が利用できる
int[] hoge = [n1, n2, n3];
// 型名の前にサイズを指定する
var hoge [n]int = [n]int{}
// 左辺の型名を省略する場合、
// サイズ指定は右辺のみ
var hoge = [n]int{}
hoge := [n]int{}
// サイズに...を指定すると、
// 初期値に指定された値の数によって
// 配列のサイズが決まる
hoge := [...]int{1, 2}
// 次のように:を使用すると
// n番目の要素に1を指定できる
// それ以外の要素は0
hoge := [...]int{n: 1}
使い方はどちらも同じ
スライス (slice) C#でいうListに近い
Go言語において、厳密ではないがイメージとして
サイズが固定のもの → 配列
サイズが可変のもの → スライス
と考えると良いかもしれない
List<int> hoge = new List<int>();
var hoge = new List<int>();
// C# 12からはコレクション式が利用できる
List<int> hoge = [];
// 要素追加
hoge.Add(334);
// 宣言自体は配列とほぼ同じ
// サイズは指定しない
var hoge []int = []int {}
var hoge = []int{}
var hoge := []int{}
// スライスは配列への参照型で値を持つので、
// 配列から部分列を取り出して作成することもできる
hoge := [...] string{"piyo", "piyo2"}
fuga := hoge[0:2] // fuga = [piyo piyo2]
// 上記の操作は次の指定方法がある
arrName[n1:n2] // n1 から n2 - 1まで
arrName[n:] // n から末尾まで
arrName[:n] // 先頭から n - 1 まで
arrName[:] // 先頭から末尾まで
// 要素の追加は
// append(追加するスライス, 追加する要素)
newHoge = append(hoge, n)
// appendとは末尾に要素を追加した新しいスライスを返す
<aside>
💡 スライスは配列への参照型で値を持つ
</aside>
要素数の確認
List<int> hoge = [1, 2, 3];
hoge.Count;
int[] hoge = [1, 2, 3];
hoge.length;
//
arr := [...]int {1, 2, 3}
slice := arr[0:2]
// 長さ(length)はそれに含まれる要素数
// 容量(capacity)はスライスの最初の要素
// から数えて、元となる配列の要素数
len(slice) // sliceの要素数は2
cap(slice) // sliceの生成元の要素数は3
サイズを指定しての宣言
List<int> hoge = new List<int>(n)
// make()関数は
// 第一引数 []T 型
// 第二引数 len 長さ
// 第三引数 cap 容量
hoge := make([]int, n1, n2)
スライス (List) は容量超過時に倍のメモリを確保し要素をコピーするので、
宣言時に容量をあらかじめ確保しておくことで、超過時のメモリ再確保を減らして速度を高めることができる
スライスの初期値
null ではなく、nil
マップ(map) C#でいうDictionary<T>
Dictionary<string, int> hoge = new()
{
{"x", 100},
{"y", 200}
};
// map[keyの型]valueの型で宣言し、
// 要素の初期化は : 区切りで行う
hoge := map[string]int {
"x": 100,
"y": 200, // 末尾の , は省略できない
}
// マップに要素追加
hoge["addKeyName"] = value
// マップの要素削除
delete(hoge, "keyName")
// マップの長さを求める
len(hoge)
// マップに要素が存在する
_, flag := hoge["searchKeyName"]
If 文
インデントと括弧にさえ気を付ければ、どちらも同じ
if (hoge == true)
{
Console.WriteLine("hoge");
}
else if (fuga == false)
{
Console.WriteLine("fuga");
}
else
{
Console.WriteLine("piyo");
}
// 条件に括弧は必要ない
if hoge == true {
fmt.Println("hoge")
// if文を続ける際に下記の通りにインデント、
// 改行をしないとコンパイルエラー
} else if fuga == false {
fmt.Println("fuga")
} else {
fmt.Println("piyo")
}
Switch文
基本的なSwitch文のみ扱う
パターンマッチングなどは扱わない
int hoge = 334;
switch (hoge)
{
case 3:
Console.WriteLine("hoge");
break;
case 34:
Console.WriteLine("fuga");
break;
// 上記のcaseの条件に当てはまらない場合
// default内の処理が実行される
default:
Console.WriteLine("piyo");
break;
}
hoge := 334
// Go言語ではbreakや括弧は記述しない
// switch 式 {...} では式の値によって
// 処理を振り分ける
switch hoge {
case 3:
fmt.Println("hoge")
case 34:
fmt.Println("fuga")
default:
fmt.Println("piyo")
}
// switch {...} ではcase分に条件を記述できる
switch {
case fuga < piyo:
fmt.Println("piyo big")
case fuga > piyo:
fmt.Println("fuga big")
default:
fmt.Println("Equal")
}
// 次の処理も実行できるfallthrough句がある
switch hoge {
case 3:
fallthrough
case 34:
fmt.Println("fuga")
default:
fmt.Println("piyo")
}
// この場合hogeが3または34の場合に
// "fuga"が実行される
While文
Go言語にそんなものは無い
For文
通常のfor文ではどちらも同じ
括弧は必要ない
for (var i = 0; i < 10; i++)
{
Console.WriteLine("hoge");
}
// 通常のカウントアップ式for文
for i := 0; i < 10; i++ {
fmt.Println("hoge")
}
// 条件を省略すると無限ループ
for {
n++
if n > 10 {
break
} else if n % 2 == 1 {
continue
} else {
fmt.Println("hoge")
}
}
// C#でいうForeach文
// range句を使用して中身を取り出す
for index, item := range array {
fmt.Printf("%d: %s\n", index, item)
}
Goto文
goto文は指定したラベルにジャンプする
セミコロン以外どちらも同じ
goto LABEL; // LABEL: の位置に遷移する
// skip
// skip
// skip
// skip
LABEL:
Console.WriteLine("hoge");
goto LABEL // LABEL: の位置に遷移する
// skip
// skip
// skip
// skip
LABEL:
fmt.Println("hoge")
関数宣言
C#と違い、戻り値で複数の型の値を返すことができる
// 戻り値無しという概念は無く
// あくまでも戻り値はvoidである
public void funcA()
{
}
// params を用いると可変引数を実現できる
public void funcB(params int[] x)
{
}
// 関数宣言時に func句を使用する
// 括弧の中に引数、後ろに戻り値を指定する
func funcA(x int, y int) int {
return x + y
}
// 戻り値が複数の場合には括弧で括る
func funcB(x int, y int)(int, int) {
return x + y, x - y
}
// ... を用いることで可変引数を実現できる
func funcC(a int, b ... int) {
for i, num := range b {
}
}
// 戻り値無しの場合
// 括弧の後ろに何も記述しない
func funcD() {
}
構造体
Go言語ではクラスが無いので、構造体を使用する
構造体にはメンバ変数のみを定義し、クラスメソッドに相当する関数は関数名の前に、
**(**thisに相当する変数 *構造体名) をつけて定義する。
public struct Person
{
public string name;
private int age;
public void Func(string name)
{
this.name = name;
}
}
var hoge = new Person
{
name = "hoge",
};
// 宣言時にtype句を使用
type Person struct {
name string
age int
}
func (p *Person) SetFunc(name string) {
p.name = name
}
func (p *Person) GetFunc() string {
return p.name
}
// 構造体のメンバの内、
// 大文字で始まるものは外からアクセス可能
// 小文字で始まるものは外からアクセス不可
type Person struct {
Name string // 外部からアクセス可能
age int // 外部からアクセス不可
}
// 構造体を使用する際のパラメータの初期化
// 順序通りに初期化
hoge := Person{"Yamada", 24}
// 変数名で初期化
fuga := Person{name: "Tanaka", age: 25}
type句について
構造体やインターフェース宣言時によく用いられるが、意味としては違うので気を付ける
Goを学びたての人が誤解しがちなtypeと構造体について #golang - Qiita
インターフェース
インターフェースの利用方法はどちらも同じで、
命名に関しては接頭詞に I を付けることはない
public interface IHoge
{
public void Hoge();
}
public class Fuga() : IHoge
{
public void Hoge()
{
}
}
// インターフェース宣言
type Hoge interface {
Hoge()
Fuga() int
Piyo(i int) int
}
type name struct {}
// インターフェース実装
type (n name) Hoge() {}
匿名型 (interface {}型)
匿名の型によって、型を明示的に定義しない型
// シンプルな宣言
var hoge = new
{
Name = "hoge",
age = 334,
};
// LINQなどで使うことが多い
// 宣言はこのように行う
func hoge(a interface {}) {}
// .(型名)でinterface{}型を他の型へ変換できる
func fuga(a interface {}) {
fmt.Printf("%d", a.(int))
}
// 型変換の第二戻り値は型変換可能かどうかを返す
func piyo(a interface {}) {
str, ok := a.(string)
}
ポインタ (pointer)
Go言語はガベージコレクション(GC)が搭載されているので、メモリリークは基本的に起こらない
メモリ割り当て自体は、内部で自動的にヒープ領域かスタック領域に割り当てられる
これらの機能により、メモリ安全な言語である
// オブジェクトのポインタを参照するには &
// ポインタの中身を参照するには *
var a int // int型変数
var p *int // intへのポインタ変数
p = &a // p に a のポインタを設定
*p = 334 // ポインタの中身に代入
// 演算子 . は、構造体のメンバ変数でも、ポインタでアクセスすることができる
a1 := Person{"Tanaka", 26} // 構造体Personのオブジェクトa1を確保して初期化
p1 := &a1 // 構造体a1へのポインタをp1に格納
fmt.Println(a1.name) // メンバ変数には左記のようにアクセス
fmt.Println((*p1).name) // ポインタpの中身(後続体)のメンバ変数には左記のようにアクセス
fmt.Println(p1.name) // ただし、Go言語ではこれを、左記のようにも記述できる
領域確保 (new)
ポインタ周りの話なので、C#は割愛
new()句 を用いて領域を動的に確保し、その領域へのポインタを得ることができる。
確保した領域は参照されなくなった後にガベージコレクションにより自動的に開放される。
type Hoge struct {
name string
}
hogeArr := []* Hoge{}
// 構造体のポインタを得る
hoge := new(Hoge)
hoge.name = "fuga" // (*hoge).name = "fuga" と同じ意味
参考資料
たくさんのサイト
Discussion