笔试

来源:互联网 发布:java读书笔记 编辑:程序博客网 时间:2024/04/29 08:24

                           笔试

1、排序算法:

typedef struct {

    int key;

    

} RecType;

/******** 冒泡 ********/

void BubbleSort(RecType R[],int n)

{

    int i,j;

    RecType temp;

    for (i = 0; i<n-1; i++) {

        for (j = n-1; j>i; j--) {  //比较找本趟最小关键字的记录

            if (R[j].key<R[j-1].key){

                temp = R[j];

                R[j] = R[j-1];

                R[j-1] = temp;

            }

        }

    }

}

/******* 插入 *******/

void InsertSort(RecType R[],int n)

{

    int i, j;

    RecType temp;

    for (i = 1; i<n; i++) {

        temp = R[i];

        j = i-1; //从右向左在有序区R[0...i-1]找到R[i]插入的位置

        while (j>=0 && temp.key<R[j].key) {

            R[j+1] = R[j];

            j--;

        }

        R[j+1] = temp;

    }

}

/******* 选择 *******/

void SelectSort(RecType R[],int n)

{

    int i,j,k;

    RecType temp;

    for (i = 0; i<n-1; i++) {  //做第i趟排序

        k = i;

        for (j = i+1; j<n; j++) {// [i...n-1]中选key最小的R[k]

            if (R[j].key<R[k].key) {

                k = j; // k记下的最小关键字所在的位置

            }

        }

        if (k!=i){ // 交换R[i]R[k]

            temp = R[i];

            R[i] = R[k];

            R[k] = temp;

        }

    }

}

int main(int argc,const char * argv[]) {

    RecType r1, r2, r3;

    r1.key = 4;

    r2.key = 2;

    r3.key = 3;

    RecType R[] = {r1,r2,r3};

//    BubbleSort(R, 3);

//    InsertSort(R,3);

    SelectSort(R, 3);

    for (int i =0; i<3; i++) {

        printf("sort%d:%d\n",i,R[i].key);

    }

    return 0;

}

 

2、cache的机制是什么?

答:把新加进内存的资源做一个hashmap存储,每一个资源加一个key。每次加载资源的时候,先查找资源是否存在,存在直接返回,否则加载进内存。

 

3、游戏主循环的内容?

答:2.x中,渲染过程是通过递归渲染树(Rendering tree)这种图关系来渲染关系图。递归调用visit()函数,并且在visit()函数中调用该节点的draw函数渲染各个节点,此时draw函数的作用是直接调用OpenGL代码进行图形的渲染

3.x通过各种RenderCommand封装起来,然后添加到一个CommandQueue队列里面去,而现在draw函数的作用就是在此函数中设置好相对应的RenderCommand参数,然后把此RenderCommand添加到CommandQueue中。最后在每一帧结束时调用renderer函数进行渲染,在renderer函数中会根据ID对RenderCommand进行排序,然后才进行渲染。

 

 

 

 

 

4、如何进行内存优化?

答:尽量不适用帧动画而使用骨骼动画;减少粒子数量;使用PVR格式的纹理;

音频文件为mp3格式并且保证你的背景音乐文件大小在800KB一下

 

5、cocos2d-x内存管理机制?

答:Cocos2d-x用了一种引用计数的方式来管理内存对象,通过类Ref、AutoreleasePool和PoolManager来完成。

Ref为引用计数类,其构造函数是protected的访问类型,当Ref的对象被创建的时候它的引用计数为1

在对象创建的时候会调用create,然后调用autorelease,将对象放入对象池(AutoreleasePool的一个对象中)方便后面的统一管理,

retain增加引用计数,release调用时会立刻减少引用计数

引擎初始化后就会创建一个默认的自动释放对象列表AutoreleasePool并加入到PoolManager里面进行管理

每次主循环,都会有一次pool的清理,把那些计数为0的object都删除掉。这就是cocos2dx中的内存管理方法

PoolManager为单例模式,不用编码者来维护,由引擎自动完成。

 

6、图片压缩的方法?

答:

1). 采用工具对资源进行[打包],例如TexturePacker等工具。

2.采用png压缩工具等,在打包图片前对每张图片进行[压缩]

如果以上两点都做了还是体积过大,那么继续采取如下办法:

3.降低图片质量,比如你项目使用的图片是24bit,那么你可以采用低一些的,例如8bit的图片质量类似。

4.使用特定的压缩格式的图片,例如wdp等等

 

7、autorelease和release的区别?

   release是立即释放引用计数,如果到达0,对象被销毁。

   autorelease是延迟释放,是为了更好管理内存产生的。可以保证外部调用者获得对象指针,而又会被释放。 autorelease的实现机制,是将对象加入一个pool统一管理,当pool被release时,pool里面每个对象都会被release。pool基于一个栈式结构管理,每一个mainloop会pop一次。同一个mainloop里面调用autorelease,会把引用加入栈顶pool。

 

8、游戏主循环?

答:mainLoop是一个死循环,首先判断循环结束(净化上一次循环)里面调用update通过时间差更新数据,调用drawScene呈现、绘制游戏画面,释放没用的对象

 

 

 

 

 

C++ 基础

一、 简答题。 ( 共7题 ,共0分,每题0分 )

1. 在什么时候需要使用“常引用”? 

:如果既要利用引用提高效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。

 

2. 将“引用”作为函数参数有哪些特点?

:1.传递引用给函数与传递指针的效果是一样的,这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

2.使用引用传递函数的参数,在内存中并没有产生实参的副本,它直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占的空间都好。

3.使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用”*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

 

 

3. 什么是“引用”?声明和使用“引用”要注意哪些问题?

:引用就是对某个变量起一个别名。对引用的操作与对原变量的操作的效果是完全一样的。

