web-dev-qa-db-ja.com

Cで文字列を反転する

私は逆文字列プログラムを開発しました。これを行うためのより良い方法があり、私のコードに潜在的な問題があるかどうか疑問に思っています。私はCのいくつかの高度な機能を練習しようとしています。

char* reverse_string(char *str)
{
    char temp;
    size_t len = strlen(str) - 1;
    size_t i;
    size_t k = len;

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

        /* As 2 characters are changing place for each cycle of the loop
           only traverse half the array of characters */
        if(k == (len / 2))
        {
            break;
        }
    }
}
34
ant2009

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
  }
}
_

Apointer(eg _char *_、右から左にpointer to charとして読み取る)は、別の値のメモリ内の場所を参照するために使用されるCのデータ型です。この場合、charが格納されている場所。dereferenceポインターの前に_*_を付けることで、その場所に格納されている値を取得できます。したがって、strに格納される値は_*str_です。

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

ここでは、1つのポインターを使用して文字列の最初の未処理のcharstr)を参照し、別のポインターを使用して最後の(end)を参照します。値(_*str_と_*end_)を交換し、ポインターを文字列の中央に移動します。 _str >= end_になったら、両方とも同じcharを指します。これは、元の文字列の長さが奇数であること(および中央のcharを逆にする必要がないこと)、またはすべてを処理したことを意味します。

スワッピングを行うために、macroを定義しました。マクロは、Cプリプロセッサによって実行されるテキスト置換です。それらは関数とは非常に異なり、違いを知ることが重要です。関数を呼び出すと、関数は指定した値のコピーに対して機能します。マクロを呼び出すと、単にテキストの置換が行われるだけです。そのため、指定した引数は直接使用されます。

_XOR_SWAP_マクロは一度しか使用しなかったため、おそらく定義するのはやり過ぎだったかもしれませんが、何をしているのかがより明確になりました。 Cプリプロセッサがマクロを展開すると、whileループは次のようになります。

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

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

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

次に、これは

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

tripleインクリメント/デクリメント操作があり、実際に行うはずのスワップを行いません。

このテーマについては、xor(_^_)の意味を知っておく必要があります。それは基本的な算術演算です-加算、減算、乗算、除算のようなものですが、通常小学校では教えられません。加算のように、2つの整数をビットごとに組み合わせますが、キャリーオーバーは気にしません。 _1^1 = 0_、_1^0 = 1_、_0^1 = 1_、_0^0 = 0_。

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

 //最初:
 // a == va
 // b == vb
 a ^ = b; 
 //現在:a == va ^ vb
 b ^ = a; 
 //現在:b == vb ^(va ^ vb)
 // == va ^(vb ^ vb)
 // == va ^ 0 
 // == va
 a ^ = b; 
 //現在:a ==(va ^ vb)^ va
 // ==(va ^ va)^ vb
 // == 0 ^ vb
 // == vb

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

 //最初:
 // a == va
 a ^ = a; 
 //現在:a == va ^ va
 // == 0 
 a ^ = a; 
 //現在:a == 0 ^ 0 
 // == 0 
 a ^ = a; 
 //現在:a == 0 ^ 0 
 // == 0 

_str < end_なので、これは上記のコードでは発生しないため、大丈夫です。

正確性を懸念する一方で、Edgeのケースを確認する必要があります。 if (str)行は、stringのNULLポインターが与えられていないことを確認する必要があります。空の文字列_""_はどうですか? strlen("") == 0であるため、endを_str - 1_として初期化します。つまり、while (str < end)条件は決して真ではないため、何もしません。どちらが正しい。

探索するCの束があります。それを楽しんでください!

Update: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 *_である必要があることを伝えます。しかし、これは私のコンパイラが住んでいる世界ではありません。

いくつかのメモリがスタック上またはヒープ内にある(したがって編集可能である)ことを確認しようとするハックがいくつかありますが、それらは必ずしも移植性があるわけではなく、かなり見苦しいかもしれません。ただし、これに対する責任を関数呼び出し元に任せてうれしいです。この関数はメモリ操作を適切に行うことを彼らに伝えました。それを可能にする引数を私に与えるのは彼らの責任です。

61
rampion

ただ再配置、および安全性チェック。また、使用されていない戻り型も削除しました。これは次のように安全でクリーンだと思います:

#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が不良メモリ位置を指し示しないように編集されました。

24
GManNickG

(len/2) forループでテストします。

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

}
16
Michael Burr

この完全なプログラムは、私がそれをどのように行うかを示しています。あなたのほとんどがあなたの母親の目にキラキラ光っていたとき、私はCを書いていたので覚えておいてください、それはそれが古い学校で、仕事をして、長いvar-names-are-for-wimpsです。必要に応じて修正し、コードの正確性に興味があります。

