第1週 文字列
INDEX

本日の目標

  • 文字列について知る。
  • ポインタを使って文字列を操作できる。

予習・復習

 以下のスライドを利用して、予習と復習をしよう。復習では、自分の理解度を確認するために、実際にプログラムを作成し、意図する結果が得られるか確認しよう。
  1. 文字列

本日の講義・演習予定

  1. 文字型と文字コード
  2. 文字列
  3. 文字列とポインタ
  4. 文字列配列とポインタ
  5. 文字列を扱う標準関数
  6. 演習問題
  7. 提出課題
内 容
  1. 文字型と文字コード
  2. 文字列
  3. 文字列とポインタ
  4. 文字列配列とポインタ
  5. 文字列を扱う標準関数


文字型と文字コード

文字型

 C言語で整数を扱うための主な型には、下表で示すようにchar型やint型などがあります。この中で、char型は通常、文字を表現するための型として利用されます。char型のサイズは1Byte(8bit)あり、半角の英数字や文字、記号などの1Byteのデータ幅で表現できる1バイト文字を表現することができます。

主な整数型の型とそのサイズ
宣言サイズ表現範囲
char1-128 〜 127var_char1
short int2-32768 〜 32767var_shortint2
int4-2147483648 〜 2147483647var_int4
long int4-2147483648 〜 2147483647var_int4
※1 サイズはchar型を1としたもので、その大きさは実行環境によって異なる。
※2 表現範囲も実行環境によって異なる。
※3 上記とは別に最上位ビットを符号として扱わないunsigned型を宣言できる

sizeof演算子

 sizeof演算子は型の大きさを調べることのできる演算子です。このsizeof演算子とライブラリlimits.hで定義されている各型の最大値と最小値の定数を使って、あなたの実行環境における各型の扱える値の範囲とデータサイズを調べてみましょう。
sizeof_type.c
#include <stdio.h>
#include <limits.h>

int main(void)
{
     printf("型        : min -- max(size)\n");
     printf("----------:---------------------------\n");
     printf("char      : %d -- %d(%u)\n",CHAR_MIN,CHAR_MAX,(unsigned)sizeof(char));
     printf("short int : %d -- %d(%u)\n",SHRT_MIN,SHRT_MAX,(unsigned)sizeof(short));
     printf("int       : %d -- %d(%u)\n",INT_MIN,INT_MAX,(unsigned)sizeof(int));
     printf("long int  : %ld -- %ld(%u)\n",LONG_MIN,LONG_MAX,(unsigned)sizeof(long));
 
 
     return 0;
}

文字コード

 コンピュータの内部で扱えるのは2進数だけでした。文字を扱うことはできません。そこで、文字を0と1からなる2進コードで置き換えてコンピュータで扱えるようにしました。これを文字コードと言います。下表に示したASCIIコードは、1963年にアメリカで生まれた標準規格です。一つの文字を7桁のコードで表しています。しかし、1バイト(8bit)を基本的な単位とするコンピュータでは、都合が悪いためASCIIコードをベースとして8ビットに拡張されたコードが利用されます。日本独自の規格としてJISコード(JIS X 0201)があります。

数字とアルファベットのASCIIコード表
数字16進数小文字16進数大文字16進数小文字16進数大文字16進数
030a61A41n6EN4E
131b62B42o6FO4F
232c63C43p70P50
333d64D44q71Q51
434e65E45r72R52
535f66F46s73S53
636g67G47t74T54
737h6H48u75U55
838i69I49v76V56
939j6AJ4Aw77W57
k6BK4Bx78X58
l6CL4Cy79Y59
m6DM4Dz7AZ5A

 次のプログラムは文字とそのASCIIコードを16進数で表示する例です。
char_code.c
#include <stdio.h>

int main(void)
{
	char ch;

	ch = 'A';				//文字’A’を代入する
	printf("%c(%#x)\n",ch,ch);	//文字と16進数で表示する
	
	ch = 0x41;				//16進数を代入する
	printf("%c(%#x)\n",ch,ch);	//文字と16進数で表示する
	
	return 0;
}
 ※ 変換指定子%#x は16進数表記において先頭部分に、0xを付け加えて表示する。

 次のプログラムは文字一覧とそのASCIIコードを表示する例です。文字はコンピュータ内部ではコード(数値)として表現されていることを利用して、その値を1ずつ増加させることで全ての文字の一覧の表示を実現しています。
