C語言再學習 -- 字串和字串函式
最近身體很不給力,很乏累!白天沒精神,晚上睡不著,心情還很煩躁。看書都有點看不下去的樣子,到了C語言最難掌握的部分了,陣列、指標、字串。硬著頭皮看書總結吧。
一、字串
1、字串介紹
字串是以空字元(\0)結尾的char陣列,例如:
char ar[20] = "hello world";
2、定義字串
字元常量,又稱字串文字,是指位於一對雙引號中的任何字元。雙引號裡的字元加上編譯器自動提供的結束標誌(\0)字元,作為一個字串被儲存在記憶體裡。
#include <stdio.h> int main (void) { char ar1[50] = "hello" " " "world""!"; char ar2[50] = "hello world!"; printf ("%s\n", ar1); printf ("%s\n", ar2); printf ("\"We are family!\"\n"); return 0; } 輸出結果: hello world! hello world! "We are family!"
3、字串初始化
定義一個字串陣列時,必須讓編譯器知道它需要多大空間。
方法一,指定一個足夠大的陣列來容字串
char ar[20] = "hello world";
char ar[2+5]; /*陣列大小也可以是常量表達式*/
指定陣列大小時,一定要確保陣列元素數比字串長度至少多1(多出來的1個元素用於容納空字元),未被使用的元素均被自動初始化為0。這裡的0是char形式的空字元,不是數字字元0.
方法二,讓編譯器決定陣列大小
char ar[] = "hello world";
char ar[] = {'h' ,'e' ,'l' ,'l' ,'o' ,' ' ,'w' ,'o' ,'r' ,'l' ,'d' ,'\0'};
注意:標示結束的空字元。如果沒有它,得到的就只是一個字元陣列而不是一個字串。
注意區分:數字字元 0,空字元 \0,空格 ' ',空指標 NULL。
方法三,指標初始化字串
char * str = "hello world";
說明:陣列和指標初始化字串的區別
陣列初始化是從靜態儲存區把一個字串複製給陣列,而指標初始化只是複製字串的地址。其主要區別在於陣列名ar是一個常量,而指標str則是一個變數。如,只有指標變數可以進行 str++。陣列的元素是變數(除非宣告陣列時帶有關鍵字const),但是陣列名不是變數。如,*(ar + 2) = 'm'; 是可以的。
擴充套件,區分單個字元和字串
用單引號引起的一個字元實際上代表一個整數,整數值對應於該字元子啊編譯器採用的字符集中的序列值。因此,對於採用 ASCII 字符集的編譯器而言,'a' 的含義與 0141(八進位制)或者97(十進位制)嚴格一致。
用雙引號引起的字串,代表的卻是一個指向無名陣列起始字元的指標,該陣列被雙引號之間的字元以及一個額外的二進位制值為零的字元 '\0' 初始化。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (void)
{
char *str1 = "abcde";
char str2[] = "abcde";
char str3[8] = "abcde";
char str4[] = {'a', 'b', 'c', 'd', 'e'};
char *p1 = malloc (20);
printf ("sizeof (str1) = %d, strlen (str1) = %d\n", sizeof (str1), strlen (str1));
printf ("sizeof (*str1) = %d, strlen (str1) = %d\n", sizeof (*str1), strlen (str1));
printf ("sizeof (str2) = %d, strlen (str2) = %d\n", sizeof (str2), strlen (str2));
printf ("sizeof (str3) = %d, strlen (str3) = %d\n", sizeof (str3), strlen (str3));
printf ("sizeof (str4) = %d, strlen (str4) = %d\n", sizeof (str4), strlen (str4));
printf ("sizeof (p1) = %d, sizeof (*p1) = %d\n", sizeof (p1), sizeof (*p1));
printf ("sizeof (malloc(20)) = %d\n", sizeof (malloc (20)));
return 0;
}
輸出結果:
sizeof (str1) = 4, strlen (str1) = 5
sizeof (*str1) = 1, strlen (str1) = 5
sizeof (str2) = 6, strlen (str2) = 5
sizeof (str3) = 8, strlen (str3) = 5
sizeof (str4) = 5, strlen (str4) = 5
sizeof (p1) = 4, sizeof (*p1) = 1
sizeof (malloc(20)) = 4
根據上面的例子,可知 str1、str2以空字元(\0)結尾是字串。而 str4 不是字串。
如果兩者混用,那麼編譯器的型別檢查功能將會檢測到這樣的錯誤:
#include <stdio.h>
#include <string.h>
int main (void)
{
char *str = '2';
return 0;
}
輸出結果:
警告: 初始化時將整數賦給指標,未作型別轉換 [預設啟用]
#include <stdio.h>
#include <string.h>
int main (void)
{
printf ("1111111\n");
printf ('\n');
printf ("2222222\n");
return 0;
}
輸出結果:
test.c: 在函式‘main’中:
test.c:7:2: 警告: 傳遞‘printf’的第 1 個引數時將整數賦給指標,未作型別轉換 [預設啟用]
/usr/include/stdio.h:363:12: 附註: 需要型別‘const char * __restrict__’,但實參的型別為‘int’
test.c:7:2: 警告: 格式字串不是一個字面字串而且沒有待格式化的實參 [-Wformat-security]
還有需要注意的,在用雙引號括起來的字串中,註釋符 /* 屬於字串的一部分,而在註釋中出現的雙引號 "" 又屬於註釋的一部分。
#include <stdio.h>
#include <string.h>
int main (void)
{
char *str = "123/*ddddd*/456";
printf ("%s\n", str);
/*"123456"*/
return 0;
}
輸出結果:
123/*ddddd*/456
再再有注意巨集定義,用於定義字串,尤其是路徑
A),#define ENG_PATH_1 E:\English\listen_to_this\listen_to_this_3
B),#define ENG_PATH_2 “ E:\English\listen_to_this\listen_to_this_3”
A 為 定義路徑, B 為定義字串
4、字串輸入/輸出
二、字串函式
經過了一個月的Hi3516A的專案,現在終於有時間,靜下心來繼續總結C語言了。雖說過了一個月,總結到什麼地方了、還有什麼沒總結的地方,基本上忘得差不多了,慢慢拾起來吧!
字串函式,其原型在/kernel/include/linux/string.h有宣告
這類字串庫函式的功能實現函式的答案在核心原始碼/kernel/lib/string.c
擴充套件:size_t型別為unsigned int型別,佔位符使用%u或%lu,C99用%zd。
下面來介紹一些最有用和最常用的函式:
1、strlen() 函式
#include <string.h>
size_t strlen(const char *s);
函式功能:用來統計字串中有效字元的個數
功能實現函式:
size_t strlen (const char *s)
{
const char *sc;
for (sc = s; *sc != '\0'; ++sc)
/"nothing"/
return sc - s;
}
strlen()函式被用作改變字串長度,例如:
#include <stdio.h>
#include <string.h>
void fit (char *, unsigned int);
int main (void)
{
char str[] = "hello world";
fit (str, 7);
puts (str);
puts (str + 8);
return 0;
}
void fit (char *string, unsigned int size)
{
if (strlen (string) > size)
*(string + size) = '\0';
}
輸出結果:
hello w
rld
可以看出:fit()函式在陣列的第8個元素中放置了一個'\0'字元來代替原有的o字元。puts()函式輸出停在o字元處,忽略了陣列的其他元素。然而,陣列的其他元素仍然存在。
puts (str + 8);
表示式str + 8是str[8]即'r'字元的地址。因此puts()顯示這個字元並且繼續輸出知道遇到原字串中的空字元。
2、strcat()函式
#include <string.h>
char *strcat(char *dest, const char *src);
函式功能:合併兩個字串
缺點:超出字串儲存區範圍的話,有可能修改陣列以外的儲存區這會導致嚴重錯誤
功能實現函式:
char *strcat(char *dest, const char *src)
{
char *tmp = dest;
while (*dest)
dest++;
while ((*dest++ = *src++) != '\0')
;
return tmp;
}
strcat()函式它將第二個字串的一份拷貝新增到第一個字串的結尾,從而使第一個字串成為一個新的組合字串,第二個字串並沒有改變,例如:
#include <stdio.h>
#include <string.h>
#define SIZE 80
int main (void)
{
char flower[SIZE];
char addon[]= " is beautiful";
gets (flower);
strcat (flower, addon);
puts (flower);
return 0;
}
輸出結果:
rose
rose is beautiful
3、strncat()函式
上面有提到strcat()函式的缺點,它並不能檢查第一個陣列是否能夠容納第二個字串。如果沒有為第一個陣列分配足夠大的空間,多出來的字元溢位到相鄰儲存單元時就會出現問題。
#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
函式功能:合併兩個字串
功能實現函式:
char *strncat(char *dest, const char *src, size_t count)
{
char *tmp = dest;
if (count) {
while (*dest)
dest++;
while ((*dest++ = *src++) != 0) {
if (--count == 0) {
*dest = '\0';
break;
}
}
}
return tmp;
}
例如:
#include <stdio.h>
#include <string.h>
#define SIZE 30
#define BUGSIZE 13
int main (void)
{
char flower[SIZE];
char addon[]= " is beautiful";
char bug[BUGSIZE];
int arg;
puts ("what is your favorite flower");
gets (flower);
if ((strlen (addon) + strlen (flower) + 1) <= SIZE)
strcat (flower, addon);
puts (flower);
puts ("what is your favorite flower");
gets (bug);
arg = BUGSIZE - strlen (bug) -1;
strncat (bug, addon, arg);
puts (bug);
return 0;
}
輸出結果:
what is your favorite flower
Rose
Rose is beautiful
what is your favorite flower
MuDan
MuDan is bea
4、strcmp()函式
#include <string.h>
int strcmp(const char *s1, const char *s2);
函式功能:比較兩個字串的大小,比較依據的是ASCII碼
返回值:依次比較字串每一個字元的ASCII碼,如果兩個字串引數相同,返回0;如果第一個字串引數較大,則返回1,如果第二個字串較大,則返回-1。
功能實現函式:
int strcmp(const char *cs, const char *ct)
{
unsigned char c1, c2;
while (1) {
c1 = *cs++;
c2 = *ct++;
if (c1 != c2)
return c1 < c2 ? -1 : 1;
if (!c1)
break;
}
return 0;
}
需要注意的是:
strcmp()函式比較的是字串,而不是陣列和字元。它可以用來比較存放在不同大小數組裡的字串。
#include <stdio.h>
#include <string.h>
#define SIZE 30
#define STOP "q"
int main (void)
{
char str[SIZE];
while (gets (str) != NULL && strcmp (str, STOP))
{
printf ("hello\n");
sleep (1);
}
return 0;
}
這裡提一下:strcmp比較區分字母大小寫 相當是比較的時候純粹按照ascii碼值來比較從頭到尾。
而 stricmp 是不區分字母的大小寫的。
5、strncmp()函式
#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
函式功能:只比較兩個字串裡前n個字元
功能實現函式:
int strncmp(const char *cs, const char *ct, size_t count)
{
unsigned char c1, c2;
while (count) {
c1 = *cs++;
c2 = *ct++;
if (c1 != c2)
return c1 < c2 ? -1 : 1;
if (!c1)
break;
count--;
}
return 0;
}
6、strcpy()函式
#include <string.h>
char *strcpy(char *dest, const char *src);
函式功能:把一個字串複製到另外一個字元數組裡
缺點:有可能修改不屬於陣列的儲存區,這會導致錯誤
功能實現函式:
char *strcpy(char *dest, const char *src)
{
char *tmp = dest;
while ((*dest++ = *src++) != '\0')
/* nothing */;
return tmp;
}
例如:
char str1[30];
char str2[ ] = "hello";
char *pts1 = str1;
char *pts2 = str2;
pts1 = pts2 (錯誤)
strcpy (pts1, pts2); (正確)
因為pts1和pts2都是指向字串的指標,上面的表示式只複製字串的地址而不是字串本身。
字串之間的複製應使用strcpy ()函式,它在字串運算中的作用等價於賦值運算子。
總之,strcpy()接受兩個字串指標引數。指向最初字串的第二個指標可以是一個已宣告的指標、陣列名或字串常量。指向複製字串的第一個指標應指向空間大到足夠容納該字串的資料物件,比如一個數組。記住,宣告一個數組將為資料分配儲存空間,而宣告一個指標只為一個地方分配儲存空間。
#include <stdio.h>
#include <string.h>
#define WORDS "best"
#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;
}
輸出結果:
best
Be the best that you can be.
Be the best
best
上述例子,運用了strcpy()函式的兩個屬性。首先,它是char*型別,它返回的是第一個引數的值,即一個字元的地址;其次,第一個引數不需要指向陣列的開始,這樣就可以只複製陣列的一部分。
7、strncpy()函式
#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
函式功能:把一個字串複製到另外一個字元數組裡
功能實現函式:
har *strncpy(char *dest, const char *src, size_t count)
{
char *tmp = dest;
while (count) {
if ((*tmp = *src) != 0)
src++;
tmp++;
count--;
}
return dest;
}
上面有講,strcpy()函式的缺點,就是不能檢查目標字串是否容納得下源字串。使用strncpy()函式,它的第三個引數可指明最大可複製的字元數。
#include <stdio.h>
#include <string.h>
int main()
{
char src[40];
char dest[12];
memset(dest, '\0', sizeof(dest));
strcpy(src, "This is w3cschool.cc");
strncpy(dest, src, 10);
printf("最終的目標字串: %s\n", dest);
return(0);
}
輸出結果:
最終的目標字串: This is w3
8、strstr()函式
#include <string.h>
char *strstr(const char *haystack, const char *needle);
函式功能:在一個字串中查詢另外一個字串所在位置
功能實現函式:
char *strstr(const char *s1, const char *s2)
{
size_t l1, l2;
l2 = strlen(s2);
if (!l2)
return (char *)s1;
l1 = strlen(s1);
while (l1 >= l2) {
l1--;
if (!memcmp(s1, s2, l2))
return (char *)s1;
s1++;
}
return NULL;
}
例如:
#include <stdio.h>
#include <string.h>
int main()
{
const char haystack[20] = "W3CSchool";
const char needle[10] = "3";
char *ret;
ret = strstr(haystack, needle);
printf("子字串是: %s\n", ret);
return(0);
}
輸出結果:
子字串是: 3CSchool
如果needle字串不是haystack的一部分,則會出現警告:
assignment discards ‘const’ qualifier from pointer target type
9、memset()函式
#include <string.h>
void *memset(void *s, int c, size_t n);
函式功能:可以把字元陣列中所有的字元儲存區填充同一個字元資料
功能實現函式:
void *memset(void *s, int c, size_t count)
{
char *xs = s;
while (count--)
*xs++ = c;
return s;
}
舉例:
#include <stdio.h>
#include <string.h>
int main ()
{
char str[50];
strcpy(str,"This is string.h library function");
puts(str);
memset(str,'$',7);
puts(str);
return(0);
}
輸出結果:
This is string.h library function
$$$$$$$ string.h library function
10、sprintf()函式
#include <stdio.h>
int sprintf(char *str, const char *format, ...);
函式功能:按照格式把資料列印在字元陣列中,形成一個字串
#include <stdio.h>
#define SIZE 30
int main (void)
{
char str[SIZE];
sprintf (str, "%s %s %d\n", "I","love",512 );
puts (str);
return 0;
}
輸出結果:
I love 512
注意:使用sprintf()和使用printf()的方法一樣,只是結果字串被存放在陣列fornal中,而不是被顯示在螢幕上。
再有如果想將2轉化為字串“02”,該怎麼辦呢?
#include <stdio.h>
int main (void)
{
char str[2];
sprintf (str, "%02d", 2);
printf ("str = %s\n", str);
return 0;
}
輸出結果:
str = 02
11、memcpy函式
#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
函式功能:從儲存區src 複製 n 個字元到儲存區dest。
引數:
dest -- 指向用於儲存複製內容的目標陣列,型別強制轉換為 void* 指標。
src -- 指向要複製的資料來源,型別強制轉換為 void* 指標。
n -- 要被複制的位元組數。
返回值:該函式返回一個指向目標儲存區 dest 的指標。
功能實現函式:
void *memcpy(void *dest, const void *src, size_t count)
{
char *tmp = dest;
const char *s = src;
while (count--)
*tmp++ = *s++;
return dest;
}
//示例一
#include <stdio.h>
#include <string.h>
int main ()
{
const char src[50] = "hello world!";
char dest[50];
printf("Before memcpy dest = %s\n", dest);
memcpy(dest, src, strlen(src)+1);
printf("After memcpy dest = %s\n", dest);
return(0);
}
輸出結果:
Before memcpy dest =
After memcpy dest = hello world!
//示例二
#include <stdio.h>
#include <string.h>
int main ()
{
const int src[50] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int dest[50];
memcpy(dest, src, 9*4);
int i;
for (i = 0; i < 9 ; i++)
{
printf ("%d ", dest[i]);
}
printf ("\n");
return(0);
}
輸出結果:
1 2 3 4 5 6 7 8 9
strcpy 和 memcpy 的區別:
(1)strcpy 和 memcpy 都是標準 C 庫函式
(2)strcpy 提供了字串的複製。即 strcpy 只用於字串複製,並且它不僅複製字串內容之外,還會複製字串的結束符。
(3)strcpy函式的原型是:char* strcpy(char* dest, const char* src);
(4)memcpy提供了一般記憶體的複製。即memcpy對於需要複製的內容沒有限制,因此用途更廣。
(5)memcpy函式的原型是:void *memcpy( void *dest, const void *src, size_t count );
strcpy 和 memcpy 主要有以下3方面的區別:
(1)複製的內容不同。strcpy 只能複製字串,而memcpy可以複製任何內容,例如字串陣列、整型、結構體、類等。
(2)複製的方法不同。strcpy不需要指定長度,它遇到被複制字串結束符'\0'才結束,所以容易溢位。memcpy則是根據其第3個引數決定複製的長度。
(3)用途不同。通常在複製字串時用strcpy,而需要複製其他型別資料時則一般用memcpy。
12、memcmp函式
#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
函式功能:
把儲存區 str1 和儲存區 str2 的前 n 個位元組進行比較。
引數:
str1 -- 指向記憶體塊的指標。
str2 -- 指向記憶體塊的指標。
n -- 要被比較的位元組數。
返回值:
如果返回值 < 0,則表示 str1 小於 str2。
如果返回值 > 0,則表示 str2 小於 str1。
如果返回值 = 0,則表示 str1 等於 str2。
示例:
#include <stdio.h>
#include <string.h>
int main (void)
{
int ret = 0;
char str1[5] = {'1','2','3','4','5'};
char str2[5] = {'1','A','B','4','5'};
ret = memcmp (str1, str2, 5);
if(ret > 0)
{
printf("str2 灝忎簬 str1\n");
}
else if(ret < 0)
{
printf("str1 灝忎簬 str2\n");
}
else
{
printf("str1 絳変簬 str2\n");
}
return 0;
}
輸出結果:
str1 小於 str2
三、將字串轉換成數字
atoi()函式、atol()函式、atof()函式
#include <stdlib.h>
int atoi(const char *nptr);
long atol(const char *nptr);
double atof(const char *nptr);
函式功能: 分別把數字的字串表示轉換為 int、long和double形式。如果沒有執行有效的轉換返回0.
atoi()功能實現函式:
int my_atoi(const char *str)
{
int value = 0;
int flag = 1; //判斷符號
while (*str == ' ') //跳過字串前面的空格
{
str++;
}
if (*str == '-') //第一個字元若是‘-’,說明可能是負數
{
flag = 0;
str++;
}
else if (*str == '+') //第一個字元若是‘+’,說明可能是正數
{
flag = 1;
str++;
}//第一個字元若不是‘+’‘-’也不是數字字元,直接返回0
else if (*str >= '9' || *str <= '0')
{
return 0;
}
//當遇到非數字字元或遇到‘\0’時,結束轉化
while (*str != '\0' && *str <= '9' && *str >= '0')
{
value = value * 10 + *str - '0'; //將數字字元轉為對應的整形數
str++;
}
if (flag == 0) //負數的情況
{
value = -value;
}
return value;
}
測試:
#include <stdio.h>
int my_atoi(const char *str)
{
int value = 0;
int flag = 1; //判斷符號
while (*str == ' ') //跳過字串前面的空格
{
str++;
}
if (*str == '-') //第一個字元若是‘-’,說明可能是負數
{
flag = 0;
str++;
}
else if (*str == '+') //第一個字元若是‘+’,說明可能是正數
{
flag = 1;
str++;
}//第一個字元若不是‘+’‘-’也不是數字字元,直接返回0
else if (*str >= '9' || *str <= '0')
{
return 0;
}
//當遇到非數字字元或遇到‘\0’時,結束轉化
while ((*str != '\0')&& (*str <= '9') && (*str >= '0'))
{
value = value * 10 + *str - '0'; //將數字字元轉為對應的整形數
str++;
}
if (flag == 0) //負數的情況
{
value = -value;
}
return value;
}
int main(void)
{
int i = my_atoi(" -1234dd");
printf("%d\n", i);
return 0;
}
輸出結果:
-1234
測試:
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int val1, val2;
long val3, val4;
double val5, val6;
val1 = atoi ("512you");
val2 = atoi ("you512");
val3 = atol ("1000000you");
val4 = atol ("you1000000");
val5 = atof ("3.14you");
val6 = atof ("you3.14");
printf ("val1 = %d\n", val1);
printf ("val2 = %d\n", val2);
printf ("val3 = %lu\n", val3);
printf ("val4 = %lu\n", val4);
printf ("val5 = %lg\n", val5);
printf ("val6 = %lg\n", val6);
return 0;
}
輸出結果:
val1 = 512
val2 = 0
val3 = 1000000
val4 = 0
val5 = 3.14
val6 = 0
itoa函式功能實現:
void itoa(int num,char str[] )
{
int sign = num,i = 0,j = 0;
char temp[11];
if(sign<0)//判斷是否是一個負數
{
num = -num;
};
do
{
temp[i] = num%10+'0';
num/=10;
i++;
}while(num>0);
if(sign<0)
{
temp[i++] = '-';//對於負數,要加以負號
}
temp[i] = '\0';
i--;
while(i>=0)//反向操作
{
str[j] = temp[i];
j++;
i--;
}
str[j] = '\0';
}
測試:
#include <stdio.h>
void my_itoa(int num,char str[] )
{
int sign = num,i = 0,j = 0;
char temp[11];
if(sign<0)//判斷是否是一個負數
{
num = -num;
};
do
{
temp[i] = num%10+'0';
num/=10;
i++;
}while(num>0);
if(sign<0)
{
temp[i++] = '-';//對於負數,要加以負號
}
temp[i] = '\0';
i--;
while(i>=0)//反向操作
{
str[j] = temp[i];
j++;
i--;
}
str[j] = '\0';
}
int main (void)
{
char arr[10]= {0};
my_itoa (-123,arr);
printf ("%s\n", arr);
}
輸出結果:
-123
四、字元轉十六進位制
void StrToHex(char *pbDest, char *pbSrc, int nLen)
{
char h1,h2;
char s1,s2;
int i;
for (i=0; i<nLen/2; i++)
{
h1 = pbSrc[2*i];
h2 = pbSrc[2*i+1];
s1 = toupper(h1) - 0x30;
if (s1 > 9)
s1 -= 7;
s2 = toupper(h2) - 0x30;
if (s2 > 9)
s2 -= 7;
pbDest[i] = s1*16 + s2;
}
}