C 言語ノート#
Frank に感謝します。彼は多くの学生に現代 C 言語への扉を開いてくれました。
[TOC]
6 月に書いたブログ記事はあまりにも内容が薄かった。このノートから、ブログ記事の内容は一段と高まります!
内容は講師 Micro_Frank の無料コースからのもので、ノートは講義内容に基づいています。著作権侵害があれば、ブロガーに連絡してください!
第 1 章#
最初のプログラム:C 言語の実行プロセス#
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
このプログラムは C 言語の基本的な例であり、1 行のテキストを出力する方法を示しています。#include <stdio.h>
文は標準入出力ライブラリを含み、int main()
はプログラムのエントリーポイントです。printf("Hello World!\n");
は文字列を印刷するために使用され、return 0;
はプログラムが成功裏に終了したことを示します。
C 言語の物語・1#
教科書の選択:C Primer Plus#
英語と中国語の対照読書をお勧めします。
C 言語の物語・2#
宣言 定義 代入#
#include <stdio.h>
int main()
{ //宣言 declaration
int number = 123;
//定義 definition
printf("%d\n", number);
//代入 assignment
return 0;
} //Frank_PPT講師はこのセクションでTABキーとタブ文字の使用についても説明しました。
コードは主に単純な C プログラムで、宣言、定義、代入の概念を含んでいます。以下は詳細な分析です: c #include // 標準入出力ライブラリをインクルード
- #include
: これはプリプロセッサ指令であり、標準入出力ライブラリをインクルードし、プログラムがprintf
などの関数を使用して入出力操作を行えるようにします。 c int main() { // メイン関数、プログラムのエントリ
- int main()
: メイン関数を定義し、C プログラムの実行はここから始まります。戻り値の型はint
で、この関数が整数値を返すことを示します。 c int number = 123; // 整数型変数を定義し初期化
- int number = 123;
: ここではnumber
という名前の整数型変数を宣言し、初期値を123
に設定しています。C 言語では、変数の宣言と定義は通常 1 つの文で行われます。 c printf("%d\n", number); // 変数numberの値を印刷
- printf("%d\n", number);
: この行のコードは変数number
の値を出力する意図がありますが、誤りがあります。正しい使い方はprintf("%d\n", number);
です。この文では%d
は整数型データを出力するためのフォーマット指定子で、\n
は改行文字です。誤ったバージョンでは、文字列"number"
が通常のテキストとして出力され、変数の値としては出力されません。 c return 0; // メイン関数を終了し、0を返すことはプログラムが成功裏に実行されたことを示す
- return 0;
: この行のコードはメイン関数の終了を示し、オペレーティングシステムに0
を返します。これは通常、プログラムが成功裏に実行されたことを示します。 修正コード 上記の分析を考慮すると、正しいコードは次のようになります: c #include int main() { int number = 123; // 変数を定義し初期化 printf("%d\n", number); // 変数の値を正しく印刷 return 0; // 0を返し、プログラムが成功したことを示す }
### まとめ このコードは基本的な C 言語の構造を示しており、変数の宣言と定義、標準ライブラリの使用、出力フォーマットの使用、メインプログラムの戻り値を含んでいます。唯一注意が必要なのはprintf
のフォーマット文字列の正しい使用です。
識別子#
identifier:
nondigit
identifier
digit
nondigit:
_ a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
digit:
0 1 2 3 4 5 6 7 8 9
コンパイラは大文字と小文字を異なる文字として扱います。
add
ADD
Add
aDD
Microsoft 専用
2 つのアンダースコアで始まるか、1 つのアンダースコアの後に大文字の文字が続く名前。
Microsoft はアンダースコアと大文字をマクロ名の先頭として使用し、ダブルアンダースコアを Microsoft 特有のキーワード名の先頭として使用します。
有効な識別子
j
count
temp1
top_of_page
skip12
LastNum
キーワード#
第 2 章 データ型#
コメント#
//行
コメント
/*
ブロック
注
釈
*/
進数#
変数#
//int 変数
#include <stdio.h>
int main(){
//整数 代入
int sun_flower = 9673;
printf("陽光の現在の値:%d\n", sun_flower);
return 0;
}
整数型#
構文:定義された型 識別子 = 値
int 型のメモリ内の格納#
コンピュータの記憶単位:
1G = 1024MB
1MB = 1024KiB
1KiB = 1024Btyes
1024Btye = 8Bit
00000000 | 00000000 | 00000000 | 00000000 | int |
---|
0は正負を表示するために使用され、合計で $ 2^{32} - 1 $ 個の値を表すことができます。
#include <stdio.h>
int main()
{
int number = 100;
//10進数で表示
printf("Decimal: %d\n", number);
printf("Octal: %o\n", number);
printf("Hexadeciaml (lowercase) : %x\n", number);
printf("Hexadeciaml (uppercase) : %X\n", number);
return 0;
}
浮動小数点数#
浮動小数点数には:2.75、3.16E7、2e-8 などがあります。
コンピュータは浮動小数点数をどのように格納するのか?#
浮動小数点数:符号(1)+ 小数(23)+ 指数(8) int 型 32 ビット
One Step : 3.14159=> $ 314159×10^{-5} $ まず int 型に変換します。
Two Step : 指数をどうやって格納するのか?$ 2^{8} $ = 256 0 から 255 まで、127 を左に偏らせた数は負の指数で、偏差は指数値です。
コンピュータにおける浮動小数点数の格納は通常 IEEE 754 標準に従います。これは浮動小数点数の表現方法を定義する標準です。浮動小数点数は 3 つの主要な部分で構成されています:符号ビット、指数部分、尾数(または有効数字)。以下は浮動小数点数の一般的な格納形式です: 1. 符号ビット:1 ビットを占め、数値の正負を示します。0 は正数、1 は負数を示します。 2. 指数部分:一定のビット数を占め(例えば、32 ビット浮動小数点数は 8 ビット、64 ビット浮動小数点数は 11 ビットを使用)、数値の大きさの範囲を示します。指数は通常オフセット(つまり、定数を加えること、オフセット値と呼ばれる)を加えた形で表現され、負の指数値を示すことができます。 3. 尾数:残りのビット数を占めます。標準の浮動小数点表現では、尾数は通常 1.XXXX の形式で格納され、暗黙的に前に 1 がある(これを暗黙の先頭ビットと呼びます)。 32 ビット浮動小数点数(単精度)と 64 ビット浮動小数点数(倍精度)の格納構造では、具体的なビット配分は以下の通りです: - 単精度(32 ビット): - 符号ビット:1 ビット - 指数:8 ビット - 尾数:23 ビット - 倍精度(64 ビット): - 符号ビット:1 ビット - 指数:11 ビット - 尾数:52 ビット この形式を使用することで、コンピュータは非常に大きな数値範囲と小数部分の精度を表現できますが、浮動小数点数の表現には丸め誤差や表現範囲の制限もあります。 コンピュータでは、浮動小数点数は通常二進数形式で格納され、これはメモリ内での保存と操作が二進数値に基づいて行われることを意味します。この保存方法により、浮動小数点数の演算がより効率的になります。
float と double 型#
float 型の範囲は約 3.4E-38 から 3.4E+38 の間です。
小数は一般に > 1 であるため、正規化された浮動小数点数はしばしば 1 より大きく、尾数(残りのビット数を占めます)。標準の浮動小数点表現では、尾数は通常 1.XXXX の形式で格納され、暗黙的に前に 1 がある(これを暗黙の先頭ビットと呼びます)。
したがって float の尾数の暗黙の長さは 24 ビット、double の尾数の暗黙の長さは 53 ビットです。
単精度(32 ビット)- 倍精度(64 ビット)
保存 > 精度の場合は double を選択し、保存 < 精度の場合は float を選択します。
浮動小数点数の印刷出力#
#include <stdio.h>
int main() {
float temperture = 36.5f;
float humidity = 48.3f;
float speed_of_sound = 343.5e2f;
float lenght = 12.3f, width = 23.45f, height = 34.56f;
printf("Temperture: %f\n", temperture);
printf("Humidity: %f\n", humidity);
printf("Speed_of_Sound: %f\n", speed_of_sound);
printf("Lenght: %f x %f x %f\n", lenght, width, height);
//精度を失う
//double %lf , float %f
return 0;
}
C99 における浮動小数点数の規定#
#include <stdio.h>
int main () {
float num = 123.456;
printf("Using %%f: %f\n", num);
//%e %E 科学的記数法形式で出力
printf("Using %%e: %e\n", num);
printf("Using %%E: %E\n", num);
//%a %A 十六進浮動小数点数p記数法
printf("Using %%a: %a\n", num);
printf("Using %%A: %A\n", num);
return 0;
}
浮動小数点数のオーバーフロー#
#include <stdio.h>
#include <float.h>//開いてみてください
int main() {
float max_float = FLT_MAX;
float overflow = max_float * 1000.0f;
//オーバーフロー
float min_float = FLT_MIN;
float underfloat = min_float / 1000.0f;
//アンダーフロー
printf("最大浮動小数点数: %e\n", max_float);
printf("オーバーフロー: %e\n", overflow);
printf("最小浮動小数点数: %e\n", min_float);
printf("アンダーフロー: %e\n", underfloat);
return 0;
}
Nan & Infinity#
#include <stdio.h>
#include <float.h>
#include <math.h>
int main() {
//正の無限大
float positive_infinty = INFINITY;
printf("Positive Infinity: %f\n", positive_infinty);
//負の無限大
float negative_infinty = -INFINITY;
printf("Negative Infinity: %f\n", negative_infinty);
//0で割ることによって生じる無限大
float num = 1.0f;
float infinity = num / 0.0f;
printf("0.0 / 0.0 = %f\n", nan);
//Nan 0/0
//float nan = 0.0f /0.0f;
//printf("0.0f / 0.0f =%f\n", nan)
//負数の平方根
float negative_sqrt = sqrt(-1.0f);
printf("sqrt(1.0f) = %f\n", negative_sqrt);
return 0;
}
最近偶数丸め基準(銀行家の丸め)#
#include <stdio.h>
int main() {
//四捨五入
//IEEE 754
//最近偶数丸め round to nearest, ties to even
//銀行家の丸め
//3.14159
float number = 3.14159f;
printf("%.4f\n", number);
//3.15
//3.25
return 0;
}
double,long double 科学研究と企業の違い#
#include <stdio.h>
int main() {
//double
//float 精度を失う
//3Dレンダリング
//利息 NASA
//浮動小数点定数 3.14
//3.14はデフォルトでdouble型、特に3.14fと明記する必要があります。
return 0;
}
コンピュータ科学とプログラミングにおいて、double
とlong double
は小数を含む数値を表すための 2 つの浮動小数点データ型です。これらは科学研究と企業での使用において、時には異なる重点が置かれることがあります。主に以下の点で違いが見られます:
- 精度の要求:
- 科学研究:科学研究はしばしば高度に正確な計算を含むため、物理学や化学のシミュレーションなどでは、
long double
が優先的に使用されることがあります。 - 企業:商業アプリケーションでは、通常、浮動小数点数の精度要求はそれほど厳しくなく、
double
がほとんどのニーズを満たすことができます。特に財務計算や統計分析などのシーンで。
- 性能の考慮:
- 科学研究:科学計算は計算の正確性により重点を置くことが多いですが、高性能計算では精度のアルゴリズムと計算性能のバランスを取る必要があります。
- 企業:企業アプリケーションは、性能、応答時間、リソース利用効率を追求することが多く、一定の精度を保証しつつ計算速度を向上させるために
double
を使用する傾向があります。
- プラットフォーム依存性:
- 科学研究:科学者は異なるハードウェアプラットフォーム間の違いを考慮し、浮動小数点数型を選択する際にクロスプラットフォームの互換性を考慮します。
- 企業:企業はソフトウェアの保守性と開発効率に関心を持つことが多く、一般的で標準化された
double
型を選択して、より良い開発サポートとコミュニティリソースを得ることができます。
- 言語とツールのサポート:
- 科学研究:研究者は、NumPy や SciPy などの専門の科学計算ライブラリを使用することがあり、これらのライブラリはさまざまな浮動小数点数型のサポートを考慮して設計されています。
- 企業:企業環境では、開発チームは一般的なデータ型を使用する傾向があり、標準ライブラリとストレージプロセスに依存するため、
double
型がより一般的です。
要するに、double
とlong double
はどちらも浮動小数点数型ですが、科学研究と企業での具体的な使用状況は、要求、性能、プラットフォームの違いによって異なる場合があります。
float と double の有効精度の比較 原理と計算#
#include <stdio.h>
int main() {
float float_num = 1.0 / 3.0;
double double_num = 1.0 / 3.0;
printf("Float precision: %20f\n", float_num);
printf("Double precision: %.20lf\n", double_num);
//floatとdoubleの有効精度の比較
printf("Defined max precision for double: %d\n", FLT_DIG);
printf("Defined max precision for float: %d\n", DBL_DIG);
//53をlog₂10で割る 24をlog₂10で割る これが精度です。
}
float(単精度浮動小数点数):通常 4 バイト(32 ビット)を占め、1 ビットが符号ビット、8 ビットが指数ビット、23 ビットが尾数(有効数字)です。通常、有効精度は約 7 桁の 10 進数です。
double(倍精度浮動小数点数):通常 8 バイト(64 ビット)を占め、1 ビットが符号ビット、11 ビットが指数ビット、52 ビットが尾数です。通常、有効精度は約 15 から 16 桁の 10 進数です。
浮動小数点数の表現は IEEE 754 標準に従います。その値は以下の式で表すことができます: [\text = (-1)^{sign} \times (1 + fraction) \times 2^{exponent} ]
sign: 符号ビット、正負を決定します。 fraction: 尾数、数値の精度を決定します。 exponent: 指数、数値の大きさを決定します。 bias: オフセット、float
の場合は 127、double
の場合は 1023 です。
銀行では ** 固定小数点数(データベース MySQL)** を使用します。
char & ASCII#
#include <stdio.h>
int main() {
char mych = 'a'; //実際にはint型で保存され、Aはint型に変換されます int(97)
printf("mych: %c\n");
//ASCIIアメリカ情報標準コード 一般に7bitを使用し、128文字を含みます。
}
エスケープシーケンス バックスラッシュ \#
エスケープシーケンス | 表示 |
---|---|
\a | 鳴き声(アラーム) |
\b | バックスペース |
\f | ページ送り |
\n | 改行 |
\r | キャリッジリターン |
\t | 水平タブ |
\v | 垂直タブ |
\' | シングルクォート |
\" | ダブルクォート |
\\ | バックスラッシュ |
\? | テキストの疑問符 |
\ ooo | 八進法での ASCII 文字 |
\x hh | 十六進法での ASCII 文字 |
\x hhhh | 十六進法での Unicode 文字(このエスケープシーケンスがワイドキャラクタ定数または Unicode 文字列テキストに使用される場合)。例えば、WCHAR f = L'\x4e00' またはWCHAR b[] = L"The Chinese character for one is \x4e00" 。 |
//画面をクリアする print("\033[2J");
//カーソルを移動する print("\033[%d;%dH");
ブール型 bool 型#
#include <stdio.h>
#include <stdbool.h>
int main() {
//trueまたはfalse
//1または0に変換
bool is_game_win = true;
bool is_game_over = false;
return 0;
}
定数 const と #define マクロ#
#include <stdio.h>
#define PI 3.14
int main() {
//定数
const double MAX_USER = 100;
printf("PI: %lf\n", PI);
return 0;
}
第 2 章 終了の言葉#
第 3 章 演算子#
演算子の紹介#
1、算術演算子
- 単項
++
(インクリメント)、--
(デクリメント)、+
(加算)と-
(減算)演算子 - 二項
*
(乗算)、/
(除算)、%
(余り)、+
(加算)と-
(減算)
2、関係演算子
演算子 | テストされる関係 |
---|---|
< | 最初のオペランドが 2 番目のオペランドより小さい |
> | 最初のオペランドが 2 番目のオペランドより大きい |
<= | 最初のオペランドが 2 番目のオペランド以下 |
>= | 最初のオペランドが 2 番目のオペランド以上 |
== | 最初のオペランドが 2 番目のオペランドと等しい |
!= | 最初のオペランドが 2 番目のオペランドと等しくない |
データオブジェクト 左辺値と右辺値#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main() {
//データオブジェクト
//左辺値 Lvalue
//右辺値 Rvalue
//演算子
uint32_t apple_box = 5;
uint32_t orange_box = 5;
printf("リンゴの箱の中には %" PRIu32 "個のリンゴがあります\n", apple_box);
printf("オレンジの箱の中には %" PRIu32 "個のオレンジがあります\n", orange_box);
uint32_t total_fruit = apple_box + orange_box;
printf("箱の中には %" PRIu32 "個の果物があります\n", total_fruit);
return 0;
}
複数代入#
前置後置インクリメントとデクリメント#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main() {
int32_t value = 5;
int32_t result_1;
int32_t result_2;
//後置インクリメント、先に代入し、その後++
result_1 = value++;
//前置デクリメント、先に--、その後代入
result_2 = --value;
printf("後置インクリメントの後、result_1 = %" PRIu32 ", result_2 = %" PRIu32 ", value = %" PRIu32 "\n", result_1, result_2, value);
return 0;
}
ビットシフト演算子#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
uint8_t num = 22;
num >> 2;//高位を0で埋め、低位を排除
return 0;
}
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
uint8_t num = 22;
//num >> 2; 高位を0で埋め、低位を排除
uint8_t num = 22;
printf("元の数: %" PRIu8 " (バイナリ: 00010110)\n", num);
uint8_t left_shifted = num << 2;
printf("2ビット左シフト: %" PRIu8 " (バイナリ: 01011000)\n", left_shifted);
uint8_t right_shifted = num >> 2;
printf("2ビット右シフト: %" PRIu8 " (バイナリ: 00000101)\n", right_shifted);
return 0;
}
乗算演算:ビットシフト演算子が直接の * 処理よりも速い理由は?
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
uint8_t num = 25;
//uint32_t result = num * 1024;
printf("結果: %" PRIu32 "\n", result);
ALUは基本的な演算を担当します。
return 0;
}
ビットシフト演算子が直接の乗算演算子(*)よりも速く処理される理由は、主に底層の実装の違いによるものです。
以下は具体的な理由です:
底層操作:乗算演算はコンピュータの底層では通常、加算やビットシフト演算よりも複雑です。乗算操作は多くのハードウェアリソースと時間を必要とし、複数回の加算と中間結果の保存が関与します。一方、ビットシフト演算は単に二進数のビット位置を調整するだけで、通常は非常に迅速な操作です。
CPU 命令セットの最適化:多くの現代の CPU はビットシフト操作を実行するための専用命令を提供しており、これらの操作はハードウェアレベルで非常に効率的に最適化されています。相対的に、乗算命令も最適化されていますが、通常、その複雑性により実行時の遅延が高くなります。
特定のシーン:2 の累乗の乗算、例えば 2、4、8 などは、ビットシフト操作で置き換えることができます。例えば、2 倍は 1 ビット左シフト、4 倍は 2 ビット左シフトで実行できます。この置き換え方式は特定の状況でパフォーマンスを大幅に向上させることができます。
コンパイラの最適化:多くのプログラミング言語のコンパイラは最適化段階で、特定の乗算演算(特に 2 の累乗の乗算)を対応するビットシフト演算に置き換えることがあります。これにより、コードの実行効率が向上します。
要するに、高レベルではこれらの詳細を気にする必要は通常ありませんが、パフォーマンスに敏感なアプリケーションでは、これらの演算の効率の違いを理解することが役立ちます。
int 型は左シフトがオーバーフローを引き起こす可能性があることに注意が必要です。
論理の真と偽、C の関係演算子#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
int a = 10;
int b = 20;
//xxx ? xxx : xxx 真または偽を判断
bool greater = a > b;
printf("a > b: %d\n", greater);
printf("a > b: %s\n", greater ? "true" : "false");
bool less = a < b;
printf("a < b: %d\n", less);
printf("a < b: %s\n", less ? "true" : "false");
bool not_equal = a != b;
printf("a != b: %d\n", not_equal);
printf("a != b: %s\n", not_equal ? "true" : "false");
bool equal = a == b;
printf("a == b: %d\n", equal);
printf("a == b: %s\n", equal ? "true" : "false");
bool greater_or_equal = a >= b;
printf("a >= b: %d\n", greater_or_equal);
printf("a >= b: %s\n", greater_or_equal ? "true" : "false");
bool less_or_equal = a <= b;
printf("a <= b: %d\n", less_or_equal );
printf("a <= b: %s\n", less_or_equal ? "true" : "false");
return 0;
}
条件式演算子#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
int score = 89;
printf("あなたの成績のレベル:%s\n", score >= 60 ? "合格" : "不合格");
return 0;
}
#include <stdio.h>
int main() {
int score = 85;
// 必要に応じてこのスコアを変更できます
const char* result = (score >= 90) ? "優秀" : (score >= 85) ? "一般" : (score >= 60) ? "合格" : "不合格";
printf("%s\n", result);
return 0;
}
ビット演算子 & ^ |#
演算子 | 説明 |
---|---|
& | ビットごとの「AND」演算子は、最初のオペランドの各ビットを、対応する 2 番目のオペランドのビットと比較します。両方のビットが 1 の場合、対応する結果ビットは 1 に設定されます。そうでない場合、対応する結果ビットは 0 に設定されます。 |
^ | ビットごとの「XOR」演算子は、最初のオペランドの各ビットを、対応する 2 番目のオペランドのビットと比較します。1 つのビットが 0 で、もう 1 つのビットが 1 の場合、対応する結果ビットは 1 に設定されます。そうでない場合、対応する結果ビットは 0 に設定されます。 |
**` | `** |
& ビットごとの AND
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
int a = 12;
int b = 25;
// & 二進数のビットを比較し、両方が1の場合に1を出力します。
printf("%d\n", 12 & 25);
//特定のビットをクリアし、特定のビットが1かどうかを確認します。
return 0;
}
| ビットごとの OR
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
int a = 2;
int b = 5;
// | 二進数のビットを比較し、1があれば1を出力します。
printf("%d\n", a | b);
//特定のビットを設定し、フラグを組み合わせます。
return 0;
}
^ ビットごとの XOR
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
int a = 2;
int b = 10;
// ^ 二進数のビットを比較し、1と0があれば1を出力します。
printf("%d\n", a ^ b);
//論理XOR操作
//特定のビットを反転し、2つの変数の値を交換し、異なるかどうかを確認します。
return 0;
}
ビットごとの NOT ~#
マスクと回路リモコン LED ライトの練習#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
void print_binary(uint8_t num);
int main() {
uint8_t status = 0b00001100; //初期状態
printf("初期状態: 0b");
print_binary(status);
printf("\t(バイナリ)\n");
status = status & 0b11111011; //マスクを使用して制御
printf("最終状態: 0b");
printf("\t(バイナリ)\n");
return 0;
}
void print_binary(uint8_t num) {
for (int index = 7; index >= 0; index--) {
printf("%d", (num >> index) & 1);
}
}
論理演算子 && ||#
演算子 | 説明 |
---|---|
&& | 両方のオペランドが非ゼロ値を持つ場合、論理「AND」演算子は値 1 を生成します。どちらかのオペランドが 0 の場合、結果は 0 になります。論理「AND」演算の最初のオペランドが 0 の場合、2 番目のオペランドは計算されません。 |
|| | 論理「OR」演算子は、そのオペランドに対して「AND」または演算を実行します。両方のオペランドの値が 0 の場合、結果は 0 になります。どちらかのオペランドが非ゼロ値を持つ場合、結果は 1 になります。論理「OR」演算の最初のオペランドが非ゼロ値を持つ場合、2 番目のオペランドは計算されません。 |
複合代入演算子#
複合代入演算子のオペランドは整数型と浮動小数点型のみです。
#include <stdio.h>
/*
struct BigStruct {
//..
//..
};
void update(BigStruct& bs) {
BigStruct temp = someExp();
bs = bs + temp;
bs += temp;
}
*/
int main() {
int base_number = 8;
int add_number = 2;
int sub_number = 3;
int mul_number = 2;
int div_number = 5;
int mod_number = 4;
base_number += add_number;
//原地修正
base_number -=
base_number *=
base_number /=
base_number %=
base_number <<=
base_number >>=
base_number &=
base_number |=
base_number ^=
return 0;
}
コンマ演算子#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
uint32_t a = 1, b = 2, c = 4;
uint32_t result = (a += 1, b -= 1, c += 3);
printf("a = %d, b = %d, c = %d, result = %d", a, b, c, result);
return 0;
}
Microsoft Learn
// cpp_comma_operator.cpp
#include <stdio.h>
int main () {
int i = 10, b = 20, c= 30;
i = b, c;
printf("%i\n", i);
i = (b, c);
printf("%i\n", i);
}
出力:
20
30
[collapse status="false" title="Microsoft Learn の説明"]
コンマ演算子は左から右への結合性を持ちます。コンマで区切られた 2 つの式は左から右に計算されます。左オペランドは常に計算され、右オペランドが計算される前にすべての副作用が完了します。
特定の文脈(例えば、関数の引数リスト)では、コンマは区切りとして使用されます。このコンマを区切りとして使用する場合と、演算子として使用する場合を混同しないでください。これらの 2 つの使用法は完全に異なります。
式e1, e2
を考えてみてください。この式の型と値は e2 の型と値です。e1 の計算結果は破棄されます。右オペランドが左辺値である場合、結果は左辺値になります。
通常、コンマが区切りとして使用されるシナリオ(例えば、関数や集約初期化子の引数リスト)では、コンマ演算子とそのオペランドは括弧内に含める必要があります。例えば:
C++ の例
func_one( x, y + 2, z );
func_two( (x--, y + 2), z );
上記のfunc_one
の関数呼び出しでは、コンマで区切られた 3 つの引数が渡されます:x
、y + 2
、およびz
。func_two
の関数呼び出しでは、括弧がコンパイラに最初のコンマを順序計算演算子として解釈させます。この関数呼び出しでは、2 つの引数がfunc_two
に渡されます。最初の引数は順序計算(x--, y + 2)
の結果であり、y + 2
の値と型を持ちます。2 番目の引数はz
です。
[/collapse]
計算の優先順位と順序#
シンボル ^1^ | 操作の種類 | 結合性 |
---|---|---|
[ ] ( ) . -> ++ -- (後置) | 式 | 左から右 |
sizeof & * + - ~ ! ++ -- (前置) | 単項 | 右から左 |
typecasts | 単項 | 右から左 |
* / % | 乗算 | 左から右 |
+ - | 加算 | 左から右 |
<< >> | ビットシフト | 左から右 |
< > <= >= | 関係 | 左から右 |
== != | 等しい | 左から右 |
& | ビットごとの「AND」 | 左から右 |
^ | ビットごとの「XOR」 | 左から右 |
| | ビットごとの「OR」 | 左から右 |
&& | 論理「AND」 | 左から右 |
|| | 論理「OR」 | 左から右 |
? : | 条件式 | 右から左 |
= *= /= %= += -= <<= >>= &= ^= |= | 単純および複合代入 ^2^ | 右から左 |
, | 順序計算 | 左から右 |
^1^ 演算子は優先順位の降順でリストされています。複数の演算子が同じ行またはグループに表示されている場合、それらは同じ優先順位を持ちます。
^2^ すべての単純および複合代入演算子は同じ優先順位を持ちます。
式には同じ優先順位の複数の演算子を含めることができます。これらの演算子が式に複数存在する場合、計算はその演算子の結合性に従って右から左または左から右の順序で実行されます。計算の方向は、同じレベルに含まれる複数の乗算(*
)、加算(+
)、またはビットごとの演算(&
、|
または^
)の式の結果に影響を与えません。言語は演算の順序を定義していません。コンパイラが一貫した結果を保証できる場合、コンパイラはこのような式を任意の順序で計算できます。
順序計算(,
)、論理「AND」(&&
)、論理「OR」(||
)、条件式演算子 (? :
) および関数呼び出し演算子は、したがって、オペランドの計算が特定の順序で行われることを保証します。関数呼び出し演算子は、関数識別子の後に続く括弧のセットです。順序計算演算子(,
)は、そのオペランドを左から右の順序で計算することを保証します。(関数呼び出しのコンマ演算子は順序計算演算子とは異なり、そのような保証を提供しません。)詳細については、シーケンスポイントを参照してください。
論理演算子は、オペランドの計算を左から右の順序で保証します。ただし、結果を決定するために必要な最小数のオペランドのみを計算します。これを「ショートサーキット」計算と呼びます。したがって、式の一部のオペランドを計算できない場合があります。例えば、以下の式では
x && y++
y++
が true(非ゼロ)である場合にのみ、2 番目のオペランド(x
)が計算されます。したがって、y
が false(0)の場合、x
は増加しません。
例
以下のリストは、コンパイラが複数の例の式を自動的にバインドする方法を示しています:
式 | 自動バインド |
---|---|
a & b || c | (a & b) || c |
a = b || c | a = (b || c) |
q && r || s-- | (q && r) || s-- |
最初の式では、ビットごとの「AND」演算子(&
)の優先順位が論理「OR」演算子(||
)の優先順位よりも高いため、a & b
は論理「OR」演算の最初のオペランドを構成します。
2 番目の式では、論理「OR」演算子(||
)の優先順位が単純代入演算子(=
)の優先順位よりも高いため、b || c
は代入の右オペランドとしてグループ化されます。注意すべきは、a
に代入される値は 0 または 1 であることです。
3 番目の式は、予期しない結果を生成する可能性のある形式的に正しい式を示しています。論理「AND」演算子(&&
)の優先順位が論理「OR」演算子(||
)の優先順位よりも高いため、q && r
はオペランドとしてグループ化されます。論理演算子は、オペランドの計算を左から右の順序で保証しますが、q && r
が非ゼロの値を計算した場合、s--
は計算されず、s
は減少しません。もしs
が減少しないと問題が発生する場合、s--
は式の最初のオペランドとして表示されるか、別の演算で減少させる必要があります。
以下の式は不正であり、コンパイル時に診断メッセージが生成されます:
不正な式 | デフォルトのグループ |
---|---|
p == 0 ? p += 1: p += 2 | ( p == 0 ? p += 1 : p ) += 2 |
この式では、等号演算子(==
)の優先順位が最も高いため、p == 0
はオペランドとしてグループ化されます。条件式演算子(? :
)は次に高い優先順位を持ち、その最初のオペランドはp == 0
、2 番目のオペランドはp += 1
です。しかし、条件式演算子の最後のオペランドはp
として扱われ、p += 2
ではありません。複合代入演算子に比べてp
のマッチが条件式演算子により密接にバインドされるため、+= 2
には左オペランドがないため、構文エラーが発生します。このようなエラーを防ぎ、より可読性の高いコードを生成するために、括弧を使用する必要があります。例えば、次のように括弧を使用して前の例を修正し、明確にすることができます:
( p == 0 ) ? ( p += 1 ) : ( p += 2 )
参照
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
int32_t result;
result = a * b + c << d > e ? b : c * sizeof(++e) / sizeof(int32_t);
printf("結果: %" PRId32 ", result");
return 0;
}
単項演算子の中で、& - + などの前置 * は解引用演算子です。
&ビットごとのAND
と前置&
は異なり、後者の優先順位が高いです!
第 3 章 分岐と制御#
決定制御#
もし天気が晴れなら —— 遊びに行く;
論理 && || ;
人は決定能力を持ち、論理的な判断がプログラムに選択させます!
前提は:プログラムに「天気の状態」が true か false かを判断させることです。
例えば、天気アプリケーション:
温度 <30° 温度> 30°
プログラミング言語:
- 実際の環境に適用される必要があります。
- 柔軟性、ランダム性
- 完全ではない、完璧があればそれは嘘です。
if 文と if else#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
uint32_t number = 100;
//if文
if (number > 10) {
printf("この数は10より大きい!\n");
printf("この数は10より大きい!\n");
}
if (number >= 100) {
printf("この数は100以上です!\n");
}
else {
printf("この数は100未満です!\n");
}
return 0;
}
論理 AND と OR のショートサーキット動作#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main() {
bool is_weather_sunny = false;
bool is_venue_available = true;
if (is_weather_sunny && is_venue_available) {
printf("イベントは予定通り開催されます!\n");
}
else {
printf("イベントは予定通り開催されません!\n");
if (!is_weather_sunny) {
printf("理由:天気が晴れていません!\n");
}
if (!is_venue_available) {
printf("理由:会場がありません!\n");
}
}
return 0;
}
//自動販売機
//コインのみをサポート
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
int main(void)
{
const uint32_t PRICE = 3; //飲み物の価格
uint32_t balance = 0; //現在の合計コイン
uint32_t coin; //それぞれのコイン
puts("飲み物の価格は$3です!");
while (balance < PRICE) //必要な金額が投じられるまで
{
puts("投げないで!"); //取引を拒否
scanf_s("%" PRIu32, &coin);
if (coin == 1 || coin == 2 || coin == 5) //金額の種類を確認
{
balance += coin; //代入 加算
printf("あなたはすでに $%" PRIu32 "を投入しました\n", balance);
}
else {
printf("申し訳ありませんが、$%" PRIu32 "のコインは受け付けていません\n", coin);
}
}
if (balance > PRICE) {
printf("お釣り:%" PRIu32 "\n", balance - PRICE);
}
return 0;
}
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
int main(void)
{
//プレイヤーが異なる条件の部屋に入る
uint32_t coins = 15;
bool is_vip = true;
bool have_special_tool = false;
if (is_vip) {
puts("VIP入場!\n");
}
else {
puts("非VIP退出!\n");
}
if (coins >= 10 || have_special_tool) {
puts("入場!\n");
}
else {
puts("退出!\n");
}
return 0;
}
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
int main(void)
{
const uint32_t all_laps = 100;
uint32_t current_laps = 0;
puts("開始!");
while (current_laps < all_laps)
{
current_laps++;
printf("第 %"PRIu32"周を完了しました。\n", current_laps);
}
return 0;
}
//自動販売機
//コインのみをサポート
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
int main(void)
{
const uint32_t price = 10; //飲み物の価格
uint32_t balance = 0; //現在の合計コイン
uint32_t coin; //それぞれのコイン
puts("飲み物の価格は$5です!");
while (balance < price) //必要な金額が投じられるまで 価格が満たされるまでループを開始
{
puts("投げないで!"); //取引を拒否
//コインを投げるシミュレーション
scanf_s("%" PRIu32, &coin);
if (coin == 1 || coin == 2 || coin == 5) //金額の種類を確認
{
balance += coin; //代入 加算 カウント
printf("あなたはすでに $%" PRIu32 "を投入しました\n", balance);
}
else {
printf("申し訳ありませんが、$%" PRIu32 "のコインは受け付けていません\n", coin);
}
}
if (balance > price) {
printf("お釣り:%" PRIu32 "\n", balance - price);
}
return 0;
}
//必ずprintfを書くことで効果を確認できます。
//ループの問題に直面した場合
//ループ内で何度も使用しないようにして、不必要なエラーを避けてください。
//合計を計算するプログラム
#include <stdio.h>
#include <inttypes.h>
int main(void)
{
uint32_t sum = 0; //初期の合計を0に設定
uint32_t number = 1; //最初のnumberの値
while (number != 0) //0をループ終了の方法として設定
{
scanf_s("%" PRIu32, &number); //scanfでnumberを再代入し、最初のnumberの値は実際には使用されません。
sum += number;
}
printf("求めた合計sumは %" PRIu32 "\n", sum);
return 0;
}
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
int main()
{
uint32_t sum = 0; //初期の合計を0に設定
char input[50]; //ユーザーの入力を文字から数値に変換
char* end;
puts("一連の数字を入力してください。改行で区切り、qで終了します。");
while (true) //0をループ終了の方法として設定
{
errno = 0;
puts("数字を入力してください:");
scanf_s(" %49s", &input, 50);
if (input[0] == 'q' && input[1] == '\0') {
break;
}
long number = strtol(input, &end, 10);
//入力された文字を数値に変換し、合計sumに加えます。
//0のASCIIは48です。
if (end == input || *end != '\0') {
printf("無効な入力です。数字または文字qを入力してください。\n");
}
else if (errno == ERANGE || number < 0 || number > UINT32_MAX){
printf("数字が範囲を超えました!%u以下の正の整数を入力してください。\n", UINT32_MAX);
}
else {
sum += (uint32_t)number;
}
}
printf("求めた合計sumは %" PRIu32 "\n", sum);
return 0;
}
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
int main()
{
uint32_t sum = 0; //初期の合計を0に設定
uint32_t number; //最初のnumberの値
puts("一連の数字を入力してください。改行で区切り、0で終了します。");
while (true) //0をループ終了の方法として設定
{
scanf_s(" %" PRIu32, &number);
if (number == 0) {
break;
}
sum += number;
}
printf("求めた合計sumは %" PRIu32 "\n", sum);
return 0;
}
//ガード文の使用:レンタカーの例
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <inttypes.h>
void check_car_rent(uint8_t age, uint8_t driving_exp_years);
int main(void)
{
check_car_rent(22, 3);
return 0;
}
void check_car_rent(uint8_t age, uint8_t driving_exp_years) {
//ガード文
if (age < 21) {
puts("条件を満たしていません、年齢が不足しています!");
return 0;
}
if (driving_exp_years < 1) {
puts("条件を満たしていません、運転年数が不足しています!");
return 0;
}
}
//論理式を簡略化
#include <stdbool.h>
#include <inttypes.h>
int main(void)
{
//論理式は簡略化すべきです!
bool is_weekend = true;
bool has_enter = true;
if (!has_enter) {
return 0;
}
return 0;
}
状態機械:複雑な状態の遷移を管理#
#include <stdbool.h>
#include <inttypes.h>
int main(void)
{
//状態機械:複雑な状態の遷移を管理し、switch caseを使用
uint8_t traffic_state = 0;
switch (traffic_state)
{
case 0:
puts("赤");
traffic_state = 1;
break;
case 1:
puts("黄");
traffic_state = 2;
break;
case 2:
puts("緑");
traffic_state = 0;
break;
default:
puts("???");
break;
}
return 0;
}
switch-case と if-else の違い#
//switch-caseとif-elseの違い
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
int main(void)
{
return 0;
}
ループの生活における役割#
//ループの生活における役割
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
int main(void)
{
int32_t number;
scanf_s("入力してください: %d %d %d", &number, &number, &number);//実際の開発ではscanfを使用しません。
//それに\nを記述しない
printf("あなたが入力した数字は: %d %d %dです\n", number, number, number);
return 0;
}
do-while と while の違い#
//do-whileとwhileの違い
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main(void)
{
uint32_t total_laps = 10;
uint32_t current_laps = 0;
puts("罰走開始!");
do {
current_laps++;
printf("ランナーは第%" PRIu32 "周を完了しました\n", current_laps);
} while (current_laps < total_laps);
return 0;
}
do-while の実際の役割#
//do-whileの実際の役割
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
int main(void)
{
uint32_t choice;
do
{
puts("****メインメニュー****");
puts("1. 新しいゲーム");
puts("2. ゲームをロード");
puts("3. 終了");
scanf_s("%" PRIu32, &choice);
switch (choice)
{
case 1:
puts("****新しいゲームを作成!");
break;
case 2:
puts("****セーブをロード!");
break;
case 3:
puts("****ゲームを終了!");
break;
}
} while (choice != 3);
return 0;
}
ランダム数当てゲームの例#
//ランダム数当てゲームの例
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <time.h>
#include <stdlib.h>
int main(void)
{
uint32_t secret_num, guess, status;
char buffer[50];
srand(time(NULL));
//1-100のランダム数を生成
secret_num = rand() % 100 + 1;
puts("当ててみて!");
do {
puts("あなたが推測した数を入力してください:");
fgets(buffer, sizeof(buffer), stdin);
status = sscanf_s(buffer, "%d", &guess);
//ガード文
if (status != 1) {
puts("無効な入力です!");
continue;
}
if (guess < secret_num) {
puts("小さすぎます!");
}
else if (guess > secret_num) {
puts("大きすぎます!");
}
} while (guess != secret_num);
printf("おめでとうございます、正解です!");
return 0;
}
continue 文#
//continue文
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(void) {
uint32_t sum = 0;
int32_t number;
puts("一連の正の整数を入力してください。負の数を入力し、0で終了します。");
while (1) {
puts("数字を入力してください");
scanf_s("%d", &number);
if (number == 0) {
break;
}
if (number < 0) {
continue; //continueはこのループの残りの部分をスキップします。
}
sum += number;//カウント
}
printf("計算結果は:%" PRIu32 "\n", sum);
return 0;
}
continue と break の連携条件判断#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main() {
uint32_t number;
puts("数字を入力してください(0-100)、-1を入力するとプログラムが終了します。");
while (1) {
puts("数字を入力してください:");
scanf_s("%d", &number);
//ガード文!条件をチェックして終了
if (number == -1) {
break;
}
if (number < 0 || number > 100) {
continue;//このループの残りの部分をスキップします。
}
sum += number;
}
return 0;
}
初探 for ループ#
//初探forループ
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(void)
{
//forループ
const uint32_t total_laps = 10; //目標周数
//uint32_t current_lap = 0; 現在の周数を初期化
puts("ランナーが走り始めます。");
for (uint32_t current_lap = 0; current_lap <= total_laps; current_lap++) {
printf("ランナーは第 %" PRIu32 "周を完了しました。\n", current_lap);
}
return 0;
}
整数の平方和#
//整数の平方和
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(void)
{
/*
目的は、プレイヤーが入力したn(nはプレイヤーが入力)までのすべての整数の平方和を計算することです。
*/
uint32_t number;
uint32_t sum_of_squares = 0;
puts("整数nを入力してください。");
scanf_s("%u", &number);
for (uint32_t index = 1; index <= number; index++) {
sum_of_squares += index * index;
}
printf("%" PRIu32 "\n", sum_of_squares);
return 0;
}
カウントダウン#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(void) {
/*
星光大道
カウントダウン
*/
uint32_t start_number;
puts("正の整数を入力してください。");
scanf_s("%" SCNu32, &start_number);
puts("カウントダウンが始まります。");
for (uint32_t index_number = start_number; index_number > 0; index_number--)
{
printf("%" SCNu32 "\n", index_number);
}
puts("カウントダウンが終了しました。");
return 0;
}
//拡張sleep
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <windows.h>
int main(void)
{
/*
星光大道
カウントダウン
*/
uint32_t start_number;
puts("正の整数を入力してください。");
scanf_s("%" SCNu32, &start_number);
puts("カウントダウンが始まります。");
for (uint32_t index_number = start_number; index_number > 0; index_number--)
{
printf("%" SCNu32 "\n", index_number);
Sleep(1000);
}
puts("カウントダウンが終了しました。");
return 0;
}
階乗#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <windows.h>
//階乗
int main(void) {
uint32_t number;
uint32_t factorial = 1;
puts("正の整数を入力してください:");
scanf_s("%" SCNu32, &number);
for (uint32_t index = 1; index <= number; index++) {
factorial *= index;
}
printf("%" PRIu32 "! = %" PRIu32 "\n", number, factorial);
return 0;
}
平方根#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
int main(void)
{
double number = 4.00;
printf("%lf\n", sqrt(121));
return 0;
}
素数#
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
int main(void)
{
uint32_t num;
bool is_prime = true;
puts("正の整数を入力してください。1以外の数で、素数かどうかを確認します:");
scanf_s("%" SCNu32, &num);
if (num <= 1) {
is_prime = false;
}
else {
//for , 1とそれ自身以外の因数をチェックします。
for (uint32_t i = 2; i * i <= num; i++) {
if (num % i == 0) {
is_prime = false;
break;
}
}
}
return 0;
}
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <math.h>
#include <stdbool.h>
int main(void)
{
uint32_t size;
puts("パターンのサイズを入力してください: ");
scanf_s("%" SCNu32, &size);
//特徴:長さと幅が同じ
puts("正方形パターンを印刷します。");
for (uint32_t i = 0; i < size; i