プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

C++を頑張る(3)

今回もC++です。

参照

まず大前提として、C++の関数における戻り値は基本1つだけです。
Lispは多値を返すのにねえ。
でも1個の処理で複数の結果が欲しいときもあるということで、
それを可能にする一つの手段として、『参照』という機能があるようです。

え〜と、そうですね…どんなシチュエーションがあるのかな、
複数の戻り値、というか結果が欲しいときって…。

今あんまり思い浮かばないので、数値を入力したらその数の
自乗を計算してくれて、あと渡した数が正の値だったかどうかも教えてくれる
処理を考えてみます。

#include <iostream>
using namespace std;

int check(int x){
    if(x >= 0){
        cout << "正の値を自乗します。" << endl;
    }else{
        cout << "負の値を自乗します。" << endl;
    }
    return(x * x);
}

void square(){
    int i, square_i;
    cout << "好きな整数を入力してください: " << flush;
    cin >> i;
    square_i = check(i);
    cout << i << "の自乗は" << square_i << "です。" << endl;
}

int main() {
    square();
    return 0;
}

関数checkといいながら計算までやっています。良くない気がしますが許してください。
これを、checkのほうではただの判定と計算だけしてもらって、
表示はsquareでするという風にしてみたいと思います。
ちょっと無理やりですが参照を使ってみます。

#include <iostream>
using namespace std;

int check(int x, bool &flg){
    flg = x >= 0;
    return(x * x);
}

void square(){
    int i, sqaure_i;
    bool plus_flg = false;
    cout << "好きな整数を入力してください: " << flush;
    cin >> i;
    sqaure_i = check(i, plus_flg);
    if(plus_flg){
        cout << "正の数を自乗します。" << endl;
    }else{
        cout << "負の数を自乗します。" << endl;
    }
    cout << i << "の自乗は" << sqaure_i << "です。" << endl;
}

int main() {
    square();
    return 0;
}

check関数の方で返しているのは、第一引数を二乗した数だけです。
flgには値を格納していますが別に返しているわけではありません。
でも動かしてみるとflgに値を格納したことで、引数にした元の
plus_flgの値も変わっているようです。

好きな整数を入力してください: 9
正の数を自乗します。
9の自乗は81です。
Program ended with exit code: 0

この参照を使うための記述は簡単で、変数を宣言するとき型のあとに&をつけるだけです。
参照は宣言時に必ず初期化しなくてはならず、あとから値を代入できないそうです。
なんで参照を使えば、参照元と参照先の2つの変数の値が連動するのかについては、
この章では特に言及がありません。しばらくはそういうものとしていきます。
この章で覚えるべきことは、参照というものがあるよ、ということでしょうか。
さて、これだけだとなんか物足りないので、もう少し参考書を進めます。

文字コード

コンピュータは0と1しか扱わないのですが、
その中でどう文字を記録しているかのお話です。

答えから言ってしまうと、文字に番号をつけてそれで各文字を
記録しているそうです。文字コードとはこの番号のことだそうです。

文字コードは文字をシングルクォーテーションで囲むことで得られるそうです。
いっちょ見てみます。

#include <iostream>
using namespace std;

void show_code(char c){
    cout << "「" << c << "」の文字コードは "
    << (int)(unsigned char)c << " です。" << endl;
}

int main() {
    show_code('0');
    show_code('9');
    show_code('A');
    show_code('Z');
    show_code('a');
    show_code('z');
}

結果はこうでした。

「0」の文字コードは 48 です。
「9」の文字コードは 57 です。
「A」の文字コードは 65 です。
「Z」の文字コードは 90 です。
「a」の文字コードは 97 です。
「z」の文字コードは 122 です。
Program ended with exit code: 0

(int)(unsigned char)はキャストといって、型を変更しているそうです。
char型はもしかしたら負の値になってしまうので、一回unsignedを指定し、
そのあと数値として表示するためにint型に変換していると。
結果は、アルファベットの大文字と小文字の間に何か挟まっていますね。
あ、参考書にASCII表とやらがついてました。記号がそこにあるんですね。
この章で覚えることは、文字は数値が文字コードであり変換されて
表示されているよ、ということでいいですかね。

文字列

文字ときたら文字列が気になるものです。というわけで次は文字列の話です。
文字は文字コードなので、文字列も当然文字コードの集まりだそうです。
ただ、文字コードを並べているだけではどこが文字列の終わりなのか
わからない、ということで文字列の終わりには0をつけるそうです。
この0を文字列の最後に置くことを、『ヌルターミネータ』というと。
これを文字列につけ忘れるのは初心者がよくやるバグだそうです。
気をつけます。

では実際に文字列の文字コードを確認してみますか。

#include <iostream>
using namespace std;

int get_charcd(char c){
    return (int)(unsigned char)c;
}

int main() {
    char hello[7];
    cout << "Hello!と入力してください:" << flush;
    cin >> hello;
    cout << hello << "を文字コードにすると" << endl;
    for(int i = 0; i < (int)sizeof hello; i++){
        int charcd = get_charcd(hello[i]);
        cout << " " << charcd << "," << flush;
    }
    cout << " になります。" << endl;
    return 0;
}

わざわざ"Hello!"を入力してもらっているのは、ただの練習です。
配列名を括弧無しで指定すれば文字列もサクッと入るようです。
この解説はいつか来ると信じて、結果はこうなりました。

Hello!と入力してください:Hello!
Hello!を文字コードにすると
 72, 101, 108, 108, 111, 33, 0, になります。
Program ended with exit code: 0

最後に0が入っていますね。こいつがヌルターミネータか。
…しかしヌル文字は表示するとどうなるんでしょうか。
ちょっとだけ変えて実験です。

#include <iostream>
using namespace std;

int get_charcd(char c){
    return (int)(unsigned char)c;
}

int main() {
    char hello[7];
    cout << "Hello!と入力してください:" << flush;
    cin >> hello;
    cout << hello << "を文字コードにすると" << endl;
    for(int i = 0; i < (int)sizeof hello; i++){
        int charcd = get_charcd(hello[i]);
        cout << hello[i] << " は " << charcd << "," << flush;
    }
    cout << " になります。" << endl;
    return 0;
}

結果はこうでした。

Hello!と入力してください:Hello!
Hello!を文字コードにすると
H は 72,e は 101,l は 108,l は 108,o は 111,! は 33, は 0, になります。
Program ended with exit code: 0

ヌル文字は表示できないようです。残念。
もともと空白を挟んでいたので分かりづらいですが、空白すら出てないです。
まあ、そういうものなのでしょう。
覚えておくこととしては、文字列の最後にはヌルターミネータありと。

文字列操作

先程の関数では、文字列の回数分for文を回すのに、(int)sizeof helloと
しました。配列いっぱいにデータが入ってなかったりするのでよくないです。
文字列の最後にはヌルターミネータがいるので、そいつを目印にすればよいですね。

#include <iostream>
using namespace std;

int get_charcd(char c){
    return (int)(unsigned char)c;
}

int main() {
    char hello[7];
    cout << "Hello!と入力してください:" << flush;
    cin >> hello;
    cout << hello << "を文字コードにすると" << endl;
    for(int i = 0; hello[i] != '\0'; i++){
        int charcd = get_charcd(hello[i]);
        cout << hello[i] << " は " << charcd << "," << flush;
    }
    cout << " になります。" << endl;
    return 0;
}

こんな感じです。ヌル文字の文字コードの表示はしてくれませんが、
helloに"hel"とかを入れても普通に動いてくれます。
ちなみに変える前の関数に"hel"とかを入れると、配列の使ってない部分には
ヌルターミネータが詰まってました。優しさを感じました。
あ、上の関数ではヌルターミネータを'\0'で表しています。
別に0でもいいけど、'\0'としたほうがヌルターミネータだと読んでわかるからだそうです。

文字列操作の関数としてsprintfが紹介されます。
覚えるのが面倒で利用価値が大きいらしいです。使ってみます。

#include <iostream>
#include <cstdio>
using namespace std;

int get_charcd(char c){
    return (int)(unsigned char)c;
}

int main() {
    char hello[7];
    cout << "文字コードを知りたい文字列を入力してください:" << flush;
    cin >> hello;
    for(int i = 0; i < (int)sizeof hello; i++){
        char str[50];
        sprintf(str, "%cを文字コードにすると%dになります。", hello[i], get_charcd(hello[i]));
        cout << str << endl;
    }
    return 0;
}

sprintfの第一引数は作った文字列を格納する変数、
第二引数は格納する文字列(%なんちゃらで変数を埋め込める)、
第三引数が埋め込む変数といった感じでしょうか。
そして使用するためには最初に #include が必要と。
とりあえずこいつらはまだおまじないです。
sprintfじゃなくてprintfを使うと、文字列を変数に格納せずに
そのまま表示するということです。
他にも色々文字列を扱う関数があるようですが、置いておきます。

一章一章が軽いのでこんなまとまりのない記事になってしまいました。
なんとなくLispをやっているときテンションが低いです。
いつか面白く感じられることを信じて、今日はおやすみなさい。