c++面试

一、c++

  1. const和static区别:
    1) const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间。
    2) static静态成员变量不能在类的内部初始化。在类的内部只是声明,定义必须在类定义体的外部,通常在类的实现文件中初始化,如:double Account::Rate=2.25;static关键字只能用于类定义体内部的声明中,定义时不能标示为static;(原因:static变量只有一份,是类拥有的,如果在构造函数中初始化,就变成了每个对象有一份)在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。
class shape
{
public:
    static int m;
    const int n=0;
    int a,b;
    shape(int a_v, int b_v):a(a_v),b(b_v),n(10)
    {
        //n = nn;
        a = a_v;
        b = b_v;

    }
    int calc_area()
    {
        return a*b;
    }
};

int shape::m=10;

static作用:
https://www.cnblogs.com/songdanzju/p/7422380.html

  1. const和define
    编译器处理时间:define是在预编译时处理的,const则是在编译运行时处理的。
    编译器处理方式:对于define编译器每次处理时只是简单的做替换,不会做类型检测,可能会有安全隐患,并且每次替换都会重新分配内存;对于const编译器会编译检查,会报编译错误,const常量在常量内存区中存储,至始至终只会占用一份内存,不会像define那样每次替换都会重新分配内存。
    数据类型:define 没有明确的数据类型,而const必须要指定数据类型
    define优点:可以宏定义函数
    const优点:const常量可以调试,而define常量不能调试。

  2. 指针和引用
    1) 引用必须被初始化,但是不分配存储空间。指针不声明时初始化,在初始化的时候需要分配存储空间。
    2)引用初始化后不能被改变,指针可以改变所指的对象。
    3) 不存在指向空值的引用,但是存在指向空值的指针。
    4) 引用没有const,指针有const,const的指针不可变;
    相同点:
    都是地址;指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。

  3. 多态
    在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数
    1) 存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
    2) 在C++中,多态性的实现和联编(也称绑定)这一概念有关。一个源程序经过编译、链接,成为可执行文件的过程是把可执行代码联编(或称装配)在一起的过程。其中在运行之前就完成的联编成为静态联编(前期联编);而在程序运行之时才完成的联编叫动态联编(后期联编)。
    静态绑定支持的多态性称为编译时多态性(静态多态性)。在C++中,编译时多态性是通过函数重载和模板实现的。利用函数重载机制,在调用同名函数时,编译系统会根据实参的具体情况确定索要调用的是哪个函数。
    动态绑定所支持的多态性称为运行时多态(动态多态)。在C++中,运行时多态性是通过虚函数来实现的。
    总结:派生类的虚表生成:
    (1)先将基类中的虚表内容拷贝一份到派生类虚表中;
    (2)如果派生类重写了基类中的某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数;
    (3)派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类的虚表的最后。
    (详见https://blog.csdn.net/studyhardi/article/details/90815766)

虚函数表创建时机:
虚函数表创建时机是在编译期间。编译期间编译器就为每个类确定好了对应的虚函数表里的内容。
所以在程序运行时,编译器会把虚函数表的首地址赋值给虚函数表指针,所以,这个虚函数表指针就有值了。
虚函数表指针(vptr)创建时机
vptr跟着对象走,所以对象什么时候创建出来,vptr就什么时候创建出来,也就是运行的时候。
当程序在编译期间,编译器会为构造函数中增加为vptr赋值的代码(这是编译器的行为),当程序在运行时,遇到创建对象的代码,执行对象的构造函数,那么这个构造函数里有为这个对象的vptr赋值的语句。

  1. 继承
    1) public:用该关键字修饰的成员表示公有成员,该成员不仅可以在类内可以被访问,在类外也是可以被访问的,是类对外提供的可访问接口
    private:用该关键字修饰的成员表示私有成员,该成员仅在类内可以被访问,在类体外是隐藏状态;
    protected:用该关键字修饰的成员表示保护成员,保护成员在类体外同样是隐藏状态,但是对于该类的派生类来说,相当于公有成员,在派生类中可以被访问。
    2) 权限:
    在这里插入图片描述
    总结:
    1.基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
    2.基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。
    3.根据上面表格所示,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符与继承方式中范围最小的那个),public > protected > private。
    4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public。

  2. 常量指针和指针常量
    1) 指针常量:顾名思义它就是一个常量,但是是指针修饰的。

//定义:int * const p;
int a,b;
int * const p=&a //指针常量
//那么分为一下两种操作
*p=9;//操作成功
p=&b;//操作错误

2) 常量指针:如果在定义指针变量的时候,数据类型前用const修饰,被定义的指针变量就是指向常量的指针变量,指向常量的指针变量称为常量指针

//定义:const int *p = &a; 
int a,b;
 const int *p=&a //常量指针
//那么分为一下两种操作
*p=9;//操作错误
p=&b;//操作成功
  1. vector,list,map,set
    1) https://blog.csdn.net/alex_xhl/article/details/37692297
    2) hash_map和map的区别:
    构造函数。hash_map需要hash函数,等于函数;map只需要比较函数(小于函数).
    存储结构。hash_map采用hash表存储,map一般采用红黑树(RB Tree)实现。因此其memory数据结构是不一样的。
    总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n)小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且hash_map的构造速度较慢。
    现在知道如何选择了吗?权衡三个因素:查找速度, 数据量, 内存使用
    hash_map原理:使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数,也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标,hash值)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素“分类”,然后将这个元素存储在相应“类”所对应的地方,称为桶。
    但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。 总的来说,“直接定址”与“解决冲突”是哈希表的两大特点。
    hash_map,首先分配一大片内存,形成许多桶。是利用hash函数,对key进行映射到不同区域(桶)进行保存。(https://www.cnblogs.com/inception6-lxc/p/9263964.html)

  2. 垃圾回收
    https://blog.csdn.net/u012611878/article/details/78947267

  3. 内存模型
    堆:由new分配的内存块,其释放编译器不去管,由我们程序自己控制(一个new对应一个delete)。如果程序员没有释放掉,在程序结束时OS会自动回收。涉及的问题:“缓冲区溢出”、“内存泄露”
    栈:是那些编译器在需要时分配,在不需要时自动清除的存储区。存放局部变量、函数参数。
    存放在栈中的数据只在当前函数及下一层函数中有效,一旦函数返回了,这些数据也就自动释放了。
    全局/静态存储区:全局和静态变量被分配到同一块内存中。
    常量存储区 :存放常量,不允许修改
    代码区 (.text段) :存放代码(如函数),不允许修改(类似常量存储区),但可以执行(不同于常量存储区)

  4. 内存泄漏
    https://www.cnblogs.com/zzdbullet/p/10478744.html

  5. map、unordered_map、multimap
    1)需要引入的头文件不同
    map: #include
    unordered_map: #include <unordered_map>
    2)<1>map
    map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作,其时间复杂度都是O(logn),最坏和平均都是。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。
    <2>unordered_map
    unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。但是并不是unordered_map查询时间一定比map短,因为实际情况中还要考虑到数据量,而且unordered_map的hash函数的构造速度也没那么快,所以不能一概而论,应该具体情况具体分析。
    hash_map和unordered_map的底层实现是一样的,但是在C++11中将unordered_map引入了标准库,而hash_map没有,所以建议还是使用unordered_map比较好。 unordered_set就是在哈希表插入value,而这个value就是它自己的key。
    3)优缺点
    <1>map
    优点
    (1)有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作;
    (2)红黑树,内部实现一个红黑书使得map的很多操作在logn的时间复杂度下就可以实现,因此效率非常的高
    缺点
    空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质,使得每一个节点都占用大量的空间。
    <2>unordered_map
    优点
    因为内部实现了哈希表,因此其查找速度非常的快。
    缺点
    哈希表的建立比较耗费时间。

  6. inline
    C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
    对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
    如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。
    在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。

  7. Hash算法冲突解决方法分析
    https://blog.csdn.net/willfcareer/article/details/6687117

  8. 详解C++11智能指针
    https://www.cnblogs.com/WindSun/p/11444429.html

  9. C++虚继承和虚基类详解

http://c.biancheng.net/view/2280.html

  1. 重载,重定义,重写的区别
    https://blog.csdn.net/han8040laixin/article/details/81703244

  2. new malloc区别
    https://www.cnblogs.com/ywliao/articles/8116622.html

  3. 虚函数表
    https://blog.csdn.net/qq_36359022/article/details/81870219

  4. 为什么 C++ 有指针了还要引用
    https://blog.csdn.net/a3192048/article/details/84621775

  5. 函数指针
    https://blog.csdn.net/zj1131190425/article/details/92065897

  6. QT信号和槽实现机制
    https://blog.csdn.net/xialianggang1314/article/details/83964392

  7. 四种强制类型转换
    https://www.cnblogs.com/socks/p/11446839.html

  8. c++类的默认成员函数
    https://blog.csdn.net/qq_42957923/article/details/90177817?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

  9. NULL
    https://www.runoob.com/cplusplus/cpp-null-pointers.html

  10. sizeof、strlen
    https://blog.csdn.net/songjiasheng1314/article/details/88644827
    说明:当char数组作为函数参数传入时,会退化为指针,也就是说用sizeof

  11. 上行下行转换
    https://www.cnblogs.com/renzhuang/articles/10466420.html

  12. c++11新特性
    https://blog.csdn.net/jiange_zh/article/details/79356417

  13. C++中使用空对象指针调用成员函数
    https://blog.csdn.net/oyhy_/article/details/107630940

  14. 结构体和UNION的内存计算
    https://juejin.cn/post/6844904022877405191

  15. STL Vector push_back()复杂度,具体怎么扩容的
    https://blog.csdn.net/yangshiziping/article/details/52550291

  16. const类成员变量,const成员函数
    https://www.cnblogs.com/cthon/p/9178701.html

  17. C++简单实现HashMap
    https://blog.csdn.net/xin_hen/article/details/108166528

  18. 函数定义与声明,参数默认值
    https://www.cnblogs.com/chenke1731/p/9651275.html

  19. 构造函数
    https://www.cnblogs.com/wangguchangqing/p/6141743.html
    拷贝构造函数:创建新实例
    复制构造函数:对已有实例进行赋值

