C语言之字符串、字符串函数和字符函数总结

目录

前言 

一、字符串

二、字符串的输入

1、gets():

2、fgets():gets()的替代品

3、scanf():

三、字符串的输出

1、puts():

2、fputs():

3、printf():

4、自定义的输出函数

四、字符串函数

1、字符串长度函数strlen():

2、字符串的复制函数strcpy():

3、字符串的连接函数strcat():

4、字符串的比较函数strcmp():

 5、字符串的复制函数strncpy():

6、字符串的连接函数strncat():

7、字符串的比较函数strncmp():

8、字符串查找函数strstr():

9、切分字符串函数strtok():

10、strerror():

11、perror():

 五、字符函数


前言 

字符串是C语言中最有用、最重要的数据类型之一,本文是对字符串、字符串函数和字符函数笔记的总结。


一、字符串

1.字符串常量(字符串字面量):用双引号括起来的内容。字符串的结束标志是一个 \0 的转义字符。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串存储在内存中。

如果字符串常量之间没有间隔,或者用空白字符分隔,C将其视为串联起来的字符串常量。如:char greeting[50] = "Hello,and""how are""you"                                                                                                                                                                                                  " today!";

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

字符串常量属于静态存储类别,这说明如果在函数中使用字符串常量,该字符串只会被存储一次,在整个程序的生命周期内存在,即使函数被调用多次。

2.字符串数组的初始化:

定义字符串数组时必须要让编译器知道需要多少空间,我们可以采取两种方法:

(1)用足够的空间的数组存储字符串(必须确保数组元素的个数至少比字符串长度多1,用于容纳\0)(其余所有未被使用的元素都被自动初始化为0,这里的0不是指数字字符0,而是char形式的空字符),如:

 (2)省略数组初始化声明中的大小,编译器会自动计算数组的大小,如:

char arr[] = "ant and";

 3.字符串数组和字符指针:

char sa[] = "array";       VS          char* sp = "point"; 

(1)数组形式:

在计算机的内存中为数组sa分配一块连续的空间。通常,字符串都作为可执行文件的一部分存储在数据段中。当把程序载入内存时,也载入了程序的字符串。字符串存储在静态存储区中,但是程序在开始运行时才会为该数组分配内存,此时,才将字符串拷贝到数组中。注意,此时字符串有两个副本:一个是在静态内存中的字符串常量,另一个是存储在sa数组中的字符串。此后,编译器便把数组名sa识别为该数组首元素的地址。注意:sa为地址常量,必能更改,如果更改,这就意味着改变了数组的存储位置。可以有sa+1这样的操作,标识数组的下一个元素,但是不允许sa++等操作,递增递减运算符只能用于变量名前,不能用于常量

(2)指针形式:

编译器也会为字符串在静态存储区预留一块连续的空间,另外,一旦开始执行程序,它会为指针变量sp也留出一块存储位置,并把字符串的地址存储在指针变量中,该变量最终指向字符串的首字符。sp为指针变量,它的值可以改变,因此,可以使用递增递减运算符。

我们来看一段代码:

#include <stdio.h>
#define MSG "I am special"
int main()
{
    char sa[] = "I am special";
    const char* sp = "I am special";

    printf("%p\n", "I am special");
    printf("%p\n", sa);
    printf("%p\n", sp);
    printf("%p\n", MSG);
    printf("%p\n", "I am special");

    return 0;
}

 编译并运行该代码,输出如下:

 

该程序的输出说明了:

 ①初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。(sp和MSG的地址相同,而sa的地址不同)

②虽然字符串常量“I  am special”在程序中的两个printf()函数中出现了两次,但是编译器只使用了一个存储位置。而且与MSG的位置相同

③静态数据使用的内存与sa使用的动态内存不同。不仅值不同,特定的编译器甚至使用不同的位数表示两种内存

(3)如果要改变字符串,就不要用指针指向字符串常量,可以把非const数组初始化为字符串常量,因为数组获得的是原始字符串的副本。我们来看一段代码:

#include <stdio.h>
int main()
{
	char* p = "Klingon";

	p[0] = 'F';
	printf("Klingon\n");
	printf(":Beware the %s!\n", "Klingon");

	return 0;
}

p[0] = 'F';这样的行为是未被定义的,这样的语句有可能导致内存访问出错,也有可能会输出Flingon:Beware the Flingons(在内存中的字符串常量只有一份,修改这一份,会影响多出使用的地方),编译器在这方面难以捉摸,所以我们建议在把指针初始化为字符串常量时使用const限定符。即:const char* p1 = "Klingon";

