C言語学習記録(12) 文字列と文字列関数

1. 文字列および文字列 I/O

文字列と書式設定された入出力のセクションで述べたように、文字列は null 文字「 \0 」で終わる char 型の配列です。文字列は非常に一般的に使用されるため、C には文字列を特別に扱う関数が多数用意されています。ここでは、文字列のプロパティ、文字列の宣言と初期化の方法、プログラムでの文字列の入力と出力、および文字列の操作方法について詳しく説明します。

#include <stdio.h>

#define MSG "I LOVE C"
#define LARGE 40

int main(void){
    
    
    
    // 定义一个字符数组
    char words[LARGE] = "I am studying C";
    // 定义一个不能改变的字符串指针
    const char *Str1 = "that`s funny";
    // 打印以下信息
    puts("Some string display Here: ");
    puts(MSG);
    puts(words);
    puts(Str1);
    // 将words中的第8个元素改为P
    words[8] = 'P';
    puts(words);
    return 0;
}

printf() 関数と同様に、puts 関数も stdio.h シリーズの入出力関数に属します。ただし、printf() とは異なり、puts() 関数は文字列を表示するだけであり (数値は出力できません)、表示された文字列の末尾に自動的に改行が追加されます。


1.1 文字列を定義する

上記のプログラムでは、文字列を定義する方法が 3 つあります。彼らです:

  1. 文字列リテラル (文字列定数)
#define MSG "I LOVE C"
    puts(MSG);
  1. 文字列の配列
#define LARGE 40
// 定义一个字符数组
char words[LARGE] = "I am studying C";
	// 输出字符串
    puts(words);
  1. 文字列ポインタ
// 定义一个不能改变的字符串指针
const char *Str1 = "that`s funny";
puts(Str1);

1.1.1 文字列リテラル(文字列定数)

二重引用符で囲まれた内容は文字列リテラル (文字列リテラル) と呼ばれ、文字列定数とも呼ばれます。二重引用符内の文字と、コンパイラによって最後に自動的に追加される「\0」文字は、文字列としてメモリに格納されます。

文字列リテラルが区切られていない場合、または空白文字で区切られている場合、C はそれらを連結された文字列リテラルとして扱います。

#include <stdio.h>

int main(void){
    
    

    char msg[50] = "Hello""World" "LOVE C";
    puts(msg); // 输出结果:HelloWorldLOVE C

    return 0;
}

に相当:

#include <stdio.h>

int main(void){
    
    

    char msg[50] = "HelloWorldLOVE C";
    puts(msg); // 输出结果:HelloWorldLOVE C

    return 0;
}

文字列内に二重引用符を出力する場合は、エスケープ文字「\」を追加する必要があります。

#include <stdio.h>

int main(void){
    
    

    char msg[50] = "Hello \"World\" LOVE C";
    puts(msg); // 输出结果:Hello "World" LOVE C

    return 0;

}

文字列定数は静的ストレージのカテゴリに属します。つまり、文字列定数が関数で使用される場合、その文字列は 1 回だけ保存され、関数が複数回呼び出されたとしても、プログラムのライフサイクル全体にわたって存在します。 。


1.1.2 文字列配列と初期化

文字列の配列を定義するときは、必要なスペースの量をコンパイラーに知らせる必要があります。1 つの方法は、文字列を格納するのに十分なスペースのある配列を使用することです。次のように:

const char str[20] = "I LOVE C";

const は文字列が変更されないことを意味します。

上記の初期化は、標準の配列の初期化 (以下の手順) よりもはるかに簡単です。

const char str[20] = {
    
    'I', ' ', 'L', 'O', 'V', 'E', ' ', 'C', '\0'};

末尾のヌル文字に注意してください。Null 文字がなければ、これは文字列ではなく、文字の配列になります。

配列のサイズを指定するときは、配列内の要素の数が文字列の長さより少なくとも 1 大きいことを確認してください (NULL 文字を収容するため)。未使用の要素はすべて自動的に 0 に初期化されます (ここでの 0 は、数字の 0 ではなく、char 形式の NULL 文字を指します)。

ここに画像の説明を挿入
多くの場合、配列自体のサイズをコンパイラーに決定させるほうが便利です。文字列を扱う関数は通常、配列のサイズを知らないため、これらの関数は、文字列の末尾にある null 文字を検索することによって、文字列がどこで終わるかを判断します。

const char str[] = "I LOVE C"; 

配列を宣言する場合、配列のサイズは評価可能な整数である必要があります。C99 で辺の長さの配列が追加される前は、配列のサイズは整数定数である必要がありました。

int n = 8;

char str1[1];	// 有效
char str2[2+5];	// 有效
char str3[2*sizeof(int)+1];	// 有效
char str4[n];	// 在C99标准之前无效,C99之后是变长数组

1.1.3 配列とポインタ

const char *pt1 = "LOVE C";
const char ar1[] = "LOVE C";

上記のステートメントでは、配列形式 (ar1[ ]) によって、コンピュータのメモリ内に 7 つの要素を含む配列が割り当てられます (各要素は文字に対応し、最後にヌル文字 '\0' が追加されます)。各要素は次のように初期化されます。文字列リテラルに対応する文字。通常、文字列は実行可能ファイルの一部としてデータ セグメントに格納されます。プログラムがメモリにロードされると、プログラム内の文字列もロードされます。文字列は静的ストレージに保存されます。ただし、プログラムの実行が開始されるまで、メモリは配列に割り当てられません。この時点で、文字列が配列にコピーされます。この時点で、文字列のコピーが 2 つあります。1 つは静的メモリ内の文字列リテラルで、もう 1 つは ar1 配列に格納されている文字列です。その後、コンパイラは、配列名 ar1 を配列の最初の要素 (ar1[0]) のアドレスのエイリアスとして認識します。配列形式では、ar1 はアドレス変数です。ar1 が変更された場合、ar1 を変更することはできません。これは、配列の格納場所 (つまり、アドレス) が変更されたことを意味します。ar1+1 のような操作を実行して、配列の次の要素を識別できます。ただし、++ar1 のような操作は許可されません。インクリメント演算子は変数名の前でのみ使用でき、定数の前では使用できません。
また、ポインター形式により、コンパイラーは文字列の静的記憶域に 7 つの要素用のスペースを予約します。さらに、プログラムが実行されると、ポインタ変数 pt1 の格納場所が予約され、文字列のアドレスがポインタ変数に格納されます。この変数は最初は文字列の最初の文字を指しますが、その値は変更される可能性があります。したがって、インクリメント操作が使用できます。

文字列リテラルは const データとして扱われます。pt1 はこの const データを指すため、pt1 は const データへのポインタとして宣言する必要があります。これは、pt1 を使用してそれが指すデータを変更することはできませんが、pt1 の値は変更できることを意味します。

つまり、配列の初期化では静的記憶領域の文字列が配列にコピーされ、ポインタの初期化では文字列のアドレスがポインタにコピーされるだけです。

#include <stdio.h>

#define MSG "LOVE C"

int main(void){
    
    

    const char ar[] = MSG;
    const char *pt = MSG;

    printf("the address of ar :%p\n", ar);//the address of ar :00000094583ff831
    printf("the address of pt :%p\n", pt);//the address of pt :00007ff63fa6a000
    printf("the address of MSG :%p\n", &MSG);//the address of MSG :00007ff63fa6a000

    return 0;
}

1.1.4 配列とポインタの違い

2 つの主な違いは、配列名 ar は定数であるのに対し、ポインタ名 pt は変数であることです。
この 2 つのポインタ表記のみインクリメントできます。

配列の要素は変数ですが (配列が const として宣言されていない限り)、配列名は変数ではありません。

ポインターを使用して文字列を初期化する場合は、const 修飾子を使用することをお勧めします。

const char *pt = "LOVE C";

結論として、文字列を変更する場合は、文字列リテラルへのポインタを使用しないでください。

文字列を変更したり、文字列入力用のスペースを確保したりする場合は、文字列リテラルへのポインタを使用しないでください。


2.文字列入​​力

プログラムに文字列を読み込む場合は、まず文字列を保存するための領域を予約し、次に input 関数を使用して文字列を取得する必要があります。


2.1 スペースの割り当て

最初に行うことは、後で読み取られる文字列に十分なスペースを割り当てることです。

ここに画像の説明を挿入
上記のコードでは、コンパイラは警告を出します。name を読み出すと、name がプログラム内のデータまたはコードを消去し、プログラムが異常終了する可能性があります。これは、scanf() がパラメータで指定されたアドレスに情報をコピーしたいためであり、この時点ではパラメータは初期化されていないポインタであり、name は任意の場所を指す可能性があるためです。
上記の問題を解決する最も簡単な方法は、配列を宣言するときに配列のサイズを指定することです。

char name[40];

2.2 gets() 関数 (非推奨)

文字列を読み取る場合、scanf() および変換指定 %s は 1 つの単語のみを読み取ることができます。プログラムでは、単一の単語だけでなく、入力の行全体を読み取りたい場合がよくあります。この状況に対処するために、何年も前に gets() 関数が使用されていました。