NULL、空の文字列、すべての文字列サイズを処理します。私は最大サイズの文字列(max(size_t))でテストしていませんが、動作するはずです。もしあなたがそのような大きな文字列を処理しているなら、とにかく正気です:-)

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

char *revStr (char *str) {
    char tmp, *src, *dst;
    size_t len;
    if (str != NULL)
    {
        len = strlen (str);
        if (len > 1) {
            src = str;
            dst = src + len - 1;
            while (src < dst) {
                tmp = *src;
                *src++ = *dst;
                *dst-- = tmp;
            }
        }
    }
    return str;
}

char *str[] = {"", "a", "ab", "abc", "abcd", "abcde"};

int main(int argc, char *argv[]) {
    int i;
    char s[10000];
    for (i=0; i < sizeof(str)/sizeof(str[0]); i++) {
        strcpy (s, str[i]);
        printf ("'%s' -> '%s'\n", str[i], revStr(s));
    }
    return 0;
}

その出力は次のとおりです。

'' -> ''
'a' -> 'a'
'ab' -> 'ba'
'abc' -> 'cba'
'abcd' -> 'dcba'
'abcde' -> 'edcba'
14
paxdiablo

これを試して:

reverse_string(NULL);
reverse_string("");
7
RossFabricant

誰もポインターを使用しなくなりましたか?

void inplace_rev( char * s ) {
  char t, *e = s + strlen(s);
  while ( --e > s ) { t = *s;*s++=*e;*e=t; }
}

編集:申し訳ありませんが、上記のXOR例...

6
Sanjaya R

Forループ宣言を変更して、コードを短くすることができます。

char* reverse_string(char *str)
{
    char temp;
    size_t len = strlen(str) - 1;
    size_t stop = len/2;
    size_t i,k;

    for(i = 0, k = len; i < stop; i++, k--)
    {
        temp = str[k];
        str[k] = str[i];
        str[i] = temp;
    }
    return str;
}
6
Ben Straub

Returnステートメントが表示されず、入力文字列を変更しているため、プログラマーにとって問題になる可能性があります。入力文字列を不変にすることができます。

また、これは難しいかもしれませんが、len/2はIMOで1回だけ計算する必要があります。

それ以外は、rossfabricantが言及した問題のケースを処理する限り、機能します。

4
James Black
void reverse(char *s)
{
  char *end,temp;
  end = s;
  while(*end != '\0'){
    end++;
  }
  end--;  //end points to last letter now
  for(;s<end;s++,end--){
    temp = *end;
    *end = *s;
    *s = temp; 
  }
}
4
kevin
rev {
int len = strlen(str)-1;
for ( int i =0; i< len/2 ; i++ ) {
        char t = str[i];
        str[i] = str[len-i];
        str[len-i] = t;
        }

}
2
resultsway
bool reverse_string(char* str)
{
    // Make sure str is reversible
    if (!str ||  strlen(str) < 2)
        return false;

    char* first = str;
    char* last = str + strlen(str) - 1; // Minus 1 accounts for Index offset
    char temp;

    do{
        temp = *first;
        *first = *last;
        *last = temp;
    }
    while (++first < --last); // Update Pointer Addresses and check for equality

    return true;
}

このソリューションは、いくつかの変更を加えたGManNickGの投稿に基づいています。 strlen操作の前に!strが評価されない場合、最初の論理ステートメントは危険です(NULL ptrの場合)。これは私のコンパイラには当てはまりません。 do-whileループの素敵な例だから、このコードを追加すると思った。

2
TheRealSheldon

私の2セント:

_/* Reverses n characters of a string and adds a '\0' at the end */
void strnrev (char *txt, size_t len) {
    size_t idx;
    for (idx = len >> 1; idx > 0; idx--) {
        txt[len] = txt[idx - 1];
        txt[idx - 1] = txt[len - idx];
        txt[len - idx] = txt[len];
    }
    txt[len] = '\0';
}

/* Reverses a null-terminated string */
void strrev (char *txt) {
    size_t len = 0;
    while (txt[len++]);
    strnrev(txt, --len);
}
_

テスト#1strrev()

_char string[] = "Hello world!";
strrev(string);
printf("%s\n", string); // Displays "!dlrow olleH"
_

テスト#2strnrev()

_char string[] = "Hello world!";
strnrev(string, 5);
printf("%s\n", string); // Displays "olleH"
_
1
madmurphy

簡単でシンプルなコードxD

