C++后端开发常考知识点

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<algorithm>
#include<memory>
#include<assert.h>
#include<climits>
#include<mutex>
#include<stack>
#include<queue>
#include<string>
#include<map>
#include<unordered_map>
using namespace std;
memmove模拟实现
char* memmove(void* dest,void* src,int size) {
	assert(dest!=nullptr && src!=nullptr);
	char* str1 = (char*)dest;
	char* str2 = (char*)src;
	if (str2 + size > str1) {
		while (size && *(str2 + size)) {
			*(str1 + size) = *(str2 + size);
			size--;
		}
		return str1;
	}
	else {
		while (size&&*str2) {
			*str1++ = *str2++;
			--size;
		}
		return str2;
	}
}
单例模式
class Singleton {
public:
	static Singleton* GetpInstance() {
	
		if (ptr == nullptr) {
			_mutex.lock();
			if (ptr == nullptr) {
				ptr = new Singleton();
			}
			_mutex.unlock();
		}
		return ptr;
	}
private:
	static Singleton* ptr;
	static mutex _mutex;
};
Singleton* Singleton::ptr = nullptr;
mutex Singleton::_mutex;
//shared_ptr
template<class T>
class shared_ptr {
	//构造函数
	shared_ptr(T* ptr = nullptr):
		_ptr(ptr),
		_count(new int(1)),
		_mutex(new mutex) {
	}
	//析构函数
	~shared_ptr() {
		Release();
	}
	//拷贝构造函数
	shared_ptr(shared_ptr<T>& sp) : 
		_ptr(sp._ptr)
		,_mutex(sp._mutex)
		,_count(sp._count){
		
		AddCount();
	}
	//赋值运算符重载
	shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
		if (_ptr != sp._ptr) {
			//释放旧资源
			Release();
			_ptr = sp._ptr;
			_count = sp._count;
			_mutex = sp._mutex;
			AddCount();
		}
		return *this;
	}
	T& operator*() return *_ptr;
	T* operator->() return _ptr;
	//增加引用计数
	void AddCount() {
		mutex->lock();
		++(*_count);
		mutex->unlock();
	}
	void Release() {

		bool flag = false;
		//加锁
		_mutex->lock();
		if (--(*_count) == 0) {
			delete _count;
			delete _ptr;
			flag = true;
		}
		_mutex->unlock();
		if (flag) {
			delete _mutex;
		}
	}

private:
	T* _ptr;
	mutex* _mutex;
	int* _count;
};

//unique_ptr
template<class T>
class unique_ptr {

	unique_ptr(T* ptr = nullptr):_ptr(ptr) {
	}
	~unique_ptr() {
		if (_ptr) {
			delete _ptr;
		}
	}
private:
	//防止拷贝
	unique_ptr(const unique_ptr<T>&) = delete;
	unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
	T* _ptr;
};

