【C/C++】程序员经典面试题,过来人的总结...

218 篇文章 6 订阅
118 篇文章 62 订阅

目录

嗨,这里是狐狸~~

一、找错题

试题1:

试题2:

试题3:

试题4:

试题5:

试题6:

试题7:

二、技巧题

试题1:写一个函数返回1+2+3+…+n的值(假定结果不会超过长整型变量的范围)

试题2:写一个函数找出一个整数数组中第二大的数

试题3:编写strcpy函数

试题4:华为面试题:怎么判断链表中是否有环?

试题5:写一个内存拷贝函数memcpy(),不用任何库函数

试题6:写出二分查找的代码

试题7:编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:

 三、知识题

试题1:请说出static和const关键字尽可能多的作用

试题2:分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)

试题 3:sizeof 和 strlen 的区别

试题 4:C中的 malloc 和C++中的 new 有什么区别

试题 5:谈谈你对拷贝构造函数和赋值运算符的认识

总结​


青春,一半明媚,一半忧伤。

嗨,这里是狐狸~~

        今天是2022年的1月14日,今天是个好日子呀,阳光明媚,微风不燥,太阳出来的那一刻心都融化了似的,希望每天都可以像今天一样美好吧。

       今天给大家讲一下C/C++程序员会遇到的面试题,都是一些常见的题目,大家可以把答案藏住自己先做一遍,效果应该会很好的。

一、找错题

试题1:

void test1()
{
 charstring[10];
 char* str1 ="0123456789";
 strcpy( string, str1 );
}

试题2:

void test2()
{
 charstring[10],str1[10];
 int i;
 for(i=0; i<10; i++)
 {
  str1 ='a';
 }
 strcpy( string, str1 );
}

试题3:

void test3(char* str1)
{
 charstring[10];
 if( strlen( str1 ) <=10 )
 {
  strcpy( string, str1 );
 }
}

解答:
  试题1字符串str1需要11个字节才能存放下(包括末尾的’\0’),而string只有10个字节的空间,strcpy会导致数组越界;

  对试题2,如果面试者指出字符数组str1不能在数组内结束可以给3分;如果面试者指出strcpy(string,str1)调用使得从str1内存起复制到string内存起所复制的字节数具有不确定性可以给7分,在此基础上指出库函数strcpy工作方式的给10分;

  对试题3,if(strlen(str1)<= 10)应改为if(strlen(str1) < 10),因为strlen的结果未统计’\0’所占用的1个字节。

剖析:
  考查对基本功的掌握:
  (1)字符串以’\0’结尾;
  (2)对数组越界把握的敏感度;
  (3)库函数strcpy的工作方式,如果编写一个标准strcpy函数的总分值为10,下面给出几个不同得分的答案:

试题4:

void GetMemory( char*p )
{
 p = (char*) malloc( 100 );
}
void Test( void ) 
{
 char*str = NULL;
 GetMemory( str ); 
 strcpy( str, "hello world" );
 printf( str );
}

试题5:

char*GetMemory( void )
{ 
 char p[] ="hello world"; 
 return p; 
}
void Test( void )
{ 
 char*str = NULL; 
 str = GetMemory(); 
 printf( str ); 
}

试题6:

void GetMemory( char**p, int num )
{
 *p = (char*) malloc( num );
}
void Test( void )
{
 char*str = NULL;
 GetMemory( &str, 100 );
 strcpy( str, "hello" ); 
 printf( str ); 
}

试题7:

void Test( void )
{
 char*str = (char*) malloc( 100 );
 strcpy( str, "hello" );
 free( str ); 
 ... //省略的其它语句
}

解答:

试题4    传入中GetMemory(char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的                改变传入形参的值,执行完

                char *str = NULL;
                GetMemory( str ); 

               后的str仍然为NULL;

试题5中

            char p[] = "hello world"; 
           return p;  的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多              程序员常犯的错误,其根源在于不理解变量的生存期。

试题6   的GetMemory避免了试题4的问题,传入GetMemory的参数为字符串指针的指针,但是在                GetMemory中执行申请内存及赋值语句

              *p = (char *) malloc( num );

            后未判断内存是否申请成功,应加上:

           if ( *p == NULL )
          {
                ...//进行申请内存失败处理
             }

试题7    存在与试题6同样的问题,在执行

              char *str = (char *) malloc(100);后未进行内存是否申请成功的判断;另外,在free(str)后                 未置str为空,导致可能变成一个“野”指针,应加上:

               str = NULL;

剖析:
  试题4~7考查面试者对内存操作的理解程度,基本功扎实的面试者一般都能正确的回答其中50~60的错误。但是要完全解答正确,却也绝非易事。

对内存操作的考查主要集中在:
      1)指针的理解;

      2)变量的生存期及作用范围;

      3)良好的动态内存申请和释放习惯。

二、技巧题

试题1:写一个函数返回1+2+3+…+n的值(假定结果不会超过长整型变量的范围)

解答:

int Sum( int n )
{ 
 return ( (long)1+ n) * n /2;  //或return (1l + n)* n / 2;
}

剖析:
  对于这个题,只能说,也许最简单的答案就是最好的答案。下面的解答,或者基于下面的解答思路去优化,不管怎么“折腾”,其效率也不可能与直接return( 1 l + n ) * n / 2相比!

int Sum( int n )
{
 long sum =0;
 for( int i=1; i<=n; i++ )
 {
  sum += i;
 }
 return sum;
}

试题2:写一个函数找出一个整数数组中第二大的数

  如:a[10]={1,2,3,4,5,6,7,8,9,10}  ==> nextmax = 9;

    a[10]={1,2,3,4,5,6,7,10,10,10}  ==> nextmax = 7;

    a[10]={8,8,8,2,8,8,8,8,8,8}  ==> nextmax = 2;

    a[10]={8,8,8,8,8,8,8,8,8,8}  ==> nextmax = 不存在;

// C语言实现如下
#include <stdio.h>
#include <stdlib.h>

int getmax(int *p, int n)
{
    //假定第一个数最大,与剩下的数逐一进行比较
    int maxdata = p[0];    //最大数的值
    int maxi = 0;        //最大数的下标

    for (int i = 1; i < n; i++)
    {
        if (maxdata<p[i])
        {
            maxdata = p[i];
            maxi = i;
        }
    }

    return maxi;    //返回下标
}

void swap(int *p1, int *p2)    //根据地址交换两个变量
{
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}


void main()
{
    int a[10] = { 8,8,8,8,5,8,8,8,8,8 };
    //printf("%d\n", getmax(a, 10));
    int maxi = getmax(a, 10);    //保留最大值的下标
    int max = a[getmax(a, 10)];    //保留最大值

    swap(&a[0], &a[maxi]);

    int i = 1;
    int flag = 0;    //0代表没有第二大的数,比如全部相同时
    while (i<10)
    {
        int nextmaxi = i + getmax(a + i, 10-i);    //注意下标+1
        int nextmax = a[nextmaxi];

        if (max != nextmax)
        {
            printf("\nnextmax = %d", nextmax);
            flag = 1;    //代表数组并非全部相同
            break;
        }

        swap(&a[i],&a[nextmaxi]);
        i++;
    }

    if (!flag)
    {
        printf("next max不存在");
    }

    system("pause");
    return ;
}

试题3:编写strcpy函数

  已知strcpy函数的原型是char *strcpy(char *strDest,char *strSrc);其中strDest是目的字符串,strSrc是源字符串。

  (1)不调用C/C++的字符串库函数,请编写出函数strcpy

#include <iostream>
#include<assert.h>
using namespace std;

char *strcpy(char *strDest,char *strSrc)
{
    assert((strDest != NULL) && (strSrc != NULL));    //异常处理
    //if ((strDest == NULL) && (strSrc == NULL))
    //{
    //    cout << "异常..." << endl;
    //}

    char *address = strDest;
    while ((*strDest++ = *strSrc++) != '\0')    //牛掰的语句,并没有把语句放在循环体中
        NULL;
    return address;
}

void main()
{
    char str1[] = "hello world";
    char str2[20];

    cout << str1 << endl;
    strcpy(str2, str1);
    cout << str2 << endl;

    system("pause");
    return ;
}

(2)strcpy()函数能把strSrc的内容复制到strDest,为什么还要用char * 类型的返回值?

    答案:为了实现链式表达式,如:

int length = strlen(strcpy(str2, str1));

试题4:华为面试题:怎么判断链表中是否有环?

  答:用两个指针来遍历这个单向链表,第一个指针p1,每次走一步;第二个指针p2,每次走两步;  当p2 指针追上 p1的时候,就表明链表当中有环路了。


int testLinkRing(Link *head)
{
    Link *t1 = head, *t2 = head;
    while (t1->next && t2->next)
    {
        t1 = t1->next;
        if (NULL == (t2 = t2->next->next))
            return 0;   //无环
        if (t1 == t2)
            return 1;   //有环
    }
 
    return 0;
}

试题5:写一个内存拷贝函数memcpy(),不用任何库函数

#include <iostream>
#include <assert.h>
using namespace std;

//内存拷贝函数:
//功能:由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始地址的空间内。
//返回值:函数返回一个指向dest的指针
//与strcpy相比:memcpy并不是遇到'\0'就结束,而是一定会拷贝完n个字节
//memcpy用来做内存拷贝,可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度
void *memcpy(void *dest, const void *src, size_t size)
{
    assert((dest != NULL) && (src != NULL));

    char *pdest = (char *)dest;
    char *psrc = (char *)src;

    while ( (size--) > 0 )
    {
        *pdest++ = *psrc++;
    }

    return pdest;
}

void main()
{
    char str1[10] = "abcdefghi";
    char str2[20];

    memcpy(str2, str1, sizeof(str1));
    cout << str1 << endl;                //abcdefghi
    cout << str2 << endl;                //abcdefghi

    //若改为如下:
    char str3[20] = "abcdefghi";
    char str4[10];

    memcpy(str4, str3, sizeof(str3));    //此时会造成str4内存地址溢出

    cout << str3 << endl;                
    cout << str4 << endl;                

    system("pause");
    return ;
}

【解析】

面试中如问到memcpy的实现,要小心了,这里有陷阱。标准的memcpy()函数中,对于地址重叠的情况,该函数的行为是未定义的。事实上陷阱也在于此,自己动手实现memcpy()时就需要考虑地址重叠的情况。

  库函数中的memcpy不能处理src和dest有重叠的情况。如果是自己实现memcpy的话,最好还是把地址有重叠的情况考虑进去,这样才能更好的体现编码的严谨。

//把地址有重叠的情况考虑进去
void *memcpy(void *dest, const void *src, size_t size)
{
    assert((dest != NULL) && (src != NULL) && (size > 0) );    //添加了size > 0的条件

    char *pdest, *psrc;

    if ((dest > src) && ((char *)dest < (char *)src + size))            //有内存重叠,则从后向前拷贝
    {
        pdest = (char *)dest + size - 1;    //指针指向拷贝的最后一个位置
        psrc = (char *)src + size - 1;        

        while (size--)
        {
            *pdest-- = *psrc--;
        }
    }

    else                                                              //没有内存重叠,从前往后拷贝即可
    {
        pdest = (char *)dest;              //指针指向拷贝的起始位置
        psrc = (char *)src;

        while (size--)
        {
            *pdest++ = *psrc++;
        }
    }

    return pdest;
}

 【扩展】

memmove和memcpy函数都是C语言中的库函数,作用是拷贝一定长度的内存的内容,它们的作用是一样的,唯一的区别就是当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果是正确的。它与memcpy的功能相似,都是将src所指的n个字节复制到dest所指的内存地址起始位置中,但该函数可以处理src和dest有重叠的情况。实际上,memcpy可以看作是memmove的子集。上面优化后的memcpy()函数实际上就是memmove()函数的实现。

试题6:写出二分查找的代码

#include <iostream>
using namespace std;

int binary_search(int *arr, int key, int n)
{
    int low = 0;
    int high = n - 1;
    int mid;

    while (low <= high)
    {
        mid = (low + high) / 2;
        if (key < arr[mid])
            high = mid - 1;
        else if (key > arr[mid])
            low = mid + 1;
        else
            return mid;
    }
    return -1;
}

void main()
{
    int A[] = { 1,3,5,7,9,11,13,15,17 };
    int len = sizeof(A) / sizeof(A[0]);
    int key;

    cout << "数组如下:" << endl;
    for(int i=0;i<len;i++)
        cout << A[i] << " ";
    cout << endl;

    cout << "请输入待查找的关键字key:" << endl;
    cin >> key;

    int ret=binary_search(A, key, len);

    if (-1 == ret)
        cout << "没有找到,数组中无关健值" << key << endl;
    else
        cout << "已找到关键值" << key << ",它是A[" << ret << "]" << endl;
    
    system("pause");
    return ;
}

试题7:编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:

class String
{ 
 public: 
  String(constchar*str = NULL); // 普通构造函数 
  String(const String &other); // 拷贝构造函数 
  ~ String(void); // 析构函数 
  String & operator =(const String &other); // 赋值函数 
 private: 
  char*m_data; // 用于保存字符串 
};

解答:

//普通构造函数
String::String(constchar*str) 
{
 if(str==NULL) 
 {
  m_data =newchar[1]; // 得分点:对空字符串自动申请存放结束标志'\0'的空
  //加分点:对m_data加NULL 判断
  *m_data ='\0'; 
 } 
 else
 {
  int length = strlen(str); 
  m_data =newchar[length+1]; // 若能加 NULL 判断则更好 
  strcpy(m_data, str); 
 }
}
// String的析构函数
String::~String(void) 
{
 delete [] m_data; // 或deletem_data;
}
//拷贝构造函数
String::String(const String &other)    // 得分点:输入参数为const型
{ 
 int length = strlen(other.m_data); 
 m_data =newchar[length+1];     //加分点:对m_data加NULL 判断
 strcpy(m_data, other.m_data); 
}
//赋值函数
String & String::operator =(const String &other) // 得分点:输入参数为const型
{ 
 if(this==&other)   //得分点:检查自赋值
  return*this; 
 delete [] m_data;     //得分点:释放原有的内存资源
 int length = strlen( other.m_data ); 
 m_data =newchar[length+1];  //加分点:对m_data加NULL 判断
 strcpy( m_data, other.m_data ); 
 return*this;         //得分点:返回本对象的引用
}

剖析:
  能够准确无误地编写出String类的构造函数、拷贝构造函数、赋值函数和析构函数的面试者至少已经具备了C++基本功的60%以上!
  在这个类中包括了指针类成员变量m_data,当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数,这既是对C++程序员的基本要求,也是《Effective C++》中特别强调的条款。
  仔细学习这个类,特别注意加注释的得分点和加分点的意义,这样就具备了60%以上的C++基本功! 

 三、知识题

试题1:请说出static和const关键字尽可能多的作用

解答:
    static关键字至少有下列n个作用:
  (1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一                     次,因此其值在下次调用时仍维持上次的值;
  (2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
  (3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在                     声明它的模块内;
  (4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
  (5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类                    的static成员变量。

  const关键字至少有下列n个作用:
  (1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它                       进行初始化,因为以后就没有机会再去改变它了;
  (2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者                       同时指定为const;
  (3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改                       变 其值;
  (4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成                      员变量;
  (5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左                          值”。例如:

                    const classA operator*(const classA& a1,const classA& a2);

              operator*的返回结果必须是一个const对象。如果不是,这样的变态代码也不会编译出错:

                 classA a, b, c;
                  (a * b) = c; // 对a*b的结果赋值
  操作(a * b) = c显然不符合编程者的初衷,也没有任何意义。

剖析:
  惊讶吗?小小的static和const居然有这么多功能,我们能回答几个?如果只能回答1~2个,那还真得闭关再好好修炼修炼。

  这个题可以考查面试者对程序设计知识的掌握程度是初级、中级还是比较深入,没有一定的知识广度和深度,不可能对这个问题给出全面的解答。大多数人只能回答出static和const关键字的部分功能。

试题2:分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)

解答:

   BOOL型变量:if(!var)

   int型变量:if(var==0)

   float型变量:

   const float EPSINON = 0.00001;

   if ((x >= - EPSINON) && (x <=EPSINON)

   指针变量:  if(var==NULL)

剖析:

  考查对0值判断的知识,BOOL型变量的0判断完全可以写成if(var==0),而int型变量也可以写成if(!var),指针变量的判断也可以写成if(!var),上述写法虽然程序都能正确运行,但是未能清晰地表达程序的意思。 
 一般的,如果想让if判断一个变量的“真”、“假”,应直接使用if(var)、if(!var),表明其为“逻辑”判断;如果用if判断一个数值型变量(short、int、long等),应该用if(var==0),表明是与0进行“数值”上的比较;而判断指针则适宜用if(var==NULL),这是一种很好的编程习惯。

  浮点型变量并不精确,所以不可将float变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。如果写成if(x == 0.0),则判为错,得0分。

试题 3:sizeof 和 strlen 的区别

   sizeof 和 strlen 有以下区别:

  1. sizeof 是一个操作符,strlen 是库函数。
  2. sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为‘\0‘的字符串作参数。
  3. 编译器在编译时就计算出了 sizeof 的结果。而 strlen 函数必须在运行时才能计算出来。并且 sizeof

计算的是数据类型占内存的大小,而 strlen 计算的是字符串实际的长度。

       数组做sizeof 的参数不退化,传递给strlen 就退化为指针了。

注意:有些是操作符看起来像是函数,而有些函数名看起来又像操作符,这类容易混淆的名称一定   要加以区分,否则遇到数组名这类特殊数据类型作参数时就很容易出错。最容易混淆为函数的操作符就   是 sizeof。

试题 4:C中的 malloc 和C++中的 new 有什么区别

malloc 和 new 有以下不同:

  1.    new、delete 是操作符,可以重载,只能在 C++中使用。
  2. malloc、free 是函数,可以覆盖,C、C++中都可以使用。
  3. new 可以调用对象的构造函数,对应的 delete 调用相应的析构函数。
  4. malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
  5. new、delete 返回的是某种数据类型指针,malloc、free 返回的是void 指针。

注意:malloc 申请的内存空间要用 free 释放,而 new 申请的内存空间要用 delete 释放,不要混用。因为两者实现的机理不同。

试题 5:谈谈你对拷贝构造函数和赋值运算符的认识

拷贝构造函数和赋值运算符重载有以下两个不同之处:

  1. 拷贝构造函数生成新的类对象,而赋值运算符不能。
  2. 由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象  是否和新建对象相同。而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配要   先把内存释放掉

注意:当有类中有指针类型的成员变量时,一定要重写拷贝构造函数和赋值运算符,不要使用默认 的。

总结

         博观而约取,厚积而薄发,希望这些题目可以帮助到大家,也希望大家在编程这条路上都可以越走越远,脚踏实地,走好每一步,不要盲目的前进,学习要有方向,有了方向前进的路才会有奔头,好吧,感谢大家的一路支持吧,后续我还会发布更多的项目源或者学习资料,希望大家可以持续关注,有什么问题可以回帖留言。领取C/C++学习资料以及其他项目的源码的可以加群【1083227756】了解。想要对程序员的未来发展有兴趣的可以关注微信公众号:【狐狸的编码时光】,希望和大家一起学习进步!

 

  • 11
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值