您的位置:首页 > 编程语言 > C语言/C++

#我是小白之牛客网学习# C++面试题(自学整理阶段-转自牛客网)

2018-10-24 21:10 190 查看

分析代码问题

1.分析下面代码有什么问题?

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

:string只有10字节空间,字符串str1还要包括结束符“\n”,故共有11个字符,strcpy把从src地址开始且含有’\0’结束符的字符串复制到以dest开始的地址空间,返回值的类型为char*。(原型声明:char strcpy(char dest, const char *src);),故会发生数组越界,程序崩溃…

2.分析下面代码有什么问题?

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

:首先,代码根本不能通过编译。因为数组名str1为 char *const类型的右值类型,根本不能赋值。
再者,即使想对数组的第一个元素赋值,也要使用 *str1 = ‘a’;
其次,对字符数组赋值后,使用库函数strcpy进行拷贝操作,strcpy会从源地址一直往后拷贝,直到遇到’\0’为止。所以拷贝的长度是不定的。如果一直没有遇到’\0’导致越界访问非法内存,程序就崩了。
修改方案为:

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

3.指出下面代码有什么问题?

void test3(char* str1)
{
if(str1 == NULL){
return ;
}
char string[10];
if( strlen( str1 ) <= 10 )
{
strcp
4000
y( string, str1 );
}
}

:a. sizeof()会统计后面的\0,strlen不会;
b.要将str1拷贝到string,string为10,str1长度最多为10,而strlen不计\0,所以str1长度最多为9,不能等于10。改为:strlen(str1 ) < 10

4. 写出完整版的strcpy函数

void strcpy( char *strDest, const char *strStr)  //将源字符串加const,表明其为输入参数,加2分
{
assert( (strDest != NULL) && (strStr != NULL) ); //对源地址和目的地址加非0断言,加3分
char *address = strDest;
while( ( *strDest++ = *strStr++) != '\0' );
return address;   //为了实现链式操作,将目的地址返回,加3分
}

5.下面代码有什么问题

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

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

char *str = NULL;
GetMenmory(str);

后的str仍然为NULL;
1:传入形参并不能真正改变形参的值,执行完之后为空;
2:在函数GetMemory中和Test中没有malloc对应的free,造成内存泄漏。
修改:

#include <iostream>
using namespace std;
//传值调用
void GetMemory (char **p)
{
*p = (char *) malloc( 100 );
//malloc-向系统申请分配size个字节的内存空间,返回类型为void*类型
}
void GetMemory_1 (char *&p)
{
p = (char *) malloc ( 100 );
}

int main()
{
char *str = NULL;
char *str1 = NULL;
GetMemory( &str );
GetMemory_1( str1 );
strcpy(str,"hello world");
strcpy(str1,"hello world1");
cout<<str<<endl;
cout<<str1<<endl;
free(str);   //函数结束时需要用free()将内存块释放
free(str1);
return 0;
}

6.下面代码有什么问题

链接:https://www.nowcoder.com/questionTerminal/13a4daacf9114ffea3bff132c8834db3
来源:牛客网

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

char p[] = "hello world";
return p;

的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。
char p[] = “hello world”,相当于char p[12],strcpy(p, “hello world”),p是一个数组名,属于局部变量,存储在栈中,“hello world”存储在文字存储区,数组p存储的是“hello world”的一个副本,当函数结束时,p被回收,副本也就消失了(确切的说p指向的栈存储区被取消标记,可能随时被系统修改),而函数返回的p指向的内容也变得不确定,文字存储区的 " hello world" 未改变。
可以修改为:1). char *p = “hello world”; return p; 这里p直接指向文字存储区的“hello world”,函数按值返回p存储的地址,所以有效。
2).static char p[] = “hello world”; return p; static 指出数组p为静态数组,函数结束也不会释放,所以有效。

7.下面代码有什么问题

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

答(转):1). 传入GetMemory的参数为字符串指针的指针,但是在GetMemory中执行申请内存及赋值语句 *p = (char *) malloc( num ); 后未判断内存是否申请成功,应加上:

if ( *p == NULL)
{
perror("malloc fail"); // 进行申请内存失败处理
return -1;
}
// 同时应该考虑num>0

2).未释放堆内存,动态分配的内存在程序结束前没有释放,应该调用free,把malloc生成的内存释放掉,释放后记得str=NULL,否则可能会导致野指针出现;
3).printf(str) 改为printf("%s",str),以字符串形式打印输出变量str后光标换行,否则可使用格式化,字符串攻击
可修改为:

void GetMemory(char **p, int num){
if(num <= 0)
printf("申请的内存空间要大于零!\n");
*p = (char*) malloc(num);
if(*p == NULL){
printf("申请内存失败\n");
return -1;
}
}
void test(){
char *str = NULL;
GetMemory(str,100);
strcpy(str,"Hello world");
printf("%s\n",str);
free(str);
str = NULL;
}

8.下面代码有什么问题

swap( int* p1,int* p2 )
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}

答(转):1.需要一个返回值void
2. 在swap函数中,p是一个“野”指针,有可能指向系统区,导致程序运行的崩溃。在VC++中DEBUG运行时提示错误“Access Violation”。
该程序应该改为

void swap( int* p1, int* p2)
{
int p;
p = *p1;
*p1 = *p2;
*p2 = p;
}

问答题

1.分别给出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分。

2.以下为Windows NT下的32位C++程序,请计算sizeof的值

void Func ( char str[100] )
{
sizeof( str ) = ?
}
void *p = malloc( 100 );
sizeof ( p ) = ?

