逆順に並び替える

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
beginner

逆順に並び替える

#1

投稿記事 by beginner » 14年前

文字列を入力させて関数を用いて(関数内では配列を使用できない)それを逆の順序に並び替えるというプログラムをつくっているのですが、
逆順にうまく並び替えることができません。
どのようにしたらよろしいのですか?

コード:

#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={NULL};
	for(i=0;n-i<0;--i)
	*b=*a-i;
}	

アバター
bitter_fox
記事: 607
登録日時: 14年前
住所: 大阪府

Re: 逆順に並び替える

#2

投稿記事 by bitter_fox » 14年前

前回の質問が放置されています。
http://dixq.net/forum/viewtopic.php?f=3&t=7620

質問の流れを見ると、しっかりと理解せずに次に行っているように思えますが、
理解せずにどんどん進んでも、理解できないことが増えるばっかりでなんら利点は無いように思います。(あやふやな知識ばかりが根づいてしまいます。)

コード:

void reverse(char *a,int n) {
    int i;
    char *b={NULL};
    for(i=0;n-i<0;--i)
    *b=*a-i;
}
これだと、iがゼロ及びnがゼロ以上の時、n-i<0は偽になってしまうのでfor内は一度も実行されません。

また、bは"\0"という文字列リテラルの先頭アドレスを指していて、そこに値を代入することは不正です。

さらに、
*b = ...;
だと常に代入先はbの指すアドレスになるので、for文に入れるには適していません。

それから、
*a-iも、aの指すアドレスに格納されている値からiを引いていることになってしまい、この場合はおかしいです。

あと、reverse関数を出ると、bは破棄されてしまいます。

maru
記事: 150
登録日時: 14年前

Re: 逆順に並び替える

#3

投稿記事 by maru » 14年前

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;       /* 退避しておいた前側の値を後ろに代入 */
    }
}
for文をwhile文にすると以下の通りです。

コード:

    while (p1 < p2) /* 交換位置が反転するまで */
    {   /* 前と後ろを交換(swap)する */
        char c = *p1;  /* 前側を一旦退避 */
        *p1++ = *p2;   /* 後ろを前に代入 */
        *p2-- = c;     /* 退避しておいた前側の値を後ろに代入 */
    }
どちらでもお好きなほうを。

C++ならstd::reverseで一発です。

コード:

#include <algorithm>
void reverse(char * p, int  n)
{
    std::reverse (p, p + n);
}

アバター
bitter_fox
記事: 607
登録日時: 14年前
住所: 大阪府

Re: 逆順に並び替える

#4

投稿記事 by bitter_fox » 14年前

maru さんが書きました:
bitter_fox さんが書きました:また、bは"\0"という文字列リテラルの先頭アドレスを指していて、そこに値を代入することは不正です。
ちょっと違いますね。
b はどこも指していません。ポインタの値がNULLであるのと"\0"という文字列リテラルの先頭アドレスを指すことは異なります。
"\0"という文字列リテラルを参照する(読み出しを行う)ことは可能ですが、NULLポインタは参照することさえ不正です。
そこに値を代入することが正しくないということは同じですけどね。
質問者さんのコードでは、

コード:

    char *b={NULL};
となっており、NULLを処理系依存ですが、0と仮定すると、

コード:

    char *b = {0}
となって、0は'\0'と同義なので、(ここも処理系依存かな・・・)

コード:

    char *b = {'\0'}
になり、ここでの{}は""を使って書き表せるので

コード:

    char *b = "\0"
になるので、「bは"\0"という文字列リテラルの先頭アドレスを指す」としたのですが、間違ってますでしょうか・・・?

maru
記事: 150
登録日時: 14年前

Re: 逆順に並び替える

#5

投稿記事 by maru » 14年前

bitter_fox さんが書きました: になるので、「bは"\0"という文字列リテラルの先頭アドレスを指す」としたのですが、間違ってますでしょうか・・・?
はい。間違っています。
集成体の初期化は{}で括ることが可能であり

コード:

    char str[] ={NULL};
となっている場合は、char配列を0で初期化していることになりますが、

コード:

    char *b = {NULL};
の場合、bはポインタであり、集成体ではないので、{}の意味が無くなり

コード:

    char *b = NULL;
と等価です。

bitter_foxさんの間違いは

コード:

    char *b = {'\0'}

コード:

    char *b = "\0"
と等価であると考えたことです。ポインタの初期化と配列の初期化は異なります。

アバター
bitter_fox
記事: 607
登録日時: 14年前
住所: 大阪府

Re: 逆順に並び替える

#6

投稿記事 by bitter_fox » 14年前

maru さんが書きました: 集成体の初期化は{}で括ることが可能であり

コード:

    char *b = {NULL};
の場合、bはポインタであり、集成体ではないので、{}の意味が無くなり

コード:

    char *b = NULL;
と等価です。
なるほど、集成体の時にだけ{}は意味を持つのですか・・・

maru さんが書きました: bitter_foxさんの間違いは

コード:

    char *b = {'\0'}

コード:

    char *b = "\0"
と等価であると考えたことです。ポインタの初期化と配列の初期化は異なります。
ポインタと配列とに、そんな初期化の違いがあるとは知りませんでした、ご指摘ありがとうございます。

[hr][追記]
だから

コード:

char **str = {"str", "ing"};
はコンパイルエラーになるんですね。。。

beginner

Re: 逆順に並び替える

#7

投稿記事 by beginner » 14年前

bitter_foxさん,maruさんご指摘ありがとうございます。
>>bitter_foxさん
ポインタの使い方がよくわからないのでお手数ですが説明の方をお願いします。

>>maruさん
分かりやすいコードで説明していただきありがとうございます。
コードを書くにあたってポイントとなる部分はどこでしょうか?

maru
記事: 150
登録日時: 14年前

Re: 逆順に並び替える

#8

投稿記事 by maru » 14年前

beginner さんが書きました:>>maruさん
分かりやすいコードで説明していただきありがとうございます。
コードを書くにあたってポイントとなる部分はどこでしょうか?
ポイントと言っても、このコードは特にポイントとなるような部分も無いかと思いますが初心者向けに解説してみます。

このような問題は、メモリの配置を図(絵)にして、その中の値をどのように操作するかを書いてみることが重要です。
例えば今回の場合、操作すべき文字列 a は以下のように表わすことができます。

コード:

 a  a  a       a    a 
[0][1][2]....[n-2][n-1]
これを逆順にするのだから、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]
    ↑         ↑
    +----------+
...(以下省略)
これをコード化すればよいわけですが、文字列がポインタで渡されているので配列で処理するよりポインタのまま処理したほうが簡単なので前側の位置を char* p1 後ろ側の位置をchar* p2としています。

コード:

 a  a  a       a    a 
[0][1][2]....[n-2][n-1]
↑                  ↑
 +------------------+
*p1                *p2
後は*p1と*p2を繰り返しの内部で交換し、ポインタを更新(p1を後ろにずらし、p2を前にずらす)すればよいだけです。
交換に際しては、一時変数を使わないやり方もありますが、単純に片方のデータを一時変数に退避して順次入れ替えを行っていきます。
繰り返しの終了条件は繰り返し回数を n/2 としてもよいですが、前を指すポインタ(p1)が後ろを指すポインタ(p2)より後ろになれば終わりと判断しています。このようにする事で n が負の場合でも何もしないようにしています。

このように、まず変数に対してどのような操作を行うかを図を使って明確することが重要です。特に初心者は。

ついでに言っておくと、
beginner さんが書きました:>>bitter_foxさん
ポインタの使い方がよくわからないのでお手数ですが説明の方をお願いします。
ポインタ(pointer)はそのまま「(何かを)指し示す」変数です。ポインタを扱う場合、その先の実体をイメージしておく必要があります。
例えばこの例でいえば reverse の引数である char* a はポインタですが、そのポインタはどこかに char の配列があり、そこを指し示しているということを意識する必要があります。実際にには呼び出し元でその配列を確保する必要があります。その確保の方法として静的に確保するのか動的に確保するのかの違いがます。その辺の説明が別トピックにある
beginner さんが書きました:>>maruさん
サイトのURLです。
http://ja.wikipedia.org/wiki/Malloc
なんですけど、それが理解できないとするとちょっと難しいかも?って感じです。
最後に編集したユーザー maru on 2011年1月24日(月) 17:34 [ 編集 1 回目 ]

maru
記事: 150
登録日時: 14年前

Re: 逆順に並び替える

#9

投稿記事 by maru » 14年前

bitter_fox さんが書きました: ポインタと配列とに、そんな初期化の違いがあるとは知りませんでした、ご指摘ありがとうございます。
ポインタ(領域サイズは固定)を初期化するのと、配列(領域サイズは宣言ごとに異なる)を初期化するのでは違いがあって当然です。
・ポインタは必要な型を持つ変数やリテラルへのポインタで初期化される。
・配列、構造体、共用体などの集成体はその要素を{}で括ることで初期化される。ただし文字列(文字配列)の場合は文字列リテラルで初期化できる。

文字列の場合、

コード:

char *p = "Pointer to char type";
char str[] = "Array of char type";
とすることで、文字列の初期化ができますが、上は定数文字列リテラル(プログラムのどこかに定義される文字列)へのポインタでpを初期化(領域確保するのはポインタ分のみ)するのに対し、strは必要な文字列の領域を確保してそこを初期化します。
つまり初期化の書き方が同じであってもやっていることは全く異なります。
ポインタと配列名はほぼ等価ですが、厳密に考えると異なる場合も有ります。

以下は蛇足ですが、上記の例でいうと

コード:

*p = 'A';	/* 定数文字列リテラルへの書き込みなので実行時エラー */
p[0] = 'B';	/* ポインタを配列として書き込み、上を同じく実行時エラー */
*str = 'C';	/* 文字配列名をポインタとして書き込み。OK */
str[0] = 'D';	/* 文字配列を普通に書き込み。OK */
というように同じ処理を書いても結果が異なります。

最近は定数文字列リテラルへのポインタはconstを付けて

コード:

const char *p = "Pointer to char type";
とします。こうすることで

コード:

*p = 'A';	/* 定数文字列リテラルへの書き込みなので実行時エラー */
p[0] = 'B';	/* ポインタを配列としてアクセス、上を同じく実行時エラー */
をコンパイル時にエラーとすることができるようになります。
bitter_fox さんが書きました: [追記]
だから

コード:

char **str = {"str", "ing"};
はコンパイルエラーになるんですね。。。
ですね。この場合、

コード:

char *str[] = {"str", "ing"};
とすれば、文字型変数へのポインタ配列なので、{"str", "ing"}を二つの文字列を持つ配列として処理することができます。
/* 多分、充分ご承知のことと思いますが。 */

beginner

Re: 逆順に並び替える

#10

投稿記事 by beginner » 14年前

maruさん
丁寧に説明していただき分かりやすかったです。
また質問すると思うのでよろしくお願いします。

アバター
bitter_fox
記事: 607
登録日時: 14年前
住所: 大阪府

Re: 逆順に並び替える

#11

投稿記事 by bitter_fox » 14年前

流れからすると動的確保の問題なので動的確保を使ったものを作りました。また、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を解放
}


maru
記事: 150
登録日時: 14年前

Re: 逆順に並び替える

#12

投稿記事 by maru » 14年前

bitter_fox さんが書きました:流れからすると動的確保の問題なので動的確保を使ったものを作りました。また、beginnerさんが書かれたコードを元にしています。
「配列を使わない」=「動的確保」と考えるのは少し短絡的かと。
動的確保はむしろ別トピックのほうの問題で、main側で処理すべき問題かと思います。
どちらかと言うと、ここでは上位から渡されたポインタを使ってその要素を操作することを考えるべきでしょう。
bitter_foxさんのコードも配列を使わないという目的にはあっているでしょうが、本来の目的とはちょっと違っていると思われます。

つまり、ここでのポイントはポインタを使ってメモリの操作をするってことではないでしょうか。

閉鎖

“C言語何でも質問掲示板” へ戻る