C#

最終更新日: 2022.11.11 (公開: 2022.05.17)

C言語の演算子とは?重要な演算子の使い方とサンプルプログラムを紹介

C言語の演算子とは?重要な演算子の使い方とサンプルプログラムを紹介

C言語の「演算子」とは、プログラムで各種演算を行うための記号です。あらゆる場面で使用する演算子は、C言語を用いてプログラミングするうえで必要不可欠な知識。しかし、C言語では同じ記号に複数の役割が割り当てられているので、学習時は注意が必要です。

演算子への理解を深めておけば、C言語でのプログラミングがスムーズに進みやすくなり、よりバグの少ないプログラムが書けるでしょう。本記事ではC言語の演算子と使い方について、初心者から上級者まで役立つ知識をサンプルプログラム付きで解説します。

C言語の演算子一覧

C言語の演算子一覧

まずはC言語で使用する演算子について、その全体像を把握しておきましょう。C言語のプログラミングでは、下記35種類の演算子を使用します。

演算子の種類 演算子の記号
算術演算子 +

*

/

%

比較演算子 <

>

<=

>=

==

!=

代入演算子 =

+=

-=

*=

/=

%=

インクリメント演算子 ++
デクリメント演算子
論理演算子 &&

||

!

三項演算子 ?

:

ビット演算子 &

|

^

~

<<

>>

sizeof演算子 sizeof(型名)
キャスト演算子 (型名)
ポインタ演算子 &

*

[]

このなかで「*」と「&」の演算子にはとくに注意が必要です。これらの演算子には複数の機能があり、C言語では極めて重要な役割を果たします。

算術演算子

算術演算子

「算術演算子」は四則演算、すなわち加算演算・減算演算・乗算演算・除算演算に加えて、剰余演算を行うための演算子です。それぞれの演算子の機能は下記のとおり。

記号 機能
+ 加算演算(足し算を行う)
減算演算(引き算を行う)
* 乗算演算(掛け算を行う)
/ 除算演算(割り算を行う)
% 剰余演算(割り算の余りを求める)

上記5種類の算術演算子を活用した、サンプルプログラムと実行結果を紹介します。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  int num1 = 7;

  int num2 = 2;

  printf(“加算:%d\n”, num1 + num2);

  printf(“減算:%d\n”, num1 – num2);

  printf(“乗算:%d\n”, num1 * num2);

  printf(“除算:%d\n”, num1 / num2);

  printf(“剰余:%d\n”, num1 % num2);

  return 0;

}

//実行結果
加算:9

減算:5

乗算:14

除算:3

剰余:1

除算部分は正式には「3.5」になるはずですが、num1とnum2、printf関数の書式設定がint型なので、小数点以下が切り捨てられて「2」となります。ちなみに乗除演算は、下記のように使用する数値の範囲を制限したいときに便利です。

//サンプルプログラム
#include <stdio.h>

#include <stdlib.h>

int main(void) {

  int random;

  int select;

  for(int i = 0; i < 5; i++) {

    // 0~32767までのランダムな数値を取得する

    random = rand();

    // 取得した数値の値を0~9の範囲に制限する

    select = random % 10;

    printf(“%d回目のランダム値:%d\n”, i + 1, select);

  }

  return 0;

}

//実行結果
1回目のランダム値:1

2回目のランダム値:7

3回目のランダム値:4

4回目のランダム値:0

5回目のランダム値:9

ある数値を10で割った余りは、必ず0~9の数値になります。こうした剰余演算の性質を利用したのが上記のプログラム。rand関数でランダムな数値を取得して、その数値を0~9の範囲に丸めています。ランダムな数値が必要な場面で、重宝するテクニックです。

比較演算子

比較演算子

「比較演算子」は2つの値や式を比較し、その結果を真偽値で返す演算子です。真偽値は「true」と「false」の2種類で、trueは比較結果が出しければtrue、誤りであればfalseとなります。それぞれの演算子の記法と意味は下記のとおりです。

記号 記法 意味
< a < b aはbより小さい
> a > b aはbより大きい
<= a <= b aはb以下
>= a >= b aはb以上
== a == b aとbは等しい
!= a != b aとbは等しくない

上記6種類の比較演算子を活用した、サンプルプログラムと実行結果を紹介します。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  int a = 10; // 自由に数値を変えてみてください

  int b = 20; // 自由に数値を変えてみてください

  if(a <  b) printf(“%dは%dより小さい\n”, a, b);

  if(a >  b) printf(“%dは%dより大きい\n”, a, b);

  if(a <= b) printf(“%dは%d以下\n”, a, b);

  if(a >= b) printf(“%dは%d以上\n”, a, b);

  if(a == b) printf(“%dと%dは等しい\n”, a, b);

  if(a != b) printf(“%dと%dは等しくない\n”, a, b);

  return 0;

}

//実行結果
10は20より小さい

10は20以下

10と20は等しくない

上記のプログラムは2つの数値を6つの方法で比較し、真偽値が「真」になったものを表示します。変数aとbの数値を自由に変えてみて、プログラムの実行結果がどのように変わるか確認してみましょう。

代入演算子

代入演算子

「代入演算子」は、変数に値を入力するための演算子です。単に代入するだけではなく、演算と代入を同時に行うことも可能。それぞれの演算子の記法と機能は下記のとおりです。

記号 機能 記述例 書き換え
= そのまま代入する a = b
+= 加算して代入する a += b a = a + b
-= 減算して代入する a -= b a = a – b
*= 乗算して代入する a *= b a = a * b
/= 除算して代入する a /= b a = a / b
%= 剰余を求めて代入 a %= b a = a % b

「=」以外の代入演算子は、基本的にはプログラムを簡潔に記載するために使います。上記6種類の代入演算子を活用した、サンプルプログラムと実行結果を見ていきましょう。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  int num1 = 7;

  int num2 = 2;

  num1 += num2; // “num1 = num1 + num2″と同じ

  printf(“加算代入:%d\n”, num1);

  num1 -= num2; // “num1 = num1 – num2″と同じ

  printf(“減算代入:%d\n”, num1);

  num1 *= num2; // “num1 = num1 * num2″と同じ

  printf(“乗算代入:%d\n”, num1);

  num1 /= num2; // “num1 = num1 / num2″と同じ

  printf(“除算代入:%d\n”, num1);

  num1 %= num2; // “num1 = num1 % num2″と同じ

  printf(“剰余代入:%d\n”, num1);

  return 0;

}

//実行結果
加算代入:9

減算代入:7

乗算代入:14

除算代入:7

剰余代入:1

上記のプログラムは基本的には「算術演算子」のものと同じですが、num1に直接代入しているため演算を行うたびにnum1の数値が変化するため結果が異なります。num1とnum2の数値を変更し、実行結果の違いを楽しんでみてください。

インクリメント演算子とデクリメント演算子

インクリメント演算子とデクリメント演算子

「インクリメント演算子」は値を1増やし、「デクリメント演算子」は値を1減らすための演算子です。それぞれの演算子の記法と機能は下記のとおり。

記号 名称 機能 記述例
++ インクリメント 1増やす a++

++a

デクリメント 1減らす a–

–a

インクリメント演算子とデクリメント演算子は、1増やす・1減らす処理を簡潔に記載するために使います。インクリメント演算子とデクリメント演算子を活用した、サンプルプログラムと実行結果を見ていきましょう。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  int num1 = 7;

  int num2 = 2;

  num1++; // “num1 = num1 + 1” や “num1 += 1″と同じ

  printf(“1回目のインクリメント:%d\n”, num1);

  ++num1; // “num1 = num1 + 1” や “num1 += 1″と同じ

  printf(“2回目のインクリメント:%d\n”, num1);

  

  num2–; // “num2 = num2 – 1” や “num2 -= 1″と同じ

  printf(“1回目のデクリメント:%d\n”, num2);

  –num2; // “num2 = num2 – 1” や “num2 -= 1″と同じ

  printf(“2回目のデクリメント:%d\n”, num2);

  return 0;

}

//実行結果
1回目のインクリメント:8

2回目のインクリメント:9

1回目のデクリメント:1

2回目のデクリメント:0

上記のプログラムの場合は、インクリメント演算子・デクリメント演算子を変数の前後どちらに書いても同じ結果が得られます。しかしプログラムの内容によっては、演算子の位置によって結果が大きく異なるので注意が必要です。

後置と前置の違いに要注意

「a++」のように演算子を後ろに置く書き方を「後置」、「++a」と前に置くものを「前置」と呼びますが、実は演算処理と評価の順番が異なります。下記のサンプルプログラムで検証してみましょう。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  int num1, num2;

  num1 = 0;

  num2 = num1++;

  printf(“後置:%d\n”, num2);

  num1 = 0;

  num2 = ++num1;

  printf(“前置:%d\n”, num2);

  return 0;

}

//実行結果
後置:0

前置:1