声明一个引用的时候,需要对其进行初始化。声明一个引用,不是定义了一个变量,它只是目标变量名的一个别名。

 

 

 

4. static变量和static 函数各有什么特点?

: 1. 用于局部变量中,成为静态局部变量. 静态局部变量有两个用法,记忆功能和全局生存期.  

2. 用于全局变量,主要作用是限制此全局变量被其他的文件调用.  

3. 用于类中的成员.表示这个成员是属于这个类但是不属于类中任意特定对象

 

static函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件

 

 

 

5. class和struct 的区别?

:

C++中,在class中声明的成员默认为private成员,而在struct中声明的成员默认为public成员。class的默认继承方式为private, struct默认继承方式为public;

 

 

6. C++和C有什么不同?

:C是经典的结构化编程语言,C++兼容C,但是在其基础上增加了面向对象的思想,支持封装、继承、多态、重载和模板机制等。

 

 

 

7. 以下为Windows NT下的32位C++程序,请计算sizeof的值

void Func ( char str[100] )

{

sizeof( str ) = ?

}

void *p = malloc( 100 );

sizeof ( p ) = ?

:

4 4

不管任何类型的指针,在32位系统下,指针占4个字节。数组作为参数传递的时候会退化为指针。

 

 

二、 问答题。 ( 共95题 ,共0分,每题0分 )

1. 分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)

:

BOOL:   if(var)

int: if(var == 0)

float: if(var <= 0.0001 && var >=  -0.0001)

由于float类型在内存中存储并不是精确存储的,故不能直接和0进行比较。

 

2. 线程与进程的区别和联系?

:

定义:

一、进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。

二、线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),一个线程可以创建和撤销另一个线程;

 

进程和线程的关系:

1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。

3)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

4)处理机分给线程,即真正在处理机上运行的是线程。

5)线程是指进程内的一个执行单元,也是进程内的可调度实体。

线程与进程的区别:

1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。

2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。

3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。

4)系统开销:在创建或撤销进程的时候,由于系统都要为之分配和回收资源,导致系统的明显大于创建或撤销线程时的开销。但进程有独立的地址空间,进程崩溃后,在保护模式下不会对其他的进程产生影响,而线程只是一个进程中的不同的执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但是在进程切换时,耗费的资源较大,效率要差些。

 

线程的划分尺度小于进程,使得多线程程序的并发性高。

另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大的提高了程序运行效率。

线程在执行过程中,每个独立的线程有一个程序运行的入口,顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,有应用程序提供多个线程执行控制。

从逻辑角度看,多线程的意义子啊与一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

 

 

3. (void *)ptr 和 (*(void**))ptr的结果是否相同?

:

结果相同。

void *ptr: ptr被强制转换为void *类型的指针。

*(void **))ptr:ptr被强制转换为void**二维指针,之后前面再加*表示二维指针指向的指针(一维指针)。故两种写法都是一样的。

 

4. char * const p;

char const * p

const char *p

上述三个有什么区别?

:char *const p:定义了一个指针常量,指针指向不能发生变化,但是可以修改指向的变量的值。定义的时候必须要初始化。

char const *p: 定义了一个常量指针,指向常量的指针。无法修改指向的变量的内容,但是可以改变指针的指向。

const char *p: 定义了一个常量指针。指向常量的指针。无法修改指向的变量的内容,但是可以改变指针的指向。

 

 

5. 什么是预编译,何时需要预编译

:预编译又叫作预处理,主要做一些代码文本替换的工作。主要处理以#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等,主要为编译做的预备工作阶段。预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。

什么时候需要预编译?

a. 总是使用不经常改动的大型代码体。

b. 程序由多个模块组成,所有模板都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。

 

 

6. 多重继承如何消除向上继承的二义性。

:采用虚基类

 

 

 

7. 为什么要引入抽象基类和纯虚函数?

: 主要目的是为了实现一种接口的效果。(比如动物类这样适合产生对象,只适合作为接口出现的类)

 

 

 

8. C也可以通过精心封装某些函数功能实现重用,那C++的类有什么优点吗(从面向对象的三大属性进行分析)

: (1). 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public),而C不具备这种语法。

   (2). 继承:派生类继承自基类,基类中拥有的数据派生类中也就拥有了,提高代码重用性,不需要再重新编写代码。

   (3). 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

 

 

 

9. 基类的有1个虚函数,子类还需要申明为virtual吗?为什么。

:不需要了,子类会默认加上virtual

 

 

 

10. 拷贝构造函数相关问题,简述什么是深拷贝,什么是浅拷贝。

:深拷贝和浅拷贝都发生在拷贝对象的时候,并且当对象里面有指针成员并且指针成员指向堆上的空间才有这个区别。比如定义一个Example类:

Class Example

{

int *p;

};

Example a;

Example b = a; //这时候会调用拷贝构造函数,发生对象拷贝(深拷贝或者浅拷贝)

深拷贝:对象a里面的指针成员p指向堆上的一块空间,对象b里面的指针成员p指向堆上的另一块空间,但是它们堆空间里面的内容是一样的。(这是两块不同的堆空间)

浅拷贝:对象a里面的指针成员p指向堆上的一块空间,对象b里面的指针成员p也指向这块空间。(两个指针指向同一块堆上的空间)

 

简单的来说,深拷贝是指针成员指向不同的堆空间,浅拷贝是指针成员指向相同的堆空间。

 

11. 构造函数可否是虚函数,为什么?析构函数呢,可否是纯虚的呢?

:构造函数不能声明为虚函数,析构函数可以声明为虚函数,但是析构函数不能声明为纯虚函数。

1. 每一个拥有虚成员函数的类都有一个指向虚函数表的指针。对象通过虚函数表里存储的虚函数地址来调用虚函数。

 

那虚函数表指针是什么时候初始化的呢?当然是构造函数。当我们通过new来创建一个对象的时候,第一步是申请需要的内存,第二步就是调用构造函数。试想,如果构造函数是虚函数,那必然需要通过vtbl来找到虚构造函数的入口地址,显然,我们申请的内存还没有做任何初始化,不可能有vtbl的。因此,构造函数不能是虚函数。

2.析构函数可以声明为虚函数。当基类指针指向派生类对象的时候,通过基类指针删除派生类对象,声明基类析构函数为虚函数,则会调用派生类的析构函数,这样能保证内存不发生泄露。

3.析构函数可以声明为纯虚函数,但是必须要给出定义。

 

 

12. 多重继承的内存分配问题:
   

比如有class A : public class B, public classC {}
   那么A的内存结构大致是怎么样的?

:A的内存大概分为三部分。第一部分为继承自基类B的成员,第二部分为继承自基类A的成员,第三部分为自己类中定义的成员。

 

 

 

13. 进程间通信的方式有?

进程间通信主要包括管道, 系统IPC(包括消息队列,信号量,共享存储), SOCKET.

具体请看:http://blog.csdn.net/yufaw/article/details/7409596

 

14. 简述数组与指针的区别?

:

数组拥有成员的空间,并且数组名代表第一个元素的地址。而指针没有成员的空间,指针只能保存其他变量的地址。

具体知识点请看:

http://blog.chinaunix.net/uid-21411227-id-1826897.html

 

15. 请说出const与#define 相比,有何优点?

:1.const在程序运行时,而#define是发生在预处理阶段。

2.工作原理不同:const用来修饰变量或者类型,可以进行类型检查。而#define只是简单的文本替换,没有安全的类型检查。

3.有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

 

 

16. 描述内存分配方式以及它们的区别?

:内存分配大致上可以分成5块:

1、 栈区(stack)。栈,就是那些由编译器在需要时分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。(由编译器管理)

2、 堆区(heap)。一般由程序员分配、释放,若程序员不是放,程序结束时可能由系统回收。注意,它与数据结构中的堆是两回事,分配方式类似于链表。

3、 全局区(静态区)(static)。全局变量和静态变量被分配到同一块内存中。程序结束后由系统释放。

4、 常量存储区。常量字符串就是放在这里的,不允许修改,程序结束后由系统释放。

5、 程序代码区。存放函数体的二进制代码。

 

 

17. 有哪几种情况只能用intializationlist (初始化列表)而不能用assignment?

:引用,常量

 

 

 

18. #define DOUBLE(x) x+x ,i = 5*DOUBLE(5); i 是多少?

:#define的作用是进行简单的文本替换,故:

i = 5 * 5+5 = 30;

 

 

 

19. New delete 与malloc free 的联系与区别?

:

new delete和malloc free都是释放申请的堆上的空间,都是成对存在的,否则将会造成内存泄露或二次释放。不同的是,new delete是C++中定义的操作符,new除了分配空间外,还会调用类的构造函数来完成初始化工作,delete除了释放空间外还会调用类的析构函数。而malloc和free是C语言中定义的函数。

 

 

20. 什么是多态,多态的作用?

:多态:对不同类的对象发出相同的消息将会有不同的行为,具体表现在程序中是父类指针指向子类对象,调用实际子类对象的函数。

作用:

1. 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承

2. 派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 //多态的真正作用,以前需要用switch实现

 

 

 

21. 重载(overload)和重写(overried“覆盖”)的区别?

:

重载:函数名一样,参数类型和个数不一样。也就是拥有相同名字的不同函数。

重写:函数定义一模一样,多发生在继承中。子类将继承来的父类函数给重新定义,将父类函数给屏蔽掉。

 

22. 面向对象的三个基本特征,并简单叙述之?

1.封装:

封装是指利用抽象数据类型和基于数据的操作结合在一起,数据被保护在抽象数据类型的内部,系统的其他部分只有通过包裹在数据之外被授权的操作,才能与这个抽象数据类型进行交互。

 

2. 继承:

它是与传统方法不同的一个最有特色的方法。它是面向对象的程序中两个类之间的一种关系,即一个类可以从另一个类(即它的父类)继承状态和行为。继承父类的类称为子类。

继承的优越性:通过使用继承,程序员可以在不同的子类中多次重新使用父类中的代码,使程序结构清晰,易于维护和修改,而子类又可以提供一些特殊的行为,这些特殊的行为在父类中是没有的

 

3.多态:

是指一个程序中同名的方法共存的情况,调用者只需使用同一个方法名,系统会根据不同情况,调用相应的不同方法,从而实现不同的功能。多态性又被称为“一个名字,多个方法”。

 

 

 

23. 请说出static和const关键字尽可能多的作用

:

static关键字的作用:

1. 隐藏。全局变量或者全局函数加上static则其他文件无法访问。

2. 保持变量内容的持久。由于static变量存储在全局/静态区,一经初始化则直到程序结束后才会被释放。

3. 默认初始化变量为0.经static修饰的变量都会被默认初始化为0

4. 在定义类的时候将成员变量或者成员函数加上static,则变为属于类的成员,所有对象共享。

const关键字的作用:

1. 修饰变量:const int a = 10;则a成为一个常量。

2. 修饰指针。分为常量指针和指针常量

3. 修饰引用。使引用成为常引用,无法通过引用修改变量的值

4. 修饰数组。数组成为常数组,无法修该里面元素的值,只能访问。

5. 修饰函数参数。不生成变量的副本,提高效率。

6. 修饰类成员函数。使类的成员函数成为const成员函数,在函数中无法修改类的成员变量的值。

 

24. “引用”与指针的区别是什么?

相同点:

1.都是地址的概念;指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。

不同点:

