2
2

C# -> Golang

Last updated at Posted at 2024-08-06

目次

まえがき

C#使いがGo言語に移行するときに役立ててほしいと思い書きました。

C#についての詳しい説明は割愛します。

言語概要

  • Google が開発したプログラミング言語です。「Go言語」や「Golang」と表記されます。
  • UNIX、B言語(C言語の元)、UTF-8の開発者ケン・トンプソンや、UNIX、Plan 9、UTF-8の開発者ロブ・パイクによって設計されました。
  • 静的型付け、メモリ安全性、ガベージコレクションを備えるコンパイル言語です。
  • シンプル、高速、メモリ効率が良い、メモリ破壊が無い、並行処理が得意などの特徴を備えています。
  • メモリ破壊が無く、並行処理を得意とする、進化したC言語という側面があります。
  • Linux、Mac OS X、Windows、Android、iOS で動作します。

言語仕様

セミコロン

C系統の言語と違い、基本的にセミコロンは必要ない

C#
int hoge = 123;
int fuga = 456;
Go
// セミコロンは各行に書く必要は無いが、
// 書いてもコンパイル自体に影響はない
var hoge int = 123;
// 次のように、一行で記述する際
// 区切りとして利用する場合がある
hoge := 123; fuga := 456

インデント

C++と同じインデント

インデントや波括弧の位置を正しく記述しないと、構文として認識されずにコンパイルエラーが起こる場合がある

C#
public static void Main()
{
	Console.WriteLine("Hello World");
}
Go
func main() {
	fmt.Println("Hello World")
}

未使用の変数

Go言語では未使用の変数があると、コンパイルエラーが起こる

ブランク変数 _

宣言する必要はあるが、値を使いたくない場合には

ブランク変数 _ を利用することで、未使用でもコンパイルエラーが起こらない

Go
var hoge = 10 // 未使用だとコンパイルエラー
var _ = 0 // 未使用でも問題ない

命名規則

言語仕様による命名規則として、

小文字からスタートするのは private

大文字からスタートするのは public

となっている

インポート

C#でいうusing

C#
using System;
using UnityEngine;
Go
// 文字列で指定する
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**)** 宣言に利用します。

C#
int hoge = 123;
// C#での型推論 見やすいね
var piyo = 456;
Go
// 省略無しの変数宣言(初期値は0)
var hoge int = 123
// 初期値により型名が明白な場合、
// 型名を省略できる
var hoge2 = 123
// 初期値を指定する場合に = ではなく、
// := を用いるとvarも省略可能 
hoge3 := 123

一括変数宣言と代入

C#
// C#は , 区切りで一括宣言を行える
int a = 1, b =3;
// 同じ値を代入する場合は=で繋げることができる
a = b = 3;

// 複数個の変数に対しても型名の省略と
// 代入を行える
var (piyo, piyo2, piyo3) = (1, 2, 3);
Go
// 丸括弧で括ると一括で宣言を行える
var (
	hoge int
	hoge2 int = 456
	// ここで初期値も指定しているので、
	// 型名も省略できる
	hoge3 = 789
)

// 複数個の変数に対しても型名の省略と
// 代入を行える
piyo, piyo2, piyo3 := 1, 2, 3

定数 (const)

C#
// 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}"; // エラー
Go
// 型無し定数 (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から順番に数値の連番を割り当てる

Go
const (
	hoge = iota // 0
	fuga // 1
	piyo // 2
	hage // 3
)
// iotaに値を割り当てると、その数基準に連番となる
const (
	hoge2 = iota + 10 // 10
	fuga2 // 11
	piyo2 // 12
	hage2 // 13
)

標準入出力

Go言語では標準入出力の種類が多め

標準入力 (fmt ライブラリ)

Go
// 改行またはスペースまで読み込む
// 型は事前に定義しておく
var str string
fmt.Scan(&str)
// スペース区切りの数が分かっている入力を受け取る
var arr [3]int
fmt.Scan(&arr[0], &arr[1], &arr[2], &arr[3])

標準入力 (bufio ライブラリ)

Go
// 入力はstring型で受け取る
// 標準入力を受け付けるスキャナ
var scanner *bufio.Scanner = bufio.NewScanner(os.Stdin)
// 1行分のスキャンを行い、スキャナ内に格納する
scanner.Scan()
// スペース区切りで分割する
var inputs []string = strings.Split(scanner.Text(), " ")

標準出力 (fmt ライブラリ)

Go
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言語はキャストする際に変数名を丸括弧でくくる

C#
int hoge = 123;
float fuga = (float)hoge;
Go
var hoge int = 123
var fuga float64 = float64(hoge)

配列 (array)

Go言語における配列とは、コンパイル時にサイズが決まっていて変更不可のもの

途中でサイズを変更することはできない

C#
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];
Go
// 型名の前にサイズを指定する
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言語において、厳密ではないがイメージとして

サイズが固定のもの → 配列

サイズが可変のもの → スライス

と考えると良いかもしれない

C#
List<int> hoge = new List<int>();
var hoge = new List<int>();

// C# 12からはコレクション式が利用できる
List<int> hoge = [];

// 要素追加
hoge.Add(334);
Go
// 宣言自体は配列とほぼ同じ
// サイズは指定しない
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とは末尾に要素を追加した新しいスライスを返す
💡 **スライスは配列への参照型で値を持つ**