class A {
public:
	A()
	{
		cout << "construct" << endl;
	}
	A(const A& a)
	{
		cout << "拷贝构造" << endl;
	}
	A& operator=(const A& a)
	{
		cout << "赋值构造" << endl;
		return *this;
	}
	//A(A &&a) = delete;
	void f() { cout << "A" << endl; }
};
A getA()
{
	return A();
}
void func(A a)
{
	cout << &a << endl;
	a.f();
}
int main()
{
	A a;
	func(a)
}
以上代码调用了几次构造函数?
答:三次,首先getA中调用一次,由于是右值,因此在赋值到实参的时候有一次转移构造,实参到形参是值传递,而形参是实参的拷贝副本,因此又一次,共三次。
  1. 源码到exe
    https://blog.csdn.net/ALexander_Monster/article/details/111811171

  2. template作用域:模板的形参从其声明之处直至模板定义结束之处可见

template<typename T>
T func(T a)
{
	return a;
}//到这里为止,下一行就超出作用域了
//class 和 struct 中的template 
template<class K, class V>
struct DLinkedNode {
public:
    K key;
    V value;
    DLinkedNode* prev;
    DLinkedNode* next;
    DLinkedNode() : key(0), value(0), prev(nullptr), next(nullptr) {}
    DLinkedNode(int _key, int _value) : key(_key), value(_value), prev(nullptr), next(nullptr) {}
};

  1. 迭代器含义:(end()是指最后一个元素的迭代器的后一个)
    在这里插入图片描述
  2. C++必须使用【初始化列表】初始化数据成员的三种情况
    https://blog.csdn.net/sinat_20265495/article/details/53670644?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link

二、计网

  1. 三次握手和四次挥手
    https://blog.csdn.net/qq_38950316/article/details/81087809

三次握手的原因
第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。!!!!!,为了防止第一次请求在网络中滞留,导致后续服务端打开无效连接.

TCP三次握手和四次挥手异常情况处理 https://segmentfault.com/a/1190000021740112

当服务端处于 SYN-RCVD 状态下时,接收到客户端真实发送来的数据包时,会认为连接已建立,并进入 ESTABLISHED 状态

三次握手,不是两次or四次
https://blog.csdn.net/lengxiao1993/article/details/82771768

  1. 输入url,显示主页的过程:
    https://blog.csdn.net/qq_42451835/article/details/106052363

  2. 两台主机之间如何进行通信
    https://blog.csdn.net/weixin_33858336/article/details/92736031

  3. 粘包问题及解决
    https://www.cnblogs.com/zhouxuchong/p/11576275.html
    https://zhuanlan.zhihu.com/p/356225028
    UDP不存在粘包问题,是由于UDP发送的时候,没有经过Negal算法优化,不会将多个小包合并一次发送出去。另外,在UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包,这样接收端应用程序一次recv只能从socket接收缓冲区中读出一个数据包。也就是说,发送端send了几次,接收端必须recv几次(无论recv时指定了多大的缓冲区)。

  4. TCP流量控制和拥塞控制
    https://www.cnblogs.com/liuwanqiu/p/10812433.html
    cubic算法
    https://blog.csdn.net/dog250/article/details/53013410

  5. ARP
    https://blog.csdn.net/ever_peng/article/details/80008638

  6. Time_wait问题小结
    https://mp.weixin.qq.com/s?__biz=MzAxOTg2NDUyOA==&mid=2657554876&idx=1&sn=05990e85c366e45415d77593b0d3bda1&mpshare=1&scene=1&srcid=0928LA06I6JCzy2vKKkaPt9f#rd

  7. 短链接转换长连接_短连接的设计和实现
    https://blog.csdn.net/weixin_39650139/article/details/111331053

  8. cookie 和 session
    https://blog.csdn.net/github_36432418/article/details/70248447
    https://www.cnblogs.com/ityouknow/p/10856177.html

  9. ssl
    https://www.cnblogs.com/fengfengyang/p/9852481.html
    https://www.cnblogs.com/SmileToTheSun/p/6727829.html

  10. CLOSE_WAIT状态的原因与解决方法
    https://blog.csdn.net/bianchengxiaosheng/article/details/78750237
    服务器大量TIME_WAIT
    https://blog.csdn.net/hyl999/article/details/106024982/

  11. tcp如何保证可靠性
    https://blog.csdn.net/liuchenxia8/article/details/80428157

  12. TCP-IP详解:Nagle算法
    https://blog.csdn.net/wdscq1234/article/details/52432095

  13. GET/POST区别
    https://www.cnblogs.com/logsharing/p/8448446.html
    1、概括
    对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
    而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)
    2、区别:
    1、get参数通过url传递,post放在request body中。
    2、get请求在url中传递的参数是有长度限制的,而post没有。
    3、get比post更不安全,因为参数直接暴露在url中,所以不能用来传递敏感信息。
    4、get请求只能进行url编码,而post支持多种编码方式。
    5、get请求会浏览器主动cache,而post支持多种编码方式。
    6、get请求参数会被完整保留在浏览历史记录里,而post中的参数不会被保留。
    7、GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
    8、GET产生一个TCP数据包;POST产生两个TCP数据包。

  14. tcp-keepalive
    https://blog.csdn.net/whatday/article/details/44833949

  15. accept()发生在三次握手的什么时候?
    答:accept()于是被放在三次握手之后。
    最初的想法是放在第三步的,但是但是,设想一个情景,若有10000个客户端都和该服务端进行连接,发送SYN,服务端收到之后,这些客户端却不再理会服务端的回复,然而此时服务端的资源却都用accept()分配了。这就是所谓的“DDOS攻击”。

  16. http vs https
    https://www.runoob.com/w3cnote/http-vs-https.html

  17. Linux中本机和本机Socket通信会走网卡吗
    https://www.zhihu.com/question/43590414

  18. SYN洪泛攻击的具体过程和解决方法
    https://blog.csdn.net/iami56789/article/details/111931235