上記のプログラムは、「num1の数値を1増やしたものをnum2に代入する」ことを目的としています。しかし「num1++」と後置にすると、「num1をnum2に代入してから、numの数値を1増やす」ことになり、想定した結果が得られません。つまり先ほどのプログラムは、下記と同じ処理を行っているということです。

#include <stdio.h>

int main(void) {

  int num1, num2;

  num1 = 0;

  num2 = num1;     // 先にnum1をnum2に代入してから、

  num1 = num1 + 1; // num1の数値を1増やす

  printf(“後置:%d\n”, num2);

  num1 = 0;

  num1 = num1 + 1; // 先にnum1の数値を1増やしてから、

  num2 = num1;     // num1をnum2に代入する

  printf(“前置:%d\n”, num2);

  return 0;

}

インクリメント演算子とデクリメント演算子を使用する際は、上記の後置・前置の仕組みを正しく理解しておくことが重要です。とくにポインタ演算を行う場合は、前置と後置を誤ると思わぬバグにつながり、原因の検出が困難なことがあるので注意してください。

論理演算子

論理演算子

「論理演算子」は複数の条件式を評価し、その結果を「true」もしくは「false」の真偽値で返す演算子です。それぞれの演算子の記法と機能は下記のとおり。

記号 機能 記述例 意味
&& 論理積 a && b aとb双方が真なら

true

一方が偽なら

false

|| 論理和 a || b aかb少なくとも

一方が真ならtrue

双方が偽ならfalse

! 否定 !a aがtrueならfalse

falseならtrue

論理演算子は、条件に応じて処理を分岐させるときに使います。C言語のプログラミングにおいては、あらゆる場面で使うのでぜひ慣れておきたいところ。論理演算子を活用したサンプルプログラムを紹介します。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  int min = 0;

  int max = 100;

  int num = 50;

  if(num >= min && num <= max) {

    printf(“条件分岐1:数値は範囲内です\n”);

  }

  if(!(num >= min && num <= max)) {

    printf(“条件分岐2:数値は範囲外です\n”);

  }

  

  if(num < min || num > max) {

    printf(“条件分岐3:数値は範囲外です\n”);

  }

  if(!(num < min || num > max)) {

    printf(“条件分岐4:数値は範囲内です\n”);

  }

  return 0;

}

//実行結果
条件分岐1:数値は範囲内です

条件分岐4:数値は範囲内です

上記のプログラムは最小値「min」と最大値「max」を設定したうえで、任意の数値「num」が範囲内にあるかを判定するものです。「min以上かつmax以下」の場合は範囲内、「minより小さいもしくはmaxより大きい」場合は範囲外となります。条件式全体を「!」で囲うと正反対の条件となることがポイントです。

三項演算子

三項演算子

「三項演算子」は、条件式で処理を2つに分岐させるための演算子。三項演算子は2つのパラメーターを取る論理演算子とは異なり、下記のように3つのパラメーターを取ります。

条件式 ? 真の場合 : 偽の場合

三項演算子は通常の「if-else文」をコンパクトに記載するために活用されます。三項演算子を活用したサンプルプログラムを見ていきましょう。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  int num;

  int abs;

  // 数値を自由に変更してみてください

  num = -100;

  // 指定した数値の絶対値をif-else文で求める

  if(num >= 0) {

    abs = num;

  } else {

    abs = -num;

  }

  printf(“%dの絶対値は%dです\n”, num, abs);

  // 指定した数値の絶対値を三項演算子で求める

  abs = (num >= 0) ? num : -num;

  printf(“%dの絶対値は%dです\n”, num, abs);

  // 三項演算子を用いてさらに簡潔に記載する

  printf(“%dの絶対値は%dです\n”, num, (num >= 0) ? num : -num);

  return 0;

}

//実行結果
-100の絶対値は100です

-100の絶対値は100です

-100の絶対値は100です

上記のプログラムのように、通常は数行以上かかる条件分岐が、三項演算子を用いるとわずか1行で完結します。ただし三項演算子は見づらいことがあるため多用は禁物です。また演算子の優先順位による思わぬバグを防ぐために、条件式は「()」で囲むようにしましょう。

ビット演算子

ビット演算子

「ビット演算子」は、数値を「0」と「1」で表現する2進数を操作するための演算子です。ビット演算子は全4種類で、それぞれの機能と記述例は下記のとおり。なお、これ以降の項目では中級者向けの要素も増えていくため、参考程度にチェックするくらいで構いません。

記号 機能 記述例 意味
& AND

論理積

