目录
一、初识字符串
- 所谓的字符串,就是字符数组。和整型数组一样:
int data[] = {1,2,3,4,5};
- 和整型数组一个道理:
char str[] = {'h','e','l','l','o'};
- 改进为字符串:
char cdata2[] = "hello";
char *pchar = "hello"; //由于数组名就是地址,所以通常写成这样
注意:第一行的字符串定义为字符串变量,是数组,里面的元素是可以更改的,但通过第二行指针去定义的字符串是不可以更改的,因为它是字符串常量。
- 输出一个字符串:
printf("%s",pchar);
puts(pchar);
- 注意指针的操作,当一个指针为野指针的时候,我们不能让它保存字符串,因为这样会影响到其他程序的内存空间:
char *p; //野指针,并没有明确的内存指向,危险
*p = 'a';
二、字符串的存放方式及结束标志
- 字符串和字符数组的区别:
#include<stdio.h>
int main()
{
int data[] = {'h','e','l','l','o'}; //字符串的结束标志'\0'
char cdata2[] = "hello";
int len = sizeof(data)/sizeof(data[0]);
printf("len = %d\n",len);
len = sizeof(cdata2)/sizeof(cdata2[0]);
printf("len = %d\n",len);
return 0;
}
运行结果:
通过以上代码,我们可以看到,当以数组的形式定义字符数组的时候,用
s
i
z
e
o
f
(
)
sizeof()
sizeof() 计算出来的字符数组的长度为
5
5
5 ,而当直接定义字符串的时候,长度为
6
6
6 ,因为字符串末尾系统会自动加上一个 ‘\0’ 作为结束标志。 所以建议大家在编程的时候直接用字符串的方式去定义,如果用字符数组,记得在字符数组最后一个元素后补上 ‘\0’ 。
三、sizeof和strlen的区别(重要)
#include<stdio.h>
#include<string.h>
int main()
{
char cdata2[128] = "hello";
printf("sizeof:%d\n",sizeof(cdata2));
printf("strlen:%d\n",strlen(cdata2));//不计算'\0'
return 0;
}
运行结果:
s
i
z
e
o
f
sizeof
sizeof 和
s
t
r
l
e
n
strlen
strlen 都是计算字符串长度的函数,它们之间的区别就在于
s
i
z
e
o
f
sizeof
sizeof 计算包含 ‘\0’ 在内的所有元素字节的个数,而
s
t
r
l
e
n
strlen
strlen 计算不包含 ‘\0’ 在内的元素字节的个数,即字符串的有效长度。所以打印结果如上。我们要酌情使用。
例如:
#include<stdio.h>
#include<string.h>
int main()
{
char *p = "hello";
printf("sizeof:%d\n",sizeof(p)); //指针或地址是8个字节
printf("sizeof:%d\n",sizeof(char *));
printf("sizeof:%d\n",sizeof(char));
printf("sizeof:%d\n",strlen(p));
return 0;
}
运行结果:
为什么会出现这种情况呢?因为p是一个
c
h
a
r
∗
char *
char∗ ,
s
i
z
e
o
f
sizeof
sizeof 用来计算的时候,得出是计算机用多少字节来表示一个地址,都是8个字节。
四、malloc动态开辟内存空间
之前我们对字符串的空间的分配都是定死的,所以就造成了内存空间的浪费,那我们如何去按需动态开辟字符串呢?在这里我们就用到了 m a l l l o c mallloc mallloc 函数。 m a l l l o c mallloc mallloc 函数就是我们用来在堆上面动态开辟字符串。之前数组我们都是在栈上开辟内存空间,现在我们的 m a l l o c malloc malloc 函数是在堆上开辟内存空间。函数原型是:
void *malloc(size_t size) //C库函数,分配所需的内存空间,并返回一个指向它的指针。
之前我们在 一、5.提到:注意指针的操作,当一个指针为野指针的时候,我们不能让它保存字符串,因为这样会影响到其他程序的内存空间:
char *p; //野指针,并没有明确的内存指向,危险
*p = 'a';
这样是野指针的操作,会对系统其他程序占用的内存造成风险,那我们用动态开辟字符串的方式呢?
#include<stdio.h>
#include<stdlib.h>
int main()
{
char *p;
p = (char *)malloc(1); //p有了具体的内存指向
*p = 'c';
printf("%c\n",*p);
puts("end");
return 0;
}
运行结果:
这样的方式去开辟内存空间使得
p
p
p 有了具体的内存指向,所以比较安全。
内存空间被开辟后,是一刀一刀被切开似的零散的分布在内存中。像这种无用的,非正常的空间,我们是需要给他释放掉。因为用
m
a
l
l
l
o
c
mallloc
mallloc 函数是在堆上分配内存空间的,而且分配后不会向栈那样自动回收,它是在程序结束以后才一起释放。如果不断循环地在堆上开辟内存空间,就可能造成系统堆空间资源的耗尽从而造成危险。
在这里,我们提到了
f
r
e
e
free
free 函数去释放已经被分配的内存空间, 它的函数原型为:
void free(void *ptr);
具体实例看以下代码:
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
int main()
{
char *p;
p = (char *)malloc(1);
*p = 'c';
free(p);
p = (char *)malloc(12);
strcpy(p,"yangchen");
puts(p);
puts("end");
return 0;
}
运行结果:
下面我们介绍扩容函数,函数原型为:
void realloc(void *pte,size_t size);
函数的功能是调整之前调用 m a l l o c malloc malloc 或 c a l l o c calloc calloc 所分配的 p t r ptr ptr 所指向的内存块的大小。 我们看一下实际应用:
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
int main()
{
char *p;
p = (char *)malloc(1);
*p = 'c';
free(p);
p = (char *)malloc(12);
printf("扩容地址:%x\n",p);
int len = strlen("yangchen111111111111111111111");
int newLen = len - 12 + 1; //加1用来存放'\0'
realloc(p, newLen);
printf("扩容后地址:%x\n",p);
strcpy(p,"yangchen111111111111111111111");
puts(p);
puts("end");
return 0;
}
运行结果:
由此可见,扩容后的起始地址不变。
补充:悬挂指针(野指针的一种),为了防止出现野指针,我们在定义指针的时候最好让它指向 N U L L NULL NULL 。而且在开辟的内存空间释放以后,一定要让 p p p 指针重新指向 N U L L NULL NULL :
char *p = NULL;
p = (char *)malloc(1);
*p = 'c';
free(p);
p = NULL;
m
e
m
s
e
t
memset
memset 函数,清空开辟内存的空间,函数原型:
void *memset(void *str,int c,size_t n);
其中第一个形式参数为新开辟的内存,第二个形式参数是要初始化填充的内容,第三个形式参数为大小。例如:
char *p;
p = (char *)malloc(12);
memset(p,'\0',12);
补充:在动态开辟空间的时候,有时候会遇到内存空间不够用的情况,比如单片机的开发,这时候我们就需要采用一种机制,当内存满了的时候,我们就让程序立即退出:
if(p == NULL){
printf("malloc error!\n");
exit(-1);
}
五、字符串常用操作函数
1.输出字符串
puts(p); //自带换行
printf("%s\n",p);
2.获取字符串
char* gets(char* str);
因为本函数可以无限读取,容易发生溢出。如果溢出,多出来的字符将被写入到堆栈中,这就覆盖了堆栈原先的内容,破坏一个或多个不相关变量的值。
scanf("%s",p);
3.拷贝字符串strcpy
char destSrc[128] = {’\0’};
char *strSrc = “我真帅!”;
strcpy(strDest,strSrc);
strncpy(strDest,“zzx hs”,3); //3的意思是拷贝到第三个字符
4.拼接字符串strcat
char test[] = “scy不chuo”;
strcat(test,"scy帅");
4.比较字符串strcmp
小 -1 大 1 一样 0
char *str1 = “1234”;
char *str2 = “123456”;
int ret = strcmp(str1,str2);
5.查找子字符串strchr
///找到第一个字符包括他往后的字符串
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
int main()
{
char *str = “yangchenzhang”;
char c = ‘z’;
char *p = NULL;
strchr(str,c);
p = strchr(str,c);
if(p == NULL)
{
printf(“没找到\n”);
}else
{
printf(“找到\n”);
}
puts(p);
system(“pause”);
return 0;
}
6.检索子串在字符串中首次出现的位置strstr
#include <stdio.h>
#include <stdlib.h>\
int main()
{
char *str = “yangchenzhang”;
char *c = “zh”;
char *p = NULL;
strstr(str,c);
p = strstr(str,c);
if(p == NULL)
{
printf(“没找到\n”);
}else
{
printf(“找到\n”);
}
puts(p);
system(“pause”);
return 0;
}
6. 转换为小写字母strlwr
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[] = “HELLOW WORLD”;
strlwr(str);
puts(strlwr(str));
system(“pause”);
return 0;
}
7. 转换为大写字母strupr
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[] = “good good!!!”;
strupr(str);
puts(strupr(str));
system(“pause”);
return 0;
}
8. 字符串的切割strtok
函数的原型如下:
char* strtok (char* str,constchar* delimiters );
从函数原型可以看出,该函数需要传入两个参数:一个是要分割的字符串首地址str;一个是常量分割标志字符串的首地址delimiters。应用如下:
#include<stdio.h>
#include<string.h>
int main()
{
char s[]="Anhui#Normal#University#is#a#beautiful#college#campus#with#a#long#history.";
char *temp=strtok(s,"#");
while(temp!=NULL)
{
printf("%s ",temp);
temp=strtok(NULL,"#");
//下次调用时,将上次执行结束前访问到的字符串s的位置作为新的起始位置,因此这里传入的参数str需要设置为空。
}
return 0;
}
六、自己实现字符串拷贝函数(面试考题)
全拷贝:
#include<stdio.h>
#include<string.h>
char* myStrcpy(char *des, char *src)
{
if(des == NULL || src == NULL){
return NULL;
}
char *bak = des;
while(*src != '\0'){
*des++ = *src++;
}
*des = '\0';
return bak;
}
int main()
{
char str[128] = {'\0'};
char *p = "yangchen handsome";
myStrcpy(str, p);
puts(str);
return 0;
}
其中,函数部分的 w h i l e while while 循环部分有三种写法,其中还有两种:
while(*src != '\0'){
*des = *src;
des++;
src++;
}
while((*des++ = *src++) != '\0');
运行结果:
限定字数拷贝:
#include<stdio.h>
#include<string.h>
char* myStrncpy(char *des, char *src, int count)
{
if(des == NULL || src == NULL){
return NULL;
}
char *bak = des;
while(*src != '\0' && count > 0){
*des++ = *src++;
count--;
}
if(count > 0){
while(count > 0){
count--;
*des = '\0';
}
return des;
}
*des = '\0';
return bak;
}
int main()
{
char str[128] = {'\0'};
char *p = "yangchen handsome";
myStrncpy(str, p, 6);
puts(str);
return 0;
}
运行结果:
七、C语言断言函数assert
a s s e r t ( ) assert() assert() 断言函数,用于在调试过程中捕捉程序错误:
//assert()相当于一个if语句
if(假设成立)
{
程序正常运行;
}
else
{
报错&&终止程序!(避免由程序运行引起更大的错误)
}
assert 宏的原型定义在 assert.h 中,其作用是如果它的条件返回错误,则终止程序执行。
#include <assert.h>
void assert( int expression );
assert() 的用法很简单,只需要传入一个表达式,在程序运行它会计算这个表达式的结果:
如果表达式的结果为“假/0”,assert() 会打印出断言失败的信息,并调用 abort() 函数终止程序的执行;
如果表达式的结果为“真”,assert() 就什么也不做,程序继续往后执行。
用法总结与注意事项
(1)在函数开始处检验传入参数的合法性
(2)每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败
(3)不能使用改变环境的语句,因为assert只在Debug 生效,如果这么做,会使用程序在真正运行时遇到问题。例如:assert(++i <= 100);
使用 assert 的缺点是:频繁的调用会极大的影响程序的性能,增加额外的开销。
assert()只有在 Debug 版本中才有效,如果编译为 Release 版本则被忽略。
八、字符串拼接strcat的使用及自己实现
函数原型:
char *strcat(char *dest,const char *src);
函数作用:把src所指向的字符串(包括’\0’)赋值到dest所指向的字符串面(删除dest原来末尾的’\0’),要保证dest足够长,以容纳被复制进来的*src,*src中原有的字符不变,返回指向dest的指针。应用:
char str[128] = "yangchenyang";
char *p = " handsome";
strcat(str, p);
puts(str);
自己实现strcat:
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include <assert.h>
char* myStrcat(char *des, char *src)
{
assert(des != NULL && src != NULL);
char *bak = des;
while(*des != '\0'){
des++;
}
while((*des++ = *src++) != '\0');
*des = '\0';
return bak;
}
char* myStrcat2(char *des, char *src)
{
assert(des != NULL && src != NULL);
char *bak = des;
strcpy(des+strlen(des),src);
return bak;
}
char* myStrcat3(char *des, char *src)
{
assert(des != NULL && src != NULL);
char *bak = des;
for(;*des != '\0';des++);
while((*des++ = *src++) != '\0');
*des = '\0';
return bak;
}
char* myStrcat4(char *des, char *src)
{
assert(des != NULL && src != NULL);
char *bak = des;
while(*des){ //'\0'也是while不成立的条件
des++;
}
while((*des++ = *src++) != '\0');
*des = '\0';
return bak;
}
int main()
{
char str[128] = "yangchenyang";
char *p = " handsome";
myStrcat4(str, p);
puts(str);
return 0;
}
八、字符串比较strcmp的使用及自己实现
函数原型:
int strcmp(const char *s1,const char *s2);
功能:若 s t r 1 = s t r 2 str1=str2 str1=str2,则返回 0 0 0 ;若 s t r 1 < s t r 2 str1<str2 str1<str2 ,则返回负数;若 s t r 1 > s t r 2 str1>str2 str1>str2 ,则返回 1 1 1 ;
int strncmp(const char *str1,const char *str2,size_t n);
功能:把 s t r 1 str1 str1 和 s t r 2 str2 str2 进行比较,最多比较前n个字节,若 s t r 1 str1 str1 和 s t r 2 str2 str2 的前n个字符相同,则返回 0 0 0 ;若 s 1 s1 s1 大于 s 2 s2 s2 ,则返回大于 0 0 0 的值,若 s 1 s1 s1 小于 s 2 s2 s2 ,则返回小于 0 0 0 的值。举例:
#include<stdio.h>
#include<string.h>
int main()
{
char *p1 = "yangchenyanga";
char *p2 = "yangchenyangb";
int ret = strcmp(p1, p2);
printf("ret = %d\n",ret);
return 0;
}
运行结果:
自己实现strcmp:
大部分写法:
int myStrcmp(char *str1, char *str2)
{
int ret = 0;
while(*str1 && *str2 && (*str1 == *str2)){
str1++;
str2++;
}
ret = str1 - str2;
if(ret > 0){
ret = 1;
}
if(ret < 0){
ret = -1;
}
return ret;
}
优化以后:(采用ASCII码相加比较的形式比较)
int myStrcmp(char *str1, char *str2)
{
int ret = 0;
int n_str1 = 0;
int n_str2 = 0;
char *bakStr1 = str1;
char *bakStr2 = str2;
while(*str1 && *str2 && (*str1 == *str2)){
str1++;
str2++;
}
if(*str1 || *str2){
str1 = bakStr1;
str2 = bakStr2;
while(*str1){
n_str1 = n_str1 + *str1;
str1++;
}
while(*str2){
n_str2 = n_str2 + *str2;
str2++;
}
}
ret = n_str1 - n_str2;
if(ret > 0){
ret = 1;
}
if(ret < 0){
ret = -1;
}
return ret;
}