【C/C++】程序员经典面试题,过来人的总结...
目录
序
嗨,这里是狐狸~~
一、找错题
试题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
#include int getmax(int *p, int n)
{//假定第一个数最大,与剩下的数逐一进行比较int maxdata = p[0]; //最大数的值int maxi = 0; //最大数的下标for (int i = 1; i < n; i++){if (maxdata
试题3:编写strcpy函数
已知strcpy函数的原型是char *strcpy(char *strDest,char *strSrc);其中strDest是目的字符串,strSrc是源字符串。
(1)不调用C/C++的字符串库函数,请编写出函数strcpy
#include
#include
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
#include
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; //abcdefghicout << 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
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;elsereturn 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> key;int ret=binary_search(A, key, len);if (-1 == ret)cout << "没有找到,数组中无关健值" << key << endl;elsecout << "已找到关键值" << 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 有以下区别:
- sizeof 是一个操作符,strlen 是库函数。
- sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为‘\0‘的字符串作参数。
- 编译器在编译时就计算出了 sizeof 的结果。而 strlen 函数必须在运行时才能计算出来。并且 sizeof
计算的是数据类型占内存的大小,而 strlen 计算的是字符串实际的长度。
数组做sizeof 的参数不退化,传递给strlen 就退化为指针了。
注意:有些是操作符看起来像是函数,而有些函数名看起来又像操作符,这类容易混淆的名称一定 要加以区分,否则遇到数组名这类特殊数据类型作参数时就很容易出错。最容易混淆为函数的操作符就 是 sizeof。
试题 4:C中的 malloc 和C++中的 new 有什么区别
malloc 和 new 有以下不同:
- new、delete 是操作符,可以重载,只能在 C++中使用。
- malloc、free 是函数,可以覆盖,C、C++中都可以使用。
- new 可以调用对象的构造函数,对应的 delete 调用相应的析构函数。
- malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
- new、delete 返回的是某种数据类型指针,malloc、free 返回的是void 指针。
注意:malloc 申请的内存空间要用 free 释放,而 new 申请的内存空间要用 delete 释放,不要混用。因为两者实现的机理不同。
试题 5:谈谈你对拷贝构造函数和赋值运算符的认识
拷贝构造函数和赋值运算符重载有以下两个不同之处:
- 拷贝构造函数生成新的类对象,而赋值运算符不能。
- 由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象 是否和新建对象相同。而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配要 先把内存释放掉
注意:当有类中有指针类型的成员变量时,一定要重写拷贝构造函数和赋值运算符,不要使用默认 的。
总结
博观而约取,厚积而薄发,希望这些题目可以帮助到大家,也希望大家在编程这条路上都可以越走越远,脚踏实地,走好每一步,不要盲目的前进,学习要有方向,有了方向前进的路才会有奔头,好吧,感谢大家的一路支持吧,后续我还会发布更多的项目源或者学习资料,希望大家可以持续关注,有什么问题可以回帖留言。领取C/C++学习资料以及其他项目的源码的可以加群【1083227756】了解。想要对程序员的未来发展有兴趣的可以关注微信公众号:【狐狸的编码时光】,希望和大家一起学习进步!
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!