1. 程式人生 > 其它 >第十章 字串和字串函式

第十章 字串和字串函式

10.1字串表示和字串I/O

  字串是以空字元(\0)結尾的char陣列。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月13日08:18:30
//strings.c--使用字串與使用者互動
#include <stdio.h>
#define MSG "You must have many talents. Tell me some."  //一個符號字串常量
#define LIM 5
#define LINELEN 81  //最大字串常量+1

int main(void)
{
	char name[LINELEN];
	char talents[LINELEN];
	int i;
	const char m1[40] = "Limit yourself to one line's worth.";
	const char m2[] = "If you can't think of anything,fake it.";
	const char *m3 = "\nEnough about me - what's your name?";
	const char* mytal[LIM] = 
	{
		"Adding number swiftly",
		"Multiplying accurately",
		"Stashing data",
		"Following instructions to the letter",
		"Understanding the C language"
	};
	printf("Hi! I'm Clyde the Computer. " " I have many talents.\n");
	printf("Let me tell you some of them.\n");
	puts("What were they? Ah, yes, here's a partial list.");
	for(i = 0; i < LIM; i++)
		puts(mytal[i]);  //列印計算機功能的列表
	puts(m3);
	gets(name);
	printf("Well, %s, %s\n", name, MSG);
	printf("%s\n%s\n", m1, m2);
	gets(talents);
	puts("Let's see if I've got that list:");
	puts(talents);
	printf("Thank for the information, %s.\n", name);


	return 0;
}

結果:

Hi! I'm Clyde the Computer.  I have many talents.
Let me tell you some of them.
What were they? Ah, yes, here's a partial list.
Adding number swiftly
Multiplying accurately
Stashing data
Following instructions to the letter
Understanding the C language

Enough about me - what's your name?
Nigel Barntwit
Well, Nigel Barntwit, You must have many talents. Tell me some.
Limit yourself to one line's worth.
If you can't think of anything,fake it.
Fencing yodeling, maling, cheese tasting, and sighing.
Let's see if I've got that list:
Fencing yodeling, maling, cheese tasting, and sighing.
Thank for the information, Nigel Barntwit.

10.1.1在程式中定義字串

  基本的辦法是使用字串常量、char陣列、char指標和字串陣列。

一、字串常量

  字串常量,又稱字串文字,是指位於一對雙引號中的任何字元。雙引號裡的字元加上編譯器自動提供的結束標誌\0字元,作為一個字串被儲存在記憶體中。可以用#define來定義字串常量。

  字串文字中間沒有間隔或者間隔的是空格符,ANSI C會將其串聯起來。例如,

char greeting[50] = "Hello, and" "how are" "you" "today!";
char greeting[50] = "Hello, and how are you today!";

相等。

  字串常量屬於靜態儲存(static storage)類。靜態儲存是指如果在一個函式中使用字串常量,即使是多次呼叫了這個函式,該字串在程式的整個執行過程中只儲存一份。整個引號中的內容作為指向該字串儲存位置的指標。這一點與把陣列名作為指向陣列儲存位置的指標類似。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月13日09:03:52
//quotes.c--把字串看作指標
#include <stdio.h>
int main(void)
{
	printf("%s, %p, %c\n", "We", "are", *"space farers");

	return 0;
}

結果:

We, 00B17B40, s

  %s格式將輸出字串We。%p格式將產生一個地址。因此如果“are"是個地址,那麼%p應該輸出字串中第一個字元的地址(ANSI之前的實現可能用%u或%lu而不用%p)。最後,*"space farers"應該產生所指向的地址中的值,即字串"space farers"中的第一個字元。

二、字串陣列及其初始化

const char m1[40] = "Limit yourself to one line's worth.";

  const表明這個字串不可以改變。

  指定陣列大小時,一定要確保陣列元素比字串長度多1(多出來的1個元素用於容納空字元)。未被使用的元素均被自動初始化為0.這裡的0是char形式的空字元,而不是數字字元0。

  初始化字元陣列是體現由編譯器決定陣列大小的優點的例子。這是因為字串處理函式一般不需要知道陣列的大小,因為它們能夠簡單地通過查詢空字元來確定字串結束。

  宣告一個數組時,陣列的大小必須為整型常量,而不能是執行時得到的變數值。編譯時陣列大小被鎖定到程式中(事實上,C99中可以使用變長陣列,但仍然無法預先知道陣列大小應為多大)。

int n = 8;
char cakes[2+5];//合法,陣列大小是一個常量表達式
char crumbs[n];//在C99之前是無效的,在C99之後是一個變長陣列(VLA)
下面的例子對於陣列成立
m1 = &m1[0], *m1 == 'L',and *(m1 + 1) == m1[1] =='i';

