您的位置:首页 > 编程语言 > C语言/C++

C++笔试面试答题(二)

2009-11-26 00:15 183 查看
31. const 符号常量;

(1)const char *p
(2)char const *p
(3)char * const p
说明上面三种描述的区别;

如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。

Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:
把一个声明从右向左读。
char * const cp;( * 读成 pointer to ) cp is a const pointer to char;
const char * p; p is a pointer to const char;
char const * p; 同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。

(1)const char *p

一个指向char类型的const对象指针,p不是常量,我们可以修改p的值,使其指向不同的char,但是不能改变它指向非char对象,如:
const char *p;
char c1='a';
char c2='b';
p=&c1;//ok
p=&c2;//ok
*p=c1;//error

(2)char const *p
(3)char * const p

这两个好象是一样的,此时*p可以修改,而p不能修改。

(4)const char * const p
这种是地址及指向对象都不能修改。

32.模65536原则
计算机一般使用8位、16位或32位二进制表示一个整数,以16位二进制表示为例,由于16位二进制最多可以有2的16次方=65536个不同的组合,可以表示无符号整数0至65535,或者可以表示有符号整数-32768至32767,如果计算机进行运算时运算结果超出这16位范围,那么计算机将只取低16位做为结果,这样运算结果始终只在0至65535或-32768至32767之间,它与实际数学上的运算结果可能会相差65536的整数倍。
设有两个整数a和b,若存在一个整数n,使得满足 a=b + 65536*n,那么这两数的二进制表示中低16位完全相同,计算机因此认为a与b是同一个数。也可以这么说,如果a与b对65536求余数(模)所得结果相同,那么计算机就认为这两数是同一个数,计算机的这个特征称为模65536原则,即任何一个数加上或减去65536所得结果相同。
举例说,设有无符号数运算65534 + 3 = 65537,用二进制运算表示为 1111 1111 1111 1110 + 0000 0000 0000 0011 = 1 0000 0000 0000 0001,由于计算机只保留16位二进制结果,所以计算机会认为结果为0000 0000 0000 0001,即计算机会认为运算结果是65534+3=1,这个结果与实际正好相差65536,有65534+3=65537=65537-65536=1。
模65536原则对负数同样适用,如1-3 = -2 = -2 + 65536 = 65534,即-2就是65534,它们的16位二进制表示完全一样,都是1111 1111 1111 1110,它既是65534转换为二进制的结果,又正好是-2的补码表示结果。
[例1]写出printf( “%ud”, 20000+20000 );的运行结果。
20000+20000=40000,要求以无符号输出,无符号数范围为0至65535,运行结果在范围内,所以输出为40000。
[例2]写出printf( “%d”, 20000+20000 );的运行结果。
同样是20000+20000=40000,要求以有符号数输出,有符号数的范围为-32768至32767之间,40000超出范围,要进行调整,40000=40000-65536=-25536,故输出 -25536。
[例3]写出printf( “%ud”, -100 );的运行结果。
-100不在范围0至65535之间,-100+65536=65436,故输出65436。
[例4]若int x;的16位二进制结果为1000 0000 0000 0001,写出printf(“%d”, x);的运行结果。
将x转换为十进制为32769,不在有符号数范围内,32769-65536=-32767,故输出-32767。
[练习]写出以下运行结果。
1.printf(“%d”, 60000);
2.printf(“%ud”,-20000);
3.printf(“%d”,x); /* x为1000 0000 0000 1000 */
4.printf(“%d”,~100);

33. 编写一个函数,作用是把一个char组成的字符串循环右移n个。比如原来是"abcdefghi"如果n=2,移位后应该是"hiabcdefgh"

  函数头是这样的:

//pStr是指向以'/0'结尾的字符串的指针
//steps是要求移动的n

void LoopMove ( char * pStr, int steps )
{
 //请填充...
}
  解答:
 正确解答1:

void LoopMove ( char *pStr, int steps )
{
 int n = strlen( pStr ) - steps;
 char tmp[MAX_LEN];
 strcpy ( tmp, pStr + n );
 strcpy ( tmp + steps, pStr);
 *( tmp + strlen ( pStr ) ) = '/0';
 strcpy( pStr, tmp );
}

