①链表反转 

单向链表的反转是一个经常被问到的一个面试题,也是一个非常基础的问题。比

如一个链表是这样的: 1->2->3->4->5 通过反转后成为 5->4->3->2->1。 

最容易想到的方法遍历一遍链表,利用一个辅助指针,存储遍历过程中当前指针

指向的下一个元素,然后将当前节点元素的指针反转后,利用已经存储的指针往

后面继续遍历。源代码如下:

1. struct linka {

2. int data;

3. linka* next;

4. };

5. void reverse(linka*&head) {

6. if(head ==NULL)

7.return;

8. linka *pre, *cur,*ne;

9. pre=head;

10.cur=head->next;

11.while(cur)

12.{

13. ne =cur->next;

14. cur->next =pre;

15. pre = cur;

16. cur = ne;

17.}

18.head->next =NULL;

19.head = pre;

20.} 

还有一种利用递归的方法。这种方法的基本思想是在反转当前节点之前先调用递

归函数反转后续节点。源代 码如下。不过这个方法有一个缺点,就是在反转后

的最后一个结点会形成一个环,所以必须将函数的返回的节点的 next 域置为

NULL。因为要改变 head 指 针,所以我用了引用。算法的源代码如下:

linka* reverse(linka* p,linka*& head)

{

if(p == NULL || p->next == NULL)

{

head=p;

return p;

}

else

{

linka* tmp = reverse(p->next,head);

tmp->next = p;

return p;

}

}

已知 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(constchar *str)

{

if ( str == NULL )//strlen 在参数为 NULL时会抛异常才会有这步判断

{

m_data = newchar[1] ;

m_data[0] = '\0' ;

}

else

{

m_data = newchar[strlen(str) + 1];

strcpy(m_data,str);

}

}

String::String(constString &another)

{

m_data = newchar[strlen(another.m_data) + 1];

strcpy(m_data,another.m_data);

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

{

if ( this ==&rhs)

return *this ;

delete []m_data; //删除原来的数据,新开一块内存

m_data = newchar[strlen(rhs.m_data) + 1];

strcpy(m_data,rhs.m_data);

return *this ;

}

String::~String()

{

delete []m_data ;

}

③网上流传的 c++笔试题汇总

1.求下面函数的返回值(微软)

int func(x)

{

int countx = 0;

while(x)

{

countx ++;

x = x&(x-1);

}

return countx;

}

假定 x = 9999。答案:8

思路:将 x转化为 2进制,看含有的 1的个数。

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

答:引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操

作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕

后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用

名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该

引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不

占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。

3. 引用作为函数参数有哪些特点?

(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成

为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形

参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对

实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分

配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝

构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效

率和所占空间都好。

(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调

函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行

运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用

点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

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

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改

变,就应使用常引用。常引用声明方式:const类型标识符&引用名=目标变量

名;

例 1

int a ;

const int&ra=a;

ra=1; //错误

a=1; //正确

例 2

string foo( );

void bar(string& s);

那么下面的表达式将是非法的:

bar(foo( ))

bar("helloworld")

原因在于 foo( )和"helloworld"串都会产生一个临时对象,而在 C++中,这些

临时对象都是 const类型的。因此上面的表达式就是试图将一个 const类型的对

象转换为非 const类型,这是非法的。

引用型参数应该在能被定义为 const的情况下,尽量定义为const。

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

格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }

好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回

一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引

用也会失效,产生 runtime error!

注意事项:

(1)不能返回局部变量的引用。这条可以参照Effective C++[1]的 Item 31。

主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所

指"的引用,程序会进入未知状态。

(2)不能返回函数内部 new分配的内存的引用。这条可以参照Effective C++[1]

的 Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数

内部 new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只

是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指

向的空间(由 new分配)就无法释放,造成memory leak。

(3)可以返回类成员的引用,但最好是const。这条原则可以参照 Effective

C++[1]的 Item 30。主要原因是当对象的属性是与某种业务规则(business rule)

相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将

赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引

用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

(4)流操作符重载返回值申明为“引用”的作用:

流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" <<endl;

因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引

用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返

回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续

的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流

指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯

一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是 C++语言

中引入引用这个概念的原因吧。赋值操作符=。这个操作符象流操作符一样,是

可以连续使用的,例如:x= j = 10;或者(x=10)=100;赋值操作符的返回值必须

是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。

 例 3

 #i nclude

int &put(intn);

int vals[10];

int error=-1;

void main()

{

put(0)=10; //以 put(0)函数值作为左值,等价于vals[0]=10;

put(9)=20; //以 put(9)函数值作为左值,等价于vals[9]=20;

cout<cout<}

int &put(int n)

{

if (n>=0&& n<=9 ) return vals[n];

else {cout<<"subscript error"; return error; }

}

(5)在另外的一些操作符中,却千万不能返回引用:+-*/四则运算符。它们不

能返回引用,Effective C++[1]的 Item23详细的讨论了这个问题。主要原因是

这四个操作符没有 side effect,因此,它们必须构造一个对象作为返回值,可

选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个 new分配的

对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个

规则,第 2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))

