c/c++面试题总结

面试题1:变量的声明和定义有什么区别
为变量分配地址和存储空间的称为定义,不分配地址的称为声明。一个变量可以在多个地方声明,但是只在一个地方定义。加入extern修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。
说明:很多时候一个变量,只是声明不分配内存空间,直到具体使用时才初始化,分配内存空间,如外部变量。
面试题2:写出bool 、int、 float、指针变量与“零值”比较的if语句
bool型数据: if( flag ) { A; } else { B; }
int型数据: if( 0 != flag ) { A; } else { B; }
指针型数: if( NULL == flag ) { A; } else { B; }
float型数据: if ( ( flag >= NORM ) && ( flag <= NORM ) ) { A;
2
}
注意:应特别注意在int、指针型变量和“零值”比较的时候,把“零值”放在左边,这样当把“==”误写成“=”时,编译器可以报错,否则这种逻辑错误不容易发现,并且可能导致很严重的后果。
面试题3:sizeof和strlen的区别
sizeof和strlen有以下区别:
 sizeof是一个操作符,strlen是库函数。
 sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为‘\0‘的字符串作参数。
 编译器在编译时就计算出了sizeof的结果。而strlen函数必须在运行时才能计算出来。并且sizeof计算的是数据类型占内存的大小,而strlen计算的是字符串实际的长度。
 数组做sizeof的参数不退化,传递给strlen就退化为指针了。
注意:有些是操作符看起来像是函数,而有些函数名看起来又像操作符,这类容易混淆的名称一定要加以区分,否则遇到数组名这类特殊数据类型作参数时就很容易出错。最容易混淆为函数的操作符就是sizeof。
面试题4:C语言的关键字 static 和 C++ 的关键字 static 有什么区别
在C中static用来修饰局部静态变量和外部静态变量、函数。而C++中除了上述功能外,还用来定义类的成员变量和函数。即静态成员和静态成员函数。
注意:编程时static的记忆性,和全局性的特点可以让在不同时期调用的函数进行通信,传递信息,而C++的静态成员则可以在多个对象实例间进行通信,传递信息。
面试题5:C中的malloc和C++中的new有什么区别
malloc和new有以下不同:
(1)new、delete 是操作符,可以重载,只能在C++中使用。
(2)malloc、free是函数,可以覆盖,C、C++中都可以使用。
(3)new 可以调用对象的构造函数,对应的delete调用相应的析构函数。
(4)malloc仅仅分配内存,free仅仅回收内存,并不执行构造和析构函数
(5)new、delete返回的是某种数据类型指针,malloc、free返回的是void指针。
注意:malloc申请的内存空间要用free释放,而new申请的内存空间要用delete释放,不要混用。因为两者实现的机理不同。
面试题6:写一个“标准”宏MIN #define min(a,b)((a)<=(b)?(a):(b))
注意:在调用时一定要注意这个宏定义的副作用,如下调用: ((++*p)<=(x)?(++*p):(x)。
p指针就自加了两次,违背了MIN的本意。
3
面试题7:一个指针可以是volatile吗
可以,因为指针和普通变量一样,有时也有变化程序的不可控性。常见例:子中断服务子程序修改一个指向一个buffer的指针时,必须用volatile来修饰这个指针。
说明:指针是一种普通的变量,从访问上没有什么不同于其他变量的特性。其保存的数值是个整型数据,和整型变量不同的是,这个整型数据指向的是一段内存地址。
面试题8:a和&a有什么区别
请写出以下代码的打印结果,主要目的是考察a和&a的区别。 #include<stdio.h> void main( void ) { int a[5]={1,2,3,4,5}; int *ptr=(int *)(&a+1); printf("%d,%d",*(a+1),*(ptr-1)); return; }
输出结果:2,5。
注意:数组名a可以作数组的首地址,而&a是数组的指针。思考,将原式的int *ptr=(int *)(&a+1);改为int *ptr=(int *)(a+1);时输出结果将是什么呢?
面试题9:简述C、C++程序编译的内存分配情况
C、C++中内存分配方式可以分为三种:
(1)从静态存储区域分配:
内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量,static变量等。
(2)在栈上分配:
在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配:
即动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
一个C、C++程序编译时内存分为5大存储区:堆区、栈区、全局区、文字常量区、程序代码区。
4
面试题10:简述strcpy、sprintf与memcpy的区别
三者主要有以下不同之处:
(1)操作对象不同,strcpy的两个操作对象均为字符串,sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
(2)执行效率不同,memcpy最高,strcpy次之,sprintf的效率最低。
(3)实现功能不同,strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他数据类型格式到字符串的转化,memcpy主要是内存块间的拷贝。
说明:strcpy、sprintf与memcpy都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。
面试题11:设置地址为0x67a9的整型变量的值为0xaa66 int *ptr; ptr = (int *)0x67a9; *ptr = 0xaa66;
说明:这道题就是强制类型转换的典型例子,无论在什么平台地址长度和整型数据的长度是一样的,即一个整型数据可以强制转换成地址指针类型,只要有意义即可。
面试题12:面向对象的三大特征
面向对象的三大特征是封装性、继承性和多态性:
 封装性:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)。
 继承性:广义的继承有三种实现形式:实现继承(使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。
 多态性:是将父类对象设置成为和一个或更多它的子对象相等的技术。用子类对象给父类对象赋值之后,父类对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
说明:面向对象的三个特征是实现面向对象技术的关键,每一个特征的相关技术都非常的复杂,程序员应该多看、多练。
面试题13:C++的空类有哪些成员函数
 缺省构造函数。
 缺省拷贝构造函数。
 缺省析构函数。
 缺省赋值运算符。
 缺省取址运算符。
 缺省取址运算符 const。
注意:有些书上只是简单的介绍了前四个函数。没有提及后面这两个函数。但后面这两个函数也是空类的默认函数。另外需要注意的是,只有当实际使用这些函数的时候,编译器才会去定义它们。
5
面试题14:谈谈你对拷贝构造函数和赋值运算符的认识
拷贝构造函数和赋值运算符重载有以下两个不同之处:
(1)拷贝构造函数生成新的类对象,而赋值运算符不能。
(2)由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象是否和新建对象相同。而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配要先把内存释放掉
注意:当有类中有指针类型的成员变量时,一定要重写拷贝构造函数和赋值运算符,不要使用默认的。
面试题15:用C++设计一个不能被继承的类 template <typename T> class A { friend T; private: A() {} ~A() {} }; class B : virtual public A<B> { public: B() {} ~B() {} }; class C : virtual public B { public: C() {} ~C() {} }; void main( void ) { B b; //C c; return; }
注意:构造函数是继承实现的关键,每次子类对象构造时,首先调用的是父类的构造函数,然后才是自己的。
面试题16:访问基类的私有虚函数
写出以下程序的输出结果: #include <iostream.h> class A
6
{ virtual void g() { cout << "A::g" << endl; } private: virtual void f() { cout << "A::f" << endl; } }; class B : public A { void g() { cout << "B::g" << endl; } virtual void h() { cout << "B::h" << endl; } }; typedef void( *Fun )( void ); void main() { B b; Fun pFun; for(int i = 0 ; i < 3; i++) { pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i ); pFun(); } }
输出结果: B::g A::f B::h
注意:本题主要考察了面试者对虚函数的理解程度。一个对虚函数不了解的人很难正确的做出本题。在学习面向对象的多态性时一定要深刻理解虚函数表的工作原理。
面试题17:简述类成员函数的重写、重载和隐藏的区别
(1)重写和重载主要有以下几点不同。
 范围的区别:被重写的和重写的函数在两个类中,而重载和被重载的函数在同一个类中。
 参数的区别:被重写函数和重写函数的参数列表一定相同,而被重载函数和重载函数的参数列表一定不同。
 virtual的区别:重写的基类中被重写的函数必须要有virtual修饰,而重载函数和被重载函数可以被
7
virtual修饰,也可以没有。
(2)隐藏和重写、重载有以下几点不同。
 与重载的范围不同:和重写一样,隐藏函数和被隐藏函数不在同一个类中。
 参数的区别:隐藏函数和被隐藏的函数的参数列表可以相同,也可不同,但是函数名肯定要相同。当参数不相同时,无论基类中的参数是否被virtual修饰,基类的函数都是被隐藏,而不是被重写。
说明:虽然重载和覆盖都是实现多态的基础,但是两者实现的技术完全不相同,达到的目的也是完全不同的,覆盖是动态态绑定的多态,而重载是静态绑定的多态。
面试题18:简述多态实现的原理
编译器发现一个类中有虚函数,便会立即为此类生成虚函数表 vtable。虚函数表的各表项为指向对应虚函数的指针。编译器还会在此类中隐含插入一个指针vptr(对vc编译器来说,它插在类的第一个位置上)指向虚函数表。调用此类的构造函数时,在类的构造函数中,编译器会隐含执行vptr与vtable的关联代码,将vptr指向对应的vtable,将类与此类的vtable联系了起来。另外在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的this指针,这样依靠此this指针即可得到正确的vtable,。如此才能真正与函数体进行连接,这就是动态联编,实现多态的基本原理。
注意:一定要区分虚函数,纯虚函数、虚拟继承的关系和区别。牢记虚函数实现原理,因为多态C++面试的重要考点之一,而虚函数是实现多态的基础。
面试题19:链表和数组有什么区别
数组和链表有以下几点不同:
(1)存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间,长度可变,每个结点要保存相邻结点指针。
(2)数据查找:数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点,效率低。
(3)数据插入或删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动。
(4)越界问题:链表不存在越界问题,数组有越界问题。
说明:在选择数组或链表数据结构时,一定要根据实际需要进行选择。数组便于查询,链表便于插入删除。数组节省空间但是长度固定,链表虽然变长但是占了更多的存储空间。
面试题20:怎样把一个单链表反序
(1)反转一个链表。循环算法。 List reverse(List n) { if(!n) //判断链表是否为空,为空即退出。 { return n; } list cur = n.next; //保存头结点的下个结点 list pre = n; //保存头结点 list tmp;
8
pre.next = null; //头结点的指针指空,转换后变尾结点 while ( NULL != cur.next ) //循环直到cur.next为空 { tmp = cur; //实现如图10.3—图10.5所示 tmp.next = pre pre = tmp; cur = cur.next; } return tmp; //f返回头指针 }
(2)反转一个链表。递归算法。 List *reverse( List *oldList, List *newHead = NULL ) { List *next = oldList-> next; //记录上次翻转后的链表 oldList-> next = newHead; //将当前结点插入到翻转后链表的开头 newHead = oldList; //递归处理剩余的链表 return ( next==NULL )? newHead: reverse( t, newHead ); }
说明:循环算法就是图10.2—图10.5的移动过程,比较好理解和想到。递归算法的设计虽有一点难度,但是理解了循环算法,再设计递归算法就简单多了。
面试题 21:简述队列和栈的异同
队列和栈都是线性存储结构,但是两者的插入和删除数据的操作不同,队列是“先进先出”,栈是“后进先出”。
注意:区别栈区和堆区。堆区的存取是“顺序随意”,而栈区是“后进先出”。栈由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
它与本题中的堆和栈是两回事。堆栈只是一种数据结构,而堆区和栈区是程序的不同内存存储区域。
面试题22:能否用两个栈实现一个队列的功能
结点结构体: typedef struct node { int data; node *next; }node,*LinkStack;
创建空栈: LinkStack CreateNULLStack( LinkStack &S) { S = (LinkStack)malloc( sizeof( node ) ); //申请新结点 if( NULL == S) { printf("Fail to malloc a new node.\n");
9
return NULL; } S->data = 0; //初始化新结点 S->next = NULL; return S; }
栈的插入函数: LinkStack Push( LinkStack &S, int data) { if( NULL == S) //检验栈 { printf("There no node in stack!"); return NULL; } LinkStack p = NULL; p = (LinkStack)malloc( sizeof( node ) ); //申请新结点 if( NULL == p) { printf("Fail to malloc a new node.\n"); return S; } if( NULL == S->next) { p->next = NULL; } else { p->next = S->next; } p->data = data; //初始化新结点 S->next = p; //插入新结点 return S; }
出栈函数: node Pop( LinkStack &S) { node temp; temp.data = 0; temp.next = NULL; if( NULL == S) //检验栈 { printf("There no node in stack!"); return temp; } temp = *S;
10
if( S->next == NULL ) { printf("The stack is NULL,can't pop!\n"); return temp; } LinkStack p = S ->next; //节点出栈 S->next = S->next->next; temp = *p; free( p ); p = NULL; return temp; }
双栈实现队列的入队函数: LinkStack StackToQueuPush( LinkStack &S, int data) { node n; LinkStack S1 = NULL; CreateNULLStack( S1 ); //创建空栈 while( NULL != S->next ) //S出栈入S1 { n = Pop( S ); Push( S1, n.data ); } Push( S1, data ); //新结点入栈 while( NULL != S1->next ) //S1出栈入S { n = Pop( S1 ); Push( S, n.data ); } return S; }
说明:用两个栈能够实现一个队列的功能,那用两个队列能否实现一个队列的功能呢?结果是否定的,因为栈是先进后出,将两个栈连在一起,就是先进先出。而队列是现先进先出,无论多少个连在一起都是先进先出,而无法实现先进后出。
面试题23:计算一颗二叉树的深度
深度的计算函数: int depth(BiTree T) { if(!T) return 0; //判断当前结点是否为叶子结点
11
int d1= depth(T->lchild); //求当前结点的左孩子树的深度 int d2= depth(T->rchild); //求当前结点的右孩子树的深度 return (d1>d2?d1:d2)+1; }
注意:根据二叉树的结构特点,很多算法都可以用递归算法来实现。
面试题24:编码实现直接插入排序
直接插入排序编程实现如下: #include<iostream.h> void main( void ) { int ARRAY[10] = { 0, 6, 3, 2, 7, 5, 4, 9, 1, 8 }; int i,j; for( i = 0; i < 10; i++) { cout<<ARRAY[i]<<" "; } cout<<endl; for( i = 2; i <= 10; i++ ) //将ARRAY[2],…,ARRAY[n]依次按序插入 { if(ARRAY[i] < ARRAY[i-1]) //如果ARRAY[i]大于一切有序的数值, //ARRAY[i]将保持原位不动 { ARRAY[0] = ARRAY[i]; //将ARRAY[0]看做是哨兵,是ARRAY[i]的副本 j = i - 1; do{ //从右向左在有序区ARRAY[1..i-1]中 //查找ARRAY[i]的插入位置 ARRAY[j+1] = ARRAY[j]; //将数值大于ARRAY[i]记录后移 j-- ; }while( ARRAY[0] < ARRAY[j] ); ARRAY[j+1]=ARRAY[0]; //ARRAY[i]插入到正确的位置上 } } for( i = 0; i < 10; i++) { cout<<ARRAY[i]<<" "; } cout<<endl; }
12
注意:所有为简化边界条件而引入的附加结点(元素)均可称为哨兵。引入哨兵后使得查找循环条件的时间大约减少了一半,对于记录数较大的文件节约的时间就相当可观。类似于排序这样使用频率非常高的算法,要尽可能地减少其运行时间。所以不能把上述算法中的哨兵视为雕虫小技。
面试题25:编码实现冒泡排序
冒泡排序编程实现如下: #include <stdio.h> #define LEN 10 //数组长度 void main( void ) { int ARRAY[10] = { 0, 6, 3, 2, 7, 5, 4, 9, 1, 8 }; //待排序数组 printf( "\n" ); for( int a = 0; a < LEN; a++ ) //打印数组内容 { printf( "%d ", ARRAY[a] ); } int i = 0; int j = 0; bool isChange; //设定交换标志 for( i = 1; i < LEN; i++ ) { //最多做LEN-1趟排序 isChange = 0; //本趟排序开始前,交换标志应为假 for( j = LEN-1; j >= i; j-- ) //对当前无序区ARRAY[i..LEN]自下向上扫描 { if( ARRAY[j+1] < ARRAY[j] ) { //交换记录 ARRAY[0] = ARRAY[j+1]; //ARRAY[0]不是哨兵,仅做暂存单元 ARRAY[j+1] = ARRAY[j]; ARRAY[j] = ARRAY[0]; isChange = 1; //发生了交换,故将交换标志置为真 } } printf( "\n" ); for( a = 0; a < LEN; a++) //打印本次排序后数组内容 { printf( "%d ", ARRAY[a] ); } if( !isChange ) //本趟排序未发生交换,提前终止算法 { break; } } printf( "\n" ); return; }
13
面试题26:编码实现直接选择排序 #include"stdio.h" #define LEN 9 void main( void ) { int ARRAY[LEN]={ 5, 6, 8, 2, 4, 1, 9, 3, 7 }; //待序数组 printf("Before sorted:\n"); for( int m = 0; m < LEN; m++ ) //打印排序前数组 { printf( "%d ", ARRAY[m] ); } for (int i = 1; i <= LEN - 1; i++) //选择排序 { int t = i - 1; int temp = 0; for (int j = i; j < LEN; j++) { if (ARRAY[j] < ARRAY[t]) { t = j; } } if (t != (i - 1)) { temp = ARRAY[i - 1]; ARRAY[i - 1] = ARRAY[t]; ARRAY[t] = temp; } } printf( "\n" ); printf("After sorted:\n"); for( i = 0; i < LEN; i++ ) //打印排序后数组 { printf( "%d ", ARRAY[i] ); } printf( "\n" ); }
注意:在直接选择排序中,具有相同关键码的对象可能会颠倒次序,因而直接选择排序算法是一种不稳定的排序方法。在本例中只是例举了简单的整形数组排序,肯定不会有什么问题。但是在复杂的数据元素序列组合中,只是根据单一的某一个关键值排序,直接选择排序则不保证其稳定性,这是直接选择排序的一个弱点。
面试题27:编程实现堆排序
堆排序编程实现: #include <stdio.h>
14
void createHeep(int ARRAY[],int sPoint, int Len) //生成大根堆 { while( ( 2 * sPoint + 1 ) < Len ) { int mPoint = 2 * sPoint + 1 ; if( ( 2 * sPoint + 2 ) < Len ) { if(ARRAY[ 2 * sPoint + 1 ] < ARRAY[ 2 * sPoint + 2 ] ) { mPoint = 2*sPoint+2; } } if(ARRAY[ sPoint ] < ARRAY[ mPoint ]) //堆被破坏,需要重新调整 { int tmpData= ARRAY[ sPoint ]; //交换sPoint与mPoint的数据 ARRAY[ sPoint ] = ARRAY[ mPoint ]; ARRAY[ mPoint ] = tmpData; sPoint = mPoint ; } else { break; //堆未破坏,不再需要调整 } } return; } void heepSort( int ARRAY[], int Len ) //堆排序 { int i=0; for ( i = ( Len / 2 - 1 ); i >= 0; i-- ) //将Hr[0,Lenght-1]建成大根堆 { createHeep(ARRAY, i, Len); } for ( i = Len - 1; i > 0; i-- ) { int tmpData = ARRAY[0]; //与最后一个记录交换 ARRAY[0] = ARRAY[i]; ARRAY[i] = tmpData; createHeep( ARRAY, 0, i ); //将H.r[0..i]重新调整为大根堆 } return; } int main( void )
15
{ int ARRAY[] ={ 5, 4, 7, 3, 9, 1, 6, 8, 2}; printf("Before sorted:\n"); //打印排序前数组内容 for ( int i = 0; i < 9; i++ ) { printf("%d ", ARRAY[i]); } printf("\n"); heepSort( ARRAY, 9 ); //堆排序 printf("After sorted:\n"); //打印排序后数组内容 for( i = 0; i < 9; i++ ) { printf( "%d ", ARRAY[i] ); } printf( "\n" ); return 0; }
说明:堆排序,虽然实现复杂,但是非常的实用。另外读者可是自己设计实现小堆排序的算法。虽然和大堆排序的实现过程相似,但是却可以加深对堆排序的记忆和理解。
面试题28:编程实现基数排序 #include <stdio.h> #include <malloc.h> #define LEN 8 typedef struct node //队列结点 { int data; struct node * next; }node,*QueueNode; typedef struct Queue //队列 { QueueNode front; QueueNode rear; }Queue,*QueueLink; QueueLink CreateNullQueue( QueueLink &Q) //创建空队列 { Q = NULL; Q = ( QueueLink )malloc( sizeof( Queue ) ); if( NULL == Q ) { printf("Fail to malloc null queue!\n"); return NULL; }
16
Q->front = ( QueueNode )malloc( sizeof( node ) ); Q->rear = ( QueueNode )malloc( sizeof( node ) ); if( NULL == Q->front || NULL == Q->rear ) { printf("Fail to malloc a new queue's fornt or rear!\n"); return NULL; } Q->rear = NULL; Q->front->next= Q->rear; return Q; } int lenData( node data[], int len) //计算队列中各结点的数据的最大位数 { int m = 0; int temp = 0; int d; for( int i = 0; i < len; i++) { d = data[i].data; while( d > 0) { d /= 10; temp ++; } if( temp > m ) { m = temp; } temp = 0; } return m; } QueueLink Push( QueueLink &Q , node node ) //将数据压入队列 { QueueNode p1,p; p =( QueueNode )malloc( sizeof( node ) ); if( NULL == p ) { printf("Fail to malloc a new node!\n"); return NULL; } p1 = Q->front; while(p1->next != NULL) { p1 = p1->next; } p->data = node.data; p1->next = p; p->next = Q->rear;
17
return NULL; } node Pop( QueueLink &Q) //数据出队列 { node temp; temp.data = 0; temp.next = NULL; QueueNode p; p = Q->front->next; if( p != Q->rear ) { temp = *p; Q->front->next = p->next; free( p ); p = NULL; } return temp; } int IsEmpty( QueueLink Q) { if( Q->front->next == Q->rear ) { return 0; } return 1; } int main( void ) { int i = 0; int Max = 0; //记录结点中数据的最大位数 int d = 10; int power = 1; int k = 0; node Array[LEN] ={{450, NULL}, {32,NULL}, { 781,NULL}, { 57 ,NULL},组 { 145,NULL},{ 613,NULL},{ 401,NULL},{ 594,NULL}}; //队列结点数 QueueLink Queue[10]; for( i = 0; i < 10; i++) { CreateNullQueue( Queue[i]); //初始化队列数组 } for( i = 0; i < LEN; i++) { printf("%d ",Array[i].data); } printf("\n"); Max = lenData( Array, LEN ); //计算数组中关键字的最大位数 printf("%d\n",Max);
18
for(int j = 0; j < Max; j++) //按位排序 { if(j == 0) power = 1; else power = power *d; for(i = 0; i < LEN; i++) { k = Array[i].data /power - (Array[i].data/(power * d)) * d; Push( Queue[k], Array[i] ); } for(int l = 0, k = 0; l < d; l++) //排序后出队列重入数组 { while( IsEmpty( Queue[l] ) ) { Array[k++] = Pop( Queue[l] ); } } for( int t = 0; t < LEN; t++) { printf("%d ",Array[t].data); } printf("\n"); } return 0; }
说明:队列为基数排序的实现提供了很大的方便,适当的数据机构可以减少算法的复杂度,让更多的算法实现更容易。
面试题29:谈谈你对编程规范的理解或认识
编程规范可总结为:程序的可行性,可读性、可移植性以及可测试性。
说明:这是编程规范的总纲目,面试者不一定要去背诵上面给出的那几个例子,应该去理解这几个例子说明的问题,想一想,自己如何解决可行性、可读性、可移植性以及可测试性这几个问题,结合以上几个例子和自己平时的编程习惯来回答这个问题。
面试题30:short i = 0; i = i + 1L;这两句有错吗
代码一是错的,代码二是正确的。
说明:在数据安全的情况下大类型的数据向小类型的数据转换一定要显示的强制类型转换。
面试题31:&&和&、||和|有什么区别
(1)&和|对操作数进行求值运算,&&和||只是判断逻辑关系。
19
(2)&&和||在在判断左侧操作数就能确定结果的情况下就不再对右侧操作数求值。
注意:在编程的时候有些时候将&&或||替换成&或|没有出错,但是其逻辑是错误的,可能会导致不可预想的后果(比如当两个操作数一个是1另一个是2时)。
面试题32:C++的引用和C语言的指针有什么区别
指针和引用主要有以下区别:
(1)引用必须被初始化,但是不分配存储空间。指针不声明时初始化,在初始化的时候需要分配存储空间。
(2)引用初始化以后不能被改变,指针可以改变所指的对象。
(3)不存在指向空值的引用,但是存在指向空值的指针。
注意:引用作为函数参数时,会引发一定的问题,因为让引用作参数,目的就是想改变这个引用所指向地址的内容,而函数调用时传入的是实参,看不出函数的参数是正常变量,还是引用,因此可能会引发错误。所以使用时一定要小心谨慎。
面试题33:在二元树中找出和为某一值的所有路径
输入一个整数和一棵二元树。从树的根结点开始往下访问,一直到叶结点所经过的所有结点形成一条路径。打印出和与输入整数相等的所有路径。例如,输入整数9和如下二元树:
3
/ \
2 6
/ \
5 4
则打印出两条路径:3,6和3,2,4。
【答案】 typedef struct path { BiTNode* tree; //结点数据成员 struct path* next; //结点指针成员 }PATH,*pPath;
初始化树的结点栈: void init_path( pPath* L ) { *L = ( pPath )malloc( sizeof( PATH ) ); //创建空树 ( *L )->next = NULL; }
树结点入栈函数: void push_path(pPath H, pBTree T) { pPath p = H->next; pPath q = H; while( NULL != p )
20
{ q = p; p = p->next; } p = ( pPath )malloc( sizeof( PATH ) ); //申请新结点 p->next = NULL; //初始化新结点 p->tree = T; q->next = p; //新结点入栈 }
树结点打印函数: void print_path( pPath L ) { pPath p = L->next; while( NULL != p ) //打印当前栈中所有数据 { printf("%d, ", p->tree->data); p = p->next; } }
树结点出栈函数: void pop_path( pPath H ) { pPath p = H->next; pPath q = H; if( NULL == p ) //检验当前栈是否为空 { printf("Stack is null!\n"); return; } p = p->next; while( NULL != p ) //出栈 { q = q->next; p = p->next; } free( q->next ); //释放出栈结点空间 q->next = NULL; }
判断结点是否为叶子结点: int IsLeaf(pBTree T) { return ( T->lchild == NULL )&&( T->rchild==NULL ); }
查找符合条件的路径: int find_path(pBTree T, int sum, pPath L)
21
{ push_path( L, T); record += T->data; if( ( record == sum ) && ( IsLeaf( T ) ) ) //打印符合条件的当前路径 { print_path( L ); printf( "\n" ); } if( T->lchild != NULL ) //递归查找当前节点的左孩子 { find_path( T->lchild, sum, L); } if( T->rchild != NULL ) //递归查找当前节点的右孩子 { find_path( T->rchild, sum, L); } record -= T->data; pop_path(L); return 0; }
注意:数据结构一定要活学活用,例如本题,把所有的结点都压入栈,而不符合条件的结点弹出栈,很容易实现了有效路径的查找。虽然用链表也可以实现,但是用栈更利于理解这个问题,即适当的数据结构为更好的算法设计提供了有利的条件。
面试题34:写一个“标准”宏MIN
写一个“标准”宏MIN,这个宏输入两个参数并且返回较小的一个。
【答案】 #define min(a,b)((a)<=(b)?(a):(b))
注意:在调用时一定要注意这个宏定义的副作用,如下调用: ((++*p)<=(x)?(++*p):(x)。
p指针就自加了两次,违背了MIN的本意。
面试题35:typedef和define有什么区别
(1)用法不同:typedef用来定义一种数据类型的别名,增强程序的可读性。define主要用来定义常量,以及书写复杂使用频繁的宏。
(2)执行时间不同:typedef是编译过程的一部分,有类型检查的功能。define是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
(3)作用域不同:typedef有作用域限定。define不受作用域约束,只要是在define声明后的引用都是正确的。
(4)对指针的操作不同:typedef和define定义的指针时有很大的区别。
注意:typedef定义是语句,因为句尾要加上分号。而define不是语句,千万不能在句尾加分号。
22
面试题36:关键字const是什么
const用来定义一个只读的变量或对象。主要优点:便于类型检查、同宏定义一样可以方便地进行参数的修改和调整、节省空间,避免不必要的内存分配、可为函数重载提供参考。
说明:const修饰函数参数,是一种编程规范的要求,便于阅读,一看即知这个参数不能被改变,实现时不易出错。
面试题37:static有什么作用
static在C中主要用于定义全局静态变量、定义局部静态变量、定义静态函数。在C++中新增了两种作用:定义静态数据成员、静态函数成员。
注意:因为static定义的变量分配在静态区,所以其定义的变量的默认值为0,普通变量的默认值为随机数,在定义指针变量时要特别注意。
面试题38:extern有什么作用
extern标识的变量或者函数声明其定义在别的文件中,提示编译器遇到此变量和函数时在其它模块中寻找其定义。
面试题39:流操作符重载为什么返回引用
在程序中,流操作符>>和<<经常连续使用。因此这两个操作符的返回值应该是一个仍旧支持这两个操作符的流引用。其他的数据类型都无法做到这一点。
注意:除了在赋值操作符和流操作符之外的其他的一些操作符中,如+、-、*、/等却千万不能返回引用。因为这四个操作符的对象都是右值,因此,它们必须构造一个对象作为返回值。
面试题40:简述指针常量与常量指针区别
指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其他地方不能改变。常量指针是指定义了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值。
指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变性。
注意:无论是指针常量还是常量指针,其最大的用途就是作为函数的形式参数,保证实参在被调用函数中的不可改变特性。
面试题41:数组名和指针的区别
请写出以下代码的打印结果: #include <iostream.h> #include <string.h> void main(void) { char str[13]="Hello world!";
23
char *pStr="Hello world!"; cout<<sizeof(str)<<endl; cout<<sizeof(pStr)<<endl; cout<<strlen(str)<<endl; cout<<strlen(pStr)<<endl; return; }
【答案】
打印结果: 13 4 12 12
注意:一定要记得数组名并不是真正意义上的指针,它的内涵要比指针丰富的多。但是当数组名当做参数传递给函数后,其失去原来的含义,变作普通的指针。另外要注意sizeof不是函数,只是操作符。
面试题42:如何避免“野指针”
“野指针”产生原因及解决办法如下:
(1)指针变量声明时没有被初始化。解决办法:指针声明时初始化,可以是具体的地址值,也可让它指向NULL。
(2)指针 p 被 free 或者 delete 之后,没有置为 NULL。解决办法:指针指向的内存空间被释放后指针应该指向NULL。
(3)指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间并且让指针指向NULL。
注意:“野指针”的解决方法也是编程规范的基本原则,平时使用指针时一定要避免产生“野指针”,在使用指针前一定要检验指针的合法性。
面试题43:常引用有什么作用
常引用的引入主要是为了避免使用变量的引用时,在不知情的情况下改变变量的值。常引用主要用于定义一个普通变量的只读属性的别名、作为函数的传入形参,避免实参在调用函数中被意外的改变。
说明:很多情况下,需要用常引用做形参,被引用对象等效于常对象,不能在函数中改变实参的值,这样的好处是有较高的易读性和较小的出错率。
面试题44:编码实现字符串转化为数字
编码实现函数atoi(),设计一个程序,把一个字符串转化为一个整型数值。例如数字:“5486321”,转化成字符:5486321。
【答案】 int myAtoi(const char * str) {
24
int num = 0; //保存转换后的数值 int isNegative = 0; //记录字符串中是否有负号 int n =0; char *p = str; if(p == NULL) //判断指针的合法性 { return -1; } while(*p++ != '\0') //计算数字符串度 { n++; } p = str; if(p[0] == '-') //判断数组是否有负号 { isNegative = 1; } char temp = '0'; for(int i = 0 ; i < n; i++) { char temp = *p++; if(temp > '9' ||temp < '0') //滤除非数字字符 { continue; } if(num !=0 || temp != '0') //滤除字符串开始的0字符 { temp -= 0x30; //将数字字符转换为数值 num += temp *int( pow(10 , n - 1 -i) ); } } if(isNegative) //如果字符串中有负号,将数值取反 { return (0 - num); } else { return num; //返回转换后的数值 } }
注意:此段代码只是实现了十进制字符串到数字的转化,读者可以自己去实现2进制,8进制,10进制,16进制的转化。
面试题45:简述strcpy、sprintf与memcpy的区别
三者主要有以下不同之处:
(1)操作对象不同,strcpy的两个操作对象均为字符串,sprintf的操作源对象可以是多种数据类型,
25
目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
(2)执行效率不同,memcpy最高,strcpy次之,sprintf的效率最低。
(3)实现功能不同,strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他数据类型格式到字符串的转化,memcpy主要是内存块间的拷贝。
说明:strcpy、sprintf与memcpy都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。
面试题46:用C编写一个死循环程序 while(1) { }
说明:很多种途径都可实现同一种功能,但是不同的方法时间和空间占用度不同,特别是对于嵌入式软件,处理器速度比较慢,存储空间较小,所以时间和空间优势是选择各种方法的首要考虑条件。
面试题47:编码实现某一变量某位清0或置1
给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清a的bit 3,在以上两个操作中,要保持其他位不变。
【答案】 #define BIT3 (0x1 << 3 ) Satic int a;
设置a的bit 3: void set_bit3( void ) { a |= BIT3; //将a第3位置1 }
清a的bit 3 void set_bit3( void ) { a &= ~BIT3; //将a第3位清零 }
说明:在置或清变量或寄存器的某一位时,一定要注意不要影响其他位。所以用加减法是很难实现的。
面试题48:评论下面这个中断函数
中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展——让标准C支持中断。具体代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义一个中断服务子程序(ISR),请评论以下这段代码。 __interrupt double compute_area (double radius) { double area = PI * radius * radius; printf(" Area = %f", area); return area;
26
}
【答案】
这段中断服务程序主要有以下四个问题:
(1)ISR 不能返回一个值。
(2)ISR 不能传递参数。
(3)在ISR 中做浮点运算是不明智的。
(4)printf()经常有重入和性能上的问题。
注意:本题的第三个和第四个问题虽不是考察的重点,但是如果能提到这两点可给面试官留下一个好印象。
面试题49:构造函数能否为虚函数
构造函数不能是虚函数。而且不能在构造函数中调用虚函数,因为那样实际执行的是父类的对应函数,因为自己还没有构造好。析构函数可以是虚函数,而且,在一个复杂类结构中,这往往是必须的。析构函数也可以是纯虚函数,但纯虚析构函数必须有定义体,因为析构函数的调用是在子类中隐含的。
说明:虚函数的动态绑定特性是实现重载的关键技术,动态绑定根据实际的调用情况查询相应类的虚函数表,调用相应的虚函数。
面试题50:谈谈你对面向对象的认识
面向对象可以理解成对待每一个问题,都是首先要确定这个问题由几个部分组成,而每一个部分其实就是一个对象。然后再分别设计这些对象,最后得到整个程序。传统的程序设计多是基于功能的思想来进行考虑和设计的,而面向对象的程序设计则是基于对象的角度来考虑问题。这样做能够使得程序更加的简洁清晰。
说明:编程中接触最多的“面向对象编程技术”仅仅是面向对象技术中的一个组成部分。发挥面向对象技术的优势是一个综合的技术问题,不仅需要面向对象的分析,设计和编程技术,而且需要借助必要的建模和开发工具。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.static有什么用途?(请至少说明两种) 1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。 2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。 3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用 2.引用与指针有什么区别? 1) 引用必须被初始化,指针不必。 2) 引用初始化以后不能被改变,指针可以改变所指的对象。 3) 不存在指向空值的引用,但是存在指向空值的指针。 3.描述实时系统的基本特 在特定时间内完成特定的任务,实时与可靠。 4.全局变量和局部变量在内存中是否有区别?如果有,是什么区别? 全局变量储存在静态数据库,局部变量在堆栈。 5.什么是平衡二叉树? 左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于1。 6.堆栈溢出一般是由什么原因导致的? 没有回收垃圾资源。 7.什么函数不能声明为虚函数? constructor函数不能声明为虚函数。 8.冒泡排序算法的时间复杂度是什么? 时间复杂度是O(n^2)。 9.写出float x 与“零值”比较的if语句。 if(x>0.000001&&x<-0.000001) 10.Internet采用哪种网络协议?该协议的主要层次结构? Tcp/Ip协议 主要层次结构为: 应用层/传输层/网络层/数据链路层/物理层。 11.Internet物理地址和IP地址转换采用什么协议? ARP (Address Resolution Protocol)(地址解析協議) 12.IP地址的编码分为哪俩部分? IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区分哪些是网络位哪些是主机位。 13.用户输入M,N值,从1至N开始顺序循环数数,每数到M输出该数值,直至全部输出。写出C程序。 循环链表,用取余操作做 14.不能做switch()的参数类型是: switch的参数不能为实型。 1.写出判断ABCD四个表达式的是否正确, 若正确, 写出经过表达式中 a的值(3分) int a = 4; (A)a += (a++); (B) a += (++a) ;(C) (a++) += a;(D) (++a) += (a++); a = ? 答:C错误,左侧不是一个有效变量,不能赋值,可改为(++a) += a; 改后答案依次为9,10,10,11 2.某32位系统下, C++程序,请计算sizeof 的值(5分). char str[] = “http://www.ibegroup.com/” char *p = str ; int n = 10; 请计算 sizeof (str ) = ?(1) sizeof ( p ) = ?(2) sizeof ( n ) = ?(3) void Foo ( char str[100]){ 请计算 sizeof( str ) = ?(4) } void *p = malloc( 100 ); 请计算 sizeof ( p ) = ?(5) 答:(1)17 (2)4 (3) 4 (4)4 (5)4 3. 回答下面的问题. (4分) (1).头文件中的 ifndef/define/endif 干什么用?预处理 答:防止头文件被重复引用 (2). #i nclude 和 #i nclude “filename.h” 有什么区别? 答:前者用来包含开发环境提供的库头文件,后者用来包含自己编写的头文件。 (3).在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”声明? 答:函数和变量被C++编译后在符号库中的名字与C语言的不同,被extern "C"修饰的变 量和函数是按照C语言方式编译和连接的。由于编译后的名字不同,C++程序不能直接调 用C 函数。C++提供了一个C 连接交换指定符号extern“C”来解决这个问题。 (4). switch()中不允许的数据类型是? 答:实型 4. 回答下面的问题(6分) (1).Void GetMemory(char **p, int num){ *p = (char *)malloc(num); } void Test(void){ char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } 请问运行Test 函数会有什么样的结果? 答:输出“hello” (2). void Test(void){ char *str = (char *) malloc(100); strcpy(str, “hello”); free(str); if(str != NULL){ strcpy(str, “world”); printf(str); } } 请问运行Test 函数会有什么样的结果? 答:输出“world” (3). char *GetMemory(void){ char p[] = "hello world"; return p; } void Test(void){ char *str = NULL; str = GetMemory(); printf(str); } 请问运行Test 函数会有什么样的结果? 答:无效的指针,输出不确定 5. 编写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 * 类型的返回值? 答:方便赋值给其他变量 6.MFC中CString是类型安全类么? 答:不是,其它数据类型转换到CString可以使用CString的成员函数Format来转换 7.C++中为什么用模板类。 答:(1)可用来创建动态增长和减小的数据结构 (2)它是类型无关的,因此具有很高的可复用。 (3)它在编译时而不是运行时检查数据类型,保证了类型安全 (4)它是平台无关的,可移植性 (5)可用于基本数据类型 8.CSingleLock是干什么的。 答:同步多个线程对一个数据类的同时访问 9.NEWTEXTMETRIC 是什么。 答:物理字体结构,用来设置字体的高宽大小 10.程序什么时候应该使用线程,什么时候单线程效率高。 答:1.耗时的操作使用线程,提高应用程序响应 2.并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求。 3.多CPU系统中,使用线程提高CPU利用率 4.改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独 立的运行部分,这样的程序会利于理解和修改。 其他情况都使用单线程。 11.Windows是内核级线程么。 答:见下一题 12.Linux有内核级线程么。 答:线程通常被定义为一个进程中代码的不同执行路线。从实现方式上划分,线程有两 种类型:“用户级线程”和“内核级线程”。 用户线程指不需要内核支持而在用户程序 中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度 和管理线程的函数来控制用户线程。这种线程甚至在象 DOS 这样的操作系统中也可实现 ,但线程的调度需要用户程序完成,这有些类似 Windows 3.x 的协作式多任务。另外一 种则需要内核的参与,由内核完成线程的调度。其依赖于操作系统核心,由内核的内部 需求进行创建和撤销,这两种模型各有其好处和缺点。用户线程不需要额外的内核开支 ,并且用户态线程的实现方式可以被定制或修改以适应特殊应用的要求,但是当一个线 程因 I/O 而处于等待状态时,整个进程就会被调度程序切换为等待状态,其他线程得不 到运行的机会;而内核线程则没有各个限制,有利于发挥多处理器的并发优势,但却占 用了更多的系统开支。 Windows NT和OS/2支持内核线程。Linux 支持内核级的多线程 13.C++中什么数据分配在栈或堆中,New分配数据是在近堆还是远堆中? 答:栈: 存放局部变量,函数调用参数,函数返回值,函数返回地址。由系统管理 堆: 程序运行时动态申请,new 和 malloc申请的内存就在堆上 14.使用线程是如何防止出现大的波峰。 答:意思是如何防止同时产生大量的线程,方法是使用线程池,线程池具有可以同时提 高调度效率和限制资源使用的好处,线程池中的线程达到最大数时,其他线程就会排队 等候。 15函数模板与类模板有什么区别? 答:函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化 必须由程序员在程序中显式地指定。 16一般数据库若出现日志满了,会出现什么情况,是否还能使用? 答:只能执行查询等读操作,不能执行更改,备份等写操作,原因是任何写操作都要记 录日志。也就是说基本上处于不能使用的状态。 17 SQL Server是否支持行级锁,有什么好处? 答:支持,设立封锁机制主要是为了对并发操作进行控制,对干扰进行封锁,保证数据 的一致和准确,行级封锁确保在用户取得被更新的行到该行进行更新这段时间内不 被其它用户所修改。因而行级锁即可保证数据的一致又能提高数据操作的迸发。 18如果数据库满了会出现什么情况,是否还能使用? 答:见16 19 关于内存对齐的问题以及sizof()的输出 答:编译器自动对齐的原因:为了提高程序能,数据结构(尤其是栈)应该尽可能 地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问 ;然而,对齐的内存访问仅需要一次访问。 20 int i=10, j=10, k=3; k*=i+j; k最后的值是? 答:60,此题考察优先级,实际写成: k*=(i+j);,赋值运算符优先级最低 21.对数据库的一张表进行操作,同时要对另一张表进行操作,如何实现? 答:将操作多个表的操作放入到事务中进行处理 22.TCP/IP 建立连接的过程?(3-way shake) 答:在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。   第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状 态,等待服务器确认; 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个 SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;   第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1) ,此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 23.ICMP是什么协议,处于哪一层? 答:Internet控制报文协议,处于网络层(IP层) 24.触发器怎么工作的? 答:触发器主要是通过事件进行触发而被执行的,当对某一表进行诸如UPDATE、 INSERT 、 DELETE 这些操作时,数据库就会自动执行触发器所定义的SQL 语句,从而确保对数 据的处理必须符合由这些SQL 语句所定义的规则。 25.winsock建立连接的主要实现步骤? 答:服务器端:socker()建立套接字,绑定(bind)并监听(listen),用accept() 等待客户端连接。 客户端:socker()建立套接字,连接(connect)服务器,连接上后使用send()和recv( ),在套接字上写读数据,直至数据交换完毕,closesocket()关闭套接字。 服务器端:accept()发现有客户端连接,建立一个新的套接字,自身重新开始等待连 接。该新产生的套接字使用send()和recv()写读数据,直至数据交换完毕,closesock et()关闭套接字。 26.动态连接库的两种方式? 答:调用一个DLL中的函数有两种方法: 1.载入时动态链接(load-time dynamic linking),模块非常明确调用某个导出函数 ,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向 系统提供了载入DLL时所需的信息及DLL函数定位。 2.运行时动态链接(run-time dynamic linking),运行时可以通过LoadLibrary或Loa dLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的 出口地址,然后就可以通过返回的函数指针调用DLL函数了。如此即可避免导入库文件了 。 27.IP组播有那些好处? 答:Internet上产生的许多新的应用,特别是高带宽的多媒体应用,带来了带宽的急剧 消耗和网络拥挤问题。组播是一种允许一个或多个发送者(组播源)发送单一的数据包 到多个接收者(一次的,同时的)的网络技术。组播可以大大的节省网络带宽,因为无 论有多少个目标地址,在整个网络的任何一条链路上只传送单一的数据包。所以说组播 技术的核心就是针对如何节约网络资源的前提下保证服务质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值