要素数の確認

C#
List<int> hoge = [1, 2, 3];
hoge.Count;

int[] hoge = [1, 2, 3];
hoge.length;
Go
// 
arr := [...]int {1, 2, 3}
slice := arr[0:2]
// 長さ(length)はそれに含まれる要素数
// 容量(capacity)はスライスの最初の要素
// から数えて、元となる配列の要素数
len(slice) // sliceの要素数は2
cap(slice) // sliceの生成元の要素数は3

サイズを指定しての宣言

C#
// 第一引数 n 容量
List<int> hoge = new List<int>(n)
Go
// make()関数は
// 第一引数 []T 型
// 第二引数 len 長さ
// 第三引数 cap 容量
hoge := make([]int, n1, n2)

スライス (List) は容量超過時に倍のメモリを確保し要素をコピーするので、

宣言時に容量をあらかじめ確保しておくことで、超過時のメモリ再確保を減らして速度を高めることができる

スライスの初期値

null ではなく、nil

マップ(map) C#でいうDictionary

C#
Dictionary<string, int> hoge = new()
{
    {"x", 100},
    {"y", 200}
};
Go
// map[keyの型]valueの型で宣言し、
// 要素の初期化は : 区切りで行う
hoge := map[string]int {
	"x": 100,
	"y": 200, // 末尾の , は省略できない
}
// マップに要素追加
hoge["addKeyName"] = value
// マップの要素削除
delete(hoge, "keyName")
// マップの長さを求める
len(hoge)
// マップに要素が存在する
_, flag := hoge["searchKeyName"]

If 文

インデントと括弧にさえ気を付ければ、どちらも同じ

C#
if (hoge == true)
{
	Console.WriteLine("hoge");
}
else if (fuga == false)
{
	Console.WriteLine("fuga");
}
else
{
	Console.WriteLine("piyo");
}
Go
// 条件に括弧は必要ない
if hoge == true {
	fmt.Println("hoge")
// if文を続ける際に下記の通りにインデント、
// 改行をしないとコンパイルエラー
} else if fuga == false {
	fmt.Println("fuga")
} else {
	fmt.Println("piyo")
}

Switch文

基本的なSwitch文のみ扱う

パターンマッチングなどは扱わない

C#
int hoge = 334;
switch (hoge)
{
	case 3:
		Console.WriteLine("hoge");
		break;
	case 34:
		Console.WriteLine("fuga");
		break;
	// 上記のcaseの条件に当てはまらない場合
	// default内の処理が実行される
	default:
		Console.WriteLine("piyo");
		break;
}

Go
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文ではどちらも同じ

括弧は必要ない

C#
for (var i = 0; i < 10; i++)
{
	Console.WriteLine("hoge");
}
Go
// 通常のカウントアップ式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文は指定したラベルにジャンプする

セミコロン以外どちらも同じ

C#
goto LABEL; // LABEL: の位置に遷移する
// skip
// skip
// skip
// skip
LABEL:
	Console.WriteLine("hoge");
	
Go
goto LABEL // LABEL: の位置に遷移する
// skip
// skip
// skip
// skip
LABEL:
	fmt.Println("hoge")

関数宣言

戻り値で複数の型の値を返すことができる

C#
// 戻り値無しはvoidとする(voidは型ではない)
public void funcA()
{
}
// params を用いると可変引数を実現できる
public void funcB(params int[] x)
{
}
// 戻り値が複数の場合には括弧で括る
public (int, int) funcC(int x, int y)
{
	return (x + y, x - y);
}
Go
// 関数宣言時に 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に相当する変数 *構造体名) をつけて定義する。

C#
public struct Person
{
    public string name;
    private int age;
    
    public void Func(string name)
    {
	    this.name = name;
    }
}
var hoge = new Person
{
	name = "hoge",
};
Go
// 宣言時に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 を付けることはない

C#
//接頭詞の I は言語仕様ではなくあくまで推奨事項
public interface IHoge
{
	public void Hoge();
}

public class Fuga() : IHoge
{
	public void Hoge()
	{
	}
}
Go
// インターフェース宣言
type Hoge interface {
	Hoge()
	Fuga() int
	Piyo(i int) int
}
type name struct {}
// インターフェース実装
type (n name) Hoge() {}

匿名型 (interface {}型)

匿名の型によって、型を明示的に定義しない型

C#
// シンプルな宣言
var hoge = new
{
	Name = "hoge",
	age = 334,
};
// LINQなどで使うことが多い
Go
// 宣言はこのように行う
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)が搭載されているので、メモリリークは基本的に起こらない

メモリ割り当て自体は、内部で自動的にヒープ領域かスタック領域に割り当てられる

これらの機能により、メモリ安全な言語である

Go
// オブジェクトのポインタを参照するには &
// ポインタの中身を参照するには *
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()句 を用いて領域を動的に確保し、その領域へのポインタを得ることができる。

確保した領域は参照されなくなった後にガベージコレクションにより自動的に開放される。

Go
type Hoge struct {
	name string
}

hogeArr := []* Hoge{}
// 構造体のポインタを得る
hoge := new(Hoge)
hoge.name = "fuga" // (*hoge).name = "fuga" と同じ意味

参考資料

A Tour of Go

とほほのGo言語入門 - とほほのWWW入門

たくさんのサイト

2
2
1

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
2
2