まず確認
・ C言語では、文字列は文字の配列として表現されます。
基本的には、文字列と言うと末尾にヌル文字がある、ヌル終端文字列を指します。
C
1char str[] = "Hello"; // ← 追加されるヌル文字を含めると6文字
2for(int i = 0; str[i] != '\0'; i++) {
3 printf("%c", str[i]);
4}
5printf("\n");
このプログラムは、標準出力に『Hello』と表示します。
・ C言語では、配列の変数名を評価すると先頭の要素のアドレスになります。
実例を出すと、strと&str[0]の返すアドレス値は同じです。
C
1char str[] = "Hello";
2printf("%s, address=%p\n", str, str);
3for(int i = 0; str[i] != '\0'; i++) {
4 printf("%5c, address=%p\n", str[i], &str[i]);
5}
実行結果(出力は実行ごとに変わり得ます)
Hello, address=0061FF26
H, address=0061FF26
e, address=0061FF27
l, address=0061FF28
l, address=0061FF29
o, address=0061FF2A
・ C言語では、ポインタ型変数に単項演算子*を適用することで、実体を参照できます。
これは説明不要だと思いますが、一応コードを載せます。
C
1char moji = 'A';
2char *p_moji = &moji;
3
4printf("%c, adress: %p\n", moji, &moji);
5printf("%c, adress: %p\n", *p_moji, p_moji);
実行結果(出力は実行ごとに変わり得ます)
A, adress: 0061FF2B
A, adress: 0061FF2B
・ C言語では、文字列リテラルは先頭文字のアドレスを返します。
リテラルという言葉は聞きなれないかもしれません。
日本語では即値といい、つまり『直接プログラム内に書かれた値』です。"Hello"とか。
C
1const char *str = "Hello";
2printf("%s, address: %p\n", str, str);
配列とポインタの類似性について気付いたかもしれませんね。
これらは不可分の関係です。配列を扱う上ではポインタは必須なのです。
また、constは定数の意味で、『この値は変わりませんよ』ということを指します。
確認したことを総括すると
次のプログラムを実行すると、H
が出力されます。挙動が理解できるでしょうか。
C
1char str[6] = "Hello";
2printf("%c\n", *str);
最近似たような質問があったので、そちらを見ると理解が深まるかもしれません。
teratail - 文字列とポインタについて(コード短いです)
ミニマムプログラム
ご提示のコードは少し煩雑なので、全く同じ警告を出すミニマムプログラムを提供します。
C
1#include <stdio.h>
2#include <string.h>
3
4int check(char *str)
5{
6 if (strncmp(*str, "Sad", 3) == 0) return 1;
7 if (strncmp(*str, "Bad", 3) == 0) return 1;
8 else return 0;
9}
10
11int main()
12{
13 char news[15] = "Good News";
14
15 if (check(news) == 1) {
16 *news = "Cool News";
17 }
18
19 printf("--\n");
20 printf("%s\n", news);
21 printf("--\n");
22
23 return 0;
24}
これを用いて、警告について考えていきましょう。
warning C4047: '=': 間接参照のレベルが 'char' と 'char [10]' で異なっています。
*news = "Cool News";
の個所に対する警告です。
両辺の型がそれぞれどうなっているのか、しっかり考えてみるといいです。
- 左辺: newsは先頭文字のアドレスを返し、*で解決するので、char型。
- 右辺: "Cool News"は先頭文字のアドレスを返すので、*char 型。
また、配列はそれ自体をごっそり入れ替えることが出来ません。
指し示す先を変更したいなら、文字通りポインタを使うのがよいです。
よって、以下のように書けばよいです。
C
1const char *news = "Good News";
2...
3 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*を受け取ってしまえばいいです。
C
1int check(const char *);
2...
3int check(const char *str) {
結局どう書けばいいのよ?
次のように書けばよいです。gccで動作確認済みです。
C
1#include <stdio.h>
2#include <string.h>
3
4int check(const char *);
5
6int main()
7{
8 const char *news[7] = {
9 "Good News", "Cool News", "Bad News",
10 "Sweet News", "Sad News", "Happy News", "Wonderful News"
11 };
12
13 for (int i = 0; i < 7; i++) {
14 if (check(news[i]) == 1) {
15 news[i] = "Cool News";
16 }
17 }
18 printf("--\n");
19 for (int i = 0; i < 7; i++) {
20 printf("%2d %s\n", i, news[i]);
21 }
22 printf("--\n");
23
24 return 0;
25}
26
27int check(const char *str)
28{
29 if (strncmp(str, "Sad", 3) == 0) return 1;
30 if (strncmp(str, "Bad", 3) == 0) return 1;
31 return 0;
32}
実行結果
--
0 Good News
1 Cool News
2 Cool News
3 Sweet News
4 Cool News
5 Happy News
6 Wonderful News
--
ここまで指摘した箇所以外では、次の箇所を書き換えています。
(変更できませんとおっしゃっている部分も含みます。ちょっと見てられないからです。)
- newsの宣言を変更し、ポインタ配列としています。
先ほど申し上げた理由の通り、必須です。
しかし、strcpyを利用すれば、この限りではありません。
カウンタ変数はfor文の初期化部分で宣言することを強くお勧めします。
よほど古いコンパイラでなければ問題なく使えるはずです。
経験を積むうちにわかると思いますが、論理和が可読性を落とす状況はままあります。
早期リターンを適切に用いることで、『条件のふるい落とし』が出来て便利です。
おわりに
いかがでしょうか。
ずらずら書いておいて難ですが、たぶんすぐには理解できないと思います。
C言語のポインタは、慣れるまでは非常に難解に感じるでしょう。
しかし、システムの本質的な挙動を垣間見ることが出来るので、非常に役に立ちます。
大変だと思いますが、頑張ってくださいね。
おまけ
一般に配列の要素を巡回する場合、次のように書くと保守性が高い。
C
1for(int i = 0; i < sizeof(arr)/sizeof(*arr); i++) {
2}
配列とポインタを混同すると上記記法が理解できないので、今回は割愛した。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/11/06 05:50
2017/11/06 05:54