【C语言】字符串

一、初识字符串

  1. 所谓的字符串,就是字符数组。和整型数组一样:
	int data[] = {1,2,3,4,5};
  1. 和整型数组一个道理:
	char str[] = {'h','e','l','l','o'};
  1. 改进为字符串:
	char cdata2[] = "hello";
	char *pchar = "hello"; //由于数组名就是地址,所以通常写成这样

注意:第一行的字符串定义为字符串变量,是数组,里面的元素是可以更改的,但通过第二行指针去定义的字符串是不可以更改的,因为它是字符串常量。

  1. 输出一个字符串:
	printf("%s",pchar);
	puts(pchar);
  1. 注意指针的操作,当一个指针为野指针的时候,我们不能让它保存字符串,因为这样会影响到其他程序的内存空间:
	char *p; //野指针,并没有明确的内存指向,危险
	*p = 'a';



二、字符串的存放方式及结束标志

  1. 字符串和字符数组的区别:
#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

-11 一样 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

IT阳晨。

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

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

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

打赏作者

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

抵扣说明:

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

余额充值