4.指针和字符串

#include <stdio.h>
int main()
{
    const char* mesg = "apple";
    const char* copy;

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

    return 0;
}

编译并运行该代码,输出如下:

 从输出结果我们可以知道:mesg和copy的地址分别是00CFFC50和00CFFC44,mesg和copy都指向同一位置,程序并没有拷贝字符串,两个value值相同。

5.字符串数组和指针数组

char ccolor[][6] = {"red","blue","green"};
char* pcolor [] = {"red","blue","green"};

区别如下:

(1)ccolor是一个内含3个数组的数组,每个数组内含6个char类型的值,共占用18个字节,而pcolor是一个内含3个指针的数组,共占用12个字节。

(2)pcolor中的指针指向初始化时所用的字符串常量的位置,这些字符串常量存储在静态内存中,它所指向的字符串常量不能更改;ccolor中的数组则存储这字符串常量的副本,所以每个字符串都被存储了两次,此外,为字符串数组分配内存的使用率较低,ccolor中的每个元素必须相同,而且必须是能存储最长字符串的大小

6.我们来做一道有关字符串数组的题目:

#include <stdio.h>
int main()
{
    char str1[] = "hello world.";
    char str2[] = "hello world.";
    char *str3 = "hello world.";
    char *str4 = "hello world.";

    if(str1 ==str2)
       printf("str1 and str2 are same\n");
    else
       printf("str1 and str2 are not same\n");
       
    if(str3 ==str4)
       printf("str3 and str4 are same\n");
    else
       printf("str3 and str4 are not same\n");
       
    return 0; 
}

编译并运行该程序,输出如下:

 str1 and str2 are not same

str3 and str4 are same

//根据前面的总结,我们可以轻松得到这条题的答案

二、字符串的输入

1、gets():

它读取整行输入,直至遇到换行符,然后丢弃换行符,存储其他字符,并在这些字符的末尾添加一个空字符使其成为一个c字符串。

注意:gets()存在安全隐患,gets()无法检查字符串是否装得下输入行,如果输入的字符串过长,会导致缓冲区溢出,如果这些多余的字符只是占用了尚未使用的内存,就不会立即出现问题;如果它们擦除程序中的其他数据,会导致程序异常终止;或者还有其他情况。所以不久C11便摒弃了gets(),然而,在实际应用中,编译器为了可以兼容以前的代码,大部分都支持该函数。

2、fgets():gets()的替代品

(1)char *fgets( char *string, int n, FILE *stream );

string:数据存储的位置

n:读入字符的最大数量

stream:要读入的文件(如果从键盘输入数据,则以stdin(标准输入)作为参数)

(2)如果一切进行顺利,该函数返回的地址和传入的第一个参数相同,如果遇到文件末尾或发生错误,该函数会返回NULL。可以使用feof或ferror确定是否发生错误。

(3)与gets()还有一点不同的是:如果fgets()读到换行符,会把它存储在字符串中。但是有时候我们并不想把\n存储在字符串中,这样的换行符会带来一些麻烦,我们可以自定义一个函数:读取整行输入并用空字符代替换行符,或者读取一部分输入,并丢弃其余部分,看如下代码段:

char* mygets (char* st, int n)
{
    char* ret;
    int i = 0;
  
    ret = fgets(st, n, stdin);
    if(ret)
    {
        while(st[i] !='\n' && st[i] != '\0')
        {
            i++;
        }
        if(st[i] == '\n')
        {
            st[i] = '\0';
        }
        else
        {
            while(getchar() != '\n')
            {
                continue;
            }
         }
      }
}

我们的这个函数的缺点在于在遇到不合适的输入时毫无反应。

3、scanf():

使用格式控制说明%s,输入参数必须是字符型数组名。该函数以下一个空白字符(空行、空格、制表符或换行符)作为字符串的结束。如果指定了字段宽度,如%10s,那么scanf()将读取10个字符或读到第一个空白字符停止。如图表:

输入语句原输入序列name中内容剩余输入序列
scanf("%s",name);Fleebert[]HupFleebert[]Hup
scanf("%5s",name);Fleebert[]HupFleebert[]Hup
scanf("%5s",name);Ann[]UlarAnn[]Ular

注:[]代表一个空格

scanf()返回一个整数值,该值等于scanf()成功读取的项数或EOF(读到文件结尾时返回EOF)

其实,scanf()和gets()一样也存在一些潜在的缺点。如果输入行的内容过长,scanf()也会导致数据溢出,不过,%s转换说明中使用字段宽度可防止溢出。

三、字符串的输出

1、puts():

puts()的使用很简单,只需要把字符串的地址作为参数传递给它即可。它在显示字符串时会自动在其末尾添加一个换行符。该函数在遇到空字符时就停止输出,所以必须确保有空字符

2、fputs():

(1)int fputs( const char *string, FILE *stream );

string:要输出的字符串

stream:要写入数据的文件。如果要打印在显示器上,可以用定义在stdio.h中的stdout(标准输出)作为该参数

(2)与puts()不同的是,fputs()不会在输出的末尾添加换行符

3、printf():

(1)用%s输出字符串,输出参数可以是字符数组名或字符串常量,输出遇'\0'结束。

(2)与puts()不同的是,printf()不会在输出的末尾添加换行符

4、自定义的输出函数

不一定非要使用C库中的标准函数,我们自己也可以设计一个函数。如该函数可以打印一个字符串,并且统计打印的字符数

int myputs(const char* string)
{
    int count = 0;

    while(*string)//*string != '\0'
    {
        putchar(*string++);
        count++;
    }
    putchar('\n');

    return 0;
}

四、字符串函数

1、字符串长度函数strlen():

size_t strlen( const char *string );

strlen(s);参数s可以是字符数组名或字符串常量

(1)引用头文件<string.h>

(2)字符串以  '\0' 作为结束标志, strlen 函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。该函数返回的是无符号整型
#include <stdio.h>
#include <string.h>
int main()
{
    const char*str1 = "abcdef";
    const char*str2 = "bbb";

    if(strlen(str2) - strlen(str1) > 0)
    {
        printf("str2>str1\n");
    } 
    else
    {
        printf("srt1>str2\n");
    }

    return 0; 
}

编译并运行该代码,输出如下:

str2>str1

//该函数返回的是的无符号整型,无符号数-无符号数=无符号数(没有符号位的概念),3-6=-3,这将被解读成一个很大的正数

(3) 参数指向的字符串必须要以 '\0' 结束
char arr = {"a","b","c"};
strlen(arr);//随机值,因为你不知道\0在哪里

(4)自定义的strlen():

#include <stdio.h>
#include <assert.h>
//法一:利用计数器
int mystrlen(const char* str)
{
    int count = 0;
    assert(str != NULL);

    while(*str != '\0')
    {
        count++;
        str++;
    }
    
    return count;
}
//法二:函数递归 
int mystrlen(const char* str)
{
     assert(str != NULL);

     if(*str != '\0')
     {
         return 1 + mystrlen(str + 1);
     }
     else
     {
         return 0;
     }
}
//法三:指针-指针
int my_strlen(const char *str) 
{
     char *p = s;

     while(*p != '\0' )
         p++;

     return p-s; 
}
int main()
{
    char* arr = "abc";
 
    printf("%d\n",mystrlen(arr));

    return 0;
}

2、字符串的复制函数strcpy():

char *strcpy( char *strDestination, const char *strSource );

strcpy(s1,s2);参数s1必须是字符型基地址,参数s2可以是字符数组名或字符串常量

(1)引用头文件<string.h>

(2)源字符串必须以 '\0' 结束。

(3)会将源字符串中的 '\0' 拷贝到目标空间。

(4)目标空间必须足够大,以确保能存放源字符串。

(5)目标空间必须可变。

#include <stdio.h>
int main()
{
    char arr1[] = "xxxxxxxxxx";
    //char arr1[] = { 'a','b','c' };//error  源字符串没有以'\0'结尾
    //char arr1[] = "xxx";//error 目标空间不够大
    //const arr1[]="xxxxxxxxxx";//error 目标空间不可变
	char arr2[] = "hello";

    strcpy(arr1, arr2);
}

(6)该函数返回的是目标字符串strDestination的起始地址

(7)自定义的strcpy():

#include <stdio.h>
#include <assert.h>
char* mystrcpy(char* dest, const char* src)
{
	assert(src && dest);
	char* ret = dest;

	while (*dest++ = *src++)
	{
		;
	}

	return ret;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[10] = { 0 };
	
	printf("%s\n", MyStrcpy(arr2, arr1));
	return 0;
}

3、字符串的连接函数strcat():

char *strcat( char *strDestination, const char *strSource );

strcat(s1,s2);参数s1必须是字符型基地址,参数s2可以是字符数组名或字符串常量

(1)引用头文件<string.h>

(2)源字符串必须以 '\0' 结束(如果没有'\0'你不知道在哪里开始连接)

(3)目标空间必须有足够的大,能容纳下源字符串的内容

