- 能过说出面向对象编程的六大特点,其中最重要的是继承,多态;
(1)封装性.①将有关的代码和数据封装在一个对象中,各对象间相对独立,互不干扰.②将对象中的某些部分对外隐蔽,隐蔽内部细节,只留下少量接口.
对象的内部实现和外部行为分隔开来,人们在外部进行控制,具体的操作细节在内部实现,这样大大降低了人们操作对象的复杂程度.
(2)抽象性. 类是对象的抽象, 对象是类的具体表现形式.
(3)继承性. 最重要的特征,继承机制解决的软件的重用问题.
(4)多态性. 由继承产生的相关的不同的类,其对象对同一消息会做出不同的响应,.
- C和C++的不同?
- 一定要知道用C语言怎么去实现面向对象编程;(思路要很清楚)
- 对于下面的每行代码一定要知道每行的作用:
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern “C”{
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /*__INCvxWorksh*/
- 按值传递、按地址传递和引用传递的区别?
值传递:形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。
指针传递:形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作
引用传递:形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
- 什么是“引用”?申明和使用“引用”要注意那些问题?
(1)引用就是对某个变量取别名。对引用的操作与对应变量的操作的效果完全一样。
(2)申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。
(3)不能建立数组的引用。
- 将“引用”作为函数参数有那些特点?
传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
- “引用”和指针的区别是什么?
- 引用实际上是所引用的对象或变量的别名,而指针是包含所指向对象或变量的地址的变量。 (2)引用在定义时必须初始化,而指针在定义时不初始化。
(3)不可以有NULL的引用,而可以有指向NULL的指针。
(4)引用在初始化后不可以改变引用关系,而指针可以随时指向其他对象(非const指针)。
- #include<file.h>和#include “file.h”的区别?
对于#include <file.h>,编译器从标准库开始搜索file.h。 对于#include “file.h”,编译器从用户工作路径开始搜索file.h,搜索不到再到标准库去找。
- 在C++程序中调用被C函数编译后的函数,为什么要加extern “C”?
C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为: void foo(int x, int y); 该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。C++提供了C连接交换指定符号extern“C”来解决名字匹配问题。
- 面向对象的三个基本特征,并简单叙述之?
(1)封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public) (2) 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。 (3)多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。 |
|
- 重载(overload)和重写(overried)的区别?
重载(overload):编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如:有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
重写(override):和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)
- 多态的作用?
- 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
(2) 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。
- new/delete和malloc/free的区别?
都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.
- struct和class的区别?
虽然在字面上struct与class的含义不一样,但在C++中其功能基本是相同的,C++中的struct不仅可以包含数据成员,而且与class一样支持新增的面向对象特性,,仅在以下细节上有略微差别。
最本质的一个区别就是默认的访问控制,体现在两个方面:
- 默认的继承访问权限。struct是public的,class是private的。
- struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。
- “class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。
- 在8086汇编下,逻辑地址和物理地址是怎样转换的?(Intel)
逻辑地址的段地址*16+逻辑地址的偏移量=物理地址
- 类成员函数的重载、覆盖和隐藏的区别?
a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
- 非C++内建型别A和B,在那几种情况下B能隐式转化为A?
- class B: public A {…}
B公有继承A,可以是间接公有继承,当把B的对象赋值给A,会发生隐式转换。
- class B{ operator A(); }
B实现了隐式转化为A的转化
- class A{ A( const B& ); }
A实现了non-explicit的参数为B(可以有其他带默认值的参数)构造函数
(4)A& operator= ( const A& );
赋值操作,虽不是正宗的隐式类型转换,但也可以勉强算一个
- 如何定义和实现一个类成员函数的为回调函数?
A.什么是回调函数?
简而言之,回调函数就是被调用者回头调用调用者的函数。使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个被调用函数。而该被调用函数在需要的时候,利用传递的地址调用回调函数。 回调函数,就是由你自己写的,你需要调用另外一个函数,而这个函数的其中一个参数,就是你的这个回调函数名。这样,系统在必要的时候,就会调用你写的回调函数,这样你就可以在回调函数里完成你要做的事。
B.如何定义和实现一个类的成员函数为回调函数
要定义和实现一个类的成员函数为回调函数需要做三件事:a.声明;b.定义;c.设置触发条件,就是在你的函数中把你的回调函数名作为一个参数,以便系统调用
一、声明回调函数类型 :typedef void (*FunPtr)(void);
二、定义回调函数
class A
{
public:
A();
static void callBackFun(void) //回调函数,必须声明为static
{
cout<<"callBackFun"<<endl;
}
virtual ~A();
};
三、设置触发条件
void Funtype(FunPtr p)
{
p();
}
void main(void)
{
Funtype(A::callBackFun);
}
C. 回调函数与API函数
回调和API非常接近,他们的共性都是跨层调用的函数。但区别是API是低层提供给高层的调用,一般这个函数对高层都是已知的;而回调正好相反,他是高层提供给底层的调用,对于低层他是未知的,必须由高层进行安装,这个安装函数其实就是一个低层提供的API,安装后低层不知道这个回调的名字,但它通过一个函数指针来保存这个回调函数,在需要调用时,只需引用这个函数指针和相关的参数指针。其实:回调就是该函数写在高层,低层通过一个函数指针保存这个函数,在某个事件的触发下,低层通过该函数指针调用高层那个函数。
所谓的回调函数,就是预先在系统的对函数进行注册,让系统知道这个函数的存在,以后,当某个事件发生时,再调用这个函数对事件进行响应。定义一个类的成员函数时在该函数前加CALLBACK即将其定义为回调函数,函数的实现和普通成员函数没有区别
- 请讲一讲析构函数和虚函数的用法和作用?
析构函数的作用是当对象生命期结束时释放对象所占用的资源。 析构函数用法:析构函数是特殊的类成员函数 ,它的名字和类名相同,没有返回值,没有参数不能随意调用也没有重载。只是在类对象生命期结束时有系统自动调用。
虚函数用在继承中,当在派生类中需要重新定义基类的函数时需要在基类中将该函数声明为虚函数,作用为使程序支持动态联编。
- 操作符重载一定要会写
- C++里面是不是所有的动作都是main()引起的?如果不是,请举例。
在运行c++程序时,通常从main()函数开始执行。因此如果没有main(),程序将不完整,编译器将指出未定义main()函数。 例外情况:如, 在windows编程中,可以编写一个动态连接库(dll)模块,这是其他windows程序可以使用的代码。由于 DLL模块不是独立的程序,因此不需要main().用于专用环境的程序--如机器人中的控制器芯片--可能不需要main().但常规的独立程序都需要main(). 比如中断引起的中断处理不是直接由main()引起的,而是由外部事件引起的.
- 函数模版和类模版有什么区别?
函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。
- 纯虚函数如何定义?使用时应该注意什么?
- 体现多态的三个方面?
编程部分
- 文件中有一组整数,要求排序后输出到另一个文件中(用C++实现)。
- 用C++写一个程序,如何判断一个操作系统是16位还是32位的?不能用sizeof()函数;
int a = ~0;
if( a > 65536 )
{ cout<<"32 bit"<<endl; }
else
{ cout<<"16 bit"<<endl; }
- 编写类String的构造函数、析构函数和赋值函数;
class String { public: String(const char *str = NULL); //通用构造函数 String(const String &another); //拷贝构造函数 ~ String(); //析构函数 String & operater =(const String &rhs); // 赋值函数 private: char *m_data; //用于保存字符串 };
String::String(const char *str) { if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断 { m_data = new char[1] ; m_data[0] = '\0' ; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data,str); |
} }
String::String(const String &another) { m_data = new char[strlen(another.m_data) + 1]; strcpy(m_data,other.m_data); }
String& String::operator =(const String &rhs) { if ( this == &rhs) return *this ; delete [ ]m_data; //删除原来的数据,新开一块内存 m_data = new char[strlen(rhs.m_data) + 1]; strcpy(m_data,rhs.m_data); return *this ; }
String::~String() { delete [ ]m_data ; } |