三、陣列與指標

  通常,被引用的字串儲存在可執行檔案的資料段部分;當程式被載入到記憶體中時,字串也被載入到記憶體中。被引用的字串被稱為位於靜態儲存區。但是在程式開始執行後才維陣列分配儲存空間。這時候,把被引用的字串複製到陣列中。此後,編譯器會把陣列名m3看做是陣列首元素的地址&m3[0]的同義詞。在陣列形式中m3是個地址常量。不能更改m3,因為這意味著更改陣列儲存的位置。可以使用運算子m3+1來標識數組裡的下一個元素,但是不允許使用++m3。增量運算子只能用在變數名前。

  指標形式(*m3)也在靜態儲存區為字串預留38個元素的空間。此外,程式一旦開始執行,還要為指標變數m3另外預留一個儲存位置,以在該指標變數中儲存字串的地址。這個變數初始時指向字串的第一個字元,但是它的值是可以改變的。因此可以對它使用增量運算子。例如,++m3指向第二個字元E。

  陣列初始化是從靜態儲存區把一個字串複製給陣列,而指標初始化只是複製字串的地址。

四、陣列和指標的差別

char heart[] = "I love Tillie!";
char *head = "I love Millie!";

  主要的差別在於陣列名heart是個常量,而指標head是個變數。

  實際使用中,首先,兩者都可使用陣列符號:heart[i]、head[i],其次,兩者都可以使用指標加法:*(heart+i)、*(head+i),但是隻有指標可以使用增量運算子:*(head++)。陣列名不能作為左值。

char *word = "frame";
word[1] = '1';

  有些編譯器會允許上面的情況,但按照當前的C標準,編譯器不允許這樣做。這種語句會導致記憶體訪問錯誤。原因在於編譯器可能選擇記憶體中的同一個單個的拷貝,來表示所有相同的字串文字。以下語句都指向字串“Klingon”的同一個單獨的記憶體位置。

char * p1 = "Klingon";
p1[0] = 'F';//OK?
printf("Klingon");
printf(": Beware the %ss!\n", "Klingon");

  編譯器可以用相同的地址來替代每一個“Klingon"的例項。如果編譯器使用這個單個拷貝表示法並且允許把p1[0]改為‘F’的話,那會將影響到所有對這個字串的使用。於是,列印字串文字"Klingon"的語句實際就變成"Flingon"。實際上,有些編譯器確實是按照這種容易混淆的方式工作,而其他得一些則會產生程式異常中斷。因此,建議的做法是初始化一個指向字串文字的指標時使用const修飾符:

const char * p1 = Klingon";//推薦用法

  用一個字串文字來初始化一個非const的陣列,則不會導致此類問題,因此陣列從最初的的字串得到了一個拷貝。

五、字串陣列

程式:

const char* mytal[LIM] = 
{
	"Adding number swiftly",
	"Multiplying accurately",
	"Stashing data",
	"Following instructions to the letter",
	"Understanding the C language"
};

  mytal是一個由5個指向char的指標組成的陣列。mytal是個一維陣列,而數組裡的每一個元素都是一個char類性值的地址。每個指標指向相應字串的第一個字元。mytal陣列實際上不存放字串,它只是存放字串的地址(字串存在程式用來存放常量的那部分記憶體中)。可以把mytal[0]看作第一個字串,*mytal[0]表示第一個字串的第一個字元,mytal[0][0]表示第一個字串的第一個字元。

  字串陣列的初始化遵循陣列初始化的規則。花括號裡那部分的形式如下:

{{...},{...},{...},{...},{...},};

  關鍵之處是第一對雙引號對應著一對花括號,用於初始化第一個字串指標。第二對雙引號對應著一堆話括號,用於初始化第二個字串指標。另一個方法是建立一個二維陣列:

char mytal_2[LIM][LINLIM];

  在這種情況下,字串本身也被儲存在數組裡。兩者差別之一就是第二種方法選擇建立了一個所有行的長度都相同的矩形陣列。而指標陣列建立的是一個不規則的陣列,每一行的長度由初始化字串決定。另一個區別就是mytal1和mytal_2的型別不同;mytal是一個指向char的指標的陣列,而mytal_2是一個char陣列的陣列。mytal存放5個地址,而mytal_2存放5個完整的字元陣列。

10.1.2指標和字串

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月13日10:37:03
//p_and_s.c--指標和字串
#include <stdio.h>

