c++面试题(40道)

本文深入探讨了C/C++编程中的关键概念,包括虚函数的限制、引用与指针的区别、树的遍历方式、排序算法、内存管理、数据结构对齐原则、字符串处理、内存分配、函数调用机制、宏定义与内联函数、链表操作、哈希原理、AVL树、堆排序、调试技巧、混合编程策略等,提供了丰富的代码示例和详细解析。

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

c/c++相关问题

1、什么函数不能声明为虚函数?
答:1:只有类的成员函数才能说明为虚函数;
  2:静态成员函数不能是虚函数;静态成员函数是可继承的。但是静态成员函数是编译时确定的,无法动态绑定,不支持多态,因此不能被重写,也就不能被声明为虚函数。
  3:内联函数不能为虚函数; 内联函数是在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。因此显然内联函数和多态的特性相违背。
  4:构造函数不能是虚函数;1.创建一个对象必须明确指出它的类型,否则无法创建,一个对象创建成功编译器获得它的实际类型,然后去调用对应的函数,而如果构造函数声明为虚函数,会形成一个死锁,虚函数是在运行才能确定确定其调用哪一个类型的函数,而具体哪一个类型是编译器通过对象的类型去确定的,但是此时对象还未创建也就没法知道其真实类型。2.虚函数对应一张虚函数表,这个虚函数表是存储在对象的内存空间的,如果构造函数是虚函数就需要通过虚函数表来调用,可是对象还没有实例化,也就是内存空间还没有,找不到虚函数表,所以构造函数是不能声明为虚函数的。
  5:析构函数可以是虚函数,而且通常声明为虚函数。

2、引用和指针的区别?
答:一、引用的定义
引用是给另外一个变量起别名,所以引用不会分配内存空间。
引用的声明方法:类型标识符 &引用名=目标变量名;(如int &ptr = num;)
  二、引用与指针的区别
1、指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。
2、引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变。(注:不能有引用的值不能为NULL)
3、有多级指针,但是没有多级引用,只能有一级引用。
4、指针和引用的自增运算结果不一样。(指针是指向下一个空间,引用时引用的变量值加1)
5、sizeof 引用得到的是所指向的变量(对象)的大小,而sizeof 指针得到的是指针本身的大小。
6、引用访问一个变量是直接访问,而指针访问一个变量是间接访问。

3、树的遍历方式有哪些,什么是平衡二叉树?
答:前序遍历、中序遍历、后序遍历;平衡二叉树是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

4、有哪些排序方法?
答:冒泡排序、快速排序、归并排序、插入排序

5、给定一个点和一个四边形的四个顶点,怎么判断点在四边形内?
答:假设四边形为ABCD,点为P,如果点在四边形内部,那么向量AB和向量PB、向量BC和向量PC、向量CD和向量PD、向量DA和向量PA的夹角小于90°;反之,必有一对向量夹角大于90°。

6、给定一篇英文文章,怎么统计里面出现次数最多的单词?
答:定义记录单词和单词个数的数据结构WordCount,定义一个类型WordCount动态向量,遍历整篇文章,保存单词和单词个数(考虑大小写,标点符号及特殊符号“-”),找出次数最大的那个单词,类似可参考统计一篇英文文章中出现次数最多的前五个单词,将静态数组改为动态。

7、跳台阶,每次跳1阶或2阶,跳到第10阶有多少种跳法?
答:斐波那契数列问题,1阶1种跳法f(1),2阶2种跳法f(2),3阶时,先跳一阶余下就是f(2),先跳两阶余下就是f(1),总共f(3)=f(1)+f(2);以此类推,f(n)=f(n-1)+f(n-2),求出10阶跳法应该是89种。

8、给定一个数组,从中随机抽出n个不重复的数,怎么做?如果数组有10亿个元素,其中90%已经抽取过了,怎么随机抽10个不重复的数出来?
答:

9、给定一些单词,给了特征库(比如child,children同特征,tell,told同特征),对比特征库将具有相同特征的单词统计到一块儿,并找出来。
答:

10、用C语言实现strcmp()函数,参数自己设定。

int strcmp(const char*str1,const char* str2)
{
	if(!str1||!str2)
		return(str1-str2);
	int i= 0;
	while(str1[i]==str2[i])
	{
		if(str[i]=='\0')
			return 0;
		++i;
	}
	return (str1[i]-str2[i]);
}

