文字列とポインタ
C言語での文字列は、char型の配列として定義され、各配列要素が一つの文字を表すものです。配列をポインタで操作するように、文字列もポインタを通じて参照や操作することができます。
この章では、ポインタを使用して文字列を操作する方法について説明します。
関数の引数に文字列を渡す
文字列の名前を引数に指定することで、配列と同じように文字列の先頭アドレスを引数として渡すことが可能です。
このサンプルプログラムは、関数str_len()を使用して文字列の長さを取得する処理を行っています。
#include <stdio.h>
int str_len(char a[]){
int len=0;
while(a[len]!='\0'){
len++;
}
return len;
}
int main(void){
char a[]="Hello";
int len=str_len(a);
printf("%sの文字列の長さは%dです\n",a,len);
return 0;
}
関数str_len()は、char型の配列を指すポインタa[]を仮引数として定義しています。
while(a[len]!=’\0′)は、終端文字(’\0’)が出現するまで、その添字を1つずつ増やしていきます。
int len = str_len(a); は、文字列の先頭アドレスを実引数として関数を呼び出しています。
関数strr_len()は、ポインタ変数を仮引数として以下のように定義することができます。
int str_len(char *a){
int len = 0;
while(*(a+len) != '\0'){
len++;
}
return len;
}
先に示したサンプルプログラムをポインタ変数を使用する形に書き換えています。
また、ポインタ変数を用いて以下の記述も可能です。
int str_len(char *a){
char *b = a;
while(*b != '\0'){
b++;
}
return b - a;
}
char *b = a; では、ポインタ変数bが作成され、仮引数aと同じアドレスを指します。
b++; このコードは、ポインタ変数bが文字列の終端文字(‘\0’)を指していない限り、ポインタ変数bの値を1ずつ増やします。これにより、bが指すアドレスはsizeof(char)の値だけ増加します。
return b – a; この式は、文字列の先頭を指しているポインタ変数aと文字列の末尾を指しているポインタ変数bの差が文字列の長さを表すことを示しています。
文字列の宣言方法について
文字列はchar型(文字型)の配列として扱われ、配列の特性を持ちます。
例えば、char str[] = “Hello”; と宣言すると、各文字は連続するメモリ位置に格納され、最後に自動的にヌル文字(\0)が追加されます。
一方、ポインタとして宣言することもできます。
例えば、char *str = “Hello”;のように記述します。
””(ダブルクォーテーション)に囲まれた文字列を文字列リテラルと言います。
文字列リテラル内の各文字は、読み取り専用の静的メモリ領域にある連続したメモリ位置に順に格納されます。
たとえば、「Hello」という文字列リテラルは、H、e、l、l、oの順番でメモリに配置され、最後に自動的にヌル文字(\0)が追加されます。
char *str = “Hello”;のchar *strは、読み取り専用の静的メモリ領域に格納されているアドレスを指します。
文字列リテラルは読み取り専用のメモリ領域に保存されるため、その値を変更しようとすると未定義の動作が発生します。これは通常アクセス違反を引き起こしますが、環境によって結果は異なる場合があります。
同じ文字列リテラルが複数回使用された場合、コンパイラはメモリの効率的な使用のために、同じメモリ領域を共有することがあります。
以下のような記述はできません。
char *p = "Hello!";
p[0] = 'N';
以下のような記述はできます。
char a[] = "Hello!";
a[0] = 'N';
char *p1 = a;
p1[0] = 'N';
C言語で用意されている文字列操作関数について
上で紹介した文字列の長さを取得する関数はC言語ではstrlen()として提供されています。
C言語にはstrlen()以外にも多くの便利な文字列関数があります。例えば、strcpy()は文字列をコピーするのに、strcat()は二つの文字列を連結するのに、strcmp()は二つの文字列を比較するのに使用されます。これらの関数は、C言語での文字列操作においてよく使われます。
strlen関数
この関数は文字列の長さを取得する関数です。指定されたアドレスから終端文字(’\0’)までのバイト数を数えますが、終端文字は長さには含まれません。
strlen()関数の書式は次の通りです。使用する際にはstring.hをインクルードする必要があります。
#include <string.h>
size_t strlen(const char* str);
引数:const char* str
文字列へのアドレスを指定します。
※constは変数を宣言するときに使用する修飾子の一つで、値を変更できなくする働きがあります。
戻り値:size_t型の値
引数strの文字列の長さを返します。
※size_tは符号なし整数型であり、「長さ」を保存するのに使用されます。
以下にstrlen()を使用したサンプルを示します。
#include <stdio.h>
#include <string.h>
int main(void){
char s[] = "0123456789";
size_t len;
len = strlen(s);
printf("len=%lu\n",len);
return 0;
}
実行結果
len=10
strcpy関数
文字列を別の文字列にコピーする関数です。
strcpy()関数の書式は次の通りです。使用する際にはstring.hをインクルードする必要があります。
#include <string.h>
char *strcpy(char* dest, const char* src);
引数:
char* dest:コピー先文字列のアドレス
const char* src:コピー元文字列のアドレス
戻り値:char *
コピーした文字列の先頭アドレスを返します。
(第1引数のdestと一致します)
【注意】
destにはsrcの内容をコピーするのに十分なスペースが必要です。destが確保している容量がsrcのコピーに不十分であってもコピー処理は実行されますが、これにより意図しないメモリ領域への書き込みが発生する可能性があるため、注意が必要です。
以下にstrcpy()を利用したサンプルを示します。
#include <stdio.h>
#include <string.h>
int main(void){
char s[] = "0123456789";
char cp[20];
strcpy(cp,s);
printf("cp=%s\n",cp);
return 0;
}
実行結果
cp=0123456789
strcat関数
strcat()関数は、2つの文字列を連結するための関数です。
strcat()関数の書式は次の通りです。使用する際にはstring.hをインクルードする必要があります。
#include <string.h>
char *strcat(char *dest, const char *src);
引数:
char *dest:連結先の文字列のポインタを指定します。
この文字列の後ろに別の引数srcが連結されます。
const char *src:連結する文字列
戻り値:
char *:この関数は、第1引数に渡された文字列(char *dest)のポインタを返します。
これは、連結操作後の結果として得られる文字列の先頭アドレスと同じです。
【補足】
destの内容はそのまま保持し、その後にsrcを連結し、文字列の最後に終端文字(‘\0’)が追加されます。
通常、戻り値を直接使用する必要はありません。関数を呼び出して連結操作を行うだけで、結果は第一引数の文字列に保存されることを理解してください。
【注意】
destにはsrcの内容を追加するのに十分なスペースが必要です。destが確保している容量がsrcを追加に不十分であっても実行されますが、これにより意図しないメモリ領域への書き込みが発生する可能性があるため、注意が必要です。
以下にstrcat()を使用したサンプルを示します。
#include <stdio.h>
#include <string.h>
int main() {
char str1[20] = "0123456789";
char str2[] = "abcdef";
strcat(str1, str2);
printf("str1=%s\n", str1);
return 0;
}
実行結果
str1=0123456789abcdef
strcpy_s、strcat_s関数について
strcpy()やstrcat()は、コピー先や連結先のバッファが不足していても実行されるため、注意が必要です。
一方、strcpy_s()やstrcat_s()のようなセキュリティ機能を強化した関数も存在します。これらは実行時にバッファのサイズをチェックし、セキュリティを向上させるために推奨されています。実際の開発現場では、これらのセキュリティを強化した関数が使用されていることが多いです。
各関数の書式は以下の通りです。出力用の領域のサイズが引数として追加されています。
#include <string.h>
errno_t strcpy_s(char *dest, rsize_t dest_size, const char *src);
errno_t strcat_s(char *dest, rsize_t destsz, const char *src);
戻り値:
errno_tはエラー番号を返すint型として定義されています。
関数が正常に終了したときは0、異常が発生した場合は0以外の値が戻ります。
2つの関数を使用したサンプルを以下に示します。
#include <stdio.h>
#include <string.h>
int main(void){
char dest[] = "0123456789";
char src[] = "abcdef";
char cp[10];
int ret;
ret=strcpy_s(cp,sizeof(cp),dest);
if(ret==0){
printf("cp=%s\n",cp);
}else{
printf("ret=%d\n",ret);
}
ret = strcat_s(dest,sizeof(dest),src);
if(ret==0){
printf("dest=%s\n",dest);
}else{
printf("ret=%d\n",ret);
}
return 0;
}
実行結果
ret=34
ret=34
上記のサンプルでは、出力先の領域サイズが不足しているため、2つの関数から返されたエラー値を表示しています。
※これらの関数はVisual Studio環境では使用可能ですが、gccのバージョンや環境によっては対応していないことがあります。そのため、ご使用の環境で動作するかどうかをご確認ください。
strcmp関数
この関数は、二つの文字列が同じかどうかを比較します。
strcmp()関数の書式は次の通りです。使用する際にはstring.hをインクルードする必要があります。
#include <string.h>
int strcmp( const char *str1, const char *str2 );
引数:
const char *stt1:比較する文字列を指すポインタ
const char *str2:比較する文字列を指すポインタ
戻り値:
負の値:辞書順で str1がstr2より前に現れる場合
0:str1 と str が等しい場合
正の値:辞書順で str1がstr2 より後に現れる場合
以下にstrcmp()を利用したサンプルを示します。
#include <stdio.h>
#include <string.h>
void show(const char *p1,const char *p2){
int ret = strcmp(p1,p2);
if(ret == 0 ){
printf("%sと%sは等しいです。\n",p1,p2);
}else if(ret < 0){
printf("%s < %s\n",p1,p2);
}else{
printf("%s > %s\n",p1,p2);
}
}
int main(void){
char s1[] = "abcdef";
char s2[] = "abcdef";
char s3[] = "abcdefg";
char s4[] = "abcdee";
int ret;
show(s1,s2);
show(s1,s3);
show(s1,s4);
return 0;
}
実行結果
abcdefとabcdefは等しいです。
abcdef < abcdefg
abcdef > abcdee
また、文字列の配列のソートをするときにも使用できます。
#include <stdio.h>
#include <string.h>
void array_sort(char array[5][10]){
char tmp[10];
for(int i=0;i<5;i++){
for(int j=i+1;j<5;j++){
if(strcmp(array[i],array[j])>0){
strcpy(tmp,array[j]);
strcpy(array[j],array[i]);
strcpy(array[i],tmp);
}
}
}
}
int main(void){
char array[5][10]={
"abcdef","abcdeg","abcdee","abcdea","abc"
};
array_sort(array);
for(int i=0;i<5;i++){
printf("array[%d]=%s\n",i,array[i]);
}
return 0;
}
実行結果
array[0]=abc
array[1]=abcdea
array[2]=abcdee
array[3]=abcdef
array[4]=abcdeg
文字列操作の関数をいくつか紹介しましたが、他にも様々な関数が存在します。全てを覚える必要はありませんが、どのような関数が存在するかを知っておくと、必要な時に適切な機能を実装するのに役立ちます。
コメント