会永远为 true而导致错误。所以可选的只剩下返回一个对象了。

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

引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可

以指向它的派生类实例。

例 4

Class A; Class B :Class A{...}; B b; A& ref = b;

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

 指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使

用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是

对目标变量的操作。此外,就是上面提到的对函数传 ref 和pointer的区别。

8.什么时候需要“引用”?

流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=

的参数、其它情况都推荐使用引用。

以上 2-8参考:

javascript:void(0)

9.结构与联合有和区别?

1. 结构和联合都是由多个不同的数据类型成员组成,但在任何同一时刻,联合

中只存放了一个被选中的成员(所有成员共用一块地址空间),而结构的所有成员都存在(不同成员的存放地址不同)。

2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了,

而对于结构的不同成员赋值是互不影响的。

10.下面关于“联合”的题目的输出?

a)

#i nclude

union

{

int i;

char x[2];

}a;

void main()

{

a.x[0] = 10;

a.x[1] = 1;

printf("%d",a.i);

}

答案:266 (低位低地址,高位高地址,内存占用情况是 Ox010A)

b)

main()

{

union{ /*定义一个联合*/

int i;

struct{ /*在联合中定义一个结构*/

char first;

char second;

}half;

}number;

number.i=0x4241; /*联合成员赋值*/

printf("%c%c\n",number.half.first, mumber.half.second);

number.half.first='a';/*联合中结构成员赋值*/

number.half.second='b';

printf("%x\n",number.i);

getch();

}

答案: AB (0x41对应'A',是低位;Ox42对应'B',是高位)

6261 (number.i 和 number.half共用一块地址空间)

11. 已知 strcpy的函数原型:char *strcpy(char *strDest, const

 

char *strSrc)其中 strDest是目的字符串,strSrc是源字符串。不

 

调用C++/C的字符串库函数,请编写函数 strcpy。

答案:

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

{

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

return NULL ;

if ( strDest ==strSrc)

return strDest ;

char *tempptr =strDest ;

while( (*strDest++= *strSrc++) != ‘\0’)

return tempptr ;

}

12. 已知 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(constchar *str)

{

if ( str == NULL )//strlen 在参数为 NULL时会抛异常才会有这步判断

{

m_data = newchar[1] ;

m_data[0] = '\0' ;

}

else

{

m_data = newchar[strlen(str) + 1];

strcpy(m_data,str);

}

 

}

 

String::String(constString &another)