1.指针是一个实体,而引用仅是个别名;

2.引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;

3.引用没有const,指针有const,const的指针不可变;

4.引用不能为空,指针可以为空;

5.“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;

6.指针和引用的自增(++)运算意义不一样;

7.引用是类型安全的,而指针不是 (引用比指针多了类型检查

 

 

 

25. “引用”与多态的关系?

:多态的实现条件有:1.要有继承 2.要有虚函数 3.要有函数重写 4.父类指针或者父类引用指向子类对象。

由此可见,引用在多态中也有着其作用。

 

 

 

26. 将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?

:将引用作为函数返回值类型的格式为:int &fun();

好处:不会生成对象的副本,不会产生临时对象,直接返回的是返回的对象。

遵循的规则:不能返回局部变量或者局部对象的引用,因为局部变量或者局部对象返回之后会被释放掉。

 

 

 

27. 如下的例子合不合法,从参数传递 角度进行考虑是否合法

string foo( );

void bar(string&s)

bar(foo( ));

bar("hello world");

:首先程序中定义了一个返回值为string类型的函数foo,之后定义了一个参数为string引用的s。将foo()传递给函数bar,就相当于传递一个string变量给函数bar,这是合法的。bar(“hello world”)中,将字符串hello world当作string对象传递给bar函数需要看string类中有没有带有一个char*类型的构造函数(注意必须是带有一个参数的构造函数),如果有的话则编译器会隐式调用构造函数将其转换为striing对象,合法。如果没有的话则不可以。(ps:详请查看C++课程构造函数的隐式转换一节)

 

28. 不使用C/C++字符串库函数,如何自行编写strcpy()函数

:

char *strcpy(char *destStr,const char *srcStr)

{

char *rStr =destStr;

if (srcStr ==NULL || destStr == NULL)

{

throw "srcStr is NULL";

}

while(*destStr++ = *srcStr++ !='\0');

return rStr;

}

 

 

29. 编译型语言与解释型语言的区别是什么

: 编译型语言在程序执行之前,有一个单独的编译过程,将程序翻译成机器语言,以后执行这个程序的时候,就不用再进行翻译了。

 

解释型语言,是在运行的时候将程序翻译成机器语言,所以运行速度相对于编译型语言要慢。

C/C++ 等都是编译型语言,而Java,C#等都是解释型语言。

 

30. 什么函数不能声明为虚函数

:普通函数(非类成员函数);构造函数;静态成员函数;内联成员函数;友元函数

 

 

 

31. C++中的继承、虚函数、纯虚函数是什么?谈一下自己的理解。

:C++中的继承说的是子类继承自父类的成员变量和成员函数,主要的好处是代码重用。

虚函数:在普通的成员函数添加virtual关键字,则成为虚函数。虚函数是多态实现的一部分。(多态包括:1.继承2.函数重写3.虚函数4.父类指针或者引用指向子类对象)

纯虚函数:将类声明为抽象类,表明这个类不能被实例化,不能产生对象。主要是作为接口来使用。

 

 

32. 什么是深复制,什么是浅复制

:请参考上面第10题

 

 

 

33. 面向对象的基本特征有哪些

:继承、封装、多态。

 

 

 

34. 类与对象有什么区别?

:类是自定义一种类型,相当于C语言的int类型等。

对象是类的实例,相当于C语言中的变量。

 

 

 

35. C++继承是如何工作的?

:派生类继承自基类,除了构造函数、赋制运算符重载、析构函数之外,基类的其他成员函数和成员变量都会被派生类继承。调用继承来的成员函数和父类调用一样。当然,派生类可以重写继承自基类的函数。

 

 

 

36. 函数重载与函数覆盖有什么不同,它们与多态有什么关系?

:函数重载:函数名一样,参数的个数和类型不一样。数不同的函数。

函数覆盖:函数一模一样。

函数覆盖是多态实现的条件之一(多态:1.继承2.虚函数3.函数重写(覆盖)4.父类指针指向子类对象)。

 

 

 

37. 拷贝构造函数在哪几种情况下调用?

:

1.当用一个已经产生的对象初始化另外一个对象的时候

2.作为函数的参数的时候。

3.作为函数的返回值,从函数中返回时。

 

 

 

38. 虚析构函数有什么作用?

如果构造函数打开了一个文件,最后不需要使用时文件就要被关闭。析构函数允许类自动完成类似清理工作,不必调用其他成员函数。

析构函数也是特殊的类成员函数。简单来说,析构函数与构造函数的作用正好相反,它用来完成对象被删除前的一些清理工作,也就是专门的扫尾工作。

 

 

39. 简述使用<iostream.h>与<iostream>命名空间std这两种形式有什么区别

: <iostream>和<iostream.h>是不一样,前者没有后缀,实际上,在你的编译器include文件夹里面可以看到,二者是两个文件,打开文件就会发现,里面的代码是不一样的。

 

     iostream.h是C的头文件库,iostream是C++标准头文件库,C++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。(但是因为早期C++继承了C的特性,为了兼容以前的C++代码,故保留了iostream.h的这种写法。早些的实现将标准库功能定义在全局空间里,声明在带.h后缀的头文件里,)目前后缀为.h的头文件c++标准已经明确提出不支持了(VC++8之后版本)。

因此,当使用<iostream.h>时,相当于在C中调用库函数,使用的是全局命名空间,也就是早期的C++实现;当使用< iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout.或是逐个定义如std::cout

 

 

 

40.什么是动态绑定,动态绑定有什么好处

:动态绑定是指在执行期间(非编译期间)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。

好处:根据对象的实际类型来调用相应对象的函数。扩展性强,

 

 

41.简单描述多重继承中怎样产生的菱形继承问题,又是如何解决的呢?

:A类和B类共同继承自C类,D类分别继承自A类和B类,那么D类中将会有“两个C类”, 一个来自A类,一个来自B类。

采用虚基类来解决

 

 

 

42.什么是抽象基类,抽象基类可不可以被实例化,为什么

:类中至少含有一个纯虚函数的类被称之为抽象类。

抽象类不能被实例化,无法产生对象。

抽象类被当作接口来使用,纯虚函数只有声明没有定义,也是无法产生对象的原因。

 

 

43. 简述重写和重载的区别

:

重写:函数一模一样,发生在继承中,子类重写继承自父类的函数。

重载: 函数名一样,参数个数或类型不一样。

 

44. 写一个拷贝构造和赋值运算符重载函数的例子,并说明分别什么情况下会调用它们。

:定义一个Complex类,有两个私有成员(实部和虚部) r i;

Complex(const Complex&complex)

{

r = complex.r;

i = complex.i;

}

Complex& operator=(Complex &complex)

{

if (this == &complex)

return *this;

r = complex.r;

i = complex.i;

return *this;

}

拷贝构造函数:1.初始化对象2.作为函数参数3.从函数中返回

赋值运算符重载:赋值的时候

 

45. 输出操作符、下标操作符、赋值运算符、自增自减操作符,算术运算符以及关系运算符中哪些操作符只能用类的成员函数实现,那些只能用类的友元函数实现,哪些即可以用类的成员函数又可以用类的友元函数实现

类的成员函数来实现:下标操作符、赋值运算符(=、[]、()、->)

类的友元函数来实现:输出操作符

类的成员函数和类的友元函数:自增自减操作符、算术运算符、关系运算符

 

 

46. 怎样把函数声明为类的友元函数,友元函数有什么作用

:加一个friend关键字即可。友元函数可以访问类的私有成员。

 

 

 

47.静态成员变量是类的属性还是对象的属性,在内存中的存储情况是什么样的

:类的属性。存储在全局/静态区

 

 

 

48.谈谈你对this指针的理解

:this指针在产生对象的时候初始化,存储的是自身对象的地址。此外,this指针作为类成员函数的一个隐藏参数,通过this指针可以访问到类中的成员变量或者成员函数。

 

 

 

49. 阐述构造函数和析构函数的特点,另外构造函数和析构函数什么时候会被调用,两种函数的作用分别是什么

:构造函数:

1. 函数名和类名一样。

2. 函数没有返回值类型

3. 函数一般声明为public

4. 函数可以带有参数

析构函数:

1. 函数名和类名一样,前面加上~.

2. 必须声明为public

3. 函数没有返回值

4. 函数不能带有参数,程序中只能有一个。

构造函数在对象产生的时候调用,析构函数在对象释放的时候调用。

构造函数主要用来做一些初始化的工作,比如初始化对象的数据成员,打开文件、数据库等。析构函数主要用来做一些对象的收尾工作,比如清理空间,关闭打开的文件、数据库等。

 

 

50. C++中什么时候会调用拷贝构造

:1.用已经定义好的对象来初始化对象时

2.作为函数的参数的时候

3.作为函数返回值,从函数返回的时候

 

 

 

51.C++中,编译器会默认提供的构造函数有哪几种

:只有一种,默认构造函数。(不带参数的构造函数)

 

 

 

52.C++中默认构造函数有几种,详细描述每一种。

:两种:1.不带有任何参数的构造函数。比如 Example();

如果用户没有定义任何构造函数,则编译器会默认提供这个构造函数。

2.带有默认值得构造函数。比如Example(int a = 23);

 

 

 

53. C++中的空类,默认产生哪些类成员函数?

:默认构造函数、析构函数、赋值运算符重载函数、拷贝构造函数

 

 

54. 简述public 、protected、 private三者的区别

: private继承:基类中所有的成员都将无法访问。

protected继承:在派生类中,基类中的public成员和protected成员可以直接访问。在类外则都无法访问。

public继承:在派生类中,基类中的public成员和protected成员可以直接访问。在类外可以直接访问public成员。

 

 

55. 下面的函数统计子字符串substr在字符串str中出现的次数,如果substr在str中不出现,则返回值0。请完成该函数。

int str_count(char *substr, char *str)

{

}

:

//统计子字符串substr在字符串str中出现的次数,index表示从strindex位置开始比较

int isContain(char *substr,char *str,int index)

{

int count = 0;

int length =strlen(substr);

for (int i = 0;i <length; i++)

{

if (*(substr +i) == *(str +index + i))

{

count++;

}

else

break;

}

if (count ==length)

{

return 1;

}

else

{

return 0;

}

}

int str_count(char *substr,char *str)

{

int count = 0;

int length =strlen(str) -strlen(substr);

if(substr ==NULL || str == NULL)

return 0;

for (int i = 0;i <length; i++)

{

if (isContain(substr,str, i) == 1)

{

count++;

i = i+strlen(substr) - 1;

}

}

return count;

}

 

 

 

56. 定义一个字符栈类Stack(包括类的实现)。数据成员包括一个存放字符的数组stack[ ]和一个栈指针tos。栈数组的尺寸由常量SIZE确定。栈的基本操作为push()和pop()。

:

#include <iostream>

using namespace std;

const int SIZE = 100;

class Stack

{

friend ostream& operator<<(ostream&out, const Stack &stack);

public:

Stack();

int isEmpty();

int isFull();

int push(char *str);

int pop(char **str);

private:

char *stack[SIZE];

char tops;//栈顶指针

};

Stack::Stack()

{

tops = -1;

}

int Stack::isEmpty()

{

if (tops == -1)

return 1;

else

return 0;

}

int Stack::isFull()

{

if(tops ==SIZE - 1)

return 1;

else

return 0;

}

int Stack::push(char *str)

{

if (isFull())

{

return 0;

}

stack[++tops] =str;

return 1;

}

int Stack::pop(char **str)

{

if (isEmpty())

{

return 0;

}

*str = stack[tops--];

return 1;

}

ostream& operator<<(ostream &out,const Stack &stack)

{

for (int i = 0;i <= stack.tops;i++)

{

out << stack.stack[i] <<" ";

}

return out;

}

int main()

{

Stack s;

char *str;

s.push("one");

s.push("two");

s.push("three");

s.push("four");

s.push("five");

cout << s  << endl;

 s.pop(&str);

 cout << str << endl;

 

cout << s << endl;

return 0;

}

 

 

57. 解释此函数原型中三个const的作用const int* computeResult( const int& var ) const

:第一个函数返回值的const表示返回的是一个常量指针;参数const说的是var是一个常引用,无法通过var来修改变量的值;第三个const表示computeResult函数是一个常成员函数,无法在函数中修改对象成员变量的值。

 

 

 

58. 写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?

least = MIN(*p++, b);

 

: #define MIN(a, b) (a) > (b) ?(b) : (a)

在预编译期间进行文本替换

least = (*p++)>(b) ? (b) : (*p++);

 

 

 

59. 下面使用常引用的例子合不合法      

int a;

const int& ra = a;

ra = 1;

a = 1;

:不合法。由于ra声明为常引用,故无法通过ra来修改变量a的值。

 

 

 

60. 分析下面程序的运行结果

#include <iostream.h>

class B

{

public:

B(){}

B(int i){b=i;}

virtual void virfun()

{

cout<<"B::virfun() called.\n";

}

private:

int b;

};

class D:public B

{

public:

D(){}

D(int i,int j):B(i){d=j;}

private:

int d;

void virfun()

{

cout<<"D::virfun() called.\n";

}

};

void fun(B *obj)

{

obj->virfun();

}

void main()

{

D *pd=new D;

fun(pd);

}

:满足多态实现的条件,故:

D::virfun() called.

 

 

61. 写出程序结果:

void Func(char str[100])

{                                          

  printf("%d\n", sizeof(str));

}

:32位机器下是4, 64位机器下是8;数组作为参数的时候会退化为指针,此题求指针所占的空间大小。

 

 

 

62. 写出运行结果:

{// test1

    char str[] = "world"; cout << sizeof(str) << ": ";

    char *p    = str;     cout << sizeof(p) << ": ";

    char i     = 10;      cout << sizeof(i) << ": ";

    void *pp   = malloc(10);  cout << sizeof(pp) << endl;

}

:32位机器下:

6 4 1 4

64位机器下:

6 8 1 8

 

 

 

63. 再看看下面的一段程序有什么错误:

swap( int* p1,int* p2 )

{

int *p;

*p = *p1;

*p1 = *p2;

*p2 = *p;

}

:指针p没有进行初始化,指向不明,而后通过*给指针指向的空间赋值,发生错误。(p是野指针)

 

 

 

64. 分析一下这段程序的输出

class B

{

public:

B()

{

cout<<"default constructor"<<endl;

}

~B()

{

cout<<"destructed"<<endl;

}

B(int i):data(i) {

cout<<"constructed by parameter "<< data <<endl;

}

private:

int data;

};

 

B Play( B b)

{

return b ;

}

:题目不全,如果main函数是这样写的话:

B b;

Play(b);

则输出结果为:

default constructor"

destructed

destructed

destructed

 

65. 再看看下面的一段程序是否错误,如有错误 ,应如何改正:

swap( int* p1,int* p2 )

{

  int *p;

*p = *p1;

*p1 = *p2;

*p2 = *p;

}

:

请参考63题

 

 

66. 运行Test函数会有什么样的结果:

void Test( void )

{

char *str = (char *) malloc( 100 );

strcpy( str, "hello" );

free( str );

if(str != NULL)

  {

   strcpy(str,”world”);

   printf(“%s\n”,str);

  }

}

:此题需要注意,free掉堆空间后,str指针保存的地址值没有变,依然保存着堆空间的地址。所以,str不为NULL。此时str成为一个野指针,对野指针进行strcpy操作,程序会崩溃。

 

 

 

67. 运行Test函数会有什么样的结果:

void GetMemory( char **p, int num )

{

*p = (char *) malloc( num );

}

void Test( void )

{

char *str = NULL;

GetMemory( &str, 100 );

strcpy( str, "hello" );

printf( str );

}

:程序输出hello,但是发生内存泄露。

 

 

 

68. 运行Test函数会有什么样的结果:

char *GetMemory( void )

{

char p[] = "hello world";

return p;

}

void Test( void )

{

char *str = NULL;

str = GetMemory();

printf( str );

}

:需要注意,GetMemory函数返回的是局部数组的地址。之后str指向被释放了的局部数组的空间。然后打印str,此时str指向空间的内容不可知,有可能是hello world,也有可能是乱码。

 

 

 

 

69. 运行Test函数会有什么样的结果:

void GetMemory( char *p )

{

p = (char *) malloc( 100 );

}

 

void Test( void )

{

char *str = NULL;

GetMemory( str );

strcpy( str, "hello world" );

printf( str );

 

}

:

GetMemory函数将str赋值给p,之后p指针指向堆上的空间。注意,函数传递参数是单向的,也就是说只能由实参传递给形参,之后形参、实参再无关系。故str的值依然为NULL。为空指针调用strcpy函数,程序崩溃。

 

 

 

70. 分析test3函数 指出其中问题:

void test3(char* str1)

{

char string[10];

if( strlen( str1 ) <= 10 )

{

strcpy( string, str1 );

}

}

:

由于数组string共有10个空间,而strlen函数返回的值不包括\0,故有可能会溢出。应该改成if( strlen( str1 ) < 10 )

 

 

 

71. 对test2函数进行分析:

void test2()

{

char string[10], str1[10];

int i;

for(i=0; i<10; i++)

{

str1[i] = 'a';

}

strcpy( string, str1 );

}

:数组str1共有10个元素,通过for循环将所有的元素都设置为’a’字符。调用strcpy函数将str1里面的内容拷贝到string数组中。由于在数组str1中不存在\0,故拷贝的时候,会发生溢出。

 

72. 找错题

运行test1函数会有什么结果:

void test1()

{

char string[10];

char* str1 = "0123456789";

strcpy( string, str1 );

}

:此题与上一道题类似。数组string共有10个元素,而str1指向的字符串共有11个字符(末尾的\0),故执行strcpy的时候会发生溢出。

 

 

 

73. 101个硬币100真、1假,真假区别在于重量。请用无砝码天平称两次给出真币重还是假币重的结论。

:

一,称俩堆50个,如果重量相等。

则说明假币是那单独的一个

       随便取一个真币和这个假币做比较,即可得出哪个硬币更重

 

如果重量不相等,说明剩下那个硬币是真的,然后俩堆50个里面有一个必然是假的,同时天平不平衡。

用一个真币跟这俩堆比较显然得不出结果

另外一个思路,如果分出假币在哪一堆,又由于假币只有一个,则可以得出是假币重还是真币重

所以可以,任取一堆,分成俩份25个,称一次,

如果重量相等,那么假币在另外一堆

否则假币在原先的50个一堆里

然后再结合先前一次称的时候俩堆50个币的轻重大小即可得知是假币重还是真币重

 

74. 程序改错

class mml

{

  private:

    static unsigned int x;

  public:

    mml(){ x++; }

    mml(static unsigned int &) {x++;}

    ~mml{x--;}

  pulic:

    virtual mon() {} = 0;

    static unsigned int mmc(){return x;}

    ......                      

 

};

class nnl:public mml

{

  private:

    static unsigned int y;

  public:

    nnl(){ x++; }

    nnl(static unsigned int &) {x++;}

    ~nnl{x--;}

  public:

    virtual mon() {};

     static unsigned int nnc(){return y;}

    ......                   

};

 

代码片断:

mml* pp = new nnl;

..........

delete pp;

:将pp的析构函数设置为虚函数。同时mml和nnl类中带有参数的构造函数需要给出参数名。析构函数需要加();

 

 

 

75. 请指出下列程序中的错误并且修改

void GetMemory(char *p){

  p=(char *)malloc(100);

}

void Test(void){

  char *str=NULL;

  GetMemory=(str);

  strcpy(str,"hello world");

  printf(str);

}

:

已经做过。参考上面

 

 

76. 输出下面程序结果。

#include <iostream.h>

class A

{

public:

 virtual void print(void)

 {

    cout<<"A::print()"<<endl;

 }

};

class B:public A

{

public:

 virtual void print(void)

 {

   cout<<"B::print()"<<endl;

 };

};

class C:public B

{

public:

 virtual void print(void)

 {

  cout<<"C::print()"<<endl;

 }

};

void print(A a)

{

   a.print();

}

void main(void)

{

   A a, *pa,*pb,*pc;

   B b;

   C c;

   

   pa=&a;

   pb=&b;

   pc=&c;

   

   a.print();

   b.print();

   c.print();

   

   pa->print();

   pb->print();

   pc->print();

   

   print(a);

   print(b);

   print(c);

}

:

A::print()

B::print();

C::print();

A::print()

B::print();

C::print();

A::print()

A::print()

A::print()

 

77. 下面哪个类中的哪几个函数是虚函数

class A

{

  virtual void func1();

  void func2();

}

Class B: class A

{

  void func1(){cout << "fun1 in class B" << endl;}

  virtual void func2(){cout << "fun2 in class B" << endl;}

}

:

A中的func1函数

B中的fun1函数、func2函数

 

 

78. int id[sizeof(unsigned long)];

这个对吗?为什么??

:对,sizeof在编译的时候执行。

 

 

 

79. unsigned short array[]={1,2,3,4,5,6,7};

int i = 3;

*(array + i) = ?

:4

 

 

 

80. i最后等于多少?

int i = 1;

int j = i++;

if((i>j++) && (i++ == j)) i+=j;

:5

 

 

 

81. union a {

 int a_int1;

 double a_double;

 int a_int2;

};

typedef struct

{

 a a1;

 char y;

} b;

class c

{

 double c_double;

 b b1;

 a a2;

};

 

输出cout<<sizeof(c)<<endl;的结果?

:

32

 

 

82. 下面的代码有什么问题?

class A

{

public:

  A() { p=this; }

  ~A() { if(p!=NULL) { delete p; p=NULL; } }

  A* p;

};

:构造函数中this指针代表的是自身对象的地址,故不为空。析构函数中判断p是否为空,然后删除p指向的空间,将p置为NULL;

如果申请的对象在栈上,则程序出错。

 

 

83. 用C 写一个逆序输出输入的数字的函数,要求用递归方法。比如输入1234,则输出4321 ;

:

#include <stdio.h>

#include <string.h>

void reverseNumber(char *array,int length)

{

if (length > 0)

{

printf("%c",array[length - 1]);

reverseNumber(array,length-1);

}

}

int main()

{

char str[100];

printf("请输入一串数字:");

scanf("%s",str);

reverseNumber(str,strlen(str));

return 0;

}

 

 

84. 不用库函数 自己实现strlen函数

:

int strlen(char *str)

{

int count = 0;

if (str ==NULL)

{

throw "str is NULL";

}

while (str++ !='\0')

{

count++;

}

return count;

}

 

 

85. 已知strcpy的函数原型:char *strcpy(char *strDest, const char *strSrc)其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数,请编写函数 strcpy。

: char *strcpy(char *strDest,const char *strSrc)

{

char *str =strDest;

if (strDest ==NULL || strSrc == NULL)

{

throw "NULL str";

}

while (*strDest++ = *strSrc++ !='\0')

return str;

}

 

 

 

86. 简述map 和 multimap的区别,那list 和 vector的区别呢

:map是单纯的一对一映射,key不允许重复;multimap可以是一对多映射,key允许重复;

list容器顺序访问每个元素,可以在任何位置添加或者删除元素;vector容器是随机访问元素,一般在末尾添加、删除元素。

 

 

 

87. 说出以下三种函数中分别可以抛出什么异常

void f();

void f() throw()

int func( int x ) throw( int, Error_message)

:可以抛出任何类型异常

不能抛出任何异常

只能抛出int类型或者Error_message类型异常

 

 

88. 写一个函数返回1+2+3+…+n的值(假定结果不会超过长整型变量的范围)

: int sumNumber(int n)

{

int sum = 0;

for(int i = 1;i <= n;i++)

{

sum += i;

}

return sum;

}

 

89. 在函数中传引用比传指针安全吗?为什么?如果使用常量指针不行吗?

: (1) 引用在创建的同时必须初始化,即引用到一个有效的对象;而指针在定义的时候不必初始化,可以在定义后面的任何地方重新赋值.

(2) 不存在NULL引用,引用必须与合法的存储单元关联;而指针则可以是NULL.

(3) 引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用;而指针在任何时候都可以改变为指向另一个对象.给引用赋值并不是改变它和原始对象的绑定关系.

(4) 引用的创建和销毁并不会调用类的拷贝构造函数

(5) 语言层面,引用的用法和对象一样;在二进制层面,引用一般都是通过指针来实现的,只不过编译器帮我们完成了转换.

不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,显得很安全。

const 指针仍然存在空指针,并且有可能产生野指针.

总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性.

 

 

 

90. 已知String类声明如下,对其进行实现:

class String

{

public:

String(const char *str = NULL); // 通用构造函数

String(const String &another); // 拷贝构造函数

~String(); // 析构函数

String& operater =(const String &rhs); // 赋值函数

private:

char* m_data; // 用于保存字符串

};

尝试写出类的成员函数实现。

: 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)