(4)目标空间必须可修改

(5)该函数返回的是目标字符串strDestination的起始地址

(6)自定义的strcat():

#include <stdio.h>
#include <assert.h>
char* mystrcat(char* dest, const char* src)
{
	assert(dest, src);
	char* ret = dest;
	//1.找到目标字符串的末尾
	while (*dest != '\0')
	{
		dest++;
	}
	//2.追加源字符串直到\0
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[20] = "abc";
	char arr2[] = { 'd','e','f','\0'};
	
	printf("%s\n", mystrcat(arr1, arr2));
	return 0;
}

4、字符串的比较函数strcmp():

int strcmp( const char *string1, const char *string2 );

strcmp(s1,s2);参数s1,s2,可以字符数组名或字符串常量

(1)引用头文件<string.h>

(2)该函数比较的是字符串的内容,不是长度。该函数的返回值表示string1和string2的关系

 该函数的比较规则是:从两个字符串的首字符串开始,依次比较相对应的字符(比较字符的ASCII码值),直到出现不同的字符或遇到'\0'为止。如果所有的字符都相同,则返回0;否则,以第一个不相同字符的比较结果为准,返回两个字符的差(即第一个字符-第二个字符的差)。

(3)自定义的strcmp():

#include <stdio.h>
#include <assert.h>
int mystrcmp(const char* s1, const char* s2)
{
    assert(s1 && s2);
     
    while(*s1 == *s2)
    {
        if(*s1 == '\0')
           return 0;
        else
        {
            s1++;
            s2++;
        }
    }
    
    return *s1 - *s2;
}   
int main()
{
    char arr1[] = "abcd";
    char arr2[] = "abc";
    int ret;

    ret = mystrcmp(arr1,arr2);
    if(ret == 0)
    {
       printf("arr1 = arr2");
    }
    else if(ret > 0)
    {
        printf("arr1 > arr2");
    }
    else
    {
        printf("arr1 < arr2");
    }

     return 0;
}

其实,strcmp,strcpy,strcat:长度不受限制的字符串函数(不够安全)
但是,strncpy,strncat,strncmp:长度受限制的字符串函数(相对安全)

 5、字符串的复制函数strncpy():

char *strncpy( char *strDest, const char *strSource, size_t count );

(1)引用头文件<string.h>

(2)strcpy()的问题在于,它不能检查目标空间能否容纳源字符串的副本,strncpy()的第三个参数指明可以拷贝的最大字符数。如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。该函数返回目标字符串strDest的起始地址。

 

(3)自定义的strncpy():

#include <stdio.h>
#include <assert.h>
#include <string.h>
char* mystrncpy(char* dest, const char* src, int n)
{
	assert(dest && src);
	char* ret = dest;
	int offset = 0;

	if (strlen(src) < n)
	{
		offset = n - strlen(src);
		n = strlen(src);
	}
	while (n--)
	{
		*dest++ = *src++;
	}
	while (offset--)
	{
		*dest = '\0';
	}
	
	return ret;
}
int main()
{
	char str1[20] = "abcdefg";
	char str2[] = "xxxxx";
	int n;

	scanf("%d", &n);
	mystrncpy(str1, str2, n);
	printf("%s\n", str1);

	return 0;
}

 

6、字符串的连接函数strncat():

char *strncat( char *strDest, const char *strSource, size_t count );

(1)引用头文件<string.h>

(2)strcat()无法检查参数1能否容得下参数2,如果分配给第一个数组的空间不够大,多出来的字符溢出到相邻存储单元就会出现问题。strncat()的第三个参数指定了最大添加字符数。该函数返回目标字符串strDest的起始地址。

(3)自定义的strncat():

#include <stdio.h>
#include <assert.h>
#include <string.h>
char* myStrncat(char* dest, const char* src, int n)
{
	char* ret = dest;

	assert(dest && src);
	while (*dest != '\0')
	{
		dest++;
	}
	if (strlen(src) < n)
	{
		n = strlen(src);
	}
	while (n)
	{
		*dest++ = *src++;
		n--;
	}
	*dest = '\0';
	return ret;
}
int main()
{
	char str1[20] = "hello";
	char str2[] = "world";
	int n;

	scanf("%d", &n);
	myStrncat(str1, str2, n);
	printf("%s\n",str1);

	return 0;
}

7、字符串的比较函数strncmp():

int strncmp( const char *string1, const char *string2, size_t count );

(1)引用头文件<string.h>

(2)与strcmp()不同的是:该函数比较到出现另个字符不一样或者一个字符串结束或者count个字符全部比较完

8、字符串查找函数strstr():

char *strstr( const char *string, const char *strCharSet );

(1)引用头文件<string.h>。

(2)在string中搜索strCharSet。该函数返回一个指针,指向字符串中第一个出现的strCharSet,如果strCharSet未出现在字符串中,则返回NULL。如果strCharSet指向长度为零的字符串,则函数返回字符串。

(3)自定义的strstr():

#include <stdio.h>
#include <assert.h>
char* mystrstr(const char* str1, const char* str2)//只是查找,不会修改内容
{
	assert(str1 && str2);
	char* s1;
	char* s2;
	char* cp = str1;

	if (*str2 == '\0')
	{
		return str1;
	}
	while (*cp)
	{
		s1 = cp;
		s2 = str2;
		while (*s1 == *s2 && *s2 != '\0' && *s1 !='\0')
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return cp;
		}
		cp++;
	}

	return NULL;
}
int main()
{
	char arr1[] = "i am a good student,a smart student";
	char arr2[] = "student";
	char* ret = mystrstr(arr1, arr2);//查找arr1中arr2第一次出现的位置
	
	if (ret == NULL)
	{
		printf("找不到\n");
	}
	else
	{
		printf("%s\n", ret);
	}

	return 0;
}

9、切分字符串函数strtok():

char *strtok( char *str, const char *sep );

(1)引用头文件<string.h>

(2)sep参数是个字符串,定义了用作分隔符的字符集合

(3)第一个参数指定一个字符串,它包含了 0 个或者多个由 sep 字符串中一个或者多个分隔符分割的标记。
(4)strtok 函数找到 str 中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用 strtok 函数切分的字符串一般都是临时拷贝的内容并且可修改。)
(5)strtok 函数的第一个参数不为 NULL ,函数将找到 str 中第一个标记, strtok 函数将保存它在字符串中的位置。
(6)strtok 函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
(7)如果字符串中不存在更多的标记,则返回 NULL 指针。
#include <stdio.h>
#include <string.h>
int main()
{
	char arr1[] = "zpw@bitedu.tech";
	char arr2[100] = { 0 };//临时数据
	char sep[] = "@.";
    //char* pch;
    char* ret = NULL;

	strcpy(arr2, arr1);//将数据拷贝一份,处理arr数组的内容
    //pch = strtok(arr2, sep);
    //while(pch!=NULL)
    //{
    //    printf("%s\n",pch);
	//    strtok(NULL, sep);
    //}
    //其实,我们第一次传的是非空指针,后面传的全部是空指针,利用这个规律,我们可以巧妙的利用for循环
	for (ret = strtok(arr2, sep); ret != NULL;ret = strtok(NULL,sep))
	{
		printf("%s\n", ret);
	}
	
	return 0;
}

10、strerror():

char *strerror( int errnum );

(1)引用头文件<string.h>和<errno.h>

(2)该函数的作用是返回错误码所对应的错误信息,C语言库函数调用失败的时候,会把错误码存储到errno变量中。

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
    FILE* pf = fopen("test.txt","r");

    if(pf == NULL)
    {
       printf("%s\n",strerror(errno));
    }
    else
    {
       printf("打开文件成功\n");
    }

    return 0;
}

编译并运行该代码,输出如下:

因为我是以读的形式打开文件的,并且我的电脑中没有这个文件,所以会打开文件失败。

11、perror():

(1)引用头文件<stdio.h>或<stdlib.h>

(2)该函数的功能:打印的功能+strerror()的功能

#include <stdio.h>
int main()
{
    FILE* pf = fopen("test.txt","r");

    if(pf == NULL)
    {
       perror("测试");
    }
    else
    {
       printf("打开文件成功\n");
    }

    return 0;
}

编译并运行该代码,输出如下:

 五、字符函数

该表所列函数在头文件<ctype.h>中说明。

函数
如果他的参数符合下列条件就返回真1,否则返回假0
iscntrl  
任何控制字符
isspace
空白字符:空格 ‘ ’ ,换页 ‘\f’ ,换行 '\n' ,回车 ‘\r’ ,制表符 '\t' 或者垂直制表符 '\v'
isdigit
十进制数字 0~9
isxdigit

 十六进制数字,包括所有十进制数字,小写字母a-f,大写字母A-F

islower
小写字母 a~z
isupper
大写字母 A~Z
isalpha
字母 a- z A- Z
isalnum
字母或者数字, a- z,A- Z,0~9
ispunct
标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph
任何图形字符
isprint
任何可打印字符,包括图形字符和空白字符

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值