ascii_code.c
#include <stdio.h>

int main(void)
{
	int i;

	for(i=0; i<16; i++){
		printf("%c(%x) ",0x20+i,0x20+i);
		printf("%c(%x) ",0x30+i,0x30+i);
		printf("%c(%x) ",0x40+i,0x40+i);
		printf("%c(%x) ",0x50+i,0x50+i);
		printf("%c(%x) ",0x60+i,0x60+i);
		printf("%c(%x)\n",0x70+i,0x70+i);
	}
	return 0;
}

文字列

 C言語には文字列を直接扱うことのできる型はありません。char型の配列を用意し、その各要素に一文字ずつを格納することで文字列を表現します。例えば、要素数6個からなる文字配列sを用意し、文字列"music"を扱いたければ、
	char s[6];

	s[0] = 'm';
	s[1] = 'u';
	s[2] = 's';
	s[3] = 'i';
	s[4] = 'c';
	s[5] = '¥0';		//文字列の最後を示すナル文字

と記述することができます。このとき、ナル文字と呼ばれる特殊な文字'¥0'(もしくは'\n')を最後に付け加えることで、文字列の最後を示すようにします。
 また、初期化の場合に限れば、下記のように記述することもできます。
	char s[6] = {'m', 'u', 's', 'i', 'c', '¥0'};

 次のように文字列リテラルで初期化することもできます。(文字列を直接記述したものを文字列リテラルといいます。)この場合、明示的には記述されませんが、文字列の最後にナル文字'¥0'が含まれています。
	char s[6] = "music";
さらに、要素数を省略して書くこともできます。
	char s[] = "music";

いずれにせよ、文字列"music"は下記の図のように文字配列に格納されることになります。
string
文字列の最後には、ナル文字¥0が含まれる

文字列を一文字ずつ表示する

print_str1.c
#include <stdio.h>

int main(void)
{
	char s[6] = {'m', 'u', 's', 'i', 'c', '¥0'};
	int i=0;
	
	while(s[i] != '¥0'){				//ナル文字が現れるまで繰り返す
		printf("%c", s[i]);
		i++;
	}

	printf("¥n");
	
	return 0;
}

文字列を表示する

 文字配列を文字列"music"で初期化して、それを変換指定%sを使って表示する例です。変換指定%sは、ナル文字までの文字列を表示してくれます。
print_str2.c
#include <stdio.h>

int main(void)
{
	char s[6] = "music";	//文字列の最後にはナル文字がある

	printf("%s¥n", s);		//文字列を表示する。

	return 0;
}
文字列を格納する配列の要素数は、ナル文字¥0の分を+1する。
文字列の変換指定は、%s

変換指定%sはナル文字までを表示する。

 変換指定%sは文字配列にナル文字が登場するまでの文字を表示します。文字列の途中にナル文字を代入する例を見てみましょう。
print_str3.c
#include <stdio.h>

int main(void)
{
	char s[6] = "music";	//文字列の最後にはNULL文字がある

	s[3] = '¥0';			//途中にナル文字を代入
	printf("%s¥n", s);		//"mus"までが表示される。

	return 0;
}

空白文字を含む文字列の入力

 空白文字を含む文字列をscanfを使って読み込むと、空白文字を文字列の終端と見なすため最後まで読み込むことができません。そこで、一文字を読み込む関数getcharを使います。このgetcharを繰り返し呼び出すことで、空白文字を含む文字列を読み込むようにします。次の例では、改行コード'\n'が入力されたところで読み込みを終了するようにしています。ただし、そのままでは文字列の最後を示すNULL文字'\0'がないので、NULL文字を追加する処理が必要になります。
getchar.c
#include <stdio.h>
 
int main(void)
{
	int i = 0;
    char ch;
    char str[32];
				      
    printf("文字列>> ");
    while((ch = getchar()) != '\n') str[i++]=ch;
    str[i] = '\0';			//NULL文字の追加
						   
    printf("%s\n", str);
							        
    return 0;
 }

