【Linux信号量】 我们在主线程当中创建四个新线程,让这四个新线程执行抢票逻辑,并且每次抢完票后打印输出此时剩余的票数,其中我们用全局变量tickets记录当前剩余的票数,此时tickets是会被多个执行流同时访问的临界资源,在下面的代码中我们并没有对tickets进行任何保护操作。当执行流在申请信号量时,可能此时信号量的值为0,也就是说信号量描述的临界资源已经全部被申请了,此时该执行流就应该在该信号量的等待队列当中进行等待,直到有信号量被释放时再被唤醒。
Linux生产者消费者模型 生产者和消费者彼此之间不直接通讯,而通过这个容器来通讯,所以生产者生产完数据之后不用等待消费者处理,直接将生产的数据放到这个容器当中,消费者也不用找生产者要数据,而是直接从这个容器里取数据,这个容器就相当于一个缓冲区,平衡了生产者和消费者的处理能力,这个容器实际上就是用来给生产者和消费者解耦的。对应到生产者消费者模型中,函数传参实际上就是生产者生产的过程,而执行函数体实际上就是消费者消费的过程,在消费者消费期间生产者可以同时进行生产,因此生产者消费者模型本质是一种松耦合。
【C++类型转换和IO流】 而使用dynamic_cast进行向下转型则是安全的,如果父类的指针(或引用)指向的是子类对象那么dynamic_cast会转换成功,但如果父类的指针或引用指向的是父类对象那么dynamic_cast会转换失败并返回一个空指针。上述代码中,如果传入func函数的是子类对象的地址,那么在转换后pb1和pb2都会有对应的地址,但如果传入func函数的是父类对象的地址,那么转换后pb1会有对应的地址,而pb2则是一个空指针。用转换后的函数指针调用该函数时没有传入参数,因此这里打印出参数i的值是一个随机值。
【C++特殊类设计】 new和delete默认调用的是全局的operator new函数和operator delete函数,但如果一个类重载了专属的operator new函数和operator delete函数. 那么new和delete就会调用这个专属的函数。将该类的构造函数设置为私有即可,因为子类的构造函数被调用时,必须调用父类的构造函数初始化父类的那一部分成员,但父类的私有成员在子类当中是不可见的,所以在创建子类对象时子类无法调用父类的构造函数对父类的成员进行初始化,因此该类被继承后子类无法创建出对象。
【C++11智能指针】 需要注意的是,智能指针就是要模拟原生指针的行为,当我们将一个指针赋值给另一个指针时,目的就是让这两个指针指向同一块内存空间,所以这里本就应该进行浅拷贝,但单纯的浅拷贝又会导致空间被多次释放,因此根据解决智能指针拷贝问题方式的不同,从而衍生出了不同版本的智能指针。当智能指针对象的生命周期结束时,所有的智能指针默认都是以delete的方式将资源释放,这是不太合适的,因为智能指针并不是只管理以new方式申请到的内存空间,智能指针管理的也可能是以new[] 的方式申请到的空间,或管理的是一个文件指针。
【C++异常处理】 实际中很多公司都会自定义自己的异常体系进行规范的异常管理。公司中的项目一般会进行模块划分,让不同的程序员或小组完成不同的模块,如果不对抛异常这件事进行规范,那么负责最外层捕获异常的程序员就非常难受了,因为他需要捕获大家抛出的各种类型的异常对象。因此实际中都会定义一套继承的规范体系,先定义一个最基础的异常类,所有人抛出的异常对象都必须是继承于该异常类的派生类对象,因为异常语法规定可以用基类捕获抛出的派生类对象,因此最外层就只需要捕获基类就行了。
【线程安全同步与互斥】 例如,现在有两个线程访问同一块临界区,一个线程往临界区写入数据,另一个线程从临界区读取数据,但负责数据写入的线程的竞争力特别强,该线程每次都能竞争到锁,那么此时该线程就一直在执行写入操作,直到临界区被写满,此后该线程就一直在进行申请锁和释放锁,而负责数据读取的线程由于竞争力太弱,每次都申请不到锁,因此无法进行数据的读取,引入同步后该问题就能很好的解决。所有的线程在进入临界区之前都必须竞争式的申请锁,因此锁也是被多个执行流共享的资源,也就是说锁本身就是临界资源。
【Linux多线程】 在一个程序里的一个执行路线就叫做线程(thread).更准确的定义是:线程是“一个进程内部的控制序列”。一切进程至少都有一个执行线程。线程在进程内部运行,本质是在进程地址空间内运行。在Linux系统中,在CPU眼中,看到的PCB都要比传统进程更轻量化。透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
Linux私房菜(十)之信号 执行该信号的默认动作处理动作提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。忽略该信号在Linux当中,我们可以通过man手册查看各个信号默认的处理动作。
【Linux私房菜(九)之进程间通信】 进程间通信简称IPC(Interprocess communication),进程间通信就是在不同进程之间传播或交换信息。管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的数据流称为一个“管道”。例如,统计我们当前使用云服务器上的登录用户个数。其中,who命令和wc命令都是两个程序,当它们运行起来后就变成了两个进程,who进程通过标准输出将数据打到“管道”当中,wc进程再通过标准输入从“管道”当中读取数据,至此便完成了数据的传输,进而完成数据的进一步加工处理。
高阶数据结构-------图 构造一个含n个顶点、不含任何边的图作为最小生成树,将图中的顶点分为两个集合,forest集合中的顶点是已经连接到最小生成树中的顶点,remain集合中的顶点是还没有连接到最小生成树中的顶点,刚开始时forest集合中只包含给定的起始顶点。每次从连接forest集合与remain集合的所有边中选出一条权值最小的边,将其加入到最小生成树中,由于选出来的边对应的两个顶点一个属于forest集合,另一个属于remain集合,因此是不会构成回路的。图由顶点和边组成,存储图本质就是将图中的顶点和边存储起来。
【并查集】 给你一个 n × n n \times nn×n 的矩阵,其中 i s C o n n e c t e d [ i ] [ j ] = 1 isConnected[i][j]=1isConnected[i][j]=1 表示第 i ii 个城市和第 j jj 个城市直接相连,而 i s C o n n e c t e d [ i ] [ j ] = 0 isConnected[i][j]=0isConnected[i][j]=0 表示二者不直接相连。查找元素所在的集合,本质就是查找元素所在集合的根节点。
[C++11——— 包装器] function包装器function是一种函数包装器,也叫做适配器。它可以对可调用对象进行包装,C++中的function本质就是一个类模板。Ret: 被包装的可调用对象的返回值类型。Args…: 被包装的可调用对象的形参类型。包装示例function包装器可以对可调用对象进行包装,包括函数指针(函数名)、lambda表达式、类的成员函数。//函数指针(函数名)//仿函数(函数对象)public://类成员函数class Pluspublic://静态成员函数。
【C++11 ——— lambda表达式】 实际当我们以[&]或[=]的方式捕获变量时,编译器也不一定会把作用域中所有的变量捕获进来,编译器可能只对lambda表达式中用到的变量进行捕获,没有必要把用不到的变量也捕获进来,这个主要看编译器的具体实现。因为lambda表达式的类型都是不同的,这也就是lambda表达式之间不能相互赋值的原因,我们可以通过typeid(变量名).name()的方式来获取lambda表达式的类型。可以看到,就算是两个一模一样的lambda表达式,它们的类型都是不同的。现在要对若干商品分别按照价格和数量进行升序、降序排序。
【可变模板参数】 函数的可变参数模板定义方式如下:返回类型 函数名(Args… args)//函数体{}模板参数Args前面有省略号,代表它是一个可变模板参数,我们把带省略号的参数称为参数包,参数包里面可以包含0到N ( N ≥ 0 ) N(N\geq 0)N(N≥0)个模板参数,而args则是一个函数形参参数包。模板参数包Args和函数形参参数包args的名字可以任意指定,并不是说必须叫做Args和args。现在调用ShowList函数时就可以传入任意多个参数了,并且这些参数可以是不同类型的。
【C++11------类的新功能】 上述代码中用一个右值去构造s2对象,但由于Person类中没有生成默认的移动构造函数,因此这里会用Person的拷贝构造函数(拷贝构造即能接收左值也能接收右值),这时在Person的拷贝构造函数中,就会调用string的拷贝构造函数对name成员进行深拷贝。如果要让Person类生成默认的移动构造函数,就必须将Person类中的拷贝构造、拷贝赋值、和析构函数全部注释掉,这时用右值去构造s2对象时就会调用Person默认生成的移动构造函数。**注意:**这里不是初始化,而是给声明的成员变量一个缺省值。
【C++11右值引用的移动语义和完美转发】 这里需要说明的是,对于返回局部对象的函数,就算只是调用函数而不接收函数的返回值,也会存在一次拷贝构造或移动构造,因为函数的返回值不管你接不接收都必须要有,而当函数结束后该函数内的局部对象都会被销毁,所以就算不接收函数的返回值也会调用一次拷贝构造或移动构造生成临时对象。右值引用和万能引用的区别就是,右值引用需要是确定的类型,而万能引用是根据传入实参的类型进行推导,如果传入的实参是一个左值,那么这里的形参t就是左值引用,如果传入的实参是一个右值,那么这里的形参t就是右值引用。
【C++11(一)之入门基础)】 在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。
【Linux私房菜(八)之动静态库】 而实际上,对于可能频繁用到的源文件,比如这里的test1.c、test2.c、test3.c、test4.c,我们可以将它们的目标文件test1.o、test2.o、test3.o、test4.o进行打包,之后需要用到这四个目标文件时就可以之间链接这个包当中的目标文件了,而这个包实际上就可以称之为一个库。静态库是程序在编译链接的时候把库文件的代码复制到可执行文件中的,生成的可执行程序在运行的时候将不再需要静态库,因此使用静态库生成的可执行程序的大小一般比较大。
【鸟哥的Linux私房菜(七)之文件IO】 我们知道,当fopen以写入的方式打开一个文件时,若文件不存在,则会自动在当前路径创建该文件,那么这里所说的当前路径指的是什么呢?例如,我们在Test_2024_8_15目录下运行可执行程序myproc,那么该可执行程序创建的log.txt文件会出现在Test_2024_8_15目录下。那是否意味着这里所说的“当前路径”是指“当前可执行程序所处的路径呢?这时我们可以将刚才可执行程序生成的log.txt文件先删除,然后再做一个测试:回退到上级目录,在上级目录下运行可执行程序。