int main(void)
{
	char * mesg = "Don't be a fool!";
	char * copy;

	copy = mesg;
	printf("%s\n", copy);
	printf("mesg = %s; &mesg = 0x%p; value = 0x%p\n", mesg, &mesg, mesg);
	printf("copy = %s; &copy = 0x%p; value = 0x%p\n", copy, &copy, copy);

	return 0;
}

結果:

Don't be a fool!
mesg = Don't be a fool!; &mesg = 0x0075FD7C; value = 0x00BB7B30
copy = Don't be a fool!; &copy = 0x0075FD70; value = 0x00BB7B30

10.2字串輸入

10.2.1建立儲存空間

  C提供了三個讀取字串的函式:scanf()、gets()和fgets()。

10.2.2gets()函式

  它從系統的標準輸入輸出裝置(通常是鍵盤)獲得一個字串。它讀取換行符之前(不包括換行符)的所有字元,在這些字元後新增一個空字元(\0),然後把這個字串交給呼叫它的程式。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月13日10:53:55
//name1.c--讀取一個名字
#include <stdio.h>
#define MAX 81

int main(void)
{
	char name[MAX];  //分配空間
	printf("Hi, what's your name?\n");
	gets(name);  //把字串放進name陣列中
	printf("Nice name, %s.\n", name);

	return 0;
}

結果:

Hi, what's your name?
The Mysterious Davina D'Lema
Nice name, The Mysterious Davina D'Lema.

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月15日07:33:26
//讀取一個名字
#include <stdio.h>
#define MAX 81

int main(void)
{
	char name[MAX];
	char *ptr;

	printf("Hi, what's your name?\n");
	ptr = gets(name);
	printf("%s? Ah! %s!\n", name, ptr);

	return 0;
}

  gets()函式通過兩種方式獲得輸入:

●它使用一個地址把字串賦予name。

●gets()的程式碼使用return關鍵字返回字串的地址,程式把這個地址分配給ptr。注意到ptr是一個char指標,這意味著gets()必須返回一個指向char的指標值。與所傳遞給它的指標是同一個。若出錯或者如果gets()遇到檔案結尾,它就返回一個空(或0)地址。這個空地址被稱為空指標,並用stdio.h裡定義的常量NULL來表示。因此gets()中還加入了一些錯誤檢測,這使它可以很方便地以如下形式使用:

while(gets(name) != NULL)

  這樣的指令使您既可以檢查是否到了檔案結尾,又可以讀取一個值。如果遇到了檔案結尾,name中什麼也不會讀入。這樣一舉兩得的方法就比getchar()函式所採用的的方法簡潔的多,getchar()只返回一個值而沒有引數:

while((ch = getchar()) != EOF)

10.2.3fgets()函式

  gets()的一個不足是它不檢查預留儲存區是否能夠容納實際輸入的資料。多出來的字元簡單的溢位到相鄰的記憶體區。fgets()函式改進了這個問題,它讓您指定最大讀入字元數。由於fgets()是為檔案I/O而設計的,在處理鍵盤輸入時就不如gets()方便。fgets()和gets()有三方面不同:

●它需要第二個引數來說明最大讀入字元數。如果這個引數值為n,fgets()就會讀取最多n-1個字元或者讀完一個換行符為止,由這而這種最先滿足的那個來結束輸入。

●如果fgets()讀取到換行符,就會把它存到字串裡,而不是像gets()那樣丟棄它。

●它還需要第三個引數來說明讀取哪一個檔案。從鍵盤上讀取資料時,可以使用stdin(代表standard input)作為改引數,這個識別符號在stdio.h中定義。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月15日08:13:12
//name3.c--使用fgets()讀取一個名字
#include <stdio.h>
#define MAX 81
int main(void)
{
	char name[MAX];
	char *ptr;

	printf("Hi, what's your name?\n");
	ptr = fgets(name, MAX, stdin);
	printf("%s? Ah! %s!\n", name, ptr);

	return 0;
}

結果:

Hi, what's your name?
Jon Dough
Jon Dough
? Ah! Jon Dough
!

●對於重要的程式設計,應該使用fgets()而不是gets()

10.2.4scanf()函式

  scnaf()更基於獲取單詞而不是獲取字串;而gets()函式,正如所看到的,會讀取所有字元,直到遇到第一個換行符為止。scanf()使用兩種方法決定輸入結束。無論哪種方法,字串都是以遇到的第一個非空白字元開始。如果使用%s格式,字串讀到(但不包括)下一個空白字元(比如空格、製表符或換行符)。如果制定了欄位寬度,比如%10s,scanf()就會讀入10個字元或直到遇到第一個空白字元,由二者中最先滿足的那一個終止輸入。scanf()函式返回一個整數值,這個值是成功讀取的專案數;或者當遇到檔案結束時返回一個EOF。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月15日08:33:29
//scan_str.c--使用scanf()
#include <stdio.h>
int main(void)
{
	char name1[11],name2[11];
	int count;

	printf("Please enter 2 names.\n");
	count = scanf("%5s %10s", name1, name2);
	printf("I read the %d names %s and %s.\n", count, name1, name2);

	return 0;
}

結果:

Please enter 2 names.
Jesse Jukes
I read the 2 names Jesse and Jukes.

10.3字串輸出

10.3.1puts()函式

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月15日08:42:13
#include <stdio.h>
#define DEF "I am a #defined string."
int main(void)
{
	char str1[80] = "An array was initialized to me.";
	const char *str2 = "An pointer was initialized to me.";

	puts("I'm an argument to puts().");
	puts(DEF);
	puts(str1);
	puts(str2);
	puts(&str1[5]);
	puts(str2 + 4);

	return 0;
}

結果:

I'm an argument to puts().
I am a #defined string.
An array was initialized to me.
An pointer was initialized to me.
ray was initialized to me.
ointer was initialized to me.

  與printf()不同,puts()顯示字串時自動在其後新增一個換行符。

  雙引號中的字元是字串常量,並被看作地址。同樣,字元陣列字串的名字也被看作是地址。puts()何時停止?遇到空字元時它就會停下來,所以應該確保有空字元存在。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月15日08:53:22
//nono.c--不要效仿這個程式!
#include <stdio.h>

int main(void)
{
	char side_a[] = "SIDE A";
	char dont[] = {'W', 'O', 'W', '!'};
	char side_b[] = "SIDE B";

	puts(dont);  //dont不是一個字串

	return 0;
}

結果:

WOW!燙燙燙燙SIDE A

 dont缺少一個表示結束的空字元,因此它不是一個字串,這樣puts()就不知道應該到哪裡停止。它只是一直輸出記憶體中dont後面的字元,直到發現一個空字元。為了使這個空字元不太遙遠,程式把dont儲存在兩個真正的字串之間。這裡用到的特定的編譯器在記憶體中把side_a陣列儲存在dont陣列之後。put()函式繼續執行直到遇到了side_a中的空字元。執行改程式時編譯器在記憶體中儲存資料的方式不同,得到的結果也不同。如果程式漏掉了side_a和side_b怎麼辦?通常記憶體中有很多空字元,如果幸運的話,puts()可能會很快發現一個,但是這個並不可靠。

10.3.2fputs()函式

  fputs()函式是gets()的面向檔案版本。兩者之間的區別是:

●fputs()需要第二個引數來說明要寫的檔案。可以使用stdout(代表standard output)作為引數來進行輸出顯示,stdout在stdio.h中定義。

●與puts()不同,fputs()並不為輸出自動新增換行符。

  注意,gets()丟掉輸入裡的換行符,但是puts()為輸出新增換行符。另一方面,fgets()儲存輸入中的換行符,而fputs()也不為輸出新增換行符。

10.3.3printf()函式

10.4自定義字串輸入/輸出函式

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月15日09:49:23
//put1.c--不新增換行符列印一個字串
#include <stdio.h>
void put1(const char* string)  //不會改變這個字串
{
	while(*string != '\0')
		putchar(*string++);
}

**說明**

  為什麼上面的程式用const char *string而不用const char string[]作為形式引數?從技術上來說,二者等價,因此它們都有效。用方括號符號的一個庸醫是提醒使用者這個函式處理的是陣列。但在使用字串時,實際的引數可以是陣列名、引起來的字串,或被宣告為char *型別的變數。使用const char *string可以提醒您實際的引數不一定是一個數組。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月15日09:59:24
//put_put.c--使用者自定義的輸出函式
#include <stdio.h>
void put1(const char *);
int put2(const char *);

int main(void)
{
	put1("If I'd as much money");
	put1(" as I could spend,\n");
	printf("I could %d characters.\n", put2("I never would cry old chairs to mend. "));

	return 0;
}

void put1(const char * string)
{
	while(*string)  //等同於*string != '\0'
		putchar(*string++);
}

int put2(const char * string)
{
	int count = 0;
	while(*string)
	{
		putchar(*string++);
		count++;
	}
	putchar('\n');

	return(count);
}

結果:

If I'd as much money as I could spend,
I never would cry old chairs to mend.
I could 38 characters.

10.5字串函式