34. 写一个程序,把一个100以内的自然数分解因数。(自然数分解因数就是将一个自然数分解为几个素数的乘积,提示,由于该数不是很大,所以可以将质数保存在数组中,以加快计算速度)
void int_devide(int nTest)
{
int itpl[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53};
cout<<"int_devide: "<<nTest<<" = ";
for(int i=0;nTest!=1&&i<sizeof(itpl)/sizeof(int);++i)
{
if(itpl[i]*itpl[i]>nTest)
break;
while(nTest%itpl[i]==0)
{
cout<<itpl[i]<<'/t';
nTest/=itpl[i];
}
}
if(nTest!=1)
cout<<nTest<<endl;
cout<<endl;
}

35. 用<<,>>,|,&实现一个WORD(2个字节)的高低位交换!
int main()
{
unsigned short a = 0xABCD;
unsigned short b ;
unsigned short c, d;
b = (a << 8)&0xff00;
c = (a >> 8)&0x00ff;
d = b | c;
printf("/n%x",b);
printf("/n%x",c);
printf("/n%x",d);
return 0;
}

36. 用变量a给出下面的定义
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

37. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;
}
这个函数有太多的错误了,以至让人不知从何说起了:
1). ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2). ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3). 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4). 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。
38. C++中的空类,默认产生哪些类成员函数?
答:
class Empty
{
public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); // 赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
};

39. 以下代码中的输出语句输出0吗,为什么?
struct CLS
{
int m_i;
CLS( int i ) : m_i(i) {}
CLS()
{
CLS(0);
}
};
CLS obj;
cout << obj.m_i << endl;

答:不能。在默认构造函数内部再调用带参的构造函数属用户行为而非编译器行为,亦即仅执行函数调用,而不会执行其后的初始化表达式。只有在生成对象时,初始化表达式才会随相应的构造函数一起调用。

40. Consider the following code:

#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
int i = 1;
char buf[4];
strcpy(buf, "AAAA");// end of “/0”
printf("%d/n", i);
return 0;
}
"AAAA"字符串实际上占了5个字节(最后有一个/0),因此strcpy到buf时引起了溢出。在x86环境下,溢出的结果可能是将i清0了,因此会输出0。

a) When compiled and executed on x86, why does this program usually not
output what the programmer intended?
栈是从高地址向低地址存放数据的。堆栈是一个先进后出的数据结构,栈顶地址总是小于等于栈的基地址。
0x00 ---- > buf[0]
0x01 ---- > buf[1]
0x02 ---- > buf[2]
0x03 ---- >buf[3]
0x04 ---- >1 //于是’/0’将覆盖这个内存地址的数据1。
0x05 ---- >0
0x06 ---- >0
0x07 ---- >0
b) Name several ways in which the security problem that causes this program not to output what the programmer intended can be prevented WITHOUT changing the code.

41.linux(Redhat)的启动顺序?
当用户打开PC 的电源,BIOS开机自检,按BIOS中设置的启动设备(通常是硬盘)启动,接着启动设备上安装的引导程序lilo或grub开始引导Linux, Linux首先进行内核的引导,接下来执行init程序,init程序调用了rc.sysinit和rc等程序,rc.sysinit和rc当完成系统初始化和运行服务的任务后,返回init;init启动了mingetty后,打开了终端供用户登录系统,用户登录成功后进入了Shell,这样就完成了从开机到登录的整个启动过程。

42.
用一个程序示意常见的错误能够导致栈破坏,如何检查?
#include “iostream.h”
#include “string.h”
void main()
{
char str[5];
cout<<”input: “;
cin>>str;
while(strlen(str)>5)
{
cout<<”too long!”<
cin>>str;
}
cout<<str;
}

43. 字符串匹配问题。要求在s1中删除所有s2的字符,要用最快的算法

44. 临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
2、互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享
3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目
4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作。

45. 以下几个知识点考的是选择题:
【知识点】reinterpret_cast
reinterpret_casts的最普通的用途就是在函数指针类型之间进行转换。
【知识点】dynamic_cast
安全地在派生类与基类之间进行类型转换。
【知识点】const_cast
const_cast用于类型转换掉表达式的const或volatileness属性。
【知识点】static_cast
让包含int类型变量的表达式产生出浮点数值的结果。double result = static_cast<double>(firstNumber)/secondNumber;

46. release版本的可执行文件。如何获知生成的文件是否过大。
看LINK属性里是否包含其它用不到的库,DLL文件,如网络,数据库相关的,有的话就说明过大。Release应该是进行了大量的编译器优化,并且不包含Debug所大量包含的调试信息。

47. 对比平衡二叉树和红黑树。
一棵红黑树是指一棵满足下述性质的二叉搜索树(BST, binary search tree):
1. 每个结点或者为黑色或者为红色。
2. 根结点为黑色。
3. 每个叶结点(实际上就是NULL指针)都是黑色的。
4. 如果一个结点是红色的,那么它的两个子节点都是黑色的(也就是说,不能有两个相邻的红色结点)。
5. 对于每个结点,从该结点到其所有子孙叶结点的路径中所包含的黑色结点数量必须相同。
在红黑树上只读操作不需要对用于二叉查找树的操作做出修改,因为它也二叉查找树。但是,在插入和删除之后,红黑属性可能变得违规。恢复红黑属性需要少量(O(log n))的颜色变更(这在实践中是非常快速的)并且不超过三次树旋转(对于插入是两次)。这允许插入和删除保持为 O(log n) 次,但是它导致了非常复杂的操作。
平衡二叉树:由于在AVL树上进行查找时,和关键字进行比较的次数不会超过树的高度h,且不再会出现蜕化为单枝树的情形,另外可以证明含有n个结点的AVL树,树的高度h=O(log2n),因此,查找AVL树的时间复杂度为O(log2n)。

48. 有1001个球,两个人轮流拿球,且每次只能拿1、2、4个球,规定拿到最后一个球的人为输。如果让你先拿,你是否有必胜的把握。如果有,该如何操作?
先拿4个,然后每轮保证两个人所拿球数之和为3或6。

49. 对于一个给定的整形数组int array
。编程实现:将数组中所有小于或等于0的元素都放在数组前面,大于0的元素放在数组后面。要求时间复杂度为o(n)
void sort(int array[], int n)
{
int *h = array, *t = array + n;
int tmp;
while(h < t)
{
if(*h <= 0)
{
h++;
continue;
}
if(*t >= 0)
{
t--;
continue;
}

tmp = *h;
*h = *t;
*t = tmp;
h++;
t--;
}
}

50. _exit终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。exit函数将终止调用进程。在退出程序之前,所有文件关闭,缓冲输出内容将刷新定义,并调用所有已刷新的“出口函数”(由atexit定义)。
‘exit()’与‘_exit()’有不少区别在使用‘fork()’,特别是‘vfork()’时变得很突出。
exit()在结束调用它的进程之前,要进行如下步骤:
1.cleanup();
2.在atexit()注册的函数;
最后调用_exit()函数

51. 一个程序可以运行多个实例(进程),那我们如何让它仅运行一个实例呢?
很简单,使用#pragma编译器指令在进程的地址空间内创建一个“共享节”就可以达到目的,这个“共享节”里的数据为多个运行的进程所共享,这样我们通过设置一个变量比如:unsigned g_nInstanceCount;作为运行实例的计数,在程序入口处检测一下g_nInstanceCount是否为1,true则禁止运行,否则g_nInstanceCount++并运行一个实例。
具体如下:
#pragma data_seg("Shared")//这个共享节叫做Shared

unsigned g_nInstanceCount =0;//计数初始为0

#pragma data_seg() //设置完毕

#pragma comment(linker,"/:SECTION:Shared,RWS")//这句话告诉连接器,我要将Shared设为读写共享

/********************************************

基于控制台的

********************************************/
#include <iostream>
using namespace std;

#pragma data_seg("Shared")