文字列とポインタ

ポインタで文字列を表示する

 次のプログラムは文字列をポインタを使って画面に表示する例です。char型へのポインタstrは、文字リテラル"Apple"を指し示すように初期化されています。これは、メモリの定数領域に文字列リテラル"Apple"を格納し、その先頭アドレスがポインタstrに代入されることを意味します。関数printfにポインタstr、すなわち文字列リテラル"Apple"が格納されているメモリの先頭アドレスが渡されることで、"Apple"と画面に表示しています。
例 pstring.c
#include <stdio.h>

int main(void)
{
	char *str ="Apple";
	
	printf("%s",str);
	return 0;
}

 次のプログラムでは、ある文字列を指し示していたポインタが別の文字列を指し示すように変更されています。文字列リテラル"Apple"も"Banana"もメモリの定数領域に格納されています。最初に文字リテラル"Apple"を指し示していたポインタstrですが、その後、"Banana"を代入することで文字列リテラル"Banana"の先頭アドレスで書き換えられます。
例 pstring.c
#include <stdio.h>

int main(void)
{
	char *str ="Apple";
	
	printf("%s",str);

	str = "Banana"; //"Banana"の先頭アドレスを代入
	printf("%s",str);
	
	return 0;
}
str_pointer

ポインタで文字列を操作する

 次のプログラムは、ポインタを使って文字列を1文字ずつ表示する例です。関数print_string(char *s)は文字列sの先頭アドレスを引数で受け取り、その文字列のNULL文字が現れるまで先頭から順に文字を表示するように定義しています。
例 printstring.c
#include <stdio.h>

void print_string(char *s);

int main(void)
{
	char str[] ="Apple";
	
	print_string(str);
	
	return 0;
}

void print_string(char *s)
{
	while(*s != '\0'){ 	 //NULL文字が現れるまで繰り返す
	   putchar(*s++);
	}
}

 次に、文字列をコピーする関数を作ってみます。関数str_copyは2つのchar型ポインタを引数とします。一つ目の引数datがコピー先の文字列を、二つ目の引数srcがコピー元の文字列を表します。while文では'\0'に到達するまで一文字ずつコピーしますが、NULL文字'\0'がコピーされないので、その後でNULL文字のコピーをしていることに注意しましょう。
例 str_copy.c
#include <stdio.h>

char* str_copy(char *dat, char *src);

int main(void)
{
	char str1[8] ="Apple";
	char str2[8];

	printf("%s\n",str_copy(str2,str1));
	
	return 0;
}

char* str_copy(char *dat, char *src)
{
	char *p = dat;
	
	while(*src != '\0') *dat++ = *src++;
	*dat = *str;			//NULL文字のコピー
	
	return p;
}


文字列配列とポインタ

文字列配列と2次元配列

 文字列は文字の集まりである1次元配列で扱われます。複数の文字列をまとめて扱いたい場合には文字列の配列、すなわち文字の2次元配列として扱うことができます。以下に、3つの文字列を文字列配列(文字の2次元配列)で扱った例を示します。
例 str_array.c
#include <stdio.h>

int main(void)
{
	char str[3][6] = {"One", "Two", "Three"};		//文字列配列の初期化
    int i;
    
    for(i=0; i<3; i++)
		printf("%s\n", str[i]);		//各文字列の先頭アドレスstr[i]

	return 0;
}

ポインタを使って文字列配列を操作する

 文字列配列をポインタを使って扱ってみます。それぞれの文字列を指し示すためには文字列分のポインタが必要になります。文字列ごとにポインタを用意することもできますが、ポインタを要素とするポインタ配列を用意すれば、同じ名前でまとめて扱うことができるようになります。次のプログラムの関数print_stringではポインタの配列とその個数を引数として受け取り、複数の文字列をまとめて表示しています。
例 str_array.c
#include <stdio.h>

void print_string(char *p[], int num);

int main(void)
{
	char *strp[3] = {"One","Two","Three"};

	print_string(strp,3);	
	return 0;
}

void print_string(char *p[], int num)
{
	int i;

	for(i=0; i<num; i++){
		printf("%s\n",*(dp+i));
	}
}

