質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.20%

C言語 文字列の比較と文字列の置き換え

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 4,336

aufheben

score 18

7つの文字列newsからSadとBadで始まる文字列を探して,文字列ごとCool Newsに置き換えるプログラムを作りたいです。

#include <stdio.h>
#include <string.h>

int check(char *);

int main()
{
    int i;
    char news[7][15] = { "Good News", "Cool News", "Bad News",
        "Sweet News", "Sad News", "Happy News", "Wonderful News" };

    for (i = 0; i < 7; i++) {

/*14行目*/        if (check(news[i]) == 1)*news[i] = "Cool News";
    }
    printf("--\n");
    for (i = 0; i < 7; i++) printf("%2d %s\n", i, news[i]);
    printf("--\n");

    return 0;
}


int check(char *str)
{
/*27行目*/    if (strncmp(*str, "Sad", 3) == 0 || strncmp(*str, "Bad", 3) == 0) {
        return 1;
}
    else return 0;

}

初心者なりにいろいろ考えてみたのですが、
(14): warning C4047: '=': 間接参照のレベルが 'char' と 'char [10]' で異なっています。
(27): warning C4047: '関数': 間接参照のレベルが 'const char *' と 'char' で異なっています。
(27): warning C4024: 'strncmp': の型が 1 の仮引数および実引数と異なります。
この3つのエラーが出てしまいます。
このエラーについて検索してみたりしたのですが納得できる説明は得られませんでした

ポインタや文字列等については知識がまだ浅いのでご教授いただければありがたいです。
あと、諸事情でcheck関数内とforループの中以外には手を加えられません

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

check解決した方法

0

14に対しては*news[i]=*news[1]とすることで解決しました

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/06 13:37

    確かに警告は消えますが、抜本的な解決になっていないです。
    私の回答の当該部分を*news[i]=*news[1]に置き換えると、次のように出力されます。
    --
    0 Good News
    1 Cool News
    2 Cad News
    3 Sweet News
    4 Cad News
    5 Happy News
    6 Wonderful News
    --
    なんでCoolじゃなくてCadになるかは、回答を見て考えてみてください。

    キャンセル

0

まず確認

・ C言語では、文字列は文字の配列として表現されます。
基本的には、文字列と言うと末尾にヌル文字がある、ヌル終端文字列を指します。

char str[] = "Hello";  // ← 追加されるヌル文字を含めると6文字
for(int i = 0; str[i] != '\0'; i++) {
    printf("%c", str[i]);
} 
printf("\n");

このプログラムは、標準出力に『Hello』と表示します。


・ C言語では、配列の変数名を評価すると先頭の要素のアドレスになります。
実例を出すと、strと&str[0]の返すアドレス値は同じです。

char str[] = "Hello";
printf("%s, address=%p\n", str, str);
for(int i = 0; str[i] != '\0'; i++) {
    printf("%5c, address=%p\n", str[i], &str[i]);
} 

実行結果(出力は実行ごとに変わり得ます)

Hello, address=0061FF26
    H, address=0061FF26
    e, address=0061FF27
    l, address=0061FF28
    l, address=0061FF29
    o, address=0061FF2A

・ C言語では、ポインタ型変数に単項演算子*を適用することで、実体を参照できます。
これは説明不要だと思いますが、一応コードを載せます。

char moji = 'A';
char *p_moji = &moji;

printf("%c, adress: %p\n",    moji,  &moji);
printf("%c, adress: %p\n", *p_moji, p_moji);

実行結果(出力は実行ごとに変わり得ます)

A, adress: 0061FF2B
A, adress: 0061FF2B

・ C言語では、文字列リテラルは先頭文字のアドレスを返します。
リテラルという言葉は聞きなれないかもしれません。
日本語では即値といい、つまり『直接プログラム内に書かれた値』です。"Hello"とか。

const char *str = "Hello";
printf("%s, address: %p\n", str, str);

配列とポインタの類似性について気付いたかもしれませんね。
これらは不可分の関係です。配列を扱う上ではポインタは必須なのです。

また、constは定数の意味で、『この値は変わりませんよ』ということを指します。

確認したことを総括すると

次のプログラムを実行すると、Hが出力されます。挙動が理解できるでしょうか。

char str[6] = "Hello";
printf("%c\n", *str);

最近似たような質問があったので、そちらを見ると理解が深まるかもしれません。
teratail - 文字列とポインタについて(コード短いです)

ミニマムプログラム

ご提示のコードは少し煩雑なので、全く同じ警告を出すミニマムプログラムを提供します。

#include <stdio.h>
#include <string.h>

int check(char *str)
{
    if (strncmp(*str, "Sad", 3) == 0) return 1;
    if (strncmp(*str, "Bad", 3) == 0) return 1;
    else return 0;
}

int main()
{
    char news[15] = "Good News";

    if (check(news) == 1) {
        *news = "Cool News";
    }

    printf("--\n");
    printf("%s\n", news);
    printf("--\n");

    return 0;
}

これを用いて、警告について考えていきましょう。


warning C4047: '=': 間接参照のレベルが 'char' と 'char [10]' で異なっています。

*news = "Cool News";の個所に対する警告です。
両辺の型がそれぞれどうなっているのか、しっかり考えてみるといいです。

  • 左辺: newsは先頭文字のアドレスを返し、*で解決するので、char型。
  • 右辺: "Cool News"は先頭文字のアドレスを返すので、char *型。

また、配列はそれ自体をごっそり入れ替えることが出来ません。
指し示す先を変更したいなら、文字通りポインタを使うのがよいです。

よって、以下のように書けばよいです。

const char *news = "Good News";
...
    news = "Cool News";

warning C4047: '関数': 間接参照のレベルが 'const char *' と 'char' で異なっています。
warning C4024: 'strncmp': の型が 1 の仮引数および実引数と異なります。

この二つはひっくるめて考えた方が自然でしょう。
strncmpの使い方(引数の与え方)を誤っているという警告です。

strncmpの引数は次の通りです。引用元:C言語関数辞典 - strncmp

int strncmp(
    const char *s1,
    const char *s2,
    size_t n
);

まず、ご提示のコードのstrncmp(*str, "Sad", 3) == 0は誤りです。
第一引数をよく見てください。char型の値を渡していますよね?

また、strncmpは第一引数にconstな文字列を要求しています。
それではstrの型をどこで調整すればいいかと言うと... checkの定義です。
check内でstrの内容を変更しないのですから、const char*を受け取ってしまえばいいです。

int check(const char *);
...
int check(const char *str) {

結局どう書けばいいのよ?

次のように書けばよいです。gccで動作確認済みです。

#include <stdio.h>
#include <string.h>

int check(const char *);

int main()
{
    const char *news[7] = {
        "Good News", "Cool News", "Bad News",
        "Sweet News", "Sad News", "Happy News", "Wonderful News" 
    };

    for (int i = 0; i < 7; i++) {
        if (check(news[i]) == 1) {
            news[i] = "Cool News";
        }
    }
    printf("--\n");
    for (int i = 0; i < 7; i++) {
        printf("%2d %s\n", i, news[i]);
    } 
    printf("--\n");

    return 0;
}

int check(const char *str)
{
    if (strncmp(str, "Sad", 3) == 0) return 1;
    if (strncmp(str, "Bad", 3) == 0) return 1;
    return 0;
}

実行結果

--
 0 Good News
 1 Cool News
 2 Cool News
 3 Sweet News
 4 Cool News
 5 Happy News
 6 Wonderful News
--

ここまで指摘した箇所以外では、次の箇所を書き換えています。
(変更できませんとおっしゃっている部分も含みます。ちょっと見てられないからです。)

  • newsの宣言を変更し、ポインタ配列としています。
    先ほど申し上げた理由の通り、必須です。
    しかし、strcpyを利用すれば、この限りではありません。
  • iのスコープを変更しています。
    カウンタ変数はfor文の初期化部分で宣言することを強くお勧めします。
    よほど古いコンパイラでなければ問題なく使えるはずです。
  • check関数内のロジックを少し変更しています。
    経験を積むうちにわかると思いますが、論理和が可読性を落とす状況はままあります。
    早期リターンを適切に用いることで、『条件のふるい落とし』が出来て便利です。

おわりに

いかがでしょうか。
ずらずら書いておいて難ですが、たぶんすぐには理解できないと思います。

C言語のポインタは、慣れるまでは非常に難解に感じるでしょう。
しかし、システムの本質的な挙動を垣間見ることが出来るので、非常に役に立ちます。
大変だと思いますが、頑張ってくださいね。

おまけ

一般に配列の要素を巡回する場合、次のように書くと保守性が高い。

for(int i = 0; i < sizeof(arr)/sizeof(*arr); i++) {
}

配列とポインタを混同すると上記記法が理解できないので、今回は割愛した。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/06 14:50

    #include <stdio.h>
    #include <string.h>

    int check(char *);

    int main()
    {
    int i;
    char news[7][15] = { "Good News", "Cool News", "Bad News",
    "Sweet News", "Sad News", "Happy News", "Wonderful News" };

    for (i = 0; i < 7; i++) {

    if (check(news[i]) == 1) {
    news[i][0] = 'C';
    news[i][1] = 'O';
    news[i][2] = 'O';
    news[i][3] = 'L';
    news[i][4] = ' ';
    news[i][5] = 'N';
    news[i][6] = 'e';
    news[i][7] = 'w';
    news[i][8] = 's';
    }
    }

    printf("--\n");
    for (i = 0; i < 7; i++) printf("%2d %s\n", i, news[i]);
    printf("--\n");

    return 0;
    }


    int check(char* str)
    {
    if (strncmp(str, "Bad News", 3) == 0||strncmp(str, "Sad News",3)==0) {
    return 1;
    }
    else return 0;

    }

    CoolがCOOLになっていますがこれで正しいです。
    上記のようにして欲しかった結果を得て、自己解決しました。
    丁寧な回答ありがとうございました。

    キャンセル

  • 2017/11/06 14:54

    結局どこまで理解しているのか不明瞭ですが、動けばいいのならそれでいいでしょう。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.20%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる