逆順に並び替える
- bitter_fox
- 記事: 607
- 登録日時: 14年前
- 住所: 大阪府
Re: 逆順に並び替える
前回の質問が放置されています。
http://dixq.net/forum/viewtopic.php?f=3&t=7620
質問の流れを見ると、しっかりと理解せずに次に行っているように思えますが、
理解せずにどんどん進んでも、理解できないことが増えるばっかりでなんら利点は無いように思います。(あやふやな知識ばかりが根づいてしまいます。)
これだと、iがゼロ及びnがゼロ以上の時、n-i<0は偽になってしまうのでfor内は一度も実行されません。
また、bは"\0"という文字列リテラルの先頭アドレスを指していて、そこに値を代入することは不正です。
さらに、
*b = ...;
だと常に代入先はbの指すアドレスになるので、for文に入れるには適していません。
それから、
*a-iも、aの指すアドレスに格納されている値からiを引いていることになってしまい、この場合はおかしいです。
あと、reverse関数を出ると、bは破棄されてしまいます。
http://dixq.net/forum/viewtopic.php?f=3&t=7620
質問の流れを見ると、しっかりと理解せずに次に行っているように思えますが、
理解せずにどんどん進んでも、理解できないことが増えるばっかりでなんら利点は無いように思います。(あやふやな知識ばかりが根づいてしまいます。)
これだと、iがゼロ及びnがゼロ以上の時、n-i<0は偽になってしまうのでfor内は一度も実行されません。
また、bは"\0"という文字列リテラルの先頭アドレスを指していて、そこに値を代入することは不正です。
さらに、
*b = ...;
だと常に代入先はbの指すアドレスになるので、for文に入れるには適していません。
それから、
*a-iも、aの指すアドレスに格納されている値からiを引いていることになってしまい、この場合はおかしいです。
あと、reverse関数を出ると、bは破棄されてしまいます。
Re: 逆順に並び替える
ちょっと違いますね。bitter_fox さんが書きました:また、bは"\0"という文字列リテラルの先頭アドレスを指していて、そこに値を代入することは不正です。
b はどこも指していません。ポインタの値がNULLであるのと"\0"という文字列リテラルの先頭アドレスを指すことは異なります。
"\0"という文字列リテラルを参照する(読み出しを行う)ことは可能ですが、NULLポインタは参照することさえ不正です。
そこに値を代入することが正しくないということは同じですけどね。
所望の関数をCで書けばこんな感じです。
void reverse
( char *a, /* 逆順にする文字列へのポインタ */
int n /* 文字列長 */
)
{
char *p1 = a; /* 交換位置(前側) */
char *p2 = a + n - 1; /* 交換位置(後側) */
for (; p1 < p2; ++p1, --p2) /* 交換位置が反転するまで */
{ /* 前と後ろを交換(swap)する */
char c = *p1; /* 前側を一旦退避 */
*p1 = *p2; /* 後ろを前に代入 */
*p2 = c; /* 退避しておいた前側の値を後ろに代入 */
}
}
while (p1 < p2) /* 交換位置が反転するまで */
{ /* 前と後ろを交換(swap)する */
char c = *p1; /* 前側を一旦退避 */
*p1++ = *p2; /* 後ろを前に代入 */
*p2-- = c; /* 退避しておいた前側の値を後ろに代入 */
}
C++ならstd::reverseで一発です。
- bitter_fox
- 記事: 607
- 登録日時: 14年前
- 住所: 大阪府
Re: 逆順に並び替える
質問者さんのコードでは、maru さんが書きました:ちょっと違いますね。bitter_fox さんが書きました:また、bは"\0"という文字列リテラルの先頭アドレスを指していて、そこに値を代入することは不正です。
b はどこも指していません。ポインタの値がNULLであるのと"\0"という文字列リテラルの先頭アドレスを指すことは異なります。
"\0"という文字列リテラルを参照する(読み出しを行う)ことは可能ですが、NULLポインタは参照することさえ不正です。
そこに値を代入することが正しくないということは同じですけどね。
となっており、NULLを処理系依存ですが、0と仮定すると、
となって、0は'\0'と同義なので、(ここも処理系依存かな・・・) になり、ここでの{}は""を使って書き表せるので になるので、「bは"\0"という文字列リテラルの先頭アドレスを指す」としたのですが、間違ってますでしょうか・・・?
Re: 逆順に並び替える
はい。間違っています。bitter_fox さんが書きました: になるので、「bは"\0"という文字列リテラルの先頭アドレスを指す」としたのですが、間違ってますでしょうか・・・?
集成体の初期化は{}で括ることが可能であり となっている場合は、char配列を0で初期化していることになりますが、 の場合、bはポインタであり、集成体ではないので、{}の意味が無くなり と等価です。
bitter_foxさんの間違いは が と等価であると考えたことです。ポインタの初期化と配列の初期化は異なります。
- bitter_fox
- 記事: 607
- 登録日時: 14年前
- 住所: 大阪府
Re: 逆順に並び替える
なるほど、集成体の時にだけ{}は意味を持つのですか・・・
ポインタと配列とに、そんな初期化の違いがあるとは知りませんでした、ご指摘ありがとうございます。
[hr][追記]
だから はコンパイルエラーになるんですね。。。
Re: 逆順に並び替える
bitter_foxさん,maruさんご指摘ありがとうございます。
>>bitter_foxさん
ポインタの使い方がよくわからないのでお手数ですが説明の方をお願いします。
>>maruさん
分かりやすいコードで説明していただきありがとうございます。
コードを書くにあたってポイントとなる部分はどこでしょうか?
>>bitter_foxさん
ポインタの使い方がよくわからないのでお手数ですが説明の方をお願いします。
>>maruさん
分かりやすいコードで説明していただきありがとうございます。
コードを書くにあたってポイントとなる部分はどこでしょうか?
Re: 逆順に並び替える
ポイントと言っても、このコードは特にポイントとなるような部分も無いかと思いますが初心者向けに解説してみます。beginner さんが書きました:>>maruさん
分かりやすいコードで説明していただきありがとうございます。
コードを書くにあたってポイントとなる部分はどこでしょうか?
このような問題は、メモリの配置を図(絵)にして、その中の値をどのように操作するかを書いてみることが重要です。
例えば今回の場合、操作すべき文字列 a は以下のように表わすことができます。 これを逆順にするのだから、a[0]とa[n-1]を交換、a[1]とa[n-2]を交換、これを順次真ん中まで実行すればよいでしょう(真ん中で止めないと元に戻ってしまう)。
交換するデータのインデックスは前の方が0, 1, 2, と増えていき、後ろのほうが n-1, n-2,と減っていきます。
従って、これをfor文で繰り返し実行すればよいでしょう。図示すると下記の通りです。
a a a a a
[0][1][2]....[n-2][n-1]
↑ ↑
+------------------+
a a a a a
[0][1][2]....[n-2][n-1]
↑ ↑
+----------+
...(以下省略)
交換に際しては、一時変数を使わないやり方もありますが、単純に片方のデータを一時変数に退避して順次入れ替えを行っていきます。
繰り返しの終了条件は繰り返し回数を n/2 としてもよいですが、前を指すポインタ(p1)が後ろを指すポインタ(p2)より後ろになれば終わりと判断しています。このようにする事で n が負の場合でも何もしないようにしています。
このように、まず変数に対してどのような操作を行うかを図を使って明確することが重要です。特に初心者は。
ついでに言っておくと、
ポインタ(pointer)はそのまま「(何かを)指し示す」変数です。ポインタを扱う場合、その先の実体をイメージしておく必要があります。beginner さんが書きました:>>bitter_foxさん
ポインタの使い方がよくわからないのでお手数ですが説明の方をお願いします。
例えばこの例でいえば reverse の引数である char* a はポインタですが、そのポインタはどこかに char の配列があり、そこを指し示しているということを意識する必要があります。実際にには呼び出し元でその配列を確保する必要があります。その確保の方法として静的に確保するのか動的に確保するのかの違いがます。その辺の説明が別トピックにある
なんですけど、それが理解できないとするとちょっと難しいかも?って感じです。
最後に編集したユーザー maru on 2011年1月24日(月) 17:34 [ 編集 1 回目 ]
Re: 逆順に並び替える
ポインタ(領域サイズは固定)を初期化するのと、配列(領域サイズは宣言ごとに異なる)を初期化するのでは違いがあって当然です。bitter_fox さんが書きました: ポインタと配列とに、そんな初期化の違いがあるとは知りませんでした、ご指摘ありがとうございます。
・ポインタは必要な型を持つ変数やリテラルへのポインタで初期化される。
・配列、構造体、共用体などの集成体はその要素を{}で括ることで初期化される。ただし文字列(文字配列)の場合は文字列リテラルで初期化できる。
文字列の場合、 とすることで、文字列の初期化ができますが、上は定数文字列リテラル(プログラムのどこかに定義される文字列)へのポインタでpを初期化(領域確保するのはポインタ分のみ)するのに対し、strは必要な文字列の領域を確保してそこを初期化します。
つまり初期化の書き方が同じであってもやっていることは全く異なります。
ポインタと配列名はほぼ等価ですが、厳密に考えると異なる場合も有ります。
以下は蛇足ですが、上記の例でいうと
*p = 'A'; /* 定数文字列リテラルへの書き込みなので実行時エラー */
p[0] = 'B'; /* ポインタを配列として書き込み、上を同じく実行時エラー */
*str = 'C'; /* 文字配列名をポインタとして書き込み。OK */
str[0] = 'D'; /* 文字配列を普通に書き込み。OK */
最近は定数文字列リテラルへのポインタはconstを付けて とします。こうすることで をコンパイル時にエラーとすることができるようになります。
ですね。この場合、 とすれば、文字型変数へのポインタ配列なので、{"str", "ing"}を二つの文字列を持つ配列として処理することができます。
/* 多分、充分ご承知のことと思いますが。 */
- bitter_fox
- 記事: 607
- 登録日時: 14年前
- 住所: 大阪府
Re: 逆順に並び替える
流れからすると動的確保の問題なので動的確保を使ったものを作りました。また、beginnerさんが書かれたコードを元にしています。
#include<stdio.h>
#include<string.h>
void reverse(char *,int );
int main(void) {
char a[100];
int n;
printf("文字列を入力してください\n");
scanf("%s",a);
n=strlen(a);
printf("逆順に並び替えます\n");
reverse(a,n);
printf("%s\n",a);
}
void reverse(char *a,int n)
{
int i;
char *b = (char*)malloc((n+1) * sizeof(char)); // 末尾のヌル文字も考えて+1
if (b == NULL) // メモリエラーチェック
{
printf("Error\n");
return;
}
strcpy(b, a); // aをbにコピー
for(i=0;i<n;++i) // 0から文字列長まで
a[i] = b[n-i-1]; // iの所にn-i-1を代入
// n-i-1はiの対極に位置する
free(b); // bを解放
}
Re: 逆順に並び替える
「配列を使わない」=「動的確保」と考えるのは少し短絡的かと。bitter_fox さんが書きました:流れからすると動的確保の問題なので動的確保を使ったものを作りました。また、beginnerさんが書かれたコードを元にしています。
動的確保はむしろ別トピックのほうの問題で、main側で処理すべき問題かと思います。
どちらかと言うと、ここでは上位から渡されたポインタを使ってその要素を操作することを考えるべきでしょう。
bitter_foxさんのコードも配列を使わないという目的にはあっているでしょうが、本来の目的とはちょっと違っていると思われます。
つまり、ここでのポイントはポインタを使ってメモリの操作をするってことではないでしょうか。