{

m_data = (char *)malloc(1);

*m_data = '\0';

}

else

{

m_data = (char *)malloc(strlen(str) + 1);

strcpy(m_data,str);

}

}

String::String(const String &another)

{

m_data = (char *)malloc(strlen(another.m_data));

strcpy(m_data,another.m_data);

}

String::~String()

{

delete [] m_data;

}

String& String::operater=(const String &rhs)

{

if(this != &rhs)

{

delete [] m_data;

m_data = (char *)malloc(strlen(rhs.m_data) + 1);

strcpy(rhs.m_data,m_data);

}

return *this;

}

 

 

91. 实现冒泡排序

: 

void buddleSort(int *array,int length)

{

for(int i = 0;i < length - 1;i++)

for (int j = 0;j < length -i - 1; j++)

{

if(array[j] >array[j+1])

{

int temp =array[j];

array[j] =array[j+1];

array[j+1] =temp;

}

}

}

 

92. 谈谈你对冒泡排序、插入排序、选择排序和快速排序的理解

:冒泡排序:将序列划分为无序和有序区,不断通过交换较大元素至无序区尾完成排序。

插入排序:将数组分为无序区和有序区两个区,然后不断将无序区的第一个元素按大小顺序插入到有序区中区,最终将所有无序区元素都移动到有序区完成排序。

