のむログ

技術メモ / 車 / 音楽 / 雑記 / etc...

こちらは旧ブログになります。

新ブログはこちらに移行しました🙇

C言語 - Part.2:演算と変数

Part.2では様々な演算方法と変数を使ったプログラムを実装していきます。

Part.1はこちら

www.nomunomu0504.work

演算とは

コンピューターの5大機能のひとつ。
四則演算、数値の大小を比較する比較演算、論理演算などの計算処理のこと。

出典:ASCII.jpデジタル用語辞典 - 演算

つまり『計算を行うこと = 演算』という考えで間違っていません。プログラミングを行う上でも『どのような演算を行うか』ということを明示してあげる必要があります。どのような演算を行えばよいかを表す記号を『演算子』と呼び、いくつかの種類に分けられます。

演算子

C言語の主な演算子には以下のような演算子があります。

 
表:CとC++の演算子の表(一部抜粋)
算術演算子
名称 構文
単項プラス +a
加算 a + b
前置インクリメント ++a
後置インクリメント a++
加算代入 a += b
単項マイナス(負符号) -a
減算 a - b
前置デクリメント --a
後置デクリメント a--
減算代入 a -= b
乗算 a * b
乗算代入 a *= b
除算 a / b
除算代入 a /= b
剰余 a % b
剰余代入 a %= b
比較演算子
小なり a < b
小なりイコール a <= b
大なり a > b
大なりイコール a >= b
非等価 a != b
等価 a == b
論理演算子
論理否定 !a
論理積 a && b
論理和 a || b
ビット演算子
左シフト a << b
左シフト代入 a <<= b
右シフト a >> b
右シフト代入 a >>= b
ビット否定 ~a
ビット積 a & b
ビット積代入 a &= b
ビット和 a | b
ビット和代入 a |= b
ビット排他的論理和 a ^ b
ビット排他的論理和代入 a ^= b
型変換演算子
型変換(キャスト) (type)a
その他の演算子
単純代入 a = b

このように、よく使う演算子でもこれだけの量があります。

これ使うの??っていうようなものまで含めると、もう少し量がありますが、とりあえずは上の演算子の意味と構文をなんとなく覚えてるだけでGOODです👍

以下に簡単なプログラム例を載せておきます。

#include <stdio.h>

int main (void)
{
  // 演算
  printf("%d + %d = %d\n", 1, 2, 1 + 2);
  printf("%d - %d = %d\n", 2, 1, 2 - 1);
  printf("%d * %d = %d\n", 2, 5, 2 * 5);
  printf("%d / %d = %d\n", 10, 2, 10 / 2);  /* 余りのない割り算 */
  printf("%d / %d = %d ... %d\n", 10, 3, 10 / 3, 10 % 3);   /* 余りのある割り算 */
  return 0;
}
1 + 2 = 3
2 - 1 = 1
2 * 5 = 10
10 / 2 = 5
10 / 3 = 3 ... 1

となります。

演算の優先順位

演算子には四則演算と同じように、優先順位があります。つまり、複数の演算子を用いた場合、計算される順序を分かっていないと思った通りに動作しないということです。

以下にC/C++での演算子の優先順位表を示します。優先順位が高い(先に計算される)演算子から記載されています。

演算子 名称 結合性
:: スコープ解決 (C++のみ) 左から右
++ -- 後置インクリメント・デクリメント
() 関数呼出し
[] 配列添え字
. 直接メンバアクセス
-> 間接メンバアクセス
typeid() 実行時型情報 (C++のみ)
const_cast 型変換 (C++のみ)
dynamic_cast 型変換 (C++のみ)
reinterpret_cast 型変換 (C++のみ)
static_cast 型変換 (C++のみ)
++ -- 前置インクリメント・デクリメント 右から左
+ - 単項プラスとマイナス
! ~ 論理否定とビット否定
(type) 型変換
* 間接演算子 (デリファレンス)
& アドレス
sizeof 記憶量
new new[] 動的記憶域確保 (C++のみ)
delete delete[] 動的記憶域解放 (C++のみ)
.* ->* メンバへのポインタ (C++のみ) 左から右
* / % 乗算・除算・剰余算
+ - 加算・減算
<< >> 左シフト・右シフト
< <= (関係演算子)小なり・小なりイコール
> >= 大なり・大なりイコール
== != 等価・非等価
& ビット積
^ ビット排他的論理和
| ビット和
&& 論理積
|| 論理和
c ? t : f 条件演算子 右から左
(throwは結合しない)
= 単純代入
+= -= 加算代入・減算代入
*= /= %= 乗算代入・除算代入・剰余代入
<<= >>= 左シフト代入・右シフト代入
&= ^= |= ビット積代入・ビット排他的論理和代入・ビット和代入
throw 送出代入 (例外送出: C++のみ)
, コンマ演算子 左から右

演算子の結合性

みなさん、表に書いてある『結合性』ってなんだと思いますか?例えば以下のような計算式があったとします

1 + 2 + 3

この計算をするとき、このように考えませんか?

((1 + 2) + 3)

この場合、同じ演算子が1つの計算式の中にあった場合、左側の演算子から計算して行きましたよね。これを『左結合性』と呼びます。

さらに、このあと説明する変数を使ってもう1つ。
変数を定義する際に、複数の変数に同じ値を代入するときは、1行にまとめて書くことができるのですが、

※ ここでの=は等しいという事を示しているのではなく、右を左に入れるという事を示しています。
※ このことについては、後述する『変数』の部分で説明します。

#include <stdio.h>

int main (void)
{
  int a, b, c = 5;
  
  a = b = c = 3;
  printf("%d, %d, %d\n", a, b, c);
  return 0;
}

この場合、a, b, cを表示させるとどうなるか...。こうなります。

3, 3, 3

これは a = b = c = 3; という式が (a = (b = (c = 3))) という風に解釈されたためです。

理由は『代入演算子』が右側から演算をおこなっていく『右結合性』の演算子だからです。

つまり『結合性』とは、同じ優先順位の演算子があった場合、どちら側から計算していくか。というのを表していることになります。

基本的な四則演算は『左結合性演算子』、代入演算子は『右結合演算子』であるということが分かります。

変数

さて、ここに来るまでにC言語で様々な演算を行うことができることは理解してもらうことができたと思います。しかし、いまの状態では 1 + 22* 5 などのプログラム上に書いた値同士の計算しかできません。実際には、値はいつ変化するか分かりませんし、変わったら毎回式を書き換えないといけないのか!!という話になります。

実は、C言語には値を常に入れ替えできる箱のような数が存在します。それを『変数』と呼びます。

変数の型

変数には『』と呼ばれる、何を保持するか。という分類分け的なものがあります。以下に基本的な型を示します。

※ ビットやバイトの解説についてはしていませんので、あらかじめご了承ください。

説明
char 1バイトの符号付整数(-128~127)の値を記憶できる.
1バイト文字(英数字など)を1字記憶できる
unsigned char 1バイトの符号なし整数(0~255)の値を記憶できる
int 2または4バイトの符号付整数の値を記憶できる
(2バイトなら-2の15乗~2の15乗-1、4バイトなら-2の31乗~2の31乗-1)
short 2バイトの符号付整数(-2の15乗~2の15乗-1)の値を記憶できる
long 4バイトの符号付整数(-2の31乗~2の31乗-1)の値を記憶できる
unsigned 2バイトまた4バイトの符号なし整数の値を記憶できる
(2バイトなら0~2の16乗-1、4バイトなら0~2の32乗-1)
unsigned long 4バイトの符号なし整数(0~2の32乗-1)の値を記憶できる
unsigned short 2バイトの符号なし整数(0~2の16乗-1)の値を記憶できる
float 4バイトの単精度浮動小数点実数(有効桁数7桁)
double 8バイトの倍精度浮動小数点実数(有効桁数16桁)

これらを用いて変数を定義していきます。変数の定義方法については以下のような方法があります

int x;            // 変数を一つだけ宣言

double s, t, u;   // 複数の変数を宣言する際は , で区切る

double hensu = 0.1;   // 宣言と初期化を同時に行う際は = で値に続ける

以下のような定義はエラーになります。(悪い例です)

int val;            // 整数型の変数 val を宣言
double val;         // val は既に整数型の変数と宣言されているので、コンパイルエラー!
int val;            // 整数型の変数 val を宣言
int val;            // 同じ宣言を二度書いてもコンパイルエラー!

はい。ここで先ほどの伏線を回収しておきましょう。 = が等しいを表すものではないということを。

数学の世界では、左と右が同じという事を表すために = を使っています。

2 * 5 = 10

また、等しくない時には を使っていましたね。

2 * 4 ≠ 10

プログラム上でこれを書くとどうなるのでしょうか。こうなります。

2 * 5 == 10
2 * 4 != 10

先ほどの演算子の中にあったのですが、気づきましたか?

==!=比較演算子 と呼ばれ、左右を比較する時に用いられます。数学でいう = と同じ意味です。

また、 =代入演算子 と呼ばれ、右の値を左に代入するという意味合いがあります。数学でいうと に近しいかも。

はい。伏線回収終了ですね。話を戻しましょう。

変数の命名規則

変数を定義するのはいいんですが、変数名には命名規則があり、それに沿った名前しかつけることができません。

  • 言語特有の 予約語 を使って変数名にすることはできない
  • 変数名には 半角の英文字, 数字, アンダースコア(_)の組み合わせのみ
  • 変数名を数字から始めることはできない
  • 同じ文字列でも大文字と小文字は別変数として見なされる(ABC != abc)

C言語の予約語はこちら

auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof volatile
do if static while

これら、予約語を 含める ことはいいですが、そのまま変数名にすることはできません。

