C ラングージにおけるポインタ操作と文字列処理の実践ガイド

ポインタによるデータ範囲の解析

メモリアドレスを直接扱うことで、関数呼び出し内で変数の状態を変更する手法について確認します。ここでは、整数配列から最小値と最大値を取得する処理を実装し、参照渡しの仕組みを理解します。

#include <stdio.h>
#include <stdlib.h>

#define DATA_SIZE 5

void read_data(int target_array[], int count);
void print_array(const int source_array[], int count);
void calculate_stats(const int data[], int len, int *lowest, int *highest);

int main(void) {
    int values[DATA_SIZE];
    int low_val, high_val;

    printf("%d items の入力:\n", DATA_SIZE);
    read_data(values, DATA_SIZE);

    printf("入力内容:\n");
    print_array(values, DATA_SIZE);

    printf("統計計算中...\n");
    calculate_stats(values, DATA_SIZE, &low_val, &high_val);

    printf("結果表示:\n");
    printf("最小値 = %d, 最大値 = %d\n", low_val, high_val);

    return EXIT_SUCCESS;
}

void read_data(int buf[], int num) {
    for (int idx = 0; idx < num; ++idx) {
        scanf("%d", &buf[idx]);
    }
}

void print_array(const int arr[], int cnt) {
    for (int k = 0; k < cnt; ++k)
        printf("%d ", arr[k]);
    putchar('\n');
}

void calculate_stats(const int dataset[], int size, int *min_ptr, int *max_ptr) {
    *min_ptr = *max_ptr = dataset[0];

    for (int m = 1; m < size; ++m) {
        if (dataset[m] < *min_ptr)
            *min_ptr = dataset[m];
        else if (dataset[m] > *max_ptr)
            *max_ptr = dataset[m];
    }
}

この実装では、メインスコープで宣言された変数に対して関数内部からアドレスを渡し、結果を書き戻すことで効率の良いデータ処理を実現しています。*min_ptr*max_ptr は初期値としてデータの先頭要素を指し、ループ内で必要に応じて更新されます。

最大値要素への参照返却

関数がポインタ型をリターンすることで、該当要素のメモリアドレスを直接外部に渡すアプローチです。これにより、呼び出し側で値を dereference して利用できます。

#include <stdio.h>
#include <stdlib.h>

#define ARR_COUNT 5

void fetch_input(int *arr, int limit);
void show_items(const int *list, int range);
int *get_max_addr(const int *nums, int len);

int main(void) {
    int numbers[ARR_COUNT];
    int *largest_pos;

    printf("%d 個の数値を読み込みます。\n", ARR_COUNT);
    fetch_input(numbers, ARR_COUNT);

    printf("格納されたリスト:\n");
    show_items(numbers, ARR_COUNT);

    largest_pos = get_max_addr(numbers, ARR_COUNT);

    if (largest_pos != NULL) {
        printf("最大値:%d\n", *largest_pos);
    }

    return EXIT_SUCCESS;
}

void fetch_input(int *p_arr, int n) {
    for (int i = 0; i < n; ++i) {
        scanf("%d", &p_arr[i]);
    }
}

void show_items(const int *p_list, int r) {
    for (int j = 0; j < r; ++j)
        printf("%d ", p_list[j]);
    putchar('\n');
}

int *get_max_addr(const int *values, int cnt) {
    int max_idx = 0;
    for (int i = 1; i < cnt; ++i) {
        if (values[i] > values[max_idx]) {
            max_idx = i;
        }
    }
    return (int *)(&values[max_idx]);
}

上記コードでは、関数内で最も大きいインデックスを特定し、その要素のアドレスを返却しています。これは配列内の位置情報を保持しながら値へアクセスする典型的なパターンです。

文字列メモリ管理とSwap の実装差異

文字列データを保持する際、「配列」方式と「ポインタ」方式の違いは、コピー処理における挙動に影響を与えます。

ケース A:char 配列の使用

固定サイズのバッファを用意し、内容を入れ替えるには標準ライブラリの関数が必要です。配列名は定数扱いとなるため、代入による交換は許可されません。

#include <stdio.h>
#include <string.h>

#define STR_LEN 80

int main(void) {
    char str_a[STR_LEN] = "First Implementation";
    char str_b[STR_LEN] = "Second Implementation";
    char temp_buf[STR_LEN];

    printf("元データ:\nA: %s\nB: %s\n", str_a, str_b);

    strcpy(temp_buf, str_a);
    strcpy(str_a, str_b);
    strcpy(str_b, temp_buf);

    printf("交換後:\nA: %s\nB: %s\n", str_a, str_b);
    
    return 0;
}

ここでの sizeof は配列全体のサイズ(バイト数)を返し、strlen は文字列中の有効文字数を計測します。配列同士の直接代入はできません。

ケース B:文字列ポインタの使用

リテラル文字列への参照としてポインタを変数宣言すると、アドレス自体を交換することが可能です。

#include <stdio.h>
#include <string.h>

int main(void) {
    const char *ptr_first = "Learning C is useful";
    const char *ptr_second = "Coding requires practice";
    const char *swap_buffer;

    printf("前:\nF: %s\nS: %s\n", ptr_first, ptr_second);

    swap_buffer = ptr_first;
    ptr_first = ptr_second;
    ptr_second = swap_buffer;

    printf("後:\nF: %s\nS: %s\n", ptr_first, ptr_second);

    return 0;
}

この場合、実際のメモリ上の文字列が移動するのではなく、変数 ptr_firstptr_second が格納するアドレス情報のみが書き換えられます。つまり、文字列本体のコピーは行われません。

多次元配列へのポインタアクセス

2 次元配列を扱う際、単純な要素へのポインタと、行ごとのポインタ(配列へのポインタ)の使い分けが重要です。

#include <stdio.h>

int main(void) {
    int matrix[2][4] = {
        {1, 9, 8, 4}, 
        {2, 0, 4, 9}
    };
    int row;
    int col;

    // 1. リテラル添字による直接アクセス
    printf("標準アクセス:\n");
    for (row = 0; row < 2; ++row) {
        for (col = 0; col < 4; ++col)
            printf("%d ", matrix[row][col]);
        putchar('\n');
    }

    // 2. 単一要素ポインタフラット化
    printf("\n要素ポインタフラットアクセス:\n");
    int *elem_ptr = matrix[0];
    for (int k = 0; k < 8; ++k) {
        printf("%d ", elem_ptr[k]);
        if ((k + 1) % 4 == 0) putchar('\n');
    }

    // 3. 行ポインタによる間接アクセス
    printf("\n行ポインタアクセス:\n");
    int (*row_ptr)[4] = matrix;
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 4; ++j)
            printf("%d ", *(*row_ptr + j));
        printf("\n");
        row_ptr++;
    }

    return 0;
}

ここで使用している int (*ptr2)[4] という型定義は、4 つの int からなる配列へのポインタを示しており、2 次元配列の各行全体を指す場合に適切です。

文字列内の置換処理

指定した文字が含まれるすべての箇所を別の文字へ置き換えるロジックを実装します。

#include <stdio.h>
#include <stdlib.h>

#define BUFFER_MAX 80

void perform_replacement(char *target_str, char old_char, char new_char);

int main(void) {
    char message[BUFFER_MAX] = "This program is tricky or not, we will see.";
    
    printf("元のテキスト:\n%s\n", message);

    perform_replacement(message, 'i', '*');

    printf("変換後のテキスト:\n%s\n", message);

    return EXIT_SUCCESS;
}

void perform_replacement(char *str, char find_c, char repl_c) {
    while (*str != '\0') {
        if (*str == find_c) {
            *str = repl_c;
        }
        str++;
    }
}

ポインタを順次インクリメントして null 終端まで処理を行うことで、効率的に変換を完了させます。

指定文字以降本の切断

入力文字列中で特定の文字が見つかった時点で、ストリングを終端文字で打ち切る機能です。

#include <stdio.h>
#include <stdlib.h>
#include 

#define MAX_INPUT 80

char *cut_at(char *src, char delimiter);

int main(void) {
    char input[MAX_INPUT];
    char ch;

    printf("テキストを入力してください:");
    fgets(input, sizeof(input), stdin);
    input[strcspn(input, "\n")] = '\0'; //改行文字の除去

    printf("カット対象の文字を入力:");
    scanf(" %c", &ch); // 前の入力残留の空白スキップ

    cut_at(input, ch);         
        
    printf("結果:\n%s\n\n", input);

    return EXIT_SUCCESS;
}

char *cut_at(char *str, char x) {
    char *p = str;
    while (*p != '\0') {
        if (*p == x) {
            *p = '\0';
            break;
        }
        p++;
    }
    return str;
}

入力を gets ではなく fgets で取得し、末尾の改行文字を処理して安全な入力を確保しています。

ID 形式の検証アルゴリズム

与えられた文字列が特定の識別子(例:パスポート番号や住民票番号形式)の構造に合致するかチェックするサブルーチンです。

#include <stdio.h>
#include <string.h>
#include 

#define CHECK_COUNT 5

int validate_format(const char *id_str);

int main(void) {
    const char *ids[CHECK_COUNT] = {
        "31010120000721656X",
        "3301061996X0203301",
        "530102200511265712",
        "510104199211197977",
        "53010220051126133Y"
    };
    int i;

    for (i = 0; i < CHECK_COUNT; ++i) {
        if (validate_format(ids[i])) 
            printf("%s\tValid\n", ids[i]);
        else
            printf("%s\tInvalid\n", ids[i]);
    }
    return EXIT_SUCCESS;
}

// 長さ 18 文字以内か、数字構成かを確認
int validate_format(const char *str) {
    int len = strlen(str);
    if (len != 18)
        return 0;
    
    for (int k = 0; k < 17; k++){
        if (str[k] < '0' || str[k] > '9') {
            return 0;
        }
    }
    // 最後の文字は数字か X か
    if (str[17] != 'X' && (str[17] < '0' || str[17] > '9')){ 
        return 0;
    }
    return 1;
}

長さと全体的な文字種をチェックすることで不正データをフィルタリングします。

シフト暗号の実装

アルファベット文字を指定された桁数だけシフトするエンコーディングと、逆変復号を実装します。

#include <stdio.h>
#include <string.h>
#include 

#define TEXT_SIZE 80
#define DEFAULT_SHIFT 3

void encrypt_text(char *msg, int steps);
void decrypt_text(char *msg, int steps);

int main(void) {
    char phrase[TEXT_SIZE];
    int step_n;

    printf("文章を入力:");
    fgets(phrase, sizeof(phrase), stdin);
    phrase[strcspn(phrase, "\n")] = '\0';

    printf("シフト量:");
    scanf("%d", &step_n);

    encrypt_text(phrase, step_n);   
    printf("暗号文:\n%s\n", phrase);

    decrypt_text(phrase, step_n);
    printf("復明文:\n%s\n", phrase);

    return EXIT_SUCCESS;
}

// ローターシフト:後方 n スト
void encrypt_text(char *str, int offset) {
    int len = strlen(str);
    for (int i = 0; i < len; i++) {
        if (str[i] >= 'a' && str[i] <= 'z') {
            str[i] = ((str[i] - 'a' + offset) % 26 + 'a');
        } else if (str[i] >= 'A' && str[i] <= 'Z') {
            str[i] = ((str[i] - 'A' + offset) % 26 + 'A');
        }
    }
}

// ローターシフト:前方 n ステップ
void decrypt_text(char *str, int offset) {
    int len = strlen(str);
    for (int k = 0; k < len; k++) {
        if (str[k] >= 'a' && str[k] <= 'z') {
            str[k] = ((str[k] - 'a' - offset + 26) % 26 + 'a');
        } else if (str[k] >= 'A' && str[k] <= 'Z') {
            str[k] = ((str[k] - 'A' - offset + 26) % 26 + 'A');
        }
    }
}

コマンドライン引数のソート処理

起動時に受け取ったパラメータを辞書順に並べ替えて出力します。ポインタの交換を活用した Bubble Sort の適用例です。

#include <stdio.h>
#include <string.h>

void exchange_strings(char **left, char **right);

int main(int argc, char *argv[]) {
    int pass_idx;
    int compare_jdx;

    // 1 をインデックス開始とし、argv[0] はプログラム名のためスキップ
    for (pass_idx = 1; pass_idx < argc - 1; ++pass_idx) {
        for (compare_jdx = 1; compare_jdx < argc - pass_idx; ++compare_jdx) {
            if (strcmp(argv[compare_jdx], argv[compare_jdx + 1]) > 0) {
                exchange_strings(&argv[compare_jdx], &argv[compare_jdx + 1]);
            }
        }
    }

    for(int m = 1; m < argc; ++m)
        printf("Hello, %s\n", argv[m]);

    return 0;
}

void exchange_strings(char **p_a, char **p_b) {
    char *temp = *p_a;
    *p_a = *p_b;
    *p_b = temp;
}

タグ: c-programming pointers arrays string-manipulation data-validation

7月22日 23:08 投稿