电话面试

本文深入探讨了TCP协议中的三次握手建立连接过程及其关键概念,包括未连接队列、SYN-ACK重传次数等。同时,文章详细解释了数据库事务处理中的ACID特性,即原子性、一致性、隔离性和持久性,并介绍了单链表循环判断方法、new与malloc的区别、指针与引用的不同之处以及快速排序的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.各层常见协议

2.三次握手过程

http://www.360doc.com/content/11/0815/17/111514_140580684.shtml

TCP三次握手/四次挥手详解http://molewan.blog.51cto.com/287340/114592

http://www.cnblogs.com/rootq/articles/1377355.html

所谓的“三握手”:对每次发送的数据量是怎样跟踪进行协商使数据段的发送和接收同步,根据所接收到的数据 量而确定的数据确认数及数据发送、接收完毕后何时撤消联系,并建立虚连接。为了提供可靠的传送,TCP 在发送新的数据之前,以特定的顺序将数据包的序号,并需要这些包传送给目标机之后的确认消息。TCP 总是用来发送大批量的数据。当应用程序在收到数据后要做出确认时也要用到TCP。 SYN:请求同步/同步序列号 ACK:应答同步/确认字段


OSI参考模型中的网络层,在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状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据,在上述过程中,还有一些重要的概念:
未连接队列:在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于Syn_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。
SYN-ACK 重传次数:服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传,如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。注意,每次重传等待的时间不一定相同。
半连接存活时间:是指半连接队列的条目存活的最长时间,也即服务从收到SYN包到确认这个报文无效的最长时间,该时间值是所有重传请求包的最长等待时间总和。有时我们也称半连接存活时间为Timeout时间、SYN_RECV存活时间。

3.acid

ACID,指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

一个支持事务(Transaction)的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程

极可能达不到交易方的要求。

4.事物

“数据库事物”简称“事物”,是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

5.如何判断单链表是一个循环链表

为每个链表节点建立一个访问次数标记,若次数大于1,则说明存在循环结构。

6.new malloc区别

相同点:都可用于申请动态内存和释放内存
不同点:
(1)操作对象有所不同。
malloc与free是C++/C 语言的标准库函数,new/delete 是C++的运算符。

对于非内部数据类的对象而言,光用maloc/free 无法满足动态对象的要求。

对象在创建的同时要自动执行构造函数, 对象消亡之前要自动执行析构函数。

由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把

执行构造函数和析构函数的任务强加malloc/free。


(2)用法上也有所不同。
函数malloc 的原型如下:
void * malloc(size_t size);
用malloc 申请一块长度为length 的整数类型的内存,程序如下:
int *p = (int *) malloc(sizeof(int) * length);
我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。
1、malloc 返回值的类型是void *,所以在调用malloc 时要显式地进行类型转换,将void * 转换成所需要的

指针类型。
2、 malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
函数free 的原型如下:
void free( void * memblock );
为什么free 函数不象malloc 函数那样复杂呢?这是因为指针p 的类型以及它所指的内存的容量事先都是知道

的,语句free(p)能正确地释放内存。如果p 是NULL 指针,那么free
对p 无论操作多少次都不会出问题。如果p 不是NULL 指针,那么free 对p连续操作两次就会导致程序运行错误。


new/delete 的使用要点:
运算符new 使用起来要比函数malloc 简单得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];
这是因为new 内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new 在创建动态

对象的同时完成了初始化工作。如果对象有多个构造函数,那么new 的语句也可以有多种形式。
如果用new 创建对象数组,那么只能使用对象的无参数构造函数。例如
Obj *objects = new Obj[100];       // 创建100 个动态对象
不能写成
Obj *objects = new Obj[100](1);        // 创建100 个动态对象的同时赋初值1
在用delete 释放对象数组时,留意不要丢了符号‘[]’。例如
delete []objects; // 正确的用法
delete objects; // 错误的用法
后者相当于delete objects[0],漏掉了另外99 个对象。
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        1、new自动计算需要分配的空间,而malloc需要手工计算字节数
        2、new是类型安全的,而malloc不是,比如:
                 int* p = new float[2]; // 编译时指出错误
                 int* p = malloc(2*sizeof(float)); // 编译时无法指出错误
          new operator 由两步构成,分别是 operator new 和 construct
        3、operator new对应于malloc,但operator new可以重载,可以自定义内存分配策略,甚至不做内

