言語仕様比較 c++ c# Lua golang 少しRust

投稿者: | 2019年7月8日

表だと詳細が書きにくいので、こちらに詳細を書いていきます。
なお、言語仕様の比較と基本ライブラリの比較になります。
golang は興味があったので。
随時更新

コメント

// コメント
/*
    コメント
*/
// コメント
/*
    コメント
*/
-- コメント
--[[
    コメント
]]--
// コメント
/*
    コメント
*/
// コメント
/*
    コメント
*/
/// このコメントの下の内容に関するドキュメントとなります
//! このコメントを含むソースのドキュメントになります

ヌル

nullptr
null
nil
nil
ptr::null()

2進数リテラル

0b1000'1111
0B0101
// 'で区切れる
0b1000_0011
0B1111
// _で区切れる
ない?
ない?
0b0101_0001
// _で区切れる

// 0B0011 error 大文字 B は無効

8進数リテラル

0765
// 頭に 0 をつけると 8進数になります。
なし
なし
0765
0o07
// 小文字の o

10進数リテラル

12345
1'000'000
// 'で区切れる
12345
1_000_000
// _で区切れる
// C#7 からの仕様だが Unity でも使えた
12345
12345
12345
1_000_000
// _で区切れる

16進数リテラル

0xff
0xFF
0x1p-1022 // c++17 から十六進浮動小数点数リテラルが採用された
0xff
0xFF
0xff
0xFF
0x1p1023 -- 指数部が1024以上だと無限大 inf になる
0xff
0xFF
0xff
0xAB

実数

3.14f
1.163e-36f
3.14f
1.163e-36f
3.14
1.163e-36
3.14
1.163e-36
3.14
1.163e-36

文字列リテラル

std::string text = "ABC";
std::string_view text = "ABC"; // c++17 から追加。コピーではなく参照を持つ
string text = "ABC";
System.String text = "ABC";

// @をつけると複数行可 エスケープなし
var text =@"https://blog.nekoteam.com/";
var text = @"abc
def";

// よく使う新機能
// $をつけると format構文が可能
// string.Format("abc={0} def={1}",abc,def)と同じ
var text = $"abc={abc} def={def}"; 
// メソッドを入れることも可能
UnityEngine.Debug.Log( $"pos={transform.position.ToString()} rot={transform.rotation.ToString()}");
-- この4例は全部同じ出力です。
text = "abc\ndef"
text = 'abc\ndef'

text = [[abc
def]]

text = [==[
abc
def]==]
text := 'abc'
text := "abc"
let text = "abc\def";

// 複数行も可能
let text = "abc
def";

文字数取得

auto length = text.size();
auto lenght = text.length();
auto length = text.end() - text.begin();
var length = text.Length;
length = #text
length := len(text)
let length = text.chars().count();

配列

// レガシーな固定配列、arrayはintのポインタと変わらない
// 範囲外の操作をしてもエラーが出ないので、おすすめできない
int array[3] = { 1, 2, 3 };

// 固定配列
std::array<int,3> array = { 1, 2, 3 };

// 可変長配列
// 実装は連続したメモリの領域である。
// 末尾の追加・削除は高速である。
// 挿入や削除は、再確保してコピーするので低速である
std::vector<int> array = { 1, 2, 3 };

// 双方向リスト
std::list<int> list = { 1, 2, 3};
// 固定配列
// 基本的に struct では使えない
var array = new int[]{ 1, 2, 3 }

// 多次元配列
var matrix = new int[3,3]{ { 0,1,2}, {3,4,5}, {6,7,8} };
// 配列の配列
var matrix = new int[3][]{
    new int[3]{ 0,1,2 },
    new int[3]{ 3,4,5 },
    new int[3]{ 6,7,8 },
};
// 配列の配列の方がアクセスが早いとの報告があります。

// 可変長配列
var array = new System.Collections.Generic.List<int>(){ 1,2,3 };

// 双方向リスト
var list = new System.Collections.Generic.LinkedList<int>();
-- テーブルを配列に見立てて使用する。
array = { 1, 2, 3 }
// 固定配列
array := [3]int{ 1, 2, 3 }

// スライス
array := make( []int. 3)
array := []int{ 1, 2, 3}
// 固定配列
let a: [i32; 3] = [1,2,3];

// スライス
let array = &a[..];
let array = &a[1..4]; 

