C語言字符

在不少教程中,字符串不過是一個以0結束的字符數組,可是,在我看來,字符串雖然不是C語言基本數據類型,但它比任何數據類型都重要,由於字符串是最經常使用的數據。git

1、字符串的概念

咱們能夠把字符串儲存在char類型的數組中,若是char類型的數組末尾包含一個表示字符串末尾的空字符\0,則該數組中的內容就構成了一個字符串。程序員

在這裏插入圖片描述

由於字符串須要用\0結尾,因此在定義字符串的時候,字符數組的長度要預留多一個字節用來存放\0,\0就是數字0。這是約定。數組

char strname[21];  // 定義一個最多存放20個英文字符或十個中文的字符串

字符串也能夠存放中文和全角的標點符號,一箇中文字符佔兩個字節(GBK編碼)。char strname[21]用於存放中文的時候,只能存10個漢字。安全

字符串採用雙引號包含起來,如:"hello"、"中華人民共和國"、"A"、"",這是約定。框架

2、佔用內存的狀況

一個字符佔用一字節的內存,字符串定義時數組的大小就是字符串佔用內存的大小。ide

char str[21];     // 佔用21字節的內存
char str[1024];   // 佔用1024字節的內存

3、字符串的初始化

char strname[21];
strname[0]=0;    // 把第一個元素的值置爲0

函數

memset(strname,0,sizeof(strname));  // 把所有的元素置爲0

strname[0]=0;不夠規範,而且存有隱患,在實際開發中,通常採用memset的函數初始化字符串。編碼

4、字符串與指針

在C語言中,數組名是數組無素的首地址,字符串是字符數組,因此在獲取字符串的地址的時候,不須要用&取地址。.net

char strname[21];
memset(strname,0,sizeof(strname));
strcpy(strname,"abcdefghijk");     // 把abcdefghijk賦值給strname
printf("%s\n",strname);              // 輸出abcdefghijk

5、字符串的結尾標誌

字符串的結尾標誌是0,若是沒有結尾標誌的狀況咱們在數組章節中已介紹過,如今咱們介紹結尾標誌後面的內容如何處理。3d

char strname[21];
memset(strname,0,sizeof(strname));
strcpy(strname,"abcdefghijk");     // 把abcdefghijk賦值給strname
strname[5]=0;     // 強制把第6個元素賦值0
printf("%s",strname);   // 輸出的結果是abcde

以上代碼輸出的結果是abcde,可是,在內存中的值還是abcde0ghijk,後面的ghijk成了內存中的垃圾值。

不要讓字符串的內存中有垃圾值,容易產生意外的後果,咱們將在後面的內容中演示,因此字符串的初始化不建議採用把第一個元素的值置爲0的方式(strname[0]=0)。

6、字符串的輸出

字符串採用%s輸出,能夠加格式控制,經常使用的以下:

printf("=%10s=\n","abcd");   // 輸出10個字符寬度,右對齊,執行結果是=      abcd=
printf("=%-10s=\n","abcd");  // 輸出10個字符寬度,左對齊,執行結果是=abcd      =

若是輸出的字符串的長度大於對齊格式中的數字,就按字符串的實際長度輸出。

7、字符串越界

字符串是字符數組,字符串越界就是數組越界。字符串的越界是初學者常常犯的錯誤之一。

示例(book80.c)

