C/C++ちょっとしたこと

「"error C2018: 文字 '0x81' は認識できません"と"error C2018: 文字 '0x40' は認識できません"の謎」
「ポインタの動作の不安定」
「文字列中で"や\を使いたい」
「配列の要素数を動的に求めたい」
「前置・後置インクリメント演算子の違い」
「コンマ演算子とは」
「C++でasmという変数を使用するとエラーが出る」
「アークタンジェント関数(atan)がうまく動作しない」
「関数の引数におけるconst char* 〜とchar* const 〜の違い」
「new/deleteを使用したときにエラーが出る」


「"error C2018: 文字 '0x81' は認識できません"と"error C2018: 文字 '0x40' は認識できません"の謎」
参考書を見ながらCのソースを書いたのですが、上のエラーが出てしまいました。どこをみてもコードに間違いはないと思うのですがエラーを回避する方法を教えてください。

解説
 文字と文字の間隔をあけるスペースには、全角スペース(日本語入力でのスペース)と半角スペース(英語入力でのスペース)があります。VisualC++では、全角スペースを理解できないため、エラーが発生するようです。したがって全角スペースになっている部分を削除する必要があります。基本的にはどのような言語でも全角スペースではエラーが起こるものだと思ってもよいかもしれません。

「ポインタの動作の不安定」
以下(左)のプログラムを作成したのですが、使用する変数を配列にした場合はうまく動作するのですが、ポインタを使用するとうまく動作しません。なぜでしょうか?

解説
 このプログラムは、コンパイルが通るために間違いがないように見えますが、きわめて重大な間違いをしているということに気が付かなくてはなくてはいけません。それはポインタpchStringが指しているアドレスの位置が不明であるということです。ポインタとはメモリのどこを指しているのかだけを指示する変数であるため、アドレス以外のデータを格納することはできません。したがって、どこかデータを書き込むことが出来るメモリ空間が確保されていて、その先頭のアドレスをポインタが持っていることで、はじめてポインタが意味を持ちます。
 この場合は、メモリを動的に確保すればよいのでmalloc関数で、ヒープ領域にメモリ空間を確保します。malloc関数の戻り値は確保したメモリの先頭アドレスなので、それをポインタの型でキャストして代入してあげれば(右)OKです。なお当然ですが、動的確保したメモリ空間は、ユーザが明示的に解放しなくてはいけません。

訂正前 訂正後
#include <stdio.h>
#include
<stdlib.h>
#include
<string.h>
int main()
{
char* pchString;
char* pchTemp = "VISUALC++";

strcpy(pchString,pchTemp);
printf("%s\n",pchTemp);
printf("%s\n",pchString);

return 0;
}
#include <stdio.h>
#include
<stdlib.h>
#include
<string.h>
int main()
{
char* pchString;
char* pchTemp = "VISUALC++";
pchString = (char *)malloc(strlen(pchTemp)+1);

strcpy(pchString,pchTemp);
printf("%s\n",pchTemp);
printf("%s\n",pchString);

free(pchString);
return 0;
}

「文字列中で"や\を使いたい」
ファイルのパスなどで\マークを使用したいのですが、うまく動きません。どうしたらよいでしょうか?

解説
\や"は特別な記号で、\はエスケープ文字列(\nは改行、\tはタブなど)をつくるための文字となり、"は文字列の初めと終わりを指定する機能文字であるので、文字列の中にこれらを使用した場合、例えば

"\abcdef"⇒ビープ音が1回鳴ってbcdefが表示される(\aがビープ音であるため)
"She says, "I love you." " ⇒エラー

などと書いてこれらを表示しようとすると、うまく表示されなかったりコンパイルエラーになります。\や"を文字列として認識させたい場合は、その前にもう1回\を書く、つまり

"\\abcedf"
"She says, \"I love you.\" "

とすればよいです。

「配列の要素数を動的に求めたい」
配列の要素数を計算したいのですが、どうすればよいでしょうか?

解説
なぜ配列の要素数を計算することがあるのかと、疑問をもつ人も多いかもしれません。確かにCやC++では、配列の宣言において要素数は定数値でなくてはならないと定められているからです。しかし、線形リスト構造など何回も動的にメモリが確保され、配列の要素数が実行されるまで分からない時などに有効です。例えばこのような手法が考えられます。

配列要素数を動的に求める
#include <stdio.h>

int main()
{
int nArray[10];
int nNum;

nNum = sizeof(nArray)/sizeof(nArray[0]);

printf("%d\n"nNum);

return 0;
}

赤いところをnNum = sizeof(nArray)/sizeof(int);と変えてもOKです。

「前置・後置インクリメント演算子の違い」
"++i"や"i++"など、違いが良く分からないので特に気を配っていないのですが、どのように使い分けたらいいのですか?
解説
この質問のキーワードは、「式の評価」です。即ち、「計算式の結果」と「式の評価値」が一致するとは限らないのです。例えばこのインクリメント演算子の場合、
コード 結果
#include <stdio.h>
int main(){
int x = 0;
int y = 0;
printf("%d\t%d\n",x++,++y);
printf("%d\t%d\n",x++,++y);
printf("%d\t%d\n",x++,++y);


return 0;
}
0 1
1 2
2 3

というコードを書いた場合、結果が異なることに気付きます。これはxもyも0の時に、

前置インクリメント z = ++y; yをインクリメントし、それから++yの評価値である1をzに代入
後置インクリメント
w = x++; x++の評価値0をwに代入し、それからxをインクリメントする

という性質があるのです(この場合評価値というのはxやyの値そのものです)。デクリメントも動作は同じです。

「コンマ演算子とは」
プログラムでコンマで区切ったものがあるのですが、ただの区切りだと思ってもいいのですか?

解説
ただの区切りのように見えますが、あれはコンマ演算子と呼ばれる演算子です。演算子の中でも一番優先度が低いことも特徴の1つだといえます。コンマ演算によって影響を受けるのが、「式の評価値」と「評価順序」でしょう。コンマ演算子は、

op1,op2

と記述され、op1,op2の順で実行されていき、式の評価値は最終ステートメント(op2)の値となります。例えば

x++,4

という文があった場合は式全体の評価値は4になります。

「C++でasmという変数を使用するとエラーが出る」
合計(sum)のa番目という意味でasmという変数名をつけたのですが、エラーや警告が出ます。どうしてですか?

解説
普通のプログラムを組んでいる場合には滅多に使うことはないのですが、asmというのはアセンブラコードを記述するための予約語なので、変数名として使用できません。

「アークタンジェント関数(atan)がうまく動作しない」


y=x*tan(θ)という式からθを求めたいので、以下のように考えてみました。
y/x =tan(θ)
θ=arctan(y/x)
したがってアークタンジェント関数atanを使用し、atan(y/x)と計算したのですが値によってはうまく動作しません。なぜですか?

解説
 実は数学的な問題になります。x=0の時,y/xの値は保証されていません。 数学的には、x=0のときy/xは無限大になるため、atan(y/x)の値はπ/2[rad]になるはずですが、コンピュータでは0の割り算を行うと何が行われるか予想できないために、この関数をむやみに使うのは危険です。そこで、θ=arctan(y/x)を求めるためだけに、mathライブラリにatan2という関数が用意されています。

atan2(y,x)

と実行すれば、θ=arctan(y/x)が計算できます。

「関数の引数におけるconst char* 〜とchar* const 〜の違い」
strcpy関数 char* strcpy(char* a , const char* b) の引数に、const char* 〜があるのですが、なにか特別な意味があるのでしょうか?また参考書などではこれと似たものに、char* const〜 というものがありました。上記のものとの違いはなんでしょうか?

解説
 関数の引数にポインタを記述する参照渡しは、関数内部でデータが書き換えることができるという利点がありますが、それは同時に関数内部でデータが書き換えれてしまうということです。しかし配列や文字列を関数に渡したいが、関数内で書き換えたくない場合があるはずです。しかし配列や文字列を渡す時は参照渡しになってしまいます。そこでconst char* 〜という形にすると、値を書き換えることができなくなります。しかし、データ自体を変更することは出来ませんが、ポインタ(アドレス)は変更することが出来ます。(左下)

 char* const〜というのは、参照渡しには変わらないので値を変更することはできますが、ポインタ(アドレス)の値を変更することはできなくなります。

const char* 〜 char* const 〜
void func1(const char* pchstr){
// strcpy(pchstr,"MFC");文字列を変更するとエラー
pchstr = NULL;
}
void func2(char* const pchstr){
strcpy(pchstr,"MFC");
// pchstr = NULL;ポインタを変更するとエラー
}

 

「new/deleteを使用したときにエラーが出る」
文字列を使用するために、new演算子で文字数を考えてメモリの動的確保(左下)を行ったのですが、deleteするときなぜかエラーが出てプログラムが不正終了してしまうのです。何が原因なのでしょうか?

解説
 これは、C/C++の文字列の特性をしっかり把握していないときに起こしてしまう典型的なエラーです。"VISUALC++"という文字列の文字数は確かに9文字なので、9byte(char型は1byte)のメモリを確保すればいいような気がしますが、文字列には、その終わりを示すNULL文字(\0)を付加して上げなくてはならないので、実際は文字バイト数+1のメモリを確保しなくてはならないので、delete時にエラーが起こるようです。これはC言語における、malloc/freeでも同じですから注意してください。

失敗例 訂正後
int main()
{

char* pchstr = "VISUALC++";
char* pchstrA = new char[strlen(pchstr)];

strcpy(pchstrA,pchstr);

delete []
pchstrA

return 0;

}
int main()
{

char* pchstr = "VISUALC++";
char* pchstrA = new char[strlen(pchstr)+1];

strcpy(pchstrA,pchstr);

delete []
pchstrA

return 0;

}