//string类的模拟实现
class string {
	//移动构造函数
	string(string&& str) :_str(str._str) {
		str._str = nullptr;
	}
	//构造函数
	string(const char* str = "") {
		if (str == nullptr) {
			assert(false);
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str,str);
	}
	//拷贝构造函数
	string(const string& str):_str(new char[strlen(str._str) + 1]) {
		strcpy(_str,str._str);
	}
	string& operator=(const string& str) {
		
		if (this != &str) {
			char* ptr = new char[strlen(str._str) + 1];
			strcpy(ptr,str._str);
			delete _str;
			_str = ptr;
		}
		return *this;
	}
	~string() {
		if (_str) {
			delete _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};
//topK
//int main() {
//	//默认是大顶堆
//	vector<int> v = { 1,2,3,5,1,3,5 };
//	priority_queue<int,vector<int>,greater<int>> p(v.begin(),v.begin() + 2);
//	for (int i = 2; i < v.size(); ++i) {
//		p.push(v[i]);
//	}
//	while (!p.empty()) {
//		cout << p.top() << " ";
//		p.pop();
//	}
//	system("pause");
//	return 0;
//}

//快速排序
int Partion(vector<int>& v,int left,int right) {
	int tmp = v[left];
	int begin = left;
	int end = right;
	while (begin < end) {
		while (begin < end && v[end] >= tmp) {
			end--;
		}
		while (begin < end && v[begin] <= tmp) {
			begin++;
		}
		swap(v[begin],v[end]);
	}
	swap(v[begin],v[left]);
	return begin;
}
void Sort(vector<int>& v,int left,int right) {

	if (left >= right) {
		return;
	}
	int d = Partion(v,left,right);
	Sort(v,left,d - 1);
	Sort(v, d+1, right);
}
归并排序
void Merge(vector<int>& v,int left,int mid,int right) {
	int* tmp = new int[right - left + 1];
	int i = left;
	int j = mid + 1;
	int k = 0;
	while (i <= mid && j <= right) {
	
		if (v[i] > v[j]) {
			tmp[k++] = v[j++];
		}
		else {
			tmp[k++] = v[i++];
		}
	}
	while (i <= mid) {
		
		tmp[k++] = v[i++];
	}
	while (j <= right) {

		tmp[k++] = v[j++];
	}

	for (int i = 0; i < k; ++i) {
		v[left + i] = tmp[i];
	}
	free(tmp);
}
void MergeSort(vector<int>& v,int left,int right) {
	if (left >= right) return;
	int mid = left + (right - left) / 2;
	MergeSort(v,left,mid);
	MergeSort(v,mid+1,right);
	Merge(v,left,mid,right);

}
//----------------------------------------------------------------------
堆排序(逻辑上是完全二叉树)
//向下取整
void AdjustDown(vector<int>& v,int size,int index) {
	
	while (1) {
		//左孩子下标
		int leftIndex = 2 * index + 1;
		if (leftIndex >= size) {
			return;
		}
		//假设最小下标是左孩子
		int minIndex = leftIndex;

		int rightIndex = 2 * index + 2;
		if (rightIndex < size && v[rightIndex] < v[leftIndex]) {
			minIndex = rightIndex;
		}
		if (v[index] < v[minIndex]) {
			return;
		}
		swap(v[index],v[minIndex]);
		index = minIndex;
	}

}

//建堆
void CreateHeap(vector<int>& v) {
	for (int i = (v.size() - 2) / 2; i >= 0; --i) {
		AdjustDown(v,v.size(),i);
	}

}
//堆排序
void HeapSort(vector<int>& v) {
	CreateHeap(v);
	for (int i = 0; i < v.size(); ++i) {
		swap(v[0],v[v.size() - 1 - i]);
		AdjustDown(v,v.size() - i - 1,0);
	}
}
//--------------------------------------------------------------


//选择排序
void SelectSort(vector<int>& v) {
	for (int i = 0; i < v.size(); ++i) {
		int Max = INT_MIN;
		int index;
		for (int j = 0; j < v.size() - i; ++j) {
			if (Max < v[j]) {
				Max = v[j];
				index = j;
			}
		}
		swap(v[index], v[v.size() - 1 - i]);
	}
}
//冒泡排序
void BubbleSort(vector<int>& v) {
	for (int i = 0; i < v.size() - 1; ++i) {
		for (int j = 0; j < v.size() - 1 - i; ++j) {
			if (v[j] > v[j + 1]) {
				swap(v[j],v[j+1]);
			}
		}
	}
}
//希尔排序
void ShellSort(vector<int>& v) {

	int N = v.size();
	//设置步长
	for (int gap = N / 2; gap > 0; gap /= 2) {
		//插入排序
		for (int i = 0; i < N; ++i) {
			int tmp = v[i];
			int j;
			for (j = i - gap; j >= 0 && tmp < v[j]; j -= gap) {
				v[j + gap] = v[j];
			}
			v[j + gap] = tmp;
		}
	}
}
//插入排序
void InsertSort(vector<int>& v) {
	for (int i = 1; i < v.size(); ++i) {
		int tmp = v[i];
		int j;
		for (j = i - 1; j >= 0 && tmp < v[j]; --j) {
			v[j + 1] = v[j];
		}
		v[j + 1] = tmp;
	}

}

LRU算法
class LRU {

	LRU(size_t capacity) :_capacity(capacity) {}
	int get(int key) {
		auto e = _hashmap.find(key);
		if (e != _hashmap.end()) {
			//说明存在数据
			auto list = e->second;
			pair<int,int> kv= *list;
			_list.erase(list);
			//将数据插入到链表头部
			_list.push_front(kv);
			_hashmap[key] = _list.begin();
			return kv.second;
		}
		else {
			return -1;
		}
	}
	void put(int key,int value) {
		auto e = _hashmap.find(key);
		//没有存在的数据则插入
		if (e == _hashmap.end()) {
			//如果数据缓存已经达到上限,则删除最长未使用的
			if (_list.size() >= _capacity) {
				_hashmap.erase(_list.back().first);
				_list.pop_back();
			}
			//如果存在空间
			_list.push_front(make_pair(key,value));
			_hashmap[key] = _list.begin();
		}
		else {
			//说明存在数据,将数据移动到最前面
			auto list = e->second;
			pair<int, int> kv = *list;
			kv.second = value;

			_list.erase(list);
			_list.push_front(kv);
			_hashmap[key] = _list.begin();

		}

	}

private:
	unordered_map<int, list<pair<int,int>>::iterator> _hashmap;
	size_t _capacity;
	list<pair<int,int>> _list;
};

//二叉树的非递归前序遍历
//void Preorder(TreeNode* root) {
//
//	TreeNode* cur = root;
//	stack<TreeNode*> s;
//	while (cur != nullptr && !s.empty()) {
//		
//		while (cur!=nullptr) {
//			cout << cur->val;
//			s.push(cur);
//			cur = cur->left;
//		}
//		TreeNode* top = s.top();
//		s.pop();
//		cur = top->right;
//	}
//}
二叉树的非递归中序遍历
//void Vinorder(TreeNode* root) {
//	
//	stack<TreeNode*> s;
//	TreeNode* cur = root;
//	while (cur != nullptr && !s.empty()) {
//	
//		while (cur != nullptr) {
//			s.push(cur);
//			cur = cur->left;
//		}
//		TreeNode* top = s.top();
//		cout << top->val;
//		s.pop();
//		cur = cur->right;
//	}
//}
//二叉树的后序非递归
//void PostOrder(TreeNode* root) {
//	stack<TreeNode*> s;
//	TreeNode* cur = root;
//	Node* last = nullptr;
//	while (cur != nullptr || !s.empty()) {
//		while (cur != nullptr) {
//			s.push(cur);
//			cur = cur->left;
//		}
//		TreeNode* top = s.top();
//		if (top->right == nullptr) {
//			cout << cur->val << " ";
//			s.pop();
//			last = top;
//		}
//		else if (top->right == last) {
//			cout << cur->val << " ";
//			s.pop();
//			last = top;
//		}
//		else {
//			cur = cur->right;
//		}
//	}
//}

01背包问题
int main() {
	int n;
	cin >> n;
	vector<int> score(n);
	vector<int> time(n);
	for (int i = 0; i < n; ++i) {
		cin >> score[i];
	}
	for (int i = 0; i < n; ++i) {
		cin >> time[i];
	}
	int m;
	cin >> m;
	vector<int> dp(m + 1, 0);
	for (int i = 0; i < n; ++i) {
		//必须从大到小,因为必须依赖上一行的值,负责会重复计算
		for (int j = m; j > 0; --j) {
			//如果当前物品装不下,则不要装
			if (time[i] > j) {
				continue;
			}
			else {
				//可以装得下;
				dp[j] = max(dp[j - time[i]] + score[i], dp[j]);
			}
		}
	}
	cout << dp[m] << endl;
	return 0;
}
使用条件变量按顺序打印数字

pthread_cond_wait(&cond,&mutex);先将该线程加入到等待队列当中,然后解锁这步必须是原子性操作,
防止唤醒丢失,然后加锁
线程A
void ThreadA() {
	while (1) {
		pthread_mutex_lock(&mutex);
		if (num%2 == 0) {
			pthread_cond_wait(&cond,&mutex);
		}
		cout << num;
		++num;
		pthread_cond_signal(&cond);
		pthread_cond_unlock(&mutex);
	}
}
线程B
void ThreadB() {
	while (1) {
		pthread_mutex_lock(&mutex);
		if (num % 2 == 1) {
			pthread_cond_wait(&cond, &mutex);
		}
		cout << num;
		++num;
		pthread_cond_signal(&cond);
		pthread_cond_unlock(&mutex);
	}
}
生产者消费者模型
class BlockQueue
{
public:
	BlockQueue(int cap = 10) :_capacity(cap) {
		pthread_mutex_init(&_mutex, NULL);
		pthread_cond_init(&_cond_productor, NULL);
		pthread_cond_init(&_cond_consumer, NULL);
	}
	~BlockQueue() {
		pthread_mutex_destroy(&_mutex);
		pthread_cond_destroy(&_cond_productor);
		pthread_cond_destroy(&_cond_consumer);
	}
	bool QueuePush(int data) {
		QueueLock();        //加锁
		while (QueueIsFull()) { //队列满了
			ProductorWait();    //生产者休眠
		}
		_queue.push(data);
		ConsumerWakeUp();       //唤醒消费者
		QueueUnLock();      //解锁
		return true;
	}
	bool QueuePop(int *data) {
		QueueLock();        //加锁
		while (QueueIsEmpty()) { //队列空
			ConsumerWait();    //消费者休眠
		}
		*data = _queue.front();
		_queue.pop();
		ProductorWakeUp();      //唤醒生产者
		QueueUnLock();      //解锁
		return true;
	}
private:
	void QueueLock() {
		pthread_mutex_lock(&_mutex);
	}
	void QueueUnLock() {
		pthread_mutex_unlock(&_mutex);
	}
	void ProductorWait() {
		pthread_cond_wait(&_cond_productor, &_mutex);
	}
	void ProductorWakeUp() {
		pthread_cond_signal(&_cond_productor);
	}
	void ConsumerWait() {
		pthread_cond_wait(&_cond_consumer, &_mutex);
	}
	void ConsumerWakeUp() {
		pthread_cond_signal(&_cond_consumer);
	}
	bool QueueIsFull() {
		return (_queue.size() == _capacity);
	}
	bool QueueIsEmpty() {
		return _queue.empty();
	}
private:
	std::queue<int> _queue;
	int _capacity;
	pthread_mutex_t _mutex;
	pthread_cond_t  _cond_productor;
	pthread_cond_t  _cond_consumer;
};

void* thr_consumer(void *arg)
{
	BlockQueue *q = (BlockQueue*)arg;
	while (1) {
		int  data;
		q->QueuePop(&data);
		std::cout << "consumer  get data:" << data << std::endl;
	}
	return NULL;
}
void* thr_productor(void *arg)
{
	int i = 0;
	BlockQueue *q = (BlockQueue*)arg;
	while (1) {
		std::cout << "productor put data:" << i << std::endl;
		q->QueuePush(i++);
	}
	return NULL;
}
int main(int argc, char *argv[])
{
	pthread_t ctid[4], ptid[4];
	BlockQueue q;
	int ret;

	for (int i = 0; i < 4; i++) {
		ret = pthread_create(&ctid[i], NULL, thr_consumer, (void*)&q);
		if (ret != 0) {
			std::cout << "pthread create error\n";
		}
	}
	for (int i = 0; i < 4; i++) {
		ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&q);
		if (ret != 0) {
			std::cout << "pthread create error\n";
		}
	}
	for (int i = 0; i < 4; i++) {
		pthread_join(ctid[i], NULL);
	}
	for (int i = 0; i < 4; i++) {
		pthread_join(ptid[i], NULL);
	}
	return 0;
}



C\C++
const的作用:
1,在C语言当中const修饰的变量是一个常变量,实际分配内存空间,并且默认具有外部连接属性
2,在C++当中const修饰的变量是一个真正的常量,存在与符号表当中,
只有对变量取地址的时候才会重新分配一块内存,但是原有的常量并没有发生改变
3,在C++当中const是默认为内部链接属性
4,const对象可以调用const成员函数,但不能调用非const成员函数
5,const成员函数不可以调用非const成员函数,但是非const成员函数可以调用const成员函数
因为const class* this 不可以转化为 class* this
6,const修饰*前面表示指针所指内容不能改变,*后面表示指针本身不可以改变

explicit: -----禁止单参数构造函数的隐式类型转换
static:
1,static修饰局部变量改变对象的生命周期,如果未初始化则在未初始化数据段,已经初始化就在初始化数据段
2,static修饰全局变量和函数,改变变量和函数的链接属性使在当前文件可见
3,在C++的类当中静态成员所有类共享,不属于某个具体的实例
4,静态成员变量必须在类外定义,不加static关键字
5,静态成员函数没有隐藏的this指针,不能访问任何非静态成员
6,静态成员函数也具有类的三种访问权限,也可以具有返回值以及const修饰符等参数

友元函数
1,对于operator << 由于输出流对象和this指针在抢占第一参数位置,this指针默认为第一个参数,cout需要是第一个参数,才能正常使用
2,友元函数可以访问类的私有成员,但不是类的成员函数
3,友元函数不能用const修饰
4,友元函数可以在类的任何地方声明,不受类的访问限定符限制
5,一个函数可以是多个类的友元函数
6,友元函数和普通函数调用原理相同
友元类:中的的所有成员函数都可以是另一个类当中的友元函数,都可以访问类的非共有成员
内部类:内部类是外部类的友元类,内部类是一个独立的类,它不属于外部类,外部类对于内部类没有任何优越的访问权限

new和malloc的区别
1,malloc/free是库函数,new/delete是操作符
2,malloc申请的空间不会初始化,new可以初始化
3,malloc申请空间需要手动计算空间大小并传递,new只需在其后跟上空间类型即可
4,malloc的返回值是void* ,在使用的时候必须强转,new不需要,因为new后面跟的是空间类型的指针
5,malloc内存申请失败会返回nullptr,因此使用的时候必须判空,new不需要,new需要捕获异常
6,malloc不会自动完成对象空间的初始化,而new在释放资源的时候会调用析构函数
7,new/delete比malloc/free效率低,因为new/delete在底层封装了malloc/free

指针和数组的区别
1, sizeof不一样
2,数组会隐身类型转换
3,数组名是一个右值,不可以修改,指针可以

指针和引用的区别
1,指针是一个实体,而引用只是对象的别名
2,引用使用时无需解引用,而指针需要解引用
3,引用定义的时候初始化一次,之后不可变,指针可变
4,引用不可以为空,指针可以
5,sizeof值不同
6,指针和++自增运算意义不一样
7,指针在符号表当中存的时指针本身的地址,引用在符号表当中存的是对象的地址

NULL和nullptr的区别
1,在C语言当中NULL----(void*)0;
2,因为C++不支持void* 类型转换,所以C++中的NULL就是0;
3,为了解决函数重载问题,C++11引用了nullptr函数指针可以隐式类型转换为任何指针

特化的前提是需要有类模板
模板特化:类模板特化和函数模板特化
类模板特化:
     全特化:全特化即是将模板参数类表中所有的参数都确定化
     偏特化:
           部分特化:将模板参数类表中的一部分参数特化
           参数限制:例如T& T*

模板为什么不支持分离编译?
 1,多个.cpp文件是单独编译的,在编译阶段编译器没有看到对模板函数的实例化,因此就不会生成具体的加法函数
 2,因此main.cpp在链接的时候的时候会报错,无法解析的外部符号
 3,将声明和定义放在一个头文件当中

复杂的菱形继承
在菱形继承当中为了解决数据的二义性和冗余性,故可以使用虚拟继承
虚拟继承当中公共数据在对象模型的最下面
使两个虚基表指针,获取到共有数据的偏移量

继承与组合
1,public继承是一个is a的关系,也就是说每一个派生类对象就是一个基类对象。
2,组合是一种has a的关系。
3,继承允许你根据基类的实现来定义派生类的实现,这种通过派生类的复用称为白箱复用
基类的内部细节对子类可见,继承一定程度上破坏了基类的封装,基类的改变对派生类的影响很大
基类与派生类之间依赖关系很强,耦合度很高
4,对象的组合是另一种复用选择,对象组合要求组合对象定义良好的接口,这种称之为黑箱复用
因为对象内部细节不可见,组合之间没有很强的依赖关系,耦合度低。所以优先使用对象组合有助于保持每个类的封装
5,另外要实现多态就必须用继承

多态的原理
1,必须使用基类的指针或者是基类的引用调用虚函数
2,被调用的函数必须是虚函数,且派生类必须重写虚函数
3,协变是一个例外,基类的虚函数返回的是基类对象的指针或者引用,派生类的虚函数返回的是派生类对象的指针或者引用
4,析构函数统一当作destructor处理
5,对虚函数使用final可以防止被继承,override用来检查是否重写了虚函数
6,虚表可以被继承,如果子类重写了父类的虚函数,虚函数表中虚函数会被覆盖
7,虚表指针存在对象模型之中,在vs下存在在代码段
8,当父类的指针指向子类的对象的时候,会在子类的虚表当中找到对应的虚函数
9,当父类的指针指向父类的对象的时候,会在父类的虚表当中找到对应的虚函数
10,静态绑定在编译期间就确定了函数的行为,例如函数重载和模板实例化
11,动态绑定在运行期间,指在运行期间识别具体的类型,确定程序的具体行为
12,在多继承派生类未重写的虚函数放在第一个继承基类的虚函数表之中
13, ?类型识别问题--对于多态父类的指针可以指向子类的对象也可以父类对象,根据运行时类型检查来区分,在虚表的头部会有类型信息

迭代器失效问题
1,vector:删除数据就会导致后面元素的迭代器失效,增加元素有可能导致整个元素的迭代器失效,vector扩容问题
2,list:删除迭代器只会使得当前元素的迭代器失效
3,deque:删除中间元素会导致整个元素的迭代器失效
4,set和map:删除元素只会时当前元素的迭代器失效,其他不影响;

异常的优缺点
1,相比较错误码的方式,异常可以清晰的定位bug,
2, 异常会打乱程序的执行流
3,//异常会造成的内存泄露,死锁等问题
4,异常应该规范化,抛出异常的类型继承一个基类
5,函数抛什么异常,应该规范化

循环引用分析
dynamic_cast:如果基类的指针指向的父类对象,在转换为子类对象指针的时候会失败,返回0,引用则触发异常

C++11
移动语义:将对象中的资源移动到另一个对象中的方式
右值:纯右值和将亡值(生命周期即将结束的对象)值返回的临时对象
完美转发:forward

lambda表达式:
[=]传值的方式 [&]传引用的方式 [&val]


LINUX
信号捕捉相关
1,信号的种类:1-31为非可靠信号;34 - 62可靠信号
2,在PCB当中对于每一个信号都设置两个标志位:分别为阻塞标志位,和未决标志位,和对应的函数指针来表明信号的处理动作,用sigset_t来表示
3,当信号到来之后如果是可靠信号那么相应的标志位置1,并且在将该信号添加到队列里,如果是非可靠信号那么给相应的标志位置1,如果是非可靠信号与原来已经存在那么就不会将该信号添加到对列里面
这也是可靠信号和非可靠信号的区别
4,每一种信号自定义设置信号处理函数,例如子进程退出会给父进程发送SIGCHLD
5,在执行主控流程的某条指令时因为系统中断,异常或系统调用进入内核
6,内核处理完异常准备返回用户模式前先处理当前进程中可以递送的信号
7,如果信号处理时自定义的信号则返回到用户模式进行信号处理,而不是进入主控流程
8,处理完用户自定义的信号之后执行特殊的系统调用再一次返回到内核
9,返回用户模式从主控流程中上次被中断的地方继续执行
10,waitpid(-1,NULL,WNOHANG) > 0

文件描述符和文件流指针
1,库函数的操作句柄时文件流指针
2,系统调用接口的句柄是文件描述符
3,文件流指针当中包含了文件描述符
4,文件描述符遵循最小未使用原则

文件的读取和存储过程
1,存储:innode_map在innode区域获取空闲的innode的节点,并且通过data_bitmap获取空闲的数据块
在innode的节点记录着文件信息以及数据块的位置
2,读取:通过文件名获取到innode的节点,通过innode的节点号在innode的区域找到innode的节点,通过innode节点中获取数据块地址信息
在指定的数据块中读取数据

软硬连接
1,删除源文件软链接将失效,硬链接无影响
2,软连接可以跨分区建立,硬链接无影响
3,软链接可以对目录创建,硬链接不可以会造成歧义和循环

动态库和静态库
1,gcc  -fPIC -c  test.c  -o test.o || gcc  --share  test.o - o  libtest.so
2,gcc  -c test.c -o test.o || ar -cr  libtest.a  test.o
3,静态库直接指定 -L选项指向便可以链接,不能使用-staic选项,标准库是动态库会出错
3,动态库:
         1,设置环境变量:LD_LIBREARY_PATH
         2,或者是放在默认路径下 /user/lib /lib64下面
         3,或者可以在配置相关文件

进程和线程的区别
在linux中进程是正在运行当中的程序,用PCB描述,在PCB中有进程标识符,进程状态(运行,僵尸,停止,休眠,阻塞)等,优先级
程序计数器,内存指针,上下文数据,io状态信息,记账信息),进程启动时会创建虚拟地址空间,所以进程时系统分配资源的基本单位
线程是一个进程内部的控制序列,一个进程至少有一个线程,线程在进程的虚拟地址空间上面运行,在Linux中线程的PCB比进程的PCB更加轻量化
进程将资源合理的分配给每一个执行流,所以说线程时系统调度的基本单位,创建一个线程比创建一个进程代价要小很多,在计算IO密集型程序时
为了提高性能,多线程可以同时等待多个IO操作,但是线程因为线程安全问题,在编程和调试的时候要困难的多

线程间通信方式
互斥量 条件变量 读写锁 信号量 全局变量

进程间通信方式
1,管道:(半双工,只能一端读另一端写)64k
匿名管道:只能用于具有亲缘关系的进程间通信
命名管道:任意进程间通信
2,共享内存:
将同一段物理内存映射到各自的虚拟地址空间,不存在数据拷贝,直接对内存操作
3,消息队列
进程之间传递有类型的数据块
4,信号量
实现进程与线程之间的同步与互斥
5,socket
网络通信

死锁
1,互斥条件:一个资源每次只能被一个执行流使用
2,请求与保持:一个执行流因请求资源而阻塞时,对已获得资源保持不放
3,不可剥夺条件:一个执行流获得资源,在未使用之前不能强行剥夺
4,循环等待条件:若干执行流之间形成头尾相接循环等待资源的关系

死锁避免
1,破环死锁的必要条件
2,加锁顺序要一致(锁按顺序分配,那么后面的线程就不会去访问前面的锁)
3,一次性获取所需要的锁,但会降低并发程度
4,资源一次性分配
5,CAS自旋锁:

银行家算法
安全状态:是指系统按某种进程的顺序《p1,p2,p3...pn》来为每一个进程分配所需的资源,直至满足每一个进程的最大需求量,使得每一个进程可以顺利的完成
数据结构:可利用资源向量Available:一个含有m个元素数组,每一个元素代表一类可利用资源的最大数目
          最大需求矩阵Max:一个n*m的矩阵,它定义了系统当中n个进程当中每一个进程对m类资源的最大需求量
          分配矩阵Allcation:一个n*m的矩阵它定义了系统中每一类资源分配给进程的资源数
          需求矩阵Need:一个n*m的矩阵表示每一进程尚需要的资源数
两个向量:
         Work:表示系统可提供给进程继续运行所需要的资源数
         Finish:表示系统是否有足够的资源分配给进程使用,开始时,Finish[i] = false;当具有足够的资源时Finish[i] = true;
关系:Need[i,j] = Max[i,j] - Allcation[i,j];
题型:
1,判断某一时刻的安全性
2,判断某资源请求是否能满足
3,求安全序列
过程:
1,安全性算法:
       (1)从进程集合当中找到一个能满足一下条件的进程
            1,Finish[i] = false;
            2, Need[i,j] < Work;(如果满足条件)执行步骤2,否则执行步骤3
       (2)当进程Pi获取到资源之后,可以顺利执行至完成任务,并且释放资源
            1,Work = Work + Allcation
            2, Finish[i] = true;
            3, 返回步骤(1);
       (3)如果所有的Finish[i] = true 都满足,则系统处于安全状态,否则处于不安全状态
银行家算法:
设Requesti是进程Pi的请求向量,如果Requesti(1,0,2),表示进程Pi需要1个R1类型的资源,0个R2类型的资源和2个R3类型的资源。
当Pi发出资源请求后,系统按下述步骤进行检查:
(1)如果Requesti < Needi,便转向步骤2;否则认为出错,因为它所需要的资源数已超过它所宣布的最大值
(2)如果Requesti < Available,便转向步骤(3);否则,表示尚无足够资源,Pi须等待
(3)系统试探着把资源分配给进程Pi,并修改下面数据结构中的数值;
       Available:=Available-Requesti;
       Allocationi:=Allocationi+Requesti;
       Needi:=Needi-Requesti;