{

m_data = newchar[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 = newchar[strlen(rhs.m_data) + 1];

strcpy(m_data,rhs.m_data);

return *this ;

}

String::~String()

{

delete []m_data ;

}

13. .h 头文件中的 ifndef/define/endif的作用? 

答:防止该头文件被重复引用。

14. #i nclude与#i nclude "file.h"的区别?

答:前者是从Standard Library的路径寻找和引用 file.h,而后者是从当前工

作路径搜寻并引用 file.h。

 15.在 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++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户

所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编

译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名

字,这个名字与用户程序中同名的全局变量名字不同。

未加 extern "C"声明时的连接方式

假设在 C++中,模块 A的头文件如下:

//模块A头文件

moduleA.h


#ifndef MODULE_A_H

#define MODULE_A_H

int foo( int x, inty );

#endif

在模块 B中引用该函数:

//模块B实现文件

moduleB.cpp

#i nclude "moduleA.h"

foo(2,3);

实际上,在连接阶段,连接器会从模块 A生成的目标文件moduleA.obj中寻找

_foo_int_int 这样的符号!

加 extern "C"声明后的编译和连接方式

加 extern "C"声明后,模块 A的头文件变为:

//模块A头文件

moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

extern"C" int foo( int x, int y );

#endif

在模块 B的实现文件中仍然调用foo( 2,3 ),其结果是:

(1)模块 A编译生成 foo的目标代码时,没有对其名字进行特殊处理,采用了C 语言的方式;

(2)连接器在为模块 B的目标代码寻找 foo(2,3)调用时,寻找的是未经修改的符号名_foo。

 

如果在模块 A中函数声明了 foo为 extern "C"类型,而模块 B中包含的是extern

int foo( int x, inty ),则模块 B找不到模块 A中的函数;反之亦然。

 

所以,可以用一句话概括 extern “C”这个声明的真实目的(任何语言中的任

何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考

问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,

动机是什么,这样我们可以更深入地理解许多问题):实现 C++与 C及其它语言

的混合编程。

明白了 C++中extern "C"的设立动机,我们下面来具体分析 extern "C"通常的

使用技巧:

extern"C"的惯用法

(1)在 C++中引用 C语言中的函数和变量,在包含 C语言头文件(假设为

cExample.h)时,需进行下列处理:

extern"C"

{

#i nclude "cExample.h"

}

而在 C语言的头文件中,对其外部函数只能指定为 extern类型,C语言中不支

持 extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

C++引用 C函数例子工程中包含的三个文件的源代码如下:

/* c 语言头文件:cExample.h */

#ifndef C_EXAMPLE_H

#define C_EXAMPLE_H

extern int add(intx,int y);

#endif

/* c 语言实现文件:cExample.c */

#i nclude "cExample.h"

int add( int x, inty )

{

return x + y;

}

// c++实现文件,调用 add:cppFile.cpp

extern"C"

{

#i nclude "cExample.h"

}

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

{

add(2,3);

return 0;

}

如果 C++调用一个 C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数

时,应加extern "C" {

}。

(2)在 C中引用 C++语言中的函数和变量时,C++的头文件需添加 extern "C",

但是在 C语言中不能直接引用声明了 extern "C"的该头文件,应该仅将 C文件

中将 C++中定义的extern "C"函数声明为extern类型。

C 引用 C++函数例子工程中包含的三个文件的源代码如下:

//C++头文件 cppExample.h

#ifndefCPP_EXAMPLE_H

#defineCPP_EXAMPLE_H

extern"C" int add( int x, int y );

#endif

 

 

 

//C++实现文件 cppExample.cpp

#i nclude "cppExample.h"

int add( int x, inty )

{

return x + y;

}

/* C 实现文件 cFile.c

/* 这样会编译出错:#i nclude "cExample.h" */

extern int add( intx, int y );

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

{

add( 2, 3 );

return 0;

}

15 题目的解答请参考《C++中extern “C”含义深层探索》注解:

16.关联、聚合(Aggregation)以及组合(Composition)的区别?

涉及到 UML中的一些概念:关联是表示两个类的一般性联系,比如“学生”和

“老师”就是一种关联关系;聚合表示 has-a的关系,是一种相对松散的关系,

聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系:

从实现的角度讲,聚合可以表示为:

 

class A {...} classB { A* a; .....}

 

而组合表示 contains-a的关系,关联性强于聚合:组合类与被组合类有相同的

生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系:

 

实现的形式是:

 

class A{...} classB{ A a; ...}

 

参考文章:javascript:void(0)

 

javascript:void(0)

 

 

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

 

 

1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行

protection(private,protected,public)

 

2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而

无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接

口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后

一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。

 

3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之

后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的

说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

18.重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?

 常考的题目。从定义上来说:

重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不

同,或许参数类型不同,或许两者都不同)。