三、操作系统

  1. 内存的堆栈
    栈:
    1)栈是连续的向下扩展的数据结构,总共只有1M或者2M的空间。空间不足就会异常提示栈溢出。
    2)存储自动变量, 函数调用者信息, 包括函数参数(可变参数列表的压栈方向是从右向左), 函数内局部变量, 函数返回值, 函数调用时的返回地址。
    堆:
    1)堆是不连续的向上扩展的数据结构,大小受限于计算机系统虚拟内存的大小。
    2)操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
    在这里插入图片描述

  2. 段页式内存分配
    https://blog.csdn.net/qq_33369979/article/details/89045204
    在这里插入图片描述
    Random-access memory (RAM) ,随机存取存储器,对于CPU来说,RAM是主要存放数据和程序的地方,所以也叫做“主存”
    Read-Only Memory(ROM),只读存储器,对于用户来说,它只能读取数据,不能写入信息,断电也没有关系,放ROM的数据一辈子都不会变

  3. 内存碎片产生原因及解决办法
    https://blog.csdn.net/csdn_kou/article/details/82891141

  4. 多线程多进程
    多线程是什么?
    说起多线程,那么就不得不说什么是线程,而说起线程,又不得不说什么是进程。
    1)进程可以简单的理解为一个可以独立运行的程序单位,它是线程的集合,进程就是有一个或多个线程构成的。而线程是进程中的实际运行单位,是操作系统进行运算调度的最小单位。可理解为线程是进程中的一个最小运行单元。
    那么多线程就很容易理解:多线程就是指一个进程中同时有多个线程正在执行。
    2)为什么要使用多线程?
    在一个程序中,有很多的操作是非常耗时的,如数据库读写操作,IO操作等,如果使用单线程,那么程序就必须等待这些操作执行完成之后才能执行其他操作。使用多线程,可以在将耗时任务放在后台继续执行的同时,同时执行其他操作。
    可以提高程序的效率。
    在一些等待的任务上,如用户输入,文件读取等,多线程就非常有用了。
    3)多线程的缺点:
    使用太多线程,是很耗系统资源,因为线程需要开辟内存。更多线程需要更多内存。
    影响系统性能,因为操作系统需要在线程之间来回切换。
    需要考虑线程操作对程序的影响,如线程挂起,中止等操作对程序的影响。
    线程使用不当会发生很多问题。
    总结:多线程是异步的,但这不代表多线程真的是几个线程是在同时进行,实际上是系统不断地在各个线程之间来回的切换(因为系统切换的速度非常的快,所以给我们在同时运行的错觉)。
    4)多进程是什么?
    进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。凡是用于完成操作系统的各种功能的进程就是系统进程,而所有由你启动的进程都是用户进程。
    同理,多进程就是指计算机同时执行多个进程,一般是同时运行多个软件
    5)对于windows来说,创建进程开销很大,因此 Windows 多线程学习重点是要大量面对资源争抢与同步方面的问题。对于Linux来说,创建进程开销很小,因此,Linux 下的学习重点大家要学习进程间通讯的方法。

  5. 内核态和用户态
    在这里插入图片描述

1)内核态(Kernel Mode):运行操作系统程序,操作硬件
用户态(User Mode):运行用户程序
特权环:R0相当于内核态,R3相当于用户态
2)指令
特权指令:只能由操作系统使用、用户程序不能使用的指令。 举例:启动I/O 内存清零 修改程序状态字 设置时钟 允许/禁止终端 停机
非特权指令:用户程序可以使用的指令。 举例:控制转移 算数运算 取数指令 访管指令(使用户程序从用户态陷入内核态)
3)CPU状态转换
用户态—>内核态:唯一途径是通过中断、异常、陷入机制(访管指令)
内核态—>用户态:设置程序状态字PSW

用户态和内核态切换的开销大,但是它的开销大在那里呢?简单点来说有下面几点
保留用户态现场(上下文、寄存器、用户栈等)
复制用户态参数,用户栈切到内核栈,进入内核态
额外的检查(因为内核代码对用户不信任)
执行内核态代码
复制内核态代码执行结果,回到用户态
恢复用户态现场(上下文、寄存器、用户栈等)
4)用户态转换到内核态的原因:
a)系统调用:这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。
b)异常.当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
c)外围设备的中断.当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,
如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。

  1. 同步IO和异步IO
    由于CPU和内存的速度远远高于外设的速度,所以,在IO编程中,就存在速度严重不匹配的问题。举个例子来说,比如要把100M的数据写入磁盘,CPU输出100M的数据只需要0.01秒,可是磁盘要接收这100M数据可能需要10秒,怎么办呢?有两种办法:
    第一种是CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO;
    另一种方法是CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步IO。

  2. 系统调用
    https://www.jianshu.com/p/9c62a65b6162

  3. 进程间通讯的7种方式
    https://blog.csdn.net/zhaohong_bo/article/details/89552188
    共享内存生命周期:
    共享内存的生命周期是随内核的。
    匿名管道、命名管道、消息队列、信号量、共享内存这五种进程间通信方式。两种管道的生命周期是随进程,剩下的都是随内核的。
    共享内存为什么是最快的通信方式?
      因为共享内存的原理是:在物理内存中直接开辟一片空间,并将空间映射到各个进程的虚拟地址空间的共享区;这时候进程就可以通过虚拟地址来直接对共享内存进行操作。
      其他通信的方式是将数据拷贝到内核态,用的时候再从内核态拷贝到用户态进行操作
      共享内存少了两步从用户态<–>内核态之间的数据拷贝过程,所以共享内存最快。

  4. 操作系统进程调度算法
    https://www.cnblogs.com/szitcast/p/10927375.html

  5. 作业与进程的区别
    进程:进程是一个程序对某个数据集的执行过程,是分配资源的基本单位。
    作业:作业是用户需要计算机完成的某项任务,是要求计算机所做工作的集合
    作业的调度属于高级调度,进程的调度属于低级调度
    作业就是从外存放到内存的一个过程,它可以包含一个或多进程。

  6. 虚拟内存
    https://blog.csdn.net/zhanyd/article/details/102987669

  7. 虚拟存储技术
    https://blog.csdn.net/qq_28602957/article/details/53729317

  8. 页、块
    页 块
    程序 内存
    逻辑地址 物理地址
    页号 块号
    页内地址 块内地址
    页长(页面大小) 块长(块大小)

  9. SPOOLing技术的特点:
    (1)提高了I/O速度.从对低速I/O设备进行的I/O操作变为对输入井或输出井的操作,如同脱机操作一样,提高了I/O速度,缓和了CPU与低速I/O设备速度不匹配的矛盾.
    (2)设备并没有分配给任何进程.在输入井或输出井中,分配给进程的是一存储区和建立一张I/O请求表.
    (3)实现了虚拟设备功能.多个进程同时使用一独享设备,而对每一进程而言,都认为自己独占这一设备,不过,该设备是逻辑上的设备.
    https://www.cnblogs.com/Codroc/p/12841295.html

  10. 测得某个请求调页的计算机系统部分状态数据为:CPU利用率20%,用于对换空间的硬盘利用率为97.7%,其他设备的利用率为5%。由此断定该系统异常。此情况下()能提高CPU的利用率。
    在这里插入图片描述
    answer:这明显是抖动现象,可通过页替换算法、减少运行进程数、增大内存来解决、但是用更快的硬盘、虽然i/o读写速度更快的、但抖动问题依在

  11. 进程间通讯的7种方式
    https://blog.csdn.net/zhaohong_bo/article/details/89552188

  12. 页面置换算法
    https://blog.csdn.net/qq_41209741/article/details/99586257

  13. 死锁,银行家算法
    https://blog.csdn.net/ccuaman/article/details/107326099

  14. 线程池
    https://www.cnblogs.com/lzpong/p/6397997.html

  15. 内存对齐的目的
    https://zhuanlan.zhihu.com/p/72915591

  16. Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈
    https://blog.csdn.net/yangkuanqaz85988/article/details/52403726

  17. 进程切换与线程切换的代价比较
    https://blog.csdn.net/liyangxueit/article/details/108510518
    上下文context: 上下文简单说来就是一个环境,相对于进程而言,就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。
    一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。
    用户级上下文: 正文、数据、用户堆栈以及共享存储区;
    寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);
    系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。

    当发生进程调度时,进行进程切换就是上下文切换(context switch).操作系统必须对上面提到的全部信息进行切换,新调度的进程才能运行。而系统调用进行的模式切换(mode switch)。模式切换与进程切换比较起来,容易很多,而且节省时间,因为模式切换最主要的任务只是切换进程寄存器上下文的切换。

  18. Linux内存分配与释放
    https://blog.csdn.net/u011370813/article/details/80271559
    Linux虚拟地址空间概述
    https://blog.csdn.net/weixin_39675679/article/details/116552895

  19. linux命令
    在linux中,&和&&,|和||介绍如下:
    & 表示任务在后台执行,如要在后台运行redis-server,则有 redis-server &
    && 表示前一条命令执行成功时,才执行后一条命令 ,如 echo '1‘ && echo ‘2’
    | 表示管道,上一条命令的输出,作为下一条命令参数,如 echo ‘yes’ | wc -l
    || 表示上一条命令执行失败后,才执行下一条命令,如 cat nofile || echo “fail”

  20. 唯快不破:linux下的epoll如何高效处理百万连接https://blog.csdn.net/zj6257/article/details/78664894

  21. fork
    https://www.cnblogs.com/dongguolei/p/8086346.html

  22. c++ 多线程 类成员函数_C++11多线程并发基础入门教程
    https://blog.csdn.net/weixin_39879674/article/details/110299307

  23. 硬链接软连接
    https://www.jianshu.com/p/dde6a01c4094

  24. 同一进程中的线程共享独占哪些内容
    线程共享的环境包括:1.进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯) 2.进程打开的文件描述符 3.信号的处理器 4.进程的当前目录和进程用户ID与进程组ID。
    线程不共享的有: 1.线程ID 2.寄存器组的值 3.线程的堆栈 4.错误返回码 5.线程的信号屏蔽码 6.线程的优先级
    在这里插入图片描述
    一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(/私有的)栈(stack),Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程共享,windows进程还有所谓进程默认堆,用户也可以创建自己的堆。
    用操作系统术语,线程切换的时候实际上切换的是一个可以称之为线程控制块的结构(TCB?),里面保存所有将来用于恢复线程环境必须的信息,包括所有必须保存的寄存器集,线程的状态等。
    堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
    栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

  25. 磁盘寻道算法
    https://blog.csdn.net/ydvampire/article/details/68070499

  26. mmap
    www.cnblogs.com/huxiao-tee/p/4660352.html

  27. 操作系统中锁的原理
    https://www.jianshu.com/p/61490effab35

四、数据库

  1. 范式
    1)第一范式 1NF
    定义: 属于第一范式关系的所有属性都不可再分,即数据项不可分。
    2)第二范式 2NF
    定义: 若某关系R属于第一范式,且每一个非主属性完全函数依赖于任何一个候选码,则关系R属于第二范式。
    候选码: 若关系中的某一属性组的值能唯一地标识一个元组,而其子集不能,则称该属性组为候选码。若一个关系中有多个候选码,则选定其中一个为主码。
    主属性: 所有候选码的属性称为主属性。不包含在任何候选码中的属性称为非主属性或非码属性。
    3)第三范式 3NF
    定义: 非主属性既不传递依赖于码,也不部分依赖于码。

  2. ACID
    1)原子性(atomicity)
    一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性
    2)一致性(consistency)
    事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态
    3)隔离性(isolation)
    事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。
    在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同,分别是:未授权读取,授权读取,可重复读取和串行化
    读未提交(Read Uncommited),该隔离级别允许脏读取,其隔离级别最低;比如事务A和事务B同时进行,事务A在整个执行阶段,会将某数据的值从1开始一直加到10,然后进行事务提交,此时,事务B能够看到这个数据项在事务A操作过程中的所有中间值(如1变成2,2变成3等),而对这一系列的中间值的读取就是未授权读取
    授权读取也称为已提交读(Read Commited),授权读取只允许获取已经提交的数据。比如事务A和事务B同时进行,事务A进行+1操作,此时,事务B无法看到这个数据项在事务A操作过程中的所有中间值,只能看到最终的10。另外,如果说有一个事务C,和事务A进行非常类似的操作,只是事务C是将数据项从10加到20,此时事务B也同样可以读取到20,即授权读取允许不可重复读取。
    **可重复读(Repeatable Read) **就是保证在事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻是一致的,因此该事务级别禁止不可重复读取和脏读取,但是有可能出现幻影数据。所谓幻影数据,就是指同样的事务操作,在前后两个时间段内执行对同一个数据项的读取,可能出现不一致的结果。在上面的例子中,可重复读取隔离级别能够保证事务B在第一次事务操作过程中,始终对数据项读取到1,但是在下一次事务操作中,即使事务B(注意,事务名字虽然相同,但是指的是另一个事务操作)采用同样的查询方式,就可能读取到10或20;
    串行化是最严格的事务隔离级别,它要求所有事务被串行执行,即事务只能一个接一个的进行处理,不能并发执行。
    4)持久性(durability)
    一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。–即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态

  3. 三种行锁(记录锁、间隙锁与临键锁)
    https://blog.csdn.net/weixin_34006468/article/details/88039873

  4. 聚簇索引和非聚簇索引(通俗易懂 言简意赅)
    https://www.cnblogs.com/jiawen010/p/11805241.html

为什么使用B+树原因:相对于B树,
(1)B+树空间利用率更高,可减少I/O次数,
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。而因为B+树的内部节点只是作为索引使用,而不像B-树那样每个节点都需要存储硬盘指针。
也就是说:B+树中每个非叶节点没有指向某个关键字具体信息的指针,所以每一个节点可以存放更多的关键字数量,即一次性读入内存所需要查找的关键字也就越多,减少了I/O操作。
e.g.假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内 部结点需要2个盘快。而B+ 树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比B+ 树多一次盘块查找时间(在磁盘中就 是 盘片旋转的时间)。 局部性原理与磁盘预读
(2)增删文件(节点)时,效率更高,
因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。
(3)B+树的查询效率更加稳定,
因为B+树的每次查询过程中,都需要遍历从根节点到叶子节点的某条路径。所有关键字的查询路径长度相同,导致每一次查询的效率相当。

叶子节点:是整个B+树最底层的,叶子节点中存储的是用户的真实记录信息和一些行信息及页信息。
非叶子节点:是叶子节点往上的所有节点,包括目录项、根节点等。目录项和叶子节点类似只不过目录项中存储的是目录项记录(包括页号、页中最小主键ID)它也是采用用户记录存储的方式来进行存储的而目录项和目录项是通过双向链表组织起来的。
在这里插入图片描述

  1. 索引失效
    https://www.cnblogs.com/liehen2046/p/11052666.html

  2. innodb myisam
    https://blog.csdn.net/qq_35642036/article/details/82820178
    innodb使用b+树,非叶子节点主要是存储主键的记录值,按照主键的大小顺序排成一个单向链表。
    叶子节点是存放用户数据的,页内数据根据用户记录的主键大小排列成的单向链表。而页和页之间是根据主键大小顺序排成一个双向链表。
    在这里插入图片描述

  3. 一致性哈希
    https://zhuanlan.zhihu.com/p/24440059

  4. 主从复制
    http://t.zoukankan.com/xuxinstyle-p-9546365.html

  5. MySQL日志系统:redo log、binlog、undo log 区别与作用
    https://blog.csdn.net/u010002184/article/details/88526708

  6. 联合索引的最左前缀匹配原则
    最左前缀匹配原则,是一个非常重要的原则,可以通过以下这几个特性来理解。

1)对于联合索引,MySQL 会一直向右匹配直到遇到范围查询(> , < ,between,like)就停止匹配。比如 a = 3 and b = 4 and c > 5 and d = 6,如果建立的是(a,b,c,d)这种顺序的索引,那么 d 是用不到索引的,但是如果建立的是 (a,b,d,c)这种顺序的索引的话,那么就没问题,而且 a,b,d 的顺序可以随意调换。
2)= 和 in 可以乱序,比如 a = 3 and b = 4 and c = 5 建立 (a,b,c)索引可以任意顺序。
3)如果建立的索引顺序是 (a,b)那么直接采用 where b = 5 这种查询条件是无法利用到索引的,这一条最能体现最左匹配的特性。
  1. count(*)和count(1)的区别和性能分析
    https://blog.csdn.net/FeiChangWuRao/article/details/89493516?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

  2. MySql分表、分库、分片和分区知识
    https://blog.csdn.net/zhangqiang_accp/article/details/87365717

  3. MySQL 中 MyISAM 中的查询为什么比 InnoDB 快?
    https://blog.csdn.net/xmtblog/article/details/87941698?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162539691916780269874157%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=162539691916780269874157&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v2~hot_rank-8-87941698.pc_v2_rank_blog_default&utm_term=mysql&spm=1018.2226.3001.4450

  4. mysql什么时候会造成数据库死锁
    https://blog.csdn.net/weixin_39670511/article/details/113383489

  5. 什么是倒排索引?
    https://www.cnblogs.com/zlslch/p/6440114.html

  6. 慢查询定位方式
    https://www.jianshu.com/p/7c9e2c479c15

  7. 使用索引的注意事项
    https://www.cnblogs.com/heyonggang/p/6610526.html

  8. mysql缓存淘汰机制:冷热数据的LRU
    https://blog.csdn.net/weixin_28834765/article/details/113387592

  9. 覆盖索引
    https://www.cnblogs.com/happyflyingpig/p/7662881.html

  10. sql语句的执行过程
    https://www.cnblogs.com/mengxinJ/p/14045520.html

  11. 如何创建索引更高效
    https://www.cnblogs.com/chenkeyu/p/12799207.html

  12. MVCC详解
    https://www.php.cn/mysql-tutorials-460111.html
    DB_TRX_ID:长度为6字节,存储了插入或更新语句的最后一个事务的事务ID
    DB_ROLL_PTR:长度为7字节,称之为:回滚指针。回滚指针指向写入回滚段的undo log记录,读取记录的时候会根据指针去读取undo log中的记录。

  13. 索引合并
    MySQL在 5.0版本中引入新特性:索引合并优化(Index merge optimization),当查询中单张表可以使用多个索引时,同时扫描多个索引并将扫描结果进行合并。
    通俗解释就是: 一条SQL中使用两个或多个索引,查出来的数据集取交集或并集
    该特新主要应用于以下三种场景
    1、 对OR语句求并集,如查询SELECT * FROM TB1 WHERE c1=“xxx” OR c2="“xxx"时,如果c1和c2列上分别有索引,可以按照c1和c2条件进行查询,再将查询结果合并(union)操作,得到最终结果
    2、 对AND语句求交集,如查询SELECT * FROM TB1 WHERE c1=“xxx” AND c2=”"xxx"时,如果c1和c2列上分别有索引,可以按照c1和c2条件进行查询,再将查询结果取交集(intersect)操作,得到最终结果
    3、 对AND和OR组合语句求结果

  14. mysql limit 很慢
    https://blog.csdn.net/weixin_33680141/article/details/113431185

  15. mysql排序原理
    https://blog.csdn.net/eagle89/article/details/81315981

  16. mysql的join操作
    https://zhuanlan.zhihu.com/p/54919968

  17. 索引下推
    MySQL 5.6引入了索引下推优化,默认开启,使用SET optimizer_switch = ‘index_condition_pushdown=off’;可以将其关闭。官方文档中给的例子和解释如下: people表中(zipcode,lastname,firstname)构成一个索引
    SELECT * FROM people WHERE zipcode=‘95054’ AND lastname LIKE ‘%etrunia%’ AND address LIKE ‘%Main Street%’;
    如果没有使用索引下推技术,则MySQL会通过zipcode='95054’从存储引擎中查询对应的数据,返回到MySQL服务端,然后MySQL服务端基于lastname LIKE '%etrunia%'和address LIKE '%Main Street%'来判断数据是否符合条件。 如果使用了索引下推技术,则MYSQL首先会返回符合zipcode='95054’的索引,然后根据lastname LIKE '%etrunia%'筛选出符合条件的索引后再返回到MySQL服务端,然后MySQL服务端基于address LIKE '%Main Street%'来判断数据是否符合条件,这样返回给MySQL服务端的索引数又会减少。有了索引下推优化,可以在有like条件查询的情况下,减少回表次数。

四、算法

  1. 100亿数据的中位数,4G内存,如何找到
    https://blog.csdn.net/weixin_44361667/article/details/104391186

海量数据排序
https://blog.csdn.net/jinzhao1993/article/details/50449640

  1. 求数组两个元素与(&)运算最大值,异或(^)运算最大值
    https://blog.csdn.net/Xu_JL1997/article/details/88735178

  2. 字节序
    https://blog.csdn.net/hyb612/article/details/82928446

  3. 海量数据处理算法——布隆过滤器
    https://blog.csdn.net/hguisu/article/details/7866173

  4. 零拷贝原理详解
    https://blog.csdn.net/weixin_42096901/article/details/103017044

  5. Linux写时拷贝技术(copy-on-write)
    https://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html

  6. Linux IO模式及 select、poll、epoll详解
    https://segmentfault.com/a/1190000003063859

  7. STL空间配置器allocator详解
    https://blog.csdn.net/xy913741894/article/details/66974004

  8. LRU
    https://leetcode-cn.com/problems/lru-cache-lcci/solution/lruhuan-cun-by-leetcode-solution/
    LFU
    https://zhuanlan.zhihu.com/p/311942904

  9. 实时排名算法
    https://blog.csdn.net/fangjian1204/article/details/38733997

  10. 排序
    https://blog.csdn.net/qq_33596574/article/details/88827925

  11. 无序数组,只有1,2,3,只遍历一遍排序。
    https://www.cnblogs.com/youxin/archive/2013/11/06/3410969.html

  12. 红黑树,B树,B+树 本质区别及应用场景
    https://zhuanlan.zhihu.com/p/335036067

  13. 负载均衡算法
    https://www.cnblogs.com/will-shun/archive/2017/09/22/7574644.html

  14. top k
    https://blog.csdn.net/suibianshen2012/article/details/52003082

  15. 并查集
    https://blog.csdn.net/yuanmartin/article/details/108489016

  16. kmp
    https://zhuanlan.zhihu.com/p/83334559

五、设计模式

  1. C++ 单例模式总结与剖析
    https://www.cnblogs.com/sunchaothu/p/10389842.html

六、题

  1. 数组中的最长连续子序列
    https://www.nowcoder.com/questionTerminal/c6b19ebae63143baa7fbb5b6d8dc24ec

  2. 不用pow实现计算m的n次幂,时间复杂度
    快速幂
    举例:4的7次方计算方法:
    7的二进制为0111
    则对应的乘法为4^4 * 4^2 * 4^1
    时间复杂度O(log(n))

  3. 数据流中位数
    https://www.cnblogs.com/gzshan/p/10904254.html

七、Redis

  1. Redis 为什么用跳表而不用平衡树
    https://cloud.tencent.com/developer/article/1666793

  2. mysql与redis数据一致性问题
    https://developer.aliyun.com/article/712285

八、Linux

  1. linux 进程 崩溃被杀 原因查找
    https://blog.csdn.net/whatday/article/details/108896318

九、网络编程

  1. epoll原理详解及反应堆模型https://blog.csdn.net/daaikuaichuan/article/details/83862311

十、场景题

  1. 执行write操作时,操作系统崩溃,有啥办法补救?
    https://zhuanlan.zhihu.com/p/140417823

  2. kafka相关角色
    https://www.cnblogs.com/wushaoyu/p/11216812.html
    kafka推拉模式
    https://blog.csdn.net/qq_38294275/article/details/108373946

  3. 消息队列

  4. 数据从网卡到应用的过程
    https://blog.csdn.net/JMW1407/article/details/108636657

敖丙技术文章连载
https://juejin.cn/user/4406498333825357
https://www.zhihu.com/people/aobingJava

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值