重要なお知らせ

「教えて! goo」は2025年9月17日(水)をもちまして、サービスを終了いたします。詳細はこちら>

【GOLF me!】初月無料お試し

C言語を勉強中です。
ポインタは宣言しなくても、*印をつけるだけで使えるものなのでしょうか?
下記のプログラムが問題なく動くのはなぜなのか、どなたか教えてください。
(それともこれはポインタではない…??)
どうぞ、よろしくお願いいたします。

-----------------
#include <stdio.h>

int main(void)
{
int values[10];
int i;

for( i = 0 ; i < 10 ; i++ )
  scanf( "%d", values + i );

for( i = 9 ; i >= 0 ; i-- )
  printf( "\n%d", *( values + i ) );

return 0;
}

A 回答 (3件)

*( values + i ) に現れる * は、「間接演算子」という名前の演算子です(単項の演算子ですね)


演算子ですので、有効なものにつければ、正しい値を返します。

さて、
int values[10];
と配列を宣言したとき、valuse という「配列名」は、その先頭配列を差すポインタになります。
なので、valuse というもの自体が、(配列を宣言したので)ポインタとして扱われます。

(values + i) という式は、「ポインタ + 整数」です。
この式は、「ポインタを、その要素(この場合は、int) i 個分だけ進めたところにあるポインタ」を返します。
※valuse[i] の存在するアドレスと同じ。

最後に、*( values + i ) で、「その場所にある値」を返します。

で、非常にややこしいのは、

int i; に対する int *ptr; という宣言に現れる * は、「ポインタ宣言子」という、ポインタを宣言することを示す記号です。

なので、宣言の時に現れる * と、*( values + i ) に現れる * は、別のものということになります。
    • good
    • 1
この回答へのお礼

ポインタ変数とは別物の、間接演算子というものなのですね。
奥が深い…というか、ビギナーには紛らわしいものですね。
ポインタをようやく乗り越えたと思っていたのにまたつまずきました。(汗)
わかりやすい回答をありがとうございました!

お礼日時:2015/08/28 18:18

トラディショナルC言語は、アセンブリ言語(マシン語、ネイティブコード)が



主流であった時代に意識されたものですから、配列の先頭とポインタは同じ扱いになります。

values[10] は、*values の使用を許可しており、

*values の定義は、values[] の記述を許可している意味になります。

アセンブリ言語では、[]のような便利なものが無く、ポインタしか存在しません。

これに対して、

「[]って記述したら、ポインタを上手に使って、プログラマの意図したようにコンパイルします。」

というのが売りです。

当時のプログラマは、これに感心して、

「便利だわ~。俺もCに乗り換えようかな。」

とか思っていたわけです。

当時のユーザーはポインタのほうが考えやすかった。

こういうユーザー配慮がある以上、ポインタのほうが優先されるわけです。

昨今では、この使い方が危険だとし、ワーニングを出したり、禁止するコンパイラもあります。

この禁止の思想はコンパイラや規格により方言として扱うのが宜しいでしょう。

同様にC++も、C言語になれた人に如何にしてアピールするか悩んで作られています。

このために、構造化とオブジェクト化の敷居が曖昧であり、

オブジェクト言語としては使いづらいものになっています。

このように、

言語仕様と言うのは、常に不完全であり、そのたびに新しい言語が登場しています。


初心者ですと、

「この様に書いたら、だめだった。」

と言う部分で悩むでしょうから、

「どの様に書いたらよいでしょうか?」

と考えてしまい、視野が狭くなります。

その前には、

・この言語は何に適しているのか?
・パターンと成る記述方法(考え方)はどんなものか?どんな特徴があるのか?
・何を苦手とするのか?

と捉えないといけません。

プログラミングの本質を大雑把に理解し、

開発直前に言語仕様を調べて開発を行い、終わったらすっかり忘れる。

これが正しいあり方です。



言語を学ぶにあたり、記述方法を覚えていくと限界があります。

何を想定して作られた言語なのかを知ると良いと思います。

そうすると、

・きっとこうなっているだろう。

・と思っていたら、そうじゃなかった。

という二つの発想で覚えていくことが出来ます。

例えば、初心者向けの本にはprintfと相対して、scanfが良く記述されています。

しかし、慣れてきますと、

scanfの変換動作がわかりづらく、これをパワーユースすることを嫌うようになります。

C言語を使う人は、文字列をメモリ情報として扱い、これを直接操作して、

文字列操作を行うことを好みます。

printfは、メモリ情報を文字列に変換してそのまま出力デバイスに送信します。

デバイスを直接操作する部分は、OSに任せたいところですので、

最初のほうは好意を持ってしようします。(ファイル操作ぐらいまでは)。

しかし、

C言語が本当に活躍するのは、多様な出力デバイスを扱い、

リッチでスピード感のあるアプリケーションやハードウェア操作をするとき

ですから、

printfは、存在そのものとして価値がなくなってしまいます。

(コンソール画面に文字しか出せないものですから)

この様に、言語そのものの仕様というのは、個人のスキルの上達には直接関係なく、

言語が何に適しているかが大事に成ります。



C言語の場合は、実際のコンピューター上のメモリを直接読み書きする思想です。

例えば、

あるメモリ領域がVRAM(画面情報をセーブしたメモリ)であると想定した場合、

これを0で埋めてしまえば(大概の仕様では)画面が黒色で埋まります。

このときに、画面上での座標はVRAMの先頭アドレスから計算して何バイト目に

相当するか計算できます。

横が2048ドットであり、1ドットが32ビット=4バイトであり、

横列終わった後、次の行が続いているとします。(大概はそうなっている。)

x=15、y=10とした場合、(共に0から始まるとする)

