データの桁あふれ(オーバーフロー)、データ型、リテラルなどの基礎知識もまとめておきます。
プログラミング未経験、初心者でもそれなりに役に立つようなまとめを目指したいと思います。
誤解、誤植などがあればコメントなどで指摘していただけるとありがたく思います。
学習のススメ
-
苦しんで覚えるC言語
C言語を学べるサイト。タイトルは怖いが、わかりやすい。
アウトプット用の問題も用意されていて復習しやすい。 -
C/C++の環境構築
- 環境構築が難しい人はWebサイト上でコードが実行できるサイトがあるので紹介します。例えばWandbox、paiza.ioなど。
プログラミングをはじめて学ぶ場合の参考書として『12歳からはじめるゼロからのC言語』がおすすめです。柴田本、高橋本などの有名な本もあります。
はじめてのプログラミングでわからないことはChatGPTに聞くか、いったん飛ばしてプログラムを写すだけ写してみると良いかもしれません。今理解する必要はないので来週か来月にでも学習が進んだ時に戻ってくると良いかもしれません。
パソコンの基本的な操作、ダブルクリック、フォルダ作成などができるくらいの前提は必要です。
前書き
執筆時点ではWindows、mingw64(gcc version 12.2.0)の環境で検証しています。
#
で始まるプリプロセッサ命令は途中で改行できないが、他の文は基本的にどこで改行しても動作する。逆に改行せずに横につなげても動作する。
また、プリプロセッサ命令の前には余計なスペースをいれない。
#include <stdio.h>
int
main
(
)
{
int
a
;
if (1) {;} else if(10); else; for(;;){break;} printf("hello\n"); unsigned long long int num;
}
// helloを出力するプログラム
読みづらいし、汚いと思われそうなので普通はやらない。
C言語の拡張子は.c
、C++は.cpp
C++の場合は他にもあるようです。
printfを使うときは\n
を忘れない
scanfでただの変数に入力するとき&
を忘れない
C言語
・1972年登場
・手続き型コンパイラ言語
Python、Rubyなどと違い、コンパイルが必要な言語です。
C89、C99、C11といった仕様があり、昔のコンパイラでは// コメント
が使えなかったり、変数の宣言を冒頭で行う必要があったりするようです。
C++
・C言語にオブジェクト指向的な拡張を施したC++(シー・プラス・プラス)
・C言語のほぼ上位互換なのでC言語のソースコードをそのままコピーペーストしても基本的に動作します。
gccコマンド
gcc -o [作成ファイル名] [ソースファイル名]
gcc Sample.c -o Sample
Sample.cをコンパイルしてSample.exe(Sample.out)という名前の実行形式ファイルが作成される。
gcc --exec-charset=cp932 -o Sample1 Sample1.c
出力の文字コードにcp932、Shift_JISを指定する。
template
基本的なひな形。
#include
指定したファイルの内容を挿入
return
返り値を指定、return
の後に書いた処理は実行されない、main関数の処理を終了させたいときにも使う。
#include <stdio.h> // 標準入出力
int main(void)
{
// 処理
return 0;
}
#include <iostream> // cout, endl, cin
using namespace std;
int main()
{
// 処理
}
これ以降ではinclude
、main(){}
は書きませんが以下のように{ }
で囲まれたブロック内にあるものとして見てください。
return 0;
も基本的に省略します。
#include <stdio.h>
#include <iostream>
using namespace std;
// main関数
int main() {
int a,b = 3,c;
scanf("%d",&a);
cout << "Hello World" << endl;
printf("Hello printf\n");
cout << 5 << "\n";
for (int i = 0;i < 10;i++) {
if (b > 1) {
b++;
} else {
b--;
}
}
} // main関数終了
// Hello World
// Hello printf
// 5
\n
、endl
は改行を表します。
改行しないと
Hello WorldHello printf5
が画面に表示されます。
基礎知識
C言語では.c
で書かれたプログラムをコンパイラによってコンパイル(機械語に翻訳)してオブジェクトファイルというものを作成します。
次にリンカというプログラムによってオブジェクトファイルとライブラリファイルを合体して実行形式のファイル.exe
などを作成します。
Sample.exe
(executable file)
この状態になればプログラムを実行することができます。
1byteは8bit、1bitは0と1の2種類の数値を表すことができます。
コンピュータは0と1の2つの値で大きな数を表したり、計算したりします。そのために2進数を使います。
2進数は小さい方から0,1,10,11,100,101,110,111...
となります。2進数の10は10進数では2を表します。
インデント
C言語では見やすいように字下げすることが一般的です。
#include <stdio.h>
int main(void)
{
int len;
do {
printf("入力は正の整数で行ってください\n");
printf("正方形の辺の長さ:");
scanf("%d", &len);
} while (len <= 0);
printf("正方形の面積は%dです。\n", len*len);
return 0;
}
制御構造
プログラムは3つの基本的な処理構造の組み合わせのみで書くことができます。
- 順次(逐次)構造
- 選択(分岐)構造
if
switch
など - 反復(繰り返し)構造
for
while
など
順次構造はただ上から順番に実行します。
選択構造はif文のように条件によって処理の内容を分岐させます。
反復、繰り返し構造は条件を満たす間、もしくは無限に処理を繰り返します。
データ型
サイズは処理系によって異なるので例
unsigned、符号なしは負の数がない代わりにより大きな正の数を記憶できる。
基本的にbyteが大きいほど多くの値を扱える。
char、int、float、doubleくらいは覚える。
- 文字型
char
1byte
unsigned char
1byte - 整数型
int
4byte
unsigned int
4byte、符号なし
short
short int
2byte
unsigned short int
2byte
long
long int
4byteだったり8byteだったりする。
long long
long long int
8byte
unsigned long long
unsigned long long int
8byte - 浮動小数点型
float
4byte 単精度浮動小数点数
double
8byte 倍精度浮動小数点数
long double
16byte 拡張倍精度浮動小数点数
基本的にfloatよりdoubleの方が大きい、細かい値を扱える。
float型の方が精度は悪いが処理速度は速く、負荷が軽い。
float型は4byteのうち23bitを仮数部に使っているため2の23乗通りの数を正確に表すことができる。有効数字は6桁。
double型は8byteのうち52bit、有効数字として15桁ほどを正確に表すことができる。
コンピュータは整数でない実数の計算を正確に行うことができません。
float型、double型のような小数を含むデータ型の計算には限界があるため。条件式などに利用すると思わぬミスを引き起こします。
float 32ビット(符号1bit、指数部8bit(-126~+127)、仮数部23bit(10進の精度として約6桁))
double 64ビット(符号1bit、指数部11bit(-1022~+1023)、仮数部52bit(10進の精度として約15桁)
https://www.ibe.kagoshima-u.ac.jp/static/www1/edu/gengo0/p4.html
ちなみにAtCoderのコードテストではlong int型のデータサイズは8byteでした。
bool型
bool型は飛ばしても大丈夫ですが、if文のサンプルにtrue、falseと書いているため、trueは整数の1、falseは整数の0と読み替えてください。
C言語では#include <stdbool.h>
を使えばbool型を扱うことができる。bool型にはtrue、falseつまり1と0を記憶できる。
if (false || true)
のように書くにはtrue、falseを定義しない限り#include <stdbool.h>
が必要。
bool a = true;
printf("%d\n",a); // 1
C++ではそのままbool型を扱える。
型変換、キャスト変換
以下のコードのcoutはC言語では使えません。
それ以外は大丈夫です。
int i = 10;
double d;
d = i; // double型にint型を代入
cout << d << endl; // 10 coutは.000000が出力されない
printf("%f\n",d); // 10.000000
double型のような大きな型にint型を代入しても値は失われません。
逆にint型にdouble型、float型のような数値を代入すると小数点以下が切り捨てられることがあります。
int i;
double d = 6.999;
i = d;
cout << i << endl; // 6
printf("%d\n",i); // 6
この型変換をあえて行う場合はキャスト変換で示すことがある。
キャスト変換は以下のように(型)
を変数やリテラルの前に書く。
(int)3.8
これは3になる。
(double)10/3
3.333333
この場合型変換されるのは10だけなので10.0/3と同じ意味になります。
int i;
double d = 6.999;
i = (int)d; // dがint型に変換されてから代入
printf("%d\n",i); // 6
printf("%f\n",(double)10/3); // 3.333333
cout << (double)10/(int)3.9 << endl; // 3.33333
オーバーフロー
short int型、unsigned short int型で扱えるのは処理系によって変わる場合もあるがここでは2byteとする。
printf("%d",sizeof(short));
で確認できる。
unsignedがつく型は符号なし
符号付きの型のように先頭bitを正負(10)を表すのに使わないため1bit分、つまり2倍の数を表すことができる。
2byteは16bitなので2の16乗通りの数値しか表せない。
2^16=65536なのでunsigned short int型は0~65536までしか表せない。
符号付きのshort型は先頭bitは正負を表すので2^15=32768通り-32768~32767までの数値を表すことができる。
short int型は先頭の1bitと15bit(2進数15桁)で数を表します。
範囲外の値を入力するとオーバーフロー、桁あふれが起こり、想定した動作をしないことがあるので注意が必要です。
細かい話
以下のことは離散数学の初歩で説明できますが詳しくは書かないので読まなくても大丈夫です。
最大値より1大きい値を入れると最小値になって、最小値より1小さい値を入れると最大値になります。
short int型に32768を入れると-32768、32770を入れると-32766になります。-32769を入れると32767になります。
筆者の環境で検証した場合の結果になるので環境によって異なる結果になる可能性があります。
これは C/C++ の仕様としては保証されません。
計算の結果が結果の型で表現できる範囲を超えたとき、結果の型が符号無しの場合についてはラップアラウンドが起こることが保証されていますが結果の型が符号付きの場合は未定義です。
また、数値の表現が 2 の補数であることも言語仕様としての保証はありません。 (1 の補数形式、符号ビットと絶対値で表す形式であってもかまいません。 実質的には滅びているのですが用途を特化したプロセッサとしてなら今でも存在はするらしいとは聞いたことがあります。)
C++20 からは数値表現として 2 の補数を使うことは明文化されましたが範囲を超えた時の結果を未定義とする規定は維持されました。
@SaitoAtsushi(齊藤 敦志)
32768がなぜ-32768になるのかというと、
10進数32768は2進数で1000 0000 0000 0000であり先頭bitが1なので符号付きのshort int型では負の数として扱われます。
先頭bitが1のときは2進数のbitを全て反転させて1を足して-をつけることでその値になります。
1000 0000 0000 0000のbitを反転させると0111 1111 1111 1111になり1を足すと1000 0000 0000 0000になります。
これは10進数では32768であり、-をつけて-32768になります。
先頭bitが0の正の数は0から32767まで2進数をそのまま10進数にすればよいだけです。
-32768は1000 0000 0000 0000で表されます。
この値から2進数で1を引いた数は0111 1111 1111 1111になります。先頭bitが0なのでそのまま10進数になおすと32767になります。
ここまで書いてこの説明は4bitくらいで全通り書いた方がわかりやすいことに気づきました。
このサイトが参考になると思います。
以下は10進数と2進数の対応ではありません。
コンピュータの符号付き2byteの数値の扱いです。
先頭bitが0の場合は10進数と2進数が対応しますが、先頭bitが1の場合はbitを反転させて1を足し、符号をマイナスにする必要があります。
10進数 : 2進数
0 : 0000 0000 0000 0000
4 : 0000 0000 0000 0100
32767 : 0111 1111 1111 1111
-32768 : 1000 0000 0000 0000
-32766 : 1000 0000 0000 0010
-2 : 1111 1111 1111 1110
-1 : 1111 1111 1111 1111
-2を表す1111 1111 1111 1110を反転すると0001、これに1を足すと0010、10進数になおすと2。マイナスをつけると-2になります。
もっと詳しい仕組みは「2の補数」などで調べてください。
リテラル
ソースコードに直接書いた数値や文字列は変数と区別してLiteralと呼ぶ。
printf("%d\n",50); // 50が整数リテラル、int型のリテラル
変数と同じくリテラルにも型があります。
以下のプログラムは10(int)わる3(int)なので3.3333...の小数点以下が切り捨てられ3が出力されます。
printf("%i\n", 10/3); // int型のリテラルの計算
int、double、文字、文字列の書き方くらいは覚える。
5
15382
int型
5.0
382.0
3.1415
double型
10/3.0
は3.3333..
広い範囲の値を記録できる型にそろえて計算されるのでこの場合は全てdouble型に変換してから計算される。
5.0f
33.382
float型
0x05
0xD3
16進数int型
05
010
8進数int型
5u
unsigned int型
5l
long型
'A'
'5'
文字型、シングルクォートで囲めば文字として扱われる。文字列でないことに注意。
これは歴史的事情の奇妙なところですが C では文字リテラルは int 型です。 (文字型ではない。)
C++ では char 型です。
@SaitoAtsushi(齊藤 敦志)
基本は英数字、半角文字1文字を囲む。
日本語などの全角文字を囲んで出力するとエラー。
"A"
"5"
"hello"
文字列型
"こんにちは0304、hello"
文字列型
文字列型はダブルクオォートで囲んだもの。
文字列型には日本語、全角文字を入れても大丈夫です。
基本文法
変数、コメント
- コメントは
//
の後の同じ行に書くか、/* */
で囲む - コメントは実行されない。
- 変数を使うときは変数の型と名前を宣言する必要がある。
- 同じブロック内で名前が重複する変数は使えない。
- 数字で始めることはできない。
- 大文字、小文字を区別する
NAMEとNameとnameとnaMeは別の変数として扱う。 - auto、double、int、struct、break、else、ifなどの予約語は使えない。
int main() {
// ここがコメント、コンパイル時に無視される。
/* コメント
囲まれた部分がコメント
ここもコメント
*/
}
int a; // int型の変数aを使うための宣言
int a,b,c,d; // a b c dを同時に宣言
int a=0,b=2; // 宣言と同時に値を代入
int integer_num3,i_num_3; // _を使っても良い。
unsigned long long llnumber;
double num, realnum = 1.4142;
char c = 'A';
char str[10];
char str[3] = {'a','b','c'};
char str[] = "文字列をここに書く";
よく使う変数名など
tmp、hoge、一文字の変数、意味の分からない変数などを大量につくると処理が意味不明になるので要注意。
hi
とかniti
のような日本語よりday
を使うことが一般的です。
for文なんかではループ回数を数える変数としてiが使われます。内側のループではj,k...と続きます。
内側のループが多い場合はcnt1,cnt2,cnt3...とかの方がわかりやすいかもしれません。
i
j
k
cnt
count
counter
カウンター変数
数値を入れる変数でよく使います。
numはnumberの略で、noはラテン語でしたっけ。
num
no
num1
no2
tmp
temp
一時的な変数につける名前
hoge
名前に迷ったとき適当につける名前
var
Variable 変数
array
配列
day
week
month
year
len
length
長さ、height
高さ
sum
合計
diff
差
sub
Substitute 代役、Subtrace 引く
mul
乗算
div
Divide 除算、Division 分割
avr
Average 平均
cal
Calculate 計算する
img
画像、イメージ
arg
args
Argument 引数
func
fun
f
関数
str
String 文字列
prev
Previous 前の
init
初期化
変数の代入
int a = 5, b = 3; // 宣言と同時に代入、初期化
a = 5; // aに5を代入
a = b; // aにbを代入
a = a + b; // aにa+bの結果を代入
{ }
内で宣言された変数は}
までしか使えない。
この範囲を変数のスコープという。
printf
文字、数値、文字列などを表示。
printf('シングルクォート');
としないこと。
C言語では#include <stdio.h>
が必要。
printf("Hello\n"); // \nは改行
printf("%d\n",100); // 整数を表示、100
printf("%f\n",10.3332); // 実数を表示 10.3332
printf("%c\n",'A'); // A
printf("%s\n","Hello"); // Hello
あえて改行させない場合を除いて基本的に改行を書きます。
数学的な実数とここでいう実数は違います。
ここでは小数点以下を含む値とします。
int a = 2,b = 3;
printf("Hello World\n"); // Hello World
printf("変数a+bは%d\n", a+b); // 変数a+bは5
printf("%d %f\n", 10+2*3, 3.0*2.0); // 16 6.000000
printf("値は%dと%fで、文字は%cです。\n", 10, 3.14, 'A');
// 値は10と3.14で文字はAです。
printf("%s World , %s\n", "Hello", "ABC");
// Hello World , ABC
char str[] = "枝豆";
printf("%s\n", str);
// 枝豆
\n
は改行です。
printfの最後の改行はよく忘れるので注意してください。
int型の変数に小数点を含む実数を入れると小数点以下が切り捨てられるようにC言語では%dなどの書式文字もデータ型を意識して記述しないと予期しない動作をする。
出力フォーマット指定子
printf関数などで使用する変換仕様です。scanf関数などは少し異なります。書式文字とも言います。
%d、%f、%cは最低限覚える。
%d
%i
整数 int型(decimal:10進数、integer:整数)
%f
実数 float型、double型の両方
%c
文字 char型(character)
%s
文字列 "Hello"
(string)
%u
unsigned int型 符号なし
%o
8進数の整数(octal number)
%x
16進数の整数(hexadecimal)
%#x
%#o
プレフィックス表示
(ex 0x01 01)
%ld
long int型
%lu
unsigned long int型
%lld
long long int型
%llu
unsigned long long int型
%Lf
long double型
printf("%d\n", 'A'); // Aの文字コード 65 が表示される
printf("%x %o\n", 100, 100); // 16進数:64 8進数:144
printf("%c\n", 65); // A
printf("%c\n", 0x41); // A 16進数の整数リテラル
printf("%c\n", 0101); // A 8進数の整数リテラル
%.1f
小数点以下1桁に丸めて実数表示
ただし四捨五入が行われることに注意
%.3f
なら小数第3位まで表示。
%5.2f
小数第2位まで表示かつ小数点含めて全部で5桁分右に詰めて表示
%4d
4桁分のスペースで右に詰めて整数表示
%-3d
3桁分のスペースで左に詰めて整数表示
%05d
5桁分に満たない数値であれば左を0で埋めて整数表示
例 00002
00123
double a = 1.391;
printf("%5.2f\n",a);
printf("%d\n",12345);
long long ll;
unsigned long long ull;
long double l_double;
scanf("%lld %llu %Lf",&ll,&ull,&l_double);
printf("%lld %llu %Lf\n",ll,ull,l_double);
もっと詳しく知りたい方はこちらのサイトを見てください。
エスケープシーケンス
\n
改行文字
\t
水平タブ文字
\\
\(バックスラッシュ)
\?
?
\'
シングルクォート
\"
ダブルクォート
printf("hello\nworld\t\n");
他にもある。
puts
改行が自動で出力されるので\n
を書かなくて良い。
%d
などの書式文字が使えない。
C言語では#include <stdio.h>
が必要。
C++であれば#include <iostream>
で動作します。
puts(a)
これはダメ
int型の変数aを出力できない。
puts("こんにちは"); // こんにちは
char str[100]="Hello world!"; // 100も必要はない
puts(str); // Hello world!
cout(C++)
C言語では使いません。
#include <iostream>
が必要。
std::cout << "Hello World" << std::endl;
// C++でC言語のprintfを書いても動作します。
using namespaceを書いておけばstd::
いらず。
using namespace std;
int main() {
cout << "A" << endl << 10*2-1 << "\n";
}
// A
// 19
int a=1,b=3,num=5;
cout << a+b*num << endl;
// 16
scanf
キーボードから数値、文字を入力するための関数
C言語では#include <stdio.h>
が必要。
int a;
char ch;
float f;
double real;
char str[100]; // 配列、文字列を扱う場合に使う
scanf("%d",&a); // 整数の入力
scanf("%c",&ch); // 英数字の入力
scanf("%f",&f); // float型の入力
scanf("%lf",&real); //double型の入力 lfはエルエフ
scanf("%s",str); // 配列名を書く場合は&がいらない
複数の値を入力する場合は以下のように記述する。
scanf("%d %d %d",&a,&b,&c);
入力する際は100 20 3281
のようにスペースをあけるか改行して入力する。
310A50を数値、文字、数値として入力したい場合は
scanf("%d%c%d",&num1,&c,&num2);
%4d
最大幅4桁分の整数を取り込む。
123456と入力すれば1234が変数に記憶される。
%*c
一文字読み飛ばす
%*3c
三文字読み飛ばす
%*1d
数値を1つ読み飛ばす
%*4d
数値を4つ読み飛ばす
int main()
{
int a;
double d;
char c;
scanf("%*c%*c%c",&c);
printf("%c\n",c);
scanf("%*3d%d",&a);
printf("%d\n",a);
}
/* 入力
abc
3821
*/
/* 出力
c
1
*/
&を忘れないようにしましょう。
ただし、配列名は配列の最初の要素のアドレスを表しているため&は必要ありません。
long long ll;
unsigned long long ull;
long double l_double;
scanf("%lld %llu %Lf",&ll,&ull,&l_double);
printf("%lld %llu %Lf\n",ll,ull,l_double);
getchar
#include <stdio.h>
が必要。
1文字だけ入力したい場合に用いる。
以下のプログラムはaが入力されればaを出力します。
abcを入力すると最初のaのみ出力。
char ch;
ch = getchar();
printf("%c\n", ch);
cin(C++)
cout、cinはC言語では使いません。
#include <iostream>
が必要です。
以下のプログラムはキーボードからxとyの値を入力し、その合計を出力します。
キーボードからの入力の方法は半角スペースで区切るか、改行します。
3 4 // space
// 改行
3
4
#include <iostream>
int main()
{
int x,y;
std::cin >> x >> y;
std::cout << x + y << std::endl;
}
#include <iostream>
using namespace std;
int main()
{
int x,y;
cin >> x >> y;
cout << x + y << endl;
}
演算子
3 + 5では+が演算子(operator)、3と5がオペランド(operand)つまり演算の対象。
算術演算子
+
足し算
-
引き算
*
掛け算
/
割り算
%
余りを求める演算子
int a=2,b=3,c=5,d;
d = (a+b*c)%3; // dの値:2
printf("%d + %d = %d\n", d, a, d+a); // 2 + 2 = 4
a = b-3; // aの値:0
比較演算子
>
<
>=
<=
==
!=
出典:https://www.sejuku.net/blog/23541
if (a == 0) printf("aは0です。\n"); // aの値が0のとき実行
else if (a > 10) printf("aは10より大きい\n");
if (a != 3) printf("aは3ではない。\n");
C言語では計算の結果が真か偽、1か0、trueかfalseになる条件式というものがよく出てきます。
int x = 2;
// xの値は2とします。
if文、while文などの( )
の中に( x < 4 )
と書いてあれば条件式の計算結果は1になります。
if文、while文の( )
の中には0かそれ以外の値を書くだけでも動作します。
printf("条件式 %d\n",(x < 4)); // 条件式 1
if (0) puts("false"); // 条件式が偽であるため実行されない
if (1) puts("true"); // true
代入演算子
=
+=
-=
*=
/=
%=
=
は等しい
ではなく代入
を表します。
int a = 2;
int型の変数aを使うための宣言と同時に2を代入します。
+=
x += 1
と x = x + 1
は同じ意味です。
int x = 1;
x += 3; // x = x + 3;
// xの値は右辺の1+3が左辺xに代入されて4になる。
x -= 10; // x = x - 10;
x *= 5; // x = x * 5;
後は同じです。
<<=
>>=
左、右シフト代入などもある。
インクリメントとデクリメント
インクリメントは値を1増やす。
デクリメントは値を1減らす。
ループ文のカウンターでよく使う。i++
int a = 0,b = 0;
a++; // a = a + 1; aの値を1増やす
a--; // a = a - 1; aの値を1減らす
// aの値は+1されて-1されたので0
この演算子には後置、前置がある。
後置 a++
a--
前置 ++a
--a
上のようなインクリメント、デクリメントだけを行う場合はどちらでも同じ結果になる。
ただし以下のような書き方に注意したい。
int a = 1;
int b = a++; // aを代入した後にaをインクリメント
// この時点でaの値は2
int c = ++a; // aをインクリメントした後に代入
// この時点でaの値は3
つまりbの値は1、cの値は3。
sizeof演算子
データ型、式、変数のサイズを調べられる。
sizeof(変数)
sizeof(型名)
sizeof(式)
int a = 1;
int b = 100000000;
printf("a:%d b:%d\n",sizeof(a),sizeof(b));
printf("a+b:%d int:%dbyte\n",sizeof(a+b),sizeof(int));
// a:4 b:4
// a+b:4 int:4byte
sizeof の結果の型は size_t であり、 size_t は符号なし整数であることは規定されているものの具体的にどうのような型であるかは規定されていません。 (通例として一般的な処理系は unsigned int か unsigned long int の別名として定義することが多いようです。) つまり %d に対応する型とは限りません。 %zu を使うのが望ましいと思います。
@SaitoAtsushi(齊藤 敦志)
実行環境によってサイズが変わる場合があるが、この場合int型は4byteであることがわかる。
char str[1000000] = "こんちには世界のみなさん、おはようございます。";
printf("float:%d , double:%d byte\n",sizeof(float),sizeof(double));
printf("short:%d , long double:%d byte\n",sizeof(short),sizeof(long double));
printf("long:%d , long long:%d byte\n",sizeof(long),sizeof(long long));
printf("char:%d , str:%d byte\n",sizeof(char),sizeof(str));
float:4 , double:8 byte
short:2 , long double:16 byte
long:4 , long long:8 byte
char:1 , str:1000000 byte
シフト演算子
2進数で表した数値の桁を左または右に指定数ずらす。
左シフト、右シフトという。
左シフトは2,4,8,...倍になり、右シフトは1/2,1/4,1/8になる。右シフトの際2進数の小数点以下は切り捨てられる。
4 << 3
4を左に3桁ずらす。
4を2進数で表すと100、これを左に3つずらすと
100000になる。10進数になおすと2^5=32
int a;
a = 4 << 3;
printf("%d\n",a); // 32
100 >> 5
100を右に5桁ずらす
100(10進数)は2進数で1100100、これを右に5つずらすと11.001になりますが、整数リテラルの計算は小数点以下が切り捨てられるので11(2進数)、これを10進数になおすと2^1+2^0=3になります。
printf("%d\n",100 >> 5); // 3
a << 3
のように変数を直接シフトしてもよい。
100.0 >> 5
はエラー。
演算子の優先順位
printf("%d\n",5+3*4*(3+6/2)); // 77
優先順が同じレベルの演算子が使われたら左結合なら左から順番に評価し、右結合なら右から順番に評価する。
a+b-cの+
-
は左結合なので左から順番にaにbを足してcをひく。
a=b=c=3の=
は右結合なのでcに3を代入してからbにcを代入して、最後にaにbを代入します。
優先度:高
()
[]
後置++
後置--
結合規則:左
!
sizrof
前置++
前置--
&(アドレス)
*(間接参照)
結合規則:右
()
これはキャスト変換
結合規則:右
%
*
/
乗算、除算が加算、減算より優先されるのは数学と同じ。 結語規則:左
+
-
結合規則:左
<<
>>
結合規則:左
> >= < <= 左
== != 左
&& 左
|| 左
= 右
, 左
優先度:低
演算子は他にもあります。
if
C言語で条件式にtrue
、false
と書く場合は
#include <stdbool.h>
が必要。
C++では必要ない。
if(0)
if(1)
if(100)
のような書き方であればC言語でもstdbool.hは必要ない。
if(条件式)
は条件式の値が真のときに処理を行う。
else(条件式)
は条件式の値が偽のときに処理を行う。
例えばaの値が5のとき条件式a <= 10
は真。
aは10以下の値であるため。
以下のひな形では条件式1が真のとき処理1、処理2...のみが実行される。
条件式1が偽であり条件式2が真であるときは処理3、処理4...のみが実行される。すべての条件式が偽であるとき、else以降の処理が実行される。
if (条件式1) {
// 処理1 条件式1が真のとき実行される
// 処理2...
} else if (条件式2) {
// 処理3 条件式1かつ条件式2が真のときのみ実行
// 処理4...
} else if (条件式3) {
// 処理5... 条件式1,2が偽かつ条件式3が真のときのみ実行
} else if (a*10-1 > 50) { // 条件式の例
// 処理 条件式1,2,3が偽でないと(a*10-1 >50)は評価されない。
} else {
// 上の全ての条件式が偽のとき実行される
// 処理6
// 処理7...
}
if文の条件は上から判定されることに注意。
下の悪い例のプログラムを見てください。
a < 3を先に判定しないとa < 10が偽であるときにa < 3が真になることはないのでprintf("aは3未満");
が実行されることはない。
if (a < 10) {
printf("aは10未満");
} else if (a < 3) {
printf("aは3未満");
}
条件式の書き方として、
a < 10
b >= 50
aが10より小さいか、bが50以上か
a*2 == 4
a*2が4と等しいとき真
5 != a
aが5でないとき真
ch == 'a'
変数chと文字'a'が等しいとき真
str[0] == 'B'
配列strの0番目の要素が'B'と等しいとき真
以下の例では3つ並べているが、条件が2つでも5でも20でも問題ありません。
50 < a && b < 300 && c >= 20
aが50より大きい、bが300未満、cが20以上の3つの条件をすべてが真であるときこの条件式は真になる。ひとつでも偽である場合は全体の条件式は偽になる。
条件A && 条件BはAとBの両方の条件を満たすときに真となる。片方が偽なら全体の条件式は偽。
&&
かつ
・
and
論理積
集合の上にとつ
などは同じ意味。
論理演算に関して詳しく学びたい方は論理代数、集合などで調べてください。
a < 1 || b < 1 || c < 1
aが1より小さい、bが1より小さい、cが1より小さいという3つの条件のうち少なくともどれか1つが真であれば全体の条件式は真になります。
3つの条件すべてが偽であるときのみ全体の条件式は偽になります。
int a = 5;
// 処理が2行以上ならブロック、{}を使って記述。
if (a > 3) {
a++;
printf("aは3より大きいです。\n");
} else printf("条件式は偽\n");
// インクリメントして aは3より大きいです。を出力
if (a*5 < 100)
puts("true"); // true
条件式が偽のときにはif文のブロック、{ }
内の処理は行われず、条件式が偽のときelseの処理を実行します。
int a = 5;
if (a > 3) printf("aは3より大きいです。\n");
// 処理が1行なので{}を使わなくても問題ない。
// もちろんブロック内、{}の中に記述しても構わない。
aの値が3より大きいのでif文の条件式の計算結果は真(true)になります。基本的に真=true=1を表します。
逆に偽=false=0です。
以下のように書くこともできます。
条件式が0以外であれば条件式が真としてif文内の処理が実行されます。
1、true、1<10をそれぞれ0、false、1>10に書きなおすと条件式は偽となるためif文内の処理は行われません。
if (1) {
// 処理
}
if (true) {
// 処理
}
if (-43873) {
// 処理
// 1以外の値は正負問わずにtrueとして処理される。
}
if (1 < 10) {
// 処理
}
以下のように{}
を記述しないとひとつ後の処理のみがif文の中の処理として扱われます。
if (false)
のあとのputs("false");
は条件式が偽であるため実行されません。
その後に書いてある`puts("hello");はif文とは関係ないので実行されます。
int a = 100;
// &&は左辺と右辺の条件式がどちらも真であるときのみ真
if (10 < a && a < 1000)
printf("10 < a < 1000\n");
printf("この処理はif文とは関係ない\n");
if (false) puts("false"); puts("hello");
// 10 < a < 1000
// ここはif文の外側
// hello
||
&&
は&&が先に演算され、同じ優先順位では左から順番に評価する。
if (true || true && false)
はどちらだろうか。
解答
まず||よりも優先順位の高い演算子&&が先に評価されるのでtrue&&falseはfalseである。 次にtrue || (false)はtrueなので全体の条件式は真。int a = 100;
long long b = -2,c = 388838;
// 処理が2行以上ならブロック、{}を使って記述。
if (false || 50 < a && b < 300 && c >= 20 || false) {
printf("ifの条件式はtrue !\n");
} else printf("条件式は偽\n");
// ifの条件式はtrue !
if
,else if
と書くとif文の条件式が真であればelse if
以降は評価されず実行されませんがelseを書かなければそれぞれの条件式が評価され、真であればそれぞれ実行されます。
if (a < 0) {
// 処理1
}
if (a < 100) {
// 処理2
}
if文のネスト
各ブロックの中にさらにif文を入れるような処理をネスト(入れ子)と呼ぶ。
何重にも深くすることができるがそれだけ読みづらくなる。
int a = 100;
long long b = -2, c = 388838;
if (a == 100 && b == -2)
{
if (c + 10 >= 10 && true)
{
if (true && true || false) {
puts("true"); // 実行されます。
}
}
else ; // ;を書くか、{}を書かないとエラー
}
// true
コラム
&&
は左辺が偽のとき右辺を評価せず偽となる。
||
は左辺が真のとき右辺を評価せず真となる。
文字列の比較
C言語ではstrcmp関数を使う。
#include <string.h>
が必要。
C++ではinclude <string>
を使って
#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
cin >> str;
if (str == "hello") {
cout << "world!!" << endl;
}
} // helloと入力すればWorld!!が出力される。
三項演算子
if文を使わずに簡単な分岐を行う。
計算対象の被演算子(オペランド)が3つある。
三項の演算子は C/C++ ではひとつしかないので間違いではないのですが、 C ではこの演算子を直接表す用語として条件演算子 (conditional operator) と言ってます。
C++ でも原文の用語は同じように conditional operator なのですが何故か JIS では二択条件演算子という用語になっていて C での訳語とは異なります。
@SaitoAtsushi(齊藤 敦志)
numの値を100より小さい値に変えると'B'
が変数cに代入される。
char c; int num = 150;
c = (num >= 100) ? 'A' : 'B';
printf("%c",c);
// A
#include <stdio.h>
int main()
{
int a = 0,b = 2;
printf("%d",(a > b) ? 1 : 0); // (a > b)は偽であるため0が出力
return 0;
} // 0
もう少し複雑な条件判断もできる。
switch case
switch (条件式)
で指定した条件式の値と同じ値のcaseから処理を開始します。break;
にあたるとswitch文のブロック{}
から抜け出します。
条件式は数値か'A'などの文字で指定します。
条件式は同じ値のcaseを見つけて開始位置を指定しているだけなのでbreak;
を書かないと}
までの全ての処理が実行されます。(fall through)
あえてbreak;
を書かずにcase文をつなげる場合がある。
caseの値に当てはまらない場合はdefaultにジャンプします。
default:
を最後に書く場合はbreak;
を書かなくても構いませんが、下にcase文を追加したりするとそこの処理も実行されてしまうので一応書いておいた方がよいかもしれません。
case、defaultの順番は特に決まっておらず、defaultを省略しても構いません。
int a;
scanf("%d",&a);
switch(a) {
case 1:
printf("1が入力されました。\n");
break;
case 2:
printf("2が入力されました。\n");
break;
// fall through
case 3:
case 4:
case 5:
printf("3 or 4 or 5が入力されました。\n");
break;
default:
printf("1,2,3,4,5以外の数値が入力されました。\n");
break;
}
繰り返し文
ループを書く際に意図しない無限ループにならないように終了条件は把握しておくとよいと思います。
C言語では主にfor文、while文を使います。
ループ回数を数えてループ回数がn回になったときにループを終了させる目的でカウント変数(ループ変数)というものを宣言することが多いです。以下ではカウンター変数とも呼んでいます。カウンターは必ず必要というわけではありません。
if文に限らずfor文もfor文の中にfor文、while文の中にif文、for文の中のif文の中にwhile文を書いてネスト構造にすることが可能です。
for (;;) {
do {
for (;;) {
if (1) {
printf("Hello\n");
return 0;
}
}
} while (1);
}
for
ループ回数を数えるためのカウンター変数をfor文の外で宣言するとfor文のブロックの外側でも変数が使える。
基本的に変数のスコープ(有効範囲)はそのブロック内である。main関数の{}
内で宣言すればmain関数が終了するまで使える。for文のブロック内で宣言すればfor文のブロック内でしかその変数は使えない。
for文の中でカウンター(例:i
)を宣言すればそのブロック内のネストのfor文でない限り、for文を書くたびに同じ変数i
を宣言できる。
for (初期化; 条件式; 更新) {
// 繰り返す処理
}
初期化とはカウンターの初期化を行うための文で最初に1回だけ実行されます。
条件式とはループの終了条件を設定するために使います。**条件式が真の間は繰り返し処理を実行し続けます。**条件式が偽になるとfor文を抜けます。
繰返し文におけるループを続ける条件の式には C では制御式 (controlling expression) という名前を付けています。 条件式は条件演算子 (三項演算子) にかかわる用語です。
C++ の仕様では専用の用語を設けずに単に条件 (the condition) と書かれているようです。
@SaitoAtsushi(齊藤 敦志)
更新、変化式と呼ばれることもあるようです。
カウンターの値を更新するために使います。ここに書かれた式は繰り返し処理が1回終了する度に実行されます。
カウンターを使ったfor文の条件式の書き方の例
初期化の値を0、条件式をi < 10とすることで10回実行できる。
int i;
for (i = 0; i < 10; i++) {
printf("%d\n",i);
}
int i;
for (i = 1; i <= 10; i++) {
printf("%d\n",i);
}
上の2つのプログラムはどちらも10回ループする。
初期化の値、条件式の書き方でループする回数が変わることに注意。
#include <stdio.h>
int main(void)
{
int i;
for (i = 0; i < 5; i++)
{
printf("%d回目\n",i);
}
// iの値が5であることに注意。
printf("forの外側のi : %d\n",i);
return 0;
}
/*
0回目
1回目
2回目
3回目
4回目
forの外側のi : 5
*/
以下のプログラムにおいて変数i
のスコープはfor文のブロック内である。したがって{}
の外でiを使うには再度宣言する必要がある。
for (int i = 0; i < 5; i++)
{
printf("%d回目\n",i);
}
エラーになるなら良いのですがエラーにならずに現在の仕様とは異なる挙動をするコンパイラが存在しました。 現在の仕様ではこの場合の i は for 文の終わりまでですが、古い C++ (ISO の規格になる前のもの) は「宣言はそれを囲むブロックの終わりまでをスコープとする」という仕様だった時期が長く、 for 文を抜けた後でも i にアクセスできました。
@SaitoAtsushi(齊藤 敦志)
for文であえて無限ループをさせる場合は条件式を常に真にすればできますが、以下のような書き方もできます。
for (;;) {
printf("loop\n");
}
ターミナル、コマンドプロンプトで実行したプログラムはCtrl+C
で強制終了させることができます。ターミナルを閉じても構いません。
break;
を使うことでfor文を強制的に抜けることができます。
for (;;) {
printf("loop\n");
break;
} // loop
while
for文は決まった回数のループで使う場合が多く、繰り返す回数がわからない場合はwhile文を使うことが多い。
どちらでも同じ処理ができるので好みもあるかもしれません。
#include <stdio.h>
int main(void)
{
int i = 0; // 初期化
while (i < 10) // 条件式
{
printf("%d\n",i);
i++; // 更新
}
return 0;
} // 10回実行されます。
/*
0
1
2
3
4
5
6
7
8
9
*/
月2000円のお小遣いをもらえる小学生が貯金をして何か月で5万円をためることができるかwhile文を使って解きなさい。
#include <stdio.h>
int main(void) {
int sum = 0,i = 1;
while (sum < 50000) // 条件式
{
sum += 2000;
printf("%dヶ月 貯金額 : %d\n", i, sum);
i++;
}
return 0;
}
do while
do while文はwhile文とほとんど同じですが少しだけ違います。
while文は{}
内の処理を行う前に条件式を判定する前判定、do~while文は{}
内の文を実行した後に条件式を判定する後判定です。
int i = 1;
do {
printf("繰り返し\n");
i++;
} while (i <= 3); // セミコロンが必要
/*
繰り返し
繰り返し
繰り返し
*/
do~while文は必ず1回は実行されるという特徴があるので入力をチェックするために使うことがあります。
int len;
do
{
printf("入力は正の整数で行ってください\n");
printf("正方形の辺の長さ:");
scanf("%d", &len);
} while (len <= 0);
printf("正方形の面積は%dです。\n", len*len);
辺の長さに0以下の整数を入力した場合は再度入力を行ってもらうプログラムです。1以上の整数を入力した場合は次のループに入らず、面積を出力します。
これをwhile文で実装するとscanf文と書いた後にwhile文を書いてその中にもうひとつscanf文を書く必要があります。
文を短く、綺麗に、楽に書くためにdo~while文を使う場合があるかもしれません。
break
break文を使うことで条件が真であってもループを終了させることができる。
以下のプログラムはi
が3のときにループを抜けるので4,5が出力されることはない。
for (int i=0; i<5; i++) {
printf("%d\n", i);
if (i == 3) {
break;
}
}
以下のbreak文では内側のインデントの深い方のループを抜けるだけで外側のrep(i, 5)のループは抜けない。
// これを書いておけばrepと書くだけでfor文を短く書ける
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
rep(i, 5) {
rep(j, 3) {
printf("i:%d j:%d\n", i, j);
if (j == 2) break;
}
}
continue
continue以下の処理を飛ばして次のループに入る処理。
for文では( )
の中にカウンタの更新を書いていればカウンタが更新されるが、while文などでcontinueの下に更新処理を書いているとその処理を飛ばしてしまう場合があるので注意。
rep(i, 10)
は標準の書き方ではありません。
この場合はカウンタをiとして0から9まで10回ループしてくれます。
以下のプログラムはiまたはj
を3でわったときの余りが0でない場合の処理を飛ばして次のループに入ります。
つまりカウンタがどちらも3の倍数のときにprintf
が実行されます。
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
rep(i, 10) {
rep(j, 10) {
if (j % 3 != 0 || i % 3 != 0) continue;
printf("i:%d j:%d\n", i, j);
}
}
i:0 j:0
i:0 j:3
i:0 j:6
i:0 j:9
i:3 j:0
i:3 j:3
i:3 j:6
i:3 j:9
i:6 j:0
i:6 j:3
i:6 j:6
i:6 j:9
i:9 j:0
i:9 j:3
i:9 j:6
i:9 j:9
以下のプログラムではカウンタが3の時にcontinue以下の処理が飛ばされるのでカウンタが更新されず無限ループします。
int i = 0;
while (i < 10) {
if (i == 3) continue;
printf("%d\n", i);
i++;
}
空文
以下は何もしないプログラムです。
かといってエラーもでません。
処理を書ける場所に;
を書くと何も行いません。
;;;;; ;; ; ; ;; ;
; ; ; // 何もしない
if (1) ; else {} for (;;) {break;} // 何もしない
for(;;){}
は終了条件がないので無限ループします。
参考文献
ここまで読んでいただきありがとうございます。
また、時間があるときに関数、配列といった機能も扱いたいと思います。