10.5.1strlen()函式

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月15日10:21:12
//test.c--試用縮短字串的函式
#include <stdio.h>
#include <string.h>  //該標頭檔案中包含字串函式的原型
void fit(char *, unsigned int);

int main(void)
{
	char mesg[] = "Hold on to your hats, hackers. ";

	puts(mesg);
	fit(mesg, 7);
	puts(mesg);
	puts("Let's look at some more of the string. ");
	puts(mesg + 8);

	return 0;
}

void fit(char* string, unsigned int size)
{
	if(strlen(string) > size)
		*(string +size) = '\0';
}

結果:

Hold on to your hats, hackers.
Hold on
Let's look at some more of the string.
to your hats, hackers.

10.5.2strcat()函式

#include <string.h>
char *strcat(char *dest, const char *src);
功能:將src字串連線到dest的尾部,‘\0’也會追加過去
引數:
	dest:目的字串首地址
	src:源字元首地址
返回值:
	成功:返回dest字串的首地址
	失敗:NULL
	char str[20] = "123";
	char *src = "hello world";
	printf("%s\n", strcat(str, src));

  strcat()函式接受兩個字串引數。它將第二個字串的一份拷貝新增到第一個字串結尾,從而第一個字串成為一個新的組合字串,第二個字串並沒有變。strcat()函式是char *(指向char的指標)型別。這個函式返回它的第一個引數的值,即其後添加了第二個字串的那個字串中第一個字元的地址。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月16日07:25:53
#include <stdio.h>
#include <string.h>  //宣告strcat()函式
#define SIZE 80

int main(void)
{
	char flower[SIZE];
	char addon[] = "s smell like old shoes.";

	puts("What is your favorate flower?");
	gets(flower);
	strcat(flower,addon);
	puts(flower);
	puts(addon);

	return 0;
}

結果:

What is your favorate flower?
Rose
Roses smell like old shoes.
s smell like old shoes.

10.5.3stancat()函式

#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
功能:將src字串前n個字元連線到dest的尾部,‘\0’也會追加過去
引數:
	dest:目的字串首地址
	src:源字元首地址
	n:指定需要追加字串個數
返回值:
	成功:返回dest字串的首地址
	失敗:NULL

	char str[20] = "123";
	char *src = "hello world";
	printf("%s\n", strncat(str, src, 5));

  strcat()函式並不檢查第一個陣列是否能夠容納第二個字串。如果沒有為第一個陣列從分配足夠大的空間,多出來的字元溢位到相鄰儲存單元時就會出現問題。為第一個陣列分配足夠大的空間後再使用strlen()函式。應該給組合串的長度加1以用來存放空字元。也可以使用strnact(),這個函式把addon字串中的內容新增到bugs上,直到加到13個字元或遇到空字元為止,由二者中先符合的那一個來終止新增過程。因此,把空字元算在內(兩種情況下都要新增空字元),bugs()陣列應該足夠大,以存放原始字串(不包括空字元)、增加的最多13個字元和結束的空字元。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月16日07:44:03
//join_chk.c--連線兩個字串,並檢查第一個字串的大小
#include <stdio.h>
#include <string.h>
#define SIZE 30
#define BUGSIZE 13

int main(void)
{
	char flower[SIZE];
	char addon[] = "s smell like old shoes.";
	char bug[BUGSIZE];
	int available;

	puts("What is your favorate flower?");
	gets(flower);
	if((strlen(addon) + strlen(flower) + 1) <= SIZE)
		strcat(flower, addon);
	puts(flower);
	puts("What is yours favorate bug?");
	gets(bug);
	available = BUGSIZE - strlen(bug) - 1;
	strncat(bug, addon, available);
	puts(bug);

	return 0;
}

結果:

What is your favorate flower?
Rose
Roses smell like old shoes.
What is yours favorate bug?
Aphid
Aphids smell

10.5.4strcmp()函式

#include <string.h>
int strcmp(const char *s1, const char *s2);
功能:比較 s1 和 s2 的大小,比較的是字元ASCII碼大小。
引數:
	s1:字串1首地址
	s2:字串2首地址
返回值:
	相等:0
	大於:>0 在不同作業系統strcmp結果會不同   返回ASCII差值
	小於:<0
	

    char *str1 = "hello world";
	char *str2 = "hello mike";

	if (strcmp(str1, str2) == 0)
	{
		printf("str1==str2\n");
	}
	else if (strcmp(str1, str2) > 0)
	{
		printf("str1>str2\n");
	}	
	else
	{
		printf("str1<str2\n");
	}

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月16日07:56:44
//nogo.c--這個程式能滿足要求嗎
#include <stdio.h>
#define ANSWER "Grant"