gets() 関数は使い方が簡単で、改行文字が見つかるまで入力行全体を読み取り、改行文字を破棄して残りの文字を保存し、これらの文字の末尾に null 文字を追加して、 C文字列。

#include <stdio.h>

#define LEN 40

int main(void){
    
    

    char words[LEN];
    puts("Enter a string :");
    gets(words);	// 典型用法
    printf("%s\n", words);
    puts(words);
    puts("Done");
    
   return 0;
}

ただし、gets() 関数は、一部のコンパイラで警告メッセージをポップアップ表示します。問題は、gets() の唯一のパラメータにあり、配列が次の入力行に適合するかどうかをチェックできません。前述したように、配列名は配列要素の最初のアドレスに変換されます。そのため、gets() 関数は知っているのはデータの先頭だけであり、配列に要素がいくつあるかはわかりません。

入力文字列が長すぎると、バッファ オーバーフローが発生します。つまり、余分な文字が指定されたターゲット スペースを超えます。これらの余分な文字が未使用のメモリを占有しているだけであれば、直ちに問題はありませんが、プログラム内の他のデータが消去されると、プログラムが異常終了する原因となります。

gets() 関数のこの機能により、システム プログラミングを通じてシステムのセキュリティを破るコードを実行する人がいます。

C99 標準委員会は、gets() の問題を認め、その使用を控えるよう勧告しました。C11 標準委員会は、gets() 関数を標準から直接廃止しました。

ただし、実際のアプリケーションでは、以前のコードとの互換性を保つために、ほとんどのコンパイラーは引き続き gets() 関数をサポートします。


2.3 gets() の代替

2.3.1 fgets() 関数 (および fputs())

fgets() 関数は、2 番目のパラメーターを通じて読み込まれる文字数を制限することで、オーバーフローの問題を解決します。この関数は、ファイル入力を処理するために特別に設計されています。したがって、一般的にはあまり役に立ちません。fgets() と gets() の違いは次のとおりです。

1. fgets() 関数の 2 番目のパラメータは、読み取る最大文字数を指定します。値が n の場合、 fgets() は n-1 文字、つまり最初に見つかった改行文字までを読み取ります。
2. fgets() が改行を読み取ると、それを文字列に格納します。これは、改行を破棄する gets() とは異なります。
3. fgets() 関数の 3 番目のパラメータは、読み込むファイルを指定します。キーボードから入力されたデータを読み込む場合は、stdinをパラメータとして使用し、識別子をstdio.hに定義します。

fgets() 関数は文字列の末尾に改行を挿入するため (入力行がオーバーフローしないと仮定して)、関数が追加しない限り、通常は fputs() 関数 (puts() と同様) とペアになります。文字列の最後に改行。fputs() 関数の 2 番目のパラメーターは、書き込むファイルを指定します。コンピューターのモニターに表示したい場合は、引数として stdout を使用する必要があります。

#include <stdio.h>

#define SIZE 4

int main() {
    
    

    char words[SIZE];

    puts("please enter a string:");
    fgets(words, 4, stdin);
    fputs(words,stdout);

    return 0;
}



2.3.2 get_s() 関数

C11 に新しく追加された get_s() 関数 (オプション) は fgets() に似ており、パラメーターを使用して読み取られる文字数を制限します。2 つの主な違いは次のとおりです。

  1. get_s() は標準入力からデータを読み取るだけなので、3 番目のパラメーターは必要ありません。
  2. get_s() が改行を読み取る場合、それを保存せずに破棄します。
  3. get_s() が改行文字を読み取らずに最大文字数を読み取る場合、次の手順が実行されます。まずターゲット配列の最初の文字を null 文字に設定し、改行文字またはファイルの終わりが読み取られるまで後続の入力を読み取って破棄し、その後 null ポインタを返します。次に、実装に依存する「ハンドラー関数」が呼び出され、場合によってはプログラムが中止または終了します。
#include <stdio.h>

#define SIZE 14

// 需要在C11中执行
int main() {
    
    

    char words[SIZE];

    puts("please enter a string:");
    gets_s(words, 4);
    fputs(words,stdout);

    return 0;
}


2.3.3 scanf()関数

scanf() 関数には、入力の終了を判断する 2 つの方法があります。いずれの方法であっても、空白以外の最初の文字が文字列の先頭として使用されます。「%s」を使用した場合は変換指定に従い、次の空白文字(スペース、空行、タブ、改行)が文字列の先頭として使用されます。文字列 文字列の終わり (文字列には空白文字は含まれません)。%10s などのフィールド幅が指定されている場合、scanf() は 10 文字を読み取るか、最初の空白文字で停止します (最初に満たされる条件が入力を終了する条件です)。