unsigned g_nInstanceCount = 0;

#pragma data_seg()

#pragma comment(linker,"/SECTION:Shared,RWS")

int main()
{

if(g_nInstanceCount>=1)
{
cout<<"Can only running an instance!"<<endl;
return 0;
}

g_nInstanceCount++; //实例计数加1

cout<<"This is a console program!"<<endl;
system("pause");
return 0;
}

52. int a[5]={1,2,3,4,5};
printf("%d/n", *((int*)(&a+1)-2);//*((int*)(a+5-2))
输出是什么。
答:4. 分析:(int*)(&a+1)=a+5=&a[5]
int a[5]={1,2,3,4,5};
printf("%d/n", *((int*)(&a+1)-2));
cout<<a<<'/t'<<a+1<<'/t'<<(&a+1)<<endl;
//分别取数组a的首地址,下一个地址,以及数组a之后的一个地址即a+5

53.strcpy()为什么会造成缓冲区溢出?可用哪个函数替代?
程序中没有仔细检查用户输入的参数。往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令。

54.虚函数,覆盖,隐藏
class A
{
public:
void func1(int)
{
cout<<"A::func1(int)"<<endl;
}
void func2(double)
{
cout<<"A::func2(double)"<<endl;
}
virtual void func3()
{
cout<<"A::func3"<<endl;
}
};

class B:public A
{
public:
void func1(int)
{
cout<<"B::func1(int)"<<endl;
}
void func3()
{
cout<<"B::func3"<<endl;
}
};

int main()
{
A* a = new B;
a->func1(1); //A::func1(int)
a->func2(2.1); //A::func2(double)
a->func3(); //B::func3

B* b=(B*) a;
b->func1(1); //B::func2(int)
b->func2(2.1); //A::func1(double)
b->func3(); //B::func3

return 0;
}

55. 内存对齐
一、为什么会有内存对齐
以下内容节选自《Intel Architecture 32 Manual》。
为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。
二、对齐规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
对齐规则.
结构体数据成员对齐的意义
许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的起始地址的值是某个数k的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。这种强制的要求一来简化了处理器与内存之间传输系统的设计,二来可以提升读取数据的速度。比如这么一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只需要一次内存操作。否则,我们就可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。
结构体对齐包括两个方面的内容对齐规则:
1)、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照 #pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2)、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3)、结合1、2推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
4).各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。
5).各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节自动填充。
6).同时为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。

结构体大小的计算方法和步骤
1). 将结构体内所有数据成员的长度值相加,记为sum_a;
2). 将各数据成员为了内存对齐,按各自对齐模数而填充的字节数累加到和sum_a上,记为sum_b。对齐模数是#pragma pack指定的数值以及该数据成员自身长度中数值较小者。该数据相对起始位置应该是对齐模式的整数倍。
3). 将和sum_b向结构体模数对齐,该模数是#pragma pac指定的数值和结构体内部最大的基本数据类型成员长度中数值较小者。结构体的长度应该是该模数的整数倍。
下面用前面的例子来说明VC到底怎么样来存放结构的。

例1.
struct MyStruct
{
double dda1;
char dda;
int type
};
为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof(int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。
例2
struct MyStruct
{
char dda;//偏移量为0,满足对齐方式,dda占用1个字节;
double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8
//的倍数,需要补足7个字节才能使偏移量变为8(满足对齐
//方式),因此VC自动填充7个字节,dda1存放在偏移量为8
//的地址上,它占用8个字节。
int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍
//数,满足int的对齐方式,所以不需要VC自动填充,type存
//放在偏移量为16的地址上,它占用4个字节。
};//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构
//的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof
//(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为
//sizeof(double)=8的倍数。

56.
(1)输出下一秒的年月日时分秒
void next_second_based_time(int *nYear,int *nMonth,int *nDate,int *nHour,int *nMinute,int *nSecond)
{
int nDays;
(*nSecond)++;
if(*nSecond >= 60)
{
*nSecond = 0;
(*nMinute)++;
if(*nMinute >= 60)
{
*nMinute = 0;
(*nHour)++;
if(*nHour >= 24)
{
*nHour = 0;
(*nDate)++;
switch(*nMonth)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
nDays = 31;
break;
case 2:// 判断闰年
if(*nYear%400 == 0 || *nYear%100 != 0 && *nYear%4 == 0)
{
nDays = 29;
}
else
{
nDays = 28;
}
break;
default:
nDays = 30;
break;
}
if(*nDate > nDays)
{
*nDate = 1;
(*nMonth)++;
if(*nMonth > 12)
{
*nMonth = 1;
(*nYear)++;
}
}
}
}
}
}

