Lesson 8

文字列

Lesson 8 Chapter 1
文字列

C言語の文字列は、文字の配列、つまりchar型などの配列として表現されています。配列の要素は文字(文字コード)で、通常の配列と同様に、添字を使って各要素(文字)を参照したり、値(文字)を格納したりできます。ここでは文字列の仕組み、文字配列の宣言と初期化、文字列の入力について学びます。

文字列リテラル、ナル文字、文字列、空文字列

文字列リテラル

プログラムに文字列を書くには、次のように"(ダブルクォーテーション)で囲んで書きます。これは文字列リテラルと呼ばれます。

文字列リテラル
"文字列"

文字列リテラルを並べて書くと、コンパイル時に1個の文字列へ連結されます。これは長い文字列を書くときや、複数行にわたって文字列を書くときなどに便利です。文字列リテラルと文字列リテラルの間には「空白」「タブ」「改行」を入れることができます。

文字列リテラル連結
"文字列a" "文字列b"

puts関数を使って3個の文字列リテラルに分けて記述し、出力するプログラムを書いてみましょう。

string1.c
#include <stdio.h>
int main(void) 

  puts("START\n"
       "EXECUTE\n"
       "END");

  return 0;
}
実行結果
string1.exe
START
EXECUTE
END

ナル文字

ナル文字とは、特殊な文字であり「文字がない、空である」ということを示す文字のことです。

ナル文字(ヌル文字)

ナル文字のことを「ヌル文字」と呼ぶ流儀もあります。C言語にはヌル文字の他に、ヌルポインタという機能もあります。どちらもヌルですが、ヌル文字とヌルポインタは用途が異なるので、混同しないように注意してください。単に「ヌル」と呼ぶと紛らわしいので「ナル文字」、「ヌル文字」、「ヌルポインタ」のように呼ぶとよいでしょう。

文字列

C言語では、文字列はchar型に配列に格納して扱うことができます。以下のように宣言します。

文字配列の宣言
char 文字配列名[要素数];

空文字列

空文字列は「""」と表現します。これは1つの文字として認識されます。

文字列と配列

先ほども述べましたが、C言語で文字列を型に格納する際には「char型の配列」を使用します。例えば"ABCDE"という文字列を配列に格納するためのコードは以下のようになります。

文字配列
char str[6]
str[0] = 'A';
str[1] = 'B';
str[2] = 'C';
str[3] = 'D';
str[4] = 'E';
str[5] = '\0';

文字配列の初期化の場合は、次のように記述することも可能です。

文字配列の初期化①
char str[6] = "ABCDE";
文字配列の初期化②
char str[] = "ABCDE";

9_1.png

scanfを使った文字列の読み込み

文字列を入力する方法を紹介します。scanf関数を使った方法で、キーボードから入力した文字列を配列に格納し、末尾にナル文字を付けます。以下のように、変換指定子は「%s」を使います。

string2.c
#include <stdio.h>
int main(void) 

  char str[100];
  printf("input string:");
  scanf("%s", str);
  printf("%s\n", str);
  
  return 0;
}
実行結果
string2.exe
input string:1234567890
1234567890

上記の文字配列名には、&(アドレス演算子)がついていません。他の配列と同様に、文字配列名は配列の先頭アドレスを表すので、&でアドレスを取得する必要がありません。ポインタ変数に配列のアドレスを格納することも可能です。アドレスを表示して確認してみましょう。

string2.c
#include <stdio.h>
int main(void) 

	printf("input string:");
    char str1[100];

	// 配列str1の「先頭のアドレス」をポインタ変数に格納
	char *str2 = str1;

	scanf("%s", str2);
	printf("%s\n", str2);
    puts("");
	printf("%p\n", str1);
	printf("%p\n", str2);
    
	return 0;
}
実行結果
string2.exe
input string:1234567890
1234567890

000000000061FDB0
000000000061FDB0

コンパイラによっては要素数以上の文字が入力できてしまう

「char str1[100];」と100文字まで入力できる文字配列を宣言していますが、コンパイラによっては要素数以上の文字列を格納できます。正常に動作するとは限らないので、コンパイラではなく、自分で書いたコードを基準に考えた方がよいでしょう。

ナル文字を利用した繰り返し処理

文字列の長さ(文字数)を調べてみましょう。簡単にするために、ここでは末尾のナル文字を除いた要素数を数えることにします。標準ライブラリのstrlen関数を使うと、文字列の長さを簡単に調べられます。「str」はstring (文字列)、「len」はlength (長さ)の略と考えられます。strlen関数は次のように使います。文字列には、文字列リテラルや文字配列(配列名)を指定します。strlen関数は文字列の長さを、size_t(サイズティー)型の値として返します。size_tは符号なしの整数型で、実際の型は処理系によって異なります。