a & b aとb双方が1なら1

一方が0なら0

| OR

論理和

a | b aかb少なくとも

一方が1なら1

双方が0なら0

^ XOR

排他的論理和

a ^ b 双方が異なれば1

同じなら0

~ NOT

ビット反転

~a aが1なら0

0なら1

ビット演算は日常生活で馴染みがないものなので、少しずつ慣れていく必要があります。上記4種類のビット演算子を活用した、サンプルプログラムと実行結果を見ていきましょう。

//サンプルプログラム
#include <stdio.h>

// printf関数で2進数を表示するためのマクロ

#define BIT(c) (c & 1) | (c & 2) << 2 | (c & 4) << 4 | (c & 8) << 6 | \

(c & 16) << 8 | (c & 32) << 10 | (c & 64) << 12 | (c & 128) << 14

int main(void) {

  unsigned char answer;

  // 2つのビット値を設定(0と1の配置を自由に変えてみてください)

  unsigned char num1 = 0b01101001;

  unsigned char num2 = 0b10100101;

  // num1とnum2のAND(論理積)

  answer = num1 & num2;

  printf(“num1:%08o\n”,   BIT(num1));

  printf(“num2:%08o\n”,   BIT(num2));

  printf(” AND:%08o\n\n”, BIT(answer));

  // num1とnum2のOR(論理和)

  answer = num1 | num2;

  printf(“num1:%08o\n”,   BIT(num1));

  printf(“num2:%08o\n”,   BIT(num2));

  printf(”  OR:%08o\n\n”, BIT(answer));

  // num1とnum2のXOR(論理和)

  answer = num1 ^ num2;

  printf(“num1:%08o\n”,   BIT(num1));

  printf(“num2:%08o\n”,   BIT(num2));

  printf(” XOR:%08o\n\n”, BIT(answer));

  // num1のNOT(ビット反転)

  answer = ~num1;

  printf(“num1:%08o\n”,   BIT(num1));

  printf(” NOT:%08o\n\n”, BIT(answer));

  // num2のNOT(ビット反転)

  answer = ~num2;

  printf(“num2:%08o\n”,   BIT(num2));

  printf(” NOT:%08o\n\n”, BIT(answer));

  return 0;

}

//実行結果
num1:01101001

num2:10100101

 AND:00100001

num1:01101001

num2:10100101

  OR:11101101

num1:01101001

num2:10100101

 XOR:11001100

num1:01101001

 NOT:10010110

num2:10100101

 NOT:01011010

上記のプログラムは、「01101001」と「10100101」の2つのビット値のAND・OR・XORとNOTを求めています。特殊なマクロをprintf関数内で使用していますが、これはprintf関数で2進数表記を実現するためのものです。マクロについては気にせず、実行結果だけに注目してください。ひとつずつ丁寧に見ていけば、ビット演算の性質が見えてくるはずです。

シフト演算子

「シフト演算子」もビット演算子の一種ですが、こちらは指定した桁数だけビット配列をずらすための演算子です。それぞれの機能と記述例は下記のとおり。

記号 機能 記述例 意味
<< 左シフト a << ビット数 aを指定した桁数

左にずらす

>> 右シフト a >> ビット数 aを指定した桁数

右にずらす

シフト演算は変数の左側に「<<」もしくは「>>」と、ずらしたいビット数を指定して行います。ビット数は桁数と同じで、たとえば「10」を2桁左にずらした結果は「1000」です。シフト演算を活用したサンプルプログラムを紹介します。

//サンプルプログラム
#include <stdio.h>

// printf関数で2進数を表示するためのマクロ

#define BIT(c) (c & 1) | (c & 2) << 2 | (c & 4) << 4 | (c & 8) << 6 | \

(c & 16) << 8 | (c & 32) << 10 | (c & 64) << 12 | (c & 128) << 14

int main(void) {

  unsigned char answer;

  // 2つのビット値を設定(0と1の配置を自由に変えてみてください)

  unsigned char num1 = 0b01101001;

  unsigned char num2 = 0b10100101;

  // num1を4ビット左シフト

  answer = num1 << 4;

  printf(“num1:%08o\n”,   BIT(num1));

  printf(“<< 4:%08o\n\n”, BIT(answer));

  // num2を4ビット左シフト

  answer = num2 << 4;

  printf(“num2:%08o\n”,   BIT(num2));

  printf(“<< 4:%08o\n\n”, BIT(answer));

  // num1を4ビット右シフト

  answer = num1 >> 4;

  printf(“num1:%08o\n”,   BIT(num1));

  printf(“>> 4:%08o\n\n”, BIT(answer));

  // num2を4ビット右シフト

  answer = num2 >> 4;

  printf(“num2:%08o\n”,   BIT(num2));

  printf(“>> 4:%08o\n\n”, BIT(answer));

  return 0;

}

//実行結果
num1:01101001

<< 4:10010000

num2:10100101

<< 4:01010000

num1:01101001

>> 4:00000110

num2:10100101

>> 4:00001010

変数型から「unsigned」を除いて符号付きにすると、右シフトした場合の挙動が変わることに要注意。符号なし変数の場合は最上位ビットに値に関わらず、右シフトした桁数だけ「0」が付加されます。符号ありの場合は、最上位ビットと同じ値で埋められます。

ビット演算子とシフト演算子を組み合わせれば、画像ファイルやバイナリファイルの取り扱いが容易になります。初心者向けのテクニックではありませんが、各演算子を大まかに理解したら、ステップアップのために下記のサンプルプログラムに触れてみましょう。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  unsigned char upper = 0xF0;

  unsigned char lower = 0x8F;

  unsigned short word;

  

  // 2つの8ビット値から1つの16ビット値を構成する

  word = upper << 8 | lower;

  printf(“upper:0x  %02X\n”, upper);

  printf(“lower:0x  %02X\n”, lower);

  printf(” word:0x%04X\n\n”, word);

  // 1つの16ビット値を2つの8ビット値に分離する

  word  = 0x8F0F;

  lower = word & 0xFF;

  upper = word >> 8;

  printf(” word:0x%04X\n”,   word);

  printf(“lower:0x  %02X\n”, lower);

  printf(“upper:0x  %02X\n”, upper);

  return 0;

}

//実行結果
upper:0x  F0

lower:0x  8F

 word:0xF08F

 word:0x8F0F

lower:0x  0F

upper:0x  8F

上記のプログラムは、2つの8ビット値から1つの16ビット値を構成しています。これは「色」を扱う場面でもよく使うテクニックです。たとえば、「ARGB1555」形式の16ビットカラー値から赤の成分を取り出すときは、「red = (color >> 10) & 0x1F」とします。

sizeof演算子

sizeof演算子

「sizeof演算子」は、変数や型のサイズを返す演算子です。sizeof演算子を活用したサンプルプログラムを見ていきましょう。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  // 各データ型のサイズを確認する

  printf(“char型:     %zdバイト\n”, sizeof(char));

  printf(“short型:    %zdバイト\n”, sizeof(short));

  printf(“int型:      %zdバイト\n”, sizeof(int));

  printf(“long型:     %zdバイト\n”, sizeof(long));

  printf(“long long型:%zdバイト\n”, sizeof(long long));

  printf(“unsigned char型:     %zdバイト\n”, sizeof(unsigned char));

  printf(“unsigned short型:    %zdバイト\n”, sizeof(unsigned short));

  printf(“unsigned int型:      %zdバイト\n”, sizeof(unsigned int));

  printf(“unsigned long型:     %zdバイト\n”, sizeof(unsigned long));

  printf(“unsigned long long型:%zdバイト\n”, sizeof(unsigned long long));

  printf(“float型:      %zdバイト\n”, sizeof(float));

  printf(“double型:     %zdバイト\n”, sizeof(double));

  printf(“long double型:%zdバイト\n”, sizeof(long double));

  printf(“ポインタ型:%zdバイト\n”, sizeof(void*));

  return 0;

}

//実行結果
char型:     1バイト

short型:    2バイト

int型:      4バイト

long型:     4バイト

long long型:8バイト

unsigned char型:     1バイト

unsigned short型:    2バイト

unsigned int型:      4バイト

unsigned long型:     4バイト

unsigned long long型:8バイト

float型:      4バイト

double型:     8バイト

long double型:8バイト

ポインタ型:8バイト

上記のプログラムは、主要なデータ型のサイズを羅列したものです。ポインタのサイズは型名に関わらず、32ビットマシンは4バイト・64ビットマシンは8ビット固定。関数の引数に正確なサイズを引き渡す必要があるときは、下記のようにsizeof演算子が欠かせません。

//サンプルプログラム
#include <stdio.h>

#include <stdlib.h>

// 構造体の定義

struct Data {

  int   a;

  short b;

  long  c;

  char  d[3];

};

