qsortに関する演習問題の解答で、以下のようなコードがあります。
C
1/*--- xおよびyが指す文字列の比較関数 ---*/ 2static int pstrcmp(const void *x, const void *y) 3{ 4 return strcmp(*(const char **)x, *(const char **)y); 5} 6 7/*--- 文字列を指すポインタの配列pを昇順にソート ---*/ 8void sort_pvstr(char *p[], int n) 9{ 10 qsort(p, n, sizeof(char *), pstrcmp); 11}
ここの、
C
1(*(const char **)x, *(const char **)y)
の意味がわかりません。
char * → 文字列
char ** → 文字列を格納したアドレス
というのはわかりますが、その後どのように式を解釈したらいいかわかりません。
よろしくお願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答6件
0
const というキーワードがありますが、ひとまず const は無いものと思ってください。大枠を理解してから const の意味を付け加えれば十分だと思います。すると質問者の問いはこうなります。
(*(char **)x, *(char **)y)
の意味がわかりません
この (char **)
という部分をキャストと呼びます。変数など(この場合、x や y)の型を、カッコ内の型であると「みなす」のがキャストです。pstrcmp()の引数は void *x, void *y ですが、
- 実際の x は
char ** x
であり - 実際の y は
char ** y
である
と見做しています。
結論的に言えば、strcmp(*(char **)x, *(char **)y)
は、
「引数 x, y は void 型ポインタとして渡ってきたものであるが、
それぞれchar ** x と char ** y
である(それぞれ文字へのポインタへのポインタである)と見做す。
その上で、strcmp(*x, *y) という呼び出しをする」という意味です。
char * → 文字列
char ** → 文字列を格納したアドレス
という書き方が私的には疑問を感じるので、そこをクリアにしたいとも思い、どういうことなのか、メモリのイメージを描いてみました。
(1) 「void *x」とは、変数 x は void 型ポインタである。
即ち、型情報が無い・アドレス値だけを持つポインタ変数である、という意味です。指している先のデータ型を特定できない事情があるので、こうするのですが、
(2) 実際の x はポインタへのポインタ(ダブルポインタとも言う)であると (char **)x
は示しています。
今、qsort() がソートしようとする配列は char *p[] という、文字列へのポインタが並ぶ配列です。
pstrcmp() を比較関数と呼びます。比較関数は何のためにあるか。それは qsort() が比較関数 pstrcmp() を呼び出し、配列の中の2つの要素の大小関係を知るためです。
比較関数を呼ぶ時 qsort() は
(3)のように、2つの要素のアドレスを引数にして、比較関数を呼びます。
例えば result = pstrcmp(&p[s], &p[t]);
のような呼び出し方をすると想像してください(あくまで「のような」です)。
ライブラリ関数 qsort() がソートするデータ型は、文字列のアドレス(char * 型)だけとは限りません。整数や浮動小数点の場合もあれば、構造体の場合もあります。従って qsort() 自身は型を特定できないので、2つの要素のアドレスを void * 型のポインタとして比較関数に渡すことにしています。これが上に書いた「事情」です。
一方、比較関数 pstrcmp() 側。
渡ってきた引数 x, y から、それぞれ x がポイントする p[s] の値(== X)と、 y がポイントする p[t] の値(== Y)を引数にして、strcmp(*x, *y) と呼び出したいわけです。
しかし、void型ポインタにはメモリを操作できないという制約があり、p[s], p[t] を読みだすことができません。そこで ** x, y を char **
型であると見做す必要がある**のです。
最後に const ですが、 const で修飾された場所を書き変えない(読むだけ)、という意味になります。
const void *x
は、図中 (1) で、ポインタ変数 x が指す void 型?のメモリを書き変えないという意味。const char **x
は、図中 (3) で、X番地の文字列、Y番地の文字列を、strcmp() 関数は書き変えないという意味です。
投稿2018/05/05 09:02
編集2018/05/05 09:44総合スコア1380
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ぶっちゃけ、strcmp関数のところで、ワーニング(もしかしたらエラーかも)を出さないようにワルアガキをしているだけですねー
投稿2018/05/05 09:50
総合スコア87719
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/05/05 13:46
2018/05/05 15:00
0
こんにちは。
pstrcmp(const void *x, const void *y)
のx, yが何を指すのかがポイントです。
char *p[]
はchar*
型変数の配列です。p[i]の型はchar*
型ですので文字列へのポインタです。
そして、pstrcmpのx, yには普通に文字列へのポインタをそのまま渡せば良いように思いますが、qsort()関数の仕様上それはできないのです。
struct tm time_tbl[10];
をソートしたい時、
qsort(time_tbl, 10);
と呼び出します。
この時p[i]の型はstruct tm
型です。
そのままpstrcmp()のx, yにstruct tm
型変数をコピーするという考え方もありますが、無駄が多いですよね? struct tm
へのポインタを渡せば十分なのですから。
従って、pstrcmp()のx, yにはstruct tm
型変数を指すポインタが渡されます。
さて、もとに戻ってp[i]の型がchar*
型の時はどうなるでしょう?
qsortは上記と全く同じプログラムですから、struct tm*
型を渡すのと同じ処理をするしかありません。
p[i]の型がstruct tm
型の時、p[i]を指すポインタが渡されるのですから、
p[i]の型がchar*
型の時も、p[i]を指すポインタが渡されます。それはchar **
型ですね。
それをchar*
を受け取るstrcmpにわたすために*
演算子でポインタへのポインタをポインタへ変換しているということなのです。
voidの部分はあまり気にしないでください。Cコンパイラは正しい型毎に処理する術を持たないので、全ての型をvoidで一括りにして扱っているだけです。プログラマは正しい型がchar*
型であることを知っているのでプログラマがキャストしているだけです。
あとconstはプログラマのミスをコンパイラが検出できる場面を増やす機能です。今回の話題では取り敢えず忘れても良いと思います。
投稿2018/05/03 15:20
総合スコア23272
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
qsort関数は並び替えるデータの型を知らないので、アドレス p から始まるサイズが sizeof(char*) バイトのデータが n 個ある配列、という情報しか持っていません。
そのため、qsortが比較関数(pstrcmp)を呼び出す際に、あるインデックス i のポインタは p + sizeof(char*) * i となり、それが void* 型で渡されます。
今回の場合、元のデータ p は文字列(char*)の配列ですから、それのあるインデックスをポインタで示そうとすると型は char** になります。
ですので、pstrcmpに渡された引数は const char* * のはずですが、qsortは const void* として渡してくるのでconst char* * にキャストする必要があり、文字列の比較をするにはそのポインタが示している先のデータ、つまり char* を取得する必要があるので ((const char *)x) となります。
投稿2018/05/03 14:57
総合スコア2850
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
キャストした後にアドレスの中身を参照しているわけです。
char * → 文字列
char ** → 文字列を格納したアドレス
に続いているわけですから、
*xは文字列を意味します。
投稿2018/05/03 14:10
総合スコア4830
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。