入力文 元の入力シーケンス 名前の内容 残りの入力シーケンス
scanf(“%s”, 名前) フリーバート・ホップ フリーバート [スペース]ハプ
scanf(“%5s”, 名前) フリーバート・ホップ フリーブ えー、行きます
scanf(“%5s”, 名前) 環状 アン [空格]彼ら

4. 文字列出力

C には、文字列を出力するための 3 つの標準ライブラリ関数、puts()、fputs()、printf() があります。

4.1 Puts() 関数

Puts() 関数は非常に簡単に使用でき、文字列のアドレスをパラメータとして渡すだけです。

#include <stdio.h>

#define MSG "Hello World"
#define SIZE 40

int main(void){
    
    

    char flower[SIZE] = "I LOVE C";
    puts(flower);
    puts(MSG);
    
   return 0;
}
//输出结果:
I LOVE C
Hello World

上に示したように、puts() は文字列を表示するときに文字列の末尾に自動的に改行を追加するため、各文字列は独自の行にあります。

プログラムは、二重引用符で囲まれた内容が文字列定数であり、その文字列のアドレスとして扱われることを再度示します。

#include <stdio.h>

#define MSG "Hello World"
#define SIZE 40

int main(void){
    
    
	
	// flower不是字符串数组,而是一个字符数组
    char flower[SIZE] = {
    
    'I', 'L', 'O', 'V', 'E', 'C'};
    puts(flower);
    puts(MSG);

   return 0;
}

上の花は、終わりを示す空白文字がないため、文字列ではありません。


4.2 fputs() 関数

fputs() 関数は、ファイル用の put() のカスタム バージョンです。それらの違いは次のとおりです。

  1. fputs() 関数の 2 番目のパラメーターは、データを書き込むファイルを指定します。ディスプレイに印刷したい場合は、stdio.h で定義された stdout (標準出力) をパラメータとして使用できます。
  2. Puts() とは異なり、 fputs() 関数は出力の末尾に改行を追加しません。

4.3 printf()関数

Puts() 関数と同様に、printf() も文字列のアドレスをパラメータとして受け取ります。printf() 関数は Put() 関数ほど便利ではありませんが、さまざまなデータをフォーマットできるため、より汎用性が高くなります。種類。

Puts() とは異なり、printf() は各文字列の末尾に自動的に改行を追加しません。したがって、パラメータ内で改行を使用する場所を指定する必要があります。

5. 文字列関数

C ライブラリには文字列を処理するための複数の関数が用意されており、ANSI C ではこれらの関数のプロトタイプが <string.h> ヘッダー ファイルに配置されます。最も一般的に使用される関数は、strlen()、strcat()、strcmp()、strncmp()、strcpy()、および strncpy() です。


5.1 strlen() 関数

strlen() 関数は、文字列の長さをカウントするために使用されます。

#include <stdio.h>
#include "string.h"

#define MSG "Hello world"

int main(void){
    
    
    //"%llu"为unsigned long long int
    printf("%llu", strlen(MSG));
    
   return 0;
}

5.2 strcat() 関数

strcat() (文字列を連結する) 関数は 2 つの文字列を引数として受け取ります。この関数は、2 番目の文字列のバックアップを最初の文字列の末尾に追加し、結合後に形成された新しい文字列を最初の文字列として受け取り、2 番目の文字列は変更されません。strcat() 関数の型は char * (つまり、char へのポインタ) です。strcat() 関数は、2 番目の文字列を結合した後の最初の文字列のアドレスである最初のパラメータを返します。

#include <stdio.h>
#include "string.h"

#define MSG " Hello world"
#define SIZE 80

int main(void){
    
    

    char flower[SIZE];
    puts("please enter a flower:");
    gets(flower);
    strcat(flower, MSG);
    printf("%s\n", flower); //iris Hello world
    
   return 0;
}

5.3 strncat() 関数

strcat() 関数は、最初の配列が 2 番目の配列を保持しているかどうかをチェックできません。最初の配列に割り当てられたスペースが十分に大きくない場合、余分な文字が隣接する記憶域にオーバーフローして問題が発生します。

strncat () 関数 この関数の 3 番目のパラメーターは、追加する最大文字数を指定します。例: strncat(flower,MSG, 4)MSG 文字列の内容は flowers に追加され、4 番目の文字に達するか、null 文字に遭遇すると停止します。したがって、空の文字フラワー配列を数えると十分な大きさになるはずです。

#include <stdio.h>
#include "string.h"

#define MSG " Hello world"
#define SIZE 40