(2)输出上一秒的年月日时分秒
void last_second_based_time(int *year,int *month,int *date,int *hour,int *minute,int *second)
{
if(*second!=0)
--*second;
else
{
if(*minute!=0)
{
--*minute;
*second=59;
}
else
{
if(*hour!=0)
{
--*hour;
*minute=59;
*second=59;
}
else
{
--*date;
*hour=23;
*minute=59;
*second=59;
if(*date ==0)
{
switch(*month)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
*date=31;
break;
case 2:
if( (*year%400==0) || (*year%100!=0 && *year%4==0) )
*date=28;
else
*date=29;
break;
default:
*date=30;
break;
}
if(*month!=1)
--*month;
else
{
--*year;
*month=12;
}
}
}
}
}
}

57.实模式,保护模式
从80386开始,cpu有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是real-mode,等到linux操作系统运行起来以后就运行在保护模式。实模式只能访问地址在1M以下的内存称为常规内存,我们把地址在1M 以上的内存称为扩展内存。在保护模式下,全部32条地址线有效,可寻址高达4G字节的物理地址空间。
保护模式同实模式的根本区别是进程、内存受保护与否。可寻址空间的区别只是这一原因的果。实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序没有区别对待,而且每一个指针都是指向"实在"的物理地址。这样一来,用户程序的一个指针如果指向了系统程序区域或其他用户程序区域,并改变了值,那么对于这个被修改的系统程序或用户程序,其后果就很可能是灾难性的。为了克服这种低劣的内存管理方式,处理器厂商开发出保护模式。这样,物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)要由操作系统转化为物理地址去访问,程序对此一无所知。
实模式是80286(80186存在时间短,数量也很少)以后的CPU才说的,工作在实模式下的80X86系列的CPU相当于高速的8086CPU 内存寻址1M,不支持优先级,所有指令工作在特权级,中断和8086一样,只比8086多寄存器,有些寄存器位长32位,有利于程序的编写。保护模式内存寻址4GB,支持分页,支持虚拟内存,支持优先级,支持多任务等。总的来说,DOS 实模式,WINDOWS 保护模式(WINDOWS内的MS-DOS为虚拟86模式)。
这样,进程(这时我们可以称程序为进程了)有了严格的边界,任何其他进程根本没有办法访问不属于自己的物理内存区域,甚至在自己的虚拟地址范围内也不是可以任意访问的,因为有一些虚拟区域已经被放进一些公共系统运行库。

58.Socket通信
1)
服务器创建侦听客户端的连接,一旦有客户连接,服务器就将其加入到一个活动客户的列表中,某个客户端发送的消息也由服务器发送到各个连接的客户端,就好像聊天室中的那样。
使用套接字在UNIX域内实现进程间通信的服务端程序。
(1) 首先,程序通过调用socket函数,建立了监听连接的套接字;
(2) 然后调用bind函数,将套接字与地址信息关联起来;
(3) 调用listen函数实现对该端口的监听,当有连接请求时,通过调用accept函数建立与客户机的连接;
(4) 最后,调用read函数来读取客户机发送过来的消息,当然也可以使用recv函数实现相同的功能。
2)Linux环境下的socket编程,select函数
阻塞和非阻塞
阻塞(blocking)函数在完成其指定的任务以前不允许程序调用另一个函数。例如,程序执行一个读数据的函数调用时,在此函数完成读操作以前将不会执行下一程序语句。当服务器运行到accept语句时,而没有客户连接服务请求到来,服务器就会停止在accept语句上,等待连接服务请求的到来。
非阻塞操作则可以立即完成。比如,如果你希望服务器仅仅注意检查是否有客户在等待连接,有就接受连接,否则就继续做其他事情,则可以通过将socket设置为非阻塞方式来实现。非阻塞socket在没有客户在等待时就使accept调用立即返回。
  #include <unistd.h>
  #include <fcntl.h>
  ……