int main(void) {

  Data *p;

  // 構造体のデータサイズを確認する

  printf(“構造体サイズ:%zdバイト\n”, sizeof(Data));

  

  // malloc関数に正しい構造体サイズを指定する

  p = (Data*)malloc(sizeof(Data) * 10);

  if(p == NULL) {

    printf(“メモリ確保に失敗しました\n”);

    return 1;

  }

  // ポインタのサイズを確認する

  printf(“ポインタサイズ:%zdバイト\n”, sizeof(p));

  free(p);

  return 0;

}

//実行結果
構造体サイズ:16バイト

ポインタサイズ:8バイト

上記のプログラムでは、定義した構造体と確保したメモリのサイズを取得しています。動的にメモリを確保するためのmalloc関数では、引数としてデータ型のサイズが必要。ここにsizeof演算子を使わなければ、正確なサイズのメモリを確保できません。

Data構造体のメンバは、int型・short型・long型と要素数3のchar型配列で、通常どおりサイズを計算すると「4+2+4+(1*3)」で「13バイト」となります。しかし、構造体のサイズは「パディング」で調整されるため、実際のサイズは「16バイト」です。

キャスト演算子

キャスト演算子

「キャスト演算子」は、型変換を行うための演算子です。宣言時に指定したものとは異なるデータ型で一時的に処理を行いたい場合、キャスト演算子を活用すれば別の変数を用意する必要がありません。キャスト演算子を活用したサンプルプログラムを紹介します。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  int num1 = 7;

  int num2 = 2;

  float answer;

  // パターン1:単にfloat型変数に格納する場合

  answer = num1 / num2;

  printf(“パターン1:%f\n”, answer);

  // パターン2:演算後にfloat型に変換する場合

  answer = (float)(num1 / num2);

  printf(“パターン2:%f\n”, answer);

  // パターン3:変数を変換してから演算する場合

  answer = (float)num1 / (float)num2;

  printf(“パターン3:%f\n”, answer);

  return 0;

}

//実行結果
パターン1:3.000000

パターン2:3.000000

パターン3:3.500000

上記のプログラムのパターン1とパターン2は、キャストが正常に機能しない例です。演算時に使用されるデータ型がint型のままなので、「7/2」の演算結果が「3」に丸められてしまいます。正しい結果を得るためには、パターン3のように事前のキャストが必要です。

キャストをポインタ演算に応用すると、さらに興味深いことができます。たとえば下記のサンプルプログラムのように、あらかじめ確保しておいたメモリ領域をほかの変数に割り当てることが可能。初心者向きのテクニックではありませんが、参考程度に確認してみてください。

//サンプルプログラム
#include <stdio.h>

// 確保するメモリサイズ

#define SIZE 512

int main(void) {

  // まずchar型で大きなメモリを確保しておく

  char buffer[SIZE];

  // int型配列にメモリ領域を割り当てる

  int *pA = (int*)buffer;

  // int型は4バイトなので、使える要素数は4分の1個となる

  for(int i = 0; i < SIZE / sizeof(int); i++) {

    pA[i] = i;

    printf(“%d “, pA[i]);

  }

  printf(“\n\n”);

  // 今度はdouble型配列にメモリ領域を割り当てる

  double *pB = (double*)buffer;

  // double型は8バイトなので、使える要素数は8分の1個となる

  for(int i = 0; i < SIZE / sizeof(double); i++){

    pB[i] = i;

    printf(“%.1lf “, pB[i]);

  }

  // 最後まで何の問題もなく動作する

  return 0;

}

//実行結果
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0 30.0 31.0 32.0 33.0 34.0 35.0 36.0 37.0 38.0 39.0 40.0 41.0 42.0 43.0 44.0 45.0 46.0 47.0 48.0 49.0 50.0 51.0 52.0 53.0 54.0 55.0 56.0 57.0 58.0 59.0 60.0 61.0 62.0 63.0

上記のプログラムでは、あらかじめchar型配列で静的に確保した512バイトのメモリを、int型配列に割り当てています。さらに同じ領域を、今度はdouble型配列に割り当てました。配列アクセス時のインデックスさえ間違わなければ、このプログラムは問題なく動作します。

ポインタ演算はC言語でとくに難しい部分なので、初心者のうちに理解しようとする必要はありません。少し余裕が出てきた段階でメモリ操作に慣れていくと、C言語を早く上達させることが可能になります。なおポインタ演算の概要については、このあとの項目で改めて解説します。

ポインタ演算子

ポインタ演算子

