C语言字符串

 

字符串的概念

       我们可以把字符串储存在char类型的数组中,如果char类型的数组末尾包含一个表示字符串末尾的空字符\0,则该数组中的内容就构成了一个字符串。

       

       因为字符串需要用\0结尾,所以在定义字符串的时候,字符数组的长度要预留多一个字节用来存放\0,\0就是数字0。

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

       字符串也可以存放中文和全角的标点符号,一个中文字符占两个字节。char strname[21]用于存放中文的时候,只能存10个汉字。

       字符串采用双引号包含起来,如:"hello"、"中华人民共和国"、"A"、""。

占用内存的情况

       一个字符占用一字节的内存,字符串定义时数组的大小就是字符串占用内存的大小。

              char str[21];     // 占用21字节的内存

              char str[1024];   // 占用1024字节的内存

字符串的初始化

              char strname[21];

              strname[0]=0;    // 把第一个元素的值置为0

              或

              memset(strname,0,sizeof(strname));  // 把全部的元素置为0

              strname[0]=0;不够规范,并且存有隐患,在实际开发中,一般采用memset的函数初始化字符串。

字符串与指针

       在C语言中,数组名是数组无素的首地址,所以在获取字符串的地址的时候,不需要用&取地址。

              char strname[21];

              memset(strname,0,sizeof(strname));

              strcpy(strname,"abcdefghijk");     // 把abcdefghijk赋值给strname

              printf("%s\n",strname);              // 输出abcdefghijk

字符串的结尾标志

       字符串的结尾标志是0,如果没有结尾标志的情况我们在数组章节中已介绍过,现在我们介绍结尾标志后面的内容如何处理。

              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)。

字符串的输出

       字符串采用%s输出,可以加格式控制,常用的如下:

              printf("=%10s=\n","abcd");   // 输出10个字符宽度,右对齐

              执行结果是=      abcd=

              printf("=%-10s=\n","abcd");  // 输出10个字符宽度,左对齐

              执行结果是=abcd      =

       如果输出的字符串的长度大于对齐格式中的数字,就按字符串的实际长度输出。

字符串越界

       字符串是字符数组,字符串越界就是数组越界。字符串的越界是初级程序员经常犯的错误之一。

       示例(book80.c)

       

       运行结果

       

       我们来分析一下book80.c。

       前8行代码定义了两个字符串变量,每个能存放20个字符或10个中文,但实际赋值都超过了10个中文,从输出结果看,没有问题。

       后6行代码采用了二维数组的方式定义了字符串变量,理论上说,与分开定义的两个字符串变量没有区别,但是,从输出结果看,很有问题。

       真正的原因是这样的,在C语言中,数组越界肯定是非法的,但非法操作并不一定会出问题,前8行代码的字符串是越界了,但是strname1和strname2变量的内存之后的内存空间是未分配的,所以对strname1和strname2赋值过长也没关系。后6行代码就不一样了,二维数组的两个变量之间的内存是连续的,第一个元素之后没有多余的空间,所以第一个元素的值就出问题了。

       总的来说,在C语言中,非法操作内存不一定会报错,要看运气。

       在现实生活中,一个农民把庄稼种到了自家的地盘之外,如果地盘之外的地没有主人,是不会有问题的,但如果有主人,这事就肯定会引起纠纷。

字符串常用的库函数

1、获取字符串的长度(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";

              sizeof(buf)为10。

              strlen(buf)是3。

2、字符串复制或赋值(strcpy)

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

       功 能: 将参数src字符串拷贝至参数dest所指的地址。

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

       复制完字符串后,在dest后追加0。

       如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。

3、字符串复制或赋值(strncpy)

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

       功能:把src前n字符的内容复制到dest中

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

       如果src字符串长度小于n,则拷贝完字符串后,在dest后追加0,直到n个。

       如果src的长度大于等于n,就截取src的前n个字符,不会在dest后追加0;

       dest必须有足够的空间放置n个字符,否则可能会造成缓冲溢出的错误情况。

4、字符串拼接(strcat)

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

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

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

       dest最后原有的结尾字符\0会被覆盖掉,并在连接后的字符串的尾部再增加一个\0。

       dest要有足够的空间来容纳要拼接的字符串,否则可能会造成缓冲溢出的错误情况。

5、字符串拼接(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要有足够的空间来容纳要拼接的字符串,否则可能会造成缓冲溢出的错误情况。

6、字符串比较(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,表示两个字符串相等。

       在实际开发中,程序员一般只关心字符串是否相等,不关心哪个字符串更大或更小。

7、字符查找(strchr、strrchr)

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

       返回一个指向在字符串s中第一个出现c的位置,如果找不到,返回0。

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

       返回一个指向在字符串s中最后一个出现c的位置,如果找不到,返回0。

8、字符串查找(strstr)

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

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

       返回值:返回字符串str中第一次出现子串substr的地址;如果没有检索到子串,则返回0。

strncpy函数的坑

       以上章节介绍了字符串处理的库函数,其中strcpy、strncpy、strcat、strncat几个函数会改变字符串的值,那么这几个函数在处理字符串的时候,是否合理的处置了字符串的结尾标志呢?不一定,我们来看以来的示例。

       示例(book81.c)

       

       运行结果

       

       注意,在字符串前后输出=号,把字符串夹起来,是为了更清楚的显示字符串前后是否有其它的内容,这是调试技巧。

在book81.c中,第一段和第三段是相同的代码,却输出了不同的结果,为什么呢?

       如果src的长度大于等于n,就截取src的前n个字符,不会在dest后追加0;

       我认为这是strncpy函数的缺陷。解决这个问题的方法就是,字符串在每次使用前习惯性的的初始化为0,或重写strncpy函数。

应用经验

1、留有余地

       字符串的strcpy和strcat函数要求dest参数有足够的空间,否则会造成内存的泄漏,所以在实际开发中,定义字符串的时候,可以大一些,例如姓名,中国人的姓名以两三个汉字为主,最多五个,少数民族可能十几个,外国人的很长,喜欢在自己的名字前加上爷爷的名字和外公的名字,那么我们在定义变量的时候,可以char name[301];存放他祖宗十八代的名字也没有问题。

       内存不值钱,程序的稳定性高于一切。

2、变量初始化

       字符串初化的可以避免入坑。

3、重写strcpy和strcat函数

       重写strcpy、strncpy、strcat和strncat函数,解决三个问题:

       1)变量初始化。

       2)内存溢出。

       3)修复strncpy的缺陷。

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

              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也可以使用偏移量。

版权声明

C语言技术网原创文章,转载请说明文章的来源、作者和原文的链接。

来源:C语言技术网(www.freecplus.net

作者:码农有道

如果这篇文章对您有帮助,请点赞支持,或在您的博客中转发我的文章,谢谢!!!

如果文章有错别字,或者内容有误,或其他的建议或意见,请您留言指正,非常感谢!!!

 

 

  • 11
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C语言技术网-码农有道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值