Skip to content

C、C++面试题(整理上)

Published: at 12:49 PM | 26 min read

new、delete、malloc、free关系

答:new和delete,malloc和free都可以用来申请动态内存和释放内存。malloc和free是C/C++语言的标准库函数,new和delete是C++的运算符。new和delete在分配内存时会执行构造函数,delete在释放内存时会执行析构函数。

delete与delete []区别

答:delete只会调用一次析构函数,而delete[]会调用每个成员的析构函数。在More Effective C++中这样解释的:“当delete 操作符用于数组时,它为每个数组元素调用析构函数,然后调用operator delete来释放内存”。对于内建简单数据类型虽然没有析构函数delete和delete[]功能类似,但是为了安全以及程序的可读性,应该new对应delete,new[]对应delete[]。

C/C++ JAVA .NET区别

C/C++ JAVA .NET区别

继承、组合优缺点

答:优先使用组合。 继承、组合优缺点

C++有哪些特质(面向对象特点)

答:数据抽象和封装、继承、多态(关键);

数据抽象是一种依赖于接口和实现分离的编程技术。类设计者必须关心类是如何实现的,但使用该类的程序员仅需了解类型的接口,而不必具体地考虑该类型如何工作。

封装也就是信息隐藏,通过封装对外界隐藏了对象的实现细节。

继承是子类自动地共享基类中定义的数据和方法的机制。继承性使得用户在开发新的应用系统时不必完全从零开始,可以继承原有的相似系统的功能或者从类库中选取需要的类,再派生出新的类以实现所需要的功能。还可以用把已有的一般性的解加以具体化的办法,来达到软件重用的目地。

子类析构时要调用父类的析构函数吗

答:析构函数与构造函数的顺序相反。构造时,先调用父类的构造函数后调用派生类的构造函数;析构时,先析构子类后析构父类。

多态,虚函数,纯虚函数

答:http://blog.csdn.net/tujiaw/article/details/6753498

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

int Func(int x)
{
	int count = 0;
	while (x)
	{
		count++;
		x = x & (x - 1);
	}
	return count;
}

答:将x转换为二进制,返回值为二进制数中1的个数。

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

答:“引用”就是某个目标变量的“别名”(alias),对“引用”的操作与对变量的操作效果完全相同。

声明一个“引用”的时候切记要对其进行初始化。“引用”声明完毕后相当于目标变量有两个名称,即该目标原名和引用名。不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。

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

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

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

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

答:如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应该使用常引用,如下声明方式:

例1
int x;
const int &y = x;
y = 1; // error
x = 1; // ok
例2
string Foo();
void Bar(string &s);
调用时:
Bar(Foo()); // error
Bar("hello,world"); // error

这两个都是错误的,原因在于Foo和”hello,world”串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此试图将一个const类型的对象转换为非const类型这是非法的。所以,引用型参数应该在能被定义为const的情况下尽量定义为const。

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

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

好处:在内存中不产生函数返回值的副本,正是由于这点所以有以下注意事项:

流操作符<<和>>,常常被希望用作连续输入和输出,如:cout << “hello” << endl;因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。其他的可选方案包括:返回一个流对象和返回一个流对象的指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是唯一选择。

赋值操作符=可以连续使用,如:x = y = 10;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的唯一返回值选择。

例3
int val[10];
int error = -1;
int& Put(int i)
{
	if (i >= 0 && i <= 9)
	{
		return val[i];
	}
	else
	{
		return error;
	}
}
int main(char argc,char *argv)
{
	Put(0) = 10;
	Put(3) = 30;

	cout << val[0] << endl;
	cout << val[3] << endl;

	return 0;
}

“引用”与多态的关系

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


例4
class A {	};
class B : public class A {	};

B b;
A& ref = b;

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

答:指针是通过某个指针变量指向一个对象后,对它所指向的变量进行间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外就是上面提到的对函数传ref和pointer的区别。

结构与联合的区别

答:结构和联合都是有多个不同的数据类型成员组成,但在任何同一时刻,联合中只存放了一个被选中的成员(所有成员共用一块地址空间),而结构的所有成员都存在(不同成员存放的地址不同)。 对联合的不同成员赋值将会对其他成员重写,原来成员的值就不存在了,而与对结构的不同成员赋值是互不影响的。

关于“联合”题目的输出结果

答:


1>
union
{
	int i;
	char x[2];
}a;

int main(char argc,char *argv)
{

	a.x[0] = 10; // 一个字节0a
	a.x[1] = 1;  // 一个字节01
	printf("%d", a.i);	// 四个字节0x0000010a

	return 0;
}

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


2>
int main(char argc,char *argv)
{
	union
	{
		int i;
		struct
		{
			char first;
			char second;
		}half;
	}number;

	number.i = 0x4241;
	printf("%c, %c\n", number.half.first, number.half.second); // A, B 

	number.half.first = 'a';
	number.half.second = 'b';
	printf("%x\n", number.i); // 0x6261

	getchar();
	return 0;
}