ダブルポインタ

 ダブルポインタはポインタを指し示すポインタでした。別の言い方をすると、ポインタが格納されているメモリの先頭アドレスを格納するポインタです。
 次のプログラムは、複数の文字列を指し示すポインタ配列の関数間での受け渡しを、ダブルポインタを使って実現した例です。ポインタ配列*strp[3]の配列名strpはポインタ配列の開始アドレスを指し示すポインタとして利用できます。ポインタ配列の各要素には文字列の格納先の先頭アドレスが格納されており、この先頭アドレスの格納先を示すポインタが配列名であるstrpということになります。すなわちstrpはポインタが格納されている場所を示すポインタと言えます。
例 double_pointer.c
#include <stdio.h>

void print_string(char **dp, int num);

int main(void)
{
	char *strp[3] = {"One","Two","Three"};

	print_string(strp,3);	//ポインタ配列の開始アドレスを渡す
	return 0;
}

void print_string(char **dp, int num)	//ダブルポインタ
{
	int i;

	for(i=0; i<num; i++){
		printf("%s\n",*(dp+i));
	}
}

 以下は、上記のプログラムにおいて、ポインタ配列をダブルポインタを使って関数間で共有していることを確認するために、ポインタ配列とダブルポインタのアドレスの表示を追加したプログラム例です。
double_pointer_address.c
#include <stdio.h>

void print_string(char **dp, int num);

int main(void)
{
	char *strp[3] = {"One","Two","Three"};
	int i;

	printf("ポインタ配列strpの開始アドレス: %p\n",strp);
	for(i=0; i<3; i++)
		//ポインタ配列の要素の格納先アドレス、アドレス,アドレス
		printf("&strp[%d] %p -> %p[ %s ]\n",i,&strp[i],strp[i],strp[i]);

	print_string(strp,3);	

	return 0;
}

void print_string(char **dp, int num)
{
	int i;

	for(i=0; i<num; i++){
		printf("   dp+%d: %p -> %p[ %s ]\n",i,dp+i,*(dp+i),*(dp+i));
	}
}


文字列を扱う標準関数

 C言語には文字列を扱うための標準ライブラリstring.hが、また数字からなる文字列を数値へ変換するための標準ライブラリstlib.hが用意されています。以下に各ライブラリで定義されている主な関数を示します。

文字列の長さを求める

strlen
ヘッダstring.h
形式size_t strlen(const char *str)
説明引数strの文字列の長さ。null文字は含めない。
戻り値引数strの文字列の長さ(文字数)

文字列をコピーする

strcpy
ヘッダstring.h
形式char *strcpy(char *s1, const char *s2)
説明引数s2が示す文字列を、引数s1が示す配列へコピーする。
戻り値引数s1の値
strncpy
ヘッダstring.h
形式char *strncpy(char *s1, const char *s2, size_t n)
説明引数s2が示す文字列を、引数s1が示す配列へコピーする。ただし、コピーする最大文字数はnまでとする。s2が示す文字列の長さがn以上の場合は以降をnull文字で埋め尽くす。
戻り値引数s1の値

文字列を連結する

strcat
ヘッダstring.h
形式char *strcat(char *s1, const char *s2)
説明引数s1が示す文字列の後に、引数s2が示す文字列を連結する。。
戻り値引数s1の値
strncat
ヘッダstring.h
形式char *strncat(char *s1, const char *s2, size_t n)
説明引数s1が示す文字列の後に、引数s2が示す文字列を連結する。ただし、連結可能な引数s2が示す文字列の最大文字数はnまでとする。
戻り値引数s1の値

文字列の大小関係

strcmp
ヘッダstring.h
形式size_t strcmp(const char *s1, const char *s2)
説明引数s1が示す文字列と引数s2が示す文字列の大小関係(辞書的順序)を調べる。
戻り値等しいときには 0 を, s1の方が大き時には正の整数を、s1 の方が小さいときには負の値を返す。
strncmp
ヘッダstring.h
形式size_t strncmp(const char *s, const char s2, size_t n)
説明引数s1が示す文字列と引数s2が示す文字列の先頭からn文字目までの大小関係(辞書的順序)を調べる。
戻り値等しいときには 0 を, s1の方が大き時には正の整数を、s1 の方が小さいときには負の値を返す。