(4)系统执行安全性算法,检查此次资源分配后,系统是否处于安全状态。
安全序列:按照安全性算法即可求出安全序列,安全序列可能不唯一。


乐观锁|悲观锁|读写锁


计算机网络
http协议
常见头部:Content-type(数据类型(text/html)) Content-Length(Body的长度) Host(所请求的主机以及资源) 
User-Agent(浏览器和操作系统信息) referer(当前页面从哪里跳转过来) location(搭配状态码使用,告诉客户端应该去哪里访问)
Cookie(用于存储少量信息,保存会话id) 

http版本差异
1,http1.0支持get post head 默认是短链接,如果需要长链接需要指明connection:Keep-Alive
2,http1.1支持PUT(传输文件) Delete(删除文件) TRACE(追踪路径) 默认是长连接,如果需要关闭需要 Connection:close
http支持分块传输,需要引入Range头域
3.http2.0采用了(首部压缩),提高了传输性能;流量控制;http1.1在浏览器客户端在同一域名下的请求数量有限制,超过限制数会阻塞
而多路复用优化了这一性能,http消息会被分解成独立的帧,在不破坏消息本身的情况下交错发送出去,在另一端组装起来
客户端可以指明请求优先级从而提高用户体验;服务器推送:服务端根据客户端的请求发送相关的内容