重写:是指子类重新定义父类虚函数的方法。

从实现原理上来说:

重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名

函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:

functionfunc(p:integer):integer;和function func(p:string):integer;。

那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这

两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地

址在编译期就绑定了(早绑定),因此,重载和多态无关!

重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给

它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期

间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地

址是在运行期绑定的(晚绑定)。

19.多态的作用?

 主要是两个:1.隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代

码重用;2.接口重用:为了类在继承和派生的时候,保证使用家族中任一类的

实例的某一属性时的正确调用。

20. Ado与 Ado.net的相同与不同?

除了“能够让应用程序处理存储于 DBMS中的数据“这一基本相似点外,两者没有太多共同

之处。但是 Ado使用 OLEDB接口并基于微软的 COM技术,而ADO.NET拥有自己的

ADO.NET接口并且基于微软的.NET体系架构。众所周知.NET体系不同于 COM体系,

ADO.NET接口也就完全不同于 ADO和 OLEDB接口,这也就是说 ADO.NET和 ADO是

两种数据访问方式。ADO.net提供对 XML的支持。

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

答案:都是在堆(heap)上进行动态的内存操作。用 malloc函数需要指定内存分配的字节数并

且不能初始化对象,new会自动调用对象的构造函数。delete会调用对象的 destructor,而

free不会调用对象的destructor.

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

答案:i为 30。

23.有哪几种情况只能用 intialization list而不能用 assignment?

答案:当类中含有 const、reference成员变量;基类的构造函数都需要初始化表。

24. C++是不是类型安全的?

答案:不是。两个不同类型的指针之间可以强制转换(用 reinterpret cast)。C#是类型安全的。

25. main函数执行以前,还会执行什么代码?

答案:全局对象的构造函数会在 main函数之前执行。

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

1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运

行期间都存在。例如全局变量,static变量。

2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行

结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。

3)从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc或 new申请任意多少的

内存,程序员自己负责在何时用 free或delete释放内存。动态内存的生存期由程序员决定,

使用非常灵活,但问题也最多。

27.struct和 class的区别

 答案:struct的成员默认是公有的,而类的成员默认是私有的。struct和 class在其他方面

是功能相当的。

 从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结构仅仅象一堆缺乏封装

和功能的开放的内存位,而类就象活的并且可靠的社会成员,它有智能服务,有牢固的封

装屏障和一个良好定义的接口。既然大多数人都这么认为,那么只有在你的类有很少的方法

并且有公有数据(这种事情在良好设计的系统中是存在的!)时,你也许应该使用 struct关

键字,否则,你应该使用 class关键字。

28.当一个类 A中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是

零,请解释一下编译器为什么没有让它为零。(Autodesk)

答案:肯定不是零。举个反例,如果是零的话,声明一个 class A[10]对象数组,而每一个对

象占用的空间是零,这时就没办法区分 A[0],A[1]…了。

29.在 8086汇编下,逻辑地址和物理地址是怎样转换的?(Intel)

答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器内地

址,就得到了真正要访问的地址。

30.比较 C++中的 4种类型转换方式?

,重点是 static_cast,

dynamic_cast和reinterpret_cast的区别和应用。

31.分别写出 BOOL,int,float,指针类型的变量 a与“零”的比较语句。

答案:

BOOL : if ( !a ) or if(a)

int : if ( a == 0)

float : const EXPRESSION EXP = 0.000001

if ( a < EXP && a >-EXP)

pointer : if ( a != NULL) or if(a == NULL)

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

答案:1)const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安

全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不

到的错误。

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

 

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

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意

类型的内存块。

(1)修改内容上的差别

char a[] = “hello”;

a[0] = ‘X’;

char *p = “world”; //注意 p指向常量字符串

p[0] = ‘X’; //编译器不能发现该错误,运行时错误

(2)用运算符sizeof可以计算出数组的容量(字节数)。sizeof(p),p为指针得到的是一个指

针变量的字节数,而不是 p所指的内存容量。C++/C语言没有办法知道指针所指的内存容

量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为

同类型的指针。

char a[] = "hello world";

char *p = a;

cout<< sizeof(a) << endl; // 12字节

cout<< sizeof(p) << endl; // 4字节

计算数组和指针的内存容量

void Func(char a[100])

{

cout<< sizeof(a) << endl; // 4字节而不是 100字节

}

34.类成员函数的重载、覆盖和隐藏区别?

答案:

a.成员函数被重载的特征:

(1)相同的范围(在同一个类中);

(2)函数名字相同;

(3)参数不同;

(4)virtual关键字可有可无。

b.覆盖是指派生类函数覆盖基类函数,特征是:

(1)不同的范围(分别位于派生类与基类);

(2)函数名字相同;

(3)参数相同;

(4)基类函数必须有virtual关键字。

c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual关键字,

基类的函数将被隐藏(注意别与重载混淆)。

(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual关

键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

35. There are twoint variables: a and b, don’t use “if”, “? :”, “switch”or other judgement

statements, findout the biggest one of the two numbers.

答案:( ( a + b ) + abs( a - b ) ) / 2

36.如何打印出当前源文件的文件名以及源文件的当前行号?

答案:

cout << __FILE__ ;

cout<<__LINE__;

__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器

定义的。

37. main主函数执行完毕后,是否可能会再执行一段代码,给出说明?

答案:可以,可以用_onexit注册一个函数,它会在 main之后执行 intfn1(void), fn2(void),

fn3(void), fn4 (void);

void main( void )

{

String str("zhanglin");

_onexit( fn1 );

_onexit( fn2 );

_onexit( fn3 );

_onexit( fn4 )

printf( "This is executedfirst.\n" );

}

int fn1()

{

printf( "next.\n" );

return 0;

}

int fn2()

{

printf( "executed " );

return 0;

}

int fn3()

{

printf( "is " );

return 0;

}

int fn4()

{

printf( "This " );

return 0;

}

The _onexit function is passed the addressof a function (func) to be called when the program

terminates normally. Successive calls to_onexit create a register of functions that are executed in

LIFO (last-in-first-out) order. Thefunctions passed to _onexit cannot take parameters.

 

38.如何判断一段程序是由 C编译程序还是由 C++编译程序编译的?

答案:

#ifdef __cplusplus

cout<<"c++";

#else

cout<<"c";

#endif

 

39.文件中有一组整数,要求排序后输出到另一个文件中

答案:

#i nclude

#i nclude

using namespace std;

void Order(vector& data) //bubble sort

{

int count = data.size() ;

int tag = false ; //设置是否需要继续冒泡的标志位

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

{

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

{

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

{

tag = true ;

int temp = data[j] ;

data[j] = data[j+1] ;

data[j+1] = temp ;

}

}

if ( !tag )

break ;

}

}

void main( void )

{

vectordata;

ifstream in("c:\\data.txt");

if ( !in)

{

cout<<"file error!";

exit(1);

}

int temp;

while (!in.eof())

{

in>>temp;

data.push_back(temp);

}

in.close(); //关闭输入文件流

Order(data);

ofstream out("c:\\result.txt");

if ( !out)

{

cout<<"file error!";

exit(1);

}

for ( i = 0 ; i < data.size() ; i++)

out<40.链表题:一个链表的结点结构

struct Node

{

int data ;

Node *next ;

};

typedef struct Node Node ;

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

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

{

if ( head == NULL || head->next == NULL )

return head;

Node *p1 = head ;

Node *p2 = p1->next ;

Node *p3 = p2->next ;

p1->next = NULL ;

while ( p3 != NULL )

{

p2->next = p1 ;

p1 = p2 ;

p2 = p3 ;

p3 = p3->next ;

}

p2->next = p1 ;

head = p2 ;

return head ;

}

(2)已知两个链表 head1 和 head2 各自有序,请把它们合并成一个链表依然有序。(保留所有

结点,即便大小相同)

Node * Merge(Node *head1 , Node *head2)

{

if ( head1 == NULL)

return head2 ;

if ( head2 == NULL)

return head1 ;

Node *head = NULL ;

Node *p1 = NULL;

Node *p2 = NULL;

if ( head1->data < head2->data)

{

head = head1 ;

p1 = head1->next;

p2 = head2 ;

}

else

{

head = head2 ;

p2 = head2->next ;

p1 = head1 ;

}

Node *pcurrent = head ;

while ( p1 != NULL && p2 != NULL)

{

if ( p1->data <= p2->data )

{

pcurrent->next = p1 ;

pcurrent = p1 ;

p1 = p1->next ;

}

else

{

pcurrent->next = p2 ;

pcurrent = p2 ;

p2 = p2->next ;

}

}

if ( p1 != NULL )

pcurrent->next = p1 ;

if ( p2 != NULL )

pcurrent->next = p2 ;

return head ;

}

(3)已知两个链表 head1 和 head2 各自有序,请把它们合并成一个链表依然有序,这次要求

用递归方法进行。 (Autodesk)

答案:

Node * MergeRecursive(Node *head1 , Node*head2)

{

if ( head1 == NULL )

return head2 ;

if ( head2 == NULL)

return head1 ;

Node *head = NULL ;

if ( head1->data < head2->data )

{

head = head1 ;

head->next =MergeRecursive(head1->next,head2);

}

else

{

head = head2 ;

head->next =MergeRecursive(head1,head2->next);

}

return head ;

}

41.分析一下这段程序的输出 (Autodesk)

class B

{

public:

B()

{

cout<<"defaultconstructor"<}

~B()

{

cout<<"destructed"<}

B(int i):data(i) //B(int) works as aconverter ( int -> instance of B)

{

cout<<"constructed by parameter" << data <}

private:

int data;

};

B Play( B b)

{

return b ;

}

(1) results:

int main(int argc, char* argv[]) constructedby parameter 5

{ destructed B(5)形参析构

B t1 = Play(5); B t2 = Play(t1);

destructed t1 形参析构


return 0;

destructed t2

注意顺序!


} destructed t1

 

 

(2) results:

int main(int argc, char* argv[]) constructedby parameter 5

{ destructed B(5)形参析构


B t1 = Play(5); B t2 = Play(10);

return 0;

constructed by parameter 10

destructed B(10)形参析构


} destructed t2

注意顺序!

destructed t1

42.写一个函数找出一个整数数组中,第二大的数(microsoft)

答案:

const int MINNUMBER = -32767 ;

int find_sec_max( int data[] , int count)

{

int maxnumber = data[0] ;

int sec_max = MINNUMBER ;

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

{

if ( data[i] > maxnumber )

{

sec_max = maxnumber ;

maxnumber = data[i] ;

}

else

{

if ( data[i] > sec_max )

sec_max = data[i] ;

}

}

return sec_max ;

}

 

43.写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。

 

KMP 算法效率最好,时间复杂度是O(n+m)。

 

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

比如有 class A : public class B, public class C {}

那么 A 的内存结构大致是怎么样的?

 

这个是 compiler-dependent 的, 不同的实现其细节可能不同。

如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。

可以参考《深入探索 C++对象模型》,或者:

javascript:void(0)

 

45.如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针)

struct node { char val; node* next;}

bool check(const node* head) {} //returnfalse : 无环;true: 有环

一种 O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环

的话两者必然重合,反之亦然):

bool check(const node* head)

{

if(head==NULL) return false;

node *low=head, *fast=head->next;

while(fast!=NULL && fast->next!=NULL)

{

low=low->next;

fast=fast->next->next;

if(low==fast) return true;

}

return false;

}