int main(void)
{
	char try[40];

	puts("Who is burid in Grant's tomb?");
	gets(try);
	while (try != ANSWER)
	{
		puts("No, that's wrong. Try again. ");
		gets(try);
	}
	puts("That's ringht!");

	return 0;
}

  ANSWER和try實際上是指標,因此比較式try!=ANSWER並不檢查這兩個字串是否一樣,而檢查這兩個字串的地址是否一樣。使用者永遠被告知是“wrong”。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月16日08:24:05
//compare.c--這個程式可以滿足要求
#include <stdio.h>
#include <string.h>  //宣告strcmp()函式
#define ANSWER "Grant"
#define MAX 40

int main(void)
{
	char try[MAX];

	puts("Who is buried in Grant's tomb?");
	gets(try);
	while (strcmp(try, ANSWER) != 0)
	{
		puts("No, that's wrong. Try again.");
		gets(try);
	}
	puts("That's right!");

	return 0;
}

結果:

Who is buried in Grant's tomb?
Gran
No, that's wrong. Try again.
Grant
That's right!

  strcmp()函式的一個優點是它比較的是字串,而不是陣列。儘管陣列try佔用40個記憶體單元,而字串“Grant”只佔用6個記憶體單元(一個用來存放空字元),但是函式在比較時只看try的第一個空字元之前的部分。因此,strcmp()可以用來比較存放在不同大小的數組裡的字串。

●strcmp()返回值

  如果第一個字串在字母表中的順序先於第二個字串,則strcmp()函式返回的是負數;相反,返回的是正數。

strcmp("A","C")為-2;

strcmp("C","A")為2;

  如果字串中的初始字元相同,一般來說,strcmp()函式一直往後查詢,直到找到第一對不一致的字元。然後它就返回相應的值。以上的比較表明strcmp()比較所有的字元,而不僅僅是字母;因此我們不應稱比較是字母表順序,而應稱strcmp()是按照機器編碼順序進行比較的。這意味著字元的比較是根據它們的數字表示法,一般是ASCLL值。

  ●說明

  strcmp()函式用於比較字串,而不是字元。因此可以使用諸如“apples”和“A”之類的引數;但是不能使用字元引數,如'A'。考慮到char型別是整數型別,因此可以使用關係運算符來對字元進行比較。嘉定word是一個儲存在char數組裡的字串,ch是一個char變數。那麼下面的語句是合法的:

if(strcmp(word, "quit") == 0)
puts("Bye!");
if(ch == 'q')
puts("Bye!");
#define _CRT_SECURE_NO_WARNINGS 1
//使用strcmp()函式來判斷一個程式是否應該停止讀取輸入。
//quit_chk.c -- 某程式的開始
#include <stdio.h>
#include <string.h>
#define SIZE 81
#define LIM 100
#define STOP "quit"

int main(void)
{
	char input[LIM][SIZE];
	int ct = 0;
	printf("Enter up to %d lines(type quit to quit): \n", LIM);
	while (ct < LIM && gets(input[ct]) != NULL && strcmp(input[ct], STOP) != 0)
	{
		ct++;
	}
	printf("%d strings entered\n", ct);

	return 0;
}

  有時候輸入一個空行來終止輸入更方便,也就是說,在一個新行中不輸入任何字元就按下Enter鍵或Return鍵。要這樣做,您可以對while迴圈的控制語句做如下的修改:

​ while (ct < LIM && gets(input[ct]) != NULL && input[ct][0] != '\0')

10.5.5strncmp()變種

#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
功能:比較 s1 和 s2 前n個字元的大小,比較的是字元ASCII碼大小。
引數:
	s1:字串1首地址
	s2:字串2首地址
	n:指定比較字串的數量
返回值:
	相等:0
	大於: > 0
	小於: < 0
	
    char *str1 = "hello world";
	char *str2 = "hello mike";

	if (strncmp(str1, str2, 5) == 0)
	{
		printf("str1==str2\n");
	}
	else if (strcmp(str1, "hello world") > 0)
	{
		printf("str1>str2\n");
	}
	else
	{
		printf("str1<str2\n");
	} 

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月16日09:17:47
//starsrch.c -- 使用strncmp()函式
#include <stdio.h>
#include <string.h>
#define LISTSIZE 5

int main(void)
{
	char * list[LISTSIZE] = 
	{
		"astronomy", "astounding", "astrophysics", "ostracise", "asterism"
	};
	int count = 0;
	int i;

	for (i = 0; i < LISTSIZE; i++)
	{
		if (strncmp(list[i], "astro", 5) == 0)
		{
			printf("Found: %s\n", list[i]);
			count++;
		}
	}
	printf("The list contained %d words begining with astro.\n", count);

	return 0;
}

