本章介绍了字会串的应用和字符串处理的一些函数。字符串是笔试以及面试的热门考,通过字符串测试可以考察程序员的编程规范以及编程习惯。其中也包括了许多知识点,例如内存越界、指针与数组操作等。许多公司在面试时会要求应试者写一段strcpy复制字符串或字符串子串操作的程序。
面试题1 使用库函数将数字转换为字符串
【解析】
C语言提供了几个标准库函数,可以将任意类型 (整型,长整型,浮点型等)的数字转换为字符串。下面列举了各函数的方法及其说明。
itoa(): 将整型值转换为字符串;
ltoa(): 将长整型值转换成字符串;
ultoa(): 将无符号长整型转换为字符串;
gcvt(): 将浮点型数转化为字符串,取四舍五入;
ecvt(): 将双精度浮点型值转化为字符串;
fcvt(): 以指定位数为转换精度,其余同ecvt();
还可以使用sprintf系列函数把数字转换成字符串,这种方式的速度比itoa()系列函数的速度慢。下面的程序演示了如何使用itoa()函数和gcvt()函数。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num_int = 435;
double num_double = 435.10f;
char str_int[30];
char str_double[30];
itoa(num_int, str_int, 10);//把整数num_int转成字符串str_int
gcvt(num_double, 8, str_double);//把浮点数num_double转换成字符串str_double
printf("str_int: %s\n", str_int);
printf("str_double: %s\n", str_double);
return 0;
}
程序输出结果:
代码第11行中的参数10表示按十进制类型进行转换,转换后的结果是“435”。如果按二进制类型进行转换,则结果为“1101110011”。
代码第12行中的参娄8表示精确位数,这里得到的结果是“435.10001”。
【答案】
可以使用atoi系列的函数把数字转换成字符串。
面试题2 不使用库函数将整数转换为字符串
【解析】
如果不使用atoi或sprintf等库函数,我们可以通过把整数的各位上的数字加‘0’转换成char类型并存到字符数组中。但要注意,
需要采用字符串逆序的方法。看下面的C++程序示例。
#include <iostream>
using namespace std;
void int2str(int n, char *str) //这边n表示整数
{
char buf[10] = "";
int i = 0;
int len = 0;
int temp = n < 0 ? -n : n; //temp为n的绝对值
if (str == NULL)
{
return;
}
while (temp)
{
buf[i++] = (temp % 10) + '0'; //把temp的每一位上的数存入buf
temp = temp / 10;
}
len = n < 0 ? ++i : i; //如果n为负数,则多一位来存储负号
str[i] = 0; //末尾是结束符0
while (1)
{
i--;
if (buf[len - i - 1] == 0)
{
break;
}
str[i] = buf[len - i - 1]; //把buff数组里的字符拷到字符串
}
if (i == 0)
{
str[i] = '-'; //如果是负数,添加负号
}
}
int main() {
int nNum;
char p[10];
cout << "Please input an integer:";
cin >> nNum;
cout << "output: ";
int2str(nNum, p); //整型数转换成字符串
cout << p << endl;
return 0;
}
这个程序中的int2str函数完成了int类型到字符串的转换。在main函数的第46行对int2str函数做了测试。程序的执行结果:
如果输入的是个负数,例如:
接下来对int2str函数的实现进行分析。
代码第9行,把传入参数n的绝对值赋给temp,以后在计算各个位的整数时就用temp了,这样保证在负数下取余不会出现问题。
代码第11~14行判断str的有效性,str应不为NULL。
代码第15~19行的while循环中,将n的各个位存放到局部数组buf中,存放的顺序与整数顺序相反。例如n为整数123456,whiile循环结束后buf应为“654321”。
代码第21行计算实际转换后的字符串长度len,如果是负数,长度应该再加1.
代码第22~31行把数组buf中的非0元素逆向复制到参数str指向的内存中,如果n是奂数,则保留str指向的第一个内存以存放负号‘-’。
面试题3 使用库函数将字符串转换为数字
【解析】
与上题数字转换为字符串类似,C/C++提供了几个标准库函数,可以将字符串转换为任意类型(整型、长整型、浮点型等)的数字。
下面列举了各函数的方法及其说明。
atof():将字符串转化为双精度浮点型值;
atoi():将字符串转换为整型值;
atol():将字符串转换为长整型值;
strtod():将字符串转化为双精度浮点型值;
strtol():将字符串转换成长整型值;
strtoul():将字符串转换成无符号长整型值;
下面的程序演示了如何使用atoi()函数和atof()函数。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num_int;
double num_double;
char str_int[30] = "435"; //将要被转为整型值的字符串
char str_double[30] = "436.55"; //将要被转为浮点型值的字符串
num_int = atoi(str_int); //转换为整型值
num_double = atof(str_double); //转换为浮点型值
printf("num_int: %d\n", num_int);
printf("num_double: %lf\n", num_double);
return 0;
}
输出结果:
面试题4 不使用库函数将字符串转换为数字
【解析】
程序代码如下。
#include <iostream>
using namespace std;
//将字符串转换为整数
int str2int(const char *str)
{
int temp = 0;
const char *ptr = str; //ptr保存str字符串开头
if (*str == '-' || *str == '+') //如果第一个字符是正负号,则移到下一个字符
{
str++;
}
while (*str != 0)
{
if ((*str < '0') || (*str > '9')) //如果当前字符不是数字,则退出循环
{
break;
}
temp = temp * 10 + (*str - '0'); //如果当前字符是数字,则计算数值,
str++; //移到下一个字符
}
if (*ptr == '-') //如果字符串以'-'开头,则换成相反数
{
temp = -temp;
}
return temp;
}
int main()
{
int n = 0;
char p[10] = "";
cin.getline(p, 20); //从终端获取一个字符串
n = str2int(p); //把字符串转换成整型数
cout << n << endl;
return 0;
}
输出结果:
上面程序中的str2int函数用于将字符串转换成整数。这个函数的转换过程与面试题2中的int2str函数相比更加简单。它没有逆序的需要,只需要做一次while循环(代码第13行)就能把数值大小计算出来。如果最后是负数,加一个负号。
面试题5 编程实现strcpy函数
已知strcpy函数的原型是:
char *strcpy(char *strDest, const char *strSrc);
(1)不调用库函数,实现strcpy函数。
(2)解释为什么要返回char*
【解析】
程序代码如下。
#include <stdio.h>
char *strcpy(char *strDest, const char * strSrc) //实现strSrc到strDest的复制
{
if ((strDest == NULL) || (strSrc == NULL)) //判断参数strDest和strSrc有效性,是否为空
{
return NULL;
}
char *strDestCopy = strDest; //保存目标字符串的首地址
while ((*strDest++ = *strSrc++) != '\0') //把strSrc字符串的内容复制到strDest下
;
return strDestCopy;
}
int getStrLen(const char *strSrc) //实现获取strSrc字符串的长度
{
int len = 0; //保存长度
while (*strSrc++ != '\0') //循环直到遇见结束符'\0'为止
{
len++;
}
return len;
}
int main()
{
char strSrc[] = "Hello World"; //要被复制的源字符串
char strDest[20]; //要复制到的目的字符数组
int len = 0; //保存目的字符数组中字符的长度
len = getStrLen(strcpy(strDest, strSrc)); //链式表达式,先复制再计算长度
printf("strDest:%s\n", strDest);
printf("Length of strDest:%d\n", len);
return 0;
}
(1)strcpy函数的实现说明:
代码第5~7行判断传入的参数strDest和strSrc是否为NULL,如果是则返回NULL。
代码第9行把strDest的值保存到strDestCopy。
代码第10行对strSrc和strDest两个指针做循环移动,并不断复制strSrc内存的值到strDest内存中。
由于第2步中保存了strDest的值,因此这里只需返回strDestCopy,那么函数调用完后返回的就是strDest的值。
(2)为什么strcpy函数要返回char*类型呢?这是为了能使用链式表达式。由于在strcpy中使用了char*返回类型,因此在代码第32行中可以通过种这链式表达式来同时做两个操作。首先调用strcpy,使得strDest复制strSrc指向的内存数据,然后调用getStrLen函数获取strDest字符串的长度。这样不仅调用方便,而且程序结构简洁明了。程序的输出结果如下。
面试题6 编程实现memcpy函数
【答案】
程序代码如下所示。
#include <stdio.h>
#include <assert.h>
void *memcpy2(void *memTo, const void *memFrom, size_t size)
{
assert((memTo != NULL) && (memFrom != NULL)); //memTo和memFrom必须有效
char *tempFrom = (char *)memFrom; //保存memFrom的首地址
char *tempTo = (char *)memTo; //保存memTo的首地址
while (size-- > 0) { //循环size次,复制memFrom的值到memTo中
*tempTo++ = *tempFrom++;
}
return memTo;
}
int main()
{
char strSrc[] = "Hello World"; //将被复制的字符数组
char strDest[20]; //目的字符数组
memcpy2(strDest, strSrc, 4); //复制strSrc的前4个字符到strDest中
strDest[4] = '\0'; //把strDest的第5个元素赋为结束符'\0'
printf("strDest:%s\n", strDest);
return 0;
}
memcpy的实现如下:
与strcpy不同,memcpy以参数size决定复制多少个字符(strcpy是遇见结束符‘\0’结束)。由于在主程序中只复制了strSrc的前4个字符(代码第22行),程序输出如下。
面试题7 strcpy与memcpy的区别
【解析】
主要有下面几方面的区别。
复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整形、结构体、类等。
复制的方法不同。strcpy不需要指定长度,它是遇到字符串结束符‘\0’而结束的。memcpy则是根据其第三个参数决定复制的长度。
用途不同。通常在复制字符串时用strcpy;而若需要复制其他类型数据,则一般用memcpy。
面试题8 改错——数组越界
试题1:
void test1()
{
char string[10];
char *str1 = "0123456789";
strcpy(string, str1);
}
试题2:
void test2()
{
char string[10], str1[10];
int i;
for(i = 0; i < 10; i++)
{
str1[i] = 'a';
}
strcpy(string, str1);
}
试题3:
void test3(char *str1)
{
char string[10];
if(strlen(str1) <= 10)
{
strcpy(string, str1);
}
}
【解析】
试题1中,string是一个含有10个元素的字符数组,str1指向的字符串长度为10,在进行strcpy调用时,会将str1的结束符也复制到string数组里,也就是说会复制的字符数为11,这样就会导致string出现数组越界。此时程序不一定会因此而崩溃,但这是一个潜在的危险。解决办法:将string的元素个数定义为11个。
试题2中,str1和string都是一个含有10个元素的字符数组,并且str1的元素全部被赋为字符'a',然后再调用strcpy。这里会出现以下两个问题:一个是str1表示的字符数组没有以'\0'结束,在随后调用strcpy进无法判断什么时候复制结束;另一个是string的数组长度不够,与试题1出现类似数组越界。解决办法:将string和str1的元素个数都定义为11个,并在调用strcpy之前加入一条语句把str1[10]赋为‘\0’。
试题3中,其中的if语句用的是小于等于“<=”比较,这里如果str1的长度等于10,也会出现试题1中数组越界的情况。解决办法:把“<=”换成“<“。
【答案】
3道题都有数组越界的问题。改正后的程序如下。
#include <stdio.h>
#include <string.h>
void test1()
{
char string[11];//字符数组长度为11,多分配一个
char *str1 = "0123456789";
strcpy(string, str1);
}
void test2()
{
char string[11], str1[11];//字符数组长度都为11,均多分配一个
int i;
for(i = 0; i < 10; i++)
{
str1[i] = 'a';
}
str1[10] = '\0';//初始化str1为空字符串
strcpy(string, str1);
}
void test3(char *str1)
{
char string[10];
if(strlen(str1) < 10)//不能用<=
{
strcpy(string, str1);
}
}
int main()
{
test1();
test2();
test3("1234");
return 0;
}
面试题9 分析程序——数组越界
下面这个程序执行后会出现什么错误或者效果。
#define MAX 255
int main()
{
unsigned char A[MAX], i;
for(i = 0; i <= MAX; i++)
A[i] = i;
return 0;
}
【解析】
代码第6行的for循环中用的是”<=“,当i=MAX时发生数组越界。注意:这个程序很容易使人误认为只有数组越界的问题,但只要再细心些就能发现,i是无符号的char类型,它的范围是0~255,所以i<=MAX一直都是真,这样会导致无限循环。可以把”i<=MAX“改为”i<MAX“,这样既避免了无限循环,又避免了数组越界。
【答案】
i<=MAX导致数组越界以及无限循环,应改为i<MAX。
面试题10 分析程序——打印操作可能产生数组越界
下面这个程序的打印结果是什么?
#include <stdio.h>
int main()
{
int a[5] = {0, 1, 2, 3, 4}, *p;
p = a;
printf("%d\n", *(p + 4*sizeof(int)));
return 0;
}
【答案】
这个程序存在着越界的问题。
代码第6行,p指向a的第一个元素,所以p+4指向a的最后一个元素,即4, p+4*sizeof(int)即p+16,此时指向的是数组a的第17个元素,显然已经越界了,因此打印的是个随机数。