文字列・数値変換

atoi
ヘッダstlib.h
形式int ato(const char *str)
説明引数strが示す文字列を、int型の値に変換する。
戻り値変換された値
atol
ヘッダstlib.h
形式long atol(const char *str)
説明引数strが示す文字列を、long型の値に変換する。
戻り値変換された値
atof
ヘッダstlib.h
形式double atof(const char *str)
説明引数strが示す文字列を、double型の値に変換する。
戻り値変換された値


標準ライブラリを利用したプログラム例

stdfuncs.c
#include <stdio.h>
#include <string.h>

int main(void)
{
	char str1[12] = "Peach";
	char str2[12] = "Beach";
	char str3[12];

	printf("str1=%s str2=%s\n",str1,str2);

	printf("[strlen] str1の文字数は、%lu個です。\n",strlen(str1)); //文字数

	if(strcmp(str1,str2))	//比較
		printf("[strcmp] str1とstr2は異なる文字列です。\n");
	else
		printf("[strcmp] str1とstr2は同じ文字列です。\n");

	strcpy(str3,str1);	//copy
	printf("[strcpy] copy str3 from str1\"%s\"\n",str3);

	strcat(str1,str2);	//連結
	printf("[strcat] str1 + str2 -> %s\n",str1);

	return 0;	
}

expand_lessBack to TOP

演習問題

  1. 半角文字を入力して、小文字なら大文字へ、大文字なら小文字へ変換して表示するプログラムを作成しなさい。ただし、小文字を大文字に変換する関数char upper_char(char c)と、大文字を小文字に変換する関数char lower_char(char c)を定義して利用すること。

  2. 半角からなる文字列を読み込み、先頭文字だけを大文字に、それ以外は小文字へ変換するプログラムを作成しなさい。ただし、先頭文字が小文字なら大文字へ、それ以外の文字が大文字なら小文字へ変化する関数void proper(char str[])を定義して利用すること。

  3. 文字列と文字を入力して、文字列の中に文字が含まれているかを判定するプログラムを作成しなさい。ただし、文字列の中に文字が含まれている場合は1を、そうでない場合は0を戻り値とする関数int char_in_str(char *s, char c)を定義して利用しなさい。また、標準ライブラリ関数は利用できないものとする。

  4. 二つの文字列を入力して、等しいかどうかを判定するプログラムを作成しなさい。ただし、文字列が等しければ1をそうでなければ0を戻り値とする関数int match(char *s1, char *s2)を定義して利用しなさい。また、標準ライブラリ関数は利用できないものとする。

  5. 文字列を入力して、その文字を逆順に並び替えるプログラムを作成しなさい。ただし、文字列を逆順に並べ替える関数void revers(char *str)を定義して利用しなさい。また、標準ライブラリ関数は利用できないものとする。

  6. 次のプログラムは5つの文字列をアルファベット順に並べ替えるプログラムです。ただし、文字列は定数であるため、それを指し示すポインタ配列を並べ替えることで実現しています。また、並べ替えは文字列の先頭文字のみを対象としています。空欄を埋めて完成させなさい。
  7. strings_sort.c
    #include <stdio.h>
    
    void sort_strp(char **str, int num);
    
    int main(void)
    {
    	char *strp[5] = {"Banana","Orange","Apple","Strawberry","Lemon"};
    	int i;
    	
    	sort_strp(strp,5);
    	for(i=0; i<5; i++)
    		printf("%s\n",strp[i]);
    
    	return 0;
    }
    
    void sort_strp([[空欄ア]], int num)
    {
    	int i,j;
    	char *tmp;
    
    	for(i=0; i<num-1; i++){
    		for(j=i+1; j<num; j++){
    			if([[空欄イ]] > [[空欄ウ]]){
    				tmp = [[空欄エ]];
    				[[空欄エ]] = *(str+j);
    				*(str+j) = tmp;
    			}
    		}
    	}
    }
    

  8. N人の氏名を入力して、その結果を表示するプログラムを作成しなさい。

  9. 学生の情報を表す学籍番号、氏名、得点を入力して、その結果を表示するプログラムを作成しなさい。ただし、学生の情報は学籍番号、氏名、得点からなる構造体studentを定義して、これを型として扱うこと。

