巨人网络笔试题

1. multiset操作

这儿是对STL各个容器的相关介绍. http://www.cplusplus.com/reference/stl/

multiset比较重要的操作就是: insert, erase, count和find

2. memcpy实现

 

void *Memcpy(void *dst, const void *src, size_t size)
{
    char *psrc;
    char *pdst;
    if(NULL == dst || NULL == src)
    {
        return NULL;
    }
    if((src < dst) && (char *)src + size > (char *)dst) // 自后向前拷贝
    {
        psrc = (char *)src + size - 1;
        pdst = (char *)dst + size - 1;
        while(size--)
        {
            *pdst-- = *psrc--;
        }
    }
    else
    {
        psrc = (char *)src;
        pdst = (char *)dst;
        while(size--)
        {
            *pdst++ = *psrc++;
        }
    }
    return dst;
}

3. vector,list,map循环删除

循环删除vector和map中的元素

删除所有偶数项,并打印出删除的项

(1). vector/queue

正确方法1:

void erase(vector<int> &v)
{
    for(vector<int>::iterator vi=v.begin();vi!=v.end();)
    {
        if(*vi % 2 == 0)
        {
            cout << "Erasing " << *vi << endl;
            vi = v.erase(vi);
        }
        else ++vi;
    }
}

正确方法2:

void erase2(vector<int> &v)
{
    for(vector<int>::reverse_iterator ri=v.rbegin();ri!=v.rend();)
    {
        if(*ri % 2 == 0)
        {
            cout << "Erasing " << *ri << endl;
            v.erase((++ri).base()); //erase()函数期待的是正向iterator,故而这里要调
            //用base()函数将逆向iterator转换为正向的
        }
        else ++ri;
    }
}

(2).map/list

正确方法

void erase(map<int,int> &m)
{
    for(map<int,int>::iterator mi=m.begin();mi!=m.end();)
    {
        if(mi->second % 2 == 0)
        {
            cout << "Erasing " << mi->second << endl;
            m.erase(mi++);
        }
        else ++mi;
    }
}

4. mysql操作

5. 静态变量与类大小关系.

首先,类的大小是什么?确切的说,类只是一个类型定义,它是没有大小可言的。

用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小。

如果

Class A;

A obj;

那么sizeof(A)==sizeof(obj)

那么sizeof(A)的大小和成员的大小总和是什么关系呢,很简单,一个对象的大小大于等于所有非静态成员大小的总和。

为什么是大于等于而不是正好相等呢?超出的部分主要有以下两方面:

1) C++对象模型本身

对于具有虚函数的类型来说,需要有一个方法为它的实体提供类型信息(RTTI)和虚函数入口,常见的方法是建立一个虚函数入口表,这个表可为相同类型的对象共享,因此对象中需要有一个指向虚函数表的指针,此外,为了支持RTTI,许多编译器都把该类型信息放在虚函数表中。但是,是否必须采用这种实现方法,C++标准没有规定,但是这几乎是主流编译器均采用的一种方案。

2) 编译器优化

因为对于大多数CPU来说,CPU字长的整数倍操作起来更快,因此对于这些成员加起来如果不够这个整数倍,有可能编译器会插入多余的内容凑足这个整数倍,此外,有时候相邻的成员之间也有可能因为这个目的被插入空白,这个叫做“补齐”(padding)。所以,C++标准紧紧规定成员的排列按照类定义的顺序,但是不要求在存储器中是紧密排列的。

基于上述两点,可以说用sizeof对类名操作,得到的结果是该类的对象在存储器中所占据的字节大小,由于静态成员变量不在对象中存储,因此这个结果等于各非静态数据成员(不包括成员函数)的总和加上编译器额外增加的字节。后者依赖于不同的编译器实现,C++标准对此不做任何保证。

C++标准规定类的大小不为0,空类的大小为1,当类不包含虚函数和非静态数据成员时,其对象大小也为1。

如果在类中声明了虚函数(不管是1个还是多个),那么在实例化对象时,编译器会自动在对象里安插一个指针指向虚函数表VTable,在32位机器上,一个对象会增加4个字节来存储此指针,它是实现面向对象中多态的关键。而虚函数本身和其他成员函数一样,是不占用对象的空间的。

5. 构造函数中使用memset(this,0,sizeof(*this))初始化类.

memset在c中是用的非常频繁的初始化函数了,当然也被带到了C++当中,因为当有如下类涉及到非常多的成员变量,很多coder经常偷懒改用memset在构造函数当中初始化

 

struct Test
{
int _1;
int _2;
long _3;
...
Test(){memset(this,0,sizeof(Test));}
};

以上如果所有成员变量是简单的内置类型是没有问题,但是可能某次需求迫使你需要往Test中增加一个数组,如下

 

struct Test
{
int _1;
int _2;
long _3;
...
std::vector<int> _n;
Test(){memset(this,0,sizeof(Test));}
};

结果在不同的编译器是不同的,最好的情况当然是启动时程序就挂掉了,至于为什么会挂掉,明白vector中实现了什么就知道了,当然不仅仅是vector,其他stl或者自己定制的容器可能都存在这个问题。

上面是一个陷阱,再一个陷阱就是在派生类的构造函数当中使用memset的问题

class Base
{
int _a;
};
class Derive : public Base
{
public:
Derive(){memset(this,0,sizeof(Derive));}
};

以上代码看出问题了没?

如果在改成下面的呢?

 

class Base
{
int _a;
public:
virtual ~Base(){}
};

memset做了一件本来你是做不到的事情,那就是把Derive的虚表指针也之位0了,结果当然就是内容泄露了

总结:

memset在内存操作方面太灵活了,但是我们也得注意在c++使用的时候是存在很多陷阱的,稍有不慎可能会造成很大的隐患,以上的问题并不是马上就一定会暴露出来的,不同的编译器现象是不一样的

5. 使用C++写成尽可能多的死循环方式.

C/C++中三种死循环的写法

一般来说,我们都会避免在程序中出现死循环。但在某些情况下我们又需要死循环,因此特总结三种常用的死循环的写法如下:

(1)、

while (1)

{

........

}

(2)、for(;;)

{

....

}

(3)、Loop:

....

Goto Loop;

6. C++类默认生成的函数.

ISO/IEC 14882(C++的国际标准文件)中说明:

一个空类必须默认生成四个成员函数:

构造函数,析构函数,拷贝构造函数,赋值函数

class Empty {

public:

Empty(); // 缺省构造函数

Empty(const Empty& rhs); // 拷贝构造函数

~Empty(); // 析构函数

Empty& operator=(const Empty& rhs); // 赋值运算符

};

有一点争议的是:

在《effective c++》中,大师说到一个类中应该包含六个默认成员函数,另外两个是

取址运算符和常取址运算符

Empty* operator&(); // address-of operators

const Empty* operator&() const;

7. 编译过程包括什么,哪些可以本地完成,哪些可以分发出去完成.

编译过程分为四步:

一、预处理工作:处理宏定义,不管是由-D参数指定,还是在源码内部通过#define,或者使用了标准库,扩展库中的宏,都会替换为定义的值。

命令:cpp a.c>a.i 输出的a.i就是预处理过的文件,包含了完整的内容。

由此可以得出,宏不是运行时的定义内容,也不是编译时的内容,而是预处理阶段就完成的。

二、编译阶段:将源代码(也就是预处理过的代码)编译为特定机器的汇编语言。

命令:gcc -S -Wall a.i 输出为特定的汇编内容。

三、汇编阶段:将汇编源码汇编为机器码内容。

命令:as a.s -o a.o 此处如果有对外部的函数使用,则会预留未定义的地址,以供最后一步链接来使用。

四、链接阶段:将链接对象文件(上编译汇编后的文件)链接为可执行文件,此过程比较复杂,需要链接很多外部的库文件,包括静态的,动态的库。

命令:ld -dynamic-linker .. .. ..

可以使用gcc a.o 来简化上一过程。

最后得出的是可执行文件。

8. TCP/IP过程

clip_image002

表1-2  TCP/IP模型各个层次的功能和协议

层次名称

功 能

协 议

网络接口

(Host-to-Net Layer)

负责实际数据的传输,对应OSI参考模型的下两层

HDLC(高级链路控制协议)

PPP(点对点协议)

SLIP(串行线路接口协议)

网际层

(Inter-network Layer)

负责网络间的寻址

数据传输,对应OSI参考模型的第三层

IP(网际协议)

ICMP(网际控制消息协议)

ARP(地址解析协议)

RARP(反向地址解析协议)

传输层

(Transport Layer)

负责提供可靠的传输服务,对应OSI参考模型的第四层

TCP(控制传输协议)

UDP(用户数据报协议)

应用层

(Application Layer)

负责实现一切与应用程序相关的功能,对应OSI参考模型的上三层

FTP(文件传输协议)

HTTP(超文本传输协议)

DNS(域名服务器协议)

SMTP(简单邮件传输协议)

NFS(网络文件系统协议)

说明  TCP/IP与OSI最大的不同在于OSI是一个理论上的网络通信模型,而TCP/IP则是实际运行的网络协议。

clip_image003clip_image004

为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的 ACK报文和FIN报文多数情况下都是分开发送的。

 

posted @ 2012-11-09 21:10  Mr.Rico  阅读(1992)  评论(0编辑  收藏  举报