さて、今日の演習問題を置いておきますので、解いてみましょう。

Q2: 演算と変数

問題を解くにあたり、以下のプログラムをコピーし、書き換えたり追加したりして使用してください。
なぜなら、scanf関数 を説明していないことに気が付いたからです。ごめんなさい。

scanf関数とは、キーボードからの入力を変数に代入することができる関数です。

#include <stdio.h>
 
int main(void)
{
  int a;                              //  変数a(キーボードからの値を代入)
  printf("a=");                       //  キーボードからの入力を促す
  scanf("%d",&a);                     //  キーボードから一文字入力
  printf("aの値は%dです\n",a);          //  aの値を表示
  return 0;
}

これを実行すると

a=

で止まると思いますので、数値を入力してEnterキーを押すと、再度動き始めると思います。

a=3 (Enterキーを押す)
aの値は3です

Q2-1

2つの整数値を入力させて、その和, 差, 積, 商, 余りを計算させて、式とともに表示させてください。 以下のような実行結果になれば正解です(a = 5, b = 3 とした場合)

a = 5
b = 3
a + b = 8
a - b = 2
a * b = 15
a / b = 1
a % b = 2

Q2-2

地球において、物体を自由落下させたときの速度を計算するとする。
物体の質量をm, 物体の加速度をa, 物体に働く力をfとおくと、運動方程式より以下の式が成り立つ。

{ \displaystyle
ma = f \\
}

物体には重力による引力, 落下運動による空気抵抗が働くものとする。重力加速度をg = 9.80665 m/s2 とし、上向き方向を正方向とすれば、引力は下向き方向にかかるため-mg, 空気抵抗は物体の運動方向の逆向き方向に働き、物体の落下速度 v に空気抵抗係数 k に比例して大きくなる。これらより以下の式が成り立つ。

{ \displaystyle
ma = -mg - kv \\
}

加速度a は速度vを時間微分したものである。したがって以下のように書き換えることができる

{ \displaystyle
m\dot{v} = -mg - kv \\
}

この微分方程式を変数分離法を用いて解く。

{ \displaystyle
\begin{eqnarray}
\frac{m}{-mg-kv}\dot{v}&=&1 \\
\require{cancel} \int{\frac{m}{-mg-kv}\dot{v}}dt&=&\int dt \\
m\int{\frac{1}{-mg-kv}\frac{dv}{\cancel{dt}}\cancel{dt}}&=&\int dt \\
-\frac{m}{k} \ln|-mg-kv|&=&t+C_1 \\
 \require{cancel} -\frac{m}{k} \ln|-mg-kv|&=&t+C_1 \\
\ln|-mg-kv|&=&-\frac{kt}{m}-\frac{kC_1}{m} \\ 
-mg-kv&=&\pm\exp{\left(-\frac{kt}{m}-\frac{kC_1}{m}\right)} \\
\require{cancel} -mg-kv&=&\pm\exp{\left(-\frac{kt}{m}\right)} \exp{\left(-\frac{kC_1}{m}\right)} \\
-kv&=&\pm\exp{\left(-\frac{kt}{m}\right)} \exp{\left(-\frac{kC_1}{m}\right)}+mg \\
v&=&\frac{\mp1}{k}\exp{\left(-\frac{kt}{m}\right)} \exp{\left(-\frac{kC_1}{m}\right)}-\frac{mg}{k} \\
v&=&C_1\exp{\left(-\frac{kt}{m}\right)}-\frac{mg}{k} \\
\end{eqnarray} \\
}

ここで、引力mg と 空気抵抗kv が等しい場合(mg = kv), C1 = 0 であり、一般解に含まれる。

そこで、mg = kv と仮定し、物質の質量m , 空気抵抗k を入力し、それに応じた速度を算出するプログラムを作成してください。

{ \displaystyle
v=\frac{mg}{k} \\
}

回答はページ一番下部にあります。

まとめ

今回は、演算子を用いた演算と、変数について解説しました。次回は何するか決めてませんが、キャスト演算子, scanf関数 について触れておこうとは思います。意外と使う場面多いような気がするので。







Q2: 回答

A2-1

#include <stdio.h>

int main (void)
{
  int a, b;
  printf("a = ");
  scanf("%d", &a);
  printf("b = ");
  scanf("%d", &b);

  printf("a + b = %d\n", a + b);
  printf("a - b = %d\n", a - b);
  printf("a * b = %d\n", a * b);
  printf("a / b = %d\n", a / b);
  printf("a % b = %d\n", a % b);
  return 0;
}

A2-2

#include <stdio.h>

int main (void)
{
  double g = 9.80665;
  int m, k;
  printf("m = ");
  scanf("%d", &m);
  printf("k = ");
  scanf("%d", &k);

  printf("v = %lf\n", (m * g) / k);
  return 0;
}

f:id:nomunomu0504:20190411151221p:plain:w0