选择排序:将序列划分为无序和有序区,寻找无序区中的最小值和无序区的首元素交换,有序区扩大一个,循环最终完成全部排序。 

快速排序:不断寻找一个序列的中点,然后对中点左右的序列递归的进行排序,直到全部序列排序完成。

 

 

93. 已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序,要求用递归方法进行。

:

struct Node *mergeLink(struct Node *head1,struct Node *head2)

{

struct Node *p =NULL;

if (head1 ==NULL && head2 == NULL)

 return NULL;

if(head1 !=NULL && head1->data < head2->data)

{

p = head1;

p->next =mergeLink(head1->next,head2);

}

else if(head2 !=NULL)

{

p = head2;

p->next =mergeLink(head1,head2->next);

}

return p;

}

 

 

94. 已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序。(保留所有结点,即便大小相同)

:

struct Node *mergeLink(struct Node *head1,struct Node *head2)

{

struct Node *head = (struct Node *)malloc(sizeof(struct Node));

head->next =NULL;

head->data = 0;

struct Node *p =head;

if (head1 ==NULL || head2 == NULL)

{

free(head);

throw "link is NULL";

}

while (head1 !=NULL || head2 != NULL)

{

if (head1 !=NULL && head1->data < head2->data)

{

p->next =head1;

head1 = head1->next;

}

else if(head2 != NULL )

{

p->next =head2;

head2 = head2->next;

}

p = p->next;

}

p = head->next;

free(head);

return p;

}

 

 

95. 链表题:一个链表的结点结构

struct Node

{

int data ;

Node *next ;

};

typedef struct Node Node ;

已知链表的头结点head,写一个函数把这个链表逆序

答:

Node *reverse(Node *head) //链表逆序

{

Node *p = head;

Node *q = head;

Node *m = head;

if(head == NULL)

{

printf("逆序链表:这是一个空链表\n");

return NULL;

}

q = p->next;

while(q != NULL) //保持位置pqm

{

m = q->next;

q->next = p;

 

p = q;

q = m;

}

head->next = NULL;

return p;

}

 

0 0
原创粉丝点击