[解決済み] 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;
}
}
}
解決方法は?
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
}
}
A
ポインタ
(例
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行にまとめています。
XOR_SWAP(*str++, *end--);
そして、これは次のように展開されます。
do { *str++ ^= *end--; *end-- ^= *str++; *str++ ^= *end--; } while (0);
どれが トリプル というのは、インクリメント/デクリメント操作のことであり、実際には は、本来行うべきスワップを行う。
この話題に触れたついでに
xor
(
^
)を意味します。 それは、基本的な
足し算、引き算、掛け算、割り算のような算数の演算です。
小学校ではあまり習わない。 これは2つの整数をビットごとに結合する
- 足し算のようなものですが、キャリーオーバーを気にすることはありません。
1^1 = 0
,
1^0 = 1
,
0^1 = 1
,
0^0 = 0
.
よく知られているのは、xorを使って2つの値を入れ替えるという方法です。 これは、次の3つの基本的な理由によるものです。
xorの特性です。
x ^ 0 = x
,
x ^ x = 0
と
x ^ y = y ^ x
すべての値に対して
x
と
y
. ということで、2つの
変数
a
と
b
という2つの値が初期状態で格納されている
va
そして
vb
.
// 最初は // a == v <サブ a // b == v <サブ b a ^= b; // 現在: a == v <サブ a ^ v <サブ b b ^= a; // 今: b == v <サブ b ^ (v <サブ a ^ v <サブ b ) // == v <サブ a ^ (v <サブ b ^ v <サブ b ) // == v <サブ a ^ 0 // == v <サブ a a ^= b; // 今: a == (v <サブ a ^ v <サブ b ) ^ v <サブ a // == (v <サブ a ^ v <サブ a ) ^ v <サブ b // == 0 ^ v <サブ b // == v <サブ b
つまり、値が入れ替わっているのです。 これには一つバグがあります。
a
と
b
は同じ変数です。
// 最初は // a == v <サブ a a ^= a; // 現在: a == v <サブ a ^ v <サブ a // == 0 a ^= a; // 現在: a == 0 ^ 0 // == 0 a ^= a; // 現在: 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 *
中身を変更することができないからです。 しかし、これは私のコンパイラが生きている世界ではありません。
あるメモリがスタックやヒープにある(つまり編集可能である)ことを確認するために試せるハックはいくつかありますが、必ずしもポータブルではなく、かなり醜いことになるかもしれません。 しかし、私はこの責任を関数の呼び出し元に投げつけても構わないと考えています。 私はこの関数がインプレースメモリ操作を行うことを彼らに伝えました、それを可能にする引数を私に与えることは彼らの責任です。
関連
-
[解決済み】MPI通信でMPI_Bcastを使用する場合
-
[解決済み] JavaScriptで文字列が部分文字列を含むかどうかを確認する方法は?
-
[解決済み] C#のStringとstringの違いは何ですか?
-
[解決済み] JavaでInputStreamを読み込んでStringに変換するにはどうすればよいですか?
-
[解決済み] なぜパスワードにはStringではなくchar[]が好まれるのですか?
-
[解決済み] Pythonには文字列の'contains'サブストリングメソッドがありますか?
-
[解決済み] 文字列の単語を反復処理するにはどうすればよいですか?
-
[解決済み] バイトを文字列に変換する
-
[解決済み】JavaScriptで文字列の出現箇所をすべて置換する方法
-
[解決済み】大文字・小文字を区別しない「Contains(string)
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】Valgrind - strcpyのサイズ1の無効な書き込み
-
[解決済み】単項演算子「*」の型が無効(「int」がある)C言語でのエラー
-
[解決済み】警告:互換性のないポインタ型からの代入
-
[解決済み】GCC Cコードで静的宣言が非静的宣言に続くことを解決するには?
-
[解決済み】cudamalloc()の使用。) なぜダブルポインタなのか?
-
[解決済み】ヒープ割り当てで初期化されていない値が作成された
-
[解決済み】コンパイラの警告 - 真理値として使用される代入の周囲に括弧を付けることを推奨する
-
[解決済み】LinuxのI_PUSHに相当するもの
-
[解決済み】未定義参照 makefile が間違っているのかも?
-
[解決済み】C言語のフォーマット文字列でデータ引数が使用されない [重複]。