答案:A, B (0x41是低位对应half中的first,0x42是高位对应half中的second); 6261(number.i和number.half公用一块地址空间)。

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

答:关联表示两个类的对象之间存在某种语义上的联系。例如,作家使用计算机,人们就认为在作家和计算机之间存在某种语义连接,因此,在类图中应该在作家类和计算机类之间建立关联关系。

聚合是关联的特例。聚合表示类与类之间的关系是整体与部分的关系。在陈述需求时使用的“包含”、“组成”。“分为…部分”等字句,往往意味着存在聚合关系。

UML中泛化关系就是通常所说的继承关系,它是通过元素和具体元素之间的一种分类关系。具体完善完全拥有通用元素的信息,并且还可以附加一些其他信息。

重载(overload)和重写(overried,也叫做“覆盖”)的区别

答:重载是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

多态的作用

答:隐藏实现细节,使得代码能够模块化:扩展代码模块,实现代码重用;

接口重用,为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。

Ado与Ado.net的相同与不同

答:除了“能够让应用程序处理存储于DBMS中的数据”这一基本相似点外,两者没有噢太多共同之处。Ado使用OLE DB接口并基于微软的COM技术,而Ado.net拥有自己的Ado.net接口并且基于微软的.net体系架构。众所周知.net体系不同于COM体系,Ado.net接口也就完全不同于Ado和OLE DB接口,这也就是说Ado.net和Ado是两种数据访问方式。Ado.net提供对XML的支持。

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

答:5 * 5 + 5 等于30.

有哪几种情况只能用initialization list而不能用assignment

答:没有默认构造函数的的类类型的成员,以及const或引用类型的成员。初始化的那个对象其本身还不存在,而赋值表示对象本身已经存在了。

C++是不是类型安全的

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

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

答:全局对象的构造函数。

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

答:全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的(初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域,同时未被初始化的对象存储区可以通过void*来访问和操纵,程序结束后由系统自行释放),在C++里面没有这个区分了,他们共同占用同一块内存区。

常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)

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

自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的,但是在部分编译器的实现上这两块内存都是同一种管理方式。

堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。堆可以动态地扩展和收缩。

struct和class的区别

答:成员的默认访问权限。class的成员默认是private权限,struct默认是public权限;

默认继承权限。如果不明确指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理。

当一个类A中没有任何成员变量与成员函数,这是sizeof(A)的值是多少

答:编译器为这样没有成员的结构体隐形定义了一个一字节(char)的成员,目的是为了能用来标识类实例之间的不同。这里可以这样理解,如果没有这一个字节,那么所有结构体的实例都是空,也就不占内存,那么他们的实例是没有区别的,因此C++的编译器不允许这么做,从而隐式增加了一个字节。

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

答:通用寄存器给出的地址是段内偏移地址,相应段寄存器地址 * 10H + 通用寄存器内地址,就得到了真正要访问的地址。

比较C++中的4中类型转换方式

答:const_cast用来剥除类型中的const修饰符;

static_cast将父类转换为子类,如果转换不成立的话,编译器会提示错误,强制类型转换则不会有提示。

reinterpret_cast一般用于指针之间的转换,通常只是将基类指针假装成一个派生类指针而不改变其值,而static_cast则将执行正确的地址操作。

dynamic_cast通常用于执行从指向基类的指针安全地向下转型为指向派生类的指针。不同于static_cast的是, dynamic_cast仅用于对多态类型进行向下转型(也就是说,被转型的表达式的类型,必须是一个指向带有虚函数的类类型的指针),并且执行运行期检查工作,来判定转型的正确性。这种转型是需要付出代价的而static_cast则不需要。

详细请看:http://blog.csdn.net/tujiaw/article/details/6101393

分别写出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)

请说出const与#define相比,有何有点

答:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

const作用:定义常量、修饰函数参数、修饰函数返回值,被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

简述数组与指针的区别

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

1>
char a[] = "hello";
a[0] = 'x';
char *p = "world";	// p指向常量字符串
p[0] = 'x';			// 编译器不能发现该错误,运行时会报错

用运算符sizeof可以计算出数组的容量(字节数)。sizeof(p), p为指针变量的字节数,而不是p所指的内存容量。C/C++语言没有办法知道指针所指的内存容量,除非在申请内存是记住它。sizeof(a)可以求出数组的大小,但是如果数组最为函数参数,该数组会退化为指针,求出来的大小就是指针变量的字节数了。

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

答:重载,发生在同一个类中,函数名相同,参数不同,virtual关键字可有可无;

覆盖,分别位于派生类与基类中,函数名相同,参数相同,基类函数必须有virtual关键字。

隐藏,派生类的函数屏蔽了与其同名的基类函数。规则如下: 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数被隐藏(注意别与重载混淆)。

如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

求出两个数中的较大者

There are two int variables:a and b, dou’t use “if”, “?:”, “switch” or other judegement statements, find out the biggest one of the two numbers.

答:((a + b) + abs(a – b)) / 2;