static的做用html
static修饰变量只能在本范围内可见(由external变为internal,做用域和连接属性并无改变):修饰全局变量只能在本cpp文件中可见,修饰局部变量只能在该代码块内可见。修饰类的静态成员在类的对象中共享这一份数据。ios
其实就是一个类,当销毁指向的内存时,能够不用手动free内存,它会自动释放内存空间。c++
auto_ptr面试
unique_ptr数据库
shared_ptrwindows
shared_ptr实现共享式拥有概念。多个智能指针能够指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就能够看出了资源能够被多个指针共享,它使用计数机制来代表资源被几个指针共享。数组
weak_ptr服务器
weak_ptr是用来解决shared_ptr相互引用时的死锁问题,若是说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能降低为0,资源永远不会释放。它是对对象的一种弱引用,不会增长对象的引用计数,和shared_ptr之间能够相互转化,shared_ptr能够直接赋值给它,它能够经过调用lock函数来得到shared_ptr。多线程
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。闭包
C++ 的全局对象的构造函数会在 main 函数以前先运行,其实在 c 语言里面很早就有啦,在 gcc 中可使用 attribute 关键字指定以下(在编译器编译的时候就绝决定了)
它们的底层都是用红黑树实现的,关于红黑树和avl树的区别(首先红黑树是不符合AVL树的平衡条件的,即每一个节点的左子树和右子树的高度最多差1的二叉查找树。可是提出了为节点增长颜色,红黑是用非严格的平衡来换取增删节点时候旋转次数的下降,任何不平衡都会在三次旋转以内解决,而AVL是严格平衡树,所以在增长或者删除节点的时候,根据不一样状况,旋转的次数比红黑树要多。因此红黑树的插入效率更高!!!
extern void *memset(void *buffer, int c, int count) ;
+ buffer:指针或者数组 + c:赋给buffer的值 + count:buffer的长度
通常用来给一段内存空间所有设置为某个字符。
管道、系统IPC(信号、信号量、共享内存、消息队列)、套接字socket
进程和线程共享地址空间,进程的堆共享给线程,可是每一个线程有本身独立的栈
互斥量、信号量、临界区
每一个进程的地址空间是独立的,位于一个进程的普通内存区域中的对象是没法被其它进程所访问的,能知足这一要求的内存区域是共享内存,于是同步对象要在进程的共享内存区域内建立。同步对象还能够放在文件中。同步对象能够比建立它的进程具备更长的生命周期。
1. std::lock_guard 2. std::unique_lock 3. std::condition_variable
#include <iostream> #include <string> #include <thread> #include <vector> #include <mutex> using std::thread; using std::vector; using std::cout; using std::endl; using std::mutex; class Incrementer { private: int counter; mutex m; public: Incrementer() : counter{0} { }; void operator()() { for(int i = 0; i < 100000; i++) { this->m.lock(); this->counter++; this->m.unlock(); } } int getCounter() const { return this->counter; } }; int main() { // Create the threads which will each do some counting vector<thread> threads; Incrementer counter; threads.push_back(thread(std::ref(counter))); threads.push_back(thread(std::ref(counter))); threads.push_back(thread(std::ref(counter))); for(auto &t : threads) { t.join(); } cout << counter.getCounter() << endl; return 0; }
运行结果
修改其中代码
for(int i = 0; i < 100000; i++) { this->m.lock(); try { this->counter++; this->m.unlock(); } catch(...) { this->m.unlock(); throw; } }
#include <iostream> #include <mutex> #include <thread> #include <condition_variable> std::mutex g_mutex; // 用到的全局锁 std::condition_variable g_cond; // 用到的条件变量 int g_i = 0; bool g_running = true; void ThreadFunc(int n) { // 线程执行函数 for (int i = 0; i < n; ++i) { { std::lock_guard<std::mutex> lock(g_mutex); // 加锁,离开{}做用域后锁释放 ++g_i; std::cout << "plus g_i by func thread " << std::this_thread::get_id() << std::endl; } } std::unique_lock<std::mutex> lock(g_mutex); // 加锁 while (g_running) { std::cout << "wait for exit" << std::endl; g_cond.wait(lock); // wait调用后,会先释放锁,以后进入等待状态;当其它进程调用通知激活后,会再次加锁 } std::cout << "func thread exit" << std::endl; } int main() { int n = 100; std::thread t1(ThreadFunc, n); // 建立t1线程(func thread),t1会执行`ThreadFunc`中的指令 for (int i = 0; i < n; ++i) { { std::lock_guard<std::mutex> lock(g_mutex); ++g_i; std::cout << "plus g_i by main thread " << std::this_thread::get_id() << std::endl; } } { std::lock_guard<std::mutex> lock(g_mutex); g_running = false; g_cond.notify_one(); // 通知其它线程 } t1.join(); // 等待线程t1结束 std::cout << "g_i = " << g_i << std::endl; }
首先,这在一个局部做用域内, std::lock_guard 在构造时,会调用 g_mutex->lock() 方法;
局部做用域代码结束后, std:;lock_guard 的析构函数会被调用,函数中会调用 g_mutex->unlock() 方法。
当线程调用 g_cond.wait(lock) 前要先手动调用 lock->lock() ,这里是经过 std::unique_lock 的构造方法实现的;
当线程调用 g_cond.wait(lock) 进入等待后,会调用 lock->unlock() 方法,因此这也是前面构造lock时使用了 std::unique_lock ;
通知使用的 g_cond.notify_one() ,这个能够通知一个线程,另外还有 g_cond.notify_all() 用于通知全部线程;
线程收到通知的代码放在一个while循环中,这是为了防止APUE中提到的虚假通知。
从 实现原理上来说,Mutex属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要经过pthread_mutex_lock操做去获得一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就能够运行其余的任务(例如另外一个线程C)而没必要进行忙等待。而Spin lock则否则,它属于busy-waiting类型的锁,若是线程A是使用pthread_spin_lock操做去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到获得这个锁为止。因此,自旋锁通常用用多核的服务器。
int num = 0; spin_mutex sm; void thread_proc() { for(int i = 0; i < 100000; ++i) { sm.lock(); ++num; sm.unlock(); } } int main() { std::thread td1(thread_proc), td2(thread_proc); td1.join(); td2.join(); std::cout << num << std::endl; return 0; }
windows系统中临界区(Critical Section)、事件对象(Event)
nullptr
类型推导 auto(不能用于推导数组类型,不能用于函数传参) 和decltype关键字
有的时候咱们只须要计算表达式得出的类型,不须要返回值
auto x = 1; auto y = 2; decltype(x+y) z;
拖尾返回类型、auto 与 decltype 配合,利用 auto 关键字将返回类型后置:
template<typename T, typename U> auto add(T x, U y) -> decltype(x+y) { return x+y; }
初始化列表
struct A { int a; float b; }; struct B { B(int _a, float _b): a(_a), b(_b) {} private: int a; float b; }; A a {1, 1.1}; // 统一的初始化语法 B b {2, 2.2};
Lambda表达式
提供了一个相似匿名函数的特性,而匿名函数则是在须要一个函数,可是又不想费力去命名一个函数的状况下去使用的。
[ caputrue ] ( params ) opt -> ret { body; };
捕获列表:lambda表达式的捕获列表精细控制了lambda表达式可以访问的外部变量,以及如何访问这些变量。
int a = 0; auto f = [=] { return a; }; a+=1; cout << f() << endl; //输出0 int a = 0; auto f = [&a] { return a; }; a+=1; cout << f() <<endl; //输出1
class A { public: int i_ = 0; void func(int x,int y){ auto x1 = [] { return i_; }; //error,没有捕获外部变量 auto x2 = [=] { return i_ + x + y; }; //OK auto x3 = [&] { return i_ + x + y; }; //OK auto x4 = [this] { return i_; }; //OK auto x5 = [this] { return i_ + x + y; }; //error,没有捕获x,y auto x6 = [this, x, y] { return i_ + x + y; }; //OK auto x7 = [this] { return i_++; }; //OK }; int a=0 , b=1; auto f1 = [] { return a; }; //error,没有捕获外部变量 auto f2 = [&] { return a++ }; //OK auto f3 = [=] { return a; }; //OK auto f4 = [=] {return a++; }; //error,a是以复制方式捕获的,没法修改 auto f5 = [a] { return a+b; }; //error,没有捕获变量b auto f6 = [a, &b] { return a + (b++); }; //OK auto f7 = [=, &b] { return a + (b++); }; //OK
lambda表达式的大体原理:每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类重载了()运算符),咱们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,是一个右值。因此,咱们上面的lambda表达式的结果就是一个个闭包。对于复制传值捕捉方式,类中会相应添加对应类型的非静态数据成员。在运行时,会用复制的值初始化这些成员变量,从而生成闭包。对于引用捕获方式,不管是否标记mutable,均可以在lambda表达式中修改捕获的值。至于闭包类中是否有对应成员,C++标准中给出的答案是:不清楚的,与具体实现有关。
#include<iostream> using namespace std; class coord { int x, y; public: coord(int i = 0, int j = 0) { x = i; y = j; } friend ostream& operator<<(ostream &stream, coord &ob);//这里第二个参数采用了引用(&ob), //是为了减小调用的开销,使用引用参数只需把对象的地址传进来就能够了,而不需把每一个域份量逐一传进来 //而消耗内存和时间。因此不用普通的对象作参数,虽然结果同样。可是<<重载的函数返回值和第一个参数必须为输出流类ostream的的引用。 friend istream& operator>>(istream &input, coord &ob);//这里的第二个参数必须为引用,目的是函数体对参数a的修改能影响实参,由于从输入 //流输入的值要存入与a对应的实参中。注意重载输出<<时的做用并非为了修改实参,此点不一样。 }; ostream & operator<<(ostream &stream, coord &ob) { stream << ob.x << "," << ob.y << endl;//stream为ostream类的一个对象的引用,做为左操做数(cout也是同样,是C++中的两个流对象) return stream; } istream& operator>>(istream &input, coord &ob) { cout << "Enter x and y value:"; input >> ob.x; input >> ob.y; return input; } int main() { coord a(55, 66), b(100, 220); cout << a << b; cin >> a; cin >> b; cout << a << b; return 0; }
分析:上面输出重载函数的形参stream是ostream类对象的引用,返回值也是ostream类对象的引用。在main中cout<<a;cout是ostream类对象,a是coord类对象,因此能够把其理解为operator<<(cout,a);
#include <iostream> using namespace std; class Distance { private: int feet; // 0 到无穷 int inches; // 0 到 12 public: // 所需的构造函数 Distance(){ feet = 0; inches = 0; } Distance(int f, int i){ feet = f; inches = i; } ostream& operator<<( ostream & os) { os<<"英寸:"<<feet<<"\n英尺:"<<inches; return os; } }; int main () { Distance d1(20,18); d1<<cout;//至关于d1.operator<<(cout) }
容器
vector向量:相似数组操做。
size 是当前 vector 容器真实占用的大小,也就是容器当前拥有多少个容器。
capacity 是指在发生 realloc 前能容许的最大元素数,即预分配的内存空间。
固然,这两个属性分别对应两个方法:resize() 和 reserve()。
使用 resize() 容器内的对象内存空间是真正存在的。
使用 reserve() 仅仅只是修改了 capacity 的值,容器内的对象并无真实的内存空间(空间是"野"的)。
#include <iostream> #include <vector> using std::vector; int main(void) { vector<int> v; std::cout<<"v.size() == " << v.size() << " v.capacity() = " << v.capacity() << std::endl; v.reserve(10); std::cout<<"v.size() == " << v.size() << " v.capacity() = " << v.capacity() << std::endl; v.resize(10); v.push_back(0); std::cout<<"v.size() == " << v.size() << " v.capacity() = " << v.capacity() << std::endl; return 0; }
针对 capacity 这个属性,STL 中的其余容器,如 list map set deque,因为这些容器的内存是散列分布的,所以不会发生相似 realloc() 的调用状况,所以咱们能够认为 capacity 属性针对这些容器是没有意义的,所以设计时这些容器没有该属性。
在 STL 中,拥有 capacity 属性的容器只有 vector 和 string。
+ 阻塞IO(Blocking IO)
在这个例子中,咱们会经过UDP而不是TCP来举例,由于对于UDP来讲,等待数据就绪这一步更加直观:要不就是收到了一个数据报,要不就是没收到一个数据报.可是对于TCP来讲,还有不少额外的变量.
上图中的recvfrom是一个系统调用.当咱们执行一次系统调用的时候,有一次从用户态到内核态的切换.
从上图中咱们能够看到,进程调用recvfrom以后,这个系统调用并不会当即返回,它会等到数据报到达而且被拷贝到应用程序的缓冲区中,或者出现了一个错误,才会返回.咱们称这个过程是阻塞的,应用程序只有在数据报被放入缓冲区以后,才能继续进行.
非阻塞IO(Nonblocking IO)
非阻塞IO和阻塞IO相对,它会告诉内核,"当我要你完成的IO操做不能完成时,不要让进程阻塞,你给我返回一个错误就好了".过程以下图所示:
在上面的三个recvfrom操做中,因为数据并无就绪,因此内核返回了一个EWOULDBLOCK错误.在第四个recvfrom中,数据已经就绪了,而且已经被拷贝到咱们的应用程序的缓冲区了,内核返回一个OK,而后咱们的应用程序处理这些数据.
咱们能够看到,在这种模型中,咱们须要使用轮询的方式来肯定数据究竟是否就绪.尽管这会浪费CPU时间,可是仍然是比较常见的模型,通常是在系统函数中用到.
I/O复用(I/O Multiplexing)
在I/O多路复用中,咱们会调用select()或者poll(),而且阻塞在这两个系统调用上.而不是阻塞在recvfrom这个实际的IO操做的系统调用上.下面是I/O多路复用模型的过程图:
从上图中,咱们能够看到,咱们会阻塞在select()这个系统调用上,并等待数据到达.当select()告诉咱们数据到达时,再经过recvfrom系统调用将数据拷贝到应用程序的缓冲区.多了一次系统调用,确实是I/O多路复用模型的缺点.可是存在即合理,它也有优势.
它的优势在于,select能够同时监听多个文件描述符,以及感兴趣的事件.因此,咱们能够在一个线程中完成以前须要好多个线程才能完成的事情.
好比,咱们想要同时从一个接受来自Socket的数据,以及从文件中读数据.在阻塞IO模型中,咱们会这么作:
1.建立一个线程A,在其中建立一个Socket Server,并经过它的accept()方法,等待客户端的链接并处理数据 2.建立一个线程B,在其中打开文件而且读数据.
这就须要两个线程,对吧?
并且咱们又知道,线程之间的切换是有开销的,也是须要涉及到用户态到内核态的转换.
而咱们在I/O多路复用模型中,能够这样作:
1.经过注册函数告诉系统,应用程序对于Socket的读事件以及文件的读事件感兴趣 2.经过轮询调用select()方法,查看哪些咱们感兴趣的事件已经发生了 3.在同一个线程中,依次进行对应的操做
咱们能够看到,在这里咱们只须要用一个线程就能够作到在阻塞IO中咱们须要两个线程才能作到的事情.这就是I/O复用中的复用的含义.
信号驱动IO(signal driven I/O)
信号驱动IO使用信号量机制,它告诉内核,当文件描述符准备就绪时,经过SIGIO信号通知咱们.过程以下:
咱们首先经过sigaction系统调用安装一个事件处理器.这个操做会当即返回.因此咱们的应用程序会继续运行,而不会阻塞.当数据准备就绪时,内核会给咱们的应用程序发出一个SIGIO信号,咱们能够继续进行下面的处理:在信号处理器中,经过recvfrom系统调用将数据从内核缓冲区读取到应用程序缓冲区中,告诉应用程序从缓冲区读取数据而且处理.这种模型的优势是,在等待数据就绪时,应用程序并不会被阻塞.应用程序能够继续运行,只须要在数据就绪时,让时间处理器通知它便可.
异步IO(Asynchronous IO)
异步IO模型跟事件驱动IO模型相似,也是告诉内核,在必定状况下通知咱们.可是它跟事件驱动IO模型不一样的是,在事件驱动IO模型中,内核会在数据就绪,即数据被拷贝到内核缓冲区时,通知咱们.而在异步IO中,内核会在整个操做都被完成,即数据从内核缓冲区拷贝到应用程序缓冲区时,通知咱们.以下图所示: