admin 管理员组

文章数量: 887021

前言

本文参考《C++面试宝典》,增加部分知识点,并修正宝典中的部分错误以及补充部分参考回答。

new、delete、malloc、free关系

  • delete会调用对象的析构函数,和malloc对应free只会释放内存;
  • new调用构造函数。
  • malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。
  • 它们都可用于申请动态内存和释放内存。
  • 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

参考回答:首先,new/delete是C++的关键字,而malloc/free是C语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数

delete与 delete []区别

  • delete只会调用一次析构函数,而delete[]会调用每一个成员的析构函数。
  • 在MoreEffective C++中有更为详细的解释:“当delete操作符用于数组时,它为每个数组元素调用析构函数,然后调用operatordelete来释放内存。”,delete与New配套,delete[]与new []配套

如下例:

 MemTest*mTest1=newMemTest[10];
 MemTest*mTest2=newMemTest;
 int*pInt1=newint[10];
 int*pInt2=newint;
 delete[]pInt1;  //-1-
 delete[]pInt2;  //-2-
 delete[]mTest1;//-3-
 delete[]mTest2;//-4-

在-4-处报错。

这就说明:对于内建简单数据类型,delete和delete[]功能是相同的。对于自定义的复杂数据类型,delete和delete[]不能互用。delete[]删除一个数组,delete删除一个指针简单来说,用new分配的内存用delete删除用new[]分配的内存用delete[]删除delete[]会调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。如果你在用delete时没用括号,delete就会认为指向的是单个对象,否则,它就会认为指向的是一个数组。

C和C++ 的共同点?不同之处?

  • 设计思想上:C++是面向对象的语言,而C是面向过程的结构化编程语言
  • 语法上:
    • C++具有封装、继承和多态三种特性
    • C++相比C,增加多许多类型安全的功能,比如强制类型转换、
    • C++支持范式编程,比如模板类、函数模板等

继承的优缺点

优点:类继承是在编译时刻静态定义的,且可直接使用,类继承可以较方便地改变父类的实现。
缺点:

  • 首先,因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。
  • 其次,父类通常至少定义了子类的部分行为,父类的任何改变都可能影响子类的行为。如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

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

封装,继承和多态。
在面向对象程序设计语言中,封装是利用可重用成分构造软件系统的特性,它不仅支持系统的可重用性,而且还有利于提高系统的可扩充性;消息传递可以实现发送一个通用的消息而调用不同的方法;封装是实现信息隐蔽的一种技术,其目的是使类的定义和实现分离。

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

析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数JAVA无析构函数深拷贝和浅拷贝

多态,虚函数,纯虚函数

  • 多态:是对于不同对象接收相同消息时产生不同的动作。C++的多态性具体体现在运行和编译两个方面:在程序运行时的多态性通过继承和虚函数来体现;
    在程序编译时多态性体现在函数和运算符的重载上
  • 虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。
  • 纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在 纯虚函数不具备函数的功能,一般不能直接被调用。

从基类继承来的纯虚函数,在派生类中仍是虚函数。如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstractclass)。
抽象类中不仅包括纯虚函数,也可包括虚函数。l抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运行时多态性。

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

int func(x)
{
    int countx = 0;
    while(x)
    {
          countx ++;
          x = x&(x-1);
     }
    return countx;
}

假定x = 9999。 答案:8

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

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

  • 引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。
  • 注意问题:
    • 申明一个引用的时候,切记要对其进行初始化。
    • 引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。
    • 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。
    • 不能建立数组的引用。

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

  • 传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
  • 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
  • 使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

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

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const类型标识符 &引用名=目标变量名;
例1

int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确

例2

string foo( );
void bar(string & s);

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

bar(foo( ));
bar("hello world");

原因在于foo( )和"helloworld"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。引用型参数应该在能被定义为const的情况下,尽量定义为const 。

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

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

  • 好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtimeerror!
    注意事项:
    • 不能返回局部变量的引用。这条可以参照EffectiveC++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
    • 不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
    • 可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
    • 流操作符重载返回值申明为“引用”的作用:
      流操作符<<和>>,这两个操作符常常希望被连续使用,
      例如:
    cout<< "hello" << endl; 
    
    因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j= 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
    例3
#include <iostream.h>
int &put(int n);
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<<vals[0];
	cout<<vals[9];
}
int &put(int n)
{
	if (n>=0 && n<=9 ) 
		return vals[n];
	else 
	{ 
		cout<<"subscript error"; return error;
	}
}
  • 在另外的一些操作符中,却千万不能返回引用:±*/四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

“引用”与多态的关系?

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

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

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

  • 指针有自己的一块空间,而引用只是一个别名;
  • 使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小; + 指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;
  • 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;
  • 可以有const指针,但是没有const引用;
  • 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
  • 指针可以有多级指针(**p),而引用至于一级;
  • 指针和引用使用++运算符的意义不一样;
  • 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。
  • 指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传ref和pointer的区别。

什么时候需要“引用”?

流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。

结构与联合有和区别?

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

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

a)

#include <stdio.h>
union
{
	int i;
	char x[2];
}a;

void main()
{
	a.x[0] = 10;
	a.x[1] = 1;
	printf("%d",a.i);
}

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

a.x[0]10
a.x[1]1
char占用一个字节,8位:
a.x[0]转化为二进制就为00001010
00
:—::—:
a.x[1]转化为二进制就为00000001
00
:—::—:
最终就是:0000 0001 0000 1010
十六进制为:0x010A
十进制为:266
b)
main()
{
	union{                   /*定义一个联合*/
		int i;
		struct
			{             /*在联合中定义一个结构*/
				char first;
				char second;
			}half;
		}number;
	number.i=0x4241;         /*联合成员赋值*/
	printf("%c%cn",number.half.first, mumber.half.second);
	number.half.first='a';   /*联合中结构成员赋值*/
	number.half.second='b';
	printf("%xn", number.i);
	getch();
}

答案: AB (0x41对应’A’,是低位;Ox42对应’B’,是高位)
6261 (number.i和number.half共用一块地址空间)

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

涉及到UML中的一些概念:关联是表示两个类的一般性联系,比如“学生”和“老师”就是一种关联关系;聚合表示has-a的关系,是一种相对松散的关系,聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系:从实现的角度讲,聚合可以表示为:

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

而组合表示contains-a的关系,关联性强于聚合:组合类与被组合类有相同的生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系:实现的形式是:

class A
{
	...
}
class B
{
	A a;
	...
}

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

  • 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private,protected,public)
  • 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。
  • 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

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

  • 常考的题目。从定义上来说:
    • 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
    • 重写:是指子类重新定义父类虚函数的方法。
  • 从实现原理上来说:
    • 重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:functionfunc(p:integer):integer;和functionfunc(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
    • 重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

多态的作用?

主要是两个:

  • 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
  • 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。

Ado与Ado的相同与不同?

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

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

答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.

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

答案:i 为30。

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

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

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

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

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

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

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

  • 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
  • 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
  • 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

struct 和 class 的区别

参考答案:在C++中,可以用struct和class定义类,都可以继承。区别在于:struct的默认继承权限和默认访问权限是public,而class的默认继承权限和默认访问权限是private。
另外,class还可以定义模板类形参,比如template <class T, int i>。

当一个类A 中没有任何成员变量与成员函数,这时sizeof(A)的值是多少?如果不是零,请解释一下编译器为什么没有让它为零。

sizeof(A) = 1。编译器不允许一个类的大小为0。那是被编译器插进去的一个char ,使得这个class的不同实体(object)在内存中配置独一无二的地址。 也就是说这个char是用来标识类的不同对象的。肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。

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

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

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

C++中四种类型转换是:static_cast, dynamic_cast, const_cast, reinterpret_cast

  • const_cast
    用于将const变量转为非const
  • static_cast
    用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;
  • dynamic_cast
    用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。
    向上转换:指的是子类向基类的转换
    向下转换:指的是基类向子类的转换
    它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。
  • reinterpret_cast
    几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

分别写出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 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
  • 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

简述数组与指针的区别?

数组指针
保存数据保存数据的地址
直接访问数据间接访问数据,首先获得指针的内容,然后将其作为地址,从该地址中提取数据
隐式的分配和删除通常用于动态的数据结构
通常用于固定数目且数据类型相同的元素通过Malloc分配内存,free释放内存
自身即为数据名通常指向匿名数据,操作匿名函数
  • 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。
  • 指针可以随时指向任意类型的内存块。
    (1)修改内容上的差别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误

(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof§,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字节
}

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

  • 成员函数被重载的特征:
    (1)相同的范围(在同一个类中);
    (2)函数名字相同;
    (3)参数不同;
    (4)virtual 关键字可有可无。
  • 覆盖是指派生类函数覆盖基类函数,特征是:
    (1)不同的范围(分别位于派生类与基类);
    (2)函数名字相同;
    (3)参数相同;
    (4)基类函数必须有virtual 关键字。
  • “隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
    (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
    (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

求出两个数中的较大者

There are two int variables:a and b, don’t use “if”, “? :”, “switch”or other judgement statements, find outthe biggest one of the two numbers.
答案:( ( a + b ) + abs( a - b ) ) / 2

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

答案:

cout << __FILE__ ;
cout<<__LINE__ ;

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

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

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

void main( void )
{
	String str("zhanglin");
	_onexit( fn1 );
	_onexit( fn2 );
	_onexit( fn3 );
	_onexit( fn4 );
	printf( "This is executed first.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 address of a function (func) to be calledwhen the program terminates normally. Successive calls to _onexit create aregister of functions that are executed in LIFO (last-in-first-out) order. Thefunctions passed to _onexit cannot take parameters.
程序正常终止时,将向_onexit函数传递要调用的函数(func)的地址。 连续调用_onexit会创建以LIFO(后进先出)顺序执行的功能的寄存器。 传递给_onexit的函数不能使用参数。

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

答案:

#ifdef __cplusplus
	cout<<"c++";
#else
	cout<<"c";
#endif

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

答案:

#include<iostream>
#include<fstream>
#include <vector>

using namespace std;

void Order(vector<int> &data) //bubble sort
{
	int count = data.size();
	for (int i = 0; i < count; i++)
	{
		for (int j = 0; j < count - i - 1; j++)
		{
			if (data[j] > data[j + 1])
			{

				int temp = data[j];
				data[j] = data[j + 1];
				data[j + 1] = temp;
			}
		}
	}
}

void main()
{
	vector<int>data;
	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 (int i = 0; i < data.size(); i++)
		out << data[i] << " ";
	out.close(); //关闭输出文件流
}

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

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;
}

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

class B
{
public:
	B()
	{
		cout << "default constructor" << endl;
	}
	~B()
	{
		cout << "destructed" << endl;
	}
	B(int i) :data(i)    //B(int) works as aconverter ( int -> instance of  B)
	{
		cout << "constructed by parameter " << data << endl;
	}
private:
	int data;
};


B Play(B b)
{
	return b;
}

问题一:

int main(int argc, char* argv[])
{
	B t1 = Play(5);
	B t2 = Play(t1);
	return 0;
}
//输出
constructed by parameter 5
destructed //B(5)形参析构
destructed //t1形参析构
destructed //t2形参析构
destructed //t1形参析构

问题二:

int main(int argc, char* argv[])
{
	B t1 = Play(5);
	B t2 = Play(10);
	return 0;
}
//输出
constructed by parameter 5 //
destructed //B(5)形参析构
constructed by parameter 10
destructed // B(10)形参析构
destructed //t2形参析构
destructed //t1形参析构

写一个函数找出一个整数数组中,第二大的数 (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;
}

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

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

//写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。
/*思路:
*/
#include <iostream>
#include <string>
using namespace std;
int Find(string str_long,string str_short)
{
    if(str_long.length()<str_short.length())
    {
        cout<<"error"<<endl;return 0;
    }
    string temp;
    char str_short_first=str_short[0];
    for(int i=0;i<str_long.length();i++)
    {
        if (str_long[i]==str_short_first)
        {
            temp.assign(str_long,i,str_short.length());
            if(temp==str_short)
                return ++i;
            else continue;
        }
    }
}
 
int main()
{
    string str_l;
    cout<<"请输入第一个长字符串"<<endl;
    cin>>str_l;
    string str_s;
    cout<<"请输入第一个短字符串"<<endl;
    cin>>str_s;
    cout<<Find(str_l,str_s)<<endl;
    return 0;
}

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

比如有class A : public class B, public class C {} 那么A的内存结构大致是怎么样的?
这个是compiler-dependent的, 不同的实现其细节可能不同。如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。可以参考《深入探索C++对象模型

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

思路:

bool check(const node* head)
{
	//return false : 无环;true: 有环
	//一种O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然)
} 
struct node
{
	char val;
	node* next;
}
bool check(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;
}

指针找错题

分析这些面试题,本身包含很强的趣味性;而作为一名研发人员,通过对这些面试题的深入剖析则可进一步增强自身的内功。
试题1:
以下是引用片段:

void test1()  //数组越界
{
	char string[10];
	char* str1 = "0123456789";
	strcpy(string, str1);
}

试题2: 
以下是引用片段:

void test2()
{
	char string[10], str1[10];
	int i;
	for (i = 0; i < 10; i++)
	{
		str1 = 'a';
	}
	strcpy(string, str1);
}

试题3:  
以下是引用片段:

void test3(char* str1)
{
	char string[10];
	if (strlen(str1) <= 10)
	{
		strcpy(string, str1);
	}
}

解答:

  • 试题1:字符串str1需要11个字节才能存放下(包括末尾的’\0’),而string只有10个字节的空间,strcpy会导致数组越界;
  • 试题2:如果面试者指出字符数组str1不能在数组内结束可以给3分;如果面试者指出strcpy(string,str1)调用使得从 str1内存起复制到string内存起所复制的字节数具有不确定性可以给7分,在此基础上指出库函数strcpy工作方式的给10分;
  • 试题3,if(strlen(str1) <= 10)应改为if(strlen(str1) <10),因为strlen的结果未统计’\0’所占用的1个字节。剖析:考查对基本功的掌握
      (1)字符串以’\0’结尾;
      (2)对数组越界把握的敏感度;
      (3)库函数strcpy的工作方式,

如果编写一个标准strcpy函数

总分值为10,下面给出几个不同得分的答案::

//2分 以下是引用片段
void strcpy(char *strDest, char *strSrc)
{
	while ((*strDest++ = *strSrc++) != ‘\0);
}
//4分 以下是引用片段:
void strcpy(char *strDest, const char *strSrc)
//将源字符串加const,表明其为输入参数,加2分
{
	while ((*strDest++ = *strSrc++) != ‘\0);
}
//7分 以下是引用片段:
void strcpy(char *strDest, const char *strSrc)
{
	//对源地址和目的地址加非0断言,加3分
	assert((strDest != NULL) && (strSrc != NULL));
	while ((*strDest++ = *strSrc++) != ‘\0);
}
//10分 以下是引用片段:
//为了实现链式操作,将目的地址返回,加3分!
char * strcpy(char *strDest, const char *strSrc)
{
	assert((strDest != NULL) && (strSrc != NULL));
	char *address = strDest;
	while ((*strDest++ = *strSrc++) != ‘\0);
	return address;
}

对strlen的掌握,它没有包括字符串末尾的’\0’。
读者看了不同分值的strcpy版本,应该也可以写出一个10分的strlen函数了,完美的版本为:

int strlen(const char *str) //输入参数const 以下是引用片段:
{
	assert(strt != NULL); //断言字符串地址非0
	int len = 0; //注,一定要初始化。
	while ((*str++) != '\0')
	{
		len++;
	}
	return len;
}

找错题:
试题1:以下是引用片段:

void GetMemory(char *p)
{
	p = (char *)malloc(100);
}
void Test(void)
{
	char *str = NULL;
	GetMemory(str);
	strcpy(str, "helloworld");
	printf(str);
}

试题2:以下是引用片段:

char *GetMemory(void)
{
	char p[] = "helloworld";
	return p;
}
void Test(void)
{
	char *str = NULL;
	str = GetMemory();
	printf(str);
}

试题3:以下是引用片段:

void GetMemory(char **p, int num)
{
	*p = (char *)malloc(num);
}
void Test(void)
{
	char *str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

试题4:以下是引用片段:

void Test(void)
{
	char *str = (char *)malloc(100);
	strcpy(str, "hello");
	free(str);
	... //省略的其它语句
}

解答:

  • 试题1传入中GetMemory( char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完
  char *str = NULL;
  GetMemory( str );

后的str仍然为NULL;

  • 试题2中
  char p[] = "helloworld";
  return p;

的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期。

  • 试题3的GetMemory避免了试题1的问题,传入GetMemory的参数为字符串指针的指针,但是在GetMemory中执行申请内存及赋值语句
  *p = (char *) malloc( num );

后未判断内存是否申请成功,应加上:

  if ( *p == NULL )
  {
  ...//进行申请内存失败处理
  }
  • 试题4存在与试题3同样的问题,在执行
      char *str = (char *)malloc(100);
      后未进行内存是否申请成功的判断;另外,在free(str)后未置str为空,导致可能变成一个“野”指针,应加上:
      str = NULL;
      试题3的Test函数中也未对malloc的内存进行释放。
      对内存操作的考查主要集中在:
      (1)指针的理解;
      (2)变量的生存期及作用范围;
      (3)良好的动态内存申请和释放习惯。
      
    再看看下面的一段程序有什么错误:  
    以下是引用片段:
swap(int* p1, int* p2)
{
	int *p;
	*p = *p1;
	*p1 = *p2;
	*p2 = *p;
}

在swap函数中,p是一个“野”指针,有可能指向系统区,导致程序运行的崩溃。在VC++中DEBUG运行时提示错误“AccessViolation”。该程序应该改为
以下是引用片段:

swap(int* p1, int* p2)
{
	int p;
	p = *p1;
	*p1 = *p2;
	*p2 = p;
}

String 的具体实现

已知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(const char *str)
{
	if (str == NULL) //strlen在参数为NULL时会抛异常才会有这步判断
	{
		m_data = new char[1];
		m_data[0] = '\0';
	}
	else
	{
		m_data = new char[strlen(str) + 1];
		strcpy(m_data, str);
	}

}
String::String(const String &another)
{
	m_data = new char[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 = new char[strlen(rhs.m_data) + 1];
	strcpy(m_data, rhs.m_data);
	return *this;
}

String::~String()
{
	delete[]m_data;
}

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

答:防止该头文件被重复引用。
#include<file.h> 与 #include "file.h"的区别?
答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。

在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?

C++调用C函数需要extern C,因为C语言没有函数重载。

几道c笔试题(含参考答案)

1.What is displayed when f() is called given the code:

class Number {
public:
	string type;

	Number() : type(void) { }
	explicit Number(short) : type(short) { }
	Number(int) : type(int) { }
};
void Show(const Number& n) { cout << n.type; }
void f()
{
	short s = 42;
	Show(s);
}

a) void
b) short
c) int
d) None of the above

  1. Which is the correct output for the following code
double dArray[2] = { 4, 8 }, *p, *q;
p = &dArray[0];
q = p + 1;
cout << q – p << endl;
cout << (int)q - (int)p << endl;

a) 1 and 8
b) 8 and 4
c) 4 and 8
d) 8 and 1

第一个选C;
虽然传入的是short类型,但是short类型的构造函数被生命被explicit,也就是只能显示类型转换,不能使用隐式类型转换。
第二个选A;
第一个是指针加减,按照的是指向地址类型的加减,只跟类型位置有关,q和p指向的数据类型以实际数据类型来算差一个位置,因此是1。而第二个加减是实际指针值得加减,在内存中一个double类型占据8个字节,因此是8

Sony笔试题

1.完成下列程序
*
*.*.
*…*…*…
*…*…*…*…
*…*…*…*…*…
*…*…*…*…*…*…
*…*…*…*…*…*…*…
*…*…*…*…*…*…*…*…

#include <stdio.h>
#define N 8
int main()
{
	int i;
	int j;
	int k;
	-------------------------------------------------------- -
		| |
		| |
		| |
	-------------------------------------------------------- -
	return 0;
}

答案:

#include<iostream>
using namespace std;
void dian(int n)
{
	if (n < 2)
	{
		while (n--)
		{
			cout << ".";
		}
	}
	else
		cout << "...";
}
void xing()
{
	cout << "*";
}

int main()
{
	cout << "输入你需要输出的行数:" << endl;
	int line;
	cin >> line;
	for (int i = 1; i <= line; i++)
	{
		int j = i;

		while (j--)
		{
			xing();
			dian(i - 1);

		}
		cout << endl;
	}
	return 0;
}

2.完成程序,实现对数组的降序排序

#include<stdio.h>
void sort(int *arr, int len);
int main()
{
	int array[] = { 4556762341342323 }//数字任//意给出
	sort(array, (sizeof(array) / sizeof(array[0])) - 1);
	return 0;
}
void sort()
{
	____________________________________
		| |
		| |
	----------------------------------------------------
}

答案:

#include <stdio.h>

void sort(int *arr, int len);
int main(void) {
	int array[10] = {45,56,76,234,1,34,23,2,3};
	int i;
	sort(array, (sizeof(array) / sizeof(array[0])) - 1);
	for (i = 0; i < (sizeof(array) / sizeof(array[0])) - 1; i++)
	{
		printf("%d \n", *(array + i));
	}
	return 0;
}
void sort(int *arr, int len)
{
	int i;
	int j;
	int tmp;
	for (i = 0; i < len; i++)
	{
		for (j = i + 1; j < len; j++)
		{
			if (*(arr + i) < *(arr + j))
			{
				tmp = *(arr + i);
				*(arr + i) = *(arr + j);
				*(arr + j) = tmp;
			}
		}
	}
}

3.费波那其数列,1,1,2,3,5……编写程序求第十项。可以用递归,也可以用其
他方法,但要说明你选择的理由。

#include <stdio.h>
int Pheponatch(int);
int main()
{
	printf("The 10th is%d", Pheponatch(10));
	return 0;
}
int Pheponatch(int N)
{
	--------------------------------
		| |
		| |
	--------------------------------
}

答案:

#include<stdio.h>
int Pheponatch(int);
int main()
{
	printf("The 10th is%d", Pheponatch(10));
	return 0;
}
int Pheponatch(int n)
{
   int n1=1;
   int n2=1;
   int result=0;
   if(n==1||n==2)result=1;
   else
  {
      for(int i=0;i<n-2;i++)
    {
       int temp=n2;
       n2=n1+n2;
       n1=temp;
    }
       result=n2;
  }
   return result;
}

4.下列程序运行时会崩溃,请找出错误并改正,并且说明原因。

#include
#include
typedef struct
{
	TNode* left;
	TNode* right;
	int value;
} TNode;
TNode* root = NULL;
void append(int N);
int main()
{
	append(63);
	append(45);
	append(32);
	append(77);
	append(96);
	append(21);
	append(17); // Again, 数字任意给出
}
void append(int N)
{
	TNode* NewNode = (TNode*)malloc(sizeof(TNode));
	NewNode->value = N;
	if (root == NULL)
	{
		root = NewNode;
		return;
	}
	else
	{
		TNode* temp;
		temp = root;
		while ((N >= temp.value&& temp.left != NULL) || (N != NULL))
		{
			while (N >= temp.value&& temp.left != NULL)
				temp = temp.left;
			while (N<temp = temp.right;
		}
		if (N >= temp.value)
			temp.left = NewNode;
		else
			temp.right = NewNode;
		return;
	}
}

答案:

//下列程序运行时会崩溃,请找出错误并改正,并且说明原因。
/*
此程序的功能是完成一个有序二叉树的建立,使得左子树结点的值小于根结点,
右子树大于根结点. 题目程序中结构体定义的地方有误,在TNode名字出现之前,
就在结构体内使用TNode,将导致编译错误.另外题目中append函数中的所有temp的点号操作符都应改成->,
因为temp是个指针.至于题目中所说的程序在运行时崩溃出现在append函数中的对temp->left和temp->right操作时候, 
因为每创建一个新结点的时候都没将left和right成员初始化为空指针,会导致append函数中的while循环访问到非法内存,
从而导致程序崩溃. 为了方便测试,添加了函数print_tree打印树的结点内容,
另外补充了一个函数free_tree来释放动态分配的内存.修改后的程序如下:
*/
#include <iostream>
using namespace std;
//typedef struct{   //错误1 TNode名字出现前,就在结构内使用TNode将导致编译错误
typedef struct TNode
{ //应该这样写
	TNode* left;
    TNode* right; 
   int value; 
}TNode;
 
TNode* root=NULL; 
void append(int N); 
void free_tree(TNode *root);
void print_tree(TNode *root);
 
 
int main() 
{ 
	append(63); 
	append(45); 
    append(32); 
	append(77); 
	append(96); 
	append(21); 
	append(17); // Again, 数字任意给出 
	print_tree(root);
	cout<<"\n memory released !\n";
	free_tree(root);
    system("pause");
return 0;
} 
 
void append(int N) 
{
	TNode* NewNode=(TNode *)malloc(sizeof(TNode)); 
	if (!NewNode)
	{
		cout<<"memory alloc failure!\n";
		exit(1);
	}
 
	NewNode->value=N; 
	//NewNode->left;
	//NewNode->right; //错误2 没有初始化新结点的left和right成员为空指针,
	                  //导致append函数中while循环访问到非法内存,从而导致运行时崩溃
	NewNode->left=NULL;
	NewNode->right=NULL;
	if(root==NULL) 
	{ 
		root=NewNode; 
		return;
	} 
	else 
	{ 
		TNode* temp; 
		temp=root; 
		//while((N>=temp.value && temp.left!=NULL) || (N<temp. value && temp. right!=NULL)) 
		//错误3 temp是指针,则下面引用成员应该使用->而不是.(点)
		while((N>=temp->value && temp->left!=NULL) || (N<temp->value&&temp->right!=NULL)) 
		{
			   while(N>=temp->value && temp->left!=NULL) 
				   temp=temp->left; 
			   while(N<temp->value && temp->right!=NULL)
				   temp=temp->right; 
		} 
 
		if(N>=temp->value) 
			temp->left=NewNode; 
		else 
			temp->right=NewNode; 
		return; 
	}
} 
 
 
//释放内存函数
void free_tree(TNode *root){
	if (!root->left && !root->right)
	{
		free(root);
		return;
	}
	if (root->left)
	{
		free_tree(root->left);
	}
	else{
		free_tree(root->right);
	}
}
 
 
//打印函数
void print_tree(TNode *root){
	if (!root->left && !root->right)
	{
		cout<<root->value<<" ";
		return;
	}
	if (root->right)
		print_tree(root->right);
 
		cout<<root->value<<" ";
 
	if (root->left)
	{
		print_tree(root->left);
	}
}

请你分别画出OSI的七层网络结构图和TCP/IP的五层结构图。


OSI七层模型及其包含的协议如下:

  • 物理层: 通过媒介传输比特,确定机械及电气规范,传输单位为bit,主要包括的协议为:IEE802.3 CLOCK RJ45
  • 数据链路层: 将比特组装成帧和点到点的传递,传输单位为帧,主要包括的协议为MAC VLAN PPP
  • 网络层:负责数据包从源到宿的传递和网际互连,传输单位为包,主要包括的协议为IP ARP ICMP
    +传输层:提供端到端的可靠报文传递和错误恢复,传输单位为报文,主要包括的协议为TCP UDP
  • 会话层:建立、管理和终止会话,传输单位为SPDU,主要包括的协议为RPC NFS
  • 表示层: 对数据进行翻译、加密和压缩,传输单位为PPDU,主要包括的协议为JPEG ASII
  • 应用层: 允许访问OSI环境的手段,传输单位为APDU,主要包括的协议为FTP HTTP DNS
    TCP/IP 4层模型包括:
  • 网络接口层:MAC VLAN
  • 网络层:IP ARP ICMP
  • 传输层:TCP UDP
  • 应用层:HTTP DNS SMTP

请你详细地解释一下IP协议的定义,在哪个层上面?主要有什么作用?TCP与UDP呢 ?

参考答案:IP处在互连网络层。负责提供基本的数据封包传送功能,让每一块数据包都能够到达目的主机(但不检查是否被正确接收)。
TCP与UDP在传输层。它提供了节点间的数据传送,应用程序之间的通信服务,主要功能是数据格式化、数据确认和丢失重传等。如传输控制协议(TCP)、用户数据报包议(UDP)等,TCP和UDP给数据包加入传输数据并把它传输到下一层中,这一层负责传送数据,并且确定数据已被送达并接收。

请问交换机和路由器各自的实现原理是什么?分别在哪个层次上面实现的?

交换机属于OSI第二层即数据链路层设备。它根据MAC地址寻址,通过站表选择路由,站表的建立和维护由交换机自动进行。路由器属于OAI第三层即网络层设备,它根据IP地址进行寻址,通过路由表路由协议产生。交换机最大的好处是快速,路由器最大的好处是控制能力强。

全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的 ?

  • 作用域不同:全局变量的作用域为整个程序,而局部变量的作用域为当前函数或循环等
  • 内存存储方式不同:全局变量存储在全局数据区中,局部变量存储在栈区
  • 生命期不同:全局变量的生命期和主程序一样,随程序的销毁而销毁,局部变量在函数内部或循环内部,随函数的退出或循环退出就不存在了
  • 使用方式不同:全局变量在声明后程序的各个部分都可以用到,但是局部变量只能在局部使用。函数内部会优先使用局部变量再使用全局变量。

全局变量在数据段,而局部变量在栈,局部 变量在函数结束时内存空间就被系统收回,所以要返回的数组或字符串不要用局部变量定义.extren和在main()函数外定义的变量都称为全局变量,操 作系统和编译器从定义变量为变量分配内存时,从变量的定义和存储区域来分别局部变量和全局变量

8086是多少位的系统?在数据总线上是怎么实现的?

8086微处理器共有4个16位的段寄存器,在寻址内存单元时,用它们直接或间接地存放段地址。
  代码段寄存器CS:存放当前执行的程序的段地址。
  数据段寄存器DS:存放当前执行的程序所用操作数的段地址。
  堆栈段寄存器SS:存放当前执行的程序所用堆栈的段地址。
  附加段寄存器ES:存放当前执行程序中一个辅助数据段的段地址。

由cs:ip构成指令地址,ss:sp构成堆栈的栈顶地址指针。DS和ES用作数据段和附加段的段地址(段起始地址或段值)
8086/8088微处理器的存储器管理

  • 地址线(码)与寻址范围:N条地址线 寻址范围=2N
  • 8086有20地址线 寻址范围为1MB 由00000H~FFFFFH
  • 8086微处理器是一个16位结构,用户可用的寄存器均为16位:寻址64KB
  • 8086/8088采用分段的方法对存储器进行管理。具体做法是:把1MB的存储器空间分成若干段,每段容量为64KB,每段存储器的起始地址必须是一个能被16整除的地址码,即在20位的二进制地址码中最低4位必须是“0”。每个段首地址的高16位二进制代码就是该段的段号(称段基地址)或简称段地址,段号保存在段寄存器中。我们可对段寄存器设置不同的值来使微处理器的存储器访问指向不同的段。
  • 段内的某个存储单元相对于该段段首地址的差值,称为段内偏移地址(也叫偏移量)用16位二进制代码表示。
  • 物理地址是由8086/8088芯片地址引线送出的20位地址码,它用来参加存储器的地址译码,最终读/写所访问的一个特定的存储单元。
  • 逻辑地址由某段的段地址和段内偏移地址(也叫偏移量)两部分所组成。写成:
    段地址:偏移地址(例如,1234H:0088H)。
  • 在硬件上起作用的是物理地址,物理地址=段基地址×10H十偏移地址

联想笔试题
  1.设计函数 int atoi(char *s)。

#include<iostream>

int atoi(char *s)
{
	int num = 0, f = 1, i = 0;

	if (s[0] == '-')
	{
		f = -1;
		i = 1;
	}
	if (s[0] == '+')
	{
		f = 1;
		i = 1;
	}

	while (s[i] != '\0')
	{
		if(s[i]<'0' || s[i]>'9')
			break;
		if (s[i] == '.')
			break;
		else
		{
			num = num * 10 + s[i] - '0';
			i++;
		}
	}

	return num * f;
}

void main()
{
	char ch[] = "+1313.377";
	cout << atoi(ch) << endl;
}

2.int i=(j=4,k=8,l=16,m=32); printf(“%d”, i); 输出是多少?
首先
int i=(j=4);
等同于:
int j = 4;
int i = j;
而int i=(j=4,k=8,l=16,m=32);
则等同于:
int j=4, k=8, l=16, m=32;
int i = j;
int i = k;
int i = l;
int i = m;
z最后i = m =32,故输出为32

解释局部变量、全局变量和静态变量的含义。

  • 局部变量
    在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外时不能使用这些变量的,它们称为局部变量;
    说明:

    • 主函数main中定义的变量也只在主函数中有效,而不因为在主函数中定义而在整个文件或程序中有效
    • 不同函数中可以使用名字相同的变量,它们代表不同的对象,互不干扰
    • 形式参数也使局部变量
    • 在一个函数内部,可以在复合语句中定义变量,这些变量只在本符合语句中有效
  • 全局变量
    在函数外定义的变量是外部变量,外部变量是全局变量,全局变量可以为本文件中其它函数所共用,它的有效范围从定义变量的位置开始到本源文件结束;
    说明:

    • 设全局变量的作用:增加了函数间数据联系的渠道
    • 建议不再必要的时候不要使用全局变量,因为:
      • a.全局变量在程序的全部执行过程中都占用存储单元;
      • b.它使函数的通用性降低了c.使用全局变量过多,会降低程序的清晰性
    • 如果外部变量在文件开头定义,则在整个文件范围内都可以使用该外部变量,如果不再文件开头定义,按上面规定作用范围只限于定义点到文件终了。如果在定义点之前的函数想引用该外部变量,则应该在该函数中用关键字extern作外部变量说明
      4.如果在同一个源文件中,外部变量与局部变量同名,则在局部变量的作用范围内,外部变量不起作用;
  • 静态变量
    在程序运行期间分配固定的存储空间的变量,叫做静态变量

论述含参数的宏与函数的优缺点。

  • 函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简单的字符替换。
  • 函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。
  • 对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
  • 调用函数只可得到一个返回值,而用宏可以设法得到几个结果。
  • 使用宏次数多时,宏展开后源程序长,因为每展开一次都使程序增长,而函数调用不使源程序变长。
  • 宏替换不占运行时间,只占编译时间;而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。
    一般来说,用宏来代表简短的表达式比较合适。

内联函数和宏很类似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样了。
内联函数是不能为虚函数的,但样子上写成了内联的,即隐含的内联方式。在某种情况下,虽然有些函数我们声明为了所谓“内联”方式,但有时系统也会把它当作普通的函数来处理,这里的虚函数也一样,虽然同样被声明为了所谓“内联”方式,但系统会把它当然非内联的方式来处理。

普天C++笔试题
  1.实现双向链表删除一个节点P,在节点P后插入一个节点,写出这两个函数。

#include <stdio.h>
#include <stdlib.h>
#define N   10
typedef struct node
{
	int data;
	struct node *next;
	struct node *front;
}ElemSN;

ElemSN *createLink(int *num);
ElemSN *DeleteNode(ElemSN *head, ElemSN *s);
ElemSN *insertNode(ElemSN *head, ElemSN *s, ElemSN *pa);

void outPut(ElemSN *head);

void main()
{
	int num[10] = { 1,2,3,4,5,6,7,8,9,10 };
	ElemSN *s, *h, *head, *q, *pa;
	s = (ElemSN*)malloc(sizeof(ElemSN));//注意要先分配空间才能用啊
	s->data = 5;
	s->front = NULL;
	s->next = NULL;
	pa = (ElemSN*)malloc(sizeof(ElemSN));
	pa->data = 100;
	pa->front = NULL;
	pa->next = NULL;
	h = createLink(num);
	printf("链表建立完如下:\n");
	outPut(h);
	printf("删除掉元素5以后链表如下:\n");
	head = DeleteNode(h, s);
	outPut(head);
	printf("插入元素后链表如下:\n");
	h = createLink(num);
	q = insertNode(h, s, pa);
	outPut(q);
}


ElemSN *createLink(int *num)
{
	int i;
	ElemSN *h, *p, *q;
	//建立头结点
	q = h = (ElemSN*)malloc(sizeof(ElemSN));
	h->data = num[0];
	h->next = h->front = NULL;

	//依次建立其余节点
	for (i = 1; i < N; i++)
	{
		p = (ElemSN*)malloc(sizeof(ElemSN));
		p->data = num[i];
		p->next = NULL;
		p->front = q;
		q->next = p;
		q = p;
	}
	return h;
}

ElemSN *DeleteNode(ElemSN *head, ElemSN *s)
{
	ElemSN *p, *h, *q;
	p = h = head;
	q = NULL;
	for (; p->data != s->data; q = p, p = p->next);
	if (p == head)
	{
		h = p->next;
		h->front = NULL;
		p->next = NULL;
		free(p);
	}
	else
	{
		q->next = p->next;
		p->next->front = q;
		p->next = p->front = NULL;
		free(p);
	}
	return h;
}

ElemSN *insertNode(ElemSN *head, ElemSN *s, ElemSN *pa)
{
	ElemSN *h, *p;
	h = head;

	for (p = head; (p != NULL) && (p->data != s->data); p = p->next);
	//插入分为头结点和其他节点
	if (p == NULL)
	{
		return NULL;
	}
	if (p == head)
	{
		pa->next = h;
		h->front = pa;
	}
	else
	{
		pa->next = p->next;
		p->next->front = pa;
		pa->front = p;
		p->next = pa;
	}
	return h;
}

void outPut(ElemSN *head)
{
	ElemSN *p = head;
	while (p != NULL)
	{
		printf("%d-->", *p);
		p = p->next;
	}
	printf("\n");
}

2.写一个函数,将其中的\t都转换成4个空格。

char*convert(char*strDest, const char*strSrc, int length)
{
	char*p = strDest;
	int i = 0;
	while (*strSrc && i < length)
	{
		if (*strSrc == '\t')
		{
			for (int j = 0; j < 4; j++)
				*p++ = ' ';
		}
		else
			*p++ = *strSrc;
		strSrc++;
		i++;
	}
	*p = '\0';//这里的结束符必须加上,否则可能会出现乱码情况
	return strDest;
}

int main()
{
	const char *str1 = "abc\tde\tfg";
	int len = strlen(str1);
	char*str2 = new char;//这个地方需要注意的,必须动态分配,要不会指向空指针!!不能这样定义的char *str2;
	//就在这个地方我开始找了很长时间的错,动态分配内存真的很重要!!!
	str2 = convert(str2, str1, len);
	cout << str2 << endl;
	system("pause");
	return 0;
}

运行效果图:

Windows程序的入口是哪里?写出Windows消息机制的流程。

Windows程序的入口是WinMain()函数。
Windows应用程序消息处理机制:

  • 操作系统接收应用程序的窗口消息,将消息投递到该应用程序的消息队列中
  • 应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息,取出消息后,应用程序可以对消息进行一些预处理。
  • 应用程序调用DispatchMessage,将消息回传给操作系统。
  • 系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理

C++里面是不是所有的动作都是main()引起的?如果不是,请举例。

不是
对于C++程序而言:

  • 第一:静态变量、全局变量、全局对象的分配早在main()函数之前已经完成了;
  • 第二:系统会为某个启动的程序分配地址空间,创建进程和主线程,并为main()指供参数(如果有的话),然后才转到main()执行;
  • 第三:是C/C++的运行时启动函数:wWinMainCRTStartup 引起的,main函数也接受这个函数的调用。所以并不是所有的动作都是由main()引起的,只是编译器是由main()开始执行的,main()只不过是一个约定的函数入口,在main()函数中的显示代码执行之前,会调用一个由编译器是生成的_main()函数,而_main()函数会进行所有全局对象的构造及初始化工作。如:
class A();
A a;
int main()
{
	//main func
}

程序在执行时,因为会首先初始化全局变量,当这个变量是一个对象时,则会首先调用该对象的构造函数,所以上例中,a的构造函数先执行,然后再执行main()函数,C++中并非所有的动作都是main()函数引起的
引申:怎样在main()函数退出以后再执行一段代码?
全局变量。当程序退出时,全局变量必须销毁,自然会调用全局对象的析构函数,所以剩下的就同构造函数一样了

如何定义和实现一个类的成员函数为回调函数?

回调函数定义
就是被调用者回头调用的函数,它是通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所所指向的函数时,此时,就可以称它为回调函数。
进一步解释
回调函数不是由该函数的实现方直接调用的,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个被调用的函数。而被调用函数在需要的时候,利用传递的地址调用回调函数。
回调函数由程序员自己调用,当需要调用另一个函数时,这个函数的其中一个参数就是这个回调函数名。系统在必要的时候会调用程序员写的回调函数,这样就可以在回调函数里完成要做的事了。
回调函数实现
(1)声明
(2)定义
(3)设置触发条件,就是在函数中把回调函数名作为一个参数,以便系统调用。
举例

typedef void(*FunPtr)(void);
//定义回调函数
class A
{
public:
	//回调函数,必须是声明为static
	static void callBackFun(void)
	{
		...
	}
};
//设置触发条件
void Funtype(FunPtr p)
{
	p();
}
void main(void)
{
	Functype(A::callBackFun);
}

回调函数和API对比

  • 相同点:
    回调函数与应用程序接口(API)很相似,都是跨层调用的函数
  • 不同点:
    • API是低层提供给高层的调用,一般这个函数对高层都是已知的。
    • 回调函数是高层提供给低层的调用,对于低层它是未知的,必须由高层进行安装,这个安装函数就是一个低层提供的API,安装后低层不是道这个回调的名字,但它通过一个函数指针来保存这个回调函数,在需要调用时,只需引用这个函数指针和相关的参数指针。

解释堆和栈的区别。

1、栈由编译器自动分配释放空间;堆 一般由程序员分配释放。
2、栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放;堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定。”

C++里面如何声明const void f(void)函数为C程序中的库函数?

extern “C” const void f(void);

下列哪两个是等同的

int b;
A const int* a = &b;
B const* int a = &b;
C const int* const a = &b;
D int const* const a = &b;

int const *a 和 const int *a 意义相同,作用等价 同理,本题C、D意义相同。
const int *a 这里const 修饰的是int,而int定义的是一个整值
int *const a 这里const修饰的是 a ,a代表的是一个指针地址 因此不能赋给a其他的地址值,但可以修改a指向的值。
const int * const a 这个代表a所指向的对象的值以及它的地址本身都不能被改变
所以:
第一个const int a, b(即a)的值不能改变,a的值可以改变;
第二个是语法错误;
第三个第四个a、b的值都不能改变;
另外,int * const a,a的值不能改变,*a的值可以改变。
最后得出答案:C和D相同

内联函数在编译时是否做参数类型检查?

A、不做检查,和宏一样
B、做类型检查
C、和编译器相关
先说宏和函数的区别:

  1. 宏做的是简单的字符串替换(注意是字符串的替换,不是其他类型参数的替换),而函数的参数的传递,参数是有数据类型的,可以是各种各样的类型.
  2. 宏的参数替换是不经计算而直接处理的,而函数调用是将实参的值传递给形参,既然说是值,自然是计算得来的.
  3. 宏在编译之前进行,即先用宏体替换宏名,然后再编译的,而函数显然是编译之后,在执行时,才调用的.因此,宏占用的是编译的时间,而函数占用的是执行时的时间.
  4. 宏的参数是不占内存空间的,因为只是做字符串的替换,而函数调用时的参数传递则是具体变量之间的信息传递,形参作为函数的局部变量,显然是占用内存的.
  5. 函数的调用是需要付出一定的时空开销的,因为系统在调用函数时,要保留现场,然后转入被调用函数去执行,调用完,再返回主调函数,此时再恢复现场,这些操作,显然在宏中是没有的.

内联函数与宏的区别:
1.内联函数在运行时可调试,而宏定义不可以;
2.编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会;
3.内联函数可以访问类的成员变量,宏定义则不能;
4.在类中声明同时定义的成员函数,自动转化为内联函数。 
答案:B

大唐电信
  DTT笔试题
  考试时间一小时,第一部分是填空和选择:
  1.数列6,10,18,32,“?”,问“?”是几?
  2.某人出70买进一个x,80卖出,90买回,100卖出,这桩买卖怎么样?
  3.月球绕地球一圈,至少要多少时间?
  4.7个人用7小时挖了7米的沟,以同样的速度在50小时挖50米的沟要多少人?
  5.鱼头长9,鱼尾等于鱼头加半个鱼身,鱼身等于鱼头加鱼尾,问鱼全长多少?
  6.一个小姐买了一块手表,回家发现手表比她家的表慢了两分钟,晚上看新闻的时候又发现她家的表比新闻里的时间慢了两分钟,则 。
  A 手表和新闻里的时间一样
  B 手表比新闻里的时间慢
  C 手表比新闻里的时间快
  7.王先生看到一则招聘启事,发现两个公司除了以下条件不同外,其他条件都相同
  A 半年年薪50万,每半年涨5万
  B 一年年薪100万,每一年涨20万
  王先生想去一家待遇比较优厚的公司,他会去哪家?
  10.问哪个袋子里有金子?
  A袋子上的标签是这样写的:B袋子上的话是对的,金子在A袋子。
  B袋子上的标签是这样写的:A袋子上的话是错的,金子在A袋子里。
  11.3个人住酒店30块钱,经理找回5块钱,服务生从中藏了2块钱,找给每人1块钱,
3×(101)+2=29,问这是怎么回事?
  12.三篇写作,均为书信形式。
  (1)一片中文的祝贺信,祝贺某男当了某公司xx
  (2)两篇英文的,一是说有事不能应邀,派别人去;另一篇是讨债的,7天不给钱就
走人(主要考business letter格式)。


大唐面试试题
  1.什么是中断?中断发生时CPU做什么工作?
  2.CPU在上电后,进入操作系统的main()之前必须做什么工作?
  3.简述ISO OSI的物理层Layer1,链路层Layer2,网络层Layer3的任务。
  4.有线电话和无线电话有何区别?无线电话特别需要注意的是什么?
  5.软件开发五个主要step是什么?
  6.你在开发软件的时候,这5个step分别占用的时间百分比是多少?
  7.makefile文件的作用是什么?
  8.UNIX显示文件夹中,文件名的命令是什么?能使文件内容显示在屏幕的命令是什么?
  9.(选做)手机用户在从一个基站漫游到另一个基站的过程中,都会发生什么?


网通笔试题
  选择题(每题5分,只有一个正确答案)
  1.中国1号信令协议属于 的协议。
  A ccs B cas C ip D atm
  2.isdnpri协议全称是 。
  A 综合业务模拟网基速协议
  B 综合业务模拟网模拟协议
  C 综合业务数字网基率协议
  D 综合业务数字网基次协议
  3.路由协议中, 协议是用距离作为向量的。
  A ospf B bgp C is-is D rip
  4.中国智能网中,ssp与scp间最上层的ss7协议是 。
  A incs B is41b C is41c D inap
  5.dtmf全称是 。
  A 双音多频 B多音双频C多音三频 D三音多频
  6.计算机的基本组成部分中,不包含下面设备的是 。
  A cpu B输入设备 C存储器D接口
  7.脉冲编码调制的简称是 。
  A pcm B pam C (delta)M D atm
  8.普通电话线接口专业称呼是 。
  A rj11 B rj45 C rs232 D bnc
  9.现有的公共数据网都采用 。
  A电路交换技术 B报文交换技术
  C语音插空 D分组交换
  10.ss7协议中的制止市忙消息简写为 。
  A stb B slb C sub D spb
  简答题(每题10分)
  1.简述普通电话与IP电话的区别。
  2.简述随路信令与公路信令的根本区别。
  3.说明掩码的主要作用。
  4.ss7协议中,有三大要素决定其具体定位,哪三大要素?
  5.描述ss7的基本通话过程。
  6.简述通信网的组成结构。
  7.面向连接与面向非连接各有何利弊?
  8.写出爱尔兰的基本计算公式。
  9.数据网主要有哪些设备?
  10.中国一号协议是如何在被叫号码中插入主叫号码的?


东信笔试题目
  笔试:30分钟。
  1.压控振荡器的英文缩写。
  2.动态随机存储器的英文缩写。
  3.选择电阻时要考虑什么?
  4.单片机上电后没有运转,首先要检查什么?
  5.计算机的基本组成部分及其各自的作用。
  6.怎样用D触发器、与或非门组成二分频电路?

static有什么用途?(请至少说明两种)

答案一

  • 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
  • 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
  • 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用

答案二:
在C语言中,static主要定义全局静态变量,定义局部静态变量,定义静态函数
一、 定义全局静态变量 :在全局变量前面加上关键字static,该全局变量变成了全局静态变量。全局静态变量有以下特点:
(1) 在全局数据区内分配内存
(2) 如果没有初始化,其默认值为0
(3) 该变量在本文件内从定义开始到文件结束可见
二、 定义局部静态变量:在局部静态变量前面加上关键字static,该局部变量便成了静态局部变量。静态局部变量有以下特点:
(1) 该变量在全局数据区分配内存
(2) 如果不显示初始化,那么将被隐式初始化为0
(3) 它始终驻留在全局数据区,直到程序运行结束
(4) 其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。
三、 定义静态函数:在函数的返回类型加上static关键字,函数即被定义成静态函数。静态函数有以下特点:
(1) 静态函数只能在本源文件中使用
(2) 在文件作用域中声明的inline函数默认为static
说明:静态函数只是一个普通的全局函数,只不过受static限制,他只能在文件坐在的编译单位内使用,不能呢个在其他编译单位内使用。
在C++语言中新增了两种作用:定义静态数据成员或静态函数成员
(1) 定义静态数据成员。静态数据成员有如下特点:
(1) 内存分配:在程序的全局数据区分配
(2) 初始化和定义:静态数据成员定义时要分配空间,所以不能在类声明中定义
(3) 静态成员函数。静态成员函数与类相联系,不与类的对象相联系。静态成员函数不能访问非静态数据成员。原因很简单,非静态数据成员属于特定的类实例,主要用于对静态数据成员的操作。
(4) 静态成员函数和静态数据成员都没有this指针。

答案三:
1、全局静态变量
在全局变量前加上关键字static,全局变量就定义成一个全局静态变量。
静态存储区,在整个程序运行期间一直存在。
初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化)。
作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。
2、局部静态变量
在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。
内存中的位置:静态存储区。
初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化)。
作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变。
3.、静态函数
在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。
函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突。
warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰。
4.、类的静态成员
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用。
5.、类的静态函数
静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);

引用与指针有什么区别?

  • 指针有自己的一块空间,而引用只是一个别名;
  • 使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;
  • 指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;
  • 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;
  • 可以有const指针,但是没有const引用;
  • 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
  • 指针可以有多级指针(**p),而引用至于一级;
  • 指针和引用使用++运算符的意义不一样;
  • 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

描述实时系统的基本特性

特定时间内完成特定的任务,实时性与可靠性。
所谓“实时操作系统”,实际上是指操作系统工作时,其各种资源可以根据需要随时进行动态分配。由于各种资源可以进行动态分配,因此其处理事务的能力较强、速度较快。
应该说,实时操作系统是在早期的操作系统基础上发展起来的,早期的操作系统的各种资源都是事先已经分配好的,工作期间这些资源不能再重新进行分配。因此其处理事务的能力较差、速度较慢,现在则称之为“非实时操作系统”。但“非实时操作系统”诞生时,其功能、性能等在当时也是非常强的,人们在未认识到更好的操作系统之前并不将其这样称呼。将来如果新的、功能更强的、实时性能更高的操作系统出现,也许现在称之为“实时”的操作系统则可能将让位于新的“实时操作系统”了。从这方面讲“实时操作系统”是一个相对的概念的。

全局变量和局部变量在内存中是否有区别?如果有,是什么区别?

  • 生存周期不同
    全局变量:全局区(静态区)(static):全局变量和静态变量是存储在一起的,初始化过的全局变量和静态变量在同一块区域,未初始化的全局变量和静态变量存放在一块相邻的区域内。此区域由系统在程序结束后释放 局部变量: 放在堆栈中。由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
  • 作用范围不同
    全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。 局部变量也只有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。
  • 静态变量分为 全局静态变量(常称为全局变量)和局部静态变量(static修饰的变量)

什么是平衡二叉树?

答 、左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于1。

堆栈溢出一般是由什么原因导致的?

答 、1.没有回收垃圾资源
2.层次太深的递归调用

什么函数不能声明为虚函数?

什么样的函数不能声明为虚函数?
1)不能被继承的函数。
2)不能被重写的函数。

1.普通函数(不能被覆盖)
2.友元函数(C++不支持友元函数继承)
3.内联函数(编译期间展开,虚函数是在运行期间绑定)
4.构造函数(没有对象不能使用构造函数,先有构造函数后有虚函数,虚函数是对对象的动作)
5.静态成员函数(只有一份大家共享)

1)普通函数
普通函数不属于成员函数,是不能被继承的。普通函数只能被重载,不能被重写,因此声明为虚函数没有意义。因为编译器会在编译时绑定函数。
而多态体现在运行时绑定。通常通过基类指针指向子类对象实现多态。
2)友元函数
友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
3)构造函数
首先说下什么是构造函数,构造函数是用来初始化对象的。假如子类可以继承基类构造函数,那么子类对象的构造将使用基类的构造函数,而基类构造函数并不知道子类的有什么成员,显然是不符合语义的。从另外一个角度来讲,多态是通过基类指针指向子类对象来实现多态的,在对象构造之前并没有对象产生,因此无法使用多态特性,这是矛盾的。因此构造函数不允许继承。
4)内联成员函数
我们需要知道内联函数就是为了在代码中直接展开,减少函数调用花费的代价。也就是说内联函数是在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。因此显然内联函数和多态的特性相违背。
5)静态成员函数
首先静态成员函数理论是可继承的。但是静态成员函数是编译时确定的,无法动态绑定,不支持多态,因此不能被重写,也就不能被声明为虚函数。

冒泡排序算法的时间复杂度是什么?

答 、O(n^2)

写出float x 与“零值”比较的if语句。

答 、if(x>0.000001&&x<-0.000001)

Internet采用哪种网络协议?该协议的主要层次结构?

答 、tcp/ip
应用层/传输层/网络层/数据链路层/物理层

Internet物理地址和IP地址转换采用什么协议?

答 、ARP (Address Resolution Protocol)(地址解析协议)

IP地址的编码分为哪俩部分?

答 、IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与之后才能区分哪些是网络位哪些是主机位。

用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序。

思路:循环链表,用取余操作做

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
typedef struct node
{
	int data;
	node* next;
}node;

void CreatList(node*& head, node*& tail, int n)
{
	if (n < 1)
	{
		head = tail = NULL;
		return;
	}
	head = new node();
	head->data = 1;
	head->next = NULL;
	node* p = head;
	for (int i = 2; i < n + 1; i++)
	{
		p->next = new node();
		p = p->next;
		p->data = i;
		p->next = NULL;
	}
	tail = p;
	tail->next = head;
}

void Print(node*& head)
{
	node* p = head;
	printf("创建出来的链表为:");
	while (p && p->next != head)
	{
		printf("%d->", p->data);
		p = p->next;
	}
	if (p)
	{
		printf("%d\n", p->data);
	}
}

void CountPrint(node*& head, node*& tail, int m)
{
	node* pre = tail;
	node* cur = head;
	int cnt = m;
	printf("取到的M值为: ");
	while (cur && cur->next != cur)
	{
		if (cnt != 1)
		{
			cnt--;
			pre = cur;
			cur = cur->next;
		}
		else
		{
			printf("%d, ", cur->data);
			pre->next = cur->next;
			delete cur;
			cur = pre->next;
			cnt = m;
		}
	}
	if (cur)
	{
		printf("%d\n", cur->data);
		delete cur;
		head = tail = NULL;
	}
}

int main()
{
	node* head;
	node* tail;
	int m;
	int n;
	printf("请输入N的值:");
	scanf_s("%d", &n);
	printf("请输入M的值:");
	scanf_s("%d", &m);
	CreatList(head, tail, n);
	Print(head);
	CountPrint(head, tail, m);
	system("pause");
	return 0;
}

运行效果图:

不能做switch()的参数类型是:

答 、switch的参数不能为实型。

局部变量能否和全局变量重名?

答、能,局部会屏蔽全局。要用全局变量,需要使用"::"
局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内

如何引用一个已经定义过的全局变量?

答 、可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错

全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?

答 、可以,在不同的C文件中以static形式来声明同名全局变量。
可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错

语句for( ;1 ;)有什么问题?它是什么意思?

答 、死循环或者说无限循环。和while(1)相同。

do……while和while……do有什么区别?

答 、前一个循环一遍再判断,后一个判断以后再循环

请写出下列代码的输出内容

#include<stdio.h>
void main()
{
	int a, b, c, d;
	a = 10;
	b = a++;
	c = ++a;
	d = 10 * a++;
	printf("b, c, d: %d, %d, %d", b, c, d);
	return;
}

运行效果图:

答 、10,12,120

static 全局变量、局部变量、函数与普通全局变量、局部变量、函数

  • static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
    答 、全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。
    从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。
    static函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件
  • static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
  • static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
  • static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。

设有以下说明和定义:

typedef union
{ 
	long i; 
	int k[5];
	char c; 
} DATE;//4

struct data 
{ 
	int cat; //4
	DATE cow;//8
	double dog;//8
}too;
DATE max;

void main()
{
	printf("结构体cat size:%d\n", sizeof(too.cat));
	printf("结构体cow size:%d\n", sizeof(too.cow));
	printf("结构体dog size:%d\n", sizeof(too.dog));
	printf("结构体size:%d\n", sizeof(too));
	printf("联合体max size:%d\n", sizeof(max));
	printf("最终结果:%d", sizeof(too) + sizeof(max));
}

执行效果图:

则语句 printf(“%d”,sizeof(too)+sizeof(max));的执行结果是?
答 、结果是:52_。DATE是一个union, 变量公用空间. 里面最大的变量类型是int[5],占用20个字节. 所以它的大小是20
data是一个struct, 每个变量分开占用空间.依次为int4 + DATE20 + double8 =32.
所以结果是 20 + 32 = 52.
当然…在某些16位编辑器下, int可能是2字节,那么结果是 int2 + DATE10 + double8 =20

-1,2,7,28,126请问28和126中间那个数是什么?为什么?

答 、应该是4^3-1=63
规律是n^3-1(当n为偶数0,2,4)
n^3+1(当n为奇数1,3,5)

用两个栈实现一个队列的功能?要求给出算法和思路!

答 、设2个栈为A,B,一开始均为空.
入队:
将新元素push入栈A;
出队:
(1)判断栈B是否为空;
(2)如果不为空,则将栈A中所有元素依次pop出并push到栈B;
(3)将栈B的栈顶元素pop出;
这样实现的队列入队和出队的平摊复杂度都还是O(1), 比上面的几种方法要好。

在c语言库函数中将一个字符转换成整型的函数是atool()吗,这个函数的原型是什么?

答 、函数名: atol
功 能: 把字符串转换成长整型数
用 法: long atol(const char *nptr);
程序例:

#include<stdio.h>
#include<iostream>
using namespace std;
int char2int(const char *str)
{
	int value = 0;
	int neg = 0;
	switch (*str)
	{
	case '-':
		neg = 1;
		/* 这里没有break */
	case '+':
		str++;
		break;
	}
	while (*str >= '0' && *str <= '9')
	{
		value *= 10;
		value += *str - '0';
		str++;
	}
	return value;
}

long char2long(const char *s)
{
	long r = 0;
	int neg = 0;
	switch (*s)
	{
	case '-':
		neg = 1;
		/* 这里没有break */
	case '+':
		s++;
		break;
	}
	while (*s >= '0' && *s <= '9')
	{
		int n = *s++ - '0';
		if (neg)
			n = -n;
		r = r * 10 + n;
	}
	return r;
}

int main(void)
{
	long l,i;
	const char *str = "9876543210";
	i = char2int(str);
	l = char2long(str);
	printf("string = %s long =%ld\n", str, l);
	printf("string = %s integer =%d\n", str, i);
	return(0);
}

对于一个频繁使用的短小函数,在C语言中应用什么实现,在C++中应用什么实现?

答 、c用宏定义,c++用inline

用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

#defineSECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1). #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2). 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3). 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4). 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。

#define MIN(A,B) ((A)<= (B) (A) : (B))
这个测试是为下面的目的而设的:

  • 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,
    对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
  • 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
  • 懂得在宏中小心地把参数用括号括起来
  • 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
    least = MIN(*p++, b);

预处理器标识#error的目的是什么?

编译程序时,只要遇到 #error 就会跳出一个编译错误,既然是编译错误,要它干嘛呢?其目的就是保证程序是按照你所设想的那样进行编译的。

嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

这个问题用几个解决方案。我首选的方案是:

while(1)
{
}

一些程序员更喜欢如下方案:

for (; ; )
{
}

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的
基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto

Loop:
...
goto Loop;

应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

用变量a给出下面的定义

a) 一个整型数(An integer)
b) 一个指向整型数的指针(A pointer to an integer)
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer toan integer)
d) 一个有10个整型数的数组(Anarray of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的(Anarray of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针(Apointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a functionthat takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数(An array of ten pointers to functions that take an integer
argument and returnan integer )

答案是:

a) int a; // Aninteger
b) int *a; // Apointer to an integer
c) int **a; // Apointer to a pointer to an integer
d) int a[10]; // Anarray of 10 integers
e) int *a[10]; // Anarray of 10 pointers to integers
f) int (*a)[10]; // Apointer to an array of 10 integers
g) int (*a)(int); //A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int);// An array of 10 pointers to functions that take an integer argument andreturn an integer

人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。
但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道
所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?

关键字static的作用是什么?

这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:

  • 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
  • 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
  • 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

关键字const是什么含意?

1)使用Const关键字的地方是为了说明这个参数为常量,是不应该被修改的。
2)合理使用const 可以是编译器自然的保护那些不希望被修改的参数,防止被无意的代码修改。
3)通过给优化器一些有用的信息,使用关键字const也许是代码更加紧凑。
下面的声明都是什么意思?
1)const int a;
2)int const a;
3)const int *a;
4)int * const a;
5)int const * a const;

1) a是一个常整数;
2) a是一个常整数;
3) a是一个指向常整形的指针
4) a是一个指向整型的常指针;
5) a是一个指向常整型的常指针;

关键字volatile有什么含意 并给出三个不同的例子。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量

1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:

int square(volatileint *ptr)
{
	return *ptr * *ptr;
}

下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatileint *ptr)
{
	int a,b;
	a = *ptr;
	b = *ptr;
	return a * b;
}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)
{
	int a;
	a = *ptr;
	return a * a;
}

位操作(Bit manipulation)

下面的代码输出是什么,为什么?

void foo(void)
{
	unsigned int a = 6;
	int b = -20;
	(a + b > 6)puts("> 6") : puts("<= 6");
}

这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是“>6”。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。

C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c;
c = a+++b;
这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:
c = a++ + b;
因此, 这段代码持行后a= 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是:这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题

线形表a、b为两个有序升序的线形表,编写一程序,使两个有序线形表合并成一个有序升序线形表h;

#include<stdio.h>
#include<malloc.h>

struct Node
{
	int data;
	Node *next;
};

void Create(Node **root)
{
	Node *p, *q;
	int count;

	if (NULL == *root)
		return;
	p = *root;
	printf("请输入节点的个数:");
	scanf_s("%d", &count);
	for (int i = 0; i < count; i++)
	{
		q = (Node*)malloc(sizeof(Node));
		printf("请输入节点数据:");
		scanf_s("%d", &(q->data));
		p->next = q;
		p = q;
		p->next = NULL;
	}
}

Node * Combine(Node *root1, Node *root2)
{
	Node *root3 = (Node*)malloc(sizeof(Node));
	Node *p1, *p2, *p3;
	p1 = root1->next;
	p2 = root2->next;
	p3 = root3;
	while (p1 != NULL && p2 != NULL)
	{
		if (p1->data < p2->data)
		{
			p3->next = p1;
			p3 = p3->next;
			p1 = p1->next;

		}
		else
		{
			p3->next = p2;
			p3 = p3->next;
			p2 = p2->next;
		}
	}
	while (p1 == NULL && p2 != NULL)
	{
		p3->next = p2;
		p3 = p3->next;
		p2 = p2->next;
	}
	while (p2 == NULL && p1 != NULL)
	{
		p3->next = p1;
		p3 = p3->next;
		p1 = p1->next;
	}
	return root3;
}

int main()
{
	Node *root1, *root2, *Combine_Root;
	Node *p, *q;
	root1 = (Node*)malloc(sizeof(Node));
	root2 = (Node*)malloc(sizeof(Node));
	Create(&root1);
	Create(&root2);
	Combine_Root = Combine(root1, root2);
	for (p = Combine_Root->next; p != NULL; p = p->next)
		printf("%d", p->data);
	free(root1);
	free(root2);
	for (p = Combine_Root; p != NULL; )
	{
		q = p;
		p = p->next;
		free(q);
	}
	return 0;
}

用递归算法判断数组a[N]是否为一个递增数组。

思路:递归的方法,记录当前最大的,并且判断当前的是否比这个还大,大则继续,否则返回false结束:

#include<stdio.h>

bool charge(int p[], int n)
{
	if (n == 1)
		return true;
	else
	{
		if (p[n - 1] > p[n - 2])
		{
			return charge(p, n - 1);
		}
		else
			return false;
	}
}

int main()
{
	int a[5] = { 1,2,3,4,5 };
	bool flag = false;
	flag = charge(a, 5);
	if (flag)
		printf("yes\n");
	else
		printf("false\n");
	system("pause");
	return 0;
}

编写算法,从10亿个浮点数当中,选出其中最大的10000个。

用外部排序,在《数据结构》书上有《计算方法导论》在找到第n大的数的算法上加工

编写一unix程序,防止僵尸进程的出现.

同学的4道面试题,应聘的职位是搜索引擎工程师,后两道超级难,(希望大家多给一些算发)
1.给两个数组和他们的大小,还有一动态开辟的内存,求交集,把交集放到动态内存dongtai,并且返回交集个数
long jiaoji(longa[],long b[],long alength,long blength,long* dongtai[])
2.单连表的建立,把’a’–'z’26个字母插入到连表中,并且倒叙,还要打印!
方法1:

typedef struct val
{
	int date_1;
	struct val *next;
}*p;

void main()
{
	char c;
	for (c = 122; c >= 97; c--)
	{
		p.date = c;
		p = p->next;
	}
	p.next = NULL;
}

方法2:

void main()
{
	node *p = NULL;
	node *q = NULL;
	node *head = (node*)malloc(sizeof(node));
	head->data = ''; head->next = NULL;
	node *first = (node*)malloc(sizeof(node));
	first->data = 'a'; first->next = NULL; head->next = first;
	p = first;
	int longth = 'z' - 'b';
	int i = 0;
	while (i <= longth)
	{
		node *temp = (node*)malloc(sizeof(node));
		temp->data = 'b' + i; temp->next = NULL; q = temp;
		head->next = temp; temp->next = p; p = q;
		i++;
	}
	print(head);
}

可怕的题目终于来了

象搜索的输入信息是一个字符串,统计300万输入信息中的最热门的前十条,我们每次输入的一个字符串为不超过255byte,内存使用只有1G,
请描述思想,写出算发(c语言),空间和时间复杂度,
7.国内的一些帖吧,如baidu,有几十万个主题,假设每一个主题都有上亿的跟帖子,怎么样设计这个系统速度最好,请描述思想,写出算发(c语言),空间和时间复杂度,

#include <string.h>
void main(void)
{
	char  *src = "hello,world";
	char  *dest = NULL;
	dest = (char  *)malloc(strlen(src));
	int  len = strlen(str);
	char  *d = dest;
	char  *s = src[len];
	while (len-- != 0)
		d++ = s--;
	printf("%s", dest);
}

找出错误!!

#include   "string.h"
#include"stdio.h"
#include"malloc.h"
void main(void)
{
	char   *src = "hello,world";
	char  *dest = NULL;
	dest = (char  *)malloc(sizeof(char)*(strlen(src) + 1));
	int  len = strlen(src);
	char  *d = dest;
	char  *s = src + len - 1;
	while (len-- != 0)
		*d++ = *s--;
	*d = '\0';
	printf("%s", dest);
}

判断字符串是否为回文

bool IsSymmetry(const char* p)
{
	assert(p != NULL);
	const char* q = p;
	int len = 0;
	while (*q++ != '\0')
	{
		len++;
	}
	bool bSign = true;
	q = p + len - 1;
	if (0 < len)
	{
		for (int i = 0; i < len / 2; i++)
		{
			if (*p++ != *q--) { bSign = false; break; };
		}
	}
	if (bSign == true)
	{
		printf("Yes!\n");
	}
	else
	{
		printf("No!\n");
	}
	return bSign;
}

ASDL使用的是什么协议?并进行简单描述?

全称Point to Point Protocol over Ethernet,意思是基于以太网的点对点协议。实质是以太网和拨号网络之间的一个中继协议,所以在网络中,它的物理结构与原来的LAN接入方式没有任何变化,只是用户需要在保持原接入方式的基础上,安装一个PPPoE客户端(这个是通用的)。之所以采用该方式给小区计时/计流量用户,是方便计算时长和流量。此类用户在使用上比包月用户增加了PPPoE虚拟拨号的过程。电信的ADSL接入也是需要安装使用PPPoE。

Static 作用是什么

  • 全局静态变量
    在全局变量前加上关键字static,全局变量就定义成一个全局静态变量.
    静态存储区,在整个程序运行期间一直存在。
    初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
    作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。
  • 局部静态变量
    在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。
    内存中的位置:静态存储区
    初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);
    作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;
  • 静态函数
    在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。
    函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突;
    warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰;
  • 类的静态成员
    在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用
  • 类的静态函数
    静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。
    在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);

什么是预编译,何时需要预编译?

预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等,就是为编译做的预备工作的阶段,主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。
c编译系统在对程序进行通常的编译之前,先进行预处理。c提供的预处理功能主要有以下三种:1)宏定义 2)文件包含 3)条件编译
1、总是使用不经常改动的大型代码体。
2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。

进程和线程的区别

什么是进程(Process):普通的解释就是,进程是程序的一次执行,而什么是线程(Thread),线程可以理解为进程中的执行的一段程序片段。在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:
进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。 一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。 同一进程中的两段代码不能够同时执行,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。

线程是指进程内的一个执行单元,也是进程内的可调度实体与进程的区别:
(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
(2)进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是.
(4)二者均可并发执行.

参考回答:
1)进程是cpu资源分配的最小单位,线程是cpu调度的最小单位。
2)进程有独立的系统资源,而同一进程内的线程共享进程的大部分系统资源,包括堆、代码段、数据段,每个线程只拥有一些在运行中必不可少的私有属性,比如tcb,线程Id,栈、寄存器。
3)一个进程崩溃,不会对其他进程产生影响;而一个线程崩溃,会让同一进程内的其他线程也死掉。
4)进程在创建、切换和销毁时开销比较大,而线程比较小。进程创建的时候需要分配系统资源,而销毁的的时候需要释放系统资源。进程切换需要分两步:切换页目录、刷新TLB以使用新的地址空间;切换内核栈和硬件上下文(寄存器);而同一进程的线程间逻辑地址空间是一样的,不需要切换页目录、刷新TLB。
5)进程间通信比较复杂,而同一进程的线程由于共享代码段和数据段,所以通信比较容易。

插入排序和选择排序

插入排序基本思想:(假定从大到小排序)依次从后面拿一个数和前面已经排好序的数进行比较,比较的过程是从已经排好序的数中最后一个数开始比较,如果比这个数,继续往前面比较,直到找到比它大的数,然后就放在它的后面,如果一直没有找到,肯定这个数已经比较到了第一个数,那就放到第一个数的前面。那么一般情况下,对于采用插入排序法去排序的一组数,可以先选 取第一个数做为已经排好序的一组数。然后把第二个放到正确位置。

选择排序(Selection Sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到所有元素均排序完毕。

运算符优先级问题

能正确表示a和b同时为正或同时为负的逻辑表达式是(D )。
A、(a>=0||b>=0)&&(a<0||b<0)
B、(a>=0&&b>=0)&&(a<0&&b<0)
C、(a+b>0)&&(a+b<=0)
D、a*b>0

以下关于运算符优先顺序的描述中正确的是©。
A、关系运算符<算术运算符<赋值运算符<逻辑与运算符
B、逻辑与运算符<关系运算符<算术运算符<赋值运算符
C、赋值运算符<逻辑与运算符<关系运算符<算术运算符
D、算术运算符<关系运算符<赋值运算符<逻辑与运算符

字符串倒序

写一个函数将"tom is cat" 倒序打印出来,即 “cat is tom”

#include <stdio.h>
#define SPACE ' '

void main()
{
	const char* str = "Tom is cat"; // 字符串
	char* p1 = (char *)str + strlen(str) - 1;
	char* p2 = p1+1; // 开始时,p1,p2都指向字符串结尾处
	char t = 0; // 临时变量,用来保存被临时替换为ENDL的字符
	while (str != p1--)
	{
		if (SPACE == *p1) 
		{
			// p1+1指向单词的第一个字母,p2指向单词的结尾,此时输出这个单词
			for (int i = 1; p1 + i != p2; ++i)
			{
				cout << *(p1 + i);
			}
			cout << ' ';
			p2 = p1;
		}
		if (str == p1)
		{
			for (int i = 0; p1 + i != p2; ++i)
			{
				cout << *(p1 + i);
			}
			cout << endl;
		}
	}
}

1)写一个递归函数将内存中的字符串翻转"abc"->“cba”

void str_reverse(char* p1, char* p2)
{
	if (p1 == p2)
		return;
	*p1 = (*p1) + (*p2);
	*p2 = (*p1) - (*p2);
	*p1 = (*p1) - (*p2);
	if (p1 == p2 - 1)
		return;
	else
		str_reverse(++p1, --p2);
}
void main()
{
	char ptest[] = "abcdefg";
	str_reverse(ptest, ptest + strlen(ptest) -1);
	printf(ptest);
}

2)写一个函数将"tom is cat" 将内存中的字符串翻转,即 “cat istomm”

#include <stdio.h>
#define SPACE ' '
#define ENDL '\0'
char s[] = "The quick brown fox jumps over the lazy dog";
void str_reverse(char* p1, char* p2)
{
	if (p1 == p2)
		return;
	*p1 = (*p1) + (*p2);
	*p2 = (*p1) - (*p2);
	*p1 = (*p1) - (*p2);
	if (p1 == p2 - 1)
		return;
	else
		str_reverse(++p1, --p2);
}
void str_word_reverse(char* str)
{
	char *q1 = str, *q2 = str, *t;
	while (*q1 == SPACE)
		q1++;
	if (*q1 == ENDL)
		return; //!
	else
		q2 = q1 + 1;
	while ((*q2 != SPACE) && (*q2 != ENDL))
		q2++;
	t = q2--;
	str_reverse(q1, q2);
	if (*t == ENDL)
		return;
	else
		str_word_reverse(t);
}

int main(int a, char** b)
{
	printf("%s\n", s);
	str_reverse(s, s + strlen(s) - 1);
	printf("%s\n", s);
	str_word_reverse(s);
	printf("%s\n", s);
	return 0;
}

Output:

The quick brown foxjumps over the lazy dog
god yzal eht revo spmuj xof nworb kciuq ehT
dog lazy the over jumps fox brown quick The


今天同学又问一道题,和上面有些类似,但是要求更严格了一些:
写一个递归函数将内存中的字符串翻转"abc"->“cba”,并且函数原型已确定:void reverse(char* p)
其实,要求越多,思路越确定,我的解如下:

#include <stdio.h>
#include <string.h>
char s[] = "0123456789";
#define ENDL '\0'
void reverse(char* p)
{
	//这是这种方法的关键,使用static为的是能用str_reverse的思路,但是不好
	static char* x = 0;
	if (x == 0)
		x = p;
	char* q = x + strlen(p) - 1;
	if (p == q)
		return;
	*q = (*p) ^ (*q);
	*p = (*p) ^ (*q);
	*q = (*p) ^ (*q);
	if (q == p + 1)
		return;
	reverse(++p);
}

//这种方法就直观多了,但是当字符串很长的时候就很低效
void reverse2(char* p)
{
	if (*(p + 1) == ENDL)
		return;
	char* o = p + strlen(p) - 1;
	char t = *o;
	for (; o != p; o--)
		*o = *(o - 1);
	*p = t;
	reverse2(p + 1);
}

int main(int c, char**argv)
{
	reverse2(s);
	printf("%s\n", s);
	return 0;
}

交换两个数的宏定义

交换两个参数值的宏定义为:

#define SWAP(a,b) (a)=(a)+(b);(b)=(a)-(b);(a)=(a)-(b);

Itearator各指针的区别

游标和指针
我说过游标是指针,但不仅仅是指针。游标和指针很像,功能很像指针,但是实际上,游标是通过重载一元的”*”和”->”来从容器中间接地返回一个值。将这些值存储在容器中并不是一个好主意,因为每当一个新值添加到容器中或者有一个值从容器中删除,这些值就会失效。在某种程度上,游标可以看作是句柄(handle)。通常情况下游标(iterator)的类型可以有所变化,这样容器也会有几种不同方式的转变:
iterator——对于除了vector以外的其他任何容器,你可以通过这种游标在一次操作中在容器中朝向前的方向走一步。这意味着对于这种游标你只能使用“++”操作符。而不能使用“–”或“+=”操作符。而对于vector这一种容器,你可以使用“+=”、“—”、“++”、“-=”中的任何一种操作符和“<”、“<=”、“>”、“>=”、“==”、“!=”等比较运算符。

C++中的class和struct的区别

从语法上,在C++中(只讨论C++中)。class和struct做类型定义时只有两点区别:
(一)默认继承权限。如果不明确指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理;
(二)成员的默认访问权限。class的成员默认是private权限,struct默认是public权限。
除了这两点,class和struct基本就是一个东西。语法上没有任何其它区别。

不能因为学过C就总觉得连C++中struct和class都区别很大,下面列举的说明可能比较无聊,因为struct和class本来就是基本一样的东西,无需多说。但这些说明可能有助于澄清一些常见的关于struct和class的错误认识:
(1)都可以有成员函数;包括各类构造函数,析构函数,重载的运算符,友元类,友元结构,友元函数,虚函数,纯虚函数,静态函数;
(2)都可以有一大堆public/private/protected修饰符在里边;
(3)虽然这种风格不再被提倡,但语法上二者都可以使用大括号的方式初始化:
A a = {1, 2, 3};不管A是个struct还是个class,前提是这个类/结构足够简单,比如所有的成员都是public的,所有的成员都是简单类型,没有显式声明的构造函数。
(4)都可以进行复杂的继承甚至多重继承,一个struct可以继承自一个class,反之亦可;一个struct可以同时继承5个class和5个struct,虽然这样做不太好。
(5)如果说class的设计需要注意OO的原则和风格,那么没任何理由说设计struct就不需要注意。
(6)再次说明,以上所有说法都是指在C++语言中,至于在C里的情况,C里是根本没有“class”,而C的struct从根本上也只是个包装数据的语法机制。

最后,作为语言的两个关键字,除去定义类型时有上述区别之外,另外还有一点点:“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。

  • 关于使用大括号初始化
    class和struct如果定义了构造函数的话,都不能用大括号进行初始化
    如果没有定义构造函数,struct可以用大括号初始化。
    如果没有定义构造函数,且所有成员变量全是public的话,可以用大括号初始化。
  • 关于默认访问权限
    class中默认的成员访问权限是private的,而struct中则是public的。
  • 关于继承方式
    class继承默认是private继承,而struct继承默认是public继承。
  • 关于模版
    在模版中,类型参数前面可以使用class或typename,如果使用struct,则含义不同,struct后面跟的是“non-type template parameter”,而class或typename后面跟的是类型参数。
  • class中有个默认的this指针,struct没有
    不同点:构造函数,析构函数 this 指针

有关重载函数

返回值类型不同构不成重载
参数参数顺序不同能构成重载
c++函数同名不同返回值不算重载!函数重载是忽略返回值类型的。
成员函数被重载的特征有:

  1. 相同的范围(在同一个类中);
  2. 函数名字相同;
  3. 参数不同;
  4. virtual关键字可有可无。
  5. 成员函数中 有无const (函数后面)也可判断是否重载

数据库与T-SQL语言

关系数据库是表的集合,它是由一个或多个关系模式定义。SQL语言中的数据定义功能包括对数据库、基本表、视图、索引的定义。

关系模型的基本概念

关系数据库以关系模型为基础,它有以下三部分组成:
●数据结构——模型所操作的对象、类型的集合
●完整性规则——保证数据有效、正确的约束条件
●数据操作——对模型对象所允许执行的操作方式
关系(Relation)是一个由行和列组成的二维表格,表中的每一行是一条记录(Record),每一列是记录的一个字段(Field)。表中的每一条记录必须是互斥的,字段的值必须具有原子性。

SQL语言概述

SQL(结构化查询语言)是关系数据库语言的一种国际标准,它是一种非过程化的语言。通过编写SQL,我们可以实现对关系数据库的全部操作。
●数据定义语言(DDL)——建立和管理数据库对象
●数据操纵语言(DML)——用来查询与更新数据
●数据控制语言(DCL)——控制数据的安全性

起来是一个很简单的问题,每一个使用过RDBMS的人都会有一个概念。
事务处理系统的典型特点是具备ACID特征。ACID指的是Atomic(原子的)、Consistent(一致的)、Isolated(隔离的)以及Durable(持续的),它们代表着事务处理应该具备的四个特征:

  • 原子性:组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分
  • 一致性:在事务处理执行之前和之后,数据是一致的。
  • 隔离性:一个事务处理对另一个事务处理没有影响。
  • 持续性:当事务处理成功执行到结束的时候,其效果在数据库中被永久纪录下来。

C语言中结构化程序设计的三种基本控制结构

顺序结构
选择结构
循环结构

CVS是什么

cvs(Concurrent Version System) 是一个版本控制系统。使用它,可以记录下你的源文件的历史。
例如,修改软件时可能会不知不觉混进一些 bug,而且可能过了很久你才会察觉到它们的存在。有了 cvs,你可以很容易地恢复旧版本,并从中看出到底是哪个修改导致了这个bug。有时这是很有用的。
CVS服务器端对每个文件维护着一个修订号,每次对文件的更新,都会使得文件的修订号加1。在客户端中也对每个文件维护着一个修订号,CVS通过这两个修订号的关系,来进行Update,Commit和发现冲突等操作操作

三种基本的数据模型

按照数据结构类型的不同,将数据模型划分为层次模型、网状模型和关系模型。

设计模式:工厂模式 和 单例模式 介绍一下?

工程模式即将对象创建过程封装即为工厂模式。
单例模式即整个类只有一个对象,并且不允许显示创建。

vector 和 list的区别?

1、概念:

1)Vector

  • 连续存储的容器,动态数组,在堆上分配空间
  • 底层实现:数组
  • 两倍容量增长:
    • vector 增加(插入)新元素时,如果未超过当时的容量,则还有剩余空间,那么直接添加到最后(插入指定位置),然后调整迭代器。
    • 如果没有剩余空间了,则会重新配置原有元素个数的两倍空间,然后将原空间元素通过复制的方式初始化新空间,再向新空间增加元素,最后析构并释放原空间,之前的迭代器会失效。
  • 性能:
    • 访问:O(1)
    • 插入:在最后插入(空间够):很快
    • 在最后插入(空间不够):需要内存申请和释放,以及对之前数据进行拷贝。
    • 在中间插入(空间够):内存拷贝
    • 在中间插入(空间不够):需要内存申请和释放,以及对之前数据进行拷贝。
    • 删除:在最后删除:很快
    • 在中间删除:内存拷贝
  • 适用场景:经常随机访问,且不经常对非尾节点进行插入删除。

2、List

  • 动态链表,在堆上分配空间,每插入一个元数都会分配空间,每删除一个元素都会释放空间。
  • 底层:双向链表
  • 性能:
    • 访问:随机访问性能很差,只能快速访问头尾节点。
    • 插入:很快,一般是常数开销
    • 删除:很快,一般是常数开销
  • 适用场景:经常插入删除大量数据

2、区别:

1)vector底层实现是数组;list是双向 链表。
2)vector支持随机访问,list不支持。
3)vector是顺序内存,list不是。
4)vector在中间节点进行插入删除会导致内存拷贝,list不会。
5)vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。
6)vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。

3、应用

vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用vector。
list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list。

纯虚函数是怎样实现的?在编译原理上讲一下?

在类内部添加一个虚拟函数表指针,该指针指向一个虚拟函数表,该虚拟函数表包含了所有的虚拟函数的入口地址,每个类的虚拟函数表都不一样,在运行阶段可以循此脉络找到自己的函数入口。
纯虚函数相当于占位符,先在虚函数表中占一个位置由派生类实现后再把真正的函数指针填进去。除此之外和普通的虚函数没什么区别。

抽象类为什么不能实例化?

抽象类中的纯虚函数没有具体的实现,所以没办法实例化。

在函数后面加个const是怎么理解的?

在函数后面加个const一般在类的成员函数中使用,表示这个函数不修改数据成员的值。
另:void set_prt_val(int val)const {*ptr= val}理解:指针ptr指向的内容不是类的数据成员,所以这可这么写:*ptr = val;但这个指针在这个函数中不能修改。如果写成这样:ptr = &i(假设i是另外一个整形变量)就不对了,因为改变了指针的内容。

进程间通信类型:

  • (1)环境变量、文件描述符 一般Unix环境下的父进程执行fork(),生成的子进程拥有了父进程当前设置的环境变量以及文件描述符;由于通信是一个单向的、一次性的通信,随后的父进程以及子进程后续的内容不能再能共享;
  • (2)命令行参数 大多数用户都使用过ShellExec相关的命令,此API可以打开新的进程,并可以通过接口里的输入参数进行信息共享;同样,他也是一个单项、一次性的通信;
  • (3)管道 使用文件和写方式访问公用的数据结构;管道分为匿名管道和命名管道,前者是用作关联进程间用,后者为无关联的进程使用;前者通过文件描述符或文件句柄提供对命名管道的访问,后者需要知道管道名称才能读写管道;一般来讲,读写的内容是字节流,需要转换为有意义的结构才有意义;
  • (4)共享内存 进程需要可以被其他进程访问浏览的进程块;进程间共享内存的关系与函数间共享全局变量的关系类似
  • (5)DDE动态数据交互

线程间通信类型:
(1)全局数据;
(2)全局变量;
(3)全局数据结构;
(4)线程间通信的参数:pThread_create这类API接口中的参数

关于内存对齐的问题以及sizof()的输出

答:编译器自动对齐的原因:为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

winsock建立连接的主要实现步骤?

答:
TCP:服务器端:1.socket()建立套接字,2将套接字绑定到本地地址和端口上,绑定(bind)3.将套接字设为监听模式,准备接收客户端,监听(listen);4.等待客户端请求到来,请求到来后,连接请求,并返回一个新的对应此连接的套接字,accept()5.用返回的套接字和客户端进行通讯(send/recv);6.返回并等待另一客户请求。7.关闭套接字。
客户端:1.socket()建立套接字2.向服务器发出连接请求,(connect)2。和服务器进行通信,send()和recv(),在套接字上写读数据,直至数据交换完毕;4closesocket()关闭套接字。
UDP:1服务器端:1.创建套接字(socekt)2.将套接字绑定到本地地址和端口上(bind);3.等待接收数据(recvfrom);4.closesocket()关闭套接字。

客户端:1.创建套接字(socekt)2,向服务器端发送数据(sendto)3.closesocket()关闭套接字。

C++中为什么用模板类。

答:(1)可用来创建动态增长和减小的数据结构
(2)它是类型无关的,因此具有很高的可复用性。
(3)它在编译时而不是运行时检查数据类型,保证了类型安全
(4)它是平台无关的,可移植性
(5)可用于基本数据类型

动态连接库的两种方式?

答:调用一个DLL中的函数有两种方法:
1.载入时动态链接(load-time dynamiclinking),模块非常明确调用某个导出函数,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。
2.运行时动态链接(run-time dynamiclinking),运行时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的出口地址,然后就可以通过返回的函数指针调用DLL函数了。如此即可避免导入库文件了。

CSingleLock是干什么的。

答:同步多个线程对一个数据类的同时访问

编写strcat函数(6分)

已知strcat函数的原型是char *strcat (char strDest, const charstrSrc);
其中strDest 是目的字符串,strSrc 是源字符串。
(1)不调用C++/C 的字符串库函数,请编写函数strcat
答:
VC源码:

char * __cdecl strcat(char * dst, const char * src)
{
	assert((dst != NULL) && (src != NULL));
	char * cp = dst;
	while (*cp)
		cp++; /* find end of dst */
	while ( (*cp++ = *src++) != '\0'); /* Copy src to end of dst */
	return dst; /* return dst */
}

(2)strcat能把strSrc的内容连接到strDest,为什么还要char * 类型的返回值?
答:方便赋值给其他变量

编写类String 的构造函数、析构函数和赋值函数(25 分)

已知类String 的原型为:

class String
{
public:
	String(const char *str = NULL); // 普通构造函数
	String(const String &other); // 拷贝构造函数
	~String(void); // 析构函数
	String & operate = (const String &other); // 赋值函数
private:
	char *m_data; // 用于保存字符串
};

请编写String 的上述4 个函数。
标准答案:

String::String(const char *str)
{
	if (str == NULL) //strlen在参数为NULL时会抛异常才会有这步判断
	{
		m_data = new char[1];
		m_data[0] = '\0';
	}
	else
	{
		m_data = new char[strlen(str) + 1];
		strcpy(m_data, str);
	}

}
String::String(const String &another)
{
	m_data = new char[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 = new char[strlen(rhs.m_data) + 1];
	strcpy(m_data, rhs.m_data);
	return *this;
}

String::~String()
{
	delete[]m_data;
}

本文标签: 最新版 宝典