結果:

Found: astronomy
Found: astrophysics
The list contained 2 words begining with astro.

10.5.6strcpy()和strncpy()函式

#include <string.h>
char *strcpy(char *dest, const char *src);
功能:把src所指向的字串複製到dest所指向的空間中,'\0'也會拷貝過去
引數:
	dest:目的字串首地址
	src:源字元首地址
返回值:
	成功:返回dest字串的首地址
	失敗:NULL

注意:如果引數dest所指的記憶體空間不夠大,可能會造成緩衝溢位的錯誤情況。

char dest[20] = "123456789";
	char src[] = "hello world";
	strcpy(dest, src);
	printf("%s\n", dest);

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月16日09:29:24
//copy1.c -- strcpy()示例程式
#include <stdio.h>
#include <string.h>  //宣告strcpy()函式
#define SIZE 40
#define LIM 5

int main(void)
{
	char qwords[LIM][SIZE];
	char temp[SIZE];
	int i = 0;

	printf("Enter %d words begning with q: \n", LIM);
	while (i < LIM && gets(temp))
	{
		if(temp[0] != 'q')
			printf("%s doesn't begin with q!\n", temp);
		else
		{
			strcpy(qwords[i], temp);
			i++;
		}
	}
	puts("Here are the words accepted: ");
	for(i = 0; i < LIM; i++)
		puts(qwords[i]);

	return 0;
}

結果:

Enter 5 words begning with q:
quackery
quasar
quilt
quotient
no more
no more doesn't begin with q!
quiz
Here are the words accepted:
quackery
quasar
quilt
quotient
quiz

  strcpy()接受兩個字串指標引數。指向最初字串的第二個指標可以是一個已宣告的指標、陣列名或字串常量。

一、strcpy()的高階屬性

  strcpy()函式還有兩個有用的屬性。首先,它是char *型別,它返回的是第一個引數的值,即一個字元的地址;其次,第一個引數不需要指向陣列的開始,這樣就可以只複製陣列的一部分。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月16日10:09:29
//copy2.c -- strcpy()示例程式
#include <stdio.h>
#include <string.h>  //宣告strcpy()函式
#define WORDS "beast"
#define SIZE 40

int main(void)
{
	char * orig = WORDS;
	char copy[SIZE] = "Be the best that you can be.";
	char * ps;

	puts(orig);
	puts(copy);
	ps = strcpy(copy + 7, orig);
	puts(copy);
	puts(ps);

	return 0;
}

結果:

beast
Be the best that you can be.
Be the beast
beast

二、較為謹慎的選擇:strncpy()

  strcpy()和gets()函式同樣都有一個問題,那就是都不檢查目標字串是否容納得下源字串。複製字串使用strncpy()比較安全。它需要第三個引數來指明最大可複製的字元數。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//2022年5月16日10:20:38
//copy3.c -- strncpy()示例程式
#include <stdio.h>
#include <string.h>  //宣告strncpy()函式
#define SIZE 40
#define TARGSIZE 7
#define LIM 5

int main(void)
{
	char qwords[LIM][TARGSIZE];
	char temp[SIZE];
	int i = 0;

	printf("Enter %d words beginning with q: \n", LIM);
	while (i < LIM && gets(temp))
	{
		if(temp[0] != 'q')
			printf("%s doesn't begin with q!\n", temp);
		else
		{
			strncpy(qwords[i], temp, TARGSIZE - 1);
			qwords[i][TARGSIZE - 1] = '\0';
			i++;
		}
	}
	puts("Here are the words accepted: ");
	for(i = 0; i < LIM; i++)
		puts(qwords[i]);

	return 0;
}

結果:

Enter 5 words beginning with q:
quack
quadratic
quisling
quota
quagga
Here are the words accepted:
quack
quadra
quisli
quota
quagga

  函式呼叫strncpy(target, source, n)從source把n個字元(或空字元之前的字元,由二者中最先滿足的那個決定何時終止)複製到target。

	strncpy(qwords[i], temp, TARGSIZE - 1);
	qwords[i][TARGSIZE - 1] = '\0';

  這就確保已經儲存了一個字串。如果源字串確實可以容納得下,和它一起復制的空字元就標誌著字串的真正結束。如果源字串在目標陣列中容納不下,這個最後的空字元就標誌著字串的結束。

10.5.7sprintf()函式

