标签:des style http color io os 使用 ar java
delete会调用对象的析构函数,和new相应free仅仅会释放内存,new调用构造函数。malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同一时候要自己主动运行构造函数,对象在消亡之前要自己主动运行析构函数。因为malloc/free是库函数而不是运算符,不在编译器控制权限之内,不可以把运行构造函数和析构函数的任务强加于malloc/free。因此C++语言须要一个能完毕动态内存分配和初始化工作的运算符new,以及一个能完毕清理与释放内存工作的运算符delete。注意new/delete不是库函数。
delete仅仅会调用一次析构函数,而delete[]会调用每个成员的析构函数。在More Effective 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就会觉得指向的是单个对象,否则,它就会觉得指向的是一个数组。
类继承是在编译时刻静态定义的,且可直接使用,类继承能够较方便地改变父类的实现。可是类继承也有一些不足之处。首先,由于继承在编译时刻就定义了,所以无法在执行时刻改变从父类继承的实现。更糟的是,父类通常至少定义了子类的部分行为,父类的不论什么改变都可能影响子类的行为。假设继承下来的实现不适合解决新的问题,则父类必须重写或被其它更适合的类替换。这样的依赖关系限制了灵活性并终于限制了复用性。
(待补充)
封装,继承和多态。
在面向对象程序设计语言中,封装是利用可重用成分构造软件系统的特性,它不仅支持系统的可重用性,并且还有利于提高系统的可扩充性;消息传递能够实现发送一个通用的消息而调用不同的方法;封装是实现信息隐蔽的一种技术,其目的是使类的定义和实现分离。
析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经所有销毁了定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数JAVA无析构函数深拷贝和浅拷贝
多态:是对于不同对象接收同样消息时产生不同的动作。C++的多态性详细体如今执行和编译两个方面:在程序执行时的多态性通过继承和虚函数来体现;
在程序编译时多态性体如今函数和运算符的重载上
虚函数:在基类中冠以keyword virtual 的成员函数。 它提供了一种接口界面。同意在派生类中对基类的虚函数又一次定义。
纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类依据须要对它进行定义。作为接口而存在 纯虚函数不具备函数的功能,一般不能直接被调用。
从基类继承来的纯虚函数,在派生类中仍是虚函数。假设一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。
抽象类中不仅包含纯虚函数,也可包含虚函数。l抽象类必须用作派生其它类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持执行时多态性。
int func(x)
{
int countx = 0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;
}
假定x = 9999。 答案:8
思路:将x转化为2进制,看含有的1的个数。
答:引用就是某个目标变量的“别名”(alias),相应用的操作与对变量直接操作效果全然同样。申明一个引用的时候,切记要对其进行初始化。引用声明完成后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其它变量名的别名。声明一个引用,不是新定义了一个变量,它仅仅表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。
(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形參就成为原来主调函数中的实參变量或对象的一个别名来使用,所以在被调函数中对形參变量的操作就是对其对应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的參数,在内存中并没有产生实參的副本,它是直接对实參操作;而使用一般变量传递函数的參数,当发生函数调用时,须要给形參分配存储单元,形參变量是实參变量的副本;假设传递的是对象,还将调用拷贝构造函数。因此,当參数传递的数据较大时,用引用比用一般变量传递參数的效率和所占空间都好。
(3)使用指针作为函数的參数尽管也能达到与使用引用的效果,可是,在被调函数中相同要给形參分配存储单元,且须要反复使用"*指针变量名"的形式进行运算,这非常easy产生错误且程序的阅读性较差;还有一方面,在主调函数的调用点处,必须用变量的地址作为实參。而引用更easy使用,更清晰。
假设既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式: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( )和"hello world"串都会产生一个暂时对象,而在C++中,这些暂时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。引用型參数应该在能被定义为const的情况下,尽量定义为const 。
格式:类型标识符 &函数名(形參列表及类型说明){ //函数体 }
优点:在内存中不产生被返回值的副本;(注意:正是由于这点原因,所以返回一个局部变量的引用是不可取的。由于随着该局部变量生存期的结束,对应的引用也会失效,产生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 <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; }
}
(5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,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;
指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传ref和pointer的差别。
流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的參数、赋值操作符=的參数、其他情况都推荐使用引用。以上 2-8 參考:http://develop.csai.cn/c/NO0000021.htm
(1). 结构和联合都是由多个不同的数据类型成员组成, 但在不论什么同一时刻, 联合中仅仅存放了一个被选中的成员(全部成员共用一块地址空间), 而结构的全部成员都存在(不同成员的存放地址不同)。
(2). 对于联合的不同成员赋值, 将会对其他成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
a)
#i nclude <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)
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共用一块地址空间)
涉及到UML中的一些概念:关联是表示两个类的一般性联系,比方“学生”和“老师”就是一种关联关系;聚合表示has-a的关系,是一种相对松散的关系,聚合类不须要对被聚合类负责,例如以下图所看到的,用空的菱形表示聚合关系:从实现的角度讲,聚合能够表示为:
class A {...} class B { A* a; .....}
而组合表示contains-a的关系,关联性强于聚合:组合类与被组合类有同样的生命周期,组合类要对被组合类负责,採用实心的菱形表示组合关系:实现的形式是:
class A{...} class B{ A a; ...}
1. 封装:将客观事物抽象成类,每一个类对自身的数据和方法实行protection(private, protected,public)
2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗口使用父窗口的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。
3. 多态:是将父对象设置成为和一个或很多其它的他的子对象相等的技术,赋值之后,父对象就能够依据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:同意将子类类型的指针赋值给父类类型的指针。
常考的题目。从定义上来说:
重载:是指同意存在多个同名函数,而这些函数的參数表不同(也许參数个数不同,也许參数类型不同,也许两者都不同)。
重写:是指子类又一次定义父类虚函数的方法。
从实现原理上来说:
重载:编译器依据函数不同的參数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这种)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这种:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
重写:和多态真正相关。当子类又一次定义了父类的虚函数后,父类指针依据赋给它的不同的子类指针,动态的调用属于子类的该函数,这种函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这种函数地址是在执行期绑定的(晚绑定)。
主要是两个:
1. 隐藏实现细节,使得代码可以模块化;扩展代码模块,实现代码重用;
2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。
除了“可以让应用程序处理存储于DBMS 中的数据“这一基本相似点外,两者没有太多共同之处。可是Ado使用OLE DB 接口并基于微软的COM 技术,而ADO.NET 拥有自己的ADO.NET 接口而且基于微软的.NET 体系架构。众所周知.NET 体系不同于COM 体系,ADO.NET 接口也就全然不同于ADO和OLE DB 接口,这也就是说ADO.NET 和ADO是两种数据訪问方式。ADO.net 提供对XML 的支持。
答案:都是在堆(heap)上进行动态的内存操作。用malloc函数须要指定内存分配的字节数而且不能初始化对象,new 会自己主动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.
答案:i 为30。
答案:当类中含有const、reference 成员变量;基类的构造函数都须要初始化表。
答案:不是。两个不同类型的指针之间能够强制转换(用reinterpret cast)。C#是类型安全的。
答案:全局对象的构造函数会在main 函数之前运行。
1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个执行期间都存在。比如全局变量,static 变量。
2) 在栈上创建。在运行函数时,函数内局部变量的存储单元都能够在栈上创建,函数运行结束时这些存储单元自己主动被释放。栈内存分配运算内置于处理器的指令集。
3) 从堆上分配,亦称动态内存分配。程序在执行的时候用malloc 或new 申请随意多少的内存,程序猿自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序猿决定,使用很灵活,但问题也最多。
答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和 class 在其它方面是功能相当的。从感情上讲,大多数的开发人员感到类和结构有非常大的区别。感觉上结构只象一堆缺乏封装和功能的开放的内存位,而类就象活的而且可靠的社会成员,它有智能服务,有坚固的封装屏障和一个良好定义的接口。既然大多数人都这么觉得,那么唯独在你的类有非常少的方法而且有公有数据(这样的事情在良好设计的系统中是存在的!)时,你或许应该使用 struct keyword,否则,你应该使用 class keyword。
答案:假设不是零,请解释一下编译器为什么没有让它为零。(Autodesk)肯定不是零。举个反例,假设是零的话,声明一个class A[10]对象数组,而每个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。
答案:通用寄存器给出的地址,是段内偏移地址,对应段寄存器地址*10H+通用寄存器内地址,就得到了真正要訪问的地址。
重点是static_cast, dynamic_cast和reinterpret_cast的差别和应用。
dynamic_casts在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上,它被用于安全地沿着类的继承关系向下进行类型转换。如你想在没有继承关系的类型中进行转换,你可能想到static_cast
答案:
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作用:定义常量、修饰函数參数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,能够预防意外的变动,能提高程序的健壮性。
1) const 常量有数据类型,而宏常量没有数据类型。编译器能够对前者进行类型安全检查。而对后者仅仅进行字符替换,没有类型安全检查,而且在字符替换可能会产生意料不到的错误。
2) 有些集成化的调试工具能够对const 常量进行调试,可是不能对宏常量进行调试。
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针能够随时指向随意类型的内存块。
(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 字节
}
答案:a.成员函数被重载的特征:
(1)同样的范围(在同一个类中);
(2)函数名字同样;
(3)參数不同;
(4)virtual keyword可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字同样;
(3)參数同样;
(4)基类函数必须有virtual keyword。
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则例如以下:
(1)假设派生类的函数与基类的函数同名,可是參数不同。此时,不论有无virtualkeyword,基类的函数将被隐藏(注意别与重载混淆)。
(2)假设派生类的函数与基类的函数同名,而且參数也同样,可是基类函数没有virtual keyword。此时,基类的函数被隐藏(注意别与覆盖混淆)
There are two int variables: a and b, don’t use “if”, “? :”, “switch”or other judgement statements, find out the biggest one of the two numbers.
答案:( ( a + b ) + abs( a - b ) ) / 2
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系统提前定义宏,这样的宏并非在某个文件里定义的,而是由编译器定义的。
答案:能够,能够用_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 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. The functions passed to _onexit
cannot take parameters.
答案:
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
答案:
#i nclude<iostream>
#i nclude<fstream>
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( void )
{
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 ( 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 ;
----------
41. 分析一下这段程序的输出 (Autodesk)
class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i) //B(int) works as a converter ( int -> instance of B)
{
cout<<"constructed by parameter " << data <<endl;
}
private:
int data;
};
B Play( B b)
{
return b ;
}
(1) results:
int main(int argc, char* argv[]) constructed by 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[]) constructed by parameter 5
{ destructed B(5)形參析构
B t1 = Play(5); B t2 = Play(10); constructed by parameter 10
return 0; destructed B(10)形參析构
} destructed t2 注意顺序!
destructed t1
答案:
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 ;
}
KMP算法效率最好,时间复杂度是O(n+m),
46.多重继承的内存分配问题:
比方有class A : public class B, public class C {} 那么A的内存结构大致是怎么样的?
这个是compiler-dependent的, 不同的实现其细节可能不同。假设不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。能够參考《深入探索C++对象模型
47.怎样推断一个单链表是有环的?(注意不能用标志位,最多仅仅能用两个额外指针)
struct node { char val; node* next;}
bool check(const node* head) {} //return false : 无环;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;
}
分析这些面试题,本身包括非常强的趣味性;而作为一名研发人员,通过对这些面试题的深入剖析则可进一步增强自身的内功。
2.找错题 试题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的工作方式,
总分值为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;
}
从2分到10分的几个答案我们能够清楚的看到,小小的strcpy居然暗藏着这么多玄机,真不是盖的!须要多么扎实的基本功才干写一个完美的strcpy啊!
(4)对strlen的掌握,它没有包含字符串末尾的‘\0‘。
读者看了不同分值的strcpy版本号,应该也能够写出一个10分的strlen函数了,完美的版本号为: int strlen( const char *str ) //输入參数const 下面是引用片段:
{
assert( strt != NULL ); //断言字符串地址非0
int len=0; //注,一定要初始化。
while( (*str++) != ‘\0‘ )
{
len++;
}
return len;
}
试题4:下面是引用片段:
void GetMemory( char *p )
{
p = (char *) malloc( 100 );
}
void Test( void )
{
char *str = NULL;
GetMemory( str );
strcpy( str, "hello world" );
printf( str );
}
试题5:
下面是引用片段:
char *GetMemory( void )
{
char p[] = "hello world";
return p;
}
void Test( void )
{
char *str = NULL;
str = GetMemory();
printf( str );
}
试题6:下面是引用片段:
void GetMemory( char **p, int num )
{
*p = (char *) malloc( num );
}
void Test( void )
{
char *str = NULL;
GetMemory( &str, 100 );
strcpy( str, "hello" );
printf( str );
}
试题7:下面是引用片段:
void Test( void )
{
char *str = (char *) malloc( 100 );
strcpy( str, "hello" );
free( str );
... //省略的其他语句
}
解答:试题4传入中GetMemory( char *p )函数的形參为字符串指针,在函数内部改动形參并不能真正的改变传入形參的值,运行完
char *str = NULL;
GetMemory( str );
后的str仍然为NULL;试题5中
char p[] = "hello world";
return p;
的p[]数组为函数内的局部自己主动变量,在函数返回后,内存已经被释放。这是很多程序猿常犯的错误,其根源在于不理解变量的生存期。
试题6的GetMemory避免了试题4的问题,传入GetMemory的參数为字符串指针的指针,可是在GetMemory中运行申请内存及赋值语句 tiffanybracelets
*p = (char *) malloc( num );
后未推断内存是否申请成功,应加上:
if ( *p == NULL )
{
...//进行申请内存失败处理
}
试题7存在与试题6相同的问题,在运行
char *str = (char *) malloc(100);
后未进行内存是否申请成功的推断;另外,在free(str)后未置str为空,导致可能变成一个“野”指针,应加上:
str = NULL;
试题6的Test函数中也未对malloc的内存进行释放。
剖析:
试题4~7考查面试者对内存操作的理解程度,基本功扎实的面试者一般都能正确的回答当中50~60的错误。可是要全然解答正确,却也绝非易事。
对内存操作的考查主要集中在:
(1)指针的理解;
(2)变量的生存期及作用范围;
(3)良好的动态内存申请和释放习惯。
再看看以下的一段程序有什么错误:
下面是引用片段:
swap( int* p1,int* p2 )
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
在swap函数中,p是一个“野”指针,有可能指向系统区,导致程序执行的崩溃。在VC++中DEBUG执行时提示错误“Access Violation”。该程序应该改为
下面是引用片段:
swap( int* p1,int* p2 )
{
int p;
p = *p1;
*p1 = *p2;
*p2 = 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(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 ;
}
答:防止该头文件被反复引用。
答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。
C++语言支持函数重载,C语言不支持函数重载。C++提供了C连接交换指定符号extern “C”
解决名字匹配问题。
首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的keyword,该keyword告诉编译器,其声明的函数和变量能够在本模块或其他模块中使用。
通常,在模块的头文件里对本模块提供给其他模块引用的函数和全局变量以keywordextern声明。比如,假设模块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, int y );
#endif
在模块B中引用该函数:
// 模块B实现文件 moduleB.cpp
#i nclude "moduleA.h"
foo(2,3);
加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, int y ) ,则模块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函数样例project中包括的三个文件的源码例如以下:
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c语言实现文件:cExample.c */
#i nclude "cExample.h"
int add( int x, int y )
{
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++函数样例project中包括的三个文件的源码例如以下:
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++实现文件 cppExample.cpp
#i nclude "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C实现文件 cFile.c
/* 这样会编译出错:#i nclude "cExample.h" */
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
15题目的解答请參考《C++中extern “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
2. 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
1.完毕下列程序
*
*.*.
*..*..*..
*...*...*...*...
*....*....*....*....*....
*.....*.....*.....*.....*.....*.....
*......*......*......*......*......*......*......
*.......*.......*.......*.......*.......*.......*.......*.......
#include
#define N 8
int main()
{
int i;
int j;
int k;
---------------------------------------------------------
| |
| |
| |
---------------------------------------------------------
return 0;
}
2.完毕程序,实现对数组的降序排序
#include
void sort( );
int main()
{
int array[]={45,56,76,234,1,34,23,2,3}; //数字任//意给出
sort( );
return 0;
}
void sort( )
{
____________________________________
| |
| |
|-----------------------------------------------------|
}
3.费波那其数列,1,1,2,3,5……编敲代码求第十项。能够用递归,也能够用其
他方法,但要说明你选择的理由。
#include
int Pheponatch(int);
int main()
{
printf("The 10th is %d",Pheponatch(10));
return 0;
}
int Pheponatch(int N)
{
--------------------------------
| |
| |
--------------------------------
}
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;
}
}
────────────────────────────────────────
应用层:为应用程序提供服务
表示层:处理在两个通信系统中交换信息的表示方式
会话层:负责维护两个结点间会话连接的建立、管理和终止,以及数据交换
传输层:向用户提供可靠的端到端服务。UDP TCP协议。
网络层:通过路由选择算法为分组通过通信子网选择最适当的路径,以及实现拥塞控制、网络互联等功能。传输数据单元是分组。IP地址,路由器,IP协议。
数据链路层:在物理层提供的服务基础上,数据链路层在通信的实体间建立数据链路连接,传输一帧为单位的数据包(,并採用差错控制与流量控制方法,使有差错的物理线路变成无差错的数据链路。)
物理层:传输比特流。传输单元是比特。调制解调器。
网络层。
交换机:数据链路层。路由器:网络层。
全局变量的生命周期是整个程序执行的时间,而局部变量的生命周期则是局部函数或过程调用的时间段。事实上现是由编译器在编译时採用不同内存分配方法。全局变量在main函数调用后,就開始分配,假设是静态变量则是在main函数前就已经初始化了。而局部变量则是在用户栈中动态分配的(还是建议看编译原理中的活动记录这一块)
8086微处理器共同拥有4个16位的段寄存器,在寻址内存单元时,用它们直接或间接地存放段地址。
代码段寄存器CS:存放当前运行的程序的段地址。
数据段寄存器DS:存放当前运行的程序所用操作数的段地址。
堆栈段寄存器SS:存放当前运行的程序所用堆栈的段地址。
附加段寄存器ES:存放当前运行程序中一个辅助数据段的段地址。
由cs:ip构成指令地址,ss:sp构成堆栈的栈顶地址指针。DS和ES用作数据段和附加段的段地址(段起始地址或段值)
8086/8088微处理器的存储器管理
1.地址线(码)与寻址范围:N条地址线 寻址范围=2N
2.8086有20地址线 寻址范围为1MB 由 00000H~FFFFFH
3. 8086微处理器是一个16位结构,用户可用的寄存器均为16位:寻址64KB
4. 8086/8088採用分段的方法对存储器进行管理。详细做法是:把1MB的存储器空间分成若干段,每段容量为64KB,每段存储器的起始地址必须是一个能被16整除的地址码,即在20位的二进制地址码中最低4位必须是“0”。每一个段首地址的高16位二进制代码就是该段的段号(称段基地址)或简称段地址,段号保存在段寄存器中。我们可对段寄存器设置不同的值来使微处理器的存储器訪问指向不同的段。
5.段内的某个存储单元相对于该段段首地址的差值,称为段内偏移地址(也叫偏移量)用16位二进制代码表示。
6.物理地址是由8086/8088芯片地址引线送出的20位地址码,它用来參加存储器的地址译码,终于读/写所訪问的一个特定的存储单元。
7.逻辑地址由某段的段地址和段内偏移地址(也叫偏移量)两部分所组成。写成:
段地址:偏移地址(比如,1234H:0088H)。
8.在硬件上起作用的是物理地址,物理地址=段基地址×10H十偏移地址
联想笔试题
1.设计函数 int atoi(char *s)。
2.int i=(j=4,k=8,l=16,m=32); printf(“%d”, i); 输出是多少?
普天C++笔试题
1.实现双向链表删除一个节点P,在节点P后插入一个节点,写出这两个函数。
2.写一个函数,将当中的\t都转换成4个空格。
4.怎样定义和实现一个类的成员函数为回调函数?
5.解释堆和栈的差别。
6.C++里面怎样声明const void f(void)函数为C程序中的库函数?
7.下列哪两个是等同的
int b;
A const int* a = &b;
B const* int a = &b;
C const int* const a = &b;
D int const* const a = &b;
8.内联函数在编译时是否做參数类型检查?
void g(base & b){
b.play;
}
void main(){
son s;
g(s);
return;
}
大唐电信
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×(101)+2=29,问这是怎么回事?
12.三篇写作,均为书信形式。
(1)一片中文的祝贺信,祝贺某男当了某公司xx
(2)两篇英文的,一是说有事不能应邀,派别人去;还有一篇是讨债的,7天不给钱就
走人(主要考business letter格式)。
大唐面试试题
1.什么是中断?中断发生时CPU做什么工作?
2.CPU在上电后,进入操作系统的main()之前必须做什么工作?
3.简述ISO OSI的物理层Layer1,链路层Layer2,网络层Layer3的任务。
4.有线电话和无线电话有何差别?无线电话特别须要注意的是什么?
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触发器、与或非门组成二分频电路?
答 、1.限制变量的作用域(文件级的)。
2.设置变量的存储域(全局数据区)。
答 、1) 引用必须被初始化,指针不必。
2) 引用初始化以后不能被改变,指针能够改变所指的对象。
3) 不存在指向空值的引用,可是存在指向空值的指针。
答 、在特定时间内完毕特定的任务,实时性与可靠性。
答 、全局变量储存在静态数据区,局部变量在堆栈中。
答 、左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于1。
答 、1.没有回收垃圾资源
2.层次太深的递归调用
答 、constructor
Deconstructor 能够声明为虚函数。
系统为一个空类创建的成员函数有那些。
答 、O(n^2)
答 、if(x>0.000001&&x<-0.000001)
答 、tcp/ip 应用层/传输层/网络层/数据链路层/物理层
答 、ARP (Address Resolution Protocol)(地址解析协议)
答 、IP地址由两部分组成,网络号和主机号。只是是要和“子网掩码”按位与之后才干区分哪些是网络位哪些是主机位。
答 、循环链表,用取余操作做
答 、switch的參数不能为实型。
华为
答、能,局部会屏蔽全局。要用全局变量,须要使用"::"
局部变量能够与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内能够定义多个同名的局部变量,比方在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内
答 、能够用引用头文件的方式,也能够用externkeyword,假设用引用头文件方式来引用某个在头文件里声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,假设你用extern方式引用时,假定你犯了相同的错误,那么在编译期间不会报错,而在连接期间报错
答 、能够,在不同的C文件里以static形式来声明同名全局变量。
能够在不同的C文件里声明同名的全局变量,前提是当中仅仅能有一个C文件里对此变量赋初值,此时连接不会出错
答 、和while(1)同样。
答 、前一个循环一遍再推断,后一个推断以后再循环
#i nclude
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 0;
}
答 、10,12,120
static全局变量与普通的全局变量有什么差别?static局部变量和普通局部变量有什么差别?static函数与普通函数有什么差别?
答 、全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的差别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件里都是有效的。 而静态全局变量则限制了其作用域, 即仅仅在定义该变量的源文件内有效, 在同一源程序的其他源文件里不能使用它。因为静态全局变量的作用域局限于一个源文件内,仅仅能为该源文件内的函数公用, 因此能够避免在其他源文件里引起错误。
从以上分析能够看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。
static函数与普通函数作用域不同。仅在本文件。仅仅在当前源文件里使用的函数应该说明为内部函数(static),内部函数应该在当前源文件里说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件里说明,要使用这些函数的源文件要包括这个头文件
static全局变量与普通的全局变量有什么差别:static全局变量仅仅初使化一次,防止在其它文件单元中被引用;
static局部变量和普通局部变量有什么差别:static局部变量仅仅被初始化一次,下一次根据上一次结果值;
static函数与普通函数有什么差别:static函数在内存中仅仅有一份,普通函数在每一个被调用中维持一份拷贝
程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。
typedef union {long i; int k[5]; char c;} DATE;
struct data { int cat; DATE cow; double dog;} too;
DATE max;
则语句 printf("%d",sizeof(struct date)+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
答 、应该是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), 比上面的几种方法要好。
答 、函数名: atol
功 能: 把字符串转换成长整型数
用 法: long atol(const char *nptr);
程序例:
#include
#include
int main(void)
{
long l;
char *str = "98765432";
l = atol(lstr);
printf("string = %s integer = %ld\n", str, l);
return(0);
}
答 、c用宏定义,c++用inline
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1). #define 语法的基本知识(比如:不能以分号结束,括号的使用,等等)
2). 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是怎样计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3). 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4). 假设你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象非常重要。
#define MIN(A,B) ((A) <= (B) (A) : (B))
这个測试是为以下的目的而设的:
1). 标识#define在宏中应用的基本知识。这是非常重要的,由于直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,
对于嵌入式系统来说,为了能达到要求的性能,嵌入代码常常是必须的方法。
2). 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个使用方法是非常重要的。
3). 懂得在宏中小心地把參数用括号括起来
4). 我也用这个问题開始讨论宏的副作用,比如:当你写以下的代码时会发生什么事?
least = MIN(*p++, b);
假设你不知道答案,请看參考文献1。这问题对区分一个正常的伙计和一个书呆子是非常实用的。仅仅有书呆子才会读C语言课本的附录去找出象这样的
问题的答案。当然假设你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
死循环(Infinite loops)
这个问题用几个解决方式。我首选的方案是:
while(1)
{
}
一些程序猿更喜欢例如以下方案:
for(;;)
{
}
这个实现方式让我为难,由于这个语法没有确切表达究竟怎么回事。假设一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的
基本原理。假设他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序猿(这或许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序猿。
数据声明(Data declarations)
a) 一个整型数(An integer)
b) 一个指向整型数的指针(A pointer to an integer)
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)
d) 一个有10个整型数的数组(An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型參数并返回一个整型数(A pointer to a function that 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 return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer 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 and return an integer
人们常常声称这里有几个问题是那种要翻一下书才干回答的问题,我允许这样的说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。
可是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。由于在被面试的这段时间里,我确定我知道这个问题的答案。应试者假设不知道
全部的答案(或至少大部分答案),那么也就没有为这次面试做准备,假设该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
Static
这个简单的问题非常少有人能回答全然。在C语言中,keywordstatic有三个明显的作用:
1). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2). 在模块内(但在函数体外),一个被声明为静态的变量能够被模块内所用函数訪问,但不能被模块外其他函数訪问。它是一个本地的全局变量。
3). 在模块内,一个被声明为静态的函数仅仅可被这一模块内的其他函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是非常少的人能懂得第三部分。这是一个应试者的严重的缺点,由于他显然不懂得本地化数
据和代码范围的优点和重要性。
Const
我仅仅要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里全然概括了const的全部使用方法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该很熟悉const能做什么和不能做什么.假设你从没有读到那篇文章,仅仅要能说出const意味着“仅仅读”就能够了。虽然这个答案不是全然的答案,但我接受它作为一个正确的答案。(假设你想知道更具体的答案,细致读一下Saks的文章吧。)假设应试者能正确回答这个问题,我将问他一个附加的问题:以下的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可改动的,但指针能够)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是能够改动的,但指针是不可改动的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可改动的,同一时候指针也是不可改动的)。假设应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,或许你可能会问,即使不用keyword const,也还是能非常easy写出功能正确的程序,那么我为什么还要如此看重keywordconst呢?我也例如以下的几下理由:
1). keywordconst的作用是为给读你代码的人传达非常实用的信息,实际上,声明一个參数为常量是为了告诉了用户这个參数的应用目的。假设你曾花非常多时间清理其他人留下的垃圾,你就会非常快学会感谢这点多余的信息。(当然,懂得用const的程序猿非常少会留下的垃圾让别人来清理的。)
2). 通过给优化器一些附加的信息,使用keywordconst或许能产生更紧凑的代码。
3). 合理地使用keywordconst能够使编译器非常自然地保护那些不希望被改变的參数,防止其被无意的代码改动。简而言之,这样能够降低bug的出现。
Volatile
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去如果这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地又一次读取这个变量的值,而不是使用保存在寄存器里的备份。以下是volatile变量的几个样例:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会訪问到的非自己主动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我觉得这是区分C程序猿和嵌入式系统程序猿的最主要的问题。嵌入式系统程序猿常常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
如果被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将略微深究一下,看一下这家伙是不是直正懂得volatile全然的重要性。
1). 一个參数既能够是const还能够是volatile吗?解释为什么。
2). 一个指针能够是volatile 吗?解释为什么。
3). 以下的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
以下是答案:
1). 是的。一个样例是仅仅读的状态寄存器。它是volatile由于它可能被意想不到地改变。它是const由于程序不应该试图去改动它。
2). 是的。虽然这并不非经常见。一个样例是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,可是,因为*ptr指向一个volatile型參数,编译器将产生类似以下的代码:
int square(volatile int *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。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。假设你答错了这个问题,你也就到了得不到这份工作的边缘。
int a = 5, b = 7, c;
c = a+++b;
这个问题将做为这个測验的一个愉快的结尾。无论你相不相信,上面的样例是全然合乎语法的。问题是编译器怎样处理它?水平不高的编译作者实际上会争论这个问题,依据最处理原则,编译器应当能处理尽可能全部合法的使用方法。因此,上面的代码被处理成:
c = a++ + b;
因此, 这段代码持行后a = 6, b = 7, c = 12。
假设你知道答案,或猜出正确答案,做得好。假设你不知道答案,我也不把这个当作问题。我发现这个问题的最大优点是:这是一个关于代码编写风格,代码的可读性,代码的可改动性的好的话题
今天早上的面试题9道,比較难,
答案在 请化大学 严锐敏《数据结构第二版》第二章例题,数据结构其中,这个叫做:两路归并排序
Linklist *unio(Linklist *p,Linklist *q){
linklist *R,*pa,*qa,*ra;
pa=p;
qa=q;
R=ra=p;
while(pa->next!=NULL&&qa->next!=NULL){
if(pa->data>qa->data){
ra->next=qa;
qa=qa->next;
}
else{
ra->next=pa;
pa=pa->next;
}
}
if(pa->next!=NULL)
ra->next=pa;
if(qa->next!=NULL)
ra->next==qa;
return R;
}
递归的方法,记录当前最大的,而且推断当前的是否比这个还大,大则继续,否则返回false结束:
bool fun( int a[], int n )
{
if( n= =1 )
return true;
if( n= =2 )
return a[n-1] >= a[n-2];
return fun( a,n-1) && ( a[n-1] >= a[n-2] );
}
用外部排序,在《数据结构》书上有《计算方法导论》在找到第n大的数的算法上加工
同学的4道面试题,应聘的职位是搜索引擎project师,后两道超级难,(希望大家多给一些算发)
1.给两个数组和他们的大小,另一动态开辟的内存,求交集,把交集放到动态内存dongtai,而且返回交集个数
long jiaoji(long* a[],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(void)
{ char c;
for(c=122;c>=97;c--)
{ p.date=c;
p=p->next;
}
p.next=NULL;
}
}
方法2:
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
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"
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;
}
首先static的最主要功能是隐藏,其次由于static变量存放在静态存储区,所以它具备持久性和默认值0。
预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比方拷贝#include包括的文件代码,#define宏定义的替换,条件编译等,就是为编译做的预备工作的阶段,主要处理#開始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,能够放在程序中的不论什么位置。
c编译系统在对程序进行通常的编译之前,先进行预处理。c提供的预处理功能主要有下面三种:1)宏定义 2)文件包括 3)条件编译
1、总是使用不常常修改的大型代码体。
2、程序由多个模块组成,全部模块都使用一组标准的包括文件和同样的编译选项。在这样的情况下,能够将全部包括文件预编译为一个预编译头。
什么是进程(Process):普通的解释就是,进程是程序的一次运行,而什么是线程(Thread),线程能够理解为进程中的运行的一段程序片段。在一个多任务环境中以下的概念能够帮助我们理解两者间的区别:
进程间是独立的,这表如今内存空间,上下文环境;线程执行在进程空间内。 一般来讲(不使用特殊技术)进程是无法突破进程边界存取其它进程内的存储空间;而线程因为处于进程空间内,所以同一进程所产生的线程共享同一内存空间。 同一进程中的两段代码不能够同一时候执行,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。 进程和线程都能够有优先级。在线程系统中进程也是一个线程。能够将进程理解为一个程序的第一个线程。
线程是指进程内的一个运行单元,也是进程内的可调度实体.与进程的差别:
(1)地址空间:进程内的一个运行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
(2)进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是.
(4)二者均可并发运行.
插入排序基本思想:(假定从大到小排序)依次从后面拿一个数和前面已经排好序的数进行比較,比較的过程是从已经排好序的数中最后一个数開始比較,假设比这个数,继续往前面比較,直到找到比它大的数,然后就放在它的后面,假设一直没有找到,肯定这个数已经比較到了第一个数,那就放到第一个数的前面。那么普通情况下,对于採用插入排序法去排序的一组数,能够先选 取第一个数做为已经排好序的一组数。然后把第二个放到正确位置。
选择排序(Selection Sort)是一种简单直观的排序算法。它的工作原理例如以下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到全部元素均排序完成。
能正确表示a和b同一时候为正或同一时候为负的逻辑表达式是(D )。
sssA、(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
下面关于运算符优先顺序的描写叙述中正确的是(C)。
A、关系运算符<算术运算符<赋值运算符<逻辑与运算符
B、逻辑与运算符<关系运算符<算术运算符<赋值运算符
C、赋值运算符<逻辑与运算符<关系运算符<算术运算符
D、算术运算符<关系运算符<赋值运算符<逻辑与运算符
写一个函数将"tom is cat" 倒序打印出来,即 "cat is tom"
//a.ch
#define SPACE ‘ ‘
#define ENDL ‘\0‘
char* str = "Tom is cat"; // 字符串
char* p1 = str+strlen(str)-1;
char* p2 = p1; // 開始时,p1,p2都指向字符串结尾处
char t=0; // 暂时变量,用来保存被暂时替换为ENDL的字符
while(str!=p1--)
{
if(SPACE!=*p1){
for(p2=p1+1;SPACE!=*p1; p1--, t=*p2, *p2=ENDL);
// p1+1指向单词的第一个字母,p2指向单词的结尾,此时输出这个单词
printf("%s ",p1+1);
*p2=t;
p2=p1;
}
}
Output:
cat is Tom
----------------------------------------------------------------------
1)写一个递归函数将内存中的字符串翻转"abc"->"cba"
2)写一个函数将"tom is cat" 将内存中的字符串翻转,即 "cat is tomm"
#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 fox jumps 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;
for(char* o=p+strlen(p)-1,char t=*o;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);
游标和指针
我说过游标是指针,但不不过指针。游标和指针非常像,功能非常像指针,可是实际上,游标是通过重载一元的”*”和”->”来从容器中间接地返回一个值。将这些值存储在容器中并非一个好主意,由于每当一个新值加入到容器中或者有一个值从容器中删除,这些值就会失效。在某种程度上,游标能够看作是句柄(handle)。通常情况下游标(iterator)的类型能够有所变化,这样容器也会有几种不同方式的转变:
iterator——对于除了vector以外的其它不论什么容器,你能够通过这样的游标在一次操作中在容器中朝向前的方向走一步。这意味着对于这样的游标你仅仅能使用“++”操作符。而不能使用“--”或“+=”操作符。而对于vector这一种容器,你能够使用“+=”、“—”、“++”、“-=”中的不论什么一种操作符和“<”、“<=”、“>”、“>=”、“==”、“!=”等比較运算符。
从语法上,在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从根本上也仅仅是个包装数据的语法机制。
---------------------------------------------------------------
最后,作为语言的两个keyword,除去定义类型时有上述差别之外,另外另一点点:“class”这个keyword还用于定义模板參数,就像“typename”。但keyword“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) virtualkeyword可有可无。
5) 成员函数中 有无const (函数后面) 也可推断是否重载
关系数据库是表的集合,它是由一个或多个关系模式定义。SQL语言中的数据定义功能包含对数据库、基本表、视图、索引的定义。
关系数据库以关系模型为基础,它有下面三部分组成:
●数据结构——模型所操作的对象、类型的集合
●完整性规则——保证数据有效、正确的约束条件
●数据操作——对模型对象所同意运行的操作方式
关系(Relation)是一个由行和列组成的二维表格,表中的每一行是一条记录(Record),每一列是记录的一个字段(Field)。表中的每一条记录必须是相互排斥的,字段的值必须具有原子性。
SQL(结构化查询语言)是关系数据库语言的一种国际标准,它是一种非过程化的语言。通过编写SQL,我们能够实现对关系数据库的所有操作。
●数据定义语言(DDL)——建立和管理数据库对象
●数据操纵语言(DML)——用来查询与更新数据
●数据控制语言(DCL)——控制数据的安全性
起来是一个非常easy的问题,每个使用过RDBMS的人都会有一个概念。
事务处理系统的典型特点是具备ACID特征。ACID指的是Atomic(原子的)、Consistent(一致的)、Isolated(隔离的)以及Durable(持续的),它们代表着事务处理应该具备的四个特征:
原子性:组成事务处理的语句形成了一个逻辑单元,不能仅仅运行当中的一部分
一致性:在事务处理运行之前和之后,数据是一致的。
隔离性:一个事务处理对还有一个事务处理没有影响。
持续性:当事务处理成功运行到结束的时候,其效果在数据库中被永久纪录下来。
顺序结构
选择结构
循环结构
cvs(Concurrent Version System) 是一个版本号控制系统。使用它,能够记录下你的源文件的历史。
比如,改动软件时可能会不知不觉混进一些 bug,并且可能过了非常久你才会察觉到它们的存在。有了 cvs,你能够非常easy地恢复旧版本号,并从中看出究竟是哪个改动导致了这个 bug。有时这是非常实用的。
CVSserver端对每一个文件维护着一个修订号,每次对文件的更新,都会使得文件的修订号加1。在client中也对每一个文件维护着一个修订号,CVS通过这两个修订号的关系,来进行Update,Commit和发现冲突等操作操作
依照数据结构类型的不同,将数据模型划分为层次模型、网状模型和关系模型。
124.设计模式:工厂模式 和 单例模式 介绍一下?
project模式即将对象创建过程封装即为工厂模式。
单例模式即整个类仅仅有一个对象,而且不同意显示创建。
125.vector 和 list的差别?
vector内部使用数组,訪问速度快,可是删除数据比較耗性能
list内部使用链表,訪问速度慢,可是删除数据比較快
126.纯虚函数是如何实现的?在编译原理上讲一下?
在类内部加入一个虚拟函数表指针,该指针指向一个虚拟函数表,该虚拟函数表包括了全部的虚拟函数的入口地址,每一个类的虚拟函数表都不
一样,在执行阶段能够循此脉络找到自己的函数入口。
纯虚函数相当于占位符,先在虚函数表中占一个位置由派生类实现后再把真正的函数指针填进去。除此之外和普通的虚函数没什么差别。
127.抽象类为什么不能实例化?
抽象类中的纯虚函数没有详细的实现,所以没办法实例化。
128.在函数后面加个const是怎么理解的?
在函数后面加个const一般在类的成员函数中使用,表示这个函数不改动数据成员的值。
另:void set_prt_val(int val)const {*ptr= val}理解:指针ptr指向的内容不是类的数据成员,所以这可这么写:*ptr = val;但这个指针在这个函数中不能改动。如果写成这样:ptr = &i(如果i是另外一个整形变量)就不正确了,由于改变了指针的内容。
129.进程间通信类型:
(1)环境变量、文件描写叙述符 一般Unix环境下的父进程运行fork(),生成的子进程拥有了父进程当前设置的环境变量以及文件描写叙述符;因为通信是一个单向的、一次性的通信,随后的父进程以及子进程兴许的内容不能再能共享;
(2)命令行參数 大多数用户都使用过ShellExec相关的命令,此API能够打开新的进程,并能够通过接口里的输入參数进行信息共享;相同,他也是一个单项、一次性的通信;
(3)管道 使用文件和写方式訪问公用的数据结构;管道分为匿名管道和命名管道,前者是用作关联进程间用,后者为无关联的进程使用;前者通过文件描写叙述符或文件句柄提供对命名管道的訪问,后者须要知道管道名称才干读写管道;一般来讲,读写的内容是字节流,须要转换为有意义的结构才有意义;
(4)共享内存 进程须要能够被其它进程訪问浏览的进程块;进程间共享内存的关系与函数间共享全局变量的关系类似
(5)DDE 动态数据交互
线程间通信类型:
(1)全局数据;
(2)全局变量;
(3)全局数据结构;
(4)线程间通信的參数:pThread_create这类API接口中的參数
130.关于内存对齐的问题以及sizof()的输出
答:编译器自己主动对齐的原因:为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了訪问未对齐的内存,处理器须要作两次内存訪问;然而,对齐的内存訪问仅须要一次訪问。
131.winsock建立连接的主要实现步骤?
答:
TCP:server端:1.socket()建立套接字,2将套接字绑定到本地地址和port上,绑定(bind)3.将套接字设为监听模式,准备接收client,监听(listen);4.等待client请求到来,请求到来后,连接请求,并返回一个新的相应此连接的套接字,accept()5.用返回的套接字和client进行通讯(send/recv);6.返回并等待还有一客户请求。7.关闭套接字。
client:1.socket()建立套接字2.向server发出连接请求,(connect)2。和server进行通信,send()和recv(),在套接字上写读数据,直至数据交换完成;4closesocket()关闭套接字。
UDP:1server端:1.创建套接字(socekt)2.将套接字绑定到本地地址和port上(bind);3.等待接收数据(recvfrom);4.closesocket()关闭套接字。
client:1.创建套接字(socekt)2,向server端发送数据(sendto)3.closesocket()关闭套接字。
132.C++中为什么用模板类。
答:(1)可用来创建动态增长和减小的数据结构
(2)它是类型无关的,因此具有非常高的可复用性。
(3)它在编译时而不是执行时检查数据类型,保证了类型安全
(4)它是平台无关的,可移植性
(5)可用于基本数据类型
133.动态连接库的两种方式?
答:调用一个DLL中的函数有两种方法:
1.加载时动态链接(load-time dynamic linking),模块很明白调用某个导出函数,使得他们就像本地函数一样。这须要链接时链接那些函数所在DLL的导入库,导入库向系统提供了加载DLL时所需的信息及DLL函数定位。
2.执行时动态链接(run-time dynamic linking),执行时能够通过LoadLibrary或LoadLibraryEx函数加载DLL。DLL加载后,模块能够通过调用GetProcAddress获取DLL函数的出口地址,然后就能够通过返回的函数指针调用DLL函数了。如此就可以避免导入库文件了。
134..CSingleLock是干什么的。
答:同步多个线程对一个数据类的同一时候訪问
135.编写strcat函数(6分)
已知strcat函数的原型是char *strcat (char *strDest, const char *strSrc);
当中strDest 是目的字符串,strSrc 是源字符串。
(1)不调用C++/C 的字符串库函数,请编写函数 strcat
答:
VC源代码:
char * __cdecl strcat (char * dst, const char * src)
{
char * cp = dst;
while( *cp )
cp++; /* find end of dst */
while( *cp++ = *src++ ) ; /* Copy src to end of dst */
return( dst ); /* return dst */
}
(2)strcat能把strSrc 的内容连接到strDest,为什么还要char * 类型的返回值?
答:方便赋值给其它变量
// String 的普通构造函数
String::String(const char *str) // 6 分
{
if(str==NULL)
{
m_data = new char[1]; // 若能加 NULL 推断则更好
*m_data = ‘\0’;
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加 NULL 推断则更好
strcpy(m_data, str);
}
}
// 拷贝构造函数
String::String(const String &other) // 3 分
{
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 推断则更好
strcpy(m_data, other.m_data);
}
// 赋值函数
String & String::operate =(const String &other) // 13 分
{
// (1) 检查自赋值 // 4 分
if(this == &other)
return *this;
// (2) 释放原有的内存资源 // 3 分
delete [] m_data;
// (3)分配新的内存资源,并复制内容 // 3 分
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 推断则更好
strcpy(m_data, other.m_data);
// (4)返回本对象的引用 // 3 分
return *this;
}
标签:des style http color io os 使用 ar java
原文地址:http://www.cnblogs.com/mengfanrong/p/4002391.html