オフセットアドレス = ((2048*10)+ 15 ) * 4;

となりますよね。

これにより、x、y座標が何バイトめになるかわかります。

そこに直接カラー情報を書き込めば、描画プログラムを作ることも可能です。


この様に、C言語はメモリを直接操作したり、これを繰り返してループする場合

に優れています。

そして思想と言うのがあります。

例えば文字列操作で、文字列を分割する操作があったとします。

splitなどと良く言われています。

”abc,def,ghi,jkelm"

という文字列から','で区切られた文字列を取り出す場合、

①元の文字列を配列にコピーする。
②出力となる文字列アドレスの配列を定義する。
③①の配列を1文字ずつ調べていき、','を見つけたら0で埋める。
④③と同時に、0で生めた場所+1のアドレスを②の配列に記録していく。
 (注:最初の文字が','でなければ、元の文字列の先頭を②にあらかじめ入れておく)
⑤①と②を出力として返却する。
 (注:返却する①②の配列の上限は予め定め、仕様とし、オーバーしないようにする)

このとき、

もとの文字列を明示的に*つきのポインタとして受け渡し、

コピーした左記の①は配列としておき、[]で操作する。

②も[]で操作します。

返却する場合は、[]で返したいところですが、これが(つまり「あれ?できないぞ」)

仕様上無理なので、呼び出し側が[]で確保しておき、ポインタを与えます。

int stringSplit( char **outputStrings , char *outputStringsBody , char *inputString , char splitChar , int maxOutStrings )

outputStrings: 出力文字列の先頭アドレス配列 ②
outputStringsBody:分割された文字列の格納先 ①
inputString:分割したい文字列
splirtChar:分割するタグに沿うとする文字
maxOutStrings:分割したとき、バッファが溢れないように制限した、最大文字列数

リターン値として、分割された文字列数を返します。

みたいな関数として作成できます。

こうした関数は、既に質問者さんが知る言語仕様だけで作成できます。

使い勝手もよろしくて、個人で作製されている人も多数いるでしょう。

C#では、string.Split( ',' )のように記述できます。

あらかじめライブラリとして用意されていると言う事です。

もちろん、for文を使用して自分で作成できますが、C#やJavaなどはC言語と違い、

直接のデータ操作が苦手です。(ループ記述での繰り返しは比較にならないくらい遅い)。

そのため、多くのライブラリを用意し、なるべくループ動作をしないように配慮しています。

C言語を使う人は、これらを嫌い、全部自分で作ります。

これ以外の言語は、ライブラリ仕様をどれだけ良く知っているかという部分が大事になります。


思想と言うのはこの様に言語により違ってきます。

C言語を使う場合は、最初の導入の記述を覚えたら、

・配列とポインタ操作で、如何にして便利な自分ライブラリが作れるか?

に進み、

多言語にあるライブラリ(もちろんC言語でも)に相当するものを、

自分で作ろうとしてテーマにし続けることが大事です。

つまり、C言語は覚えることが少ないのであっけなく、習得は終わります。

その後が長いわけです。

どこまで、

「他人が作った物と同じモノを自分だけで作れるか?」

だけが上達になります。

もう、大体の記述を覚えているようですから、

文字列操作系のライブラリを全部自分で作ることをお勧めします。



ベンチマークと言うのがありまして、

(数万回実施したときとか)、どのくらいの時間差になるのかを、

自作の場合と比較します。


こうしたものしか利点がありませんので、

既存ライブラリより早いスピードで動くものを作れない場合は、

C言語をつかう意味はあまりないと言って良いでしょう。


上達とは、これを指しますので、文法上での検討は、わからないとき本を読む程度にし、

数日でおわらせて、(繰り返しメモリ操作での)ライブラリ自作を死ぬほどやってください。

文法上では多くのC言語プログラマが知っていることは、もう知っているはずです。


ここから如何にしてLinuxが作れるのか?

如何にしてSkypeが作れるのか?

はたまた如何にしてオンラインゲームが作れるのか?

そこに秘匿されたノウハウがあり、壁になるのです。

通信を扱うアプリケーションなどでは、TCP/IPそのものが適していない場合があります。

こういうときは、UDPなどからTCPに相当する通信スタックを自作します。

これを数日で出来ないようですと、仕事そのものが納期に間に合わない場合もあります。

例えば、音声のコーダ(圧縮)が高くて、自作したことがあります。

買うと実験用だけで数百万円もしました。

これは半日で作ることが出来ました。

ライブラリ仕様を調べたり、製品の納品を待つ間の時間よりも、

自作でのスピードが速ければいい。

こういう種族がCプログラマです。


以上、ご参考に成れば。
    • good
    • 2
この回答へのお礼

とてもプロフェッショナルなご回答ありがとうございました。
ようやく読み終わりました…もちろん?4分の1も理解出来ておりません(すみません!)が、
半分でも理解できるよう頑張りたいと思います。
簡単なプログラムが書ければと気軽に思っていましたが、とても奥が深いのですね。
まだまだ勉強不足を痛感しました。

お礼日時:2015/08/28 20:17

最初に、「int values[10];」って宣言しているでしょ?



だから、その範囲内なら使えるというだけ

これはポインタ変数ではなく、配列の要素を表す二つの方式の内の一方ということ

試しにループの回数をもっと増やしてみるとよい
途端に例外発生することになる
    • good
    • 1
この回答へのお礼

実はこちらは、ポインタについての例題としてネットで見つけたものでした。
要素を表す方式、であってポインタ変数ではないのですね…。

ループの回数を増やしてあとでやってみます。
早速ありがとうございました。

お礼日時:2015/08/28 18:13

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!