目录
序
嗨,这里是狐狸~~
一、找错题
试题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 有以下区别:
- 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】了解。想要对程序员的未来发展有兴趣的可以关注微信公众号:【狐狸的编码时光】,希望和大家一起学习进步!
?
|