言語仕様比較 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");