/*
 * 程序名:book80.c,此程序演示字符串的越界。
 * 做者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>

int main()
{
  char strname1[21];
  memset(strname1,0,sizeof(strname1));
  char strname2[21];
  memset(strname2,0,sizeof(strname2));
  strcpy(strname1,"真的只能存十個漢字嗎,多幾個行不行?");
  strcpy(strname2,"是的,只能十個,多了不行。");
  printf("=%s=\n",strname1);
  printf("=%s=\n",strname2);

  char strname[2][21];
  memset(strname,0,sizeof(strname));
  strcpy(strname[1],"是的,只能十個,多了不行。");
  strcpy(strname[0],"真的只能存十個漢字嗎,多幾個行不行?");
  printf("=%s=\n",strname[0]);
  printf("=%s=\n",strname[1]);
}

運行效果

在這裏插入圖片描述

咱們來分析一下book80.c。

前8行代碼定義了兩個字符串變量,每一個能存放20個字符或10箇中文,但實際賦值都超過了10箇中文,從輸出結果看,沒有問題。

後6行代碼採用了二維數組的方式定義了字符串變量,理論上說,與分開定義的兩個字符串變量沒有區別,可是,從輸出結果看,頗有問題。

真正的緣由是這樣的,在C語言中,數組越界確定是非法的,但非法操做並不必定會出問題,前8行代碼的字符串是越界了,可是strname1和strname2變量的內存以後的內存空間是未分配的,因此對strname1和strname2賦值過長也不要緊。後6行代碼就不同了,二維數組的兩個變量之間的內存是連續的,第一個元素以後沒有多餘的空間,因此第一個元素的值就出問題了。

總的來講,在C語言中,非法操做內存不必定會報錯,要看運氣。

在現實生活中,一個農民把莊稼種到了自家的地盤以外,若是您的地盤以外的地沒有主人,是不會有問題的,但若是有主人,這事就確定會引發糾紛,系統對這種糾紛的裁決是內存越界的程序非法,強制終止它(段錯誤)。

8、字符串經常使用的庫函數

一、獲取字符串的長度(strlen)

size_t  strlen( const char*  str);

功能:計算字符串的有效長度,不包含0。

返回值:返回字符串的字符數 。

strlen 函數計算的是字符串的實際長度,遇到第一個0結束。

函數返回值必定是size_t,是無符號的整數,即typedef unsigned int size_t。

若是您只定義字符串沒有初始化,求它的長度是沒意義的,它會從首地址一直找下去,遇到0中止。

char name[50];
memset(name,0,sizeof(name));
strcpy(name, "wucongzhou");
printf("name 的長度是%d\n",strlen(name));     // 輸出結果:name 的長度是10
memset(name,0,sizeof(name));
strcpy(name, "西施");
printf("name 的長度是%d\n",strlen(name));     // 輸出結果:name 的長度是4

還有一個注意事項,sizeof返回的是變量所佔的內存數,不是實際內容的長度。

char buf[10] = "abc";                           // 定義的時候初始化。
printf("strlen(buf)=%d\n",strlen(buf));    // 輸出結果strlen(buf)=3
printf("sizeof(buf)=%d\n",sizeof(buf));    // 輸出結果sizeof(buf)=10

二、字符串複製或賦值(strcpy)

char *strcpy(char* dest, const char* src);

功 能: 將參數src字符串拷貝至參數dest所指的地址。

返回值: 返回參數dest的字符串起始地址。

複製完字符串後,在dest後追加0。

若是參數dest所指的內存空間不夠大,可能會形成緩衝溢出的錯誤狀況。

三、字符串複製或賦值(strncpy)

char * strncpy(char* dest,const char* src, const size_t n);

功能:把src前n字符的內容複製到dest中

返回值:dest字符串起始地址。

dest必須有足夠的空間放置n個字符,不然可能會形成緩衝溢出的錯誤狀況。

四、字符串拼接(strcat)

char *strcat(char* dest,const char* src);

功能:將src字符串拼接到dest所指的字符串尾部。

返回值:返回dest字符串起始地址。

dest最後原有的結尾字符0會被覆蓋掉,並在鏈接後的字符串的尾部再增長一個0。

dest要有足夠的空間來容納要拼接的字符串,不然可能會形成緩衝溢出的錯誤狀況。

五、字符串拼接(strncat)

char *strncat (char* dest,const char* src, const size_t n);

功能:將src字符串的前n個字符拼接到dest所指的字符串尾部。

返回值:返回dest字符串的起始地址。

若是n大於等於字符串src的長度,那麼將src所有追加到dest的尾部,若是n大於字符串src的長度,只追加src的前n個字符。

strncat會將dest字符串最後的0覆蓋掉,字符追加完成後,再追加0。

dest要有足夠的空間來容納要拼接的字符串,不然可能會形成緩衝溢出的錯誤狀況。

六、字符串比較(strcmp、strncmp)

int strcmp(const char *str1, const char *str2 );

功能:比較str1和str2的大小;返回值:相等返回0,str1大於str2返回1,str1小於str2返回-1;

int strncmp(const char *str1,const char *str2 ,const size_t n);

功能:比較str1和str2的大小;返回值:相等返回0,str1大於str2返回1,str1小於str2返回-1;

兩個字符串比較的方法是比較字符的ASCII碼的大小,從兩個字符串的第一個字符開始,若是分不出大小,就比較第二個字符,若是所有的字符都分不出大小,就返回0,表示兩個字符串相等。

在實際開發中,程序員通常只關心字符串是否相等,不關心哪一個字符串更大或更小。

七、字符查找(strchr、strrchr)

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

返回一個指向在字符串s中第一個出現c的位置,若是找不到,返回0。

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

返回一個指向在字符串s中最後一個出現c的位置,若是找不到,返回0。

八、字符串查找(strstr)

char *strstr(const char* str,const char* substr);

功能:檢索子串在字符串中首次出現的位置。

返回值:返回字符串str中第一次出現子串substr的地址;若是沒有檢索到子串,則返回0。

9、應用經驗

一、留有餘地

字符串的strcpy和strcat函數要求dest參數有足夠的空間,不然會形成內存的泄漏,因此在實際開發中,定義字符串的時候,能夠大一些,例如姓名,中國人的姓名以兩三個漢字爲主,最多五個,少數民族可能十幾個,外國人的很長,喜歡在本身的名字前加上爺爺的名字和外公的名字,那麼咱們在定義變量的時候,能夠char name[301];存放他祖宗十八代的名字也沒有問題。

內存不值錢,程序的穩定性高於一切。

二、變量初始化

字符串在每次使用前都要初化,減小入坑的可能,是每次,不是第一次。這是職業程序員的良好習慣。

三、位置(地址)偏移的用法

字符串的地址偏移其本質是指針的運算,經常使用於靈活的處理字符串。

char strname[21];
memset(strname,0,sizeof(strname));
strcpy(strname,"abcdefghijk");     // 把abcdefghijk賦值給strname

char strname1[21];
memset(strname1,0,sizeof(strname1));
strcpy(strname1,strname+1);       // 把bcdefghijk的值賦給strname1
strncpy(strname1,strname+2,3);    // 把cde的值賦給strname1

固然,對strname1也可使用偏移量。

四、不要在子函數中對字符指針用sizeof

若是把一個字符串(如char strname[21])的地址傳給子函數,子函數用一個字符指針(如char *pstr)來存放傳入的字符串的地址,若是在子函數中用sizeof(pstr),獲得的不是字符串佔用內存的字節數,而是字符指針變量佔用內存的字節數(8字節)。

因此,不能在子函數中對傳入的字符串進行初始化,除非字符串的長度也做爲參數傳入到了子函數中。

10、課後做業

本章節的課後做業很是要,必定要認真完成,字符串操做是C程序員的主要工做之一,這些都是職業程序員在平常開發中用到的技巧。還有,這些做業題能夠培養寫程序的感受。

1)編寫示例程序,實現字符串操做經常使用的庫函數的功能,函數的聲明以下:

size_t strlen1( const char*  str);          // 實現strlen函數的功能。
char *strcpy1(char* dest, const char* src);  // 實現strcpy函數的功能,下同。
char *strncpy1(char* dest, const char* src,size_t n);
char *strcat1(char* dest, const char* src);
char *strncat1 (char* dest, const char* src,size_t n);
char *strchr1(const char *s, const int c);
char *strrchr1(const char *s, const int c);

如下三個函數難度較大,若是沒法完成,不要過於糾結,之後功力提高了再作。

char *strstr1(const char* str, const char* substr);   // 實現strstr1函數的功能,下同。
int strcmp1( const char *str1, const char *str2 );
int strncmp1(const char *str1, const char *str2, const size_t n);

2)豐富您的函數庫,增長STRCPY、STRNCPY、STRCAT、STRNCAT四個安全的函數,彌補庫函數的缺陷,解決dest的初始化和內存越界的問題,函數的聲明以下:

char *STRCPY(char* dest,const size_t destsize,const char* src);
char *STRNCPY(char* dest,const size_t destsize,const char* src,size_t n);
char *STRCAT(char* dest,const size_t destsize,const char* src);
char *STRNCAT(char* dest,const size_t destsize,const char* src,size_t n);

注意,上述函數的第二個參數destsize是第一個參數dest佔用內存的字節數。

3)豐富您的函數庫,增長如下函數,這些是freecplus框架中的函數。

// 刪除字符串左邊指定的字符
void DeleteLChar(char *str,const char in_char);
// 刪除字符串右邊指定的字符
void DeleteRChar(char *str,const char in_char);
// 刪除字符串兩邊指定的字符
void DeleteLRChar(char *str,const char in_char);
// 刪除字符串中間的字符串
void DeleteMStr(char *str,const char *in_str);
// 在字符串的左邊補字符到指定長度
void LPad(char *str,const char in_char,unsigned int in_len);
// 在字符串的右邊補字符到指定長度
void RPad(char *str,const char in_char,unsigned int in_len);
// 把小寫轉換成大寫,忽略不是字母的字符
void ToUpper(char *str);
// 把大寫轉換成小寫,忽略不是字母的字符
void ToLower(char *str);
// 判斷內容是否所有是數字,0-是,-1-不是。
int  IsDigit(const char *str);
// 判斷內容是否所有是大寫字母,0-是,-1-不是。
int  IsUpper(const char *str);
// 判斷內容是否所有是小寫字母,0-是,-1-不是。
int  IsLower(const char *str);
// 判斷內容是否所有是ASCII字符,0-是,-1-不是。
int  IsASCII(const char *str);

11、版權聲明

C語言技術網原創文章,轉載請說明文章的來源、做者和原文的連接。
來源:C語言技術網(www.freecplus.net)
做者:碼農有道

若是這篇文章對您有幫助,請點贊支持,或在您的博客中轉發個人文章,謝謝!!!若是文章有錯別字,或者內容有錯誤,或其餘的建議和意見,請您留言指正,很是感謝!!!

相關文章
相關標籤/搜索