OPEN ANSWER
  1. 回答例
  2. code/1/ex1-1.c

  3. 回答例
  4. code/1/ex1-2.c

  5. 回答例
  6. code/1/ex1-3.c

  7. 回答例
  8. code/1/ex1-4.c

  9. 回答例
  10. code/1/ex1-5.c

  11. 回答例
  12. ア.**str イ.*(str+i)[0] ウ.*(str+i)[0] エ. *(str+i)

  13. 回答例
  14. code/1/ex1-7.c

  15. 回答例
  16. code/1/ex1-8.c



expand_lessBack to TOP

今週の確認テスト テスト(PDF)

本日の提出課題

 二つの文字列を入力して、それらを結合して一つの文字列にした後、先頭文字だけを大文字にした結果を表示する下記のプログラムkadai4-1.cを完成させなさい。文字列の結合には3つの文字列str、str1、str2を引数として、文字列str1と文字列str2を結合した文字列をstrとする関数void join(char *str, const char *str1, const char *str2)を定義して利用すること。先頭文字だけを大文字に変換するには、文字列strを引数として、その文字列strの先頭文字を大文字に、残りの文字を小文字へ変換する関数proper(char *str)を定義して利用すること。ただし、標準ライブラリstring.hは利用できないものとします。また、入力される文字は半角アルファベットのみと想定して良い。
[実行例]
文字列1> baum 文字列2> Kuchen Baumkuchen
[プログラム kada4-1.c]
#include <stdio.h>

void join(char *str, const char *str1, const char *str2)
{
	/*
	ここを埋めて完成させる
	*/	
}

void proper(char *str)
{
	/*
	ここを埋めて完成させる
	*/		
}

int main(void)
{
	char s1[32],s2[32],s[32];

	printf("文字列1> ");
	scanf("%s",s1);
	printf("文字列2> ");
	scanf("%s",s2);

	join(s,s1,s2);
	proper(s);
	printf("%s\n",s);

	return 0;
}
	


  • [提出方法]
    • 電子メールの添付ファイルとして提出してください。宛先は指定のアドレスです。
    • 表題は、課題1とします。
    • 本文中には、苦心した点などを記入してください。
    • 添付ファイルは下記の2点です。
    • 1) ソースコード・ファイル kadai4-1.c
      2) 実行結果画面(ウインドウのみ)のハードコピーの画像ファイル kadai4-1.jpg

  • [実行結果画面のハードコピーの取り方]
    1. [メニュー]→[アクセサリ]→Snipping Toolを起動する。ただし、Snipping Toolのウインドウが実行画面に重ならないように、必要に応じてウインドウを移動すること。
    2. タイトルバーを含めた実行結果画面をマウスドラッグして選択して、キャプチャする。
    3. メニューから「ファイル」→「名前をつけて保存」を選択する。ファイルの種類にはJPG形式を選択して、保存先フォルダを選び、ファイル名を付けて「保存」する。
    ※Windows10(creators update以降)では、Windows+Shift+S キーを押して、キャプチャしたい矩形領域をドラッグして、クリップボードにコピーします。アプリ「切り取り&スケッチ」が起動したら、画像をJPG形式で保存します。

  • [評価について]
  • プログラムの内容の評価とは別に以下の要件を満たすことを評価の前提とする。
    1. ソースコードがC言語で記述されていること。
    2. 提出されたソースコードファイルをコンパイルし、ワーニングやエラーが出力されないこと。scanfをscanf_sとすべきワーニングについては除外する。
    3. 提出されたソースコードファイルから生成される実行形式ファイルを実行した結果、所定の出力結果が得られること。
    4. 提出されたソースコードは、インデントや適当な改行が施された見やすい状態であること。
    5. 変数の利用目的、処理や判定の意味など必要と思われる解説を簡潔にコメント文として付けること。
    6. 提出された実行結果の画面コピーは、コマンドプロンプトのウインドウのみとすること。

  • [提出期限]
  • 2019年 11月20日(水)午後2時まで