存分配,甚至分配到非内存设备上。而malloc无能为力
        4、new将调用constructor,而malloc不能;delete将调用destructor,而free不能。
        5、malloc/free要库文件支持,new/delete则不要。 
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1、本质区别
malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符。
对于用户自定义的对象而言,用maloc/free无法满足动态管理对象的要求。对象在创建的同时要自动执行

构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控

制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++需要一个能完成动态

内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。


[cpp] view plaincopy
class Obj  
{  
public:  
    Obj( )   
    { cout  <<  "Initialization"  <<  endl; }  
    ~ Obj( )  
    { cout  <<  "Destroy" <<  endl; }  
    void Initialize( )  
    { cout  <<  "Initialization"  <<  endl; }  
    void  Destroy( )  
    { cout  <<  "Destroy"  <<  endl; }  
}obj;  
  
void  UseMallocFree( )  
{  
    Obj   * a  =  (Obj  *) malloc( sizeof ( obj ) );      //  allocate memory   
    a -> Initialize();                                    //  initialization  
    // …   
    a -> Destroy();                                        // deconstruction   
    free(a);                                               // release memory  
}  
  
void  UseNewDelete( void )  
{  
    Obj   * a  =   new  Obj;                                             
    // …   
    delete a;   
}  
类Obj的函数Initialize实现了构造函数的功能,函数Destroy实现了析构函数的功能。函数UseMallocFree

中,由于malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成“构造”与“

析构”。所以我们不要用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的

“对象”没有构造与析构的过程 ,对它们而言malloc/free和new/delete是等价的。

2、联系
既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用

C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无

法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,

但是该程序的可读性很差。所以new/delete、malloc/free必须配对使用。


7.指针和引用区别

(1)引用总是指向一个对象,没有所谓的 null reference .所有当有可能指向一个对象也由可能不指向对象则必须使用 指针. 

由于C++ 要求 reference 总是指向一个对象所以 reference要求有初值. 

String & rs = string1; 

由于没有所谓的 null reference 所以所以在使用前不需要进行测试其是否有值.,而使用指针则需要测试其的有效性. 

(2)指针可以被重新赋值而reference则总是指向最初或地的对象. 

(3)必须使用reference的场合. Operator[] 操作符 由于该操作符很特别地必须返回 [能够被当做assignment 赋值对象] 的东西,所以需要给他返回一个 reference. 

(4)其实引用在函数的参数中使用很经常. 

void Get***(const int& a) //这样使用了引用有可以保证不修改被引用的值 


}
引用和指针
★ 相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
★ 区别:
1. 指针是一个实体,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
引用“从一而终” ^_^
4. 引用没有 const,指针有 const,const 的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,
但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。
7. 指针和引用的自增(++)运算意义不一样;
★ 联系
1. 引用在语言内部用指针实现(如何实现?)。
2. 对一般应用而言,把引用理解为指针,不会犯严重语义错误。引用是操作受限了的指针(仅容许取内容操作)。
引用是C++中的概念,初学者容易把引用和指针混淆一起。一下程序中,n 是m 的一 
个引用(reference),m 是被引用物(referent)。 
int m; 
int &n = m; 
n 相当于m 的别名(绰号),对n 的任何操作就是对m 的操作。例如有人名叫王小毛, 
他的绰号是“三毛”。说“三毛”怎么怎么的,其实就是对王小毛说三道四。所以n 既不 
是m 的拷贝,也不是指向m 的指针,其实n 就是m 它自己。 
引用的一些规则如下: 
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。 
(2)不能有NULL 引用,引用必须与合法的存储单元关联(指针则可以是NULL)。 
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。 
以下示例程序中,k 被初始化为i 的引用。语句k = j 并不能将k 修改成为j 的引 
用,只是把k 的值改变成为6。由于k 是i 的引用,所以i 的值也变成了6。 
int i = 5; 
int j = 6; 
int &k = i; 
k = j; // k 和i 的值都变成了6; 
上面的程序看起来象在玩文字游戏,没有体现出引用的价值。引用的主要功能是传 
递函数的参数和返回值。C++语言中,函数的参数和返回值的传递方式有三种:值传递、 
指针传递和引用传递。 
以下是“值传递”的示例程序。由于Func1 函数体内的x 是外部变量n 的一份拷贝, 
改变x 的值不会影响n, 所以n 的值仍然是0。 
void Func1(int x) 

x = x + 10; 

int n = 0; 
Func1(n); 
cout << “n = ” << n << endl;// n = 0 
以下是“指针传递”的示例程序。由于Func2 函数体内的x 是指向外部变量n 的指 
针,改变该指针的内容将导致n 的值改变,所以n 的值成为10。 
void Func2(int *x) 

(* x) = (* x) + 10; 

&#8943; 
int n = 0; 
Func2(&n); 
cout << “n = ” << n << endl; // n = 10 
以下是“引用传递”的示例程序。由于Func3 函数体内的x 是外部变量n 的引用,x 
和n 是同一个东西,改变x 等于改变n,所以n 的值成为10。 
void Func3(int &x) 

x = x + 10; 

&#8943; 
int n = 0; 
Func3(n); 
cout << “n = ” << n << endl; // n = 10 
对比上述三个示例程序,会发现“引用传递”的性质象“指针传递”,而书写方式象 
“值传递”。实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用” 
这东西? 
答案是“用适当的工具做恰如其分的工作”。 
指针能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。 
就象一把刀,它可以用来砍树、裁纸、修指甲、理发等等,谁敢这样用? 
如果的确只需要借用一下某个对象的“别名”,那么就用“引用”,而不要用“指针”, 
以免发生意外。比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如 
果把取公章的钥匙交给他,那么他就获得了不该有的权利。 
---------- 
摘自『高质量c++编程』
指针与引用,在More Effective C++ 的条款一有详细讲述,我给你转过来 
条款一:指针与引用的区别 
指针与引用看上去完全不同(指针用操作符’*’和’->’,引用使用操作符’.’),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢? 
首先,要认识到在任何情况下都不能用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。 
“但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?” 
char *pc = 0; // 设置指针为空值 
char& rc = *pc; // 让引用指向空值 
这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生),应该躲开写出这样代码的人除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。 
因为引用肯定会指向一个对象,在C里,引用应被初始化。 
string& rs; // 错误,引用必须被初始化 
string s("xyzzy"); 
string& rs = s; // 正确,rs指向s 
指针没有这样的限制。 
string *ps; // 未初始化的指针 
// 合法但危险 
不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。 
void printDouble(const double& rd) 

cout << rd; // 不需要测试rd,它 
} // 肯定指向一个double值 
相反,指针则应该总是被测试,防止其为空: 
void printDouble(const double *pd) 

if (pd) { // 检查是否为NULL 
cout << *pd; 


指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。 
string s1("Nancy"); 
string s2("Clancy"); 
string& rs = s1; // rs 引用 s1 
string *ps = &s1; // ps 指向 s1 
rs = s2; // rs 仍旧引用s1, 
// 但是 s1的值现在是 
// "Clancy" 
ps = &s2; // ps 现在指向 s2; 
// s1 没有改变 
总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。 
还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。 
vector<int> v(10); // 建立整形向量(vector),大小为10; 
// 向量是一个在标准C库中的一个模板(见条款35) 
v[5] = 10; // 这个被赋值的目标对象就是操作符[]返回的值 
如果操作符[]返回一个指针,那么后一个语句就得这样写: 
*v[5] = 10; 
但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这有一个有趣的例外,参见条款30) 
当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针 
假设你有 
void func(int* p, int&r); 
int a = 1; 
int b = 1; 
func(&a,b); 
指针本身的值(地址值)是以pass by value进行的,你能改变地址值,但这并不会改变指针所指向的变量的值, 
p = someotherpointer; //a is still 1 
但能用指针来改变指针所指向的变量的值, 
*p = 123131; // a now is 123131 
但引用本身是以pass by reference进行的,改变其值即改变引用所对应的变量的值 
r = 1231; // b now is 1231

8.快速排序及时间复杂度

快速排序-时空复杂度:
快速排序每次将待排序数组分为两个部分,在理想状况下,每一次都将待排序数组划分成等长两个部分,则需要logn次划分。
而在最坏情况下,即数组已经有序或大致有序的情况下,每次划分只能减少一个元素,快速排序将不幸退化为冒泡排序,所以快速排序时间复杂度下界为O(nlogn),最坏情况为O(n^2)。在实际应用中,快速排序的平均时间复杂度为O(nlogn)。
快速排序在对序列的操作过程中只需花费常数级的空间。空间复杂度S(1)。
但需要注意递归栈上需要花费最少logn最多n的空间。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值