最後に紹介するのは、C言語のなかで最も厄介な「ポインタ演算子」です。ポインタ演算子とは、メモリを操作するための演算子。ポインタ演算子は大きく分けて下記3種類のものがあります。本章では各演算子について、押さえておくべきポイントを見ていきましょう。

名称 記号 機能 記述例
アドレス演算子 & アドレスを取得する &a
間接参照演算子 * ポインタ変数の宣言

変数の実体取得

int *p

*p = 0

配列添字演算子 [] 配列内の指定した

インデックスに

アクセスする

p[0]

アドレス演算子

「アドレス演算子」は変数のアドレスを取得するための演算子です。実はC言語の初心者も、scanf関数を使用するときは下記のように、変数のアドレスを引数に渡しています。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  char c;

  // scanf関数で入力を受け付けるときは

  // 「変数のアドレス」を渡す必要がある

  printf(“1文字入力してください:”);

  scanf(“%c”, &c);

  printf(“入力した文字は%cです\n”, c);

  return 0;

}

//実行結果
1文字入力してください:a

入力した文字はaです

なおコンパイラの設定でscanf関数のセキュリティエラーが出る場合は、関数名を「sscanf_s」に置き換えると動作します。上記のプログラムで変数のアドレスを渡すのは、関数内で値を「書き換える」必要があるからです。下記のプログラムを確認してみましょう。

//サンプルプログラム
#include <stdio.h>

// 関数プロトタイプ宣言

void funcA(int);

void funcB(int*);

int main(void) {

  int i = 0;

  

  // funcAにiをそのまま渡す

  funcA(i);

  printf(“そのまま渡した場合:%d\n”, i);

  // funcBにiのアドレスを渡す

  funcB(&i);

  printf(“アドレスを渡した場合:%d\n”, i);

  return 0;

}

void funcA(int i) {

  i = 100;

}

void funcB(int *i) {

  *i = 100;

}

//実行結果
そのまま渡した場合:0

アドレスを渡した場合:100

funcAはint型、funcBはint型変数のアドレスを引数に取ります。関数内での変更が反映されるのはfuncBのみ。実はC言語の関数では、値をそのまま引き渡すとコピーされます。つまりmain関数とfuncA関数の「i」は別物で、funcAの影響は受けないということです。

一方でfuncBには「i」のアドレスが引き渡されています。詳細は後述しますが、funcB内ではアドレスを通して「i」の実体にアクセスして、「i」そのものを書き換えることが可能。だからこそmainでfuncBを呼び出すときは、「&」によりアドレスの取得が必要です。

間接参照演算子

「間接参照演算子」には、「ポインタ変数の宣言」と「変数の実体取得」の2つの大きな役割があります。初心者がつまずきやすいポイントは、ここです。具体的にどのようなシステムになっているのか、単純なサンプルプログラムで確認しておきましょう。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  int i;

  int *p; // “*p”でポインタ変数を宣言する

  i = 100;

  p = &i; // ポインタ変数pにiのアドレスを格納する

  *p = -1; // “*p”でポインタ変数の実体を取得する

  printf(“iの値:%d\n”, i);

  printf(“pの値:%d\n”, *p);

  

  return 0;

}

//実行結果
iの値:-1

pの値:-1

最初の「int *p」の部分では、int型のアドレスを格納するポインタ変数を宣言し、アドレス演算子を使って「p」に「i」のアドレス値を代入しています。それから間接参照演算子で「p」に格納したアドレスを経由し、「i」の実体にアクセスしていることが重要です。

ここがポインタ演算で最もわかりづらいポイントでしょう。そもそもアドレスとは、変数を格納しているメモリ上の番地のことです。現実の世界でもアドレスがわかれば目的地に向かえるように、プログラミングでもアドレスから変数の実体をたどることができます。

//サンプルプログラム
#include <stdio.h>

int main(void) {

  // 通常の変数に値を格納する

  int   i = 100;

  float f = 1.0f;

  char  c = ‘a’;

  // ポインタ変数にアドレスを格納する

  int   *pI = &i;

  float *pF = &f;

  char  *pC = &c;

  // 各変数のアドレスを表示する

  printf(“iのアドレス:%p\n”, pI);

  printf(“fのアドレス:%p\n”, pF);

  printf(“cのアドレス:%p\n”, pC);

  // アドレスをたどって実体を変更する

  *pI = 0;

  *pF = 0.0f;

  *pC = ‘A’;

  // 元の変数が変更されたことを確認する

  printf(“iの値:%d\n”, i);

  printf(“fの値:%f\n”, f);

  printf(“cの値:%c\n”, c);

  return 0;

}