// 可変長配列
let array = vec![1,2,3,];

キャスト

dynamic_cast<type>(a)
static_cast<type>(a)
const_cast<type>(a)
reinterpret_cast<type>(a)
(type)a
type(a)
a as type
(type)a
まだよくわからん
type(a)
a as Type

比較演算子

==
!=
<
>
<=
>=
==
!=
<
>
<=
>=
==
~= これをよく間違える
<
>
<=
>=
==
!=
<
>
<=
>=
==
!=
<
>
<=
>=

論理演算子

&&
||
!
&&
||
!
and
or
not
&&
||
!
&&
||
!

算術演算子

+ 和
- 差
* 積
/ 商
% 剰余
++a 前置インクリメント 加算後評価される
a++ 後置インクリメント 評価後加算される
--a 前置デクリメント 減算後評価される
a-- 後置デクリメント 評価後減算される
+= 加算代入
-= 減算代入
*= 乗算代入
/= 除算代入
%= 剰余代入
// c++ と同じですかね?
+ 和
- 差
* 積
/ 商
% 剰余
++a 前置インクリメント 加算後評価される
a++ 後置インクリメント 評価後加算される
--a 前置デクリメント 減算後評価される
a-- 後置デクリメント 評価後減算される
+= 加算代入
-= 減算代入
*= 乗算代入
/= 除算代入
%= 剰余代入
+ 和
- 差
* 積
/ 商
% 剰余
^ べき乗
// 切り捨て除算
+ 和
- 差
* 積
/ 商
% 剰余
a++ 後置インクリメント
a-- 後置デクリメント
+= 加算代入
-= 減算代入
*= 乗算代入
/= 除算代入
%= 剰余代入
+ 和
- 差
* 積
/ 商
% 剰余
+= 加算代入
-= 減算代入
*= 乗算代入
/= 除算代入
%= 剰余代入

// インクリメント演算子++ デクリメント演算子-- は無いみたいです。
// オブジェクトのメソッドであるため 式 として採用されないのでしょう

関数定義

int func()
{
    処理
    return 0;
}
int func()
{
    処理
    return 0;
}
function func()
    処理
    return 0
end
func() int {
    処理
    return 0
}
fn func() -> i32
{
    処理
    0
}

可変長引数

C育ちのせいか、自分は可変長引数は印象悪いです。
他人の関数を呼ぶ場合は仕方なく使いますが、自分からは可変長引数の関数は作らないですね。

// レガシーな方法
// 使いたくない
void func( int quantity, ...)
{
    va_list args;
    va_start( args, quantity);
    for( int i = 0; i < quantity; ++i)
    {
        // パラメータがintである保証はない。
        // 使う側との相談が必要
        int value = va_arg( args, int);
    }
    va_end( args);
}

// 可変引数テンプレートを使用
// 再帰呼び出しを繰り返し、最後に空の func() が呼ばれて終了
void func(){}
template <class... Args>
void func( int first, Args... args)
{
    // 処理 first に次々引数の値が入ってくる
    func( args...);    
}
// 利用例
func( 1,2,3,4); 

// 言語仕様ではなくライブラリで無理に解決しようとしているので、カオスに感じる。
// params キーワードを使う
void func( params int[] param)
{
    foreach( var value in param)
    {
        // 処理
    }
}

// __arglist キーワードを使う
// Unity2019 では コンパイルが通らない
using System;
void func( __arglist)
{
    ArgIterator argumentIterator = new ArgIterator(__arglist);
    while( argumentIterator.GetRemainingCount() > 0)
    {
        var arg = argumentIterator.GetNextArg();
        var t = __reftype( arg);
        if( t == typeof(int))
        {
            int value = __refvalue( arg , int);
            // 処理
        }
    }
}
func( __arglist( 1, 2, 3, 4)); // 呼び出しにも __arglist をつける
function func(...)
    local param = {...}
end
func test( param ...int) {
    for _, value := range param {
        // 処理
    }
}
// なし
// スライスで代用
fn func( param: &[i32])
{
	for value in param
	{
		println!( "{}", value);
	}
}
func( &[1, 2, 3, 4, 5]);

if

if( exp)
{
    // true
}
else
{
    // false
}
if( exp)
{
    // true
}
else
{
    // false
}
if a == 0 then
    -- a=0
elseif a == 1 then
   -- a=1
else
    -- その他
end
if a == 0 {
    // ok
} else if a == 1 {
    // ok
} else
if a == 2 {
    // ok
} else {
    // ok
}

// 以下の書き方はエラーになる
if exp {
}
else { // ここで syntax error
}
if a == 0 {
    // a = 0
}
else
{
    // a != 0
}

三項演算子

std::string result = ( true ) ? "真" : "偽";
string retult = true ? "真" : "偽";
-- なし、代わりに and or で代用する
result = ( true) and "真" or "偽"

-- A and B : Aが true なら A を返し、false なら B を返す
-- A or B : Aが false なら B を返し、false なら A を返す
-- らしい、A and B or C で B が nil/false/void型 だと
-- A が true でも C が返ってくるので お薦めできない
なし
// なし、 if が式なので大抵こう書く
let result = if true { "真" } else { "偽" };

switch

switch( exp)
{
    case 0:
        break;
    case 1:
        break;
    case 2:
        break;
    case 3:
        // break を書かなければ 下の case 4 に素通り
        // 意図して書く場合があるが、他人が見ると忘れてるのかと思うので
        // コメントを書くと吉
    case 4:
        break;
    default:
        // どれにも当てはまらなければ default に飛んでくる
        break;
}
switch( exp)
{
    case 0:
        break;
    default:
        break;
}

// 文字列も使える
switch( text)
{
    case "neko":
        break;
    case "inu":
        break;
    default:
        break;
}

// C#7 から case に型を付けれるようになった。
// Unity2019 でも可能確認
switch( obj)
{
    case int n:
        a = a + n;
        break;
    case string s:
        length = s.Length;
        break;
    default:
        break;
}
なし
// break は存在しない
switch exp {
    case 0:
       // exp == 0
    case 1,2,3:
      // exp == 1 or 2 or 3
}

// テキストも行ける
switch text {
    case "neko":
    case "inu":
}

// 条件式もかける
// なにこれこわい
switch {
    case exp <= 1:
        // 処理
    case (2 <= exp && <= 5):
       // 2 ~5
    case ( exp <= 10):
      // 6,7,8,9,10
    default:
        // そのた
}
// switch はない
// 分岐条件はmatch を使う

match exp
{
    0 => println!( "0"),
    1 => println!( "1"),
    2 => println!( "2"),
    3 => println!( "3"),
    _ => println!( "default"),
}

// match は式であるため代入が可能
let result = match exp
{
    0 => "zero",
    1 => "one",
    2 => "tow",
    3 => "three",
    4 => "four",
    _ => "over flow"
};

for

for( int index = 0; index < 10; ++index)
{
    // 処理
}
for( int index = 0; index < 10; ++index)
{
    // 処理
}
for i = 0, 10, 1 do
    --処理
end
for i := 10; i < 10; ++i {
    // 処理
}
for i in 0..10
{
    // 処理
}

範囲ベースfor

for(  auto p : map)
{
    auto key = p.first;
    auto value = p.second;
}
foreach( var p in dictionary)
{
    var key = p.Key;
    var value = p.Value;
}
for key,value in pairs(tbl) do
    --処理
end
for key, value := range _map {
    //処理
}

while

while( exp)
{
    // 処理
}
while( exp)
{
    // 処理
}
while exp do
    -- 処理
end
// while はなし
// 代わりに for を使う
for exp {
}
while exp
{
    // 処理
}

// 無限ループは
loop
{
    // 処理
}

do-while

do
{
    // 処理
} while( exp);
do
{
    // 処理
} while( exp);
repeat
    --処理
until exp
// なし
// 代わりに for if break を使う
for {
    // 処理
    if exp {
        break
    }
}
なし?

break

break;
break;
break
break
break;

// ループラベルが使える これはいいね。gotoが減る
'name: for x in 0..10
{
    break 'name;
}

continue

continue;
continue;
--なし
--gotoで代用するとか
while true do
    if exp then
        goto continue
    end
    ::continue::
end
continue
continue;

// ループラベルが使える これはいいね。gotoが減る
'name: for x in 0..10
{
    continue 'name;
}

ラムダ式

[](int n) -> int { return n*n; };
(int n) => { retunr n*n; }
(int n) => n*n;
n => n*n;
function( n) return n*n end
func( n int) int { return n*n }
// クロージャ
| n:i32 | -> i32 { n*n }
| n:i32 | n*n