string2.exe
strlen(文字列)

文字列の長さを調べるプログラムを自作する場合には、例えば次のような方法で長さを調べます。文字列の最初の要素(文字)から始めて、ナル文字でない限り、次の要素に進むことを繰り返します。何回進んだのかをカウントしておけば、これが長さになります。

9_2.png

string3.c
#include <stdio.h>
#include <string.h>
int main(void) 

    char str[] = "C language";

	// strlen関数で文字列の長さを取得
	printf("&d\n", strlen(str));

	// 自作関数で文字列の長さを取得
    int i;
    for(i = 0; str[i]; i++);
	printf("%d\n", i);
    
	return 0;
}
実行結果
string3.exe
10
10

実行結果は10文字でした。strlen関数の結果と一致したので、自作のプログラムは正しく動いていると思われます。上記のプログラムでは、変数iが文字列の長さを表します。iを0で初期化しておき、ヌル文字が出現する(str[i]が0になる)まで、i++で回数を数えます。

大文字と小文字の変換

「ctype.h」をincludeファイルに追加することで、大文字・小文字変換する関数を使用することができます。

toupper関数(小文字を大文字に変換)
int toupper(int c);
tolower関数(大文字を小文字に変換)
int tolower(int c);

今回は標準ライブラリ関数を使用せずに、英大文字から英小文字、英小文字から英大文字に変換する関数を作成します。英小文字や英大文字は「アスキーコード」と呼ばれる対応表で一意に数値が決まっています。英小文字と英大文字の部分のみを下記に抜粋します。

16進数 英大文字 16進数 英小文字
0x41 A 0x61 a
0x42 B 0x62 b
0x43 C 0x63 c
0x44 D 0x64 d
0x45 E 0x65 e
0x46 F 0x66 f
0x47 G 0x67 g
0x48 H 0x68 h
0x49 I 0x69 i
0x4A J 0x6A j
0x4B K 0x6B k
0x4C L 0x6C l
0x4D M 0x6D m
0x4E N 0x6E n
0x4F O 0x6F o
0x50 P 0x70 p
0x51 Q 0x71 q
0x52 R 0x72 r
0x53 S 0x73 s
0x54 T 0x74 t
0x55 U 0x75 u
0x56 V 0x76 v
0x57 W 0x77 w
0x58 X 0x78 x
0x59 Y 0x79 y
0x5A Z 0x7A z

このアスキーコードをよく見てください。規則性があるのがわかります。
・A、B、C…の順に16進数の数が1ずつ増えている
・Aとa、Bとb、Cとc…の差は20
値は16進数ですが、数字であることには変わりません。Lesson6で基数について学習しましたが、16進数を書くときは先頭に「0x」の「整数定数」をつけます。A~Zまでの数の範囲は「0x41」~「0x5A」、a~zまでの数の範囲は「0x61」~「0x7A」、あとは規則性に従って、演算を行えば値の変換は可能です。プログラムのイメージができたら、コードを書いていきましょう。main関数のほかに、toUpper関数(英子文字 → 英大文字)とtoLower関数(英大文字 → 英子文字)を作成します。char型の'a'を'A'に変換するためにはどういった計算が必要になるか、関数を作成するためには引数はどうするか、処理を細分化して考えるとイメージしやすいと思います。

string4.c
#include <stdio.h>

int toUpper(int character) {

	//	英小文字の場合は英大文字へ変換
	if ((character >= 'a') && (character <= 'z')) {
      // 20を減算
      return c - 0x20;
    }

	//	英小文字以外の場合
	return character;
}

int toLower(int character) {

	//	英大文字の場合は英小文字へ変換
	if ((character >= 'A') && (character <= 'Z')) {
      // 20を加算
      return character + 0x20;
    }
    
    

	//	英大文字以外の場合
	return character;
}

int main(void)
{
	printf("toUpper\n");
	printf("a => %c\n", toUpper('a'));
	printf("b => %c\n", toUpper('b'));
	printf("c => %c\n", toUpper('c'));
	printf("d => %c\n", toUpper('d'));
	printf("e => %c\n", toUpper('e'));

	printf("toLower\n");
	printf("A => %c\n", toLower('A'));
	printf("B => %c\n", toLower('B'));
	printf("C => %c\n", toLower('C'));
	printf("D => %c\n", toLower('D'));
	printf("E => %c\n", toLower('E'));

	return 0;
}
実行結果
string4.exe
a => A
b => B
c => C
d => D
e => E
A => a
B => b
C => c
D => d
E => e

文字の範囲をチェックして、変換対象の文字かどうかをif文で判定します。その後、英大文字であれば「- 0x20」、英子文字であれば「+ 0x20」という計算を行って対象文字を変換します。完成したコードを確認すると、コード自体はシンプルで難しい処理は行っていないことが分かります。