#include <stdio.h>
int sprintf(char *str, const char *format, ...);
功能:根據引數format字串來轉換並格式化資料,然後將結果輸出到str指定的空間中,直到出現字串結束符 '\0'  為止。
引數:
	str:字串首地址
	format:字串格式,用法和printf()一樣
返回值:
	成功:實際格式化的字元個數
	失敗: - 1

	char dst[100] = { 0 };
	int a = 10;
	char src[] = "hello world";
	printf("a = %d, src = %s", a, src);
	printf("\n");

	int len = sprintf(dst, "a = %d, src = %s", a, src);
	printf("dst = \" %s\"\n", dst);
	printf("len = %d\n", len);  

  sprintf()函式是在stdio.h而不是在,string.h裡宣告的。它的作用和printf()一樣,但是它寫到字串裡而不是寫到輸出顯示。因此,它提供了把幾個元素組合成一個字串的一種途徑。sprintf()的第一個引數是目標字串的地址,其餘的引數和printf()一樣:一個轉換說明字串,接著是要寫的專案的列表。

程式:

#define _CRT_SECURE_NO_WARNINGS 1
//format.c -- 格式化一個字串
//2022年5月16日10:54:29
#include <stdio.h>
#define MAX 20

int main(void)
{
	char first[MAX];
	char last[MAX];
	char formal[2 * MAX + 10];
	double prize;

	puts("Enter your first name: ");
	gets(first);
	puts("Enter your last name: ");
	gets(last);
    puts("Enter your prize money: ");
	scanf("%lf", &prize);
	sprintf(formal, "%s, %-19s: $%6.2f\n", last, first, prize);
	puts(formal);

	return 0;
}

結果:

Enter your first name:
Teddy
Enter your last name:
Behr
Enter your prize money:
2000
Behr, Teddy              : $2000.00

10.5.8其他字串函式

  ANSI C有20多個處理字串的函式:

●char * strcpy(char * s1, const char *s2);

該函式把s2指向的字串(包括空字元)複製到s1 指向的位置,返回值是s1。

●char * strncpy(char * s1, const char * s2, size_t n);

該函式把s2指向的字串複製到s1指向的位置,複製的字元數不超過n個。返回值是s1.空字元後的字元不被複制。如果源字串的字元數少於n個,在目標字串就以空字元填充。如果源字串的字元數大於或等於n個,空字元就不被複制。返回值是s1。

●char * strcat(char * s1, const char * s2);

s2指向的字串被複制到s1指向的字串的結尾。複製過來的s2所指字串的第一個字元覆蓋了s1所指字串結尾的空字元。返回值是s1。

●char * strncat(char * s1, const char * s2, size_t n);

s2字串中只有前n個字元被追加到s1字串,複製過來的s2字串的第一個字元覆蓋了s1字串結尾的空字元。s2字串中的空字元及其後的任何字元都不會被複制,並且追加一個空字元到所得結果後面。返回值是s1。

●int strcmp(const chat * s1, const char *s2);

如果s1字串在機器編碼順序中落後於s2字串,函式的返回值是一個正數;如果兩個字串相同,返回值是0;如果第一個字串在機器編碼順序中先於第二個字串,返回值是一個負數。

●int strncmp(const chat * s1, const char *s2);

該函式的作用和strcmp()一樣,只是比較n個字元後或者遇見第一個空字元時會停止比較,由二者中最先滿足的那一個條件來終止比較過程。

●char * strchr(const char * s, int c);

該函式返回一個指向字串s中存放字元c的第一個位置的指標(標誌結束的空字元是字串的一部分,因此也可以搜尋到它)。如果沒找到該字元,函式就返回空指標。

●char * strpbrk(const char * s1, const char * s2);

該函式返回一個指標,指向字串s1中存放s2字串中的任何字元的第一個位置。如果沒找到任何字元,函式就返回空指標。

●char * strrchr(const char * s, int c);

該函式返回一個指標,指向字串s中字元c最後一次出現的地方(標誌結束的空字元是字串的一部分,因此也可以搜尋到它)。如果沒找到該字元,函式就返回空指標。

●char * strstr(const char * s1, const char * s2);

該函式返回一個指標,指向s1字串中第一次出現s2字串的地方。如果在s1中沒找到s2字串,函式就返回空指標。

●size_t strlen(const char * s);

該函式返回s字串中的字元個數,其中不包括標誌結束的空字元。

  size_t型別是sizeof運算子返回的任何型別。C規定sizeof運算子返回一個整數型別,但是沒有指定是哪種整數型別。因此size_t在一個系統上可以是unsigned int型別;在另一個系統上,又可以使unsigned long型別。string.h檔案為您的特定系統定了size_t,或者可以參考其他有該定義的標頭檔案。