Microsoft
下面哪些调用转换支持可变长度参数:
A. cdecl B. stdcall C. pascal D. fastcall
几种函数调用方式:
__cdecl 是C Declaration的缩写,表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。
_stdcall 是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。这些堆栈中的参数由被调用的函数在返回后清除,称为自动清栈。函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。
PASCAL 是Pascal语言的函数调用方式,也可以在C/C++中使用,参数压栈顺序与前两者相反。返回时的清栈方式与_stdcall相同。
_fastcall是编译器指定的快速调用方式。由于大多数的函数参数个数很少,使用堆栈传递比较费时。因此_fastcall通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。不同编译器编译的程序规定的寄存器不同。返回方式和_stdcall相当。
_thiscall 是为了解决类成员调用中this指针传递而规定的。_thiscall要求把this指针放在特定寄存器中,该寄存器由编译器决定。VC使用ecx,Borland的C++编译器使用eax。返回方式和_stdcall相当。
_fastcall 和 _thiscall涉及的寄存器由编译器决定,因此不能用作跨编译器的接口。所以Windows上的COM对象接口都定义为_stdcall调用方式。
C中不加说明默认函数为_cdecl方式(C中也只能用这种方式),C++也一样,但是默认的调用方式可以在IDE环境中设置。
带有可变参数的函数必须且只能使用_cdecl方式,例如下面的函数:
int printf(char * fmtStr, ...);
int scanf(char * fmtStr, ...);
以下代码的输出结果:
class A
{
public:
virtual void f()
{
cout<<"A:f()"<<endl;
}
void f() const
{
cout<<"A:f()const"<<endl;
}
};
class B:public A
{
public:
void f()
{
cout<<"B:f()"<<endl;
}
void f() const
{
cout<<"B:f()const"<<endl;
}
};
void ga(const A *a)
{
a->f();
}
int _tmain(intargc, _TCHAR* argv[])
{
A *a=new B();
a->f();
ga(a);
}
答案:B::f()A::f()const
第一个,b->f()为动态绑定,输出B::f没问题
第二个,由于函数ga的参数有const,所以调用成员函数也是调用const版本,但是const版本的不是虚函数,不存在动态绑定,所以输出A::f const。
const 修饰函数和没有const是不同的重载,对于非const对象调用非const函数,当然也可以调用const函数(优先前者);然而对于const对象则只能调用const函数;其次,A中的f ()函数标注为 virtual,那么子类 B 中跟它原模原样声明即使没有标注为 virtual的函数依然是 virtual 函数。(c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。)
const是函数类型的一部分。虚函数重载的时候这个const也必须一致。如果基类的虚函数带有const,而在子类的实现中没有带const,则相当于在子类中重新定义了一个新的函数。
定义虚函数的限制:
(1)非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。构造函数不能是虚函数,内联函数也不能是虚函数。构造函数不能是虚函数原因在于:首先构造函数构造一个对象时必须知道对象的实际类型,其次,虚函数的执行依赖于虚函数表,而虚函数表在构造函数中进行初始化工作。
(2)只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。
(3)当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数自动成为虚函数。
(4)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现这种同名函数。
子类中可以不重写父类中的虚函数:
class A
{
public:
virtual void f()
{
cout<<"A:f()"<<endl;
}
};
class B:public A
{
public:
void f2()
{
cout<<"B:f()"<<endl;
}
};
int main(){
A *a=new B();
a->f();
}
输出结果:A:f()
但如果基类中定义的是纯虚函数,而在子类中没有实习,那么在这个子类中,该函数仍然是纯虚函数,且该子类也不能被实例化。
线程与进程
程序与进程的关系:程序是计算机指令的集合,它以文件的形式存储在磁盘上,而进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。一个程序可以对应多个进程,同时,在一个进程中也可以同时访问多个程序。
进程是资源申请、调度和独立运行的单位,因此它使用系统中的运行资源。程序不能申请系统资源,不能被系统调度,也不能作为独立运行的单位,因此它不占用系统的运行资源。
进程与线程的关系:
1)进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。也就是说,进程实际上是线程的执行环境。
2)单个进程可能包含若干个线程,这些线程都“同时”(时间片)执行进程地址空间中的代码。每个进程至少拥有一个线程。当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主线程,也就是执行主函数的线程。此后主线程可以创建其他线程。
3)进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,子进程和父进程有不同的代码和数据空间。而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,多个线程则共享数据空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮。
4)在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
以下代码的输出结果
int x=10;
x= x++;
printf("%d /n", x);
不同编译器编译出的结果不同。可能为10(DevC++),也可能为11(VC6.0).
以下const用法正确的是:
A. const int a; // a 是常数
B. int const a; // a 是常数
C. int const *a; // a 指向常数的指针
D. const int *a; // a 是常指针
E. int const *a; // a 是常指针
答案:A B C
注:D 和 E都是指向常量的指针,而 int * const a; 才是常量指针。约束指针还是约束常量主要看*的位置。
代码验证:
const int a=10;
int const b=11;
cout<<a<<" "<<b<<endl;
输出:10 11
为什么下面的例子在使用一个const变量来初始化数组,ANSI C的编译器会报告一个错误呢?
const int n = 5;
int a[n];
答案与分析:
1)这个问题讨论的是“常量”与“只读变量”的区别。常量,例如5, "abc",等,肯定是只读的,因为常量是被编译器放在内存中的只读区域,当然也就不能够去修改它。而“只读变量”则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。C语言关键字const就是用来限定一个变量不允许被改变的修饰符(Qualifier)。上述代码中变量n被修饰为只读变量,可惜再怎么修饰也不是常量。而ANSI C规定数组定义时长度必须是“常量”,“只读变量”也是不可以的,“常量”不等于“不可变的变量”。
2)但是在标准C++中,这样定义的是一个常量,这种写法是对的。实际上,根据编译过程及内存分配来看,这种用法本来就应该是合理的,只是ANSI C对数组的规定限制了它(实际上用GCC或VS2005编译以上代码,确实没有错误产生,也没有给出警告)。
3)那么,在ANSI C中用什么来定义常量呢?答案是enum类型和#define宏,这两个都可以用来定义常量。
例:下面的代码编译器会报一个错误,请问,哪一个语句是错误的呢?
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string; //1式
const pStr p2 = string; //2式
p1++;
p2++;
答案与分析:
问题出在p2++上。
1)const使用的基本形式: const type m;
限定m不可变。
2)替换基本形式中的m为1式中的*p1,替换后const char *p1;
限定*p1不可变,当然p1是可变的,因此问题中p1++是对的。
3)替换基本形式中的type为2式中的pStr,替换后const pStr m;
限定m不可变,题中的pStr就是一种新类型,因此问题中p2不可变,p2++是错误的。
下面分别用const限定不可变的内容是什么?
1)const在前面
const int nValue; //nValue是const
const char *pContent; //*pContent是const, pContent可变
const char* const pContent; //pContent和*pContent都是const
2)const在后面,与上面的声明对等
int const nValue; //nValue是const
char const * pContent; //*pContent是const, pContent可变
char* const pContent; //pContent是const,*pContent可变
char const* const pContent; //pContent和*pContent都是const
答案与分析:
const和指针一起使用是C语言中一个很常见的困惑之处,在实际开发中,特别是在看别人代码的时候,常常会因为这样而不好判断作者的意图,下面讲一下我的判断原则:
const只修饰其后的变量,至于const放在类型前还是类型后并没有区别。如:const int a和int const a都是修饰a为const。*不是一种类型,如果*pType之前是某类型,那么pType是指向该类型的指针
一个简单的判断方法:指针运算符*,是从右到左,那么如:char const * pContent,可以理解为char const (* pContent),即* pContent为const,而pContent则是可变的。
int const * p1,p2;
p2是const;(*p1)是一整体,因此(*p1)是const,但p1是可变的。int * p1,p2只代表p1是指向整型的指针,要表示p1、p2都是指针是需写成int * p1,* p2。所以无论是* const p1,p2还是const * p1,p2,里面的*都是属于p1的。
int const * const p1,p2;
p2是const,是前一个const修饰的,*p1也被前一个const修饰,而p1被后一个const修饰。
int * const p1,p2;
p1是const,(* const p1)是整体,所以const不修饰p2。
const在*的左边,则指针指向的变量的值不可变;在*的右边,则指针的指向不可变。简记为“左定值,右定向”。
1)指针指向的变量的值不能变,指向可变
int x = 1;
int y = 2;
const int* px = &x;
int const* px = &x; //这两句表达式一样效果
px = &y; //正确,允许改变指向
*px = 3; //错误,不允许改变指针指向的变量的值
2)指针指向的变量的值可以改变,指向不可变
int x = 1;
int y = 2;
int* const px = &x;
px = &y; //错误,不允许改变指针指向
*px = 3; //正确,允许改变指针指向的变量的值
3)指针指向的变量的值不可变,指向不可变
int x = 1;
int y = 2;
const int* const px = &x;
int const* const px = &x;
px = &y; //错误,不允许改变指针指向
*px = 3; //错误,不允许改变指针指向的变量的值
附:
在c中,对于const定义的指针,不赋初值编译不报错,
即int* const px;等不会报错。
但是,在C++中
int* const px;和const int* const px;会报错,const int* px;不报错。
必须初始化指针的指向int* const px = &x;const int* const px=&x;
强烈建议在初始化时说明指针的指向,防止出现野指针
有1000瓶水,其中有一瓶有毒,小白鼠只要尝一点带毒的水24小时后就会死亡至少要多少只小白鼠才能在24小时鉴别出哪瓶水有毒。
给1000个瓶分别标上如下标签(10位长度):
0000000001 (第1瓶)
0000000010 (第2瓶)
0000000011 (第3瓶)
......
1111101000 (第1000瓶)
从编号最后1位是1的所有的瓶子里面取出1滴混在一起(比如从第一瓶,第三瓶,…里分别取出一滴混在一起)并标上记号为1。以此类推,从编号第一位是1的所有的瓶子里面取出1滴混在一起并标上记号为10。现在得到有10个编号的混合液,小白鼠排排站,分别标上10,9,…1号,并分别给它们灌上对应号码的混合液。24小时过去了,过来验尸:
从左到右,死了的小白鼠贴上标签1,没死的贴上0,最后得到一个序号,把这个序号换成10进制的数字,就是有毒的那瓶水的编号。
3*4的格子有几个矩形:
M*N网格中有横竖各M+1、N+1条直线,其中,任意各取两条都可以组成一个长方形。
C(4,2)*C(5,2)=6*10=60;
A(N,N)=N!
A(N,M)=N*(N-1)*…*(N-M+1)
C(N,M)=A(N,M)/A(M,M)
一条线把平面分成两块,两条线把平面分成四块,如果任意两条线不平行,且没有3条线交在同一点,问100条线将平面分成多少块。
答案:5051
1条直线最多将平面分成2个部分;2条直线最多将平面分成4个部分;3条直线最多将平面分成7个部分;现在添上第4条直线.它与前面的3条直线最多有3个交点,这3个交点将第4条直线分成4段,其中每一段将原来所在平面部分一分为二,所以4条直线最多将平面分成7+4=11个部分.
完全类似地,5条直线最多将平面分成11+5=16个部分;6条直线最多将平面分成16+6=22个部分;7条直线最多将平面分成22+7=29个部分;8条直线最多将平面分成29+8=37个部分.
一般地,n条直线最多将平面分成2+2+3....+N=(N*N+N+2)/2
有N个球,其中只有一个是重量较轻的,用天平只称三次就能找到较轻的球,以下的N值哪个是可能的?
A 12
B 16
C 20
D 24
E 28
3 个一次可以测出来,3*3 = 9个以内 2 次,3*3*3 = 27个以内,3次!
下列代码的输出是什么?
#include <iostream>
using namespace std;
class A{
public:
long a;
};
class B : public A
{
public:
long b;
};
void seta(A* data, int idx)
{
data[idx].a = 2; // 形参为A的指针。数组步长按A的大小来取。
}
int main(int argc, char *argv[])
{
B data[4];
for(int i=0; i<4; ++i){
data[i].a = 1;
data[i].b = 1;
seta(data, i);
}
cout<<sizeof(A)<<endl; // 4
cout<<sizeof(B)<<endl; // 8
for(int i=0; i<4; ++i){
std::cout << data[i].a << data[i].b;
}
}
输出为22221111
附:
struct Test{
short int a;
short int b;
};
int main(int argc, char *argv[])
{
int test = 0x12345678;
printf("%x %x", (*(struct Test*)&test).a, (*(struct Test*)&test).b); // 5678 1234
}
//Intel是小端处理器