11、数组和指针申请内存的区别是什么?
答:指针变量保存的是那段内存的首地址,数组定义是必须确定大小,申请的是一段内存,数组名是那段内存的首地址。

12、结构体对齐,怎么对齐法?
答:为什么要结构对齐
如何对齐:
  1、数据类型自身的对齐值:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
  2、结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
  3、指定对齐值:#pragma pack (value)时的指定对齐值value。
  4、数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
  结构体总大小为有效齐值(每个成员变量除了第一个成员都有一个对齐数)的整数倍。举例如下:

1.下面的结构体大小分别是多大(假设32位机器)?
struct A {
char a; //1
char b; //1
char c; //1
};
//进行整体对齐,最大类型为1<对齐系数4,按1整体对齐,所以1+1+1=3 
struct B {
int a;  //4
char b; //1
short c;//2
};
//进行整体对齐,最大类型为4=对齐系数4,所以按4整体对齐4 1+2=3<4 对齐4 所以4+4=8 
struct C {
char b; //1
int a;  //4
short c;//2
};
//进行整体对齐,最大类型为4=对齐系数4,所以按4整体对齐 1<4(对齐4) 4=4 2<4(对齐4)  所以4+4+4=12 
#pragma pack(2)
struct D {
char b; //1
int a;  //4
short c;//2
};
//进行整体对齐,最大类型为4>对齐系数n(2),所以按2整体对齐 1<2(对齐2)4>2(2的倍数) 2=2 所以2+4+2=8 
答案及解析:3 8 12 8
 
 
2. 有一个如下的结构体:
struct A{
 long a1;
 short a2;
 int a3;
 int *a4;
};
请问在64位编译器下用sizeof(struct A)计算出的大小是多少?
24
28
16
18
 
答案及解析:24
64位编译器下:指针8字节(一定不能忘记),题目不说默认4字节对齐
long a1;    //8
 short a2;  //2 8+2=10(不是4的倍数)对齐到4的倍数12
 int a3;    //4 4+12=16(4的倍数)
 int *a4;   //8 8+16=24(4的倍数)
 
 
 
 
3.32位cpu上选择缺省对齐的情况下,有如下结构体定义:
struct A{
    unsigned a : 19;
    unsigned b : 11;
    unsigned c : 4;
    unsigned d : 29;
    char index;
};sizeofstruct A)的值为()
9
12
16
20
答案及解析16
题目不说,默认4字节对齐
19+11=30<32bit     4
4+29=33>32bit      4+4
1byte=8bit         1  对齐到 4
4+4+4+4=16

13、如何判断两个float数相等?
答:判断它们差值在一个很小范围内就默认两个数相等。

14、给你一个文件,将文件读到buff里怎么确定文件的大小?

15、main函数的两个参数分别是什么?
答:一般形式为int main (int argc,char *argv[]) 或者 int main (int argc,char **argv);第一个表示参数的个数;第二个参数中argv[0]为自身运行目录路径和程序名,argv[1]指向第一个参数、argv[2]指向第二个参数……

16、对于一个函数fun(fun1(),fun2()),fun1,fun2的调用顺序是什么?为什么?(问题不全面)

17、堆、栈、数据段、代码段的概念和区别
答:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
  代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
  堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
  栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

18、sizeof和strlen区别是什么,sizeof(string)是多少?
答:1、sizeof是一个操作符,strlen是库函数。
  2、sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为‘\0‘的字符串作参数。
  3、编译器在编译时就计算出了sizeof的结果。而strlen函数必须在运行时才能计算出来。并且sizeof计算的是数据类型占内存的大小,而strlen计算的是字符串实际的长度,strlen不算’\0’字符。
  4、数组做sizeof的参数不退化,传递给strlen就退化为指针了。
  sizeof(string)在不同的系统和编译器中值不一样,但是在同一系统和编译器中值一样,不随字符串的大小而改变。

19、写一个宏定义Max函数,宏定义和inline区别
答:#define Max(a,b) ((a)>=(b)?(a):(b))
1).为什么要引入内联函数(内联函数的作用)
用它替代宏定义,消除宏定义的缺点宏定义使用预处理器实现,做一些简单的字符替换因此不能进行参数有效性的检测。另外它的返回值不能被强制转换为可转换的合适类型,且C++中引入了类及类的访问控制,在涉及到类的保护成员和私有成员就不能用宏定义来操作。
2).inline相比宏定义有哪些优越处
(1)inline函数代码是被放到符号表中,使用时像宏一样展开,没有调用的开销效率很高;
(2)inline函数是真正的函数,所以要进行一系列的数据类型检查;
(3)inline函数作为类的成员函数,可以使用类的保护成员及私有成员;
3).inline函数使用的场合
(1)使用宏定义的地方都可以使用inline函数;
(2)作为类成员接口函数来读写类的私有成员或者保护成员;
4).为什么不能把所有的函数写成inline函数
内联函数以代码复杂为代价,它以省去函数调用的开销来提高执行效率。所以一方面如果内联函数体内代码执行时间相比函数调用开销较大没有太大的意义;另一方面每一处内联函数的调用都要复制代码,消耗更多的内存空间,因此以下情况不宜使用内联函数
(1)函数体内的代码比较长,将导致内存消耗代价;
(2)函数体内有循环,函数执行时间要比函数调用开销大; 另外类的构造与析构函数不要写成内联函数。
5).内联函数与宏定义区别
(1)内联函数在编译时展开,宏在预编译时展开;
(2)内联函数直接嵌入到目标代码中,宏是简单的做文本替换;
(3)内联函数有类型检测、语法判断等功能,而宏没有;
(4)inline函数是函数,宏不是;
(5)宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义;

20、struct {int a,int b} #define(type struct b ),求b的偏移量(问题不全面、有误)

21、memcpy函数功能及实现,代码,它和memmove的区别
答:memcpy和memmove都是C语言中的库函数,在头文件string.h中,作用是拷贝一定长度的内存的内容,原型分别如下:
  void *memcpy(void *dst, const void *src, size_t count);
  void *memmove(void *dst, const void *src, size_t count);
  他们的作用是一样的,唯一的区别是,当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果的正确。

void* my_memcpy(void* dst, const void* src, size_t n)
{
	if(dst==nullptr||src==nullptr)
		return ;
    char *tmp = (char*)dst;
    char *s_src = (char*)src;
    while(n--) 
    {
        *tmp++ = *s_src++;
    }
    return dst;
}

void* my_memmove(void* dst, const void* src, size_t n)
{
	if(dst==nullptr||src==nullptr)
		return ;
    char* s_dst= (char*)dst;
    char* s_src= (char*)src;
    //如果是指向同一字符串,但是dest在src后面,且dest<=src+count,那么从后往前赋值
    if(s_dst>s_src && (s_src+n>s_dst)) 
    {  
        s_dst = s_dst+n-1;
        s_src = s_src+n-1;
        while(n--) 
        {
            *s_dst-- = *s_src--;
        }
    }
    //这里来判别dest和src是否是指向同一字符串中不同位置,
    //如果是指向同一字符串,但是dest在src前面,则可以从前往后逐个赋值
    //如果是指向同一字符串,但是dest在src后面,且dest>=src+count,那么仍然从前往后赋值
    else {
        while(n--) {
            *s_dst++ = *s_src++;
        }
    }
    return dst;
}

22、给定一个不知道长度的单链表,求倒数第k个节点空间,空间o(1),时间o(n)
答:剑指offer原题

23、printf函数是如何知道输出变量个数?
答:有几个格式化规定符“%”和对应的参数表,就有几个变量。

24、给定一个字符串,求该字符串循环左移n位后,输出该字符串。
答:

#include<iostream>
#include<string>

//using namespace std;

void reverStr(char*str, int start, int end)
{
	if (str == nullptr)
		return;
	while (start < end)
	{
		char temp = str[end];
		str[end--] = str[start];
		str[start++] = temp;
	}
}
void leftShift(char*str, int n)
{
	if (str == nullptr)
		return;
	int length = strlen(str);
	if (length <= 0)
		return;
	n %= length;
	reverStr(str, 0, n - 1);
	reverStr(str, n, length - 1);
	reverStr(str, 0, length - 1);
	for (int i = 0; i < length; ++i)
	{
		printf("%c", str[i]);
	}
}

int main()
{
	char str[]="Hello word";
	leftShift(str, 13);
	system("pause");
	return 0;
}

25、new和malloc的区别
答:
0. 属性
  new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
1.参数
  使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
2. 返回类型
  new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
3. 分配失败
  new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
4. 自定义类型
  new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
5. 重载
  C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。
6. 内存区域
  new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

同样也拥有阅读课,

26、字符串和字符数组的区别(本题应该是回答表示字符串的两种方式的区别)
答:在c中字符串可以用字符指针变量和字符数组来表示char s1[] = "helloworld"; char *s2 = "helloworld";
  1)字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以‘\0’作为串的结束。字符数组是由于若干个数组元素组成的,它可用来存放整个字符串。
  2)字符串指针可以更改指针的值来指向字符串不同元素,但是不能修改元素值;字符串数组在定义时便在内存中为其分配了空间,不能随意的改变这个数组的地址,但是可以修改每个元素的值。
  3)它们sizeof的值不一样,sizeof(s1)=11;sizeof(s2)=4(64位操作系统为8),前者求的是整个数组大小,后者求的是这个指针变量大小,尽管s1和s2都表示字符串首地址;但是它们strlen值是一样的,都是10.

27、哈希原理是什么?

28、写代码实现单链表反转
答:剑指offer原题

29、Vector的内存实现机制是什么?
答:关于vector,简单地讲就是一个动态数组,里面有一个指针指向一片连续的内存空间,当空间不够装下数据时会自动申请另一片更大的空间,然后把原有数据拷贝过去,接着释放原来的那片空间;当释放或者说是删除里面的数据时,其存储空间并不会释放,仅仅只是清空了里面的数据。vector虽然是动态数组,但是本质上和数组没什么区别,频繁的销毁新建,效率很低,所以正确的做法是新建vector的时候初始化一个合适的大小。

30、现有一片森林,需要你用一根绳子围起来,最短需要多长的绳子。(??)

31、Linux中怎么查看程序是否执行正确。

34、手写string类的实现
答:剑指offer原题

#include<cstring>
#include<cstdio>

class CMyString
{
public:
    CMyString(char* pData = nullptr);
    CMyString(const CMyString& str);
    ~CMyString(void);

    CMyString& operator = (const CMyString& str);

    void Print();
      
private:
    char* m_pData;
};

CMyString::CMyString(char *pData)
{
    if(pData == nullptr)
    {
        m_pData = new char[1];
        m_pData[0] = '\0';
    }
    else
    {
        int length = strlen(pData);
        m_pData = new char[length + 1];
        strcpy(m_pData, pData);
    }
}

CMyString::CMyString(const CMyString &str)
{
    int length = strlen(str.m_pData);
    m_pData = new char[length + 1];
    strcpy(m_pData, str.m_pData);
}

CMyString::~CMyString()
{
    delete[] m_pData;
}

CMyString& CMyString::operator = (const CMyString& str)
{
    if(this == &str)
        return *this;

    delete []m_pData;
    m_pData = nullptr;

    m_pData = new char[strlen(str.m_pData) + 1];
    strcpy(m_pData, str.m_pData);

    return *this;
}

35、堆的构成原理,堆排序的时间复杂度
答:堆通常是一个可以被看做一棵树,它满足下列性质:
[性质一] 堆中任意节点的值总是不大于(不小于)其子节点的值;
[性质二] 堆总是一棵完全树。
  将任意节点不大于其子节点的堆叫做最小堆或小根堆,而将任意节点不小于其子节点的堆叫做最大堆或大根堆。
堆排序时间复杂度为o(nlogn)。

36、二叉树,avl树构成原理
答:二叉树是每个节点最多有两个子树的树结构,avl树是最先发明的自平衡二叉查找树,也被称为高度平衡树。相比于"二叉查找树",它的特点是:AVL树中任何节点的两个子树的高度最大差别为1
37、extern ,volatile
答:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。volatile 提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如 果没有 volatile 关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。所以遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

38、strcpy的缺陷,深浅拷贝,手写实现strcpy和strncpy和memcpy(21题)
答:strcpy原型char strcpy(char dest, const char *src);strncpy原型char *strncpy(char *dest,char *src,int size_t n);前者功能是将字符串src复制到dest中,当源字符串src的长度大于目标字符串dest长度就会造成内存溢出的问题;后者复制src中的内容(字符,数字、汉字…)到字符串dest中,复制多少由size_t n的值决定。如果src的前n个字符不含NULL字符,则结果不会以NULL字符结束。如果n<src的长度,只是将src的前n个字符复制到dest的前n个字符,不自动添加’\0’,也就是结果dest不包括’\0’,需要再手动添加一个’\0’。如果src的长度小于n个字节,则以‘\0’填充dest直到复制完n个字节。

char * strcpy ( char * dest, const char * src )
{
	if (dest==NULL || src ==NULL)
	{
		throw "pointer error";
	}
	char * pTemp = dest;
	while((*dest++ = *src++) != '\0');
	return pTemp;
}

char * strncpy ( char *dest, const char * src, size_t num )
{
	if (dest==NULL || src ==NULL)
	{
		throw "pointer error";
	}
	char * pTemp = dest;
	while((*dest++ = *src++) && num)
		num--;
	if(num)
	{
		while (num--)
			*dest++ = '\0';
	}
	return pTemp;
}

  也称位拷贝,编译器只是将对象中的值采用基本类型值复制的方式拷贝过来,如果对象中管理资源,就会导致多个对象共享同一份资源(资源泄露),当一个对象销毁时就会将该资源释放,而此时另一些对象不知道该资源已经被释放,以为该资源还有效,所以当继续对资源进行操作时,就会发生访问违规。
  用一句简单的话来说就是浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

39、大数据怎么排序
答:关于海量数据查找排序问题,如果数据没有什么重复,可以考虑使用位数组,下标代表数据值,对应内存储存0、1值,1代表有这个数据,0代表没有。

40、c与c++混合编程,如果c++需要调用c,怎么办?c调用c++呢
答:C与C++之间的相互调用(混合编程)

41、c++怎么调试?
答:设置断点,逐步或者逐过程调试,看变量值变化

42、return一个指向局部变量指针会怎么样?
答:可以正常返回指针值。

43、输出一个整数集合的全部子集复杂度是多少?
答:o()

44、public protected private的区别
答:1)public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用,在程序的任何其它地方访问。
  2)private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用,私有财产神圣不可侵犯嘛,即便是子女,朋友,都不可以使用。和public相反,加上这个修饰的属性和方法,只允许在自己本身这个类里访问,程序的任何其它地方都不能访问
  3)protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。受保护的,位于public和private中间,加上这个修饰的属性和方法,只能在子类(extends)和同包下的程序访问,别的的地方不能访问。
  4)default(默认):同一包中的类可以访问,声明时没有加修饰符,认为是friendly。

算法(机器学习)相关问题

1、L1/L2范数

2、如何解决过拟合

3、正负样本不平衡怎么办?

4、随机森林

5、解释一下模拟退火算法

6、解释一下GAN模型

7、

通信相关问题

1、三次握手四次挥手
参考链接:TCP的三次握手与四次挥手(详解+动图)程序员面试被问到“三次握手,四次挥手”怎么办?
答:通俗理解:客户端和服务端通信前要进行连接,“3次握手”的作用就是双方都能明确自己和对方的收、发能力是正常的。
  第一次握手:客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
  第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。 从客户端的视角来看,我接到了服务端发送过来的响应数据包,说明服务端接收到了我在第一次握手时发送的网络包,并且成功发送了响应数据包,这就说明,服务端的接收、发送能力正常。而另一方面,我收到了服务端的响应数据包,说明我第一次发送的网络包成功到达服务端,这样,我自己的发送和接收能力也是正常的。
  第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力,服务端的发送、接收能力是正常的。 第一、二次握手后,服务端并不知道客户端的接收能力以及自己的发送能力是否正常。而在第三次握手时,服务端收到了客户端对第二次握手作的回应。从服务端的角度,我在第二次握手时的响应数据发送出去了,客户端接收到了。所以,我的发送能力是正常的。而客户端的接收能力也是正常的。
  经历了上面的三次握手过程,客户端和服务端都确认了自己的接收、发送能力是正常的。之后就可以正常通信了。
四次挥手:TCP连接是双向传输的对等的模式,就是说双方都可以同时向对方发送或接收数据。当有一方要关闭连接时,会发送指令告知对方,我要关闭连接了。这时对方会回一个ACK,此时一个方向的连接关闭。但是另一个方向仍然可以继续传输数据,等到发送完了所有的数据后,会发送一个FIN段来关闭此方向上的连接。接收方发送ACK确认关闭连接。

2、I/O多路复用,epoll模型

3、大小端是什么?
答:不同的系统在存储数据时是分大端(bit-endian)小端(little-endian)存储的,比如,Inter x86、ARM核采用的是小端模式,Power PC、MIPS UNIX和HP-PA UNIX采用大端模式。大端模式和小端是实际的字节顺序和存储的地址顺序对应关系的两种模式,总结如下:
大端模式:低地址对应高字节;
小端模式:低地址对应低字节;
  不管是大端还是小端模式,我们在读取和存储数据的时候一定都是从内存的低地址依次向高地址读取或写入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值