转载自:http://blog.sina.com.cn/s/blog_6541e59a0101p3xv.html
科大讯飞面试题:
1、问TCP/UDP区别
2、c++是完全的oop么
3、c++怎么体现出是oop
4、memcpy 与strcpy区别,安全性等等
5、野指针
6、设计模式
7、怎么查内存泄露问题
1:
TCP
传输控制协议,
提供的是面向连接、可靠的字节流服务。
当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。
TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
TCP支持的应用协议:Telnet(远程登录)、FTP(文件传输协议)、SMTP(简单邮件传输协议)。TCP用于传输数据量大,可靠性要求高的应用
UDP
用户数据报协议,是一个简单的面向数据报的运输层协议。
UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。
由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快
面向非连接的(正式通信前不必与对方建立连接,不管对方状态就直接发送,像短信,QQ),不能提供可靠性、流控、差错恢复功能。UDP用于一次只传送少量数据,可靠性要求低、传输经济等应用。
UDP支持的应用协议:NFS(网络文件系统)、SNMP(简单网络管理系统)、DNS(主域名称系统)、TFTP(通用文件传输协议)等。
2:不是的
C++属于OOP.oop三个特性缺一不可:封装,继承,多态。C++是学习OOP的精髓
不完全是OOP:C++兼容C的特性,程序入口函数没有放入类中
完全的OOP:JAVA,C#
JAVA打印数组:
public class Demo {
public static void main(String[] args) {
int[] array = new int[]{1, 2, 3};
Demo demo = new Demo();
demo.printArray(array);
}
/**
* 打印数组
* @param array
*/
public void printArray(int[] array){
System.out.println("第一种方法: ");
for(int i : array){
System.out.print(i + ", ");
}
System.out.println();
System.out.println("第二种方法: ");
// 这个也行
for(int i = 0; i < array.length; i++){
System.out.print(i + ", ");
}
}
}
3:
OOP有三个特性:
第一个关键特性是定义了类,封装了表现和操作的抽象数据类型。在oop语言中,类是模块、封装和数据抽象的基础。
第二个关键特性是继承,从已存在的类型中继承元素(表现和方法),改变或扩展旧类型的方法。
第三个关键技术被称为多态性,它允许使用类似的方法操作不同类型的对象(通常是子类对象)。它使得类的可用性进一步提高,程序也因此更容易维护和扩展。
如果你熟悉C++,从上面的总结就可以知道了:
C++定义了类,这不用多说;
C++基类与派生类的关系就是继承;
C++通过一个指向基类的指针调用虚成员函数的时候,运行时系统将能够根据指针所指向的实际对象调用恰当的成员函数实现,这是多态的体现。
要点:封装,继承,多态
4.
strcpy提供了字符串的复制。即strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符。
memcpy提供了一般内存的复制。即memcpy对于需要复制的内容没有限制,因此用途更广。
strcpy和memcpy主要有以下3方面的区别。
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
补充:
memcpy 和 memmove 都是C语言中的库函数:
1相同点:其原型相似,它们都是从src所指向的内存中复制count个字节到dest所指内存中。并返回dest的值。
2不同点:当源内存区域 和 目标内存区域无交叉重叠时,两者的结果是一样的,但如果有交叉,memcpy是从src的起始部分开始复制,所以此时会发生错误,交叉部分的src内容就会被覆盖掉了。
而memmove则由于采用不同的复制机制,所以可以正确处理第二种情况。
void *memmove(
void *dst,
const
void *src,
int n)
{
char *dp = (
char *)dst;
char *sp = (
char *)src;
assert((src!=
0)&&(dst!=
0)&&(n>
0));
//
not null
//
非重叠
//
dp < sp
//
dp > (sp+n)
if(sp>dp||(sp+n)<dp)
{
while(n--)
*(dp++) = *(sp++);
*dp =
'
\0
';
}
else
if(sp<dp)
//
重叠 (此时条件 sp<dp<(sp+n))如果sp==dp则快速的返回
{
//
反向拷贝
sp += n;
dp += n;
*dp =
'
\0
';
while(n--)
*(--dp) = *(--sp);
}
return dst;
}
memset
原型:extern void *memset(void *buffer, int c, int count);
用法:#include <string.h>
功能:把buffer所指内存区域的前count个字节设置成字符c。
说明:返回指向buffer的指针。用来对一段内存空间全部设置为某个字符。
举例:char a[100];memset(a, '\0', sizeof(a));
memset可以方便的清空一个结构类型的变量或数组。
但注意,类中有虚函数(即虚指针)构造函数中使用memset会情况vptr.
如:
struct sample_struct
{
char csName[16];
int iSeq;
int iType;
};
对于变量
struct sample_strcut stTest;
一般情况下,清空stTest的方法:
stTest.csName[0]='\0';
stTest.iSeq=0;
stTest.iType=0;
用memset就非常方便:
memset(&stTest,0,sizeof(struct sample_struct));
如果是数组:
struct sample_struct TEST[10];
则
memset(TEST,0,sizeof(struct sample_struct)*10);
5.
“野指针”不是NULL指针,是指向“垃圾”内存(不可用内存)的指针。
1)指针变量没有被初始化;2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针;3)指针操作超越了变量的作用范围(或者生命周期)
补充:
空指针(NULL,0),野指针,void*
int a=10;//假设a的内存地址为0x1000,
//访问a的时候,实际上都是访问的0x1000单元中的10
现在定义:int *p;//给指针变量p分配一个内存单元,假设为0x1003;
//此时,0x1003中的值是不确定/随机的,p是野指针
//即野指针指向不可用内存区域的指针
p=&a;//在0x1003单元中保存0x1000(存放了变量a的内存地址)
p=NULL,就是说:内存单元0x1003不存放任何变量的内存地址。
void 指针。
int n=3, *p;
void *gp;
gp = &n;
p=(int *)gp1;
野指针
NULL指针相对野指针的好处:可以if判断是否为空;对NULL指针操作/删除不会发生不可预知的错误
“野指针”是很危险的,if无法判断一个指针是正常指针还是“野指针”,操作野指针发生不可预知的行为。
野指针的成因主要有三种:
一、 指针变量没有被初始化。任何 指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。
二、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。
三、指针操作超越了 变量的作用范围。比如不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。示例程序如下:
class A
{
public:
void Func(void){ cout << “Func of class A” << endl; }
};
class B
{
public:
A *p;
void Test(void)
{
A a;
p = &a; // 注意 a 的 生命期 ,只在这个函数Test中,而不是整个class B
}
void Test1()
{
p->Func(); // p 是“野指针”
}
};
函数 Test1 在执行语句 p->Func()时,p 的值还是 a 的地址,对象 a 的内容已经被清除,所以 p 就成了“野指针” 。
避免方法
1)指针变量一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。
2)当指针p指向的内存空间释放时,设置指针p的值为NULL。
增加:
用智能指针(推荐shared_ptr,但注意循环引用引发的问题)进行包装
空指针/0/NULL
空指针是一个被赋值为0的指针,在没有被具体初始化之前,其值为0.
NULL 是一个标准规定的宏定义,用来表示空指针常量。
#define NULL 0 或者
#define NULL ((void*)0)
判断一个指针是否为空指针:
f(!p) 和 if(p == NULL) ,if(NULL == p)
VOID指针
void *可以指向任何类型的数据。
任何指针都可以赋值给void指针。
void指针赋值给其他类型的指针时都要进行转换,如malloc 函数,其返回类型是 void* 类型。
int* p;
p = (int *) malloc (sizeof(int));
赋值前要进行强制转化。
小心使用void指针类型
按照ANSI(American National Standards Institute)标准,不能对void指针进行算法操作:
void * pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误
//ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。
//例如:
int *pint;
pint++; //ANSI:正确
pint++的结果是使其增大sizeof(int)。
GNU编译器认为正确:
pvoid++; //GNU:正确
pvoid += 1; //GNU:正确
pvoid++的执行结果是其增大了1。
void不能代表一个真实的变量
下面代码都企图让void代表一个真实的变量,因此都是错误的代码:
void a; //错误
function(void a); //错误
void体现了一种抽象,这个世界上的变量都是“有类型”的。
6.
《大话设计模式》,c++编写演示代码
7.
工具:CompuWare的BoundChecker
CRT:_CrtCheckMemory
MFC:AfxCheckMemory, AfxDumpMemoryLeaks
补充:
方法一:任务管理器--》性能---》运行程序,观察CPU的情况
方法二:VC6.0中有两种方式
1】CRT:_CrtCheckMemory 调试器和 CRT 调试堆函数
详见: http://blog.sina.com.cn/s/blog_4012aec7010085jo.html
个人对这种方法有疑问:下列VC6.0上的运行结果有问题
#define CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
#include <stdio.h>
using namespace std;
int main(){
int *p=(int*)malloc(sizeof(int));
*p=6;
cout<<*p<<endl;
free(p);
p=NULL;
_CrtDumpMemoryLeaks();//注意这条语句的摆放位置,因为这会直接影响到你的判断结果
return 0;
}
当注释掉free(p);p=NULL;或者不注释掉时,程序均出现内存泄漏?????????
2】安装boundschecker
详见: http://blog.sina.com.cn/s/blog_67299aec0100rrqn.html
3】自己的想法:
编写指针数组,存入内存指针,程序结束时一次free/delete掉