第4章 字符串

       本章介绍了字会串的应用和字符串处理的一些函数。字符串是笔试以及面试的热门考,通过字符串测试可以考察程序员的编程规范以及编程习惯。其中也包括了许多知识点,例如内存越界、指针与数组操作等。许多公司在面试时会要求应试者写一段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个元素,显然已经越界了,因此打印的是个随机数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值