//実行結果
iのアドレス:00000093BCBFF6F4

fのアドレス:00000093BCBFF714

cのアドレス:00000093BCBFF734

iの値:0

fの値:0.000000

cの値:A

上記のサンプルプログラムは、これまで紹介した内容をまとめたものです。ポインタ変数は間接参照演算子で宣言し、そこに格納できるのはアドレス。ポインタ変数から実体にアクセスするときは、同じく間接参照演算子を使えば実体の内容を書き換えることができます。

アドレス演算子の項目で紹介したfuncB関数は、こうしたポインタ変数の性質を利用したものです。main関数から変数のアドレスを受け取り、関数内で間接参照演算子を通じてアドレスをたどります。これにより、main関数の「i」にアクセスして変数を更新できました。

配列添字演算子

「配列添字演算子」は、配列内の任意の場所にアクセスするための演算子です。実は配列添字演算子は下記のように、間接参照演算子によるポインタ演算の「簡易版」だといえます。

//サンプルプログラム
#include <stdio.h>

#define SIZE 100

int main(void) {

  // int型の配列とポインタ変数を宣言する

  int data[SIZE];

  int *p;

  // 配列の全要素の値を設定する

  for(int i = 0; i < SIZE; i++) {

    data[i] = i;

  }

  // dataの先頭アドレスをpにコピーする

  p = data;

  // dataとpに格納された値を確認する

  printf(“「data」と「p」の値:%p\n”, p);

  // pを通じてdataの要素を書き換えて表示する

  for(int i = 0; i < SIZE; i++){

    *(p + i) *= 10;      // 間接参照演算子でアクセスする

    printf(“%d “, p[i]); // 配列添字演算子でアクセスする

  }

  return 0;

}

//実行結果
「data」と「p」の値:000000329AEFF660

0 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360 370 380 390 400 410 420 430 440 450 460 470 480 490 500 510 520 530 540 550 560 570 580 590 600 610 620 630 640 650 660 670 680 690 700 710 720 730 740 750 760 770 780 790 800 810 820 830 840 850 860 870 880 890 900 910 920 930 940 950 960 970 980 990

配列変数はポインタ変数と同じです。「p = data」の部分に注目すると、ポインタ変数「p」に配列変数「data」を代入しています。これは「data」に配列の先頭アドレスが格納されているからです。実際にprintf関数で中身を表示すると、アドレスが格納されています。

さらに最後のforループでは、間接参照演算子と配列添字演算子の双方を用いて、p経由でdataの内容を書き換えています。つまり配列変数はポインタ変数は、どちらの演算子を使っても実体にアクセス可能ということ。「p[i]」と「*(p + i)」の意味はまったく同じです。

ちなみに「*(p + i)」は「pのアドレスをiだけ進めて、その部分の実体にアクセスする」ことを意味します。今まで何気なく書いてきた「p[i]」という書き方は、ポインタ演算を簡素化したものだということ。ここまで理解しておけばC言語のポインタは難しくありません。

C言語の演算子はひとつずつ丁寧に覚えよう

C言語の演算子はひとつずつ丁寧に覚えよう

C言語の演算子は多種多様で、いずれもプログラミングに重要なものばかりです。しかし「ビット演算子」以降の項目は難易度が上がるため、まずは「算術演算子」から「三項演算子」までの内容を身につけましょう。初心者が苦戦しがちなポインタ演算も、今回紹介した点を踏まえてひとつずつ理解していけば、決して難攻不落の砦ではありません。

アクセスランキング 人気のある記事をピックアップ!

    コードカキタイがオススメする記事!

    1. 子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介

      2024.01.26

      子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介

      #プログラミングスクール

    2. 【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説

      2022.01.06

      【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説

      #プログラミングスクール

    3. 【未経験でも転職可】30代におすすめプログラミングスクール8選!

      2024.01.26

      【未経験でも転職可】30代におすすめプログラミングスクール8選!

      #プログラミングスクール

    4. 初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説

      2024.01.26

      初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説

      #JAVA

    5. 忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説

      2024.01.26

      忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説

      #プログラミングスクール

    1. 【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介

      2022.01.06

      【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介

      #プログラミングスクール

    2. 【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説

      2024.01.26

      【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説

      #プログラミングスクール

    3. 【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説

      2024.01.26

      【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説

      #プログラミングスクール

    4. 【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説

      2024.01.26

      【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説

      #プログラミングスクール

    5. 【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説

      2024.01.26

      【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説

      #プログラミングスクール