C语言——字符与字符串函数

内容

1.字符
2.字符串
3.检验参数合法性(非常必要)
4.字符串相关库函数实现(面试)


*1.字符

  • 字符:字符型数据;
    例:‘d’、’!’、’=’、’+’、’?’…

  • 在C语言中,字符型数据特点为:
    * 字符型数据只能用单引号括起来,不能用双引号或其它括号。
    * 字符型数据只能是单个字符,不能是字符串。
    * 字符可以是字符集中任意字符。但数字被定义为字符型之后就不能参与数值运算。如’5’和5 是不同的。'5’是字符型数据,不能参与运算。

  • 转义字符:转义字符是一种特殊的字符。转义字符以反斜线""开头,后跟一个或几个字符。转义字符具有特定的含义,不同于字符原有的意义,故称“转义”字符。转义字符主要用来表示那些用一般字符不便于表示的控制代码。转义字符链接

  • 字符变量的存储:每个字符变量被分配一个字节的内存空间,因此只能存放一个字符。字符值是以ASCII码的形式存放在变量的内存单元之中的。
    * C语言允许对整型变量赋以字符值,也允许对字符变量赋以整型值。在输出时,允许把字符变量按整型量输出,也允许把整型量按字符量输出。
    * 整型量为2个字节单位,字符变量为单字节量,当整型量按字符型量处理时,只有低八位字节参与处理。(‘a’-(十进制整数表示为)97,‘A’=‘a’-32=65;

*2.字符串

  • 字符串:字符串是由一对双引号括起的字符序列
    例:“CHINA” , “C program” , “$12.5”…
  • 字符与字符串区别
    • 字符由单引号括起来,字符串由双引号括起来。
    • 字符只能是单个字符,字符串则可以含一个或多个字符。
    • 可以把一个字符型数据赋予一个字符变量,但不能把一个字符串赋予一个字符变量。在C语言中没有相应的字符串变量,也就是说不存在这样的关键字,将一个变量声明为字符串。但是可以用一个字符数组来存放一个字符串。
    • 字符占一个字节的内存空间。字符串占的内存字节数等于字符串中字节数加1。增加的一个字节中存放字符"\0" (ASCII码为0)。这是字符串结束的标志。

例:‘a’——内存中存储ASCII仍为’a’; “a”——内存中为 a \0;

*3.检验参数合法性

#include<stdlib.h>
#include<string.h>
#include<assert.h>  //包含assert的库函数头文件

int maxNum(int a, int b) {
//assert校验参数
	assert(a != 0);
	assert(b != 0);
//if校验参数
	if (a == 0 || b == 0) {
		return 0;
	}
	else {
		int i = 1;
		int max = 0;
		for (; i <= (a < b ? a : b); i++) {
			if (a % i == 0 && b % i == 0) {
				max = i;
			}
		}
		return max;
	}
}

int main() {
	int a = 1;
	int b = 2;
	//不但要在函数体内检验参数合法性,函数调用时也需检验;
		if (a != 0 && b != 0) {
			int ret = maxNum(a, b);
			printf("%d\n", ret);
		}
	system("pause");
	return 0;
}

在这里插入图片描述
以上两种校验方法都可以进行参数合法性检验(校验思想很重要);

  • if 校验参数:针对服务器接收到某次请求有问题时,发现数据有问题,需要用if检验相对温和;不至于影响其他请求的接受和处理;(系统需要一定的容错性/鲁棒性)
  • assert校验:针对在启动服务器时,发现数据加载有问题,可能影响服务器后续的接收和处理请求;故采用assert相对强烈的方式检验;可以在数据加载阶段让系统直接停止运行;(可以避免空指针也可以避免野指针)

在函数内部设置校验方法是有必要的;在函数体外函数调用的时候仍需要进行参数合法性判断——因为在实际开发中,一般是多人多人协作完成开发,比如一个人写一个函数另一个人进行使用;所以不同人都对函数进行参数检验,双重保证!

  • 在线笔试中,不要使用assert ,因为在提交运行时,系统会给出不合法的参数进行检测;使用assert会使程序崩溃,不利于得分!

*4.字符串库函数实现

  • C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在 常量字符串中或者 字符数组 中。 字符串常量 适用于那些对它不做修改的字符串函数。
  • const 修饰的字符串表示不可修改;

<1>实现计算字符串长度的库函数strlen
在这里插入图片描述

  • strlen函数计算字符串长度不包括字符串结尾的’\0’;
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

size_t strlen(const char* str) {
	//校验参数的合法性 1.(不能校验野指针)
	if (str == NULL) {
		return 0;
	}
	//校验参数的合法性 2.(避免野指针)
	assert(str != NULL);
	size_t size = 0;
	while (str[size] != '\0') {
		size++;
	}
	return size;
}

int main() {
	printf("字符串“abcd”的长度为:%d\n", strlen("abcd"));
	char* p = "zxcv";
	if (p != NULL) {
		printf("p的长度为:%d\n", strlen(p));
	}
	system("pause");
	return 0;
}

在这里插入图片描述

  • strlen 只针对字符串使用;
  • size_t 是无符号整数,相减会发生溢出(慎用);

<2>实现字符串拷贝的库函数strcpy
在这里插入图片描述

  • 源字符串必须以 ‘\0’ 结束。
  • 会将源字符串中的 ‘\0’ 拷贝到目标空间。(这样拷贝给目标的才是一个字符串)
  • 目标空间必须足够大,以确保能存放源字符串。
  • 函数返回的是一个指向拷贝的目标字符串内存空间的指针(首地址);
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

typedef struct student {
	int id;
	char name[100];
}stu;

char* Strcpy(char* dest, const char* src) {
	assert(dest != NULL);
	assert(src != NULL);
	int i = 0;
	while (src[i] != '\0') {
		dest[i] = src[i];
		i++;
	}
	dest[i] = '\0';
	return dest;
}

int main() {
	char src[] = "hehe";
	char dest[1024] = { 0 };
	Strcpy(dest,src);
	printf("%s\n", dest);
	stu s = { 1,"zwr" };
	//s.name = "lisa";
	strcpy(s.name, "lisa");
	printf("%s\n", s.name);
	system("pause");
	return 0;
}

在这里插入图片描述
在这里插入图片描述

  • 可修改的左值,其类型不可以被声明为限定符 const,并且可修改的左值 不能是数组类型。 如果把数组名当做左值赋值,会引发lvalue required as increment operand错误。
    在这里插入图片描述
  • 若目标数组空间不够大,会发生上图所示异常——数组下标越界!

<3>实现将一个字符串添加在另一个字符串后面——实现两个字符串拼接的库函数strcat
在这里插入图片描述

  • 源字符src 串必须以 ‘\0’ 结束。
  • 目标空间必须有足够的大,能容纳下源字符串的内容。
  • 目标空间必须可修改。
  • 函数返回的是指向(需要拼接的目标 dest 字符串)空间的指针(地址);
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

char* Strcat(char* dest, const char* src) {
	assert(dest != NULL);
	assert(src != NULL);
	int destTail = 0;
	while (dest[destTail] != '\0') {
		destTail++;
	}
	//Strcpy(dest + destTail, src);
	int i = 0;
	while (src[i] != '\0'){
		dest[i + destTail] = src[i];
		i++;
	}
	dest[i + destTail] = '\0';
	return dest;
}

int main() {
	char src[] = "heihei";
	char dest[1024] = "xixi";
	Strcat(dest, src);
	printf("%s\n", dest);
	system("pause");
	return 0;
}

在这里插入图片描述

<4>实现比较两个字符串是否相等(大小)的库函数strcmp
在这里插入图片描述

  • 第一个字符串大于第二个字符串,则返回大于0的数字
  • 第一个字符串等于第二个字符串,则返回0
  • 第一个字符串小于第二个字符串,则返回小于0的数字
  • 字符串比较大小,常见 如AB…Z 的字典序;(越靠前越小
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

int Strcmp(const char* dest, const char* src) {
	assert(dest!= NULL);
	assert(src != NULL);
	const char* p1 = dest;
	const char* p2 = src;
	//方法1:
	while (*p1 != '\0' || *p2 != '\0') {
		int diff = *p1 - *p2;
		if (diff < 0 || diff>0) {
			return diff;
		}
		else if (diff == 0) {
			p1++;
			p2++;
		}
	}
	return 0;
	//方法二:
	//while (*p1 != '\0' && *p2 != '\0') {
	//	if (*p1 < *p2) {
	//		return -1;
	//	}
	//	else if (*p1 > *p2) {
	//		return 1;
	//	}
	//	p1++;
	//	p2++;
	//}
	//if (*p1 < *p2) {
	//	return -1;
	//}
	//else if (*p1 > *p2) {
	//	return 1;
	//}
	//else if(*p1==*p2){
	//	return 0;
	//}
	//return *p1 - *p2;
}

int main() {
	char src[] = "hiii";
	char dest[] = "hiig";
	int ret = Strcmp(dest, src);
	printf("%d\n", ret);
	system("pause");
	return 0;
}

上述程序中 int diff = *p1 - *p2——可以直接表示两个指向字符的指针相减=>两个字符在字典序中相差几位;
在这里插入图片描述

<5>实现拷贝特定长度的字符串的库函数strncpy
在这里插入图片描述

  • 拷贝num个字符从源字符串到目标空间。
  • 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边填充0,直到num个。
  • 如果num小于源字符串长度,则将源的前num个字符拷贝给目标dest ,再最后加上’\0’;
  • 返回拷贝完成之后的目标字符串;
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

char* Strncpy(char* dest, const char* src, size_t num) {
	assert(dest != NULL);
	assert(src != NULL);
	assert(num != NULL);
	size_t i = 0;
	while (src[i] != '\0' && i < num) {
		dest[i] = src[i];
		i++;
	}
	//dest[i] = '\0';
	while (i <= num) {
		dest[i] = '\0';
		i++;
	}
	return dest;
}

int main() {
	char src[] = "hehe";
	char dest[1024] = { 0 };
	Strncpy(dest, src, 3); //size_num<=sizeof(dest)-1;
	printf("%s\n", dest);
	system("pause");
	return 0;
}
  • 在调用strncpy函数时,需要注意传入的num(字节数)值 <= sizeof(dest)-1 ,确保有最后一个字节的空间放置’\0’,使得目标成功拷贝为字符串!
    在这里插入图片描述

<6>实现特定长度将源字符串拼接到目标字符串的库函数strncat
在这里插入图片描述

  • 目标要足够大以包含连接的结果字符串,包括附加的空字符
  • 当源小于num时,只赋值src中空字符之前的字符;最后给目标字符串后加上’\0’
  • 当源大于num时,将源src中前num个字符给目标,最后加上’\0’
  • 返回的仍然是目标;
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

char* Strncat(char* dest, const char* src, size_t num) {
	assert(dest != NULL);
	assert(src != NULL);
	assert(num != 0);
	size_t  destTail = 0;
	while (dest[destTail] != '\0') {
		destTail++;
	}
	int i = 0;
	while (src[i] != '\0' && i < num) {
		dest[destTail + i] = src[i];
		i++;
	}
	dest[destTail + i] = '\0';
	return dest;
}

int main() {
	char src[] = "hehe";
	char dest[1024] = "haha";
	Strncat(dest, src, 2); //size_num<=sizeof(dest)-1;
	printf("%s\n", dest);
	system("pause");
	return 0;
}

在这里插入图片描述
<7>实现两个字符串比较特定长度的大小的库函数strncmp
在这里插入图片描述

  • 比较两个字符串前num个字符的大小;返回原理和strcmp同理;
  • 如果两个字符串第一个字符相等,则继续向后对应位进行比较,直到达到终止空字符,或直到两个字符串中的num 个字符匹配,以先发生者为准;
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

int Strncmp(const char* dest, const char* src, size_t num) {
	assert(dest != NULL);
	assert(src != NULL);
	assert(num != 0);
	size_t i = 0;
	while (dest[i] != '\0' && src[i] != '\0' && i < num) {
		if (dest[i] < src[i]) {
			return -1;
		}
		else if (dest[i] > src[i]) {
			return 1;
		}
		else {
			i++;
		}
	}
	return dest[i - 1] - src[i - 1];
}

int main() {
	char src[] = "hehe";
	char dest[] = "heha";
	int ret = Strncmp(dest, src, 4); 
	printf("%d\n", ret);
	system("pause");
	return 0;
}

在这里插入图片描述
<8>实现判断两个字符串前者是否包含于后者的库函数strstr
在这里插入图片描述

  • 创建两个指针分别指向两个字符串;
  • blank指向str1,sub指向str2,red始终=blank;
  • 从第一个字符开始比较,若blank指向的字符和sub指向的字符不同,则指针blank向后移动一位(指向第二个字符),再和sub指向的字符进行比较,依次进行比较;
  • 直到blank和sub指向的字符相同,则red 和 sub 向后++继续比较;
  • 如果比较结果不相等,则blank再继续++向后和初始sub比较;
  • 若red==sub 并且sub指向的str2字符都比较完了,则str2包含于str1,可以返回blank;
  • 反之,blank==’\0’时,sub 仍没有匹配完, 则返回空,表示str2不包含于str1;
    在这里插入图片描述
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

char* Strstr(const char* str1, const char* str2) {
	assert(str1 != NULL);
	assert(str2 != NULL);
	assert(*str1 != '\0');
	assert(*str2 != '\0');
	const char* blank = str1;
	while (*blank != '\0') {
		char* red = blank; //临时比较
		char* sub = str2;
		while (*sub != '\0' && *red != '\0' && *red == *sub) {
			red++;
			sub++;
		}
		if (*sub == '\0') {
			return blank;
		}
		blank++;
	}
	return NULL;
}

int main() {
	char* str1 = "hello TiMi!";
	char* str2 = "TiMi";
	const char* ret = Strstr(str1, str2);
	printf("%s\n", ret);
	system("pause");
	return 0;
}
  • char* 可以赋值给 const char*,发之不能;
    在这里插入图片描述

<9>实现字符串切分的库函数strtok
在这里插入图片描述

  • 将字符串str通过tokens(分割标记)分割成不同的连续字符;
  • 在第一次调用时,该函数需要一个 C 字符串作为str 的参数,其第一个字符用作扫描标记的起始位置。在随后的调用中,该函数需要一个空指针,并使用最后一个标记结束后的位置作为新的扫描起始位置。
  • 要确定标记的开头和结尾,该函数首先从起始位置开始扫描不包含在分隔符中的第一个字符(它成为标记的开头))。然后从标记的这个开头开始扫描分隔符中包含的第一个字符,它成为标记的结尾。如果找到终止空字符,扫描也会停止。标记的这一结尾会自动替换为空字符,并且标记的开头由函数返回。
  • 一旦在对strtok的调用中找到str的终止空字符,对该函数的所有后续调用(以空指针作为第一个参数)都将返回空指针。

例:
在这里插入图片描述

  • strtok 缺点
    • 该函数需要多次调用才能完成功能,复杂;
    • 每次调用参数不同,麻烦;
    • 调用该函数破坏了原有的字符串str;
    • 函数内部,由于每次调用会使用上次的调用位置,使用到static 改变局部变量的生存周期(变量存活于 整个程序进程),使用静态变量,会导致线程不安全

<10>实现内存空间的拷贝的库函数memcpy
在这里插入图片描述

  • 将num个字节的值从source指向的位置直接复制到destination指向的内存块;
  • 任意类型的指针都可赋值给void* ,其只知道地址,不知道大小;
  • 目标和源参数所指向的数组的大小至少应为num;
  • 但是该函数不能实现空间重叠的拷贝;
  • memcpy和strcpy及和 字符串没有关系,只是把一块内存中的数据拷贝到另一块内存中
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

typedef struct stu {
	int id;
	char name[100];
}student;

void* Memcpy(void* dest, const void* src, size_t num) {
	assert(src != NULL);
	assert(num != 0);
	char* cdest = (char*)dest;
	const char* csrc = (const char*)src;
	for (size_t i = 0; i < num; i++) {
		cdest[i] = csrc[i];
	}
	return dest;
}

int main() {
	int a1[] = { 3,2,1 };
	int a2[100] = { 0 };
	Memcpy(a2, a1, sizeof(a1)); //数组传参时,数组名可以隐式转化为首元素地址
	for (int i = 0; i < 3; i++) {
		printf("%d ", a2[i]);
	}
	student s1 = { 1,"zwr" };
	student s2;
	Memcpy(&s2, &s1, sizeof(s1)); 
	printf("\n%d,%s\n", s2.id, s2.name);

	system("pause");
	return 0;
}

在这里插入图片描述

<11>实现内存空间拷贝(包括重叠的拷贝)的库函数memmove
在这里插入图片描述

  • 将num个字节的值从source指向的位置直接复制到destination指向的内存块;
  • 允许源和目标内存空间重叠;
  • 为了避免溢出,两个指向的数组的大小目标和源参数至少应为num个字节;
    在这里插入图片描述
  • 当目标空间重叠了源空间的前面部分空间,则和memcpy拷贝方法同理;
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

void* Memmove(void* dest, const void* src, size_t num) {
	assert(src != NULL);
	assert(num != 0);
	char* cdest = (char*)dest;
	const char* csrc = (const char*)src;
	if (cdest > csrc && cdest < csrc + num) {
		for (size_t i = num; i > 0; i--) {
			cdest[i - 1] = csrc[i - 1];
		}
	}
	else {
		for (size_t i = 0; i < num; i++) {
			cdest[i] = csrc[i];
		}
	}
	return dest;
}

int main() {
	int a1[] = { 3,2,1 };
	int a2[100] = { 0 };
	Memmove(a2, a1, sizeof(a1));
	for (int i = 0; i < 3; i++) {
		printf("%d ", a2[i]);
	}
	system("pause");
	return 0;
}
  • 目前的设备内存(大)很少出现内存重叠的现象;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
sscanf函数C语言中一个非常常用的函数,它可以将一个字符串按照指定的格式转换成相应的数据类型。在嵌入式开发中,sscanf函数也是非常常见的,因为很多时候需要从串口或者其他外部设备中读取数据,并将其转换成相应的数据类型进行处理。下面是一些sscanf函数的使用技巧: 1. 使用sscanf函数时一定要注意格式字符串的正确性。格式字符串中的占位符必须与待转换的数据类型相对应,否则会发生未知错误。 2. 如果待转换的字符串中包含多个数据,可以使用多个占位符进行转换。例如,如果待转换的字符串为"1,2,3",可以使用" %d,%d,%d"的格式字符串进行转换。 3. 可以使用sscanf函数的返回值来判断转换是否成功。如果返回值等于待转换字符串的长度,则说明转换成功,否则转换失败。 4. 如果待转换的字符串中包含浮点数,可以使用"%f"或者"%lf"的格式字符串进行转换。 5. 如果待转换的字符串中包含十六进制数,可以使用"%x"的格式字符串进行转换。 6. 如果待转换的字符串中包含字符字符串,可以使用"%c"或者"%s"的格式字符串进行转换。 7. 如果待转换的字符串中包含指针类型的数据,可以使用"%p"的格式字符串进行转换。 总之,在使用sscanf函数时一定要注意格式字符串的正确性,否则很容易出现转换错误的情况。同时,还应该注意sscanf函数返回值的判断,以确保转换的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值