http常见状态码
100(k客户端继续发送) 101(服务端根据客户端的请求切换协议)200(请求成功 搭配get post)206(部分成功 搭配range)
301(永久重定向) 302(临时重定向) 401(要求用户的身份认证) 403(服务器理解客户端的请求但拒绝执行)404(客户端请求的资源无法找到)
408(请求超时) 414(URL过长) 500(服务器内部错误)502(代理服务器从远程服务器获取到了一个无效的响应)504(代理服务器错误)
505(服务器不支持当前http协议版本)

https四次握手
1、客户端请求建立SSL链接,并向服务端发送一个随机数–Client random和客户端支持的加密方法,比如RSA公钥加密,此时是明文传输。
2,服务端回复一种客户端支持的加密方法、一个随机数–Server random、授信的服务器证书和非对称加密的公钥。
3,客户端收到服务端的回复后利用服务端的公钥,加上新的随机数–Premaster secret 通过服务端下发的公钥及加密方法进行加密,发送给服务器
4,服务端收到客户端的回复,利用已知的加解密方式进行解密,同时利用Client random、Server random和Premaster secret通过一定的算法生成HTTP链接数据传输的对称加密key – session key。

tcp的三次握手和四次挥手
三次握手
1,客户端发送SYN报文端,处于SYN_SEND状态,服务端收到SYN后将该链接添加到SYN半链接队列当中,服务端发送SYN+ACK此时服务端处于SYN_RECV状态
客户端收到确认应答之后回复ACK此时客户端处于ESTABLISH状态,服务端收到ACK后处于ESTABLISH状态,将此连接添加到全连接队列当中等待ACCEPT接受
2,三次握手的目的是为了防止已失效的SYN链接,占用系统资源。
3,三次握手过程当中会根据不同的数据链路确认MTU以及滑动窗口和起始序列号