sockfd = socket(AF_INET,SOCK_STREAM,0);
fcntl(sockfd,F_SETFL,O_NONBLOCK);
……
  通过设置socket为非阻塞方式,可以实现"轮询"若干socket。当企图从一个没有数据等待处理的非阻塞socket读入数据时,函数将立即返回,返回值为-1,并置errno值为EWOULDBLOCK。但是这种"轮询"会使CPU处于忙等待方式,从而降低性能,浪费系统资源。而调用select()会有效地解决这个问题,它允许你把进程本身挂起来,而同时使系统内核监听所要求的一组文件描述符的任何活动,只要确认在任何被监控的文件描述符上出现活动,select()调用将返回指示该文件描述符已准备好的信息,从而实现了为进程选出随机的变化,而不必由进程本身对输入进行测试而浪费CPU开销。select函数原型为:
int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

59.Windows, post()函数, send()函数
(1) Post()将指定消息加入到某个窗口的消息队列的尾部,然后返回到应用程序中,它并不等待相应事件处理程序的执行。post()函数采用的是异步方式,Send()函数采用的是同步方式。
语法post( handle, messageno, word, long )
参数handle:long类型,指定窗口的系统句柄,将向该窗口邮寄消息;messageno:UnsignedInteger类型,指定要邮寄的消息号; word:long类型,指定与消息一起邮寄的word类参数值。如果messageno参数指定的消息不使用该参数,那么将这个参数的值设置为0;long:long类型或string,指定与消息一起邮寄的long型参数值或字符串返回值Boolean。如果任何参数的值为NULL,Post()函数返回NULL。
Post()函数的参数handle指定接收消息的窗口句柄,对其它应用程序的窗口来说,可以调用系统API函数查找窗口并得到相应窗口的句柄。当应用程序在long参数位置指定一个字符串时,Post()函数复制一个该字符串的副本,然后将副本的地址传送给指定窗口。
(2)Send()向窗口发送指定的消息并立即执行相应的事件处理程序,Send()函数直接触发指定窗口相应的事件,执行事件处理程序后返回到调用应用中。
语法Send( handle, messageno, word, long )
参数handle:long类型,指定窗口的系统句柄,将向该窗口发送消息messageno:UnsignedInteger类型,指定要发送的消息号word:long类型,指定与消息一起发送的word类参数值。如果messageno参数指定的消息不使用该参数,那么将这个参数的值设置为0long:long类型或string,指定与消息一起发送的long型参数值或字符串返回值Long。函数执行成功时返回Windows系统调用SendMessage()的返回值,发生错误时返回-1。如果任何参数的值为NULL,Send()函数返回NULL。
实际上,Send()函数把它的各个参数直接传送给Windows的系统调用SendMessage()。在各种C++开发工具的WINDOWS.H文件中可以查到各消息编号。当应用程序在long参数位置指定一个字符串时,Send()函数复制一个该字符串的副本,然后将副本的地址传送给指定窗口。

60.数据库系统,事务的特性:原子性、一致性、隔离性、持久性。
(1)原子性(Atomicity)
事务中的所有操作要么全部执行,要么都不执行;如果事务没有原子性的保证,那么在发生系统故障的情况下,数据库就有可能处于不一致状态。即使没有故障发生,系统在某一时刻也会处于不一致状态。原子性的要求就是这种不一致状态除了在事务执行当中出现外,在其他任何时刻都是不可见的。保证原子性是DBMS的责任:即事务管理器和恢复管理器的责任。
(2)一致性(Consistency)
主要强调的是,如果在执行事务之前数据库是一致的,那么在执行事务之后数据库也还是一致的;所谓一致性简单地说就是数据库中数据的完整性,包括它们的正确性。
(3)隔离性(Isolation)
即使多个事务并发(同时)执行,每个事务都感觉不到系统中有其他的事务在执行,因而也就能保证数据库的一致性。
事情的起因:即使每个事务都能保持一致性和原子性,但如果几个事务并发执行,且访问相同的数据项,则它们的操作会以人们所不希望的某种方式交叉执行,结果导致不一致的状态!
解决办法:如果几个事务要访问相同的数据项,为了保证数据库的一致性,可以让这几个事务:
①串行执行:即一个接着一个地执行事务;
②并发执行:即同时执行多个事务,但用并发控制机制来解决不同事务间的相互影响。
(4)持久性(Durability)
事务成功执行后它对数据库的修改是永久的,即使系统出现故障也不受影响。持久性的含义是说:一旦事务成功执行之后,它对数据库的更新是永久的。可以用以下两种方式中的任何一种来达到持久性的目的:一是以牺牲应用系统的性能为代价,一是以多占用磁盘空间为代价。
1)以牺牲应用系统的性能为代价:要求事务对数据库系统所做的更新在事务结束前已经写入磁盘;
2)以多占用磁盘空间为代价:要求事务已经执行的和已写到磁盘的、对数据库进行更新的信息是充分的(例如,数据库日志的信息就足够的多),使得DBMS在系统出现故障后重新启动系统时,能够(根据日志)重新构造更新。

61.并发操作带来的数据库不一致性可以分为四类:丢失或覆盖更新、脏读、不可重复读和幻像读,上例只是并发问题的一种。
(1)丢失或覆盖更新(lost update)
当两个或多个事务选择同一数据,并且基于最初选定的值更新该数据时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。上面预定飞机票的例子就属于这种并发问题。事务1与事务2先后读入同一数据A=16,事务1执行A-1,并将结果A=15写回,事务2执行A-1,并将结果A=15写回。事务2提交的结果覆盖了事务1对数据库的修改,从而使事务1对数据库的修改丢失了。
(2)脏读
一个事务读取了另一个未提交的并行事务写的数据。当第二个事务选择其它事务正在更新的行时,会发生未确认的相关性问题。第二个事务正在读取的数据还没有确认并且可能由更新此行的事务所更改。换句话说,当事务1修改某一数据,并将其写回磁盘,事务2读取同一数据后,事务1由于某种原因被撤销,这时事务1已修改过的数据恢复原值,事务2读到的数据就与数据库中的数据不一致,是不正确的数据,称为脏读。
(3)不可重复读(nonrepeatable read)
一个事务重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务修改过。即事务1读取某一数据后,事务2对其做了修改,当事务1再次读数据时,得到的与第一次不同的值。
(4)幻像读
如果一个事务在提交查询结果之前,另一个事务可以更改该结果,就会发生这种情况。这句话也可以这样解释,事务1按一定条件从数据库中读取某些数据记录后未提交查询结果,事务2删除了其中部分记录,事务1再次按相同条件读取数据时,发现某些记录神秘地消失了;或者事务1按一定条件从数据库中读取某些数据记录后未提交查询结果,事务2插入了一些记录,当事务1再次按相同条件读取数据时,发现多了一些记录。
产生上述四类数据不一致性的主要原因是并发操作破坏了事务的隔离性。并发控制就是要用正确的方式调度并发操作,使一个用户事务的执行不受其他事务的干扰,从而避免造成数据的不一致性。

62静态地址重定位发生的阶段
静态地址变换:又称静态地址重定位,地址变换在程序装入时一次完成,以后不再改变。
特点:不需硬件支持,但程序运行时不能在内存移动,程序需要连续存储空间,难以共享。动态地址变换:又称动态地址重定位,在程序执行过程中,每次访问内存之前将要访问程序地址转换成内存地址。
特点:需要硬件支持,不需连续空间,可以实现虚拟存储。

