C++大杂烩
附几个楼主在B站学的相关知识网站,大家自取所需哈~
C++学习
Linux系统编程
Linux网络编程
git版本控制器
QT入门教程
AD软件教程
虚表
每个包含了虚函数的类都包含一个虚表,一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表;一个类只需要一个虚表即可,同个类的对象共享一个虚表
虚表指针
为了指定对象的虚表,对象内部包含一个虚表的指针,来指向自己所使用的虚表,当类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表;
对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向其继承的最近的一个类的虚函数
string 和 vector 类的查找
string:使用自带的 find() 函数进行元素查找,找不到则返回自带的函数 npos();
vector:使用外部的 find() 函数进行元素查找,找不到返回 end();
string的end() 和 vector的end() 都指向的是最后一个元素的下一个位置,不是指向最后一个元素!!
new多维数组方法:
一维:char *a=new char[n] 二维:char **array=new char*[n]; //二维n*m for(int i=0;i<n;i++) array[i]=new char[m]; vector<vector<int>>dp(m,vector<int>(n,default_value)); //vector 二维数组m*n的初始化
智能指针(每种指针都是以类模板的方式实现),头文件<memory>
shared_ptr
指针指向null的时候,计数为0;
提供拷贝构造函数(让p1指向的内存也被新的p2指向)和移动构造函数(让p1指向null,并把新的P2指向p1原先指向的内存)
常用成员函数:
reset() 当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1
get() 获得 shared_ptr 对象内部包含的普通指针
use_count() 返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量
unique() 判断当前 shared_ptr 对象指向的堆内存,是否不再有其它 shared_ptr 对象再指向它
unique_ptr
由于不共享内存,只能有一个指向,所以没有拷贝构造函数,只有移动构造函数(将新的指针指向旧的内存,并把旧的指针置为null)
常用成员函数:
get():获取当前 unique_ptr 指针内部包含的普通指针
release():释放当前 unique_ptr 指针对所指堆内存的所有权,但该存储空间并不会被销毁,可用另一个指针进行接收
reset(p):其中 p 表示一个普通指针,如果 p 为 nullptr,则当前 unique_ptr 也变成空指针;反之,则该函数会释放当前 unique_ptr 指针指向的堆内存(如果有),然后获取 p 所指堆内存的所有权(p 变为 nullptr)
weak_ptr
只能和 shared_ptr 类型指针搭配使用,没有*和->,所以无法修改堆内存数据,只能访问;指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1,释放也不会减1
可以和 shared_ptr 类型指针指向同一块内存,可使用shared_ptr 类型指针进行初始化
常用成员函数:
reset():将当前 weak_ptr 指针置为空指针
use_count():查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量
expired():判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)
lock():如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针
作用:当定义一个双向节点类型的时候,定义两个shared_ptr指针p,q,分别指向两个节点,两个节点再互指,这样每个节点的计数为2,如果要释放其中一个节点必须使得计数为1,p要等待q释放,q要等待p释放,这样就造成死循环了,要避免可在节点类型定义那里将指针改成weak_ptr,使得计数始终为1
lambda表达式
[外部变量访问方式说明符] (参数) mutable noexcept/throw() -> 返回值类型 { 函数体; };
[]:= 以值传递的方式导入所有外部变量;& 以引用传递的方式导入所有外部变量;this 以值传递的方式导入当前的 this 指针
mutable:以值传递方式传入外部变量,默认不修改他们的值,传入的是const类型,加上该关键字可修改,修改的是拷贝的那份,原先的不变
noexcept/throw():默认可抛出异常,标注 noexcept 关键字,则表示函数体内不会抛出任何异常;使用 throw() 可以指定 lambda 函数内部可以抛出的异常类型
可使用auto name=表达式 来给lambda表达式起一个名字
类模板和函数模板的参数都可以有默认值
关键字using:using newname=oldtype ;和typedef功能一样,给类型起别名,可用于模板的重定义
自动类型推导:
关键字auto:可进行类型的自动推导,使用时必须初始化
关键字decltype:decltype(exp) varname = value ;varname 表示变量名,value 表示赋给变量的值,exp 表示一个表达式
如果 exp 是一个不被括号( )包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致,这是最普遍最常见的情况。
如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。
如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&。
左右值:
左值是指那些在表达式执行结束后依然存在的数据,也就是持久性的数据;
右值是指那些在表达式执行结束后不再存在的数据,也就是临时性的数据;使用&&
有一种很简单的方法来区分左值和右值,对表达式取地址,如果编译器不报错就为左值,否则为右值
移动构造函数
为防止普通的拷贝构造函数对临时变量进行复制后删除等操作,而是直接将临时变量的资源直接返回,并将临时变量指针指向null
使用std::move()函数可以使左值强制转换变成右值
完美转发:指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变
NULL和nullptr区别:
在 c 中,null定义为#define NULL ((void )0),当复制为null的时候发生了隐式类型转换,把void指针转换成了相应类型的指针;
C++是强类型语言,void是不能隐式转换成其他类型的指针的,所以NULL=0;
但是在函数重载的时候会出现问题,传入参数为null的时候会错传为int型(认为null=0为int型),所以需要nullptr来代替
构造函数调用规则:
如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认构造拷贝函数
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
c++中成员变量和成员函数是分开存储的
在类中,只有非静态成员变量属于类的,即算内存的,静态成员变量和非静态、静态成员函数都不属于类
this指针
是隐含每一个非静态成员函数内的一种指针,不需要定义,指向被调用的成员函数所属的对象
用途:当形参和成员变量同名时,可用this进行区分;在类的非静态成员函数中返回对象本身,可使用return *this
类指针可以指向null,但是如果有对类成员变量进行处理的操作则会出错,因为调用的成员变量前面都是this->,而this==null
this是指向实例化对象后本身的一个指针,里面存储的是对象本身的地址,通过该地址可以访问内部的成员函数和成员变量
为什么需要静态成员函数
静态成员函数属于类,而不是对象。由于私有静态成员无法类外访问,有了静态成员函数之后,在访问私有静态成员时,可以在创建对象前,通过类的静态成员函数访问静态成员
为什么静态成员函数没有this指针
静态成员函数无法访问内部非静态成员变量,只能访问静态成员变量,所以没有;普通函数可以通过调用对象(由this引入)来调用成员变量
const修饰类
修饰成员函数:称为常函数,在函数内不能修改成员属性
修饰类:称为常对象,只能调用常函数
mutable关键字加上后可使得所修饰的变量在常函数中也可以被修改
在成员函数后面加const,修饰的是this指向,让this指向的值不可修改,void fun() const{} 类似于 const 类*const this
友元函数
不是类的成员函数
三种方式:全局函数、友元类、一个类的成员函数为本类的友元函数(要加上类的作用域)
运算符重载
本质就是 operator+的函数名,可以使用类.operator+()进行调用,运算符重载函数也可以重载
类中实现:类 operator+(const 类&p) 默认有this指向本对象,所以一个就够
全局函数实现:类 operator+(const 类&a,const 类&b) 没有this,需要两个参数
各种运算符重载
<<重载只能使用全局函数实现,因为 cout<<中cout在左边,ostream & operator<<(ostream &cout,类 &p);
++重载中,++类返回的是 类&,类++返回的是void,因为要使用到中间变量记录,返回的是局部变量,同时因为重载的函数名一样,一个使用占位符进行区分,void operator++(int)
=赋值运算符重载,在带有内存申请的赋值操作中会出现浅拷贝问题,需要重载,类&operator=(类&),为满足连续赋值需要返回自身*this引用
()运算符重载,类似于函数用法,又叫仿函数调用,可进行匿名函数对象调用,类()(参数) ,使用完即释放
继承相关
父类中所有非静态成员属性都会被子类继承下去,占子类内存;父类中的私有成员属性是被编译器隐藏了,因此访问不到
构造和析构顺序:先构造父类再构造子类,先析构子类再析构父类
子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,要使用父类的需加上作用域
菱形/钻石继承:两个子类继承同一个父类,另一个新类同时继承两个子类,这样在新类中会有父类两个数据的拷贝(分别继承自两个子类),可以加上virtual关键字做虚继承,这样就只有一份了
多态原理
当类中有virtual修饰的虚函数时,类会维护一个虚指针,这个指针指向一个虚表,虚表放的是虚函数,当子类继承父类时,会拷贝一份父类的虚表,如果子类没有重写同名的虚函数,则所指向的虚函数和父类一样,如果在子类中重写了虚函数,则会覆盖掉子类的虚指针所指向的虚表函数为对应修改的函数
抽象类:不能实例化;子类继承了抽象类必须实现抽象类中的函数
虚析构函数:防止父类指针不能析构子类对象;纯虚析构函数也必须实现析构函数,此时该类也变成抽象类
函数模板
定义方法:template<typename t=""> 函数具体定义,类型用T代替;typename用在函数,class可以用于函数或者类
两种使用方法:直接代入,函数自动推导;显式指定类型,如funname<int>(参数)</int></typename>
普通函数和函数模板的区别
普通函数调用会发生自动类型转换(隐式类型转换),模板调用利用自动类型推导,不会发生隐式类型转换
调用规则:
如果函数模板和普通函数都可以实现(函数同名),优先调用普通函数;
函数模板可以重载;通过空模板参数列表<>可以强制选择模板函数;
函数模板不是万能的,对于一些特殊类型的数据,如果模板没办法处理的时候要另外指定对应的处理模板,可重载定义为 template<>模板函数名字(参数)
c++异常处理
如果有一个块抛出一个异常,捕获异常的方***使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码
try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
stl库
for_each(begin,end,function_name); 遍历容器并调用回调函数function对每个元素进行操作 字符串的比较,compare相等返回0,大于返回1,小于返回-1 所有不支持随机访问迭代器的容器,内部会提供,不可以用标准算法,如sort、reverse等 set的find方法,如果找到返回元素第几个迭代器,找不到返回最后一个元素,即end的迭代器 pair对组的创建:pair<string,int>p1("tom",20); pair<string,int>p2=make_pair("tom",30); set元素为自定义的时候,如果含有多个类型(如类),则必须指定排序的规则,具体为创建一个类的仿函数代入set的形参中 map是按照key进行排序的 函数对象(类似于回调函数,只不过是类作为载体) 重载函数调用操作符的类,其对象称为函数对象;函数对象使用重载的()时,行为类似函数调用,也叫仿函数(本质是一个类,不是函数)
函数对象使用
函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值;
函数对象超出普通函数的概念,函数对象可以有自己的状态,即可以定义成员变量用来统计重载()被调用的次数等
函数对象可以作为参数传递,如map、set等元素为自定义的时候,自己设置的排序规则需要用到
谓词(形参为 pr_pred需要使用到)
返回bool类型的仿函数称为谓词
如果operator()接受一个参数,那么叫做一元谓词; bool operator()(int val1) find_if()可调用
如果operator()接受两个参数,那么叫做二元谓词;bool operator()(int val1,int val2)
stl库提供函数对象
用法和一般函数完全相同,需要头文件<functional>
算数仿函数,实现四则运算;plus minus(减) multiplies divedes modulus(取模) negate(取反)
关系仿函数,实现关系对比;equal_to not_equal_to greater greater_equal less less_equal
逻辑仿函数,实现与或非; and or not
stl库创建堆函数
创建:make_heap(temp.begin(),temp.end(),greater<int>()); // greater 小根堆/less 大根堆
插入元素后需要重新调整:push_heap(temp.begin(),temp.end(),greater<int>());
stl常用算法(很多算法里面需要的类函数为自定义或者调用stl提供的规则,对容器里面的元素进行的操作)</int></int>
主要头文件:
<algorithm>
最大的一个,范围涉及到比较、交换、查找和遍历操作<numeric>
体积小,只包括几个在序列上面进行简单数***算的模板函数<functional>
定义了一些模板类,用以声明函数对象
算法分类
<algorithm>
头文件
遍历:for_each() 遍历容器; fransform() 搬运容器到另一个容器 查找:find(); find_if() 按条件查找;adjacent_find() 查找相邻重复元素;binary_search() 二分查找;count() 统计元素个数;count_if() 按条件统计元素个数 排序:sort() random_shuffle() 洗牌,指定范围内的元素随机调整次序; merge() 容器元素合并,两个容器必须有序,归并排序,并存储到另一个容器中;reverse() 反转指定范围的元素 拷贝和替换:copy() replace() 将容器中指定范围的旧元素修改为新元素;replace_if() 指定范围满足条件的元素替换为新元素;swap() 互换两个容器的元素 <numeric>头文件 accumulate():计算容器元素累计总和 fill() 向容器指定区间内填充元素 set_intersection() 求两个容器的交集,必须有序的 set_union() 求两个容器的并集 set_difference() 求两个容器的差集
emplace_back为什么比push_back快这么多?
push_back要求输入的参数是一个已经存在的对象。 当输入的参数,不是这样的对象时,会调用对应类的构造函数,构造一个临时的对象,然后把这个对象执行拷贝构造函数或者
函数插入到vector中
emplace_back 可以直接使用参数,在本地构建对象。这样一来,只需要调用构造函数,没有调用拷贝构造函数或者移动构造函数的过程
实现strcpy()和strcmp()
char* my_strcpy(char* str1, char* str2){ assert(str2);//判断源地址是否为空; char* cur = str1; while (*str2 != '\0'){ *str1 = *str2; str1++; str2++; } *str1 = '\0'; return cur; } //Str1是目的串,str2是源串 int my_strcmp(const char *str1, const char *str2){ assert(str1); assert(str2); while (*str1++ == *str2++){ if (*str1 == '\0') return 0; } if (*str1 - *str2) //不等的情况 return 1; else return -1; } //Str1比str2,小于返回-1,大于返回1,等于返回0
pair和make_pair 头文件<utility>
std::pair主要的作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型
创建方式不同:
std::pair<int, char>(42, '@');
std::make_pair(42, '@'); 自动推导类型
红黑树和哈希
红黑树时间复杂度是o(logn),元素是有序的;
哈希时间复杂度是o(1),元素是无序的;
平衡二叉树AVL和红黑树RB
平衡二叉树搜索快,插入和删除慢,因为要多次进行大量的平衡度计算
红黑树插入和删除快,搜索较慢,因为比平衡二叉树不平衡最多一层
单调队列和单调栈
单调队列用于解决取某一区间极值问题
单调栈用于查找相邻次极值问题
单调栈和单调队列:单调栈主要解决 下一最大值 问题;单调队列主要解决 滑动窗口 问题
二分搜索
两种不同的结束方式区别
right=numsize-1;区间/每次搜索范围-[left,right];判断方式-while(left<=right) ;终止条件-left==right+1;结束范围-[right+1,right] 没有值可以取
right=numsize;区间/每次搜索范围-[left,right);判断方式-while(left<right) ;终止条件-left==right;结束范围-[right,right) 还有一个right可以取
二维数组转成一维数组:a(x,y)->x*n+y n为列数
方向数组:int d[4][2] = {{1,0}, {0,1}, {0,-1}, {-1,0}}; //方向数组 d 是上下左右搜索的常用手法 for (int k = 0; k < 4; k++) { //依次取上下左右的值进行处理 int x = i + d[k][0]; int y = j + d[k][1]; } //一般是通过 % 运算符求模(余数),来获得环形特效: while (true) { print(arr[index % arr.length()]); //控制index==整数倍的arr.length()可以实现环形遍历数组x次 index++; }
位操作
利用或操作 | 和空格将英文字符转换为小写:('A' | ' ') = 'a'
利用与操作 & 和下划线将英文字符转换为大写:('b' & '_') = 'B'
利用异或操作 ^ 和空格进行英文字符大小写互换:('d' ^ ' ') = 'D' ('D' ^ ' ') = 'd'
n&(n-1) 这个操作是算法中常见的,作用是消除数字 n 的二进制表示中的最后一个 1
异或:一个数和它本身做异或运算结果为 0,即 a ^ a = 0;一个数和 0 做异或运算的结果为它本身,即 a ^ 0 = a
加密算法
对称加密:使用同一个密钥加密和解密
非对称加密:生成一对儿密钥,把加密和解密的工作分开了
非对称加密有两种用途:
如果用于加密,可以把公钥发布出去用于加密,只有自己的私钥可以解密,保证了数据的机密性;
如果用于数字签名,把公钥发布出去后,用私钥加密数据作为签名,以证明该数据由私钥持有者所发送
数字签名:利用了非对称性密钥的特性,但是和公钥加密完全颠倒过来;仍然公布公钥,但是用你的私钥加密数据,然后把加密的数据公布出去,能解开说明是本人发的
公钥证书:就是公钥 + 签名,由第三方认证机构颁发,将数字签名的公钥进行加密发送,确认接收到的是否为正确的公钥
并行和并发
并行指的是多个事件同一时刻发生,并发指的是多个事件同一时间间隔发生
并行在不同实体上的多个事件,并发在同一个实体上的多个事件
变量命名
大驼峰法:第一个字母大写,后面也大写,如类名、函数名、属性名、命名空间等
小驼峰法:第一个字母小写,后面的大写,如变量
memcpy和memset操作的对象是字节型,不能对int型数组进行初始!!
序列是不连续的,子串是连续的
判断是否为素数: for (int i = 2; i * i <= n; i++)
向上取整是一个常用的算法技巧。大部分编程语言中,如果你想计算 M 除以 N,M / N 会向下取整,你想向上取整的话,可以改成 (M+(N-1)) / N
面向对象的本质
使用了对象就叫基于对象,基于对象的基础上增加了继承从而变成了面向对象;
基于对象的最大特点就是封装;面向对象,继承,多态只是它的特点而已;
本质就是分类的问题,把问题工程化,细分多个子项目,进行分类解决并处理好继承关系
面向对象和面向过程区别
面向过程
优点:性能比面向对象好,因为类调用时需要实例化,开销比较大,比较消耗资源。
缺点:不易维护、不易复用、不易扩展
面向对象
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程差
协程
协程属于轻量级线程,主要解决线程数量多时占用非常多的内存空间和线程切换占用大量系统时间的问题
协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上
协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程;
而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多
协程不要调用阻塞IO方法,除非改成异步调用
在协程调用阻塞IO操作的时候,操作系统会让线程进入阻塞状态,当前的协程和其它绑定在该线程之上的协程都会陷入阻塞而得不到调度
TCP和IP的关系
IP提供基本的数据传送,而高层的TCP对这些数据包做进一步加工,如提供端口号等等;
tcp各标记
序号:seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记
确认号:ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1
标志位:
SYN:发起一个新连接
FIN:释放一个连接
ACK:确认序号有效(为了与确认号ack区分开,我们用大写表示)
TCP头部
源端口号+目的端口号+序号(对每个字节进行编号,即总字节数)+确认号(希望收到的下一个报文的第一个数据字节的序号)+首部长度+标志位+窗口大小+校验和+紧急数据指针+数据
UDP头部
源端口号+目的端口号+UDP长度+校验和+数据