反転した文字列を保持する別のバッファを必要とせずに、C または C++ で文字列を反転するにはどうすればよいですか?
20 に答える
#include <algorithm>
std::reverse(str.begin(), str.end());
これは、C++ で最も簡単な方法です。
カーニハンとリッチーを読む
#include <string.h>
void reverse(char s[])
{
int length = strlen(s) ;
int c, i, j;
for (i = 0, j = length - 1; i < j; i++, j--)
{
c = s[i];
s[i] = s[j];
s[j] = c;
}
}
標準的なアルゴリズムは、始点/終点へのポインターを使用し、それらが途中で出会うか交差するまで内側に向かって歩きます。あなたが行くように交換してください。
逆 ASCII 文字列、つまり、すべての文字が 1 に収まる 0 で終わる配列char
。(または他の非マルチバイト文字セット)。
void strrev(char *head)
{
if (!head) return;
char *tail = head;
while(*tail) ++tail; // find the 0 terminator, like head+strlen
--tail; // tail points to the last real char
// head still points to the first
for( ; head < tail; ++head, --tail) {
// walk pointers inwards until they meet or cross in the middle
char h = *head, t = *tail;
*head = t; // swapping as we go
*tail = h;
}
}
// test program that reverses its args
#include <stdio.h>
int main(int argc, char **argv)
{
do {
printf("%s ", argv[argc-1]);
strrev(argv[argc-1]);
printf("%s\n", argv[argc-1]);
} while(--argc);
return 0;
}
既知の長さの整数配列に対して同じアルゴリズムが機能しtail = start + length - 1
ます。終了検出ループの代わりに使用するだけです。
(編集者注: この回答はもともと、この単純なバージョンにも XOR スワップを使用していました。この人気のある質問の将来の読者のために修正されました 。XOR スワップは強くお勧めしません。読みにくく、コードのコンパイル効率が低下します。xor-swap を x86-64 用に gcc -O3 でコンパイルすると、asm ループ本体がどれほど複雑になるか、Godbolt コンパイラ エクスプローラで確認できます。)
では、UTF-8 文字を修正しましょう...
(これは XOR スワップのことです。self とのスワップを避ける必要があることに注意してください。*p
と*q
が同じ場所である場合、a^a==0 でゼロにするためです。XOR スワップは、2 つの異なる場所を持つことに依存します。それぞれを一時的なストレージとして使用します。)
編集者注: tmp 変数を使用して、SWP を安全なインライン関数に置き換えることができます。
#include <bits/types.h>
#include <stdio.h>
#define SWP(x,y) (x^=y, y^=x, x^=y)
void strrev(char *p)
{
char *q = p;
while(q && *q) ++q; /* find eos */
for(--q; p < q; ++p, --q) SWP(*p, *q);
}
void strrev_utf8(char *p)
{
char *q = p;
strrev(p); /* call base case */
/* Ok, now fix bass-ackwards UTF chars. */
while(q && *q) ++q; /* find eos */
while(p < --q)
switch( (*q & 0xF0) >> 4 ) {
case 0xF: /* U+010000-U+10FFFF: four bytes. */
SWP(*(q-0), *(q-3));
SWP(*(q-1), *(q-2));
q -= 3;
break;
case 0xE: /* U+000800-U+00FFFF: three bytes. */
SWP(*(q-0), *(q-2));
q -= 2;
break;
case 0xC: /* fall-through */
case 0xD: /* U+000080-U+0007FF: two bytes. */
SWP(*(q-0), *(q-1));
q--;
break;
}
}
int main(int argc, char **argv)
{
do {
printf("%s ", argv[argc-1]);
strrev_utf8(argv[argc-1]);
printf("%s\n", argv[argc-1]);
} while(--argc);
return 0;
}
- はい、入力が中断された場合、これは場所の外で元気にスワップします。
- UNICODE での破壊行為に役立つリンク: http://www.macchiato.com/unicode/chart/
- また、0x10000 を超える UTF-8 はテストされていません (フォントがないようで、hexeditor を使用する忍耐もないようです)。
例:
$ ./strrev Räksmörgås ░▒▓○◔◑◕●
░▒▓○◔◑◕● ●◕◑◔○▓▒░
Räksmörgås sågrömskäR
./strrev verrts/.
文字列が null で終わる配列である一般的なケースを想定して、悪意のない C char
:
#include <stddef.h>
#include <string.h>
/* PRE: str must be either NULL or a pointer to a
* (possibly empty) null-terminated string. */
void strrev(char *str) {
char temp, *end_ptr;
/* If str is NULL or empty, do nothing */
if( str == NULL || !(*str) )
return;
end_ptr = str + strlen(str) - 1;
/* Swap the chars */
while( end_ptr > str ) {
temp = *str;
*str = *end_ptr;
*end_ptr = temp;
str++;
end_ptr--;
}
}
しばらく経ちましたが、どの本でこのアルゴリズムを学んだか覚えていませんが、非常に独創的で理解しやすいと思いました。
char input[] = "moc.wolfrevokcats";
int length = strlen(input);
int last_pos = length-1;
for(int i = 0; i < length/2; i++)
{
char tmp = input[i];
input[i] = input[last_pos - i];
input[last_pos - i] = tmp;
}
printf("%s\n", input);
slashdottirの厚意による、このアルゴリズムの視覚化:
Note that the beauty of std::reverse is that it works with char *
strings and std::wstring
s just as well as std::string
s
void strrev(char *str)
{
if (str == NULL)
return;
std::reverse(str, str + strlen(str));
}
NULL で終了するバッファを逆にする場合は、ここに掲載されているほとんどのソリューションで問題ありません。しかし、Tim Farley がすでに指摘したように、これらのアルゴリズムは、文字列が意味的にバイトの配列 (つまり、1 バイト文字列) であると仮定することが有効な場合にのみ機能します。これは間違った仮定だと思います。
たとえば、文字列 "año" (スペイン語で年) を考えてみましょう。
Unicode コード ポイントは 0x61、0xf1、0x6f です。
最も使用されているエンコーディングのいくつかを検討してください。
Latin1 / iso-8859-1 (1 バイト エンコーディング、1 文字は 1 バイト、またはその逆):
オリジナル:
0x61、0xf1、0x6f、0x00
逆行する:
0x6f、0xf1、0x61、0x00
結果はOKです
UTF-8:
オリジナル:
0x61、0xc3、0xb1、0x6f、0x00
逆行する:
0x6f、0xb1、0xc3、0x61、0x00
結果は意味不明で、不正な UTF-8 シーケンスです
UTF-16 ビッグ エンディアン:
オリジナル:
0x00、0x61、0x00、0xf1、0x00、0x6f、0x00、0x00
最初のバイトは NUL ターミネータとして扱われます。逆転は起こりません。
UTF-16 リトル エンディアン:
オリジナル:
0x61、0x00、0xf1、0x00、0x6f、0x00、0x00、0x00
2 番目のバイトは NUL ターミネータとして扱われます。結果は、文字「a」を含む文字列 0x61, 0x00 になります。
完全を期すために、さまざまなプラットフォームに文字列の表現があり、文字ごとのバイト数が文字によって異なることを指摘しておく必要があります。昔ながらのプログラマーは、これをDBCS (Double Byte Character Set)と呼んでいました。現代のプログラマーは、UTF-8 (およびUTF-16など) でこれに遭遇することがよくあります。このようなエンコーディングは他にもあります。
これらの可変幅エンコーディング スキームのいずれにおいても、ここに掲載されている単純なアルゴリズム (悪、非悪、またはそれ以外) はまったく正しく機能しません! 実際、文字列が判読不能になったり、そのエンコーディング スキームで不正な文字列になったりする可能性さえあります。いくつかの良い例については、 Juan Pablo Califano の回答を参照してください。
std::reverse() は、プラットフォームの標準 C++ ライブラリ (特に文字列反復子) の実装がこれを適切に考慮している限り、この場合でも機能する可能性があります。
別のC++の方法(私はおそらくstd :: reverse()を自分で使用しますが:)より表現力があり高速です)
str = std::string(str.rbegin(), str.rend());
Cの方法(多かれ少なかれ:))そしてお願いします、スワッピングのためのXORトリックに注意してください、コンパイラは時々それを最適化することができません。
char* reverse(char* s)
{
char* beg = s, *end = s, tmp;
while (*end) end++;
while (end-- > beg)
{
tmp = *beg;
*beg++ = *end;
*end = tmp;
}
return s;
} // fixed: check history for details, as those are interesting ones
#include <cstdio>
#include <cstdlib>
#include <string>
void strrev(char *str)
{
if( str == NULL )
return;
char *end_ptr = &str[strlen(str) - 1];
char temp;
while( end_ptr > str )
{
temp = *str;
*str++ = *end_ptr;
*end_ptr-- = temp;
}
}
int main(int argc, char *argv[])
{
char buffer[32];
strcpy(buffer, "testing");
strrev(buffer);
printf("%s\n", buffer);
strcpy(buffer, "a");
strrev(buffer);
printf("%s\n", buffer);
strcpy(buffer, "abc");
strrev(buffer);
printf("%s\n", buffer);
strcpy(buffer, "");
strrev(buffer);
printf("%s\n", buffer);
strrev(NULL);
return 0;
}
このコードは、次の出力を生成します。
gnitset
a
cba
文字列を元の場所に戻す再帰関数(余分なバッファなし、malloc)。
短くてセクシーなコード。悪い、悪いスタックの使用法。
#include <stdio.h>
/* Store the each value and move to next char going down
* the stack. Assign value to start ptr and increment
* when coming back up the stack (return).
* Neat code, horrible stack usage.
*
* val - value of current pointer.
* s - start pointer
* n - next char pointer in string.
*/
char *reverse_r(char val, char *s, char *n)
{
if (*n)
s = reverse_r(*n, s, n+1);
*s = val;
return s+1;
}
/*
* expect the string to be passed as argv[1]
*/
int main(int argc, char *argv[])
{
char *aString;
if (argc < 2)
{
printf("Usage: RSIP <string>\n");
return 0;
}
aString = argv[1];
printf("String to reverse: %s\n", aString );
reverse_r(*aString, aString, aString+1);
printf("Reversed String: %s\n", aString );
return 0;
}
Evgeny の K&R の回答が気に入っています。ただし、ポインターを使用したバージョンが表示されるのはうれしいことです。それ以外は、基本的に同じです。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *reverse(char *str) {
if( str == NULL || !(*str) ) return NULL;
int i, j = strlen(str)-1;
char *sallocd;
sallocd = malloc(sizeof(char) * (j+1));
for(i=0; j>=0; i++, j--) {
*(sallocd+i) = *(str+j);
}
return sallocd;
}
int main(void) {
char *s = "a man a plan a canal panama";
char *sret = reverse(s);
printf("%s\n", reverse(sret));
free(sret);
return 0;
}
GLib を使用している場合、g_strreverse()とg_utf8_strreverse()の 2 つの関数があります。
ATL/MFC を使用している場合はCString
、単に を呼び出しますCString::MakeReverse()
。
さらに別の:
#include <stdio.h>
#include <strings.h>
int main(int argc, char **argv) {
char *reverse = argv[argc-1];
char *left = reverse;
int length = strlen(reverse);
char *right = reverse+length-1;
char temp;
while(right-left>=1){
temp=*left;
*left=*right;
*right=temp;
++left;
--right;
}
printf("%s\n", reverse);
}