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

C语言再学习 -- 详解C++/C 面试题 1

2017-01-20 10:44 597 查看
参看:《高质量C++ C编程指南》.林锐

对这篇文章记忆犹新,因为之前找工作面试的时候,遇到过一家公司就是用的这套面试题。现在就结合考查的知识点和我总结完 C 语言再学习后的深入理解,来详细的讲讲我对这篇文章的总结。

一、请填写BOOL , float,指针变量 与“零值”比较的if语句。(10分)

提示:这里“零值”可以是0, 0.0 , FALSE或者“空指针”。例如int变量n与“零值”
比较的 if
语句为:
if ( n == 0 )

if ( n != 0 )

以此类推。

1、请写出BOOL flag与“零值”比较的if语句:

标准答案:

if ( flag )

if ( !flag )


如下写法均属不良风格,不得分。

if (flag == TRUE)

if (flag == 1 )

if (flag == FALSE)

if (flag == 0)




2、请写出 float x与“零值”比较的if语句:

标准答案示例:

const float EPSINON = 0.00001;

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

不可将浮点变量用“ ==”或“! =”与数字比较,应该设法转化成“ >=”或“ <=”此类形式。


如下是错误的写法,不得分。

if (x == 0.0)

if (x != 0.0)


3、请写出 char *p与“零值”比较的if语句:

标准答案:

if (p == NULL)

if (p != NULL)

如下写法均属不良风格,不得分。

if (p == 0)

if (p != 0)

if (p)

if (!)


解答:

1、根据布尔类型的语义,零值为“假”(记为 FALSE),任何非零值都是“真”(记为TRUE)。

/usr/include/curses.h文件

/* X/Open and SVr4 specify that curses implements 'bool'.  However, C++ may also
* implement it.  If so, we must use the C++ compiler's type to avoid conflict
* with other interfaces.
*
* A further complication is that <stdbool.h> may declare 'bool' to be a
* different type, such as an enum which is not necessarily compatible with
* C++.  If we have <stdbool.h>, make 'bool' a macro, so users may #undef it.
* Otherwise, let it remain a typedef to avoid conflicts with other #define's.
* In either case, make a typedef for NCURSES_BOOL which can be used if needed
* from either C or C++.
*/

#undef TRUE
#define TRUE    1

#undef FALSE
#define FALSE   0


2、在浮点数比较中不能使用 < 和 >,千万要留意,无论是 float 还是 double 类型的变量,都有精度限制。所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。 

请写出 float x 与“零值”比较的 if 语句

const float EPSINON = 0.000001;

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

或者 if ( fabs (x) <= EPSINON)  
// fabs (x) 取 x 的绝对值,其中EPSINON是允许的误差(即精度)。

3、参看:C语言再学习 -- NUL和NULL的区别
NULL用于表示什么也不指向,也就是空指针((void *)0)
#include <stdio.h>
#include <curses.h>

int main (void)
{
char *p = NULL;
if (p != (void*)0)
{
printf ("11111111\n");
}
printf ("22222222\n");
return 0;
}
输出结果:
22222222


程序员为了防止将 if (p == NULL) 误写成 if (p = NULL),而有意把 p 和 NULL 颠倒。编译器认为 if (p = NULL) 是合法的,但是会指出 if (NULL = p)是错误的,因为 NULL不能被赋值

扩展:在表达式中使用无符号数
库函数 strlen 的原型如下:
size_t strlen (char const *string);
注意:strlen 返回一个类型为 size_t 的值。这个类型是在头文件 stddef.h 中定义的,它是一个无符号整数类型。在表达式中使用无符号数可能导致不可预料的结果。例如下面的表达式:
#include <stdio.h>
#include <string.h>
int main (void)
{
char ptr1[] = "beijing";
char ptr2[] = "hello world";
if (strlen (ptr1) - strlen (ptr2) >= 0)
{
printf ("1111111111\n");
}
printf ("2222222222\n");
return 0;
}
输出结果:
1111111111
2222222222

但 strlen (ptr1) - strlen (ptr2) 为无符号类型,得不到想要的结果,应该为 if (strlen (ptr1) >= strlen (ptr2)) 
#include <stdio.h>
#include <string.h>
int main (void)
{
char ptr1[] = "beijing";
char ptr2[] = "hello world";
if (strlen (ptr1) >= strlen (ptr2))
{
printf ("1111111111\n");
}
printf ("2222222222\n");
return 0;
}
输出结果:
2222222222


二、以下为Windows NT下的32C++程序,请计算sizeof的值(10分)

void Func ( char str[100])

{


请计算
sizeof( str ) = 4

}



char str[] = “Hello” ;

char *p = str ;

int n = 10;


请计算
sizeof (str ) = 6

sizeof ( p ) = 4

sizeof ( n ) = 4



void *p = malloc( 100 );


请计算
sizeof ( p ) = 4

解答:

参看:C语言再学习 -- 关键字sizeof与strlen
记住这两句话:
在 32 位系统下,不管什么样的指针类型,其大小都为 4 byte。
参数传递数组永远都是传递指向数组首元素的指针。

三、简答题(25分)

1、头文件中的ifndef/define/endif干什么用?

答:防止该头文件被重复引用。

2#include <filename.h>#include
“filename.h”
有什么区别?

答:对于#include <filename.h> ,编译器从标准库路径开始搜索 filename.h

对于#include “filename.h” ,编译器从用户的工作路径开始搜索 filename.h


3const有什么用途?(请至少说明两种)

( 1)可以定义 const 常量

( 2) const 可以修饰函数的参数、返回值,甚至函数的定义体。被 const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。


4、在C++程序中调用被C编译器编译后的函数,为什么要加extern
“C”
声明?

答: C++语言支持函数重载, C 语言不支持函数重载。函数被 C++编译后在库中的名字与 C 语言的不同。假设某个函数的原型为: void foo(int x, int y);该 函 数 被 C 编 译 器 编 译 后 在 库 中 的 名 字 为 _foo,
而 C++编 译 器 则 会 产 生 像_foo_int_int 之类的名字。C++提供了 C 连接交换指定符号 extern“ C”来解决名字匹配问题。


5、请简述以下两个for循环的优缺点

// 第一个
for (i=0; i<N; i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
}

优点:程序简洁
缺点:多执行了 N-1 次逻辑判断,并且打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。

// 第二个
if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}

优点:循环的效率高

缺点:程序不简洁

解答:

1、参看:C语言再学习 -- C 预处理器

#ifndef 标识符
程序段1
#else
程序段2
#endif
它的作用是,若标识符未被定义则编译程序段1,否则编译程序段2

一般地,当某文件包含几个头文件,而且每个头文件都可能定义了相同的宏,使用#ifndef可以防止该宏重复定义

//头文件卫士
#ifndef __THINGS_H__
#define __THINGS_H__
#endif


2、 参看:C语言再学习 -- C 预处理器

#include <filename.h>    文件名放在尖括号中

在UNIX系统中,尖括号告诉预处理器在一个或多个标准系统目录中寻找文件。 

如: #include <stdio.h>

查看:

ls /usr/include

ls kernel/include 

#include "filename.h"    文件名放在双引号中

在UNIX系统中,双引号告诉预处理器现在当前目录(或文件名中指定的其他目录)中寻找文件,然后在标准位置寻找文件。

如: #include "hot.h"     #include "/usr/buffer/p.h"

3、参看:C语言再学习 -- 关键字const

const 修饰类型

1、const 修饰一般常量

2、const修饰指针、数组

3、const 修饰函数的形参和返回值

4、const 修饰常对象

5、const 修饰常引用

6、const 修饰类的成员变量

7、const 修饰类的成员函数



const 作用

1)可以定义 const 常量,具有不可变性。

2)便于进行类型检查,使编译器对处理内容有更多了解,消除一些隐患。

3)可以避免意义模糊的数字出现,同样可以很方便进行参数的调整和修改。同宏定义一样,可以做到不变则已,一变都变。

4)可以保护被修改的东西,防止意外的修改,增强程序的健壮性。

5)可以节省空间,避免不必要的内存分配。

6)为函数重载提供了一个参考

7)提高效率

4、参看:C语言再学习 -- 存储类型关键字

C 程序中,不允许出现类型不同的同名变量。而C++程序中 却允许出现重载。重载的定义:同一个作用域,函数名相同,参数表不同的函数构成重载关系。因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,来解决名字匹配问题。简单来说就是,extern “C”这个声明的真实目的是为了实现C++与C及其它语言的混合编程

5、参看:C语言再学习 -- 循环语句

第一个程序比第二个程序多执行了 N-1 次逻辑判断。并且由于前者老要进行逻辑判断,打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。如果 N 非常大,最好采用第二个程序的写法,可以提高效率。如果 N 非常小,两者效率差别并不明显,采用第一个程序的写法比较好,因为程序更加简洁。


四、有关内存的思考题(20分)

void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
请问运行 Test 函数会有什么样的结果?
答:程序崩溃。

因为 GetMemory 并不能传递动态内存,Test 函数中的 str 一直都是 NULL。、

char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行 Test 函数会有什么样的结果?
答:可能是乱码。

因为 GetMemory 返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。

Void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行 Test 函数会有什么样的结果?
答:能够输出 hello

内存泄漏

void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
请问运行 Test 函数会有什么样的结果?
答:篡改动态内存区的内容,后果难以预料,非常危险。

因为 free(str);之后, str 成为野指针,if(str != NULL)语句不起作用。




解答:

主要理解,堆和栈的区别。栈,自动释放内存,不能跨函数使用存储区。堆,手动释放内存,可跨函数使用存储区
内存分配方式有三种:

(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量, static 变量。

(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 或 new 申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

参看:C语言再学习 -- 再论内存管理

1、出现 段错误(核心已转储)

参看:C语言再学习 -- 再论数组和指针

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

int main (void)
{
Test ();
return 0;
}
输出结果:
段错误 (核心已转储)

解决方法:
第一种方法:使用二级指针作为函数的形式参数,可以让被调用函数使用其他函数的指针类型存储区
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void fa(char** p)  //主要还是指针的问题
{
*p=(char* )malloc(100);
if(*p)
{
return;
}
}
int main()
{
char* str=NULL;//这块没问题的
fa(&str);
strcpy(str,"hello");
printf("%s\n",str);
free(str);
str=NULL;
return 0;
}
输出结果:
hello
第二种方法:使用返回值
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* fa(char* p)  //主要还是指针的问题
{
p=(char* )malloc(100);
return p;
}
int main()
{
char* str=NULL;//这块没问题的
str = fa(str);
strcpy(str,"hello");
printf("%s\n",str);
free(str);
str=NULL;
return 0;
}
输出结果:
hello
2、警告: 函数返回局部变量的地址 [默认启用]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

int main (void)
{
Test ();
return 0;
}
输出结果:
警告: 函数返回局部变量的地址 [默认启用]
解决方法:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *GetMemory (char* p_str)
{
char *p = p_str;
p = "hello world";
return p;
}
void Test( void )
{
char *str = NULL;
str = GetMemory(str);
puts (str);
}

int main (void)
{
Test ();
return 0;
}
输出结果:
hello world
3、段错误(核心已转储)
//循环多次执行后
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

int main (void)
{
while (1)
{
Test ();
}
return 0;
}
输出结果:
hello
hello
。。。
hello
hello
段错误 (核心已转储)
解决方法:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void GetMemory (char **p, int num)
{
*p = (char *)malloc(num*sizeof (char));
}
void Test (void)
{
char *str = NULL;
GetMemory (&str, 100000);
strcpy (str, "hello");
puts (str);
free (str); //释放
str = NULL;
}

int main (void)
{
while (1)
{
Test ();
}
return 0;
}
输出结果:
hello
hello
。。。
4、结果难料
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
puts (str);
free(str);
if(str != NULL)
{
strcpy (str, "world");
puts (str);
}
}

int main (void)
{
Test ();
return 0;
}
输出结果:
hello
world

五、编写 strcpy 函数( 10 分)

已知 strcpy 函数的原型是
char *strcpy(char *strDest, const char *strSrc);
其中 strDest 是目的字符串, strSrc 是源字符串。
1)不调用 C++/C 的字符串库函数,请编写函数 strcpy
char *strcpy(char *strDest, const char *strSrc);
{
assert((strDest!=NULL) && (strSrc !=NULL)); // 2分
char *address = strDest; // 2分
while( (*strDest++ = * strSrc++) != ‘\0’ ) // 2分
NULL ;
return address ; // 2分
}
2strcpy 能把 strSrc 的内容复制到 strDest,为什么还要 char * 类型的返回值?
答:为了实现链式表达式。 // 2 分
例如 int length = strlen( strcpy( strDest, “hello world”) );
解答:
参看:C语言再学习 -- 字符串和字符串函数
1、功能实现函数:
char *strcpy(char *dest, const char *src)
{
char *tmp = dest;

while ((*dest++ = *src++) != '\0')
/* nothing */;
return tmp;
}
2、char*类型,它返回的是第一个参数的值,即一个字符的地址
#include <stdio.h>
#include <string.h>
#define WORDS "best"
#define SIZE 40

int main (void)
{
char *orig = WORDS;
char copy[SIZE] = "Be the best that you can be.";
char *ps;

puts (orig);
puts (copy);
ps = strcpy (copy + 7 , orig);
puts (copy);
puts (ps);
return 0;
}
输出结果:
best
Be the best that you can be.
Be the best
best

六、编写类 String 的构造函数、析构函数和赋值函数( 25 分)

已知类 String 的原型为:
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operate =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
请编写 String 的上述 4 个函数。
标准答案:
// String 的析构函数
String::~String(void) // 3 分
{
delete [] m_data;
// 由于 m_data 是内部数据类型,也可以写成 delete m_data;
}

// String 的普通构造函数
String::String(const char *str) // 6 分
{
if(str==NULL)
{
m_data = new char[1]; // 若能加 NULL 判断则更好
*m_data = ‘\0’;
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, str);
}
}

// 拷贝构造函数
String::String(const String &other) // 3 分
{
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, other.m_data);
}

// 赋值函数
String & String::operate =(const String &other) // 13 分
{
// (1) 检查自赋值 // 4 分
if(this == &other)
return *this;
// (2) 释放原有的内存资源 // 3 分
delete [] m_data;
// ( 3)分配新的内存资源,并复制内容 // 3 分
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, other.m_data);
// ( 4)返回本对象的引用 // 3 分
return *this;
}
解答:如果不做C++,理解这四个函数,就够了!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: