基础面试题整理

2013/5/18~2013/9/X  

 

1. C和C++申请内存的区别... 1

2. 堆和栈的区别... 1

3 套接口(IO模型)4

4 进程和线程有什么区别?... 4

5 几种进程间的通信方式... 5

6 C++中 struct 和class的区别... 5

7 static 作用... 6

8 C++虚函数与纯虚函数用法与区别... 7

9 内存泄露(Memory leak)与溢出(Overflow)... 8

10 数据库... 9

11 死锁产生的原因及四个必要条件... 10

 

1. C和C++申请内存的区别

cmallocfreec++newdelete,区别如下:
1newdelete是操作符,可以重载,只能在C++中使用。
2mallocfree是函数,可以覆盖,CC++中都可以使用。
3new 可以调用对象的构造函数,对应的delete调用相应的析构函数。
4malloc仅仅分配内存,free仅仅回收内存,并不执行构造和析构函数
5newdelete返回的是某种数据类型指针,mallocfree返回的是void指针

2. 堆和栈的区别

 一个由C/C++编译的程序占用的内存分为以下几个部分
1
、栈区(stack由编译器自动分配释放,存放函数的参数值,局部变量的值等。其
操作方式类似于数据结构中的栈。
2
、堆区(heap一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3
、全局区(静态区)(static,全局变量和静态变量的存储是放在一块的,初始化的
全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另
一块区域。 - 程序结束后由系统释放。
4
、文字常量区常量字符串就是放在这里的。程序结束后由系统释放
5
、程序代码区存放函数体的二进制代码。

 

二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0;
全局初始化区
char *p1;
全局未初始化区
main()
{
int b;

char s[] = "abc";

char *p2;

char *p3 = "123456"; 123456\0
在常量区,p3在栈上。
static int c =0
全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得1020字节的区域就在堆区。
strcpy(p1, "123456"); 123456\0
放在常量区,编译器可能会将它与p3所指向的"123456"
优化成一个地方。
}
二、堆和栈的理论知识
2.1
申请方式
stack:
由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空

heap:
需要程序员自己申请,并指明大小,在cmalloc函数
p1 = (char *)malloc(10);
C++中用new运算符
p2 = new char[10];
但是注意p1p2本身是在栈中的。

2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢
出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表
中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的
首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。
另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部
分重新放入空闲链表中。

2.3
申请大小的限制
栈:在Windows,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有
的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将
提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储
的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小
受限于计算机系统中有效的虚拟内存
。由此可见,堆获得的空间比较灵活,也比较大。

2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈,是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。

2.5堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可
执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。


2.6
存取效率的比较
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa
是在运行时刻赋值的;
bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到
edx
中,再根据edx读取字符,显然慢了。

 

总之:堆和栈的区别可以用如下的比喻来看出
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就
走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自
由度小。使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。 (经典!)

3 套接口(IO模型)

1. 阻塞IO:发出IO请求一直等到回应后才处理其他操作

2 非阻塞IO:发出IO请求,而不得不等待时,不是把进程投入睡眠态,而是返回一个错误,需要轮询(polling),耗费大量CPU时间。

3  IO复用:等待多个描述字就绪,或者IO就绪。(可使用多线程+阻塞IO代替IO复用模型,其每个线程对应一个描述字)

4 信号驱动IO:安装新号处理函数,IO就绪时,使用SIGIO 通知,在等待IO就绪之间,进程不被阻塞,而是继续执行。

5 异步IO:IO就绪与IO操作完成之后发出信号告诉进程。前面几种

同步IO:导致请求进程阻塞,只知道IO完成

异步IO:不导致请求进程阻塞。

4 进程和线程有什么区别?

1 概念: a.进程是资源分配的基本单位,线程是cpu调度,或者说是程序执行的最小单位。在MacWindows NT等采用微内核结构的操作系统中,进程的功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。

2 空间使用: b.进程有独立的地址空间,比如在linux下面启动一个新的进程,系统必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种非常昂贵的多任务工作方式。而运行一个进程中的线程,它们之间共享大部分数据,使用相同的地址空间,因此启动一个线程,切换一个线程远比进程操作要快,花费也要小得多。当然,线程是拥有自己的局部变量和堆栈(注意不是堆)的,比如在windows中用_beginthreadex创建一个新进程就会在调用CreateThread的同时申请一个专属于线程的数据块(_tiddata)

3 效率  c.线程之间的通信比较方便。统一进程下的线程共享数据(比如全局变量,静态变量),通过这些数据来通信不仅快捷而且方便,当然如何处理好这些访问的同步与互斥正是编写多线程程序的难点。而进程之间的通信只能通过进程通信的方式进行。

4 稳定性  d.b,可以轻易地得到结论:多进程比多线程程序要健壮。一个线程死掉整个进程就死掉了,但是在保护模式下,一个进程死掉对另一个进程没有直接影响。

5 使用:  e.线程的执行与进程是有区别的。每个独立的线程有有自己的一个程序入口,顺序执行序列和程序的出口,但是线程不能独立执行,必须依附与程序之中,由应用程序提供多个线程的并发控制。

5几种进程间的通信方式

# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道 (named pipe): 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量( semophore ): 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存( sharedmemory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
# 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

6 C++中 struct 和class的区别

1)默认的继承访问权限。struct是public的,class是private的。

2)struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。

3)“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。

 4) 还是上面所说的,C++中的struct是对C中的struct的扩充,既然是扩充,那么它就要兼容过去C中struct应有的所有特性。class和struct如果定义了构造函数的话,都不能用大括号进行初始化;如果没有定义构造函数,struct可以用大括号初始化。如果没有定义构造函数,且所有成员变量全是public的话,class可以用大括号初始化。

例如你可以这样写:

    structA    //定义一个struct

    {

     charc1;

     int  n2;

     doubledb3;

    };

    Aa={'p',7,3.1415926};  //定义时直接赋值

    也就是说struct可以在定义的时候用{}赋初值。

    向上面的struct中加入一个构造函数(或虚函数),struct也不能用{}赋初值了。的确,以{}的方式来赋初值,只是用一个初始化列表来对数据进行按顺序的初始化,如上面如果写成A a={'p',7};c1,n2被初始化,而db3没有。这样简单的copy操作,只能发生在简单的数据结构上,而不应该放在对象上。加入一个构造函数或是一个虚函数会使struct更体现出一种对象的特性,而使此{}操作不再有效。事实上,是因为加入这样的函数,使得类的内部结构发生了变化。而加入一个普通的成员函数呢?你会发现{}依旧可用。其实你可以将普通的函数理解成对数据结构的一种算法,这并不打破它数据结构的特性。至于虚函数和普通成员函数有什么区别,我会具体写篇文章讨论。

    那么,看到这里,我们发现即使是struct想用{}来赋初值,它也必须满足很多的约束条件,这些条件实际上就是让struct更体现出一种数据机构而不是类的特性。那为什么我们在上面仅仅将struct改成class{}就不能用了呢?其实问题恰巧是我们之前所讲的——访问控制!你看看,我们忘记了什么?对,将struct改成class的时候,访问控制由public变为private了,那当然就不能用{}来赋初值了。加上一个public,你会发现,class也是能用{}的,和struct毫无区别!!!

  从上面的区别,我们可以看出,struct更适合看成是一个数据结构的实现体,class更适合看成是一个对象的实现体。

7 static 作用

1. 限制作用域在一个模块内。(对其他模块隐藏)

在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。(一个模块可以使一个文件)

2. static的第二个作用是保持变量内容的持久

存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。

3.static的第三个作用是默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加’\0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是’\0’。

 

 

8 C++虚函数与纯虚函数用法与区别

1. 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。

2. 虚函数可以被直接使用,也可以被子类(sub class)覆盖以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。

3. 虚函数和纯虚函数都可以在子类(sub class)中被覆盖,以多态的形式被调用。

4. 虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类覆盖,目的是提供一个统一的接口。

5. 虚函数的定义形式:virtual {method body}

  纯虚函数的定义形式:virtual { } = 0;

在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。

6. 虚函数必须实现,如果不实现,编译器将报错,错误提示为:

error LNK****: unresolved external symbol "public: virtual void __thiscallClassName::virtualFunctionName(void)"

7. 对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。

8. 实现了纯虚函数的子类,该纯虚函数在子类中就变成了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。

9. 虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数

10. 多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性
   a.编译时多态性:通过重载函数实现
   b 运行时多态性:通过虚函数实现。

11. 如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承覆盖以后,根据要求调用其子类的方法。

9 内存泄露(Memory leak)与溢出(Overflow)

1 什么是内存泄漏(memory leak)?
   指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。 
2. 对于C和C++这种没有Garbage Collection 的语言来讲,我们主要关注两种类型的内存泄漏: 堆内存泄漏(Heap leak)对内存指的是程序运行中根据需要分配通malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak. 系统资源泄露(Resource Leak).主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。  
3. 如何解决内存泄露?
内存泄露的问题其困难在于1.编译器不能发现这些问题。2.运行时才能捕获到这些错误,这些错误没有明显的症状,时隐时现。3.对于手机等终端开发用户来说,尤为困难。下面从三个方面来解决内存泄露:
第一,良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和复杂度。
第二,重载  new 和 delete。这也是大家编码过程中常常使用的方法
第三,Boost 中的smart pointer
 
内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是会产生内存溢出的问题。
常见的溢出主要有:
(1)内存分配未成功,却使用了它。
常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p 是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc 或new 来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理。
(2)内存分配虽然成功,但是尚未初始化就引用它。
(3)内存分配成功并且已经初始化,但操作越过了内存的边界。
     例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for 循环语句中,循环次数很容易搞错,导致数组操作越界。
(4)使用free 或delete 释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

 

 

10 数据库

1)子查询中不能使用order by语句

2)聚簇索引与非聚簇索引的区别

3) 一些带exists 或者notexists 的谓词子查询不能被其他形式的子查询等价替换,但是带有In,比较运算符,any, 或all的子查询都可用exists的子查询等价替换

Student(Sno,Sname,Ssex,Sage,Sdept);// 粗体为主键

Course(Cno,Cname,Cpno,Ccredit);//Cpno 为先行课,是Cno的外键

SC(Sno,Cno,Grade);//Sno,Cno 即是主键也是外键

 

查询类型:

1)单表查询,

2)连接查询 (一般连接,等值连接,外连接(左外,右外),

 

列出所有学生的选课信息

select student.*, Cno,Grade

from student left join SC

on (student.Sno=SC.Sno)

 

3)嵌套查询(不相关子查询,相关子查询)

 

找出每个学生超过他选修课程的平均成绩的课程号

Select Sno,Cno

from SC x

where Grade>=(selectavg(Grade) from SC y

              where y.Sno=x.Sno);

查询没有选修1号课程的学生姓名

Select Sname

From Student

Where not exists

(select *

from SC

     WhereSno=Student.Sno and Cno=’1’);

查询选修了全部课程的学生姓名

==没有一门课程是这个学生不选的

Select Sname

From Student

Where not exists

    (select * from Course

        Where not exists

         (select * from SC

            Where Sno=Student.Sno andCno=Couser.Cno);

查询至少选修了学生22选修的全部课程的学生号码

==不存在这样的课程y,学生22选了y,而学生x没选

Select distinct Sno

From SC x

Where not exists

    (select * from SC y

     Wherey.Sno=’22’and not exists

        (select * from SC z

        Where z.Sno=x.Sno and z.Cno=Y.Cno);

 

4)集合操作

UNION,Intersect,except(差)

 

范式:

1NF: 每一个分量(列)都是不能再分割的数据项

2NF:  在1NF基础上,非主属性对码不存在部分依赖

3NF: 在2NF基础上,非主属性对码不存在传递依赖

BCNF:在3NF基础上,每个决定因素都包含码

4NF:属性之间不允许有非平凡的且非函数依赖的多值依赖

 

事务:恢复和并发控制的基本单位

ACID原则

11 死锁产生的原因及四个必要条件

产生死锁的原因主要是:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则
就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。
产生死锁的四个必要条件
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之
一不满足,就不会发生死锁。
死锁的解除与预防:
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和
解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确
定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态
的情况下占用资源。因此,对资源的分配要给予合理的规划。

如何预防死锁?  

答:根据产生死锁的四个必要条件,只要使其中之一不能成立,死锁就不会出现。为此,可以采取下列三种预防措施:  

1、采用资源静态分配策略,破坏"请求保持"条件; 

2、允许进程剥夺使用其他进程占有的资源,从而破坏"不可剥夺"条件; 

3、采用资源有序分配法,破坏"环路"条件。 (资源编号,按编号的大小顺序分配) 

如何避免死锁?  

答:死锁的避免不严格地限制死锁的必要条件的存在,而是系统在系统运行过程中小心地避免死锁的最终发生。最著名的死锁避免算法是银行家算法。死锁避免算法需要很大的系统开销。 

如何检测死锁?  

答:解决死锁的另一条途径是死锁检测方法,这种方法对资源的分配不加限制,即允许死锁的发生。但系统定时地运行一个"死锁检测"程序,判断系统是否已发生死锁,若检测到死锁发生则设法加以解除。  

如何解除死锁?  

答:常常采用下面两种方法:   1、资源剥夺法;2、撤消进程法 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值