C++ char型 文字リテラルと文字列リテラル 書いて覚えるための初心者自己中記事
char型 まずは何もしないでバイト数表示
--------------------------------------------
int main(){
char t;
cout << sizeof t << endl;
}
(結果)
1
--------------------------------------------
1バイトあるらしい。
次に 'a' を入れて見る
--------------------------------------------
int main(){
char t = 'a';
cout << "sizeof t = " << sizeof t << " バイト" << endl;
cout << "t の中身は " << t << endl;
}
(結果)
sizeof t = 1 バイト
t の中身は a
--------------------------------------------
普通に入った。
ここからはちゃんと理解できてない部分があるのでそれを試す。
'a' だと入るけど "a" だと入らない。
char t = 'a';
の場合はchar型 t に a という文字の文字コードを代入した状態。
char t = "a";
の場合は、文字を " で囲むと「文字列です」ということになるようで
文字列の場合はその終わりに \0 が自動でつく。ここまでですよっていう印。
これも1バイト必要。
そうなってくると a = 1バイト \0 = 1バイト で合計2バイト必要になってくる。
なのに俺は1バイトしかないchar t に"a"を入れようとしたからエラーになったのだ。
では"a"を入れるにはどうしたらいいかというと
--------------------------------------------
int main(){
char t[2] = "a";
cout << "sizeof t = " << sizeof t << " バイト" << endl;
cout << "t の中身は " << t << endl;
}
(結果)
sizeof t = 2 バイト
t の中身は a
--------------------------------------------
こうすると出来る。
2バイトのデータを入れたいのなら char t [ 2 ] で2バイト分を確保する。
この [ 2 ] の部分(要素数)は宣言した型(今回ならchar)の一個分のバイト数を1単位として考えてくれる。
なので例えばdouble型の場合俺のPCでは8バイトあるらしいので [ 2 ] にすると16バイト分確保してくれる。
ちなみに"あ"を入れようとすると
--------------------------------------------
int main(){
char t[4] = "あ";
cout << "sizeof t = " << sizeof t << " バイト" << endl;
cout << "t の中身は " << t << endl;
}
(結果)
sizeof t = 4 バイト
t の中身は あ
--------------------------------------------
4バイト必要らしい。(char t [ 3] だとエラーになった)
この必要なバイト数は環境によって変わるそうだ。文字コードも色々あるそうなので。
つまり あ = 3バイト \0 = 1バイト で合計4バイトだ。
じゃあ "todayは15日" とかは入れるのには何バイト必要だよ。ってなる。
面倒なのでchar t [ ] のように要素数を省略して
--------------------------------------------
int main(){
char t[ ] = "todayは15日";
cout << "sizeof t = " << sizeof t << " バイト" << endl;
cout << "t の中身は " << t << endl;
}
(結果)
sizeof t = 14 バイト
t の中身は todayは15日
--------------------------------------------
こうするようになる。
後から入れようとして下記のようにしてもダメ
--------------------------------------------
int main(){
char t[ ]; //[error]
t[ ] = "todayは15日"; //[error]
cout << "sizeof t = " << sizeof t << " バイト" << endl;
cout << "t の中身は " << t << endl;
}
--------------------------------------------
要素数を省略して楽して入れられるのは初期化時のみ。
そうしないとメモリを確保する時に困るらしい。
ちなみに
'a' を 文字リテラル
"a" を 文字列リテラル
と言うのだそうだ。
文字列リテラルの場合は自動で末尾に \0 がつく。
代入について
--------------------------------------------
int main(){
char a = 'a';
char aa = a;
cout << "--'a'(文字リテラルを代入)--" << endl;
cout << "値の比較 a = " << a << " : aa = " << aa << endl;
cout << "sizeof a = " << sizeof a << " : aa = " << sizeof aa << endl;
printf("アドレス a = %p\n : aa = %p\n",&a,&aa);
}
(結果)
--'a'(文字リテラルを代入)--
値の比較 a = a : aa = a
sizeof a = 1 : aa = 1
アドレス a = 0x7fff5fbff6ff
: aa = 0x7fff5fbff6fe
--------------------------------------------
文字リテラルを別の変数に代入するのは問題なし。
問題は文字列リテラル・・・
文字列リテラルというのはアドレスを返すらしい。
つまり "なんらかの文字列" <- これはアドレスです。(?)
アドレスってのは先頭のアドレス?
--------------------------------------------
int main(){
printf("%p\n","なんらかの文字列");
}
(結果)
0x100000f9e
--------------------------------------------
確かにアドレスがある。
突然現れた "なんらかの文字列" にすでにアドレスがある。
" で囲むということはそういうことらしい。
アドレスということなら
--------------------------------------------
int main(){
const char* a = "なんらかの文字列";
cout << a << endl;
}
(結果)
なんらかの文字列
--------------------------------------------
ポインタ変数にそのまま代入してみた。代入じゃなくて初期化らしいが。
でも疑問が二つ。
一つはポインタ宣言時に const をつけないとエラーになった。
一つはアドレスを渡してもインデックス(要素数)がわからないじゃん。
の二点。
疑問書いている間に気づいた、要素数に関してはわからなくても問題ない。
文字列リテラルには末尾に \0 があるからそこまでいったら終わりなのだ。
-------------追記-------------------------
ってか要素数?配列なのか?ちがくね?
-------------追記終わり------------------
あとは const か・・
昨日 const についてやったけどその時にわかったことは
const 型* ポインタ変数
の場合はポインタ変数のアドレスは変更できるけどアドレスに格納されている値は変更できない、だったはず。
てことは、逆にconst つけないと"なんらかの文字列" で先頭アドレスから \0 までギチギチにきっちり格納されたメモリの範囲の外まで操作出来てしまう状況になるのか。
ダメだな。危ない。
だからconst つけないとエラーが出るのか。(あ、エラーというか警告?)
ちなみにこのconst のつけ方は参照の逆の状態だからアドレスを貰うなら参照でも良いじゃん?とはならないわけだ。
--------------------------------------------
int main(){
char &a = "なんらかの文字列"; [error]
cout << a << endl;
}
--------------------------------------------
こんなのはダメってことか。
関数の実引き値に文字列リテラルを使うときも同じようにやるのか。
つまり
--------------------------------------------
void Test(const char* x){
cout << x << endl;
}
int main(){
Test("なんらかの文字列");
}
(結果)
なんらかの文字列
--------------------------------------------
ということね。仮引数に const char* x と。
ポインタのアドレスも変更したくない場合は const をポインタ変数の前にも追加すればいいってことかな。const char* const x とね。
と思ったら!!
ここでふと疑問発生
下記は①文字列リテラルのアドレスを直接入れたポインタと
②配列に入れてからその配列のアドレスを入力したポインタです。
--------------------------------------------
int main(){
const char* p = "なんらかの文字列"; //①
char s[ ] = "なんらかの文字列";
char* ps = s; //②
}
--------------------------------------------
①はさっきやったヤツ。
②はどうして const ないのにエラー出ないの?
同じじゃないの?という疑問。
ちなみに②のポインタで s[ ] の配列は操作できた・・・
さっきこれは危ない。ドヤァ とか言ったのに・・
大丈夫じゃん・・・
(初心者なので)きちんとしたソースはまだ見つけられないけど
--------------------------------------------
int main(){
printf("%p\n","なんらかの文字列"); //文字列リテラルのアドレス
const char* p = "なんらかの文字列";
printf("%p\n",p); //ポインタpのアドレス
char s[ ] = "なんらかの文字列";
printf("%p\n",s); //配列sの先頭アドレス
char* ps = s;
printf("%p\n",ps); //ポインタpsのアドレス
}
(結果)
0x100001f00 <ー文字列リテラルのアドレス
0x100001f00 <ーを写したポインタpのアドレス
0x7fff5fbff700 <ー配列sの先頭アドレス
0x7fff5fbff700 <ーを写したポインタpsのアドレス
--------------------------------------------
このアドレスが怪しい。
普段見てる変数のアドレスより文字列リテラルの方が桁が少ない。
全然違う。怪しい。
なので「文字列リテラルのアドレス」とかでgoogle先生に聞いて見たら
あんまりぴったりとしたのは出ないが、「文字列リテラルのアドレス」は特殊で書き換え不可っていうのが少しあった。
もしそれが本当なら納得。
書き換え不可のアドレスをポインタ変数に移すんだから当然受け入れ側のポインタ変数も const させなきゃならないね。
あと、結果の一行目のアドレスって二行目のアドレスと無関係だから違うアドレスが出ると思ったら全く一緒のアドレスが出た。
何度か試して見たけど文字の内容が全く一緒の文字列リテラルだとアドレスが一緒になる。
しかも別の文字列を色々試したあとまた "なんらかの文字列"でアドレス見たら最初にやった時と同じアドレス。
これは本当にアドレスなのか?
まだ勉強していない範囲なだけかもしれない。
といあえずここまで
-----------追記
テキストに少し書いてあった。
やっぱり文字リテラルが格納されるアドレスは編集してはいけないらしい。
あと文字リテラルの寿命はプログラムが終了するまでらしいので、つまりずっと保持されるということ。
想像だけど、文字リテラル置き場がメモリにあって文字リテラルをそこに詰め込んでいるんじゃないだろうか。ぎっちりとね・・。
なので複数の文字リテラルがあったとしてそのうちのどれかを弄ってしまうと周囲の文字リテラルを上書きすることになりかねないから編集禁止なのでは。
うん、そう覚えておくと覚えやすい。(個人の感想です)
あと、文字リテラルは結局ただのアドレス値だと思えばポインタ配列にたくさんの文字リテラルを格納するのも簡単だとわかるね。
--------------------------------------------
int main(){
const char* str[ ]{
"なんらかの文字列",
"意味のない文字列",
"文字列リテラルだよ"
};
cout << str[0] << endl;
printf("%p\n",str[0]);
printf("%p\n","なんらかの文字列");
cout << str[1] << endl;
printf("%p\n",str[1]);
printf("%p\n","意味のない文字列");
cout << str[2] << endl;
printf("%p\n",str[2]);
printf("%p\n","文字列リテラルだよ");
}
(結果)
なんらかの文字列
0x100001ecc
0x100001ecc
意味のない文字列
0x100001ee5
0x100001ee5
文字列リテラルだよ
0x100001efe
0x100001efe
--------------------------------------------
ポインタ変数一個につき一個のアドレスを持てるから、ポインタ変数の配列ではたくさんのアドレスが持てる。
ただしこのままのやり方で関数の引数には出来ない模様。
--------------------------------------------
void Test(const char* str[ ]){
}
int main(){
Test("なんらかの文字列","意味のない文字列","文字列リテラルだよ");
}
(結果)
エラー
--------------------------------------------
なんでだろうと悩んだけど、実引数で , で区切っちゃってるのがダメなんだろうな。
仮引数は1個なのに実引数が3個ある感じになっちゃってるし。
どうしたらいいんだろう。
--------------------------------------------
void Test(const char* str[ ]){
cout << str[0] << endl;
cout << str[1] << endl;
cout << str[2] << endl;
}
int main(){
const char* str[ ]{
"なんらかの文字列",
"意味のない文字列",
"文字列リテラルだよ"
};
Test(str);
}
(結果)
なんらかの文字列
意味のない文字列
文字列リテラルだよ
--------------------------------------------
結局それぞれの文字リテラルの先頭アドレスが入ったポインタ配列の先頭アドレスを仮引数のポインタ配列に渡しました。(合ってる?)
一つ理解できない部分があった、
配列を引数で渡すときは配列の先頭アドレスを渡すはず。
んで引数の型ごとのバイト数でアドレスをずらして配列の要素へアクセスするんじゃなかったかな?
ってことは、今回ポインタの配列を引数にしたわけだけど渡ったのは1個目の文字リテラルのアドレスだけなのではないのだろうか?
普通の配列なら順番にデータが並んでるけど、文字列リテラルはバラバラなようなイメージなので二番目・三番目の要素はどうしているのだろう。
--------------------------------------------
void Test(const char* str[ ]){
cout << str[0] << endl;
printf("%p\n",str[0]);
cout << str[1] << endl;
printf("%p\n",str[1]);
cout << str[2] << endl;
printf("%p\n",str[2]);
}
int main(){
const char* str[ ]{
"なんらかの文字列",
"意味のない文字列",
"文字列リテラルだよ"
};
char size1[ ] = "なんらかの文字列";
printf("なんらかの文字列 %dバイト\n",(int)sizeof size1);
printf("%p\n",str[0]);
char size2[ ] = "意味のない文字列";
printf("意味のない文字列 %dバイト\n",(int)sizeof size2);
printf("%p\n",str[1]);
char size3[ ] = "文字列リテラルだよ";
printf("文字列リテラルだよ %dバイト\n",(int)sizeof size3);
printf("%p\n",str[2]);
Test(str);
}
(結果)
なんらかの文字列 25バイト
0x100001e30 //ここと
意味のない文字列 25バイト
0x100001e80 //ここと
文字列リテラルだよ 28バイト
0x100001ed0 //ここまでがそれぞれ80バイト間隔だ
なんらかの文字列
0x100001e30
意味のない文字列
0x100001e80
文字列リテラルだよ
0x100001ed0
--------------------------------------------
確認して見ました。
とりあえず動かした結果は普通に正常に動きます。
三つの文字列リテラルはそれぞれ25バイト・25バイト・28バイトです。
アドレスを確認するとどれも80バイト間隔でした。
どれも80バイト間隔なのがちょっと気になりますので文字列リテラルを長めにして見ましょう。
--------------------------------------------
void Test(const char* str[ ]){
cout << str[0] << endl;
printf("%p\n",str[0]);
cout << str[1] << endl;
printf("%p\n",str[1]);
cout << str[2] << endl;
printf("%p\n",str[2]);
}
int main(){
const char* str[ ]{
"なんらかの文字列",
"意味のない文字列",
"文字列リテラルだよ"
};
char size1[ ] = "なんらかの文字列のアドレスを80バイト以上にしなければならないのでひたすら文章を書き続けますがそろそろいいのでしょうか";
printf("なんらかの文字列 %dバイト\n",(int)sizeof size1);
printf("%p\n",str[0]);
char size2[ ] = "意味のない文字列";
printf("意味のない文字列 %dバイト\n",(int)sizeof size2);
printf("%p\n",str[1]);
char size3[ ] = "文字列リテラルだよ";
printf("文字列リテラルだよ %dバイト\n",(int)sizeof size3);
printf("%p\n",str[2]);
Test(str);
}
(結果)
なんらかの文字列 178バイト//バイト数が激増!
0x100001d74 //ここだけ間隔が268バイトになりアドレスも変わった(アドレスが手前にずれた)
意味のない文字列 25バイト
0x100001e80 //ここのアドレスは変わらず
文字列リテラルだよ 28バイト
0x100001ed0 //ここのアドレスは変わらず
なんらかの文字列
0x100001e30
意味のない文字列
0x100001e80
文字列リテラルだよ
0x100001ed0
--------------------------------------------
1個目の文字列リテラルを長めの文章にして見た。
さっきは80バイト間隔だった。
長くした文字列リテラルは178バイト。
そしたらその1個目のアドレスがスゥーっと手前にズレだした・・
アドレスの間隔もここだけ268バイト間隔になってるし・・
なんなん?
待てよ、一個目だからズレられたけど真ん中の2個目ならどうだ!!
--------------------------------------------
void Test(const char* str[ ]){
cout << str[0] << endl;
printf("%p\n",str[0]);
cout << str[1] << endl;
printf("%p\n",str[1]);
cout << str[2] << endl;
printf("%p\n",str[2]);
}
int main(){
const char* str[ ]{
"なんらかの文字列",
"意味のない文字列",
"文字列リテラルだよ"
};
char size1[ ] = "なんらかの文字列";
printf("なんらかの文字列 %dバイト\n",(int)sizeof size1);
printf("%p\n",str[0]);
char size2[ ] = "意味のない文字列なんかこの世界にはないのさラーラーラそんな君は";
printf("意味のない文字列 %dバイト\n",(int)sizeof size2);
printf("%p\n",str[1]);
char size3[ ] = "文字列リテラルだよ";
printf("文字列リテラルだよ %dバイト\n",(int)sizeof size3);
printf("%p\n",str[2]);
Test(str);
}
(結果)
なんらかの文字列 25バイト //元に戻した
0x100001df0 //とうとう二番目の文字列リテラルのアドレスより多くなった(順番が逆転したということ)
意味のない文字列 94バイト //94バイトになった
0x100001dcd//さっきまでのアドレス位置から179バイト手前に下がった(手前に下がった?)(179バイト少なくなりました)
文字列リテラルだよ 28バイト
0x100001ed0
なんらかの文字列
0x100001df0
意味のない文字列
0x100001dcd
文字列リテラルだよ
0x100001ed0
--------------------------------------------
はい。二番目の文字列リテラルの文字を増やしました。94バイトに増やしました。
当然さっきまでの80バイト感覚では入りきらないのでアドレスが移動しました。
文字列リテラルのアドレスの順番が 2個目ー>1個目ー>3個目 になりました。
ただし処理結果は今まで通り正常です。
順番が逆になったりはしません。
もう全然わからん。
ちゃんと知ってる人に教えてもらわないとわかんないよ!!
結局何が確認したかったかと言いますと、文字列リテラルの先頭アドレスをコピーしたポインタを複数個格納したポインタ配列を引数にして関数に渡した時に仮引数に渡るのはポインタ配列の先頭アドレスだけだから二番目のポインタ要素の先頭アドレスはどうしてるんだろう?
という疑問は解決しませんでした。
間違いなく上記の疑問内で俺が勘違いしている部分があるのでしょうけど、どこが間違っていて本当はどうなのかを特定できないと後々困るかな・・・
まぁ、勉強続けてればそのうちわかるでしょ!!
とりあえずここまで