1.
请写出 BOOL flag 与“零值”比较的 if 语句
标准答案:
if (flag)
if (!flag)
float x 与“零值”比较的 if 语句
const float EPSINON = 0.000001;
if ((x >= – EPSINON) && (x <= EPSINON)
不可将浮点变量用“==”或“!=”与数字
比较,应该设法转化成“>=”或“<=”此
类形式。
如下是错误的写法,不得分。
if (x == 0.0)
if (x != 0.0)
写出 char *p 与“零值”比较的 if 语句
标准答案:
if (p == NULL)
if (p != NULL)
如下写法均属不良风格,不得分。
if (p == 0)
if (p != 0)
if (p)
if (!)
2.
以下为Windows NT 下的32 位C++程序,请计算sizeof 的值
void Func ( char str[100])
{
请计算
sizeof( str ) = 4 (2 分)
}
char str[] = “Hello” ;
char *p = str ;
int n = 10;
请计算
sizeof (str ) = 6 (2 分)
sizeof ( p ) = 4 (2 分)
sizeof ( n ) = 4 (2 分)
void *p = malloc( 100 );
请计算
sizeof ( p ) = 4 (2 分)
3.
编写strcpy 函数
已知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分
}
(2)strcpy 能把strSrc 的内容复制到strDest,为什么还要char * 类型的返回值?
为了实现链式表达式。 // 2 分
例如 int length = strlen( strcpy( strDest, “hello world”) );
4.
编写类String 的构造函数、析构函数和赋值函数
已知类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;
}
5.
求下面函数的返回值(微软)
int func(x)
{
int countx = 0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;
}
假定x = 9999。 答案:8
思路:将x转化为2进制,看含有的1的个数。
6 .h头文件中的ifndef/define/endif 的作用?
答:防止该头文件被重复引用。
7. #i nclude<file.h> 与 #i nclude "file.h"的区别?
答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。
8.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?
答案:C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为: void foo(int x, int y);
该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。
C++提供了C连接交换指定符号extern“C”来解决名字匹配问题。
解释:首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数
extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。
_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
9.
请简述以下两个for循环的优缺点
for (i=0; i<N; i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
}
if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}
1.优点:程序简洁
缺点:多执行了N-1次逻辑判断,并且打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。
2.优点:循环的效率高
缺点:程序不简洁
10。请问运行Test函数会有什么样的结果?
A.
char *GetMemory(void)
{
char p[] = "helloworld";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
答:可能是乱码。
因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。
B.
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "helloworld");
printf(str);
}
答:程序崩溃。
因为GetMemory并不能传递动态内存,Test函数中的str一直都是NULL。strcpy(str, "helloworld");将使程序崩溃。
C
void GetMemory2(char **p, intnum)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str,"hello");
printf(str);
}
(1)能够输出hello
(2)内存泄漏
D.
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
答:篡改动态内存区的内容,后果难以预料,非常危险。
因为free(str);之后,str成为野指针,
if(str != NULL)语句不起作用。
11.编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,且该字符串是由同一字符组成的。
char * search(char *cpSource, char ch)
{
char *cpTemp=NULL, *cpDest=NULL;
int iTemp, iCount=0;
while(*cpSource)
{
if(*cpSource == ch)
{
iTemp = 0;
cpTemp = cpSource;
while(*cpSource == ch)
++iTemp, ++cpSource;
if(iTemp > iCount)
iCount = iTemp, cpDest = cpTemp;
if(!*cpSource)
break;
}
++cpSource;
}
return cpDest;
}
12.
const有什么用途?
1)可以定义 const 常量,定义变量为只读变量,不可修改
(2)const可以修饰函数的参数、返回值,甚至函数的定义体。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
修饰函数的参数和返回值(后者应用比较少,一般为值传递)
3)const成员函数(只需要在成员函数参数列表后加上关键字const,如char get() const;)可以访问const成员变量和非const成员变量,但不能修改任何变量。在声明一个成员函数时,若该成员函数并不对数据成员进行修改操作,应尽可能将该成员函数声明为const成员函数。
定义常量
被const
修饰过的变量不能被修改,故此具有常量之称。如果类的成员变量是常量,那么在初始化的时候必须初始化。
const int MAX = 100;
MAX = 300; // 提示语法错误
修饰函数
const
可以修饰函数的返回值,参数及,函数的定义体,被const修饰会受到强制的保护,能防止意外的修改,从而提高函数的健壮性。
1.修饰参数
不能在定义体中修改形参的值
// 函数声明
void updateWithID(const int id);
// 函数定义
void updateWithID(const int id)
{
id = 0; // 提示语法错误!
}
2.修饰返回值
被修饰的返回值不能作为左值,只有作为右值使用
// 函数声明
const int getNum();
getNum() = 10; // 提示语法错误!
3.修饰函数定义体
被const
修饰的函数定义体的函数能被const
或者非const
对象调用,但是const
对象只能调用被const
修饰过定义体的函数。
// 函数声明
void getDescription() const;
// 函数定义
void getDescription() const
{
// code
}
volatile的作用
用来修饰变量的,表明某个变量的值可能会随时被外部改变,因此这些变量的存取不能被缓存到寄存器,每次使用需要重新读取。
总结:防止脏读,增加内存屏障。
指针与引用的区别
- 指针只是一个变量,只不过这个变量存储的是一个地址;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已,不占用内存空间。
- 引用必须在定义的时候初始化,而且初始化后就不能再改变;而指针不必在定义的时候初始化,初始化后可以改变。
- 指针可以为空,但引用不能为空
智能指针怎么实现?什么时候改变引用计数?
- 构造函数中计数初始化为1;
- 拷贝构造函数中计数值加1;
- 赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一;
- 析构函数中引用计数减一;
- 在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象
shared_ptr 使用引用计数的方式来实现对指针资源的管理。同一个指针资源,可以被多个 shared_ptr 对象所拥有,直到最后一个 shared_ptr 对象析构时才释放所管理的对象资源。
可以说,shared_ptr 是最智能的智能指针,因为其特点最接近原始的指针。不仅能够自由的赋值和拷贝,而且可以安全的用在标准容器中。
extern关键字详解
extern可以置于函数或者变量前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义,也可用来进行链接指定
与C一起联用时,则告诉编译器在编译这个函数名时按照C的规则去翻译相应的函数名而不是C++
在.c文件中声明了一个全局变量,这个变量如果要被引用,就放在.h中并用extern来声明
函数声明中带有extern表示这个函数可能在别的源文件里定义
============================================================================================
- 什么是智能指针,智能指针有什么问题?
<memory>头文件中,分为shared_ptr和unique_ptr。unique_ptr,只允许基础指针的一个所有者,shared_ptr允许有多个所有者,通过计数的方式进行管理,最好是使用make_shared标准库函数。 - 什么是多态,什么是虚函数,什么是纯虚函数,虚函数是怎么实现的?virtual修饰类表示什么意思?
多态:通过基类的指针或引用调用虚函数时,编译时并不确定执行的是基类还是派生类的虚函数;当程序运行到该语句时,如果基类的指针指向的是基类的对象,则基类的虚函数被调用,如果指向的是派生类的对象,则派生类的虚函数被调用。
多态的作用:增强程序的可扩充性
虚函数:虚函数是声明时在函数前面加了virtual关键字的成员函数。
虚函数是怎么实现的:每个包含虚函数的类都有一个虚函数表,该类的任何对象都存放着该虚函数表的指针。根据基类指针或引用指向的对象中所存放的虚函数表地址,在虚函数表中查找虚函数地址,调用虚函数,从而实现多态。
纯虚函数:没有函数体的虚函数,写法是在函数声明后面加“=0”,不写函数体。包含纯虚函数的类叫做抽象类,抽象类不能实例化。 - 什么函数不能是虚函数?
构造函数 静态函数 - public procted privated继承方式有什么区别?
- 析构函数是为什么要是虚析构函数?构造函数为什么不能被定义为虚函数?
- 防止内存泄露。如果不是虚析构函数,当使用基类的指针去销毁子类对象时,不会调用子类的析构函数,会导致内存泄露。
- static的作用
- 修饰局部变量:存储在静态存储区,默认初始化为0
- 修饰全局变量或全局函数:只在本文件可见,在其他文件不可见
- 修饰类的成员变量只能在类外初始化(如int Base::N = 10),或者用const修饰static在类内初始化;
- 修饰类的成员函数。注意不能同时用const和static修饰类的成员函数,因为类中的const修饰的函数,编译器会默认添加一个this指针,而static函数是属于整个类的,没有this指针,两者相互矛盾。
- const的作用
- 定义常变量
- 修饰成员函数,表示不可修改成员变量的值
- C和C++的区别是什么?
C++在C的基础上添加了类。C是面向过程的,C++是面相对象的。 - C++11特性有哪些?
智能指针、lambda表达式、auto和decltype、基于范围的for循环、override和final关键字、右值引用、无序容器
- new 和malloc的区别是什么?参考链接
- new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存
- new是运算符,malloc是库函数
- new返回指定类型,malloc范围void*类型,需要强制类型转换
- new内存分配失败时返回bad_alloc异常,malloc返回Null
- 是否需要指定内存大小,new不需要,malloc需要显式指定字节大小
- new会调用构造函数,delete会调用析构函数,malloc和free不会
- new可以被重载,malloc不能
- 有没有用过模板编程?
功能相同而数据不同,分为函数模板和类模板。 - 右值引用是什么?
能出现在赋值号左边的表达式称为左值,不能出现在赋值号左边的表达式称为右值。
非const的变量都是左值,函数调用的返回对象如果不是引用,则函数调用是右值。
因为大部分引用都是引用变量的,而变量是左值,所以这些引用称为左值引用。
右值引用可以引用无名的临时变量,主要目的是提高运行效率。方式是&&,如A&& r = A() - 内存分区
- 栈区:函数参数和局部变量
- 堆区:malloc/new手动申请
- 全局区(或叫静态区):全局变量、静态变量
- 常量存储区,这是一块比较特殊的存储区,里面存放的是常量,不允许修改
- 代码区:存放二进制代码
多线程知识
- vector的遍历方式
访问分为下标访问,for each访问,at()访问。at()加了越界判断,效率会低一点。 - stl函数sort函数
- 函数对象
如果一个类将“()”运算符重载为成员函数,这个类就成为函数对象类,这个类的对象就是函数对象。 - stl迭代器有哪些?迭代器失效场景
vector clear()操作发生了什么,会清内存吗,迭代器有哪些? - 多线程用到了哪些东西?原子变量的原理是什么?
原子变量,读写锁,条件变量 - mutex怎么用
shared_lock,unique_lock