前回の復習
型を修了. 演算子/制御構文についてやっていく
§3 演算子
用語の解説. オペレーターこと演算子に対してオペランドこと被演算子が存在.
X(operand) =(operator) 3(operand) +(operator) 2(operand)
c#においては, 何かしらの値を返すもののことを式といい, 式から構成されて";"で占められる構造を文(値を返さずともよい)というんだって
3.1 算術演算子(代数演算子)
あるあるのやつら. インクリメントとデクリメント久しぶりに見た.
- + たしざん
- - ひきざん
- * かけざn
- / わりざん
- % じょうよ
- ++ いんくりめんと
- -- でくりめんと
インクリメントとデクリメントに関しては前置か後置の違いで副作用が変わる.
3.1.1 被数値が存在する演算
"+"は文字列結合も表す. 数値と文字列における暗黙的な型変換のルールを確認する
1 + 2 // 3
"a" + 5 // a5
5 + "b" // 5b
"1" + "2" // 12
"a" + DateTime.Now // a2019/01/20 10:35:50
ちなみに文字列ではなくて'a'+2
を実行してみたら99と返ってきた.
そういえばcharは数値だったね...
基本的に文字列型に優先して変換される模様.
DateTime.Nowみたいなものも内部でToStringが呼ばれるみたい.
ただし
- 文字列 + obj だった場合はobj.ToString()が呼ばれる
- 数値 + obj だった場合はエラー
よく覚えておこう
ちなみにJuliaという言語で文字列結合が"*"扱われている理由が, 文字列結合は可換ではないから積だ! というのを思い出した. 次いこう
3.1.2 連続した文字列結合に注意
+ による文字列結合はimmutableなstringに対しては非効率なので, 5~6文字程度の連結じゃなくループ内での連結の時はmutableなstringを使おうとのこと
`StringBuilder'クラスというのがそのmutable stringらしい.
中で大き目のメモリを確保するらしい.
// immutable
var result = "";
for (int i=0; i<100000; i++){
result += "abc";
}
// mutable
using System.Text;
var builder = new StringBuilder();
for (int i=0; i<100000; i++){
builder.Append("abc"); // use Append to join mutable strings
}
あらかじめあらかたの文字列サイズが決まっている場合はStringBuilder(1000)という風にサイズを指定したほうが良いらしい.
オーバーヘッドしたときの処理が大き目なのだそうな
3.1.3 インクリメント/デクリメント
前置, 後置演算での挙動の確認がはいった.
基本的に意識する必要はないが, 変数の代入の際に気を付けたほうが良いとな
- n = ++m , (m=m+1) -> (n=m)
- n = m++ , (n=m) -> (m=m+1)
リテラル操作はできない
1++などはできないらしい.
つまり, (m++)++もできない. (式評価の値が返るため?)
mに二回インクリメントをしたいのであれば, m+=2
を使おうとのこと.
また代入の操作が入ってしまうため, constに対する操作もきかない
3.1.4 除算とデータ型
整数同士の除算
- 3/4 -> 0
- 3d/4 -> 0.75
少数に対して暗黙型変換が発生する.
個人的に double result = 3/4 // 0
というのが, たしかに, ポイントだった
ゼロ除算
整数だとError, 少数だと除算の場合∞が, 剰余の場合はNaNが帰るらしい.
数学においてはゼロ徐算は定義不可能となるはずなんだけれど...なぜこうなるんだw
3.1.5 浮動小数点の演算に注意
浮動小数点は0.1をうまく表現することができないから, 一部の実行をするときは丸めないとだめだよという内容
float/doubleでは内部ではこの誤差はカバーできてないため, decimal型を使おう
0.2*3 == 0.4 //false
0.2M * 3M =0.6M // true
ただし, decimalは計算は遅いし, 扱える範囲も狭い. そのため, 整数計算をした後で10で除算する方法も追記.
2*3 / 10d == 0.6 // true
3.2 代入演算子
今までつかってきた代入演算子と, 算術なりビット演算と代入を一括でやる演算子.
- = 代入
- += 右オペランドを足して代入
- -= 引いて代入
- += 積を取って代入
- %= 剰余結果を代入
- /= 除算結果を代入
- &= ビット論理積を代入
- |= ビット論理和を代入
- ^= ビット排他論理和を代入
- <<= 右オペランドの値だけ左シフトした結果を代入
- >>= 右オペランドの値だけ右シフトした結果を代入
任意の演算子をXとしたとき, 複合的な代入演算子はi X= j <=> i = i X j
と互換を持つ.
毎度毎度ビット系の演算子, 把握するはいいけど使ったことはないから挙動を知らないんだよなぁ...
勉強をしなければ
3.2.1 値型/参照型による代入
参照渡しの話っぽい.
参照型の変数を別の変数に代入したとき, その変数に代入されるのはリテラルではなくてアドレスのため, 後者の変数に値を再代入するとアドレス先の値が変更されるため, 同じアドレス先を参照している前者の値も変更されるとかそういうやつ
using System.Text;
var x = 1;
var y = x;
x += 10;
Console.WriteLine(x); // 11
Console.WriteLine(y); // 1
var builder1 = new StringBuilder("asdf");
var builder2 = builder1;
builder1.Append("gh");
Console.WriteLine(builder1.ToString()); // asdfghg
Console.WriteLine(builder2.ToString()); // asdfgh
3.3 関係演算子
true/falseを返す演算子たち. 比較演算子のほうが聞き馴染みがある
- == 両辺の値が等しい時T
- != 両辺の値が等しくないときT
- > 左辺が右辺より大きければT
- < 右辺が左辺より大きければT
- >= 左辺が右辺以上であればT
- <= 右辺が左辺以上であればT
- A?B:C AがTならばB, FならばCを返す
- A??B null合体演算子. AがnullならばB, nullでなければA自身を返す
3.3.1 同一性と同値性
- 同一性: オブジェクト参照同士が同じオブジェクトを参照していること
- 同値性: オブジェクトが同じ値を持っていること
要するに, ==
の演算子を使うとき, 参照型をただ単にA==B
のように比較させてしまうと, 参照型はアドレスを返すため, 同じ値をA, B両辺持っていたとしても異なるアドレスを持っているのならばfalseを返してしまう.
その場足はEqualsメソッドを使おう
var builder1 = new StringBuilder("asdf");
var builder2 = new StringBuilder("asdf");
builder1 == builder2 // false
builder1.Equals(builder2); // true
文字列比較は"=="でも可能
ただし文字列型のstringだけは参照型にもかかわらず, デフォルトでEqualsメソッドを呼び出すようにしてあるらしい.
Equalsメソッドは型安全を保証してないので逆に非推奨なのだとか.
==が型安全な言語ってなんか新鮮だ...
string data="123";
int data2 = 123;
data.Equals(data2) // false
data == data2 // error
3.3.2 浮動小数点数の比較
本質的に十進法の表記に向いていない浮動小数点は比較の際に丸めをするなり, decimalを使用しようねというもの.
また, 丸め単位を指定してやる古典的な方法もある
const double = EPSILON = 10e-5;
double x = 0.2 * 3;
double y = 0.6;
Math.Abs(x-y) < EPSILON // true
こうすることで, 誤差EPSILONまでの範囲でxとyの同値性が担保できる.
ただ個人的にはMath.Roundでいいのでは...とも思えてしまう.
切り捨てる部分の問題なのだろうな
3.3.3 配列の比較
配列の比較は==は参照型のため使えないけれどequalsメソッドも同様に比較する先がさらなる参照なので使えない.
要素数が1の時も, 空配列の時でもダメだった.
EnumクラスのSequenceEqualメソッドを使えばいいらしい.
3.3.4 条件演算子?:
いわゆるの三項演算子. score >= 70 ? "pass!" : "fail"
みたいにして使うと, それぞれの値がかえってくる.
コードゴルフでよくifの代わりに使われるイメージ. 確認してみたら処理はスキップみたい.
以下のような挙動をする.
A?:B:Cと表記したとき
- B, Cは何かしらの値を返さない場合エラー. (ifの代わりにはならなかった
- B, Cは同型, もしくは暗黙的な型変換が可能なこと
- テンプレート付き文字列$""のなかで使う場合は, ()でくくる事
意外と面倒なのだな...
3.3.5 null合体演算子
a??b
で, aがnullではなければa, nullならbを返す.
rubyとかでいう, ||=
を思い出す
長くなるので次回に回します. 論理演算子からです.