63. 并发,并行
所有的并发处理都有排队等候,唤醒,执行至少三个这样的步骤。所以并发肯定是宏观概念,在微观上他们都是序列被处理的,只不过资源不会在某一个上被阻塞(一般是通过时间片轮转),所以在宏观上看多个几乎同时到达的请求同时在被处理。如果是同一时刻到达的请求也会根据优先级的不同,而先后进入队列排队等候执行。
并发与并行是两个既相似而又不相同的概念:并发性,又称共行性,是指能处理多个同时性活动的能力;并行是指同时发生的两个并发事件,具有并发的含义,而并发则不一定并行,也亦是说并发事件之间不一定要同一时刻发生。
并发和并行的区别就是一个处理器同时处理多个任务和多个处理器或者是多核的处理器同时处理多个不同的任务。
前者是逻辑上的同时发生(simultaneous),而后者是物理上的同时发生.

64. 进程间通信,p1,p2两个进程,间断访问数组中数据,p1访问偶数,p2访问奇数,某进程访问一个数据后需让一个等待的进程访问数据,写一个程序。
a[]={2,3,5,6,8,4,9,7}

65.白盒测试 黑盒测试 的集大成方案
V测试模型
灰盒测试
灰盒测试,确实是介于黑盒测试与白盒测试之间。
灰盒测试关注输出对于输入的正确性,同时也关注内部表现,但这种关注不象白盒那样详细、完整,只是通过一些表征性的现象、事件、标志来判断内部的运行状态,有时候输出是正确的,但内部其实已经错误了,这种情况非常多,如果每次都通过白盒测试来操作,效率会很低,因此需要采取这样的一种灰盒的方法。
  灰盒测试结合了白盒测试盒黑盒测试的要素。它考虑了用户端、特定的系统知识和操作环境。它在系统组件的协同性环境中评价应用软件的设计。
  灰盒测试由方法和工具组成,这些方法和工具取材于应用程序的内部知识盒与之交互的环境,能够用于黑盒测试以增强测试效率、错误发现和错误分析的效率。
灰盒测试涉及输入和输出,但使用关于代码和程序操作等通常在测试人员视野之外的信息设计测试。

66.排序算法
按平均时间将排序分为四类:
(1)平方阶(O(n2))排序
一般称为简单排序,例如直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlgn))排序
如快速、堆和归并排序;
(3)O(n1+£)阶排序
£是介于0和1之间的常数,即0<£<1,如希尔排序;
(4)线性阶(O(n))排序
如桶、箱和基数排序。
各种排序方法比较
简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。
影响排序效果的因素
因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素:
①待排序的记录数目n;
②记录的大小(规模);
③关键字的结构及其初始状态;
④对稳定性的要求;
⑤语言工具的条件;
⑥存储结构;
⑦时间和辅助空间复杂度等。
比较次数与序列初始状态无关的有:归并排序,选择排序;
比较次数与序列初始状态有关的有:冒泡排序,快速排序。

67.查找算法
(1)静态
顺序查找,二分查找,折半查找,静态树查找,索引顺序表查找。
(2)动态
二叉排序树,平衡二叉树,红黑树,B-树,B+树,哈希表查找

68. WINDOWS的消息机
WINDOWS的消息系统由3部分组成:(1)消息队列,WINDOWS能够为所有的应用程序维护一个消息队列,应用程序必须从消息队列中获取消息,然后分派给某个窗口。(2)消息循环,通过这个循环机制,应用程序从消息队列中检索消息,再把它分派给适当的窗口,依次进行。(3)窗口过程,每个窗口都有一个窗口过程,以接收WINDOWS传递给窗口的消息,窗口过程的任务就是要获取消息并响应它(窗口过程是一个回调函数,处理完一个消息后,通常要给WINDOWS一个返回值)。
从消息的产生到消息被一个窗口响应,这其中要经历以下几个步骤:(1)系统中发生了某个事件。(2)WINDOWS把这个事件翻译成消息,然后把它放在消息队列中。(3)应用程序从消息队列中接收这个消息,并把它存放在TMsg记录中。(4)应用程序把消息传递给一个适当的窗口过程。(5)窗口过程响应这个消息并进行处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: