一次元,二次元の配列だけではなく,三次元以上の配列を作ることもできる.
int a3[10][3][4];
これは,3×4 の 二次元配列が 10 個,計120個の整数が利用できる.
さらに配列の次元を増やして,
int a3[10][3][4][5][10][3];
のような記述も可能であるが,工学上の問題の性質を考えれば,せいぜい2∼3次元程度が実用的である.
いずれの多次元配列も,コンピュータのメモリ上では1次元配列として格納されている.
多次元配列を引数として,関数に渡す場合は注意が必要である.
関数の仮引数に多次元配列を渡す場合は,各次元の要素数が確定している必要があるため,引数として要素数を渡す方法ではエラーとなる.
(正確には,最初の次元の要素数[]
のみ,省略が許される.)
したがって,2次元以上の配列を関数に渡す場合は,以下のように配列の要素数 M, N をグローバル変数として定義しておき,仮引数で明示しておくと良い.
あるいは,別の方法として,要素数 M * N の1次元配列を確保して,添え字の変換を自分で管理するやりかたもある.
#include <stdio.h>
// 配列(行列)サイズをグローバル変数としておく.
const int M = 4;
const int N = 3;
//
// 2D配列を表示する関数
// void disp_2d_matrix(int mat[][]) はコンパイルエラーとなる.
//
void disp_2d_matrix(int mat[M][N])
{
int i, j;
for(i=0; i<M; i++) {
for(j=0; j<N; j++) {
printf("%d ", mat[i][j]);
}
printf("\n"); // 1行分表示したら改行
}
}
int main(void)
{
int mat[M][N] = {{ 1, 2, 3},
{ 4, 5, 6},
{ 7, 8, 9},
{10, 11, 12} };
disp_2d_matrix(mat);
return 0;
}
注意:以下の説明では,「文字」 と 「文字列」という用語を,厳密に区別して使用している.
コンピュータでの文字の処理においては,アルファベット文字や漢字,数字,ひらがな,カタカナ,記号などに数値を割り当て,文字と数値とを対応づけて処理が行われる.
この「文字」⇔「数値」の対応は「文字コード」と呼ばれ,言語や国によって様々な種類が規定されている.
一番単純な文字コード表は「ASCII(アスキー)コード」と呼ばれるものである.
アスキーコードは英数字や記号に1バイト分の数値を割り当てたの最も基本的なものであり,プログラムで扱いやすい.
以下の表は,文字コード表の一例である.
8ビットの16進数(頭に0xをつけて表す)のうち,上位4ビットを横軸に,下位4ビットを縦軸で表している.
例えば,大文字の 'A' の文字コードは 0x40 + 0x01 = 0x41,小文字の 'z'の文字コードは 0x70 + 0x0A = 0x7A である.
詳細なASCIIコード表はこちら(外部サイト)
左側から2列の最初の32文字(0x00 - 0x1F)は,「改行」や「タブ」などの「画面制御用コード」と呼ばれ,表示可能ではないため空白になっている.
また,0x20は何も印字されていないように見えるが,「スペース文字」である.
C言語では,文字(1文字)を格納するための変数が用意されており,char
型 (=character,チャー型,キャラ型,文字型など)と呼ばれる.
文字はコンピュータ内部ではすべて文字コードによる「数値」として管理されているため,ソースコード中において,以下の三つの表現は全く同じ意味となる.
'A'
0x41
65
char
型は「文字を扱うための変数」と考えれば良いが,その実体は8ビット符号なし整数であり,0∼255(16進数で0x00-0xff)の整数である.
以下のコードを実行し,ソースコード中での文字の扱いを確認しよう.
c,n
の値を変更し,画面に表示される文字が変化することを確認しよう. c
に整数を加減算をしてみよう.)
'\n'
や,タブ'\t'
の文字コードが,10進数および16進数でいくつになるか,調べてみよう.#include <stdio.h>
int main(void)
{
int n = 0x5A; // 整数型
char c = 'k'; // 文字型
// %d : 整数(10進数)として表示
printf("n = %d , c = %d \n", n, c);
// %X : 整数(16進数,大文字)として表示
printf("n = 0x%X , c = 0x%X \n", n, c);
// %c : 文字として表示
printf("n = %c , c = %c \n", n, c);
return 0;
}
ここでは,「文字」ではなく「文字列」の説明を行う.
char
型は,文字1つ分を格納する変数であるが,では「abc」 や 「Meiji」 など,2文字以上からなる単語や文章などの「文字列」をどう扱えばよいだろうか.
実はC言語には文字列を直接扱う「組込み型(プリミティブ型という)」が存在しない.
ではどうするかと言うと,char
型の配列を用いることとなっている.
即ち,「文字列」=「文字型 char
」の「配列」ということである.
ここで問題となるのは,C言語の配列サイズはコンパイル時に決めておかなければならないが,実際の文章や単語を扱う場合は,単語や文章に必要な文字数が状況に応じていろいろと変化するのが普通である.
そこで,以下のような工夫をする.
char
配列を準備.この特別な記号のことをヌル文字または終端文字と呼ぶ.(ナルと呼ばれることもある.)
ヌル文字はソースコード中で '\0'
と表され,ASCIIコードでは文字コード 0x00
が割り当てられている.
ソースファイル中では,以下のように記述され,すべて等価である.
'\0'
または '¥0'
: 文字としてのヌル文字.普通はこれを使う.0x00
: ASCIIコードでのヌル文字に対応する文字コード.0
と書いても同じ.NULL
が使えてしまう場合があるが,正しくない.これはヌルポインタ(nullptr)と呼ばれ,用途が異なる.ただし,ヌルポインタにも数値の0が割り当てられることが多いため,結果としてヌル文字として使えてしまうが正しくはない.
文字列のは,以下のようにダブルクォーテーションで囲まれた区間で表される.
"Meiji"
このダブルクオーテーションで括られた部分を「文字列定数」という.
この例では,配列の定義と同時に初期化しており,配列の要素数を省略している.(c.f. 配列)
さて,Meiji
の文字数は 5 文字であるが,実際には最後の文字 i の後に
このため,文字列のサイズを指定するときには,ヌル文字分を含めて確保する必要がある.
char str[6] = "Meiji"; // 要素数は 6 以上であればOK. 暗黙に str[5]=='\0' が設定される.
char str[] = "Meiji"; // ヌル文字含め,6文字分の配列が設定される
とする必要がある.
もちろん,余分に配列を確保しておこくとは問題ない.
以下の場合,str[0]=='M', str[1]=='e', ... , str[4]=='i', str[5]=='\0'
であり,str[6]
以降str[99]
までは不定である.
char str[100] = "Meiji"; // str[5]=='\0' となる.
キーボードからchar
型配列に文字列を入力する例を以下に示す.
scanf()
関数において,書式指定文字列に %s
を使用する.
例:キーボードから文字列の入力
#include <stdio.h>
int main(void)
{
char str[20]; // ヌル文字を含めて 20 文字分の文字列
printf("Input a string : ");
scanf("%s", str); // %s は文字列用の書式指定
// scanfは,入力された文字の最後に自動的にヌル文字を追加する.
// str は配列(=アドレス)なので,& は不要
// ここで,キーボードから Hello と入力
printf("%s \n", str); // printf関数は自動的にヌル文字の手前まで表示.ヌル文字自体は表示しない.
return 0;
}
実行例:
input a string : ABC
ABC
input a string : 100
100
「あれ,scanf()
で%d
を使って,数値の100と入力する場合と同じでは?」と思うかもしれません.
見かけは同じですが,実は違います.
この例では%s
を使用して,文字列として入力していますので,例えば「100」と入力した場合は「'1'の文字コードに相当する整数」「'0'の文字コードに相当する整数」「'0'の文字コードに相当する整数」(と,ヌル文字)が配列str
に格納されます.
画面に表示するだけであれば文字でも数値でも同じですが,四則演算や大小比較をを行うためには数値でなければならず,文字列のままでは計算ができません.
一方,scanf()
関数のフォーマット文字列に%d
や%f
を使用すると,入力された文字列をscanf()
関数が文字列を解析して整数や実数に適切に変換してくれています.
scanf()
関数によらないで,文字列を数値に変換する方法(atoi(), strtol()
)は別途説明します.
scanf()
のフォーマット指定を,"%s"
から "%[^\n]%*c"
に変更するとどうなるか.str[]
のサイズ(=20文字)よりも長い文字列をキーボードから入力するとどうなるか試してみよう.
キーボード入力ではどのような文字・数値が入力されるかわからない.
特に,文字配列を超える文字数の文字列が入力されるとメモリの不正アクセス(バッファーオーバーフローという)になるので,scanf()
では入力される文字数を制限することができる.
このテキストの説明では文字数の制限についてはあまり考慮しないが,実用的なプログラムではこのような入力文字数の制限は重要である.
#include <stdio.h>
int main(void)
{
char str[21]; // 21 文字分の文字列(ヌル文字含む)
printf("Input a string : ");
scanf("%20s", str); // %20s は,20文字で打ち切り
// ここで,キーボードから文字列を入力
printf("%s\n", str);
return 0;
}
C言語の配列は,連続したメモリ領域に配置されることが保証されています.
配列は添字 0 が先頭アドレスに配置され,添字順にアドレス値が順次増加します.
従って,以下のような文字列の設定が可能です.
#include <stdio.h>
int main(void)
{
char a[50]; // 50文字分の文字列
int i;
for(i=0; i<5; i++) { // 5文字分の値を書き込む
a[i] = 'A'+i; // 'A' の実体は数値! 文字コード表参照
}
a[5] = '\0'; // 最後にヌル文字を置く.a[5] = 0x00; と書いてもOK.
printf("%s \n", a);
return 0;
}
この例では,文字列 a
の,メモリ中の配置は,
となる.(最後に必ずヌル文字が置かれるが,それ以降は不定と決められている.)
printf
関数以降にコードを追加し,配列の各要素をすべて整数として画面に順次表示せよ.実行例: a[0] = 65 a[1] = 66 a[2] = 67 . . . a[48] = ???? a[49] = ????
文字列の最後に必ずヌル文字が入る事を利用して,以下のように文字列要素の全てを1文字ごとに走査(スキャン)できる.
#include <stdio.h>
int main(void)
{
char str[] = "Hello Meiji!"; // 文字列
int i=0;
while( str[i] != '\0' ) {
if(str[i] == 'M') {
printf("Found character M! %d th character = %c", i+1, str[i]); // %c は,1文字表示
}
i++;
}
printf("\n");
return 0;
}
for
文を用いる場合は,以下のように書くこともできる.
for(i=0; str[i] != '\0'; i++ ) { ...
ヌル文字'\0'
の文字コードは 0x00 = 0 であり,これは「偽」と等価であることを利用して,while, for
の条件判定部はさらに省略できる.
while( str[i] ) {
// ヌル文字(=0x00)に到達したら「偽」になるので,このループが終了する
i++;
}
この方法は便利であるが,手動で文字列を生成した場合など,文字列の最後にヌル文字を追加し忘れるとループ処理が正しく動かないので注意を要する.
char
型変数 = 8 bit 整数型
半角英数字を表すには,ASCIIコードで充分であるが,日本語で使用する漢字やカタカナ,ひらがな等の全角文字の場合は文字種類が非常多いため,char
型 = 8bit = 256通り,では到底不足する.
いろいろな歴史的経緯があるが,現在日本語コードとしては,JISコード(ISO-2022-JP)(電子メールで使用),Shift JISコード(以前のWindows標準),EUCコード(おもに旧式のUNIX)が使用されてきたが,最近では,世界共通のUNICODE(最近のOSや,このWebページ)など,多くの体系が現在も混在して利用されている.
ここでは扱わないが,漢字などの全角文字では複数バイトで1文字を表すワイド文字型・マルチバイト文字型が使用されている.
以下のプログラムで全角文字の内部表現を確認してみよう.
(.cppファイル自体の文字エンコードによっては,異なる結果となる可能性がある.)
#include <stdio.h>
int main(void)
{
const int N = 30;
// char str[N] = "AAAbcdefg"; // 半角文字
char str[N] = "あああいうえお"; // 全角文字
int i;
for(i=0; i<N; i++) {
int k = (unsigned char)str[i]; // 一旦,別の変数にキャストして代入
printf("str[%d] = %d , 0x%X\n", i, k, k); // 10進数,16進数で表示
}
return 0;
}
実行結果はOSやPCの環境に依存するが,おそらくchar配列数個分で,全角1文字を表現する結果となるはずである.
このようなマルチバイト文字の文字コード判定や使い分け,文字列の比較・変換処理には高度で複雑な処理が必要となるため,ここでは半角の英数字+記号のみ取り扱う.
char
型の配列である「文字列」を関数に渡す場合は,通常の数値配列と同様の手順で可能である.
ただし,文字列の場合には最後にヌル文字が含まれていることを利用して,引数を減らすことができる.
(つまり,配列の要素数を渡す必要がない.)
// 文字列を関数に渡す方法
#include <stdio.h>
void disp_str( char str[] ) // 文字列を引数にとる.要素数は不要.
{
printf("string = %s \n", str); // この例では,単に表示するだけ.
}
int main(void)
{
char a[] = "Hello world! My name is Meiji."; // 文字列
disp_str(a); // 文字列を引数として,関数呼び出し.要素数は不要
return 0;
}
文字列を関数に渡す場合は,「最後に必ずヌル文字がある」という約束のもとで,このように配列のみを渡せば良い.
逆に,この約束を破ってヌルで正しく終端されていない文字列を関数に渡すと,さまざまな事故の原因になる.
上記サンプルの disp_str
関数を参考に
disp_odd()
を作り,動作を確認せよ.disp_rev()
を作り,動作を確認せよ. 引数の型や個数,名前などは,各自で適切に設定せよ.
既に述べたように,C言語ではプリミティブな「文字列型」は存在せず,「文字型」の「配列」を使用することで対応しているため,文字列の操作によくある「複製」や「連結」,「検索」などが面倒である.
そこで,文字列操作に特化した便利なライブラリ関数が用意されている.
そのうちのいくつかを実際に使用してみよう.
詳しくは,C言語ランタイムライブラリ関数のリファレンスを参照.https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/string-manipulation-crt?view=msvc-170
以下は,string.h
で定義されている文字列操作用のライブラリ関数である.
(ここに出てくるchar*
とは,「文字列の先頭アドレス」を指すポインタ型であり,配列をそのまま渡すことができる.)
文字列操作関数は非常に多岐にわたるが,ここでは4種類のみ紹介する.
strlen()
・・・文字列の長さ(Length)を返す.strcpy()
・・・文字列を,別の文字列のコピー(copy)するstrcat()
・・・2つの文字列を連結(catenate)する.strcmp()
・・・2つの文字列を比較(compare)する.str, str1
など,文字列定数の例題については,文字列の内容を適宜変更して,変化を確認しよう.int strlen(const char* s);
このプロトタイプ宣言は,「引数は文字列1つ」「戻り値は整数」を表す.
文字列の長さを返す.string lengthの略.
このとき,最後のヌル文字は長さに含まない.
#include <stdio.h>
#include <string.h> // 文字列操作関数の使用に必要
int main(void)
{
char str[] = "Abcdefg";
printf(" string = %s \n", str );
printf(" length is %d \n", strlen(str) );
return 0;
}
char* strcpy(char* dst, const char* src);
このプロトタイプ宣言は,「引数は文字列2つ」を表す.
上記の関数プロトタイプ宣言は,「引数は文字列2つ」「戻り値は文字列の先頭(アドレス)」を表す.以下同様.
文字を src(sourceの意) から dst(destinationの意) にコピーする関数である.string copy.
第1引数dst
に既に文字が入っている場合は,上書きされる.
第2引数src
にはconst
がつけられているため,こちらはstrcpy()
によって変更されることはない.
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[] = "Meiji university";
char str2[100] = "Oh No!"; /* 100文字分の文字列. */
/* str1 から str2 へ文字列をコピー */
strcpy(str2, str1);
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
return 0;
}
char* strcat(char* dst, const char* src);
このプロトタイプ宣言は,「引数は文字列2つ」を表す.
文字列を連結する関数.string catenate.
dst
に入っている文字列の末尾に,文字列 src
を連結(追加)する.
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[100] = "Meiji";
char str2[100] = " university";
/* str1の最後尾にstr2を追加. */
strcat(str1, str2);
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
return 0;
}
int strcmp(const char* s1, const char* s2);
このプロトタイプ宣言は,「引数は文字列2つ」「戻り値は整数」を表す.
2つの文字列を比較する.string compare.
戻り値は
s1 == s2
の場合, 0s1 != s2
の場合,ゼロ以外の値.
である.
興味があれば,ゼロ以外の値としてどのような値が返ってくるか,自分で調べてみよう.
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[100], str2[100];
printf("str1 = ?"); scanf("%s", str1);
printf("str2 = ?"); scanf("%s", str2);
if( strcmp(str1, str2) == 0) {
printf("str1 = %s, str2 = %s are identical.\n", str1, str2);
} else {
printf("str1 = %s, str2 = %s are different.\n", str1, str2);
}
return 0;
}
文字列は,あくまで文字列なので,「100」や「-0.5」などの数値を入力するには,工夫が必要である.
例えば,文字列としての「100」(文字コード 0x31 0x30 0x30)は,文字として1,0,0(イチ,ゼロ,ゼロ)がchar
型配列3個分に格納されているだけなので,数値の「百」として四則演算などを行うためには,まず文字列を数値に変換する必要がある.
文字列を整数および実数に変換するライブラリ関数( stdlib.h )は,atoi(), atof()
であり,それぞれ,ascii to int, ascii to float の略である.
より高度な変換処理を行う strtol(), strtof()
もある.
これはstring to long, string to float の略.
(scanf()
でキーボードから入力する際には,この文字→数値の変換が自動的に行われている.)
下記の例で,キーボードから数値(100, -20, 3.14 など)だけでなく,文字(A, ZZZ など)を入力してみよう.
どのような動作をするか.
#include <stdio.h>
#include <stdlib.h> // atoi用
int main(void)
{
char str[100];
scanf("%s", str); // %s = 文字として入力
int n = atoi(str); // ここで文字列を数値に変換.変換できない場合は0が返る.
printf("character : %s \n", str); // 文字列として表示
printf("number : %d \n", n); // 整数として表示
return 0;
}
以下の例は,キーボードから繰り返し数値を入力し,"end"が入力されたら終了する. 無限ループで文字列をキーボードから入力し,"end"であれば終了. それ以外は数値に変換し,その値を2倍した数値を表示する.
#include <stdio.h>
#include <string.h> // strcmp用
#include <stdlib.h> // atof用
int main(void)
{
char str[100];
while(1) {
printf("Input a number(type [end] to finish):");
scanf("%s", str); // 文字として入力
if( strcmp(str, "end") == 0 ) {
printf("Finish!\n");
break;
}
float r = atof(str); // 文字列を数値に変換.
printf("number*2 = %f\n", r*2); // 数値として,2倍して表示
}
return 0;
}
上記のプログラムを実行して動作を確認してみよう.
char
型変数を用いて文字を表すが,その実体は整数である.'\0'
と書いておくことが望ましいです.NULL
と書いてはだめですか?scanf()
や str???()
系の関数では,文字列に自動的にヌル文字を付加しますが,自分で文字列を設定した場合にヌル文字を忘れると,関数が思いがけない動作をします.
例えば,printf("%s", ...)
では,ヌル文字を探してそこまで表示しようとするので,ヌル文字が無いと配列の境界を飛び越えて読み込もうとし,アプリケーションが不正終了する(落ちる)場合があります.
逆に,わざと終端文字を抜いて関数呼び出しして,不正アクセスさせるようなクラッキング方法(バッファーオーバーフロー)もあるので,十分注意が必要です.
strcpy()
ではなく,文字数を制限できる strcpy_s()
を使うことが推奨されています.
そのほか,printf_s()
,scanf_s()
,strcat_s()
など,_s がつく関数がいくつかあります.
興味があれば調べてみてください.
std::string
という便利なクラスがあります.
詳しく知りたい人は,C++言語を勉強すると良いでしょう.
ただし,その場合でも,C言語では文字列がどのように扱われているか,を知っておく必要があります.
char
型ではなく,2バイト以上wchar_t
型や,char16_t
型とchar32_t
型を使うことで実現可能です.
以下の 6×6 の表に従い,「科目」の平均点と,「個人」の平均点を計算して,表全体を画面に表示するプログラムを作成せよ.
表の初期値は,例のとおり二次元配列の初期化を用いてソースコード中に記入してある(右端と下段の 0 の部分).
科目名には順に添字番号(0=語学, 1=材力, ..., 5=個人平均など)を割り当てることとする.
ヒント:配列の添え字の範囲をよく考えて,上手にfor
文の範囲を決めよう.
/* ヒント:添え字は[出席番号] [科目]の順となる. */
/* 語学 材力 熱力 機力 流力 個人平均*/
float score[6][6] = { {100, 71, 74, 82, 80, 0}, /* 出席番号1番 */
{ 64, 83, 74, 94, 62, 0}, /* 出席番号2番 */
{ 70, 70, 48, 80, 78, 0}, /* 出席番号3番 */
{ 93, 95, 57, 92, 100, 0}, /* 出席番号4番 */
{ 21, 45, 100, 70, 10, 0}, /* 出席番号5番 */
{ 0, 0, 0, 0, 0, 0} }; /* この行に科目平均を計算して代入 */
/* 注:整数定数は,自動的に実数型に変換される */
/* 表の右端の列と,最下行の 0 の部分に平均を計算 */
strlen関数を自作してみよう.関数名は My_strlen()
とする.もちろんライブラリ関数の strlen() を使用しないこと.
ヒント:ヌル文字の位置を探そう.整数を返す際,ヌル文字は文字数に含まない.
#include <stdio.h>
??? My_strlen(...)
{
....
return ????;
}
int main(void)
{
char str[100] = "Hello Meiji Univ.";
printf("Length = %d\n ", My_strlen(str));
return 0;
}
実行例
Length = 17
キーボードから文字列を入力すると,その最初の文字と最後の文字を表示するプログラムを作成せよ.
ヒント:strlen()
関数に文字列を渡すと,配列中の文字数がわかる.
#include <stdio.h>
#include <string.h> // strlen()用
int main(void)
{
char str[100]; /* 100文字分の文字列 */
printf("Input a string: ");
scanf("%s", str); /* 文字列入力.strは配列なので,&は不要. */
int n = strlen (str); /* nに文字数が入る */
...
return 0;
}
実行例
Input a string: ABCDEFG
The first char is A
The last char is G
Input a string: Z
The first char is Z
The last char is Z
上の本文中で表示したアスキーコード表と同じものを書いてみよ.
この時,画面に印字されない制御コードを書いてしまうと表示が乱れるので,ライブラリ関数isprint()
関数を用いて表示可能なものだけ表示せよ.
isprint()
関数の使用法をまず各自で調べること.
Microsoft社のCライブラリ関数リファレンス(アルファベット順)
https://msdn.microsoft.com/ja-jp/library/634ca0c2.aspx
ヒント:isprintは、文字 c
を渡すと,c が表示可能な文字 (0x20 – 0x7E) である場合,0 以外の値を返す.
つまり,isprint('A') != 0 ,isprint('\n') == 0となる.
isprint()関数の使用例.変数cの値を'A'や'\n'に変えてみよ.
#include <stdio.h>
/* これを追加すること. */
#include <ctype.h>
int main(void)
{
char c='a'; /* 何か文字 */
if( isprint(c) != 0) {
printf("表示可能です\n");
} else {
printf("表示できません\n");
}
return 0;
}