`

程序员面试题

阅读更多

(一)c是malloc和free,c++是new和delete,区别如下:
(1)new、delete 是操作符,可以重载,只能在C++中使用。
(2)malloc、free是函数,可以覆盖,C、C++中都可以使用。
(3)new 可以调用对象的构造函数,对应的delete调用相应的析构函数。
(4)malloc仅仅分配内存,free仅仅回收内存,并不执行构造和析构函数
(5)new、delete返回的是某种数据类型指针,malloc、free返回的是void指针。

请教如何在频繁的动态分配内存时尽量减少内存碎片?

想在频繁的动态分配内存时尽量减少内存碎片,一个可行的做法是重载自己的operatornew以及operatordelete,具体的做法可以参考EffectiveC++中的有关条款,你可以用自己维护的对象池来减少碎片,所谓什么对象池无非就是一个列表,里面每一项指向一块你常用的那种数据的buf,在一开始就初始化好,然后用标明哪个未用,哪个已用,当不够用的时候再申请更多的内存,如果是要长时间运行的程序,可以在中途决定是否释放一部分,具体如何实现可以根据你的习惯,c的话只能用函数,c++可用重载的new和delete,也可以写成方法名譬如get,free,这个方法在c++里不如在java里有用
 

 

(二)Invalidate和UpdateWindow的区别

http://zhidao.baidu.com/question/314348918.html

在编程的时候经常把UpdateData、Invalidate、InvalidateRect和UpdateWindow四个函数混淆,在这里将简单介绍它们的区别。
UpdateData():

当你使用了ClassWizard建立了控件和变量之间的联系后:当你修改了变量的值,而希望对话框控件更新显示,就应该在修改变量后调用

UpdateData(FALSE);如果你希望知道用户在对话框中到底输入了什么,就应该在访问变量前调用UpdateData(TRUE),将控件的输入映射到变

量中。

Invalidate():
该函数的作用是使整个窗口客户区无效。窗口的客户区无效意味着需要重绘,例如,如果一个被其它窗口遮住的窗口变成了前台窗口

,那么原来被遮住的部分就是无效的,需要重绘。这时Windows会在应用程序的消息队列中放置WM_PAINT消息。MFC为窗口类提供了WM_PAINT

的消息处理函数OnPaint,OnPaint负责重绘窗口。视图类有一些例外,在视图类的OnPaint函数中调用了OnDraw函数,实际的重绘工作由

OnDraw来完成。参数bErase为TRUE时,重绘区域内的背景 将被擦除,否则,背景将保持不变。

InvalidateRect():
该函数的功能与Invalidate基本一样,不同的是,它是使指定的某个区域无效,需要输入一个区域。

UpdateWindow():
UpdateWindow( )的作用是使窗口立即重绘。调用Invalidate等函数后窗口不会立即重绘,这是由于WM_PAINT消息的优先级很低,它需

要等消息队列中的其它消息发送完后才能被处理。调用UpdateWindow函数可使WM_PAINT被直接发送到目标窗口,从而导致窗口立即重绘。


(三)什么时候要用虚析构函数?
通过基类的指针来删除派生类的对象时,基类的析构函数应该是虚的。否则其删除效果将无法实现。
一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,从而千万内存泄漏。
原因:
在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员。如果想要用基类对非继承成员进行操作,则要把基类的这个操作(函数)定义为虚函数。
那么,析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的。
注意:
如果不需要基类对派生类及对象进行操作,则不能定义虚函数(包括虚析构函数),因为这样会增加内存开销。
语法如下:
class Base
...{ public:
Base( ) ...{ };
virtual ~Base( ) ...{ }; //注意:基类的析构函数被定义为虚的
};
class Derived
...{
public:
Derived( ) ...{ };
~Derived( ) ...{ };
};
void main( )
...{
Base *p;
p = new Derived;
delete p;
}
注:似乎这样的使用经常在new和delete行为时。
=================================================
我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小例子来说明:
有下面的两个类:
class ClxBase
{
public:
ClxBase() {};
virtual ~ClxBase() {};
virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase
{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
代码
ClxBase *pTest = new ClxDerived;
pTest->DoSomething();
delete pTest;
的输出结果是:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
这个很简单,非常好理解。
但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:
Do something in class ClxDerived!
也就是说,类ClxDerived的析构函数根本没有被调用!一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。
所以,文章开头的那个问题的答案就是--这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。
写成虚的是为了在实现多态的时候不造成内存泄露, 比如:
class a
{
int aa;
public:
virtual ~a(){};
};
class b : public a
{
int bb;
};
如果你这样:
a *pa = new b; // upcast
然后这样:
delete pa;
这句delete, 如果你基类的析构函数不是虚的的话, 就会造成内存泄露, 具体表现为派生类的内存被释放了而基类没有.(疑问:似乎应该是基类内存释放而派生类内存没有释放)
//////////////////////////参考资料的地方, Efftive C++
在继承中使用多态来创建动态对象时, 比如上面的:a *pa = new b;
由于pa是个基类的指针, 只能识别属于基类的部分, 所以如果没有虚析构函数的话, 那么子类中特有的部分就不会被释放, 造成"经典"的释放一半, 泄露一半的内存泄露.
这和object slicing的原理是一样的, 至于object slicing:
为什么继承一个没有虚析构函数的类是危险的?
  一个没有虚析构函数的类意味着不能做为一个基类。如std::string,
std::complex, 和 std::vector 都是这样的。为什么继承一个没有虚析构函数的
类是危险的?当你公有继承创建一个从基类继承的相关类时,指向新类对象中的指
针和引用实际上都指向了起源的对象。因为析构函数不是虚函数,所以当你delete
一个这样的类时,C++就不会调用析构函数链。
class A
{
 public:
 ~A() // 不是虚函数
 {
 // ...
 }
};
class B: public A //错; A没有虚析构函数
{
 public:
 ~B()
 {
 // ...
 }
};
int main()
{
 A * p = new B; //看上去是对的
 delete p; //错,B的析构函没有被调用
}


(四)将10阶对称矩阵压缩存储到一维数组A中,则数组A的长度最少为

将10阶对称矩阵压缩存储到一维数组A中,则数组A的长度最少为( C )。

(A) 100 (B) 40 (C) 55 (D) 80

解答:((100-10)/2)+10

 

 

1、操作系统中的同步和异步有什么区别?分别应用在什么场合?

答:同步,就是说你的程序在执行某一个操作时一直等待直到操作完成。 最常见的例子就是 SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的 LRESULT值返回给调用者。
异步,就是说程序在执行某一个操作时,只是发出开始的指令;由另外的并行程序执行这段代码,当完成时再通知调用者。 当一个客户端通过调用 Connect函数发出一个连接请求后,调用者线程立刻可以朝下运行。当连接真正建立起来以后,socket底层会发送一个消息通知该对象。

打个比喻:
有一个男的 看上了两个漂亮MM 想通过写信的方式跟他们交流感情 这两个MM分别是A女、B女
同步:他先给A女写了封信 然后发了出去。等了好几天 A女给他回了信,之后他才给B女写信。就是说等到一个任务返回或者结束 他才继续往下做他想做的任务。
异步:他先给A女写了封信,然后发了出去,马上又给B女写了封信 也发了出去。 就是说不用等到一个任务结束就去做下一个任务。

但是如果第一个任务需要第二个任务的返回值 那就得用同步让第一个任务等待第二个任务结束后,获取第二个任务的返回值,在继续往下做。
并行:两个帅哥同时给这两个妹妹写信。
同步和异步的简单区别:
举个例子:普通B/S模式(同步)AJAX技术(异步)
同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
--------------------------------------------------------------------------------------------------------------------
同步就是你叫我去吃饭,我听到了就和你去吃饭;如果没有听到,你就不停的叫,直到我告诉你听到了,才一起去吃饭。
异步就是你叫我,然后自己去吃饭,我得到消息后可能立即走,也可能等到下班才去吃饭。
所以,要我请你吃饭就用同步的方法,要请我吃饭就用异步的方法,这样你可以省钱。
--------------------------------------------------------------------------------------------------------------------
举个例子:打电话是同步,发消息是异步

2、数据库的ACID特定是什么?以及他们分别应用的场合?

答:ACID是指数据库事务具有的四个特性:原子性、一致性、隔离性、持久性

原子性:事务是数据库的逻辑工作单位,事务中包括的操作要么都做,要么都不做。只有使据库事务中所有的操作都执行成功,才算整个事务成功;事务中任何一个SQL语句执行失败,那么已经执行成功的SQL语句也必须撤销,数据库状态应该回滚(ROLLBACK)到执行事务前的状态。

一致性:如果在执行事务之前数据库是一致的,那么在执行事务之后数据库也还是一致的;事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些尚未完成的事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。例如某公司在银行中有A、B两个账号,现在公司想从A中减去一万元,存入账号B。那么就可以定义一个事务,该事务包括两个操作,第一个操作就是从账号A减去一万元,第二个操作就是向账号B中加入一万元。这两个操作要么全做,要么全不做,数据库都处于一致性状态。如果只做一个操作则用户逻辑上就会发生错误,少了一万元,这时数据库就处于不一致状态。可见一致性与原子性是密切相关的。

隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能互相干扰。独立的数据库事务集合以不相互冲突的方式执行。仍使用这个银行类比,考虑两个客户同时在帐户之间转移资金。数据库必须分别跟踪两个转帐;否则,资金可能进入错误的帐户。

持久性:指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其执行结果有任何影响。 只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
3、TCP和UDP的区别以及应用有什么不同?

答:TCP与UDP的区别

TCP---传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP---用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。

应用: HTTP协议在运输层采用的就是TCP协议,在浏览器中输入IP地址后,与服务器建立连接,采用的就是TCP协议,是一种面向连接、可靠的字节流服务。

当强调传输性能而不是传输的完整性时,如:音频、多媒体应用和视频会议时,UDP是最好的选择。另外,腾讯QQ采用也是UDP协议。
UDP
UDP 与 TCP 的主要区别在于 UDP 不一定提供可靠的数据传输。事实上,该协议不能保证数据准确无误地到达目的地。UDP 在许多方面非常有效。当某个程序的目标是尽快地传输尽可能多的信息时(其中任意给定数据的重要性相对较低),可使用 UDP。ICQ 短消息使用 UDP 协议发送消息。
许多程序将使用单独的TCP连接和单独的UDP连接。重要的状态信息随可靠的TCP连接发送,而主数据流通过UDP发送。

TCP
TCP的目的是提供可靠的数据传输,并在相互进行通信的设备或服务之间保持一个虚拟连接。TCP在数据包接收无序、丢失或在交付期间被破坏时,负责数据恢复。它通过为其发送的每个数据包提供一个序号来完成此恢复。记住,较低的网络层会将每个数据包视为一个独立的单元,因此,数据包可以沿完全不同的路径发送,即使它们都是同一消息的组成部分。这种路由与网络层处理分段和重新组装数据包的方式非常相似,只是级别更高而已。
为确保正确地接收数据,TCP要求在目标计算机成功收到数据时发回一个确认(即 ACK)。如果在某个时限内未收到相应的 ACK,将重新传送数据包。如果网络拥塞,这种重新传送将导致发送的数据包重复。但是,接收计算机可使用数据包的序号来确定它是否为重复数据包,并在必要时丢弃它。

TCP与UDP的选择

如果比较UDP包和TCP包的结构,很明显UDP包不具备TCP包复杂的可靠性与控制机制。与TCP协议相同,UDP的源端口数和目的端口数也都支持一台主机上的多个应用。一个16位的UDP包包含了一个字节长的头部和数据的长度,校验码域使其可以进行整体校验。(许多应用只支持UDP,如:多媒体数据流,不产生任何额外的数据,即使知道有破坏的包也不进行重发。)
很明显,当数据传输的性能必须让位于数据传输的完整性、可控制性和可靠性时,TCP协议是当然的选择。当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择,如:DNS交换。把SNMP建立在UDP上的部分原因是设计者认为当发生网络阻塞时,UDP较低的开销使其有更好的机会去传送管理数据。TCP丰富的功能有时会导致不可预料的性能低下,但是我们相信在不远的将来,TCP可靠的点对点连接将会用于绝大多数的网络应用。

4、判断字符串是否为IP地址。

思路:输入字符串的时候,把分隔符“.”读取出来,然后判断分隔符旁边的数字是否在0~~255之间,然后判断是否合法。

[cpp]view plaincopy
 
  1. #include<stdio.h>
  2. #include<string.h>
  3. intmain(void)
  4. {
  5. charstr[31],temp[31];
  6. inta,b,c,d;
  7. while(gets(str)!=NULL)
  8. {
  9. if(sscanf(str,"%d.%d.%d.%d",&a,&b,&c,&d)==4&&a>=0&&a<=255&&b>=0&&b<=255&&c>=0&&c<=255&&d>=0&&d<=255)
  10. {
  11. sprintf(temp,"%d.%d.%d.%d",a,b,c,d);//把格式化的数据写入字符串temp
  12. if(strcmp(temp,str)==0)
  13. {
  14. printf("YES\n");
  15. }
  16. else
  17. {
  18. printf("NO\n");
  19. }
  20. }
  21. else
  22. {
  23. printf("NO\n");
  24. }
  25. }
  26. return0;
  27. }

5、指针和引用的区别?

1、从现象上看:指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变。
2、从内存分配上看:程序为指针变量分配内存区域,而引用不分配内存区域。
3、从编译上看:程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。

引用:一个变量的别名,为什么引入别名呢?原因是我们想定义一个变量,他共享另一个变量的内存空间,使用别名无疑是一个好的选择。变量是什么?是一个内存空间的名字,如果我们给这个内存空间在起另外一个名字,那就是能够共享这个内存了,引用(别名)的由此而来。
指针:指向另一个内存空间的变量,我们可以通过它来索引另一个内存空间的内容,本身有自己的内存空间。
二者区别:

(1)引用访问一个变量是直接访问,而指针是间接访问。
(2)引用是一个变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间,指针是一个实体,而引用不是。
(3)引用在开始的时候就绑定到了一个内存空间(开始必须赋初值),所以他只能是这个内存空间的名字,而不能改成其他的,当然可以改变这个内存空间的值。
例如
int i = 3,j = 4;
int &x = i; //成为i的别名
x = j; //不能否认x仍然引用i,并没有成为j的别名,只是修改了x和i共享的内存空间的值为4

 

6、abstract class(抽象类)和interface(接口)
在编程语句中用 abstract 修饰的类是抽象类。 在C++中,含有纯虚函数的类称为抽象类,它不能生成对象。 在java中,含有抽象方法的类称为抽象类,同样不能生成对象。 凡是包含纯虚函数的类都是抽象类。 抽象类是不完整的,并且它只能用作基类。

纯虚函数是一种特殊的虚函数,它的一般格式如下:
class <类名>
{
virtual <类型><函数名>(<参数表>)=0;

};
在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。

interface是面向对象编程语言(C#、Java)中接口操作的关键字,功能是把所需成员组合起来,以封装一定功能的集合。它好比一个模板,在其中定义了对象必须实现的成员,通过类或结构来实现它。接口不能直接实例化,即iCount ic=new iCount()是错的。接口不能包含成员的任何代码,只定义成员本身。接口成员的具体代码由实现接口的类提供。接口使用interface关键字进行声明。

区别:
1. 含有abstract修饰符的class即为抽象类,abstract 类不能创建的实例对象。
2. 含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。
3. abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。
4. 如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。
5. 接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。
6. 接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

下面比较一下两者的语法区别:
1. 抽象类可以有构造方法,接口中不能有构造方法。
2. 抽象类中可以有普通成员变量,接口中没有普通成员变量
3. 抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4. 抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
7. 一个类可以实现多个接口,但只能继承一个抽象类。

 

7、只在栈或者只在堆分配内存

 

只在栈上的类:
// 私有重载new即可,但只是限制了不能建立在new出的堆上,并没有限制全局以及局部静态类,因此严格
// 来说不能算“只在栈上”
class A
{
    private:
             static   void   *operator   new   (size_t   size){};  
};
//只在堆上的类:
// 私有化析构函数,通过一个public函数来进行实际的析构。
class B
{
    public:
            void del()const{ };
    private:
            ~B(){};
};

 //StackOnly.cpp    
  //2005.07.18------2009.06.05    
  #include   <iostream>    
  using   namespace   std;    
      
  class   StackOnly    
  {    
  public:    
  StackOnly()   {   cout   <<   "constructor."   <<   endl;   }    
  ~StackOnly()   {   cout   <<   "destructor."   <<   endl;   }    
  private:    
  void*   operator   new   (size_t);    
  };    
      
  int   main()    
  {    
  StackOnly   s;                                                             //okay    
  StackOnly   *p   =   new   StackOnly;                           //wrong    
      
  return   0;    
  }  
//HeapOnly.cpp  
  #include   <iostream>  
  using   namespace   std;  
    
  class   HeapOnly  
  {  
  public:  
  HeapOnly()   {   cout   <<   "constructor."   <<   endl;   }  
  void   destroy   ()   const   {   delete   this;   }  
  private:  
  ~HeapOnly()   {}    
  };  
    
  int   main()  
  {  
  HeapOnly   *p   =   new   HeapOnly;  
  p->destroy();  
  // HeapOnly   h;  
  // h.Output();  
    
  return   0;  
  }  

8、域名解析过程:

第一步:客户机提出域名解析请求,并将该请求发送给本地的域名服务器。
第二步:当本地的域名服务器收到请求后,就先查询本地的缓存,如果有该纪录项,则本地的域名服务器就直接把查询的结果返回。
第三步:如果本地的缓存中没有该纪录,则本地域名服务器就直接把请求发给根域名服务器,然后根域名服务器再返回给本地域名服务器一个所查询域(根的子域)的主域名服务器的地址。
第四步:本地服务器再向上一步返回的域名服务器发送请求,然后接受请求的服务器查询自己的缓存,如果没有该纪录,则返回相关的下级的域名服务器的地址。
第五步:重复第四步,直到找到正确的纪录。
第六步:本地域名服务器把返回的结果保存到缓存,以备下一次使用,同时还将结果返回给客户机。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics