## 1、C和C++的区别
1)C是面向过程的语言,是一个结构化的语言,考虑如何通过一个过程对输入进行处理得到输出;C++是面向对象的语言,主要特征是“**封装、继承和多态**”。封装隐藏了实现细节,使得代码模块化;派生类可以继承父类的数据和方法,扩展了已经存在的模块,实现了代码重用;多态则是“一个接口,多种实现”,通过派生类重写父类的虚函数,实现了接口的重用。
2)C和C++**动态管理内存**的方法不一样,C是使用malloc/free,而C++除此之外还有new/delete关键字。
3)C++支持**函数重载**,C不支持函数重载 4)C++中有**引用**,C中不存在引用的概念
## 2、C++中指针和引用的区别
1)指针是一个新的变量,存储了另一个变量的地址,我们可以通过访问这个地址来修改另一个变量;
引用只是一个别名,还是变量本身,对引用的任何操作就是对变量本身进行操作,以达到修改变量的目的
2)**引用**只有一级,而指针可以有多级
3)指针**传参**的时候,还是值传递,指针本身的值不可以修改,需要通过解引用才能对指向的对象进行操作
引用传参的时候,传进来的就是变量本身,因此变量可以被修改
## 3、结构体struct和共同体union(联合)的区别
结构体:将不同类型的数据组合成一个整体,是自定义类型
共同体:不同类型的几个变量**共同占用一段内存**
1)结构体中的每个成员都有自己独立的地址,它们是同时存在的;
共同体中的所有成员占用同一段内存,它们不能同时存在;
2)sizeof(struct)是内存对齐后所有成员长度的总和,sizeof(union)是内存对齐后最长数据成员的长度、
结构体为什么要内存对齐呢?
## 4、#define和const的区别
1)#define定义的常量没有类型,所给出的是一个立即数;const定义的常量有类型名字,存放在静态区域
2)处理阶段不同,#define定义的宏变量在预处理时进行替换,可能有多个拷贝,const所定义的变量在编译时确定其值,只有一个拷贝。
3)#define定义的常量是不可以用指针去指向,const定义的常量可以用指针去指向该常量的地址
4)#define可以定义简单的函数,const不可以定义函数
## 5、重载overload,覆盖override,重写overwrite,这三者之间的区别
1)overload,将语义相近的几个函数用同一个名字表示,但是**参数和返回值不同**,这就是函数重载
特征:相同范围(同一个类中)、函数名字相同、参数不同、virtual关键字可有可无
2)override,派生类覆盖基类的虚函数,实现接口的重用
特征:不同范围(基类和派生类)、函数名字相同、参数相同、基类中必须**有****virtual关键字**(必须是虚函数)
3)overwrite,派生类屏蔽了其同名的基类函数
特征:不同范围(基类和派生类)、函数名字相同、参数不同或者参数相同且**无****virtual**关键字
## 6、new、delete、malloc、free之间的关系
new/delete, malloc/free都是动态分配内存的方式
1)malloc对开辟的空间大小严格指定,而new只需要对象名
2)new为对象分配空间时,调用对象的构造函数,delete调用对象的析构函数
既然有了malloc/free,C++中为什么还需要new/delete呢?
**因为malloc/free是库函数而不是运算符,不能把执行构造函数和析构函数的功能强加于malloc/free**
## 7、delete和delete\[\]的区别
delete只会调用一次析构函数,而**delete\[\]会调用每个成员的析构函数**
用new分配的内存用delete释放,用new\[\]分配的内存用delete\[\]释放
## 8、STL库用过吗?常见的STL容器有哪些?算法用过几个?
STL包括两部分内容:**容器和算法**
容器即存放数据的地方,比如array, vector,分为两类,序列式容器和关联式容器
**序列式容器**,其中的元素不一定有序,但是都可以被排序,如vector, list, queue, stack, heap, priority-queue, slist
**关联式容器**,内部结构是一个平衡二叉树,每个元素都有一个键值和一个实值,比如map, set, hashtable, hash\_set
算法有排序,复制等,以及各个容器特定的算法
迭代器是STL的精髓,迭代器提供了一种方法,使得它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构,它将容器和算法分开,让二者独立设计。
## 9、const知道吗?解释一下其作用
const修饰类的成员变量,表示常量不可能被修改
const修饰类的成员函数,表示该函数不会修改类中的数据成员,不会调用其他非const的成员函数
## 10、虚函数是怎么实现的
每一个含有虚函数的类都至少有有一个与之对应的**虚函数表**,其中存放着该类所有虚函数对应的函数指针(地址);类的示例对象不包含虚函数表,只有虚指针;
派生类会生成一个兼容基类的虚函数表。
## 11、堆和栈的区别
1)栈 stack 存放函数的参数值、局部变量,由编译器自动分配释放
堆heap,是由new分配的内存块,由应用程序控制,需要程序员手动利用delete释放,如果没有,程序结束后,操作系统自动回收
2)因为堆的分配需要使用频繁的new/delete,造成内存空间的不连续,会有大量的碎片
3)堆的生长空间向上,地址越大,栈的生长空间向下,地址越小
## 12、关键字static的作用
1)函数体内: static 修饰的局部变量作用范围为该函数体,不同于auto变量,其内存只被分配一次,因此其值在下次调用的时候维持了上次的值
2)模块内:static修饰全局变量或全局函数,可以被模块内的所有函数访问,但是不能被模块外的其他函数访问,使用范围限制在声明它的模块内
3)类中:修饰成员变量,表示该变量属于整个类所有,对类的所有对象只有一份拷贝
4)类中:修饰成员函数,表示该函数属于整个类所有,不接受this指针,只能访问类中的static成员变量
注意和const的区别!!!**const强调值不能被修改,而static强调唯一的拷贝,对所有类的对象**
## 13、STL中map和set的原理(关联式容器)
map和set的底层实现主要通过**红黑树**来实现
红黑树是一种特殊的二叉查找树
1)每个节点或者是黑色,或者是红色 2)根节点是黑色
3) 每个叶子节点(NIL)是黑色。 \[注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!\]
4)如果一个节点是红色的,则它的子节点必须是黑色的
5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
特性4)5)决定了没有一条路径会比其他路径长出2倍,因此红黑树是**接近平衡的二叉树**。
## 14、#include #include "file.h" 的区别
前者是从标准库路径寻找,后者是**从当前工作路径**
## 15、什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?
动态分配内存所开辟的空间,在使用完毕后未手动释放,导致一直占据该内存,即为内存泄漏。
方法:malloc/free要配套,对指针赋值的时候应该注意被赋值的指针是否需要释放;使用的时候记得指针的长度,防止越界
## 16、定义和声明的区别
声明是告诉编译器变量的类型和名字,不会为变量分配空间
定义需要分配空间,同一个变量可以被声明多次,但是只能被定义一次
## 17、C++文件编译与执行的四个阶段
1)预处理:根据文件中的预处理指令来修改源文件的内容
2)编译:编译成汇编代码
3)汇编:把汇编代码翻译成目标机器指令
4)链接:链接目标代码生成可执行程序
## 18、STL中的vector的实现,是怎么扩容的?
vector使用的注意点及其原因,频繁对vector调用push\_back()对性能的影响和原因。
vector就是一个**动态增长**的数组,里面有一个指针指向一片连续的空间,当空间装不下的时候,会申请一片更大的空间,将原来的数据拷贝过去,并释放原来的旧空间。当删除的时候空间并不会被释放,只是清空了里面的数据。对比array是静态空间一旦配置了就不能改变大小。
vector的动态增加大小的时候,并不是在原有的空间上持续新的空间(无法保证原空间的后面还有可供配置的空间),而是**以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,并释放原空间**。在VS下是1.5倍扩容,在GCC下是2倍扩容。
在原来空间不够存储新值时,每次调用push\_back方法都会重新分配新的空间以满足新数据的添加操作。如果在程序中频繁进行这种操作,还是比较消耗性能的。
## 19、STL中unordered\_map和map的区别
map是STL中的一个关联容器,提供键值对的数据管理。底层通过红黑树来实现,实际上是二叉排序树和非严格意义上的二叉平衡树。所以在map内部所有的数据都是有序的,且**map的**查询、插入、删除操作的**时间复杂度都是****O(logN)**。
unordered\_map和map类似,都是存储key-value对,可以通过key快速索引到value,不同的是**unordered\_map不会根据key进行排序**。unordered\_map底层是一个防冗余的哈希表,存储时根据key的hash值判断元素是否相同,即unoredered\_map内部是无序的。
## 20、C++的内存管理
在C++中,内存被分成五个区:**栈、堆、自由存储区、静态存储区、常量区**
栈:存放函数的参数和局部变量,编译器自动分配和释放
堆:new关键字动态分配的内存,由程序员手动进行释放,否则程序结束后,由操作系统自动进行回收
自由存储区:由malloc分配的内存,和堆十分相似,由对应的free进行释放
全局/静态存储区:存放全局变量和静态变量
常量区:存放常量,不允许被修改
## 21、 构造函数为什么一般不定义为虚函数?而析构函数一般写成虚函数的原因 ?
1、**构造函数不能声明为虚函数**
1)因为创建一个对象时需要确定对象的类型,而虚函数是在运行时确定其类型的。而在构造一个对象时,由于对象还未创建成功,编译器无法知道对象的实际类型,是类本身还是类的派生类等等;
2)虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数即构造函数了;
2、**析构函数最好声明为虚函数**
首先析构函数可以为虚函数,当析构一个指向派生类的基类指针时,最好将基类的析构函数声明为虚函数,否则可以存在内存泄露的问题。
如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除指向派生类的基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。
## 22、静态绑定和动态绑定的介绍
静态绑定和动态绑定是C++多态性的一种特性
1)**对象的**静态类型和动态类型
静态类型:对象在声明时采用的类型,在**编译时确定**
动态类型:当前对象所指的类型,在**运行期决定**,对象的动态类型可变,静态类型无法更改
2)静态绑定和动态绑定
静态绑定:绑定的是对象的静态类型,函数依赖于对象的静态类型,在编译期确定
动态绑定:绑定的是对象的动态类型,函数依赖于对象的动态类型,在运行期确定
只有虚函数才使用的是动态绑定,其他的全部是静态绑定
## 23、 引用是否能实现动态绑定,为什么引用可以实现
可以。因为引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指的对象的实际类型所定义的。
## 24、深拷贝和浅拷贝的区别
深拷贝和浅拷贝可以简单的理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,如果**资源重新分配了就是深拷贝**;反之没有重新分配资源,就是浅拷贝。
## 25、 什么情况下会调用拷贝构造函数(三种情况)
系统自动生成的构造函数:普通构造函数和拷贝构造函数 (在没有定义对应的构造函数的时候)
生成一个实例化的对象会调用一次普通构造函数,而用一个对象去实例化一个新的对象所调用的就是拷贝构造函数
调用拷贝构造函数的情形:
1)用类的一个对象去初始化另一个对象的时候
2)当函数的参数是类的对象时,就是值传递的时候,如果是引用传递则不会调用
3)当函数的返回值是类的对象或者引用的时候
class A{
private:
int data;
public:
A(int i){ data = i;} //自定义的构造函数
A(A && a); //拷贝构造函数
int getdata(){return data;}
};
//拷贝构造函数
A::A(A && a){
data = a.data;
cout <<"拷贝构造函数执行完毕"<<endl;
}
//参数是对象,值传递,调用拷贝构造函数
int getdata1(A a){
return a.getdata();
}
//参数是引用,引用传递,不调用拷贝构造函数
int getdata2(A &a){
return a.getdata();
}
//返回值是对象类型,会调用拷贝构造函数
A getA1(){
A a(0);
return a;
}
//返回值是引用类型,会调用拷贝构造函数,因为函数体内生成的对象是临时的,离开函数就消失
A& getA2(){
A a(0);
return a;
}
int main(){
A a1(1);
A b1(a1); //用a1初始化b1,调用拷贝构造函数
A c1=a1; //用a1初始化c1,调用拷贝构造函数
int i=getdata1(a1); //函数形参是类的对象,调用拷贝构造函数
int j=getdata2(a1); //函数形参类型是引用,不调用拷贝构造函数
A d1=getA1(); //调用拷贝构造函数
A e1=getA2(); //调用拷贝构造函数
return 0;
}
## 26、 C++的四种强制转换
类型转化机制可以分为隐式类型转换和显示类型转化(强制类型转换)
(new-type) expression; new-type (expression)
隐式类型转换比较常见,在混合类型表达式中经常发生;四种强制类型转换操作符:
**static\_cast、dynamic\_cast、const\_cast、reinterpret\_cast**
1)static\_cast :**编译时期**的静态类型检查
static\_cast ( expression )
该运算符把expression转换成type-id类型,在编译时使用类型信息执行转换,在转换时执行必要的检测(指针越界、类型检查),其操作数相对是安全的
2)dynamic\_cast:**运行时**的检查
用于在集成体系中进行安全的向下转换downcast,即基类指针/引用\->派生类指针/引用
dynamic\_cast是4个转换中唯一的RTTI操作符,提供运行时类型检查。
dynamic\_cast如果不能转换返回NULL
源类中必须要有虚函数,保证多态,才能使用dynamic\_cast(expression)
3)const\_cast: 去除const常量属性,使其可以修改 ; volatile属性的转换
4)reinterpret\_cast: 通常为了将一种数据类型转换成另一种数据类型
## 27、调试程序的方法
windows下直接使用vs的debug功能
linux下直接使用gdb,我们可以在其过程中给程序添加断点,监视等辅助手段,监控其行为是否与我们设计相符
## 28、extern“C”作用
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
## 29、typdef和define区别
#define是预处理命令,在预处理是执行简单的替换,不做正确性的检查
typedef是在编译时处理的,它是在自己的作用域内给已经存在的类型一个别名
typedef (int\*) pINT;
#define pINT2 int\*
效果相同?实则不同!实践中见差别:pINT a,b;的效果同int \*a; int \*b;表示定义了两个整型指针变量。而pINT2 a,b;的效果同int \*a, b;表示定义了一个整型指针变量a和整型变量b。
## 30、volatile关键字在程序设计中有什么作用
volatile是“易变的”、“不稳定”的意思。volatile是C的一个较为少用的关键字,它用来解决变量在“共享”环境下容易出现读取错误的问题。
## 31、引用作为函数参数以及返回值的好处
对比值传递,引用传参的好处:
1)在函数内部可以对此参数进行修改
2)提高函数调用和运行的效率(所以没有了传值和生成副本的时间和空间消耗)
如果函数的参数实质就是形参,不过这个形参的作用域只是在函数体内部,也就是说实参和形参是两个不同的东西,要想形参代替实参,肯定有一个值的传递。函数调用时,值的传递机制是通过“形参\=实参”来对形参赋值达到传值目的,产生了一个实参的副本。即使函数内部有对参数的修改,也只是针对形参,也就是那个副本,实参不会有任何更改。函数一旦结束,形参生命也宣告终结,做出的修改一样没对任何变量产生影响。
用引用作为返回值最大的好处就是在内存中不产生被返回值的副本。
但是有以下的限制:
1)不能返回局部变量的引用。因为函数返回以后局部变量就会被销毁
2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak
3)可以返回类成员的引用,但是最好是const。因为如果其他对象可以获得该属性的非常量的引用,那么对该属性的单纯赋值就会破坏业务规则的完整性。
## 32、纯虚函数
纯虚函数是只有声明没有实现的虚函数,是对子类的约束,是接口继承
包含纯虚函数的类是抽象类,它不能被实例化,只有实现了这个纯虚函数的子类才能生成对象
普通函数是静态编译的,没有运行时多态
## 33、什么是野指针
野指针不是NULL指针,是**未初始化或者未清零的指针**,它指向的内存地址不是程序员所期望的,可能指向了受限的内存
成因:1)指针变量没有被初始化 ; 2)指针指向的内存被释放了,但是指针没有置NULL
3)指针超过了变量了的作用范围,比如b\[10\],指针b+11
## 33、线程安全和线程不安全
线程安全就是多线程访问时,采用了**加锁机制**,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可以使用,不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能多个线程先后更改数据所得到的数据就是脏数据。
## 34、C++中内存泄漏的几种情况
内存泄漏是指**己动态分配的堆内存由于某种原因程序未释放或无法释放**,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
1)类的构造函数和析构函数中new和delete没有配套
2)在释放对象数组时没有使用delete\[\],使用了delete
3)没有将基类的析构函数定义为虚函数,当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确释放,因此造成内存泄露
4)没有正确的清楚嵌套的对象指针
## 35、栈溢出的原因以及解决方法
1)函数调用层次过深, 每调用一次, 函数的参数、局部变量等信息就压一次栈
2)局部变量体积太大。
解决办法大致说来也有两种:
1> **增加栈内存的数目**;增加栈内存方法如下,在vc6种依次选择Project->Setting->Link,在Category中选择output,在Reserve中输入16进制的栈内存大小如:0x10000000
2> 使用**堆内存**;具体实现由很多种方法,可以直接把数组定义改成指针, 然后动态申请内存; 也可以把局部变量变成全局变量, 一个偷懒的办法是直接在定义前边加个static, 呵呵, 直接变成静态变量(实质就是全局变量)
## 36、C++标准库vector以及迭代器
每种容器类型都定义了自己的迭代器类型,每种容器都定义了一队命名为**begin和end的函数,用于返回迭代器**。
迭代器是容器的精髓,它提供了一种方法使得它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构,它将容器和算法分开,让二者独立设计。
## 37、C++ 11有哪些新特性
C++11不仅包含核心语言的新机能,而且扩展了C++的标准程序库(STL),并入了大部分的C++ Technical Report 1(TR1)程序库。C++11包括大量的新特性:包括**lambda表达式,类型推导关键字auto、decltype,和模板的大量改进**。
**auto**: C++11中引入auto第一种作用是为了**自动类型推导**
auto的自动类型推导,用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推导,可以大大简化我们的编程工作
decltype: decltype实际上有点像auto的反函数,auto可以让你声明一个变量,而**decltype则可以从一个变量或表达式中得到类型**,有实例如下:
nullptr: nullptr是为了解决原来C++中NULL的二义性问题而引进的一种新的类型,因为NULL实际上代表的是0,
lambda表达式类似Javascript中的闭包,它可以用于**创建并定义匿名的函数对象**,以简化编程工作。Lambda的语法如下:
**\[函数对象参数\](操作符重载函数参数)mutable或exception声明\->返回值类型{函数体}**
## 38、C++中vector和list的区别
vector和数组类似,拥有一段连续的内存空间。vector申请的是一段连续的内存,当插入新的元素内存不够时,通常以2倍重新申请更大的一块内存,将原来的元素拷贝过去,释放旧空间。因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。
list是由双向链表实现的,因此内存空间是不连续的。只能通过指针访问数据,所以**list的随机存取非常没有效率,时间复杂度为o(n); 但由于链表的特点,能高效地进行插入和删除。**
vector拥有一段连续的内存空间,能很好的支持随机存取,因此vector::iterator支持“+”,“+=”,“<”等操作符。
list的内存空间可以是不连续,它不支持随机访问,因此list::iterator则不支持“+”、“+=”、“<”等
vector::iterator和list::iterator都重载了“++”运算符。
总之,如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;
如果需要大量的插入和删除,而不关心随机存取,则应使用list。
## 39、C语言的函数调用过程
函数的调用过程:
1)从**栈**空间分配存储空间; 2)从实参的存储空间复制值到形参栈空间; 3)进行运算
形参在函数未调用之前都是没有分配存储空间的,在函数调用结束之后,形参弹出栈空间,清除形参空间。
数组作为参数的函数调用方式是地址传递,形参和实参都指向相同的内存空间,调用完成后,形参指针被销毁,但是所指向的内存空间依然存在,不能也不会被销毁。
当函数有多个返回值的时候,不能用普通的 return 的方式实现,需要通过传回地址的形式进行,即地址/指针传递。
**传值**:传值,实际是把实参的值赋值给行参,相当于copy。那么对行参的修改,不会影响实参的值 。
**传址**: 实际是传值的一种特殊方式,只是他传递的是地址,不是普通的赋值,那么传地址以后,实参和行参都指向同一个对象,因此对形参的修改会影响到实参。
## 40、C++中的基本数据类型及派生类型
1)整型 int ; 2)浮点型 单精度float,双精度double; 3)字符型 char; 4)逻辑型 bool; 5)控制型 void
基本类型的字长及其取值范围可以放大和缩小,改变后的类型就叫做基本类型的派生类型。派生类型声明符由基本类型关键字char、int、float、double前面加上类型修饰符组成。
类型修饰符包括:
\>short 短类型,缩短字长; \>long 长类型,加长字长;
\>signed 有符号类型,取值范围包括正负值; \>unsigned 无符号类型,取值范围只包括正值
## 41、友元函数和友元类
友元提供了不同类的成员函数之间、类的成员函数和一般函数之间进行数据共享的机制。
**通过友元,一个不同函数或者另一个类中的成员函数可以访问类中的私有成员和保护成员**。
友元的正确使用能提高程序的运行效率,但同时也破坏了类的封装性和数据的隐藏性,导致程序可维护性变差。
1)**友元函数**
有元函数是可以访问类的私有成员的非成员函数。它是定义在类外的普通函数,不属于任何类,但是需要在类的定义中加以声明。
**friend 类型 函数名(形式参数);**
一个函数可以是多个类的友元函数,只需要在各个类中分别声明。
2)**友元类**
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
**friend class 类名**;
使用友元类时注意:
(1) 友元关系不能被继承。
(2) 友元关系是**单向的**,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系**不具有传递性**。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明
## 42、C++线程中的几种锁机制
线程之间的锁有:**互斥锁、条件锁、自旋锁、读写锁、递归锁**。一般而言,锁的功能越强大,性能就会越低。
**1)互斥锁**
互斥锁用于控制多个线程对他们之间共享资源互斥访问的一个信号量。也就是说是为了避免多个线程在某一时刻同时操作一个共享资源。例如线程池中的有多个空闲线程和一个任务队列。任何是一个线程都要使用互斥锁互斥访问任务队列,以避免多个线程同时访问任务队列以发生错乱。
在某一时刻,只有一个线程可以获取互斥锁,在释放互斥锁之前其他线程都不能获取该互斥锁。如果其他线程想要获取这个互斥锁,那么这个线程只能以阻塞方式进行等待。
头文件:
类型:pthread\_mutex\_t,
函数:pthread\_mutex\_init(pthread\_mutex\_t \* mutex, const phtread\_mutexattr\_t \* mutexattr);//动态方式创建锁,相当于new动态创建一个对象
pthread\_mutex\_destory(pthread\_mutex\_t \*mutex)//释放互斥锁,相当于delete
pthread\_mutex\_t mutex = PTHREAD\_MUTEX\_INITIALIZER;//以静态方式创建锁
pthread\_mutex\_lock(pthread\_mutex\_t \*mutex)//以阻塞方式运行的。如果之前mutex被加锁了,那么程序会阻塞在这里。
pthread\_mutex\_unlock(pthread\_mutex\_t \*mutex)
int pthread\_mutex\_trylock(pthread\_mutex\_t \* mutex);//会尝试对mutex加锁。如果mutex之前已经被锁定,返回非0,;如果mutex没有被锁定,则函数返回并锁定mutex
//该函数是以非阻塞方式运行了。也就是说如果mutex之前已经被锁定,函数会返回非0,程序继续往下执行。
**2)条件锁**
条件锁就是所谓的条件变量,某一个线程因为某个条件为满足时可以使用条件变量使改程序处于阻塞状态。一旦条件满足以“信号量”的方式唤醒一个因为该条件而被阻塞的线程。最为常见就是在线程池中,起初没有任务时任务队列为空,此时线程池中的线程因为“任务队列为空”这个条件处于阻塞状态。一旦有任务进来,就会以信号量的方式唤醒一个线程来处理这个任务。这个过程中就使用到了条件变量pthread\_cond\_t。
头文件:
类型:pthread\_cond\_t
函数:pthread\_cond\_init(pthread\_cond\_t \* condtion, const phtread\_condattr\_t \* condattr);//对条件变量进行动态初始化,相当于new创建对象
pthread\_cond\_destory(pthread\_cond\_t \* condition);//释放动态申请的条件变量,相当于delete释放对象
pthread\_cond\_t condition = PTHREAD\_COND\_INITIALIZER;//静态初始化条件变量
pthread\_cond\_wait(pthread\_cond\_t \* cond, pthread\_mutex\_t \* mutex);//该函数以阻塞方式执行。如果某个线程中的程序执行了该函数,那么这个线程就会以阻塞方式等待,直到收到pthread\_cond\_signal或者pthread\_cond\_broadcast函数发来的信号而被唤醒。
注意:pthread\_cond\_wait函数的语义相当于:首先解锁互斥锁,然后以阻塞方式等待条件变量的信号,收到信号后又会对互斥锁加锁。
为了防止“虚假唤醒”,该函数一般放在while循环体中。例如
pthread\_mutex\_lock(mutex);//加互斥锁
while(条件不成立)//当前线程中条件变量不成立
{pthread\_cond\_wait(cond, mutex);//解锁,其他线程使条件成立发送信号,加锁。}
...//对进程之间的共享资源进行操作
pthread\_mutex\_unlock(mutex);//释放互斥锁
pthread\_cond\_signal(pthread\_cond\_t \* cond);//在另外一个线程中改变线程,条件满足发送信号。唤醒一个等待的线程(可能有多个线程处于阻塞状态),唤醒哪个线程由具体的线程调度策略决定
pthread\_cond\_broadcast(pthread\_cond\_t \* cond);//以广播形式唤醒所有因为该条件变量而阻塞的所有线程,唤醒哪个线程由具体的线程调度策略决定
pthread\_cond\_timedwait(pthread\_cond\_t \* cond, pthread\_mutex\_t \* mutex, struct timespec \* time);//以阻塞方式等待,如果时间time到了条件还没有满足还是会结束
3)自旋锁
前面的两种锁是比较常见的锁,也比较容易理解。下面通过比较互斥锁和自旋锁原理的不同,这对于真正理解自旋锁有很大帮助。
假设我们有一个两个处理器core1和core2计算机,现在在这台计算机上运行的程序中有两个线程:T1和T2分别在处理器core1和core2上运行,两个线程之间共享着一个资源。
首先我们说明互斥锁的工作原理,互斥锁是是一种sleep-waiting的锁。假设线程T1获取互斥锁并且正在core1上运行时,此时线程T2也想要获取互斥锁(pthread\_mutex\_lock),但是由于T1正在使用互斥锁使得T2被阻塞。当T2处于阻塞状态时,T2被放入到等待队列中去,处理器core2会去处理其他任务而不必一直等待(忙等)。也就是说处理器不会因为线程阻塞而空闲着,它去处理其他事务去了。
而自旋锁就不同了,自旋锁是一种busy-waiting的锁。也就是说,如果T1正在使用自旋锁,而T2也去申请这个自旋锁,
此时T2肯定得不到这个自旋锁。与互斥锁相反的是,此时运行T2的处理器core2会一直不断地循环检查锁是否可用(自旋锁请求),直到获取到这个自旋锁为止。
从“自旋锁”的名字也可以看出来,如果一个线程想要获取一个被使用的自旋锁,那么它会一致占用CPU请求这个自旋锁使得CPU不能去做其他的事情,直到获取这个锁为止,这就是“自旋”的含义。
当发生阻塞时,互斥锁可以让CPU去处理其他的任务;而自旋锁让CPU一直不断循环请求获取这个锁。通过两个含义的对比可以我们知道“自旋锁”是比较耗费CPU的
头文件:
自旋锁的类型:spinlock\_t
相关函数:初始化:spin\_lock\_init(spinlock\_t \*x);
spin\_lock(x); //只有在获得锁的情况下才返回,否则一直“自旋”
spin\_trylock(x); //如立即获得锁则返回真,否则立即返回假
释放锁:spin\_unlock(x);
spin\_is\_locked(x)// 该宏用于判断自旋锁x是否已经被某执行单元保持(即被锁),如果是, 返回真,否则返回假。
注意:自旋锁适合于短时间的的轻量级的加锁机制。
4)读写锁
说到读写锁我们可以借助于“读者\-写者”问题进行理解。首先我们简单说下“读者\-写者”问题。
计算机中某些数据被多个进程共享,对数据库的操作有两种:一种是读操作,就是从数据库中读取数据不会修改数据库中内容;另一种就是写操作,写操作会修改数据库中存放的数据。因此可以得到我们允许在数据库上同时执行多个“读”操作,但是某一时刻只能在数据库上有一个“写”操作来更新数据。这就是一个简单的读者\-写者模型
- 概述
- html
- 盒模型
- 块行元素及继承
- 语义化
- html5中新特性
- Doctype
- meta 标签的作用
- defer和async
- CSS
- CSS3新特性
- 选择器优先级
- 清除浮动
- 隐藏元素
- positon-overflow-lineheight-display
- 重排和重绘
- 伪类和伪元素
- BFC
- 浏览器兼容
- 布局
- 三栏布局
- 其他
- 浮动布局
- 绝对布局
- flexbox布局
- grid网格布局
- table表格布局
- 双飞翼布局
- 圣杯布局
- 居中
- 水平居中
- 垂直居中
- 水平垂直居中
- 两栏布局
- 瀑布流布局
- 响应式-自适应布局
- offsetWidth-offsetHeight-offsetLeft-offsetTop
- px-rpx-em-rem-%-vw-vh-vm
- 预处理器-Sass
- Sass代码重用
- sass循环-控制语句
- sass嵌套
- sass数据类型
- Sass基本运算
- css预处理
- Html+CSS编程
- 1px物理像素
- 实现移动端适配
- CSS创建一个三角形
- 满屏品字
- 让Chrome支持小于12px 的文字
- css旋转图
- css-sprites
- 避免CSS全局污染
- 换肤方案
- ES6
- ES6的新特性
- var, let, const
- Proxy和Object.defineProperty
- proxy
- Object.defineProperty
- 箭头函数
- class类
- class基础
- constructor方法
- Class的几个注意点
- for/forEach/for-in/for-of 的区别
- js数组方法--字符串
- js字符串操作
- promise
- Generator函数-async/await
- set/map
- 模块化-import-export
- rest与(...)
- Symbol
- 事件循环eventLoop
- json
- JavaScript
- 数据类型
- call,apply,bind
- 闭包
- 深拷贝浅拷贝
- this-new-callapplybind
- 原型/构造函数/实例
- 作用域和作用域链
- 继承
- 内存-垃圾回收
- 对象-函数创建方式
- 设计模式
- 六大原则
- 单例模式
- 策略模式
- 工厂模式
- 适配器模式
- 代理模式
- 迭代器模式
- 发布订阅模式--观察者模式
- 命令模式
- 组合模式
- 装饰器模式
- js与java区别
- 正则
- 正则案例
- 正则语法
- eval
- js专门编程
- 数组扁平化
- 随机数组
- 去掉数组中重复的数据
- promise题目
- 函数柯里化
- 获取 url 中的参数
- 手动实现map(forEach以及filter也类似)
- 函数节流和防抖函数
- TypeScript
- vue与ts
- 单-多线程
- Vue.js
- 源码原理
- 生命周期
- 组件生命周期调用顺序
- 使用场景-生命周期
- 双向绑定
- MVVM与MVC
- 虚拟DOM
- diff算法
- nextTick异步
- keep-alive
- key
- 组件
- 组件继承
- 组件通信
- props/$emit
- $emit/$on
- Vuex
- Vuex与localStorage
- $attrs/$listeners
- provide/inject
- $parent / $children与 ref
- 组件基础
- 插槽slot
- Vue实现高阶组件
- 指令
- 具体指令
- 自定义指令
- 自定义指令案例
- webpack
- 核心概念
- 打包和拆包
- Loader
- plugin
- vite
- webpack优化
- 写脚手架
- 手写loader和plugin
- vue CLI脚手架
- vue-cli 3 与 2 版本
- Runtime-Compiler和Runtime-only的区别
- run dev和npm run build
- render函数的使用
- Vue程序源码运行过程解析
- vue CLI脚手架安装
- vue-router
- route原理和SPA
- 改变路径
- 基本设置使用
- router参数传递
- 优化-懒加载,嵌套路由
- 单页面多路由
- $route 和 $router
- 导航守卫
- 动态-默认路由
- 设置404页面
- 路由脚本
- vuex
- vuex 认识
- Vuex基本使用
- vuex各模块
- 优化方法
- Mutation
- Action
- vue性能优化
- data-computed-watch
- watch的几种用法
- vue数组
- SPA单页面
- vue和react区别
- vue与angular比较
- vue3.0
- ref-reactive-toRef-toRefs
- setup()-computed()-watch()
- Teleport-Suspense-Fragment-defineAsyncComponent
- 过滤器--filter
- vue3生命周期
- ElementUI源码
- 搭建组件库
- 典型组件实现
- 微前端qiankun
- 服务端渲染nuxtJs
- render()函数
- 剑指offer
- 最小的K个数
- 字符串
- 排列字符串
- 第一个只出现一次的字符
- 和为S的字符串
- 单词翻转序列
- 把字符串转换成整数
- 表示数值的字符串
- 最长不含重复字符的子字符串
- 最长回文
- 电话号码的字母组合
- 二叉树
- 遍历二叉树
- 打印二叉树
- 镜像-翻转二叉树
- 判断平衡二叉树
- 二叉树深度
- 二叉搜索树
- 和为某值的路径
- 重建二叉树
- 判断对称二叉树
- 下一个节点
- 序列化二叉树
- 树的子结构
- 最近公共祖先
- 链表
- 链表中环的入口结点
- 两链表的公共节点
- 从头到尾打印链表
- 倒数第K个结点-链表
- 反转链表
- 合并两个排序的链表
- O(1)时间内删除链表节点
- 复杂链表的复制
- 数组
- 逆序对
- 旋转数组中的最小数字
- 奇数位于偶数前面
- 连续子数组和的最大值
- 只出现一次的数字-数组
- 和为S的两个数字
- 重复数字-数组
- 智力题
- 股票的最大利润
- 扑克牌顺子
- 孩子圆圈
- n个骰子的点数
- 滑动窗口的最大值
- 礼物的最大值
- 丑数
- 斐波那契数列
- 剪绳子
- 栈
- 两个栈实现队列
- 包含main函数的栈
- 栈的压入-弹出序列
- 数字
- 二进制中1的个数
- 数值的整数次方
- 1~n整数中1出现的次数
- 求1+2+3+...+n
- 不用加减乘除做加法
- 和为S的连续正数序列
- X的平方根
- 整数反转
- 素数的个数统计
- 41-和为S的连续正数序列
- 最小公倍数-最大公约数-质数
- 双指针
- 两数、三数之和
- 哈希表
- 字母异位词分组
- 和为K的子数组
- 前 K 个高频元素
- 回溯算法
- 数组的子集
- 全排列
- 并集查
- 岛屿数量
- reactJs
- React-router
- 生命周期-R
- 组件-R
- 组件通信-R
- react组件基础
- 组件渲染
- redux
- react-redux
- redux使用
- Redux中间件
- Redux和Vuex
- react-hook
- 前端--杂
- 原生js
- BOM-浏览器对象模型
- 事件委托
- 原生js添加事件
- js 操作DOM
- 原生js小实验
- 表单
- 点击空白关闭弹窗
- js兼容
- 前端性能优化
- 性能优化-网络
- 回流与重绘
- 图片懒加载
- 渲染几万条数据且不卡住页面
- 前端性能优化总结
- 渲染篇(浏览器渲染)
- 雪碧图
- 图片优化
- CDN
- Ajax、fetch、axios
- axios
- axios原理
- 登录验证
- token
- Session
- 认证-授权- 凭证
- JWT
- 登录-token验证
- 登录代码
- 登录退出-session验证
- cookie
- jQuery
- document load 和 document ready 的区别
- $.get()提交和$.post()提交有区别
- jquery选择器
- 判断checkbox是否选中
- 操作元素和样式--jquery
- 操作DOM-- jquery
- 事件--jquery
- 动画-- jQuery
- 过滤方法-- jQuery
- 查找方法- jQuery
- AJAX --jQuery
- 前端调试
- 监控和埋点
- 错误监控与上报
- 异常捕获问题
- 前端部署
- docker
- jenkins
- 灰度发布
- nginx
- k8s
- jest测试
- 移动端
- 跨端方案
- uni-app
- 移动端常见问题
- react-Native
- npm-yarn-pnpm
- Node.js
- Koa.js
- node汇总
- linux和git
- linux
- git
- git工作原理
- Git和SVN的区别
- rebase 与 merge的区别
- git reset、git revert 和 git checkout 有什么区别
- git pull”和“git fetch
- 解决分支合并冲突
- 代码提交和分支
- 创建配置增加删除
- 代码格式化
- 计算机网络
- 输入url到页面渲染全过程
- TCP和UDP
- 跨域
- 不跨域通信
- 跨域-新
- JSONP
- CORS
- nginx代理跨域
- WebSocket协议跨域
- postMessage跨域
- PostMessage的用法
- Node中间件代理(两次跨域)
- document.domain + iframe
- http状态码
- Http2.0-http1.1
- 七层模型
- tcp拥塞控制和流量控制
- 请求-GET和POST
- 请求报文和响应报文
- cookie-localStorage-sessionStorage
- localStorage-sessionStorage
- 强缓存-弱缓存
- HTTP缓存控制
- 缓存机制
- 具体http缓存机制
- HTTP-HTTPS
- 浏览器通讯
- 网络安全
- XSS攻击
- CSRF攻击
- sql注入原理
- ⽹络劫持
- 长连接和短连接、长轮询和短轮询
- Websocket
- socket
- socket和http
- 数据结构
- 八大排序
- 冒泡排序
- 快速排序
- 直接插入
- 希尔排序
- 堆排序
- 简单选择
- 归并排序
- 基数排序
- 数据结构1
- 数组和链表
- 哈希函数,哈希表
- 数据库
- 事务
- 视图
- insert /update/delete
- create/drop/alter
- 数据库锁
- 数据库索引
- hash-B+
- 索引类型
- select
- join 连接
- 子查询和联合查询
- 范式
- 触发器-存储过程
- 高并发环境
- 优化-数据库
- SQL语句优化
- 索引优化
- 缓存参数优化
- 分库分表
- 分表—水平分割和垂直分割
- 主键 超键 候选键 外键
- 数据库总结
- 项目整理
- 电商APP
- 商品详情
- 目录
- 菜单联动
- mixins混入--backtop回到顶部封装
- better-scroll
- 轮播图原理
- 移动适配
- 大数据开发
- cuda
- 基于深度学习快速检测老鼠脸部并测定其疼痛程度
- yolo
- 深度学习
- 基于 CUDA 并行加速的多图谱三维脑图像分割
- 各大公司笔试题
- 美团
- 一个数是否由数组里的数组成
- 字符串的最长不重复子串长度
- 正则把a字符串替换成b字符串
- 招银
- 恒生电子
- 大数据
- hadoop
- mapreduce
- map 数量 和 reduce 数量
- MapReduce的工作原理
- combiner、partition的作用
- Shuffle
- join
- wordcount例子
- MapReduce优化
- 数据倾斜-mapreduce
- 6个类-mapreduce
- Hdfs
- hdfs文件读流程
- hdfs可靠性策略
- hdfs的体系结构
- 二次排序
- hadoop调度器
- hadoop1.x和hadoop2.x的区别
- NameNode
- 你对hadoop的了解
- yarn
- 启动hadoop的进程-脚本和用法
- Hadoop 序列化和反序列化
- 安装配置hadoop
- hadoop生态圈组件
- hive
- 解决数据倾斜
- 4种排序方式
- 分区和分桶的区别
- Sort By,Order By,Cluster By,Distrbute By
- 内部表和外部表
- hive 底层与数据库交互原理
- Hbase
- rowkey以及热点问题
- habse机制
- zookeeper
- spark
- spark有什么特点
- Spark运行机制与原理
- Spark有哪些组件
- hadoop和spark的shuffle相同和差异
- 几种部署模式
- RDD
- 常见RDD
- 会导致shuffle的算子
- DAG有向无循环图
- map与mapPartitions的区别
- treeReduce与reduce的区别
- foreach和foreachPartition区别
- 宽窄依赖
- groupByKey-aggregateByKey-reduceByKey
- 存储级别StoreLevel
- stage划分过程
- Checkpoint
- 广播变量
- 数据倾斜的现象、原因、后果
- Spark的runtime
- spark内存
- Spark中的OOM问题
- task之间的内存分配
- spark的内存管理机制
- spark的性能优化
- (1)参数优化
- (2)代码优化
- spark streaming
- 从kafka中读数据的两种方式
- kafka
- 消息队列
- Kafka架构
- 另外总结
- flume
- sqoop
- 并发
- 海量数据处理
- topN词语
- 不重复的整数
- HR
- 非技术问答
- 工业互联网
- python
- python语言特点
- 封装继承多态-python
- 类定义和方法-python
- 继承-python
- 重载
- 抽象类和接口类
- 基础语法
- 数字number
- 字符串(String)
- List(列表)
- 删除list里的重复元素
- Set(集合)
- Tuple(元组)
- Dictionary(字典)
- 类型转换
- 自省-反射-is 和 == 获知对象的类型
- 字典推导式
- 单下划线和双下划线
- *args and **kwargs
- 迭代器和生成器
- 新旧式
- python单例模式
- 作用域--Python
- 深拷贝与浅拷贝--Python
- 函数式编程-- Python
- 闭包--python
- 内存管理与垃圾回收机制
- 值传递还是引用传递
- 类方法、类实例方法、静态方法
- 类变量和实例变量
- 模块Python
- Java
- 面向对象和面向过程
- 抽象、封装、继承,多态
- 重载和重写的区别
- 访问控制符public,protected,private
- String和StringBuffer、StringBuilder的区别
- 抽象类和接口的区别
- ----重要对比-----
- Concurrenthashmap实现原理
- ArrayList和LinkedList区别及使用场景
- ArrayList和vector区别
- HashMap和HashTable区别
- HashTable实现原理
- HashMap实现原理
- Collection和Collections的区别
- 多线程
- 多线程的实现方式
- 如何保证线程安全
- synchronized如何使用
- synchronized和Lock的区别
- 多线程如何进行信息交互
- sleep和wait的区别
- 死锁
- 线程的基本状态
- 线程-程序-进程
- 异常处理
- Error、Exception区别
- Java 异常类层次
- 异常处理总结
- NIO,BIO,AIO
- 序列化与反序列化
- 装箱和拆箱
- volatile关键字
- == 与 equals
- final,static,this,super 关键字
- final 关键字
- static 关键字
- this 关键字
- super 关键字
- Java 和 C++的区别
- JVM JDK 和 JRE
- hashCode 与 equals
- Java 中 IO 流
- 继承-java
- JavaWeb
- JDBC访问数据库的基本步骤
- 数据库连接池的原理
- Truncate与delete的区别
- PreparedStatement相比Statement的好处
- C++
- C和C++的区别
- C++中指针和引用的区别
- 结构体struct和共同体union(联合)的区别
- #define和const的区别
- 重载overload-覆盖override-重写overwrite的区别
- 虚函数
- 堆和栈的区别
- 关键字static的作用
- STL中的vector的实现,是怎么扩容的
- C++中volatile
- 操作系统
- 调度算法
- --死锁
- 进程
- 进程状态
- 进程间的通信方式
- 进程与线程的区别
- 进程同步
- 上下文切换
- 进程调度
- 页面置换算法
- 临界资源
- 中断与系统调用
- 分页和分段区别
- 虚拟内存
- 测试
- 黑白单元集成系统
- 黑盒
- 测试用例设计
- 测试例子
- 为什么选择软件测试