101. winsock建立连接的主要实现步骤?
答:
服务器端:socket()建立套接字,绑定(bind)并监听(listen),用accept()等待客户端连接, accept()发现有客户端连接,建立一个新的套接字,自身重新开始等待连接。该新产生的套接字使用send()和recv()写读数据,直至数据交换完毕,closesocket()关闭套接字。
客户端:socket()建立套接字,连接(connect)服务器,连接上后使用send()和recv(),在套接字上写读数据,直至数据交换完毕,closesocket()关闭套接字。
102. 进程间主要的通讯方式?
答:信号量,管道,消息,共享内存
103. 构成Win32 API 函数的三个动态链接库是什么?
答:内核库,用户界面管理库,图形设备界面库。
104. 创建一个窗口的步骤是?
答:填充一个窗口类结构->注册这个窗口类->然后再创建窗口->显示窗口->更新窗口。
105. 模态对话框和非模态对话框有什么区别?
答:1.调用规则不同:前者是用DoModal()调用,后者通过属性和ShowWindow()来显示。
2.模态对话框在没有关闭前用户不能进行其他操作,而非模态对话框可以。
3.非模态对话框创建时必须编写自己的共有构造函数,还要调用Create()函数。
106. 从EDIT框中取出数据给关联的变量,已经把关联的变量的数据显示在EDIT框上的函数是什么?
答: UpdateData(TRUE), Updatedata(FALSE).
107. 简单介绍GDI?
答: GDI是Graphics Device Interface 的缩写,译为:图形设备接口;是一个在Windows应用程序中执行与设备无关的函数库,这些函数在不同的输出设备上产生图形以及文字输出。
108. windows消息分为几类?并对各类做简单描述。
答:
1.窗口消息:与窗口相关的消息,除WM_COMMAND之外的所有以WM_开头的消息;
2.命令消息;用于处理用户请求,以WM_COMMAND表示的消息;
3.控件通知消息:统一由WM_NOTIFT表示,
4.用户自定义消息。
109. 如何自定义消息?
答:使用WM_USER 和WM_APP两个宏来自定义消息,
110. 简述Visual C++ 、Win32 API和MFC之间的关系?
答:(1) Visual C+是一个以C++程序设计语言为基础的、集成的、可视化的编程环境;
(2) Win32 API是32位Windows操作系以C/C++形式提供的一组应用程序接口;
(3) MFC是对Win32 API的封装,简化了开发过程。
111.怎样消除多重继承中的二义性?
答: 1.成员限定符 2.虚基类
112什么叫静态关联,什么叫动态关联
答:在多态中,如果程序在编译阶段就能确定实际执行动作,则称静态关联,
如果等到程序运行才能确定叫动态关联。
113多态的两个必要条件
答:1.一个基类的指针或引用指向一个派生类对象 2.虚函数
114.什么叫智能指针?
答:当一个类中,存在一个指向另一个类对象的指针时,对指针运算符进行重载,那么当前类对象可以通过指针像调用自身成员一样调用另一个类的成员。
115.什么时候需要用虚析构函数?
答:当基类指针指向用new运算符生成的派生类对象时,delete基类指针时,派生类部分没有释放掉而造成释放不彻底现象,需要虚析构函数。 补充:虚函数就是让派生类调用基类的虚函数。
116. MFC中,大部分类是从哪个类继承而来?
答:CObject
117.什么是平衡二叉树?
答:左右子树都是平衡二叉树,而且左右子树的深度差值的约对值不大于1。
118.语句for( ;1 ;)有什么问题?它是什么意思?
答:无限循环,和while(1)相同。
119.派生新类的过程要经历三个步骤
答:1.吸收基类成员 2.改造基类成员 3.添加新成员
121. TCP/IP 建立连接的过程
答:在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送连接请求到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到客户端连接请求,向客户端发送允许连接应答,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的允许连接应答,向服务器发送确认,客户端和服务器进入通信状态,完成三次握手
122. memset ,memcpy 的区别
答:memset用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化为’ ′。
memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度;
123. 在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”?
答:C++语言支持函数重载,C 语言不支持函数重载。函数被C++编译后在库中的名字
与C 语言的不同。假设某个函数的原型为: void foo(int x, int y);该函数被C 编译器编译后在库中的名字为_foo , 而C++编译器则会产生像_foo_int_int 之类的名字。C++提供了C 连接交换指定符号extern“C”来解决名字匹配问题。
124怎样定义一个纯虚函数?含有纯虚函数的类称为什么?
答:在虚函数的后面加=0,含有虚函数的类称为抽象类。
125.已知strcpy函数的原型是:
char * strcpy(char * strDest,const char * strSrc);不调用库函数,实现strcpy函数。其中,strSrc是原字符串,strDest是目标字符串 。
答案:
char *strcpy(char *strDest, const char *strSrc)
{
if ( strDest == NULL || strSrc == NULL)
return NULL ;
if ( strDest == strSrc)
return strDest ;
char *tempptr = strDest ; //指针tempptr指向strDest的地址;
while( (*strDest++ = *strSrc++) != ‘\0’) //注意:别忘了转义符;
;
return tempptr ; //返回指针向的地址;
}
126.已知类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] = ” ;
}
else
{
m_data = new char[strlen(str) + 1];
strcpy(m_data,str);
}
}
//拷贝构造函数
String::String(const String &other)
{
m_data = new char[strlen(other.m_data) + 1];
strcpy(m_data,other.m_data);
}
//赋值函数(重载运算符)
String & String::operator =(const String &other)
{
if ( this == &other)
return *this ;
delete []m_data;
m_data = new char[strlen(other.m_data) + 1];
strcpy(m_data,other.m_data);
return *this ;
}
//析构函数
String::~ String(void)
{
delete []m_data ;
}
127.类成员函数的重载、覆盖和隐藏的区别
答案:
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
128.如何打印出当前源文件的文件名以及源文件的当前行号?
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。
129.文件中有一组整数,要求排序后输出到另一个文件中
答案:
void Order(vector &data) //冒泡排序
{
int count = data.size() ;
int tag = false ;
for ( int i = 0 ; i < count ; i++)
{
for ( int j = 0 ; j < count – i – 1 ; j++)
{
if ( data[j] > data[j+1])
{
tag = true ;
int temp = data[j] ;
data[j] = data[j+1] ;
data[j+1] = temp ;
}
}
if ( !tag )
break ;
}
}
void main( void )
{
vectordata;
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();
}
130.一个链表的结点结构
struct Node
{
int data ;
Node *next ;
};
typedef struct Node Node ;
已知链表的头结点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 ;
}
131. 一个链表的结点结构
struct Node
{
int data ;
Node *next ;
};
typedef struct Node Node ;
已知两个链表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 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 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 ;
}
132.已知两个链表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 data )
{
head = head1 ;
head->next = MergeRecursive(head1->next,head2);
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2->next);
}
return head ;
}
133.分析一下这段程序的输出 (Autodesk)
class B
{
public:
B()
{
cout<<”default constructor”<<endl;
}
~B()
{
cout<<”destructed”<<endl;
}
B(int i):data(i)
{
cout<<”constructed by parameter” << data <<endl;
}
private:
int data;
};
B Play( B b)
{
return b ;
}
int main(int argc, char* argv[])
{
B temp = Play(5);
return 0;
}
133 将“引用”作为函数参数有哪些特点?
(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用”*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
134. 什么时候需要“引用”?
流操作符(>)和赋值操作符(=)的返回值、拷贝构造函数的参数、赋值操作符的参数、其它情况都推荐使用引用。
135.面向对象的三个基本特征,并简单叙述之?
1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)
2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。
3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
136.求下面函数的返回值(微软)
int func(x)
{
int countx = 0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;
}
假定x = 9999。 答案:8
思路:将x转化为2进制,看含有的1的个数。
137、写出下列代码的输出内容
#i nclude
int inc(int a)
{
return(++a);
}
int multi(int*a,int*b,int*c)
{
return(*c=*a**b);
}
typedef int(FUNC1)(int in);
typedef int(FUNC2) (int*,int*,int*);
void show(FUNC2 fun,int arg1, int*arg2)
{
INCp=&inc;
int temp =p(arg1);
fun(&temp,&arg1, arg2);
printf(“%d
”,*arg2);
}
main()
{
int a;
show(multi,10,&a);
return 0;
}
答:110
138。编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,且该字符串是由同一字符组成的。
char * search(char *cpSource, char ch)
{
char *cpTemp=NULL, *cpDest=NULL;
int iTemp, iCount=0;
while(*cpSource)
{
if(*cpSource == ch)
{
iTemp = 0;
cpTemp = cpSource;
while(*cpSource == ch)
++iTemp, ++cpSource;
if(iTemp > iCount)
iCount = iTemp, cpDest = cpTemp;
if(!*cpSource)
break;
}
++cpSource;
}
return cpDest;
}
139。请编写一个 C 函数,该函数在给定的内存区域搜索给定的字符,并返回该字符所在位置索引值。
int search(char *cpSource, int n, char ch)
{
int i;
for(i=0; i<n && *(cpSource+i) != ch; ++i);
return i;
}
140.一个单向链表,不知道头节点,一个指针指向其中的一个节点,问如何删除这个指针指向的节点?
将这个指针指向的next节点值copy到本节点,将next指向next->next,并随后删除原next指向的节点。
141、用指针的方法,将字符串“ABCD1234efgh”前后对调显示
#i nclude
#i nclude
#i nclude
int main()
{
char str[] = “ABCD1234efgh”;
int length = strlen(str);
char * p1 = str;
char * p2 = str + length – 1;
while(p1 < p2)
{
char c = *p1;
*p1 = *p2;
*p2 = c;
++p1;
–p2;
}
printf(“str now is %s
”,str);
system(“pause”);
return 0;
}
142、有一分数序列:1/2,1/4,1/6,1/8……,用函数调用的方法,求此数列前20项的和
#i nclude
double getValue()
{
double result = 0;
int i = 2;
while(i < 42)
{
result += 1.0 / i;//一定要使用1.0做除数,不能用1,否则结果将自动转化成整数,即0.000000
i += 2;
}
return result;
}
int main()
{
printf(“result is %f
”, getValue());
system(“pause”);
return 0;
}
143、有一个数组a[1000]存放0–1000;要求每隔二个数删掉一个数,到末尾时循环至开头继续进行,求最后一个被删掉的数的原始下标位置。
以7个数为例:
{0,1,2,3,4,5,6,7} 0–>1–>2(删除)–>3–>4–>5(删除)–>6–>7–>0(删除),如此循环直到最后一个数被删除。
方法1:数组
#include
using namespace std;
#define null 1000
int main()
{
int arr[1000];
for (int i=0;i<1000;++i)
arr[i]=i;
int j=0;
int count=0;
while(count<999)
{
while(arr[j%1000]==null)
j=(++j)%1000;
j=(++j)%1000;
while(arr[j%1000]==null)
j=(++j)%1000;
j=(++j)%1000;
while(arr[j%1000]==null)
j=(++j)%1000;
arr[j]=null;
++count;
}
while(arr[j]==null)
j=(++j)%1000;
cout<<j<<endl;
return 0;
}
方法2:链表
#i nclude
using namespace std;
#define null 0
struct node
{
int data;
node* next;
};
int main()
{
node* head=new node;
head->data=0;
head->next=null;
node* p=head;
for(int i=1;i<1000;i++)
{
node* tmp=new node;
tmp->data=i;
tmp->next=null;
head->next=tmp;
head=head->next;
}
head->next=p;
while(p!=p->next)
{
p->next->next=p->next->next->next;
p=p->next->next;
}
coutdata;
return 0;
}
方法3:通用算法
#i nclude
#define MAXLINE 1000 //元素个数
/*
MAXLINE 元素个数
a[] 元素数组
R[] 指针场
suffix 下标
index 返回最后的下标序号
values 返回最后的下标对应的值
start 从第几个开始
K 间隔
*/
int find_n(int a[],int R[],int K,int& index,int& values,int s=0) {
int suffix;
int front_node,current_node;
suffix=0;
if(s==0) {
current_node=0;
front_node=MAXLINE-1;
}
else {
current_node=s;
front_node=s-1;
}
while(R[front_node]!=front_node) {
printf(“%d
”,a[current_node]);
R[front_node]=R[current_node];
if(K==1) {
current_node=R[front_node];
continue;
}
for(int i=0;i<K;i++){
front_node=R[front_node];
}
current_node=R[front_node];
}
index=front_node;
values=a[front_node];
return 0;
}
int main(void) {
int a[MAXLINE],R[MAXLINE],suffix,index,values,start,i,K;
suffix=index=values=start=0;
K=2;
for(i=0;i<MAXLINE;i++) {
a[i]=i;
R[i]=i+1;
}
R[i-1]=0;
find_n(a,R,K,index,values,2);
printf(“the value is %d,%d
”,index,values);
return 0;
}
144、指出下列程序有什么错误:
void test2()
{
char string[10], str1[10];
int i;
for(i=0; i<10; i++)
{
str1[i] = ‘a’;
}
strcpy( string, str1 );
}
解答:对试题2,如果面试者指出字符数组str1不能在数组内结束可以给3分;如果面试者指出strcpy(string, str1)调用使得从str1内存起复制到string内存起所复制的字节数具有不确定性可以给7分,在此基础上指出库函数strcpy工作方式的给10分;
str1不能在数组内结束:因为str1的存储为:{a,a,a,a,a,a,a,a,a,a},没有’ ′(字符串结束符),所以不能结束
strcpy( char *s1,char *s2)他的工作原理是,扫描s2指向的内存,逐个字符付到s1所指向的内存,直到碰到’ ′,因为str1结尾没有’ ′,所以具有不确定性,不知道他后面还会付什么东东。
正确应如下
void test2()
{
char string[10], str1[10];
int i;
for(i=0; i<9; i++)
{
str1[i] = ‘a’+i; //把abcdefghi赋值给字符数组
}
str[i]=’ ′;//加上结束符
strcpy( string, str1 );
}
145、实现strcmp
int StrCmp(const char *str1, const char *str2)
{
assert(str1 && srt2);
while(*str1 && *str1++ = = *str2++);
return *str1-*str2;
}
146.符串A和B,输出A和B中的最大公共子串。
比如A=”aocdfe” B=”pmcdfa” 则输出”cdf”
*/
//Author: azhen
#i nclude
#i nclude
#i nclude
char *commanstring(char shortstring[], char longstring[])
{
int i, j;
char *substring=malloc(256);
if(strstr(longstring, shortstring)!=NULL) //如果……,那么返回shortstring
return shortstring;
for(i=strlen(shortstring)-1;i>0; i–) //否则,开始循环计算
{
for(j=0; j<=strlen(shortstring)-i; j++){
memcpy(substring, &shortstring[j], i);
substring[i]=’ ′;
if(strstr(longstring, substring)!=NULL)
return substring;
}
}
return NULL;
}
main()
{
char *str1=malloc(256);
char *str2=malloc(256);
char *comman=NULL;
gets(str1);
gets(str2);
if(strlen(str1)>strlen(str2)) //将短的字符串放前面
comman=commanstring(str2, str1);
else
comman=commanstring(str1, str2);
printf(“the longest comman string is: %s
”, comman);
}
147、写一个函数比较两个字符串str1和str2的大小,若相等返回0,若str1大于
str2返回1,若str1小于str2返回-1
int strcmp ( const char * src,const char * dst)
{
int ret = 0 ;
while( ! (ret = *(unsigned char *)src – *(unsigned char *)dst) && *dst)
{
++src;
++dst;
}
if ( ret < 0 )
ret = -1 ;
else if ( ret > 0 )
ret = 1 ;
return( ret );
}
148、判断一个字符串是不是回文
int IsReverseStr(char *aStr)
{
int i,j;
int found=1;
if(aStr==NULL)
return -1;
j=strlen(aStr);
for(i=0;i<j/2;i++)
if(*(aStr+i)!=*(aStr+j-i-1))
{
found=0;
break;
}
return found;
149 #include main()
{
int c[3][3]={1,2,3,4,5,6,7,8,9};
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
printf(“%ld
”,&c[j]);
printf(“————————-
”);
printf(“%ld
”,(c+1));
printf(“%ld
”,(*c+1));
printf(“%ld
”,&c[0][0]);
printf(“%ld
”,**c);
printf(“%ld
”,*c[0]);
if(int(c)==int(*c)) printf(“equl”);
}
为什么c,*c的值相等,(c+1),(*c+1)的值不等 c,*c,**c,代表什么意思?
参考答案:
c是第一个元素的地址,*c是第一行元素的首地址,其实第一行元素的地址就是第一个元素的地址,这容易理解。**c是提领第一个元素。
为什么c,*c的值相等?
int c因为直接用c表示数组c[0][0] printf(“%ld
”,*c[0]);语句已将指针移到数组头。 int(*c)表示c0的值为1,所以相等。 数组c的存放空间示意如下:(机器中是行优先存放的) c[0][0] c[0][1] c[0][2] c[1][0] c[1][1] c[1][2] c[2][0] c[2][1] c[2][2] c是一个二维数组名,实际上它是一个指针常量,不能进行自加、自减运算,即:c++、c–、++c、–c 都是不允许的;
c: 数组名;是一个二维指针,它的值就是数组的首地址,也即第一行元素的首地址(等于 *c),也 等于第一行第一个元素的地址( & c[0][0]);可以说成是二维数组的行指针。 *c: 第一行元素的首地址;是一个一维指针,可以说成是二维数组的列指针。 **c:二维数组中的第一个元素的值;即:c[0][0] 所以: c 和 *c的值是相等的,但他们两者不能相互赋值,(类型不同); (c + 1) :c是行指针,(c + 1)是在c的基础上加上二维数组一行的地址长度,即从&c[0][0] 变到了&c[1][0]; (*c + 1):*c是列指针,(*c + 1)是在*c的基础上加上二数组一个元素的所占的长度,即从 &c[0][0]变到了&c[0][1] 从而(c + 1)和(*c + 1)的值就不相等了
150、定义 int **a[3][4], 则变量占有的内存空间为:__32___ 参考答案:
int **p; /*16位下sizeof(p)=2, 32位下sizeof(p)=4*/
总共 3*4*sizeof(p)