内容
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;
}
- 目前的设备内存(大)很少出现内存重叠的现象;