参看:《高质量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;
}
输出结果:
段错误 (核心已转储)
解决方法:
第一种方法:使用二级指针作为函数的形式参数,可以让被调用函数使用其他函数的指针类型存储区
第二种方法:使用返回值
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、功能实现函数:
标准答案: // 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++,理解这四个函数,就够了!
#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 个函数。
char *strcpy(char *dest, const char *src)
{  char *tmp = dest;  while ((*dest++ = *src++) != '\0')  /* nothing */;  return tmp;
}  
2、char*类型,它返回的是第一个参数的值,即一个字符的地址

五、编写 strcpy 函数( 10 分)

已知 strcpy 函数的原型是
char *strcpy(char *strDest, const char *strSrc);
其中 strDest 是目的字符串, strSrc 是源字符串。
( 1)不调用 C++/C 的字符串库函数,请编写函数 strcpy
#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
#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 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>    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>    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* 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>
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

C语言再学习 -- 详解C++/C 面试题 1相关推荐

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

    (经典)C语言测试:想成为嵌入式程序员应知道的0x10个基本问题. 参看:嵌入式程序员面试问题集锦 1.用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) #define ...

  2. 一文弄懂元学习 (Meta Learing)(附代码实战)《繁凡的深度学习笔记》第 15 章 元学习详解 (上)万字中文综述

    <繁凡的深度学习笔记>第 15 章 元学习详解 (上)万字中文综述(DL笔记整理系列) 3043331995@qq.com https://fanfansann.blog.csdn.net ...

  3. C语言再学习--关键字

    如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/53021879 C语言一共有32个关键字,如下表所示: 关键字 说明 auto ...

  4. C语言再学习 -- 再论数组和指针

    之前有总结指针数组,但是现在看来总结的太简单了.好多重要的知识点都是一带而过的.本想在后面添加后来想想算了,还是再写一篇文章来详细介绍数组和指针这对冤家吧. 之前总结的,参看:C语言再学习 -- 数组 ...

  5. C语言再学习 -- 关键字volatile

    上周确实事情挺多的,年会.公司聚餐,一到过年就有忙不完的事分心.还好C语言再学习总结的已经差不多了,年前也不展开别的了,接下来这十几天.总结几篇典型的面试题吧. 言归正传,接下来看看关键字 volat ...

  6. C语言再学习 -- 关键字const

    const 关键字其实我们并不陌生,之前有讲过const修饰数组和指针.现在来详细介绍这个关键字. 参看:[C/C++和指针]著名的<const的思考> 一.const 介绍 1.cons ...

  7. C语言再学习 -- Linux下find命令用法

    参看:linux下find(文件查找)命令的用法总结 linux下查找文件的命令有两个:locate 和 find 首先说一下locate: 这个命名是对其生成的数据库进行遍历(生成数据库的命令:uo ...

  8. C语言再学习 -- C 预处理器

    gcc/cc xxx.c  可以编译链接C源程序生成一个可执行文件 a.out 整个过程中可以划分为以下的4步流程: (1)预处理/预编译: 主要用于包含头文件的扩展,以及执行宏替换等 //加上 -E ...

  9. 【C语言】函数详解(入门到进阶)

    目录 前言 一.什么是函数 二.函数的构成 三.函数的调用和声明 四.函数的参数 五.函数的递归 总结 写在后面 前言 最近帮家里的小朋友整理一些学习C语言的知识点 有整体入门基础文章--[C语言]拯 ...

最新文章

  1. ArcGIS的许可文件问题
  2. 【C 语言】二级指针作为输出 ( 指针输入 | 指针输出 | 二级指针 作为 函数形参 使用示例 )
  3. Windows 7 部署 Android 开发环境傻瓜式教程(Eclipse+ADT)
  4. 常考数据结构与算法:单链表的排序
  5. 递归 || 递归的相关实例练习
  6. 最大公约数gcd和Win32版本实现
  7. git-stash用法小结
  8. 什么是机器人的五点校正法_Epson机器人原点校准命令及方法(详细解释指令)
  9. python k线合成_手把手教你写一个Python版的K线合成函数
  10. View-屏幕坐标 Content-网页(内容)坐标 mScrollX和mScrollY-屏幕坐标偏移
  11. NYOJ 305 表达式求值 (字符串处理)
  12. python3 单例模式_当python,单例模式,多例模式,一次初始化遇到一起
  13. 公交车宜配备逃生绳索
  14. python中函数包括_python中有哪些函数
  15. 斯皮尔曼相关(spearman)系数法
  16. python学习(四):犹如鸿雁一般的Flask,小小框架有着无限可能
  17. 串行异步通信_什么是并行传输、串行传输、异步传输?
  18. 神州战神电脑关闭触摸板
  19. java pdf添加透明水印,PDF怎么加透明水印?
  20. OCJP(1Z0-851) 模拟题分析(三)

热门文章

  1. ubuntu18docker下安装MySQL
  2. redis解决“高并发定时秒杀”库存误差问题
  3. Dictionary To Dynamic
  4. 《JavaScript语言精髓与编程实践》读书笔记二
  5. 利用线程下载网页中的程序并另存到本地
  6. django 国际化 ugettext()
  7. Python学习笔记:入门(1)
  8. 你一定要知道的关于Linux文件目录操作的12个常用命令
  9. 【算法】深度学习神经网络都调哪些参数?
  10. 【Paper】Origin绘制误差棒图(标准差围绕均值)