Cで文字列を逆にする

C の高度な機能を練習したい場合は、ポインターはどうですか?楽しみのためにマクロや xor-swap を追加することもできます!

#include <string.h> // for strlen()

// reverse the given null-terminated string in place
void inplace_reverse(char * str)
{
  if (str)
  {
    char * end = str + strlen(str) - 1;

    // swap the values in the two given variables
    // XXX: fails when a and b refer to same memory location
#   define XOR_SWAP(a,b) do\
    {\
      a ^= b;\
      b ^= a;\
      a ^= b;\
    } while (0)

    // walk inwards from both ends of the string, 
    // swapping until we get to the middle
    while (str < end)
    {
      XOR_SWAP(*str, *end);
      str++;
      end--;
    }
#   undef XOR_SWAP
  }
}

ポインター (例:char * 、右から左へ char へのポインタとして読み取ります ) は、別の値のメモリ内の場所を参照するために使用される C のデータ型です。この場合、char の場所 保存されています。 逆参照できます * を前に付けることによるポインター 、その場所に格納されている値を提供します。したがって、str に格納されている値 *str です .

ポインタを使って簡単な算術演算を行うことができます。ポインターをインクリメント (またはデクリメント) するときは、単純にポインターを移動して、その型の値の次 (または前) のメモリ位置を参照します。 C では値が異なればバイト サイズも異なるため、異なる型のポインタをインクリメントすると、異なるバイト数だけポインタが移動する場合があります。

ここでは、1 つのポインターを使用して、最初の未処理の char を参照します。 文字列 (str ) と最後 (end) を参照する別の ).それらの値を交換します (*str および *end )、ポインタを内側に移動して文字列の中央に移動します。一度 str >= end 、どちらも同じ char を指している 、これは元の文字列が奇数の長さ (および中央の char 元に戻す必要はありません)、またはすべて処理済みです。

スワップを行うために、マクロを定義しました .マクロは、C プリプロセッサによって行われるテキスト置換です。それらは関数とは大きく異なり、違いを知ることが重要です。関数を呼び出すと、関数は指定された値のコピーを操作します。マクロを呼び出すと、単純にテキストの置換が行われるため、指定した引数が直接使用されます。

XOR_SWAP しか使っていないので マクロを定義するのはやり過ぎかもしれませんが、自分が何をしているかがより明確になりました。 C プリプロセッサがマクロを展開した後、while ループは次のようになります:

    while (str < end)
    {
      do { *str ^= *end; *end ^= *str; *str ^= *end; } while (0);
      str++;
      end--;
    }

マクロ引数は、マクロ定義で使用されるたびに 1 回表示されることに注意してください。これは非常に便利ですが、間違って使用するとコードが壊れる可能性があります。たとえば、インクリメント/デクリメント命令とマクロ呼び出しを次のように 1 行に圧縮した場合

      XOR_SWAP(*str++, *end--);

次に、これは

に展開されます
      do { *str++ ^= *end--; *end-- ^= *str++; *str++ ^= *end--; } while (0);

トリプル インクリメント/デクリメント操作を実行し、本来行うべきスワップを実際には実行しません。

この件に関しては、xor について知っておく必要があります。 (^ ) 意味。足し算、引き算、掛け算、割り算などの基本的な算術演算ですが、小学校では通常教えられません。 2 つの整数を加算のようにビットごとに結合しますが、キャリーオーバーは気にしません。 1^1 = 01^0 = 1 ,0^1 = 10^0 = 0 .

よく知られているトリックは、xor を使用して 2 つの値を交換することです。これは、xor の 3 つの基本プロパティ x ^ 0 = x によって機能します。 、 x ^ x = 0x ^ y = y ^ x すべての値 xy . 2 つの変数 a があるとします。 と b 最初に2つの値を格納していますvavb .

  // initially:
  // a == va
  // b == vb
  a ^= b;
  // now: a == va ^ vb
  b ^= a;
  // now: b == vb ^ (va ^ vb)
  //        == va ^ (vb ^ vb)
  //        == va ^ 0
  //        == va
  a ^= b;
  // now: a == (va ^ vb) ^ va
  //        == (va ^ va) ^ vb
  //        == 0 ^ vb
  //        == vb

したがって、値が交換されます。これには 1 つのバグがあります - a の場合 と b は同じ変数です:

  // initially:
  // a == va
  a ^= a;
  // now: a == va ^ va
  //        == 0
  a ^= a;
  // now: a == 0 ^ 0
  //        == 0
  a ^= a;
  // now: a == 0 ^ 0
  //        == 0

str < end以来 、これは上記のコードでは発生しないため、問題ありません。

正確性については心配していますが、エッジケースを確認する必要があります。 if (str) 行は NULL が与えられていないことを確認する必要があります 文字列へのポインタ。空の文字列 "" はどうですか ?まあ strlen("") == 0 、だから end を初期化します str - 1 として 、つまり while (str < end) 条件は決して真ではないので、何もしません。どちらが正しいですか。

探索する C がたくさんあります。楽しんでください!

更新: mmw は良い点をもたらします。それは、インプレースで動作するため、これを呼び出す方法に少し注意する必要があるということです.

 char stack_string[] = "This string is copied onto the stack.";
 inplace_reverse(stack_string);

stack_string 以来、これは正常に動作します 内容が指定された文字列定数に初期化される配列です。ただし

 char * string_literal = "This string is part of the executable.";
 inplace_reverse(string_literal);

コードが炎上し、実行時に死ぬ原因となります。それは string_literal のためです 実行可能ファイルの一部として保存されている文字列を指すだけです。これは通常、OS によって編集が許可されていないメモリです。より幸せな世界では、コンパイラはこれを認識し、コンパイルしようとするとエラーを吐き出し、string_literal を伝えます。 タイプ char const * である必要があります 内容を変更することはできません。しかし、これは私のコンパイラが住んでいる世界ではありません.

一部のメモリがスタックまたはヒープにあることを確認するためのハックがいくつかあります (したがって、編集可能です)。ただし、関数の呼び出し元に責任を負わせることができれば幸いです。私は彼らに、この関数はインプレース メモリ操作を行うことを伝えました。それを可能にする議論を私に与えるのは彼らの責任です。


ただの再配置と安全確認。未使用の戻り値の型も削除しました。これは安全でクリーンだと思います:

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

void reverse_string(char *str)
{
    /* skip null */
    if (str == 0)
    {
        return;
    }

    /* skip empty string */
    if (*str == 0)
    {
        return;
    }

    /* get range */
    char *start = str;
    char *end = start + strlen(str) - 1; /* -1 for \0 */
    char temp;

    /* reverse */
    while (end > start)
    {
        /* swap */
        temp = *start;
        *start = *end;
        *end = temp;

        /* move */
        ++start;
        --end;
    }
}


int main(void)
{
    char s1[] = "Reverse me!";
    char s2[] = "abc";
    char s3[] = "ab";
    char s4[] = "a";
    char s5[] = "";

    reverse_string(0);

    reverse_string(s1);
    reverse_string(s2);
    reverse_string(s3);
    reverse_string(s4);
    reverse_string(s5);

    printf("%s\n", s1);
    printf("%s\n", s2);
    printf("%s\n", s3);
    printf("%s\n", s4);
    printf("%s\n", s5);

    return 0;
}

strlen が 0 の場合に end が不適切なメモリ位置を指さないように編集されました。


(len/2) を入れることができます for ループでテストします:

for(i = 0,k=len-1 ; i < (len/2); i++,k--)
{
        temp = str[k];
        str[k] = str[i];
        str[i] = temp;

}