デリゲート

std::function<int(int)> f = []( int n)
{
    return n*n;
}
System.Func<int,int> f = ( int n) =>
{
    return n*n;
}
f = function( n) return n*n end
f := func( n int) int { return n*n }
let f = |n:i32| -> i32 { n*n };
let f = |n:i32| n*n ;

並列処理

スレッドとコルーチンは別物ですが、目的が並列処理ということで一緒に掲載します。Unity のコルーチンは言語仕様でないのでここでは紹介しません。

// この例では std::cout がスレッドセーフでないため mutex を使って 排他制御 している
// 完全に独立していれば mutex 不要で、最大のパフォーマンスを発揮できるはず
void func()
{
	std::mutex mutex;
	std::future<void> futureA = std::async( std::launch::async, [&mutex]() -> void {
		for( int i = 0; i < 10000; ++i)
		{
			mutex.lock();
			std::cout << "A" << i << std::endl;
			mutex.unlock();
		}
	});
	std::future<void> futureB = std::async( std::launch::async, [&mutex]() -> void {
		for( int i = 0; i < 10000; ++i)
		{
			mutex.lock();
			std::cout << "B" << i << std::endl;
			mutex.unlock();
		}
	});
}
using System.Threading.Tasks;
void func()
{
	Task[] tasks = new Task[]
	{
		Task.Run( () => {
			for( int i = 0; i < 1000; ++i)
			{
				Console.WriteLine( $"A={i}");
			}
		}),
		Task.Run( () => {
			for( int i = 0; i < 1000; ++i)
			{
				Console.WriteLine( $"B={i}");
			}
		}),
	};
    // tasks[]内のタスクが全部終わるまで待つ
	Task.WaitAll( tasks);
}
-- 並行処理処理というより、中断・再開を処理する
function coLoopA()
	for i = 0, 10, 1 do
	    print( "loopA "..i)
	    coroutine.yield() -- 一時中断
	end
end

function coLoopB()
	for i = 0, 10, 1 do
	    print( "loopB "..i)
	    coroutine.yield() -- 一時中断
	end
end

-- 関数のコルーチンを作る
coA = coroutine.create( coLoopA)
coB = coroutine.create( coLoopB)

-- 終わるまでループさせる
while coA ~= nil or coB ~= nil do
	if coA ~= nil then
	   coroutine.resume( coA) -- コルーチンを進める
		if coroutine.status( coA ) == "dead" then
			coA = nil
		end
	end

	if coB ~= nil then
	   coroutine.resume( coB) -- コルーチンを進める
		if coroutine.status( coB ) == "dead" then
			coB = nil
		end
	end
end
// コルーチンとは違い、別スレッドで並列に動いている
package main

import (
    "fmt"
	"sync"
)

var wg sync.WaitGroup

func goLoopA() {
	for i := 0; i < 10000; i++ {
		print( fmt.Sprintf( "goLoopA=%v\n", i))
	}
	defer wg.Done()
}

func goLoopB() {
	for i := 0; i < 10000; i++ {
		print( fmt.Sprintf( "goLoopB=%v\n", i))
	}
	defer wg.Done()
}

func main(){
	wg.Add( 2)
	go goLoopA();
	go goLoopB();
	wg.Wait()
}

タプル

よく使います。局所的に構造体を作ることなく、複合体を受け渡しできるので便利です。

auto tuple = std::make_tuple( 1, 2, 3);
std::cout << std::get<0>(tuple) << std::get<1>(tuple) << std::get<2>(tuple) << std::endl;
// 123

auto tuple = std::forward_as_tuple( 4, 5, "abc");
std::cout << std::get<0>( tuple ) << std::get<1>( tuple ) << std::get<2>( tuple ) << std::endl;
// 45abc
// C#7以前
var tuple = new System.Tuple<int,int,int>( 1, 2, 3);
Console.WriteLine( $"{tuple.item1} {tuple.item2} {tuple.item3}");
// 1 2 3

// C#7から
// Unity2019 でも使えた
var tuple = ( x:1, y:2, z:3);
Console.WriteLine( $"{tuple.x} {tuple.y} {tuple.z}");
// 1 2 3

( int x, int y, int z) tuple = (x:1, x:2, x:3);
Console.WriteLine( $"{tuple.x} {tuple.y} {tuple.z}");
// 1 2 3
-- タプルというかテーブル
tuple = { x = 1, y = 2, z = 3 }
print( tuple.x.." "..tuple.y.." "..tuple.z)
-- 1 2 3
// go の tuple があるかわからない
// 無名構造体で代用するとこんな感じですかね?あまりスマートでない印象
tuple := struct {
	x int
	y int
	z int
}{ 1, 2, 3 }
print( fmt.Sprintf( "%v %v %v\n", tuple.x, tuple.y, tuple.z))
// 1 2 3
let tuple = ( 1, 2, 3);
println!( "{} {} {}", tuple.0, tuple.1, tuple.2);
// 1 2 3

let ( x, y ,z) = ( 1, 2, 3);
println!( "{} {} {}", x, y, z);
// 1 2 3

列挙型

enum class Language : char
{
	Cpp,
	Cs,
	Lua,
	Golang,
};
Language lang = Language::Cpp;
// キャストが楽になった
Language lang = (Language)0; // C++17
Language lang = static_cast<Language>( 0); // C++11

// レガシー版
// スコープがないので名前衝突が起こりやすい
enum Language
{
	Cpp,
	Cs,
	Lua,
	Golang
};
// 昔はこうしていた
struct Language
{
	enum enum_t
	{
		Cpp,
		Cs,
		Lua,
		Golang
	};
};
Language::enum_t lang = Language::Cpp;
enum Language : byte
{
	Cpp,
	Cs,
	Lua,
	Golang,
}
var lang = Language.Cpp;

// フラグとして利用する場合
[System.Flags]
enum Language : byte
{
	Cpp = (1<<0),
	Cs = (1<<1),
	Lua = (1<<2),
	Golang = (1<<3),
}
var flag = Language.Cpp | Language.Cs;
なし
// なし
// iota で代用
package enum
type Language int
const(
	_ Language = iota // 0番目は無視するのが一般的らしい
	Cpp
	Cs
	Lua
	Golang
)
language := Cpp
enum Language 
{
    Cpp,
    Cs,
    Lua,
    Golang,
    Rust,
}
let lang = Language::Rust;

ラムダ式の再帰呼び出し

定番のハノイの塔をラムダ式の再帰呼び出しで実装例

// 自分をキャプチャすると自分自身を呼べる
std::function<void(int,std::string_view const&,std::string_view const&,std::string_view const&)> hanoi = [&hanoi]( int n, std::string_view const& a, std::string_view const& b, std::string_view const& c)
{
	if( n >= 1)
	{
		hanoi( n-1, a, c, b);
		std::cout << a << "から" << c << std::endl;
		hanoi( n-1, b, a, c);
	}
};
hanoi( 3, "A", "B", "C");
// 宣言とインスタシングを分けると自分自身を呼べる
System.Action<int,string,string,string> hanoi = null;
hanoi = ( n, a, b, c) =>
{
	if( n >= 1)
	{
		hanoi( n-1, a, c, b);
		System.Console.WriteLine( $"{a}から{c}");
		hanoi( n-1, b, a, c);
	}
};
hanoi( 3, "A", "B", "C");
function hanoi( n, a, b, c)
	if n >= 1 then
		hanoi( n-1, a, c, b);
		print( a.."から"..c);
		hanoi( n-1, b, a, c);
	end
end
hanoi( 3, "A", "B", "C")
// 宣言とインスタシングを分けると自分自身を呼べる
var hanoi func( n int, a string, b string, c string)
hanoi = func( n int, a string, b string, c string) {
	if( n >= 1) {
		hanoi( n-1, a, c, b);
		print( fmt.Sprintf( "%vから%v\n", a, c))
		hanoi( n-1, b, a, c);
	}
};
hanoi( 3, "A", "B", "C");
fn hanoi ( n:i32, a:&str, b:&str, c:&str){
	if n >= 1
	{
		hanoi( n-1, a, c, b);
		println!( "{} から {}", a, c);
		hanoi( n-1, b, a, c);
	}
}
hanoi( 3, "A", "B", "C");

関連項目

文字列→数値、数値→文字列

ディレクトリ内のファイルを再帰的に列挙する

文字列操作 比較 検索 置換

コマンドライン引数

コメントを残す