四次挥手
4,客户端调用Close,发送FIN报文端此时处于FIN_WAIT_1状态,服务端收到客户端的FIN后回复ACK此时服务端处于close_WAIT状态,当客户端收到服务端的ACK之后处于FIN_WAIT_2状态
因为TCP是全双工所以服务端仍然可以
继续发送数据,待服务端的数据发送完成之后,然后发送FIN报文端表明服务端已经没有数据要发送此时服务端处于LAST_ACK状态,当客户端收到服务端的FIN报文端的之后处于TIME_WAIT状态并回复ACK
在等待2MSL后关闭链接

为什么要等待2MSL?
1,保证TCP全双工链接可以可靠关闭
2,保证这一次的数据段消失在网络当中,否则有可能影响下一次链接
3,等待2MSL是人为约定的值,如果有特殊出现,那也没有办法,仁至义尽

如何解决服务端出现大量的TIME_WAIT状态以及Close_WAIT状态
1,使用setsockopt()将socket描述符选项SO_RUSEADDR为1,实现端口复用
2,出现close_wait一般是指服务没有检测到客户端已经关闭链接,主动关闭连接即可
3,通过服务端的read的返回值为0表示对端已经关闭连接此时需要服务进行确认

TCP的选项参数以及校验和
1,时间戳和窗口扩大因子
2,tcp首部连同数据以16位为单位二进制反码求和放在校验和字段
3,在接收端以16位为单位二进制反码求和与校验和字段相加判断是否等于1

TCP的可靠传输
1,确认应答机制:每一个ACK都带有确认序列号(背后的原理是客户端每发送一个数据段都会启用一个计时器(Linux为500ms的整数倍))
2,滑动窗口机制:如果每发送一个数据都确认ACK的话会影响效率,所以TCP引入了滑动窗口机制,无需等待确认应答可以继续发送数据的最大值
操作系统内核为了维护这个滑动窗口需要开辟发送缓冲区,来记录哪些数据没有应答,只有应答之后的数据才能从缓冲区删除掉
3,快速重传机制:如果客户端连续收到三个重复的ACK那么客户端就会重传对应的数据
4,流量控制:在数据交互过程当中接收端将自己可以接受的缓冲区放到TCP窗口字段,通过ACK通知发送端,进而控制发送速度
如果接受去窗口满了就会将窗口置为0,这是发送方不在发送数据,但是需要定期发送窗口探测包,使接受端告诉发送端窗口大小(使用坚持计时器)
5,拥塞控制:为了防止网络拥堵,引入了拥塞窗口,开始时是1个报文段大小,每收到一个ACK拥塞窗口呈指数级增长,当达到窗口阈值(窗口大小)呈线性增长
如果出现丢包等问题,拥塞窗口置1,阈值减为一半
延迟应答:目的为了提高窗口大小,提高网络吞吐量
捎带应答:让ACK搭上顺风车,提高传输效率
面向字节流:会带来粘包题;解决方案:对于定长的包,每次按照固定大小读取即可,对于变长的包添加特殊符号;

如何使得UDP可靠传输
1,引入序列号
2,引入确认应答,确保对端收到数据
3,引入超时重传
4,。。。。。。。。。。

IP分片原理
1,对于TCP而言会在传输层自动分段,分段的大小与MTU有关系
2,对于UDP会在网络层自动分片
3,由于IP的不可靠性质,如果TCP在网络层自动分片的话会提高数据报丢失的概率,对于UDP需要在应用组织好数据再发送,尽可能减小丢失的可能性

TCPlisten()的第二个参数
同一时刻最大并发连接数;

高级IO
1, 阻塞IO:在内核数据准备好之前,系统调用会一直等待,所有的套接字默认都是阻塞方式:如果要修改使用(fcntl()和)
2, 非阻塞IO:在内核还没有将数据准备好之前,系统调用会直接返回,并且返回错误码(较大的CPU消耗)
3,信号驱动IO:内核数据准备好以后,使用信号通知程序进行IO操作
4,异步IO:在内核拷贝完数据之后,进而通知程序;
5,IO多路转接:核心在于IO多路转接可以同时等待多个文件描述符就绪状态

Select
1,将描述符添加到不同的集合当中,写集合,读集合,异常集合
2,对于读事件来说,如果缓冲区的数据大于低水位标记,触发事件,
对于写事件来说,缓冲区空闲位置大于低水位标记触发事件,对于异常事件来说,这个和TCP中的紧急指针有关系(不做讨论)
3,可监控的文件描述符和fd_set有关,最大4096
4,将描述符加入select的同时需要提前添加到数组里面,用于select返回后进行对比
5,select会将无事件发生的描述清空

select缺点
1,每次调用select需要手动设置描述符集合,从接口的使用角度来说很不方便
2,每次调用select需要将描述符从内核态拷贝到用户态
3,每次调用select都需要在内核遍历传进来的描述符
4,select支持的描述符太少

poll
基本和select一样,只不过采用了事件结构列表的形式

epoll---Linux2.6之下最好的IO就绪通知方法
1,当我们调用epoll_create方法时,linux内核会创建一个结构体,这个结构体当中有红黑树的根节点(存储着我们需要监听的事件)
以及双向链表(存储着返回给用户满足条件的事件)
2,每一个epoll对象都有独立的结构体,用于存放通过epoll_ctl方法向epoll对象添加进来的事件,这些事件都会挂载到红黑树当中,重复事件会被识别出来;
3,所有的epoll中的事件都会与网卡建立回调关系,当相应事件发生时就会回调这个方法,将发生的事件添加到双向链表当中
4,内核只需要拷贝少量的就是事件,用户不用遍历直接对就绪描述符进行操作

epoll的优点
1,数据拷贝轻量
2,采用事件回调机制
3,直接访问就绪队列就知道哪些文件描述符就绪
4,没有数量限制
5,epoll在有多个描述符但同一时间只有少量的描述符就绪
6,与select相比epoll不支持跨平台

epoll的工作方式
1,水平触发(LT)和边沿触发(ET)
2,LT:对于读事件来说如果缓冲区有数据那么每次都会触发事件
3,ET:对于读事件来说如果第一次没有将缓冲区的数据读完,那么下次遗留在缓冲区的数据就不会触发事件
所以在工程实践上需要将文件描述符设置为非阻塞,需要轮询处理缓冲区
select和poll其实也是工作在LT模式下. epoll既可以支持LT, 也可以支持ET

epoll的惊群问题
1,同时有多个线程等待epoll_wait,但是accept只能由一个线程成功处理accept事件,其他线程都将失败
而且错误码为EAGAIN;
2,让一个线程等待,采用负载均衡

MySQL(B+树存储结构)
MyISAM和lnnoDB的区别
1,事务:MyISAM不支持事务,而lnnoDB支持事务
2,MyISAM保存表的行数,而lnnoDB不保存
3,MyIASM支持全文索引而lnnoDB不支持
4,MyIASM只支持表锁,lnnoDB支持表锁,行锁可以大幅度提高多用户并发操作,但是lnnoDB的行锁只有where主键时才有效,否则仍然会锁定全表
5,MyISAM是非聚集索引而InnoDB是聚集索引

事务的隔离级别
1,读未提交:事务A修改了数据,而事务B读到了事务A修改后的数据,但是事务A回滚就会造成脏读
2,读已提交:写数据的时候加锁,那么就可以保证读的数据时提交之后的数据,不会出现脏读,但是会造成不可重复读
在两次读数据过程如果有中间有事务修改了数据;
3,可重复读:为了解决不可重复读,可以读的时候加锁并持有;就可以避免不可重复读,这也是MySQL的默认隔离等级
4,可串行化:幻读是因为并发事务插入和删除数据导致的,为了解决幻读问题,可以加范围锁,在当前操作范围内不可增加或者删除数据;

视图
1,使用视图可以定制用户数据,聚焦特定的数据
2,简化数据操作
3,视图可以提高安全性,必须具有足够的访问权限 

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值