出力フォーマットと基本的なデータ型
C言語において、printf関数のフォーマット指定子を適切に使用することは、表示制御において重要です。整数の出力幅を指定し左寄せにするには%-8dのように記述します。マイナス記号が左寄せを指示し、数字が最小フィールド幅を表します。また、浮動小数点数の精度を制御するには%.3fを使用し、小数点以下の桁数を固定します。パーセント記号そのものを出力したい場合は、%%と記述してエスケープ処理を行います。
#include <stdio.h>
int main() {
int val1, val2, val3;
scanf("%d %d %d", &val1, &val2, &val3);
// 左寄せで8文字幅を出力
printf("%-8d%-8d%-8d", val1, val2, val3);
float pi = 3.14159;
// 小数点第3位までを出力
printf("\n%.3f", pi);
double ratio = 0.1234;
// パーセンテージとして出力
printf("\n%.2f%%", ratio * 100);
return 0;
}
データ型のサイズとメモリ
変数がメモリ上で占めるサイズは、sizeof演算子を使用して確認できます。bool型を使用する場合は<stdbool.h>のインクルードが必要です。以下のコードは、異なるデータ型のメモリサイズを表示します。
#include <stdio.h>
#include <stdbool.h>
int main() {
boolean flag;
char character;
// データ型のサイズを出力 (size_t型のため%zuを使用)
printf("%zu %zu", sizeof(flag), sizeof(character));
return 0;
}
配列の初期化について理解しておくことは重要です。特に、関数内(ローカルスコープ)で宣言された配列を初期化しない場合、その値は不定(ゴミ値)となります。一方、グローバル配列や{0}で初期化された配列は、ゼロクリアされます。大量のデータを処理する際は、この初期化コストやメモリの挙動を考慮する必要があります。
ファイル終端(EOF)までのループ処理
競技プログラミングや標準入力処理では、入力の終わり(EOF)まで繰り返し処理を行う場面がよくあります。scanfは読み込みに成功した項目数を返すため、これがEOF(通常は-1)でないことを確認しながらループします。
#include <stdio.h>
int main() {
double radius;
// 入力がある限り計算を続ける
while (scanf("%lf", &radius) != EOF) {
double volume = (4.0 / 3.0) * 3.1415926535 * radius * radius * radius;
printf("%.3lf\n", volume);
}
return 0;
}
数学関数とマクロ定義
累乗を計算するpow関数は<math.h>で定義されており、戻り値は常にdouble型です。整数結果が必要な場合はキャストが必要です。また、複雑な条件判定はマクロとして定義することでコードを簡潔にできますが、マクロ展開時の挙動には注意が必要です。
#include <stdio.h>
#include <math.h>
// うるう年を判定するマクロ
#define IS_LEAP(year) ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
int main() {
int year;
scanf("%d", &year);
// 2のべき乗の計算
int exp;
scanf("%d", &exp);
printf("%lld\n", (long long)pow(2, exp));
// マクロの使用
if (IS_LEAP(year)) {
printf("Leap Year\n");
}
return 0;
}
アルゴリズムの最適化:素数判定
素数を求めるアルゴリズムでは、計算量の削減が重要です。単純に2からn-1までループする方法は非効率です。これを改善するため、2と3の倍数を事前に除外し、その後は6k±1の形の数のみを判定することで、チェック対象を大幅に削減できます。
#include <stdio.h>
#include <stdbool.h>
// 効率的な素数判定関数
bool check_prime(int n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
// 6k ± 1 の形の数のみを確認
for (int i = 5; i * i <= n; i += 6) {
if (n % i == 0 || n % (i + 2) == 0) {
return false;
}
}
return true;
}
int main() {
int count, target = 1;
scanf("%d", &count);
while (count > 0) {
target++;
if (check_prime(target)) {
count--;
}
}
printf("%d\n", target);
return 0;
}
文字列処理と配列操作
文字列操作を行う際、文字定数' 'と文字列リテラル" "の混同はバグの原因となります。文字列リテラルはポインタを返すため、直接文字と比較することはできません。また、多次元配列を使用した行列計算では、ループ変数と境界条件を正しく管理する必要があります。
以下は、2次元配列を時計回りに出力するスパイラル処理の実装例です。
#include <stdio.h>
int main() {
int rows, cols;
scanf("%d %d", &rows, &cols);
int matrix[100][100];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
scanf("%d", &matrix[i][j]);
}
}
int top = 0, bottom = rows - 1;
int left = 0, right = cols - 1;
while (top <= bottom && left <= right) {
// 上辺を左から右へ
for (int i = left; i <= right; i++) printf("%d ", matrix[top][i]);
top++;
// 右辺を上から下へ
for (int i = top; i <= bottom; i++) printf("%d ", matrix[i][right]);
right--;
if (top <= bottom) {
// 下辺を右から左へ
for (int i = right; i >= left; i--) printf("%d ", matrix[bottom][i]);
bottom--;
}
if (left <= right) {
// 左辺を下から上へ
for (int i = bottom; i >= top; i--) printf("%d ", matrix[i][left]);
left++;
}
}
return 0;
}
配列の重複排除とメモリ初期化
配列から重複する要素を削除する場合、新しい配列を用意して既出の要素かどうかをチェックしながら格納します。この際、比較用配列を必ずゼロ初期化しておかないと、ローカル変数の不定値により誤動作する可能性があります。
#include <stdio.h>
#define MAX_SIZE 20000
int main() {
int n;
scanf("%d", &n);
int input[MAX_SIZE];
int unique[MAX_SIZE] = {0}; // 全要素を0で初期化
int unique_count = 0;
for (int i = 0; i < n; i++) {
scanf("%d", &input[i]);
}
for (int i = 0; i < n; i++) {
int is_duplicate = 0;
for (int j = 0; j < unique_count; j++) {
if (input[i] == unique[j]) {
is_duplicate = 1;
break;
}
}
if (!is_duplicate) {
unique[unique_count++] = input[i];
}
}
for (int i = 0; i < unique_count; i++) {
printf("%d ", unique[i]);
}
return 0;
}