「目录」
- 一、C/C 编程基础
- 二、计算机网络编程
- 三、操作系统
- 四、数据结构及算法
- 参考
本篇参考网上及自身的面试经验,总结一些高频考察的Linux C/C 知识点,方便后续查阅总结。
一、C/C 编程基础
- C 多态的实现 virtual关键字修饰基类的成员函数,派生类中重写此函数,实现多态
- C 四种强制类型转换[1]
- static_cast
static_cast<type>(expression)
该运算符把 expression 转换为 type 类型,主要用于基本数据类型之间的转换,如把 uint 转换为 int,把 int 转换为 double 等。 另外,static_cast 还可用于类层次结构中,基类和派生类之间指针或引用的转换,但也要注意: static_cast 进行上行转换是安全的,即把派生类的指针转换为基类的; static_cast 进行下行转换是不安全的,即把基类的指针转换为派生类的。 注:static_cast 没有运行时类型检查来保证转换的安全性,需要程序员来判断转换是否安全。
uint x = 1;
int y = static_cast<int>(x); // 转换正确
int x = 1;
double y = static_cast<double>(x); // 转换正确
// 上行转换,派生类→基类
Derive* d = new Derive();
Base* b = static_cast<Base*>(d);
// 下行转换,基类→派生类
Base* b = new Base();
Derive* d = static_cast<Derive*>(b);
- const_cast
const_cast<type>(expression)
主要是用来去除复合类型中const和volatile属性(没有真正去除)。expression 和 type 的类型需要保持一致。 - dynamic_cast
dynamic_cast<type>(expression)
主要用于类层次间的上行转换或下行转换。在进行上行转换时,dynamic_cast 和 static_cast 的效果是一样的,但在下行转换时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全。 - reinterpret_cast
reinterpret_cast<type>(expression)
该运算符可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针。
- static的作用[2]
- 限制变量或函数作用域 被static关键字修饰的全局函数或者变量具有文件作用域,即只在当前文件中可见。
- 保持变量内容的持久 被static修饰的变量会被存储在静态存储区,生命周期也为从定义直至程序结束。对于局部变量,即使在函数退出后该静态变量依然存在,然而却也无法访问。此外,static修饰的变量一生只会被初始化一次。
- 默认初始化为0 因为被static修饰的变量会被存储在静态存储区,所以才有了这个一条。因为静态存储区的变量会被默认初始化为0。
除此之外,在C 中,static也可以类成员变量和类成员函数。
- 类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数。
- 静态成员函数不含有this指针,所以可以作为回调函数。但同时为了可以访问类的成员变量可以将对象的this指针当做实参传入回调函数中。
- 静态成员函数在类定义体外定义时不能加static关键字修饰,因为成员函数本是类作用域,而在类外用static修饰会将其作用于扩大为文件作用域,所以是不合理的。
- 静态成员变量并不像一般的成员变量在构造函数中初始化,而是在类的实现文件中初始化,即必须在.cpp文件中初始化,否则在程序链接时会出错,重定义,且初始化时无需再使用static关键字修饰。
- static修饰的const成员变量可以再类中被定义时初始化。
- 利用static只会被初始化一次的特性,可以实现单例对象。
- extern 有什么作用 extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。extern声明既不是定义,也不分配存储空间。
- 说一说const关键字[3] const关键字告诉了编译器,它修饰的目标值不能被改变,如果代码中发现有类似改变该变量的操作,那么编译器就会捕捉这个错误。修饰函数时, 好的编程习惯告诫程序员,当不需要改变的变量,最好使用const修饰,例如非头文件定义的常量,最好用const而非define。
- sizeof 和 strlen 的区别
- sizeof会将空字符 计算在内,而strlen不会将空字符 计算在内;
- sizeof会计算到字符串最后一个空字符 并结束,而strlen如果遇到第一个空字符 的话就会停止并计算遇到的第一个空字符 前面的长度。
- #define和const的区别[4]
- #define 只是在预处理阶段简单的做字符替换,其可以实现宏函数和变量等;const是在编译、运行阶段起作用,修饰变量。
- const 定义的常数是变量也带类型,#define 定义的只是个常数 不带类型。
- #define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
- #define属于预处理,会占用代码空间;const 类型定义属于变量定义,占用数据段空间。
- 一个指针可以是 volatile 吗 可以。volatile关键词修饰的变量意思为值可能会改变,指针是可以改变的,与const关键词相反。
- 定义和声明的区别 声明是将一个名称引入程序,定义提供了一个实体在程序中的唯一描述。
- 什么是野指针 访问一个已销毁或者访问受限的内存区域的指针。野指针的使用,可能会引起程序崩溃,且无法用NULL检测野指针。
- 指针和引用的区别[5] 指针是一个变量,存储的是一个地址,指向内存的一个存储单元,指针变量占用内存; 引用是原变量的一个别名,跟原来的变量实质上是同一个东西,引用变量不占用内存。
- 简述指针常量与常量指针区别[6] 指针常量:本质是一个常量,而用指针修饰它。指针常量的值是指针,这个值因为是常量,所以不能被赋值。 常量指针:又叫常指针,可以理解为常量的指针,也即这个是指针,但指向的是个常量,这个常量是指针的值(地址),而不是地址指向的值。
- 引用作为函数参数以及返回值的好处[7] 在内存中不产生被返回值的副本。(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!)
- 谈谈C 内存管理[8]
- 「stack栈区」:专门用来实现函数调用-栈结构的内存块。相对空间下(可以设置大小,Linux 一般默认是8M,可通过 ulimit –s 查看),系统自动管理,从高地址往低地址,向下生长。
- 「内存映射区」:包括文件映射和匿名内存映射, 应用程序的所依赖的动态库,会在程序执行时候,加载到内存这个区域,一般包括数据(data)和代码(text);通过mmap系统调用,可以把特定的文件映射到内存中,然后在相应的内存区域中操作字节来访问文件内容,实现更高效的IO操作;匿名映射,在glibc中malloc分配大内存的时候会用到匿名映射。这里所谓的“大”表示是超过了MMAP_THRESHOLD 设置的字节数,它的缺省值是 128 kB,可以通过 mallopt() 去调整这个设置值。还可以用于进程间通信IPC(共享内存)。
- 「heap堆区」:主要用于用户动态内存分配,空间大,使用灵活,但需要用户自己管理,通过brk系统调用控制堆的生长,向高地址生长。
- 「BBS段和DATA段」:用于存放程序全局数据和静态数据,一般未初始化的放在BSS段(统一初始化为0,不占程序文件的空间),初始化的放在data段,只读数据放在rodata段(常量存储区)。
- 「text段」:主要存放程序二进制代码。
- 谈谈new、delete、malloc、free[9]
共同点:
- 都是从堆上申请空间,并且需要用户手动释放。
不同点:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可。
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型。
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
- delete和delete[]的区别 delete只会调用一次析构函数,而delete []会调用每一个成员的析构函数。
- 简述 strcpy、sprintf 与 memcpy 的区别 操作对象不同,strcpy 的两个操作对象均为字符串; sprintf 的操作源对象可以是多种数据类型,目的操作对象是字符串; memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
- C 的空类有哪些成员函数 一个默认构造函数、一个拷贝默认构造函数、一个默认拷贝赋值操作符和一个默认析构函数。这些函数只有在第一次被调用时,才会被编译器创建。所有这些函数都是inline和public的。
- 构造函数为什么不能是虚函数[10]
- 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象 的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。无法确定。。。
- 虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。
- 深拷贝和浅拷贝的区别
- 浅拷贝: 与拷贝对象共享同一片内存。只复制对象的基本类型,对象类型,仍属于原来的引用。
- 深拷贝: 申请新的内存,并将目标对象复制到新的内存。
- 知道STL吗,挑两个你最常用的容器说一说
容器分为两大类: 顺序容器和关联容器
- 顺序容器 顺序容器有以下三种:可变长动态数组 vector、双端队列 deque、双向链表 list。 之所以被称为顺序容器,是因为元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置(尾部、头部或中间某处)插入,元素就会位于什么位置。 「vector」: vector 实现的是一个动态数组,单向开口的连续性空间,支持内部元素随机访问,能够自动根据需要动态扩容。[11] 「deuque」: deque是双向开口的连续线性空间,支持内部元素的随机访问。擅长在序列尾部添加或删除元素(时间复杂度为O(1)),而不擅长在序列中间添加或删除元素。且支持头部添加和删除元素的成员。[12] 「list」: 双向链表容器,即该容器的底层是以双向链表的形式实现的。非连续空间、通过指针来连接每一个小空间、插入和删除都是O(1)操作,元素访问效率较低等等,不支持随机访问。
- 关联容器 关联容器有以下四种:set、multiset、map、multimap。关联容器内的元素是排序的。插入元素时,容器会按一定的排序规则将元素放到适当的位置上,因此插入元素时不能指定位置。 「set」: 含有 Key 类型对象的已排序集。用比较函数 比较 (Compare) 进行排序。搜索、移除和插入拥有对数复杂度。set 通常以红黑树实现。[13] 「multiset」: 是排序好的集合(元素已经进行了排序),并且允许有相同的元素。[14] 「map」: 是有序键值对容器,它的元素的键是唯一的。[15] 「multimap」:multimap也是存储两个元素之间的映射关系的容器,不相同的是,multimap的key值可以重复出现。[16]
- STL中的vector的实现,是怎么扩容的 vector 为空的时候没有预分配空间,每次添加一个元素时,会判断当前是否还有剩余可用空间,如果没有则进行试探性扩容,并且把内存拷贝到新申请的内存空间上,并且释放原先的内存。
- C 中vector和list的区别 「vector」是动态数组,内部存储是一片连续性的空间,支持通过下标随机访问。 「list」是双向链表,内部存储可能是不连续的空间,通过指针链接这些不连续的空间,不支持随机访问。
- 怎么确定一个程序是C编译的还是C 编译的
如果编译器在编译cpp文件那么
__cplusplus
就会被定义 如果是一个c文件在被编译那么__STDC__
就会被定义。 - 说一下什么是内存泄漏,如何避免 是指程序在申请内存后,无法释放已申请的内存空间,称之为内存泄露。无法释放的内存会一直被无效占用,且无法被再次使用,累计下来会导致进程占用内存越来越大,直至无内存资源可用,导致进程崩溃。
- C 中内存泄漏的几种情况[17]
- 在类的构造函数和析构函数中没有匹配的调用new和delete函数
- 没有正确地清除嵌套的对象指针
- 在释放对象数组时在delete中没有使用方括号
- 指向对象的指针数组不等同于对象数组
- 缺少拷贝构造函数
- 缺少重载赋值运算符
- 没有将基类的析构函数定义为虚函数
- 一个文件从源码到可执行文件所经历的过程 ① 预处理,产生.ii文件 ② 编译,产生汇编文件 (.s文件) ③ 汇编,产生目标文件 (.o或.obj文件) ④ 链接,产生可执行文件
- 聊聊C 11新特性[18]
- C 构造函数和析构函数的调用顺序[19] 「构造函数顺序」: 基类构造函数、对象成员构造函数、派生类本身的构造函数。 「析构函数顺序」: 派生类本身的析构函数、对象成员析构函数、基类析构函数(与构造顺序正好相反)。
- 用 C 设计一个不能被继承的类 将自身构造函数和析构函数声明为private。
- 什么是纯虚函数 基类中声明的虚函数,仅有声明无实现。要求派生的子类必须定义自身的实现方法,达到多态效果。
- 构造函数为什么一般不定义为虚函数?而析构函数一般写成虚函数的原因[20]
- 构造函数不能声明为虚函数 因为创建一个对象时需要确定对象的类型,而虚函数是在运行时确定其类型的。而在构造一个对象时,由于对象还未创建成功,编译器无法知道对象的实际类型,是类本身还是类的派生类等等 虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数即构造函数了
- 析构函数最好声明为虚函数 首先析构函数可以为虚函数,当析构一个指向派生类的基类指针时,最好将基类的析构函数声明为虚函数,否则可以存在内存泄露的问题。 如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除指向派生类的基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。
- 析构函数的作用 对象消亡时,自动被调用,用来释放对象占用的空间。
- 栈和堆的区别,什么时候必须使用堆
- 栈:为函数分配的一块内存,函数内部声明的所有局部变量都将占用栈内存。函数执行完毕后,占用的栈会被销毁回收,内部定义的变量均会销毁。效率很高,但是分配的内存容量有限。
- 堆:程序中未使用的内存,在程序运行时可用于动态分配内存。由程序员自己维护申请和回收,若使用完毕未回收会导致内存泄漏。
- 栈溢出(stack overflow)的原因以及解决方法[21]
- 栈溢出原因 ① 局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出。 ② 递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。 ③ 指针或数组越界。这种情况最常见,例如进行字符串拷贝,或处理用户输入等等。
- 解决方法 增大栈空间; 变量过大时,改用动态分配,使用堆(heap)而不是栈(stack)。
- 编码实现某一变量某位清 0 或置 1
// 将第index为置1. index: 0~7
unsigned char set_bit(unsigned char value, int index)
{
return value | (1 << index);
}
//将第index位清0, index: 0~7
unsigned char clear_bit(unsigned char value, int index)
{
return value & (~(1 << index));
}
- 头文件<>和""的区别
- <>: 表示引用标准库头文件,编译器会从系统默认库环境路径查找。
- "": 一般表示用户自己定义使用的头文件,编译器默认会优先从当前路径中寻找;若未找到,再从系统默认库环境路径中去查找。
注:linux下C和C 默认库环境路径:/usr/include
- 静态绑定和动态绑定的介绍
把一个方法与其所在的类/对象关联起来叫做方法的绑定。[22]
「静态类型」:对象在声明时采用的类型,在编译期既已确定。
「动态类型」:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的。
「静态绑定」:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期。
「动态绑定」:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期。
非虚函数一般都是静态绑定,而虚函数都是动态绑定(如此才可实现多态性)
- 引用是否能实现动态绑定,为什么引用可以实现 可以。引用在创建的时候必须初始化,在访问虚函数时,编译器会根据其所绑定的对象类型决定要调用哪个函数。注意只能调用虚函数。
- 什么情况下会调用拷贝构造函数(三种情况) 拷贝构造函数从来不显示调用,而是由编译器隐式地调用。 ① 用类的一个对象去初始化另一个对象时; ② 当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用; ③ 当函数的返回值是类的对象或引用时。
- extern "C"作用
extern "C"
的 主要 作用 就 是 为了能够正确实现C 代码调用其他C语言代码。加上extern "C"
后,编译器会按照C语言语法进行编译。 - typedef和define的区别
typedef和define都是替一个对象取一个别名,以此增强程序的可读性。[23] ①作用阶段不同 「typedef」: 编译阶段有效, 有类型检查的功能。 「define」: 预处理阶段有效, 编译前, 只进行简单而机械的字符串替换, 不进行任何检查。 ② 功能不同 「typedef」: 用来定义类型(内部或自定义类型)的别名, 起到使类型易于记忆的功能。 「define」: 不止可以为类型取别名, 还可以定义常量, 变量, 编译开关等。 ③ 作用域不同[24] 「typedef」: 与变量生命期类似。放在函数外,作用域从定义开始到文件尾;若放在函数内,定义域从定义处到该函数结尾。 「define」 没有作用域的限制,从定义开始到文件结尾。
代码语言:javascript复制//源自https://www.cnblogs.com/retry/p/14045305.html
//a.c
typedef …//此处开始到文件结尾
#define …//此处开始到文件结尾
int negate(int num)
{
…
typedef …//此处开始到该函数结束。注意,该函数内,此定义前,也不能用
#define …//此处开始到文件结尾
…
}
typedef …//此处开始到文件结尾
#define …//此处开始到文件结尾
void show()
{
typedef …//此处开始到该函数结束。
#define …//此处开始到文件结尾
}
④ 对指针的操作: 二者修饰指针类型时, 作用不同。
代码语言:javascript复制Typedef int * pint;
#define PINT int *
const pint p;//p不可更改,p指向的内容可以更改,相当于 int * const p;
const PINT p;//p可以更改,p指向的内容不能更改,相当于 const int *p;或 int const *p;
pint s1, s2; //s1和s2都是int型指针
PINT s3, s4; //相当于int * s3,s4;只有一个是指针。
- 友元函数和友元类[25]
- 友元函数 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
- 友元类
在类A中,用
friend
修饰另一个已经定义的类B,类B就属于类A的友元类,类B中的所有成员函数都是类A的友元函数,类B可以访问类A的所有成员,包括public
、protected
、private
属性的。
- 提高c 性能,你用过哪些方式去提升[26]
- 空间足够时,可以将经常需要读取的资源,缓存在内存中。
- 尽量减少大内存对象的构造与析构,考虑缓存暂时不用的对象,等待后续继续使用。
- 尽量使用C 11的右值语义,减少临时对象的构造。
- 简单的功能函数可以使用内联。少用继承,多用组合,尽量减少继承层级。
- 在循环遍历时,优化判断条件,减少循环次数。
- 优化线程或进程的同步方式,能用原子操作的就不用锁。能应用层同步的就不用内核对象同步。
- 优化堆内存的使用,如果有内存频繁的申请与释放,可以考虑内存池。
- 优化线程的使用,节省系统资源与切换造成的性能损耗,线程使用频繁的可以考虑线程池。
- 尽量使用事件通知,谨慎使用轮循或者sleep函数。
- 界面开发中,耗时的业务代码不要放在UI线程中执行,使用单独的线程去异步处理耗时业务,提高界面响应速度。
- 经常重构、优化代码结构。优化算法或者架构,从设计层面进行性能的优化。
二、计算机网络编程
- select、poll、epoll的区别[27]
select、poll、epoll是多路复用主要用到的三种技术,主要区别如下:
① 支持一个进程所能打开的最大连接数 「select」 单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是3232,同理64位机器上FD_SETSIZE为3264),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。 「poll」 poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的。 「epoll」 虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接。
② FD剧增后带来的IO效率问题 「select」 因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。 「poll」 同上。 「epoll」 因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。
③ 消息传递方式 「select」 内核需要将消息传递到用户空间,都需要内核拷贝动作。 「poll」 同上。 「epoll」 epoll通过内核和用户空间共享一块内存来实现的。
- TCP和UDP的区别[28]
- TCP是面向连接的,UDP是无连接的
- TCP是可靠的,UDP是不可靠的
- TCP是面向字节流的,UDP是面向数据报文的
- TCP只支持点对点通信,UDP支持一对一,一对多,多对多
- TCP报文首部20个字节,UDP首部8个字节
- TCP有拥塞控制机制,UDP没有
- TCP协议下双方发送接受缓冲区都有,UDP并无实际意义上的发送缓冲区,但是存在接受缓冲区
- TCP三次握手[29]
① 第一次握手 客户端给服务器发送一个SYN段(在 TCP 标头中 SYN 位字段为 1 的 TCP/IP 数据包), 该段中也包含客户端的初始序列号(Sequence number = J)。
② 第二次握手 服务器返回客户端 SYN ACK 段(在 TCP 标头中SYN和ACK位字段都为 1 的 TCP/IP 数据包), 该段中包含服务器的初始序列号(Sequence number = K);同时使 Acknowledgment number = J 1来表示确认已收到客户端的 SYN段(Sequence number = J)。
③ 第三次握手 客户端给服务器响应一个ACK段(在 TCP 标头中 ACK 位字段为 1 的 TCP/IP 数据包), 该段中使 Acknowledgment number = K 1来表示确认已收到服务器的 SYN段(Sequence number = K)。
- 三次握手的原因[30]
为了实现可靠数据传输,TCP 协议的通信双方,都必须维护一个序列号,以标识发送出去的数据包中,哪些是已经被对方收到的。三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤。
- TCP四次挥手[31]
① 第一次挥手:Client端发起挥手请求,向Server端发送标志位是FIN报文段,设置序列号seq,此时,Client端进入FIN_WAIT_1状态,这表示Client端没有数据要发送给Server端了。
② 第二次挥手:Server端收到了Client端发送的FIN报文段,向Client端返回一个标志位是ACK的报文段,ack设为seq加1,Client端进入FIN_WAIT_2状态,Server端告诉Client端,我确认并同意你的关闭请求。
③ 第三次挥手:Server端向Client端发送标志位是FIN的报文段,请求关闭连接,同时Client端进入LAST_ACK状态。
④ 第四次挥手 :Client端收到Server端发送的FIN报文段,向Server端发送标志位是ACK的报文段,然后Client端进入TIME_WAIT状态。Server端收到Client端的ACK报文段后,关闭连接。此时,Client端等待2MSL的时间后依然没有收到回复,则证明Server端已正常关闭,Client端也关闭连接。
- 四次挥手的原因[32]
其实是客户端和服务端的两次挥手,也就是客户端和服务端分别释放连接的过程。客户端在发送完最后一次确认之后,还要等待2MSL的时间。有两个原因: 一是为了让B能够按照正常步骤进入CLOSED状态,二是为了防止已经失效的请求连接报文出现在下次连接中。
① 由于客户端最后一个ACK可能会丢失,这样B就无法正常进入CLOSED状态。于是B会重传请求释放的报文,而此时A如果已经关闭了,那就收不到B的重传请求,就会导致B不能正常释放。而如果A还在等待时间内,就会收到B的重传,然后进行应答,这样B就可以进入CLOSED状态了。
② 在这2MSL等待时间里面,本次连接的所有的报文都已经从网络中消失,从而不会出现在下次连接中。
- 编写socket套接字的步骤
- 服务器端程序的编写步骤 ① 调用socket()函数创建一个用于通信的套接字。 ② 第二步:给已经创建的套接字绑定一个端口号,这一般通过设置网络套接口地址和调用bind()函数来实现。 ③ 调用listen()函数使套接字成为一个监听套接字。 ④ 调用accept()函数来接受客户端的连接,就可以和客户端通信了。 ⑤ 处理客户端的连接请求。 ⑥ 终止连接。
- 客户端程序编写步骤 ① 调用socket()函数创建一个用于通信的套接字。 ② 通过设置套接字地址结构,说明客户端与之通信的服务器的IP地址和端口号。 ③ 调用connect()函数来建立与服务器的连接。 ④ 调用读写函数发送或者接收数据。 ⑤ 终止连接。
三、操作系统
- 如何不用
sizeof()
判断系统是16位还是32位? 可通过内存地址地址长度判断,定义一个指针变量,通过打印其地址即可判断。 - 进程和线程间的通信方式 匿名管道(pipe)、高级管道(popen)、有名管道(fifo)、消息队列、信号量、信号(sinal)、共享内存、套接字(socket)。
- 线程安全和线程不安全 线程安全指支持多线程访问,多线程访问时不会导致共享数据污染。反之,为线程不安全。
- 死锁产生的原因和死锁的条件[33]
- 死锁的定义 两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
- 死锁的产生原因 通常是源于多个进程对资源的争夺,不仅对不可抢占资源进行争夺或引起死锁,而且对可消耗资源进行争夺也会引起死锁。总结如下: ① 系统资源不足; ② 进程运行推进的顺序不当; ③ 资源分配不当;
- 死锁产生的必要条件 ① 互斥条件: 进程在运行中对资源进行排他性使用,即一个资源仅能被一个进程使用,此时其他进程请求资源时,只能等待其释放。 ② 请求与保持条件: 某进程已经保持了一个资源,但又请求另一个资源,若该资源被其他进程占有,此时请求阻塞,且对已经占有的资源不释放; ③ 不可抢占条件: 进程获得的资源在未使用完时不可被抢占,只能在进程使用完时自己释放; ④ 循环等待条件 发生死锁时,必然存在这样一个循环,一个进程p1等待p2占有的资源进程p2等待p3占有的资源...进程pn等待p1占有的资源。
- 死锁的处理方法 ① 预防死锁:事先预防策略,容易实现,通过实现设置限制,破坏产生死锁的四个条件之一。(如对资源采用按序分配策略) ② 避免死锁:事先预防策略,在资源的动态分配过程中,用某些方法防止系统禁图不安全状态。常见的方法有银行家算法。 ③ 检测死锁:通过检测机构等及时检测出死锁,采取适当措施,把进程从死锁中解脱。 ④ 解除死锁:检测出死锁后,采取措施解决。比如剥夺资源,撤销进程。 这四种方法对死锁的防范逐渐减弱,但对应的是资源利用率的提高。
- 如何采用单线程处理高并发 采用非阻塞,异步编程的思想。 ① IO多路复用技术 ② 采用事件驱动模型,基于异步回调来处理事件
- 线程的状态 运行期、挂起、死亡、正常退出、和线程阻塞。
- 进程的状态 ① 运行(running)态:进程占有处理器正在运行。 ② 就绪(ready)态:进程具备运行条件,等待系统分配处理器以便运行。 ③ 等待(wait)态:又称为阻塞(blocked)态或睡眠(sleep)态,指进程不具备运行条件,正在等待某个事件的完成。
- 系统调用brk和mmap[34]
从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。
- brk是将数据段(.data)的最高地址指针_edata往高地址推;
- mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。
这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
- 进程和线程的区别 ① 地址空间 线程共享本进程的地址空间,而进程之间是独立的地址空间。 ② 资源 线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。 ③ 健壮性 多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。 ④ 执行过程 每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。 但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。 ⑤ 可并发性 两者均可并发执行。 ⑥ 切换时 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。 ⑦ 其他 线程是处理器调度的基本单位,但是进程不是。
- 工作常用到的Linux命令 find、grep、ps、top、ls(比较简单,不展开)
- gdb 参考网上gdb常用的调试手法。
- gcc/g C和C 的编译工具。
- 什么是虚拟内存 《操作系统》:虚拟存储技术的基本思想时利用大容量外存来扩充内存,产生一个比有限的实际内存空间大得多的、逻辑的虚拟空间,简称虚存,以便能够有效地支持多道程序系统的实现和大型程序运行的需要,从而增强系统的处理能力。
- 常见的内存管理机制[35]
简单的分为连续分配管理方式和非连续分配管理方式这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,比如块式管理。非连续分配管理方式允许一个程序内存分散,比如页式管理、段式管理和段页式管理。
- 块式管理 远古时代的计算机操作系统的内存管理方式,将内存分为几个固定大小的块,每个块只包含一个进程,如果程序运行需要内存,操作系统就给它分配一块,如果程序运行只需要很小的空间,则分配的这块内存很大一部分就浪费了,这些在每个块中未被利用的空间,我们称为碎片。
- 页式管理 把主存分为大小相等且固定的一页一页的形式,页比较小,相对于块式管理的划分力度更大,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
- 段式管理 页式管理虽然提高了内存利用率,但是其中的页没有任何实际意义,段式管理把主存分为一段一段 ,每一段的空间又比一页的空间小很多。但是段有实际意义,段式管理通过段表对应逻辑地址和物理地址。
- 段页式管理 段页式管理机制结合了段式管理和页式管理的优点,就是把主存分为若干段,每个段又分为若干页,也就是说段页式管理机制中段和段之间以及段的内部都是离散的。
- 大端和小端,用C 代码怎么确定
- 指针法
int JudgeSystem(void) {
int a = 1;
char *p = (char *)&a;
// 如果是小端则返回 1,如果是大端则返回 0
return *p;
}
- 联合体法
int JudgeSystem(void) {
union {
char c;
int i;
} un; // 匿名联合体 un
un.i = 1;
// 如果是小端则返回 1,如果是大端则返回 0
return un.c;
}
四、数据结构及算法
- 几种常见的排序算法[36] ① 冒泡排序 ② 选择排序 ③ 插入排序 ④ 希尔排序 ⑤ 快速排序 ⑥ 归并排序 ⑦ 堆排序
- 链表的特点和操作
- 特点 ① 采用动态存储分配,不会造成内存浪费和溢出。 ② 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素。
- 常见操作 ① 创建 ② 插入 ③ 修改 ④ 查找 ⑤ 删除
- 常见的查找算法 ① 顺序查找 ② 二分查找 ③ 插值查找 ④ 斐波那契查找 ⑤ 树表查找 ⑥ 分块查找 ⑦ 哈希查找
- 如何判断链表是否为环形[37]
通过快慢指针。慢指针每次走1步,快指针每次走2步。当快慢指针相遇,说明链表为循环链表。
代码语言:javascript复制bool IsRingList(SNode* pHead)
{
bool ret = false;
SNode *pSlow = pHead, *pFast = pHead;
while(pFast && pFast->next)
{
pSlow = pSlow->next;
pFast = pFast->next->next;
if (pSlow == pFast) {
return true;
}
}
return ret;
}
- 计算环形链表的长度
通过快慢指针。慢指针每次走1步,快指针每次走2步,两者速度差为1步。当两指针相遇时,快指针比慢指针多走1圈。
代码语言:javascript复制int GetRingListLength(SNode* pHead)
{
int length = 0;
SNode *pSlow = pHead, *pFast = pHead;
while(pFast && pFast->next)
{
pSlow = pSlow->next;
pFast = pFast->next->next;
length ;
if (pSlow == pFast) {
break;
}
}
if (pSlow != pFast)
{
length = 0;
printf("It is not ring list.n");
}
return length;
}
- 查找单链表倒数第n个节点
通过快慢指针,快指针先走n步,随后慢指针与快指针同步走,当快指针到达链表尾部时,慢指针即为倒数第n个节点。
参考
Reference
[1]
C 四种强制类型转换]: https://zhuanlan.zhihu.com/p/258975506
[2]
static的作用: https://www.cnblogs.com/lit10050528/p/3910271.html
[3]
C const关键字小结: https://www.runoob.com/w3cnote/cpp-const-keyword.html
[4]
#define和const的区别: https://blog.csdn.net/yi_ming_he/article/details/70405364
[5]
C 中指针与引用的区别: https://zhuanlan.zhihu.com/p/140966943
[6]
C 中指针常量和常量指针的区别: https://www.cnblogs.com/lizhenghn/p/3630405.html
[7]
将“引用”作为函数返回值类型的格式、好处和需要遵守的规则: https://blog.csdn.net/weibo1230123/article/details/82014538
[8]
C 内存管理全景指南|C 硬核指南: https://zhuanlan.zhihu.com/p/368901281
[9]
C ——malloc/free和new/delete的区别: https://blog.csdn.net/WEIYANGBIN/article/details/109699674
[10]
为什么构造函数不能声明为虚函数,析构函数可以: https://www.cnblogs.com/lpxblog/p/5890933.html
[11]
C 打怪 之 vector: https://mp.weixin.qq.com/s?__biz=MzUzNjExNjMyNA==&mid=2247484576&idx=1&sn=c55235b7632bb18c05bcebb02436d07e&chksm=fafa69abcd8de0bd514592592c65730590ab00ad8f48b0de3dc0b7ce809d4ee5a45e1509e1dc&token=1237879194&lang=zh_CN#rd
[12]
从零开始,打造自己的STL(五、deque): https://zhuanlan.zhihu.com/p/35087078
[13]
std::set API参考文档: https://www.apiref.com/cpp-zh/cpp/container/set.html
[14]
C multiset,STL multiset详解: http://c.biancheng.net/view/386.html
[15]
std::map API参考文档: https://www.apiref.com/cpp-zh/cpp/container/map.html
[16]
C STL multimap容器用法完全攻略(超详细): http://c.biancheng.net/view/7190.html
[17]
C 中内存泄漏的几种情况: https://www.cnblogs.com/liushui-sky/p/7727865.html
[18]
c 11新特性,所有知识点都在这了: https://zhuanlan.zhihu.com/p/139515439
[19]
C 构造函数和析构函数的调用顺序: https://blog.csdn.net/xyzyhs/article/details/82356948
[20]
构造函数为什么一般不定义为虚函数?而析构函数一般写成虚函数的原因: https://blog.csdn.net/libaineu2004/article/details/85684224
[21]
栈溢出几种情况及解决方案: https://blog.csdn.net/Beyond_2016/article/details/81286223
[22]
C 中的静态绑定和动态绑定: https://www.cnblogs.com/lizhenghn/p/3657717.html
[23]
define 和 typedef 区别: https://www.jianshu.com/p/53ac91a23979
[24]
#define与typedef作用域: https://www.cnblogs.com/retry/p/14045305.html
[25]
C 友元函数和友元类(C friend关键字): http://c.biancheng.net/view/2233.html
[26]
C 程序性能优化指南: https://segmentfault.com/a/1190000039136866
[27]
select、poll、epoll之间的区别(搜狗面试): https://www.cnblogs.com/aspirant/p/9166944.html
[28]
TCP和UDP的区别: https://blog.csdn.net/weixin_43796685/article/details/104558965
[29]
TCP三次握手详解-深入浅出(有图实例演示): https://blog.csdn.net/jun2016425/article/details/81506353
[30]
TCP 为什么三次握手而不是两次握手(正解版): https://blog.csdn.net/lengxiao1993/article/details/82771768
[31]
一文彻底搞懂 TCP三次握手、四次挥手过程及原理: https://www.cnblogs.com/cooffeeli/p/TCP_Establish_Connection_Close_Connection.html
[32]
TCP四次挥手及原因: https://www.cnblogs.com/GuoXinxin/p/11657933.html
[33]
死锁的定义、产生原因、必要条件和处理方法: https://blog.csdn.net/zymx14/article/details/78046830
[34]
操作系统--brk()和mmap()详解: https://blog.csdn.net/shuzishij/article/details/86574927
[35]
操作系统常见的几种内存管理机制: https://blog.csdn.net/qq_38197844/article/details/108691639
[36]
常见的7种排序算法: https://blog.csdn.net/liang_gu/article/details/80627548
[37]
链表常见操作和15道常见面试题: https://zhuanlan.zhihu.com/p/265159396
最后
用心感悟,认真记录,写好每一篇文章,分享每一框干货。
更多文章内容包括但不限于C/C 、Linux、开发常用神器等,可进入“开源519公众号”聊天界面输入“文章目录” 或者 菜单栏选择“文章目录”查看。公众号后台聊天框输入本文标题,在线查看源码。