void strrev (char s[]) {

int i;
int dim = strlen (s);
char l;

for (i = 0; i < dim / 2; i++) {
    l = s[i];
    s[i] = s[dim-i-1];
    s[dim-i-1] = l;
    }

}
1
Dev_God

あなたは空想を得たいと言うので、おそらくあなたは XORスワップ を使用してキャラクターを交換したいと思うでしょう。

1
chaos

途中で中断するのではなく、単にループを短くする必要があります。

size_t length = strlen(str);
size_t i;

for (i = 0; i < (length / 2); i++)
{
    char temp = str[length - i - 1];
    str[length - i - 1] = str[i];
    str[i] = temp;
}
1
dreamlax
Here is my shot which will handle all the cases 
char *p ="KDLAKDADKADAD"
char p[] = "lammdlamldaldladadada"
also empty string 

#include<stdio.h>
#include<string.h>enter code here
#include<stdlib.h>
char *string_reverse(char *p);
int main()
{

        char *p = " Deepak@klkaldkaldkakdoroorerr";
        char *temp = string_reverse(p);
        printf("%s", temp);
}
char *  string_reverse( char *p )
{

        if(*p == '\0')
        {
                printf("No charecters are present \n");
                return 0;
        }
        int count = strlen(p)+1;
        int mid = strlen(p)/2;
        char *q  = (char *)malloc(count * sizeof(char));
        if( q )
        {
                strcpy(q,p);
                char *begin,*end,temp;
                begin = q ;
                end = q+strlen(p)-1  ;
                int i = 0;
                while( i < mid/2 )
                {
                        temp = *end;
                        *end = *begin;
                        *begin = temp;
                        begin++;
                        end--;
                        i++;
                }
                return q;
        }
        else
        {

                printf("Memory Not allocated ");
        }
        free(q);
}
1
#include <stdio.h>

int main()    
{

    char string[100];
    int i;

    printf("Enter a string:\n");
    gets(string);
    printf("\n");
    for(i=strlen(string)-1;i>-1;i--)

    printf("%c",string[i]);
}
1
Kevin

コードは不必要に複雑に見えます。これが私のバージョンです:

void strrev(char* str) { 
    size_t len = strlen(str);
    char buf[len]; 

    for (size_t i = 0; i < len; i++) { 
        buf[i] = str[len - 1 - i]; 
    }; 

    for (size_t i = 0; i < len; i++) { 
        str[i] = buf[i]; 
    }
}
1
Alex Richardson

このポインター演算を試すことができます:

void revString(char *s)
{
  char *e = s; while(*e){ e++; } e--;
  while(e > s){ *s ^= *e; *e ^= *s; *s++ ^= *e--; }
}

それはいい質問です ant2009 。スタンドアロン関数を使用して、文字列を反転できます。コードは...

#include <stdio.h>
#define MAX_CHARACTERS 99

int main( void );
int strlen( char __str );

int main() {
    char *str[ MAX_CHARACTERS ];
    char *new_string[ MAX_CHARACTERS ];
    int i, j;

    printf( "enter string: " );
    gets( *str );

    for( i = 0; j = ( strlen( *str ) - 1 ); i < strlen( *str ), j > -1; i++, j-- ) {
        *str[ i ] = *new_string[ j ];
    }
    printf( "Reverse string is: %s", *new_string" );
    return ( 0 );
}

int strlen( char __str[] ) {
    int count;
    for( int i = 0; __str[ i ] != '\0'; i++ ) {
         ++count;
    }
    return ( count );
}
1
SDAH

これが私のショットです。標準のstrcpyパターンを使用するだけでスワップを回避します。

char *string_reverse(char *dst, const char *src)
{
    if (src == NULL) return NULL;

    const char *src_start = src;
    char *dst_end = dst + strlen(src);
    *dst_end = '\0';

    while ((*--dst_end = *src_start++)) { ; }

    return dst;
}

そしてここでは実行例

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

int main() 
{
    char *data = "hello world";
    int length=strlen(data);
    char bytes[length];
    int n=0;
    while(n<=length)
    {
       bytes[n] = data[length-n-1];
       n++;
    }
    printf("%s\n", bytes);
    return 0;   
}
1
kyle k
/* Author: Siken Dongol */
#include <stdio.h>

int strLength(char *input) {
    int i = 0;
    while(input[i++]!='\0');
    return --i;
}

int main()
{
    char input[] = "Siken Man Singh Dongol";

    int len = strLength(input);
    char output[len];

    int index = 0;
    while(len >= 0) {
        output[index++] = input[--len];
    }

    printf("%s\n",input);
    printf("%s\n",output);
    return 0;
}
1
siken.dongol