int main(void){
    
    

    char flower[SIZE];
    puts("please enter a flower:");
    gets(flower);
    strncat(flower, MSG, 8);
    printf("%s\n", flower); // iris Hello w

   return 0;
}

5.4 strcmp() および strncmp() 関数

どちらの関数も、2 つの文字列パラメーターが同じかどうかを比較します。2 つの文字列が同じ場合は 0 を返し、それ以外の場合は 0 以外を返します。

5.4.1 strcmp() 関数

この関数は、文字列のアドレスではなく、文字列の内容を比較します。

#include <stdio.h>
#include "string.h"

#define MSG "iris"
#define SIZE 40

int main(void){
    
    

    char flower[SIZE];
    puts("please enter a flower:");
    gets(flower);
    // 返回值为0或非零值
    int bl =  strcmp(flower, MSG);
    printf("%d\n", bl);

   return 0;
}

strcmp() 関数は、配列全体ではなく文字列を比較します。配列は 40 バイトを占有し、そこに格納されているアイリスは 5 バイト (およびヌル文字) のみを占有しますが、strcmp() 関数はヌル文字の前の部分のみを比較します。

注: strcmp() 関数は、文字ではなく文字列を比較します。


5.4.2 strncmp() 関数

strncmp() 関数は、別の文字が見つかるまで文字列内の文字を比較します。比較は文字列の終わりまで続く場合があります。strncmp() 関数が 2 つの文字列を比較する場合、文字が異なる箇所を比較することも、3 番目のパラメータで指定された文字数のみを比較することもできます。

#include <stdio.h>
#include "string.h"

#define MSG "iris"
#define SIZE 40

int main(void){
    
    

    char flower[SIZE];
    puts("please enter a flower:");
    gets(flower);
    int bl =  strncmp(flower, MSG, 4);
    printf("%d\n", bl);

   return 0;
}


5.5 strcpy() および strncpy() 関数

5.5.1 strcpy() 関数

文字列全体をコピーする場合は、strcpy() 関数を使用します。

strcpy() 関数の 2 番目のパラメーターが指す文字列は、最初のパラメーターが指す配列にコピーされます。コピーされた文字列はターゲット文字列と呼ばれ、元の文字列はソース文字列と呼ばれます。つまり、最初の文字列はターゲット文字列で、2 番目の文字列はソース文字列です。

#include <stdio.h>
#include "string.h"

#define MSG "iris"
#define SIZE 40

int main(void){
    
    

    char flower[SIZE];
    puts("please enter a flower:");
    gets(flower);
    puts(flower);
    strcpy(flower, MSG);
    printf("%s\n", flower);
    
   return 0;
}

strcpy() 関数のその他の属性:

  1. strcpy() の戻り値の型は char * で、この関数は最初のパラメーターの値 (文字のアドレス) を返します。
  2. 最初のパラメータは配列の先頭を指す必要はありません。このプロパティは、配列の一部をコピーするために使用できます。

5.5.2 strncpy() 関数

strcpy() 関数と strcat() 関数は、最初の配列が 2 番目の配列を保持できるかどうかをチェックできません。最初の配列に割り当てられたスペースが十分に大きくない場合、余分な文字が隣接する記憶域にオーバーフローして問題が発生します。

strncpy() 関数を使用して文字列をコピーする方が安全です。この関数の 3 番目のパラメータは、コピーできる最大文字数を示します。

#include <stdio.h>
#include "string.h"

#define MSG "iris"
#define SIZE 40

int main(void){
    
    

    char flower[SIZE];
    puts("please enter a flower:");
    gets(flower);
    puts(flower);
    strncpy(flower, MSG, 2);
    printf("%s\n", flower);

   return 0;
}


5.6 sprintf()関数

sprintf() 関数は、「string.h」ではなく、<stdio.h> で宣言されます。この関数は printf() に似ていますが、データをディスプレイに表示するのではなく文字列に書き込みます。したがって、この関数は複数の要素を 1 つの文字列に結合できます。sprintf() 関数の最初のパラメータは、ターゲット文字のアドレスです。残りのパラメータは printf() と同じ、つまり書式文字列と書き込む項目のリストです。

#include <stdio.h>

#define MSG "iris"
#define SIZE 40

int main(void){
    
    

    char flower[SIZE];
    char pt[2 * SIZE + 10];
    puts("please enter a flower:");
    gets(flower);
    puts(flower);
    sprintf(pt, "%s, %s", MSG, flower);
    printf("%s\n", pt);

   return 0;
}

おすすめ

転載: blog.csdn.net/qq_46292926/article/details/127819819