参考答案
sizeof( str ) = 4
sizeof ( p ) = 4
剖析
Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。
数组名的本质如下:
(1)数组名指代一种数据结构,这种数据结构就是数组;
例如:

char str[10];
count << sizeof(str) << endl;

输出结果为10,str指代数据结构char[10]。
(2)数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;

char str[10];
str++;   // 编译出错,提示str不是左值

(3)数组名作为函数形参时,沦为普通指针
Windows NT 32位平台下,指针的长度(占用内存的大小)为4字节,故sizeof( str ) 、sizeof ( p ) 都为4。

3.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?
least = MIN(*p++, b);
首先学习下C++中的宏定义(转):https://www.geek-share.com/detail/2678774487.html
参考答案

#define MIN(A,B) ((A) <= (B) ? (A) : (B))

MIN(*p++, b)会产生宏的副作用
剖析
这个面试题主要考查面试者对宏定义的使用,宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对“参数”进行的是一对一的替换。
程序员对宏定义的使用要非常小心,特别要注意两个问题:
(1)谨慎地将宏定义中的“参数”和整个宏用用括弧括起来。所以,严格地讲,下述解答:

#define MIN(A,B) (A) <= (B) ? (A) : (B)
#define MIN(A,B) (A <= B ? A : B )

都应该判为0分;
(2)防止宏的副作用
宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))对MIN(*p++, b)的作用结果是:

((*p++) <= (b) ? (*p++) : (b))

这个表达式会产生副作用,指针p会作2次++自增操作。(不要用改变变量的调用方法,来调用宏定义,会有副作用)
除此之外,另一个应该判0分的解答是:

#define MIN(A,B) ((A) <= (B) ? (A) : (B));

这个解答在宏定义的后面加**“;”**,显示编写者对宏的概念模糊不清

4.编写一个函数,作用是把一个char组成的字符串循环右移n个。
比如原来是“abcdefghi”如果n=2,移位后应该是“hiabcdefg” 函数头是这样的:
//pStr是指向以’\0’结尾的字符串的指针
//steps是要求移动的n

void LoopMove(char *pStr, int steps)
{
//请填充.....
}

答(转)

#include <stdio.h>
#include <string.h>
void LoopMove(char *pStr, int steps)
{
int len = strlen(pStr);
int st = steps % len; //取余
//字符串长度为0,或不需移动,或移动步数小于等于0,返回,也可报错。
if (len == 0 || st == 0|| step <=0) return;
char temp[100] = {0};
memcpy(temp,pStr+len-tr,tr);  //以上为例,hi
memcpy(temp+tr,pStr,len-sr);
mencpy(pStr,temp,len);
}
int main()
{
char s[138]="abcdefghi";
LoopMove(s,2);
printf("%s\n",s);
return 0;
}
输出结果为:hiabcdefg

作者:YLD10
链接:https://www.nowcoder.com/questionTerminal/64492230b659496dbae4957dd16d20b9
来源:牛客网

memcpy(目标地址, 源地址, 拷贝的长度)。
例如 pStr=“123456”,steps=2,那么 len=6,st=2。
第一个memcpy 中,pStr+len-st=“56”,st=2 所以就是把 “56” 这两个字符拷贝给 temp,temp="56"两个字符,即 temp[0]=‘5’,temp[1]=‘6’。
第二个 memcpy 中,temp+st=temp[2] 所在的地址,pStr=“123456”,len-st=4,也就是说把 pStr 的前 4 个字符拷贝到从 temp[2] 开始的地址里,即 temp[2]=‘1’,temp[3]=‘2’,temp[4]=‘3’,temp[5]=‘4’,即 temp="561234"六个字符。
第三个 memcpy 就是把 temp 里面的 6 个字符拷贝到从 pStr 起的连续 6 个 char 空间里头,因为第 7个空间里至始至终都没有人动过,所以第 7 个空间里头还有 ‘\0’,所以 pStr=“561234” 字符串。这样就能实现循环右移了。

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

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

参考答案

String::String(const char *str)
{
if(str == NULL)   // 对m_date加NULL判断
{
m_date = new char[1];
*m_date = '\0';  // 对空字符串自动申请存放结束标志'\0'的空
}
else
{
int length = strlen(str);
m_date = new char[length+1];
strcpy(m_date, str);
}
// String的析构函数
String::~String( void )
{
delete [] m_date;  //或delete m_date
}
//拷贝构造函数
Sring::String(const String &other)     // 输入参数为const型
{
int length = strlen(other.m_date);
m_date = new char[length+1];
strcpy(m_date, other.m_date);
}
// 赋值函数
String& String::operater = (const String &other)  //输入参数为const
{
if(this == &other)  // 检查自赋值
return *this;
delete [] m_date;   // 释放原有的内存资源
int length = stlen( other.m_date);
my_date = new char[length+1];
strcpy(m_date, other.m_date);
return *this;  // 返回本对象的引用
}
}

6.请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1
解答

int checkCPU()
{
{
union w
{
int a;
char b;
} c;
c.a = 1;
return (c.b == 1);
}
}

剖析:采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节;Big-endian模式对操作数的存放方式是从高字节到低字节
联合体union的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。如果谁能当场给出这个解答,那简直就是一个天才的程序员。
例如,16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

而在Big-endian模式CPU内存中的存放方式则为:

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

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

int Sum2(int n)
{
long sum = 0;
for(i=1;i<=n;i++){
sum += i;
}
return sum;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: