C/C++教程

C++总结

本文主要是介绍C++总结,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

c++学习总结

现代C++语言(C++11、C++14、C++17特性):http://3ms.huawei.com/km/blogs/details/5813939
C++编译与执行的四阶段
a)预处理:根据文件中的预处理指令来修改源文件的内容, 产生.ii文件
b)编译:编译成汇编代码,产生汇编文件(.s文件)
c)汇编:把汇编代码翻译成目标机器指令,产生目标文件(.o或.obj文件)
d)链接:链接目标代码生成可执行程序,产生可执行文件(.out或.exe文件)

1、类的成员变量必须显示初始化,优先使用初始化列表;

2、explicit:避免单参数的构造函数隐式转换
Base base1(‘a’); // 用于构建单参数的类对象
Base base2 = ‘b’; // 隐含的类型转换操作符,隐式转换,容易引发bug
为了避免隐式转换,单参数构造函数声明为explicit,如果没有explicit会成为隐式转换函数;拷贝构造函数不要声明为explicit
隐式转换:是系统根据程序的目的需要而自动转换的。
显示转换:也叫强制转换,是自己主动让这个类型转换成别的类型。static_cast();
3、禁止虚函数使用缺省参数,即在声明虚函数的时候为其指定一个默认值。
4、static:
类的静态成员是该类所有实例的共同成员,在该类的范畴内是个全局变量,自带类安全的属性,在程序初始化的时候就分配内存,只分配一次,所以是共用的。类的静态成员必须初始化。
类的静态成员函数是在该类范畴内的全局函数,实现的时候不需要static关键字修饰,因为static是声明性关键字,不能访问private成员变量,只能访问类的静态成员,不需要类的实例即可调用,实际上,他就是增加了类的访问权限的全局函数,静态成员函数可以继承和覆盖,但是不能是虚函数。

5、const关键字
https://blog.csdn.net/kaida1234/article/details/80403534
const修饰成员函数返回值:const char * GetString(void);
函数返回值(即指针)的内容不能被修改,改返回值只能赋值给加const修饰的同类型指针。
const修饰成员函数:int test(const string &str) const;
在类中将成员函数修饰为const,表明在该成员函数体内,不能修改对象的数据成员,而且不能调用非const函数。为什么不能调用非const函数?因为非const函数可能修改数据成员,const成员函数是不能修改数据成员的,所以在const成员函数内只能调用const函数。
const修饰函数参数:
防止传入的参数代表的内容在函数体内被改变,但仅对指针和引用有意义。因为如果是按值传递,传给参数的仅仅是实参的副本,即使在函数体内改变了形参,实参也不会得到影响。当函数参数为类对象时,推荐按引用传递,但是会有安全隐患,即通过函数参数的引用可以修改实参的内部数据成员,所以用const来保护实参。
当const修饰普通成员函数时,修饰的是this指针(this指针指向的内存空间值是不会发生变化的)。
const指针可以接收非const和const指针,而非const指针只能接收非const指针。
const修饰指针指向的内容,内容不可变,但是指针指向的地址是可以改变的。简称左定值,const位于的左边; const int * p = &a;
const修饰指针变量,则指针指向的内存地址不可变,但是内容是可变的。简称右定向,const位于
右边; int * const p = &a;
const修饰指针和指针指向的内容,则指针和指针指向的内容都为不可变量。例:const int * const p = &a;
const int* p:常指针,const修饰的是int*,指针变量p并不是“只读”的,而是该指针所指向的数据是“只读”的,常指针的作用是保护指向的数据不被更改。
int* const p:指针常量,const修饰的是指针变量p,因此指针p就是“只读”的,不能再改变指针本身的值,也即不能改变指针的指向,但指向的数据却是能更改的。

6、dynamic_cast:动态转换,把基类指针转换成派生类指针,把指向基类的左值转换成派生类的引用。

7、类和结构体的区别:
①当有很多数据时,可以用结构体封装起来。类是面向对象的思想,可以有很多接口可以调用,私有变量等类型不允许外部调用,类中还有保护型的变量。
②结构体也可以被认为是一个特殊的类,它不存在任何函数,也不存在析构、构造函数,而且是一个公共的类。
③结构体的成员在默认情况下是public,而类是private的。
④结构体的默认继承是public,而类的默认继承是private。
⑤类要加上public变成共有才能被访问,而结构体本身就是共有的可以直接访问。

8、深拷贝和浅拷贝:
c++类的拷贝有两种,深拷贝和浅拷贝。
浅拷贝:当出现类的等号赋值时,即会调用拷贝函数;
浅拷贝就是指在对象复制时,只对对象中的数据成员进行简单的赋值,默认的拷贝构造函数也是浅拷贝;
深拷贝会在堆内存中另外申请空间来储存数据,当数据成员中有指针时,必须使用深拷贝;
浅拷贝是指对指针变量的地址的拷贝,拷贝之后两个指针指向同一个内存空间;而深拷贝不但对指针进行拷贝,还会对指针指向的内容进行拷贝,会重新开辟内存空间,进行深拷贝后的指针指向不同的内存地址。

9、封闭类:一个类的成员变量如果是另一个类的对象,就称之为“成员对象”,包含成员对象的类叫做封闭类。
封闭类的对象初始化时,要先执行成员对象的构造函数,是因为封闭类的构造函数可能会用到成员对象,如果此时的成员对象还没初始化就不合理了。

10、常用容器的区别:list、vector、map、set、queue
https://www.cnblogs.com/depend-wind/articles/10143612.html
序列容器:list、vector、queue
使用区别:
①如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;
②如果需要大量的插入和删除,而不关心随机存取,则应使用list;
③如果需要随机存取,而且关心两端数据的插入和删除,则应使用deque。
关联容器:map、set
Map,Set属于标准关联容器,使用了非常高效的平衡检索二叉树:红黑树,他的插入删除效率比其他序列容器高是因为不需要做内存拷贝和内存移动,而直接替换指向节点的指针即可。
set和vector的区别是set不包含重复的数据,set和map的区别是set只有key,而map有一个key和key所对应的value两个元素。
unordered_map:内部元素是无序的,底层实现为哈希表,也不能有重复的键
multimap:允许元素使用重复的关联容器,它的内部实现是红黑树,支持重复的键,不支持[]运算

11、友元函数:可以在类的外部访问类的私有属性,但是会破坏类的封装。声明位置和类访问修饰符无关 friend

12、引用:
引用的本质:
①引用在c++中的内部实现就是一个常量指针;
②c++编译器在编译过程中使用常量指针作为引用的内部实现,因此引用占用的内存空间和指针相同。
当引用作为函数返回值时:引用返回调用函数中的地址,变量的地址能一直存在:
a.当函数的返回值是引用时,若返回的是普通变量,则不能作为其他引用的初始值。
b.当函数的返回值是引用时,若返回的是静态变量或全局变量时,能够成为其他引用的初始值。
c.当函数的返回值是引用时,若返回普通变量,它不能作为左值使用。但是,返回的是静态变量或者是全局变量时,可以作为左值使用。

13、C++程序内存分配
栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
堆区(heap):一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
全局区(静态区static):存放全局变量、静态数据、常量。程序结束后由系统释放。全局区分为已初始化全局区(data)和未初始化全局区(bss)。
常量区(文字常量区):存放常量字符串,程序结束后有系统释放。
代码区:存放函数体(类成员函数和全局区)的二进制代码。
内存模型:

14、结构体中内存对齐原则:https://blog.csdn.net/liukun321/article/details/6974282
原则1:结构体中的元素是按照定义顺序一个一个放到内存中去的,但是并不是紧密排列的,每一个元素放置在内存中,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始。
原则2:在经过原则1之后,检查计算出存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐它为整数倍。
若是结构体中包含指针类型的情况,只需记住在32位系统中,指针本身只占4个字节,在64位系统中指针只占8个字节就行了,而不必看它是什么类型的指针。
由于结构体所占空间与其内部元素的类型有关,而且与不同类型元素的排列有关,因此在定义结构体时,在元素类型及数量确定之后,我们还应该注意一下其内部元素的定义顺序。

15、指针和引用的区别:
①指针和引用都是让你间接引用其他的对象,指针可以被重新赋值以指向另一个不同的对象,引用则指向在初始化时被指向的对象,以后不能改变所指的对象;
②不存在指向的空值的引用,但是存在指向空值的指针;
③从概念上来讲,指针从本质上讲就是存放变量地址的变量,在逻辑上是独立的,它可以被改变,包括其指向的地址的改变和其指向地址中所存放的数据的改变。而引用只是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就要被初始化,且其引用的对象在整个生命周期都是不能被改变的。
指针传递参数和引用传递参数的区别:本质是值传递和引用传递的区别
①指针传递和引用传递是以不同的方式实现相同的效果,但是指针传递的本质还是值传递,指针存储了值的地址的值,只是传递的值是地址。
②指针传递相比于引用传递更加灵活,也就更不受约束。引用传递本质上就是一个const指针
https://blog.csdn.net/weikangc/article/details/49762929
C++值传递、引用传递、指针传递 https://www.cnblogs.com/dingxiaoqiang/p/8012578.html
值传递:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,
不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
指针传递:
形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作
引用传递:
形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈
中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过
栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

16、C++ make_pair和pair
std::pair: 将两个数据组合成一个数据,两个数据可以是同一个类型或者不通的类型。例如:
std::pair<unsigned int, unsigned int>,
std::pair<int, char>(42, ‘@’)
std::make_pair(42, ‘@’);
std::make_pair:无需写出类别,就可以生成一个pair对象

17、重写、重载、隐藏、多态性
重写:也叫覆盖,子类对虚函数的重写,主要在继承中体现
重载:函数名相同,参数列表不同
隐藏:也叫重定义,子类重新定义父类相同名称的非虚函数,参数列表可以相同也可以不同,会覆盖父类的同名函数,未体现多态
多态性:通过虚函数实现多态。最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。
编译时的多态:通过重载函数实现
运行时的多态:通过虚函数实现

18、关于虚函数
含有纯虚函数的类是抽象类,抽象类不能创建实例化对象,抽象类不能作为函数的参数,抽象类不能作为函数的返回值,可以声明抽象类的指针,可以声明抽象类的引用。当定义一个有虚函数类的对象时,对象的第一块内存空间就是一个指向虚函数列表的指针,在64位系统中,例如:Base b; (long*)(&b)就是虚函数表的地址,(long*)(long*)(&b)就是第一个虚函数的地址。虚函数表在最后会有一个结束标志,为1说明还有虚表,为0则表示没有了,编译器不同,结束标志可能存在差异。

19、多态的实现原理:

  1. 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。
  2. 存在虚函数的类都有一个一维的虚函数表叫做虚表。当类中声明虚函数时,编译器会在类中生成一个虚函数表。
  3. 类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
  4. 虚函数表是一个存储类成员函数指针的数据结构。
  5. 虚函数表是由编译器自动生成与维护的。
  6. virtual成员函数会被编译器放入虚函数表中。
  7. 当存在虚函数时,每个对象中都有一个指向虚函数的指针(C++编译器给父类对象,子类对象提前布局vptr指针),当进行test(parent *base)函数的时候,C++编译器不需要区分子类或者父类对象,只需要再base指针中,找到vptr指针即可)。
  8. vptr一般作为类对象的第一个成员。
    ①实现多态,父类对象的指针指向父类对象调用的是父类的虚函数,指向子类调用的是子类的虚函数。
    同一个类的多个对象的虚函数表是同一个,所以这样就可以节省空间,一个类自己的虚函数和继承的虚函数还有重写父类的虚函数都会存在自己的虚函数表。

20、为什么要把基类的析构函数定义为虚析构函数?
在使用基类的指针操作派生类时,为了防止执行基类的析构函数,不执行派生类的析构函数,因为这样的删除只能够删除基类对象,不能删除子类对象,形成了删除一半的现象,会造成内存泄漏。

21、为什么调用普通函数的效率比调用虚函数的效率高?
因为普通函数使用的是静态联编,而调用虚函数是动态联编,
联编的作用:程序调用函数,编译器决定使用哪个可执行代码块。

22、创建类对象时使用new和不new的区别
new创建类对象需要指针接收,一处初始化,多处使用;
new创建类对象使用完需delete销毁;
new创建对象直接使用堆空间,而局部不用new定义类对象则使用栈空间;
new对象指针用途广泛,比如作为函数返回值、函数参数、可以赋值给全局变量等;
new创建对象为动态分配内存,从而使对象的可控性增强
频繁调用场合并不适合new,就像new申请和释放内存一样。
使用普通方式创建对象,使用完成之后不需要手动释放,该类的析构函数就会自动执行(但是对象的引用,指向对象的指针离开其作用域时,不会调用析构函数)。而new创建的对象,要调用到delete时才会执行析构函数,否则在程序退出时为delete的话就会出现内存泄漏。
类中的静态成员属于类,不属于类的对象,他们的资源不会被析构函数释放。

23、拷贝构造函数(避免浅拷贝问题):是一个特殊的构造函数,其形参是本类的对象引用。
类对象和普通对象不同,类对象内部结构一般比较复杂,存在各种成员变量。
拷贝构造函数是一种特殊的构造函数,函数名和类名一致,它必须的一个参数是本类型的一个引用变量。
eg:MxStreamManager(const MxStreamManager &)
拷贝构造函数通常用于:
• 通过使用另一个同类型的对象来初始化新创建的对象。
• 复制对象把它作为参数传递给函数。
• 复制对象,并从函数返回这个对象。
什么时候需要用户自定义拷贝构造函数?
一般情况下,当类中成员变量有指针变量、有动态分配内存时常常需要自己定义拷贝构造函数。
什么时候调用拷贝构造函数?
(1)用类的一个对象去初始化另一个对象时,程序中新创建一个对象,并用另一个同类对象对它初始化
(2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用
(3)当函数的返回值是类的对象或引用时
MxStreamManager(const MxStreamManager &) = delete; //删除默认拷贝构造函数,即不能进行默认拷贝
MxStreamManager& operator=(const MxStreamManager &) = delete;//删除默认拷贝构造函数,即不能进行默认拷贝

24、c++ continue、break、return的区别
return 语句的作用
(1) return 从当前的方法中退出,返回到该调用的方法的语句处,继续执行。
(2) return 返回一个值给调用该方法的语句,返回值的数据类型必须与方法的声明中的返回值的类型一致。
(3) return后面也可以不带参数,不带参数就是返回空,其实主要目的就是用于想中断函数执行,返回调用函数处。
break语句的作用
(1) break在循环体内,强行结束循环的执行,也就是结束整个循环过程,不在判断执行循环的条件是否成立,直接转向循环语句下面的语句。
(2) 当break出现在循环体中的switch语句体内时,其作用只是跳出该switch语句体。
continue 语句的作用
终止本次循环的执行,即跳过当前这次循环中continue语句后尚未执行的语句,接着进行下一次循环条件的判断。

25、operator关键字
operator是c++的关键字,它和运算符一起使用,表示一个运算符函数,理解时应该将 operator = 整体上视为一个函数。这是C++拓展运算符功能的方法,可以理解为:一方面要使运算符的使用方法和原来的一致,另一方面拓展其功能只能通过函数的方式实现(c++中“功能”都是由函数实现的);
C++中预定义的运算符的操作对象只能是基本数据类型。但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。运算符重载的实质是函数重载,它提供了C++的可扩展性,也是C++最吸引人的特性之一。
运算符重载是通过创建运算符函数实现的,运算符函数定义了重载的运算符将要进行的操作。运算符函数的定义与其他函数的定义类似,惟一的区别是运算符函数的函数名是由关键字operator和其后要重载的运算符符号构成的。运算符函数定义的一般格式如下:
<返回类型说明符> operator <运算符符号>(<参数表>)
{
<函数体>
}
为什么要使用操作符重载?
对于系统的所有操作符,一般情况下,只支持基本数据类型和标准库中提供的类,对于用户自己定义的类,如果想支持基本操作,比如比较大小,判断是否相等等等,则需要用户自己来定义关于这个操作符的具体实现
怎么声明一个重载的操作符?
A:操作符重载实现为类的成员函数
重载操作符在类体被声明,声明方式和普通的类成员函数一样,只不过它的名字包含关键字operator,以及紧跟其后的一个c++预定义的操作符。eg:bool operator == (const class &cla);
B:操作符重载实现非类成员函数(全局函数)
对于全局重载操作符,代表左操作数的参数必须被显式指定。eg:bool operator == (person const &p1 ,person const & p2);
C:怎么决定使用全局重载操作符函数类成员重载操作符
①如果一个重载操作符是类成员,那么只有当与他一起使用的左操作数是该类的对象时,该操作符才会被调用。如果该操作符的左操作数必须是其他的类型,则操作符必须被重载为全局名字空间的成员。
②C++要求赋值=,下标[],调用(),和成员指向->操作符必须被定义为类成员操作符。任何把这些操作符定义为名字空间成员的定义都会被标记为编译时刻错误。
③如果有一个操作数是类类型如string类的情形那么对于对称操作符比如等于操作符最好定义为全局名字空间成员。
operator可以用作类的转换函数:类可以使用构造函数将其他类型转换成此类的对象;
作为类的转换函数的条件:
①转换函数必须是类方法
②转换函数不能指定返回值
③转换函数不能有参数
eg:
operator int()//定义了一个将类转化为int的转换函数

26、c++内存
内存分配方式:
从静态存储区分配,从栈上分配内存和从堆上分配内存

27、函数调用
函数的调用是在计算机的堆栈中实现的,这是为缓冲区的利用提供了技术基础

28、sizeof和strlen的用法和区别:
sizeof不等同于strlen:p为局部数组变量时,sizeof§为数组的字节长度
p为指针参数传参时,sizeof§为指针的字节长度:4
strlen只能用char*类型作参数,且必须是以’\0’结尾的,strlen返回的字符串的真实长度,但不包括’\0’
字符串拷贝:
当目的内存与源内存区域存在重叠时,memcpy_s不能正确拷贝,这时要使用memmove_s来进行拷贝,来避免内存重叠时产生的问题,但会增加内存开销,并校验返回值确保函数执行正确

29、C++文件操作常用系统函数
std::ifstream是从硬盘到内存,所谓的流缓冲就是内存空间,默认输入方式打开文件
std::ifstream file(pipelineConfigPath.c_str(), std::ifstream::binary);// 以二进制文件形式打开pipelineConfigPath
file.seekg(0, std::ifstream::end);// 读指针位置定位,把文件的读指针追溯到文件尾部
int fileSize = file.tellg(); // 配合seekg使用,可以求出文件的大小为多少bytes,即获取文件流的长度
file.seekg(0); // 把读指针移到文件的最前面,回到流的最前面
std::unique_ptr<char[]> data(new char[fileSize]);
file.read(data.get(), fileSize); // data.get()获取管理对象的指针,从文件中读取fileSize长度到data.get()里
file.close();// 关闭文件流
std::ifstream is_open(); // 判断文件流的打开状态是否正常 返回值为bool型
assess()用于文件读取方面,判断文件是否存在并是否可写
realpath(file_name, absolute_path); // 获取文件的绝对路径,填充到absolute_path,失败返回null,成功则返回指向absolute_path的指针。
FILE *fp = fopen(path, “rb”); // 只读模式打开一个二进制文件
fseek(fp, 0, SEEK_END); // 重新定位流上的文件内部位置指针,从二进制流文件中将当前位置指针移到文件的末尾
long fileSize = ftell(fp); // 用于得到文件位置指针当前位置相对于文件首的偏移字节数,确定文件的当前位置
fseek(fp, 0, SEEK_SET); // 将当前位置指针移到文件的开头
fread(buffer,size,count,fp); // 在fp文件指针中读count个size字节的数据项到buffer指针中

30、string常用的方法
string a = “abcd”;
const char* ch = a.c_str();// string转换成char*,指向一个以’\0’结尾的数组,data()与c_str()类似,但不是返回以’\0’结尾的数组。
将 char*、char[] 转换为 string 类型时,直接进行赋值操作,将 char*、char[] 的变量赋值给 string 对象即可。
说明:这里所说的“赋值”操作,实际上是将 char*、char[] 定义的字符串的首地址赋值给 string 对象了。
size_t count = str.copy(p,n );// 从string类型对象中至多复制n个字符到字符指针p指向的空间中。默认从首字符开始,但是也可以指定,开始的位置(记住从0开始)。返回真正从对象中复制的字符。要确保p指向的空间足够保存n个字符。
auto b = a.back();// 获取字符串的最后一个字符
a.back() = ‘e’;// 修改字符串的最后一个字符为e
b = a.front(); // 获取字符串的第一个字符
a.front() = ‘A’;// 修改字符串的第一个字符为A
a.pop_back();// 删除源字符串的最后一个字符,有效的减少字符串长度

31、inline关键字
为了解决一些频繁调用的小函数大量消耗栈空间(放置程序局部数据的内存空间)的问题,引入了inline修饰符,称为内联函数,建议放置头文件里

32、Linux C++常用的系统函数
char *getenv(const char *name);获取环境变量,name为请求变量名称的字符串
int setenv(const char *name,const char * value,int overwrite); 改变或者增加环境变量,name为环境变量的名称,value为内容,如果overwrite不为0则改变环境变量原有内容,为0则不改变,忽略value的值。
getline(cin, inputline); 读取整行,包括前导和嵌入的空格,并将其存储在字符串对象中;cin是读取输入流,inputline是接收输入字符串的string变量
istringstream:串流的输入操作,从string对象的str中读取字符;
istringstream is(str);//创建对象is,并使is和字符串str绑定
string tmp;
while (is >> tmp) { //将从str中读取到的字符串is写入到字符串tmp中
cout<<tmp;
}
max_element(); 返回容器中最大值和最小值的指针。用来查询最大值所在的第一个位置;
eg:
vector.push_back(tmp);
cout << *max_element(vec.begin(), vec.end()) << endl;

33、C++ json

34、C++ 11新特性
decltype关键字:有时我们希望从表达式的类型推断出要定义的变量类型,但是不想用该表达式的值初始化变量(如果要初始化就用auto了)。为了满足这一需求,C++11新标准引入了decltype类型说明符,它的作用是选择并返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
unique_lock:互斥锁保证了线程间的同步,但是却将并行操作变成了串行,对性能造成了很大影响,所以我们要尽可能的减少锁定区域,使用细粒度锁。
std::function<void()> func:类模板是一种通用的、多态的函数封装,是一个函数对象类,它包装其他任意的函数对象,被包装的函数对象具有类型为T1,….,TN个 参数,并且返回一个可转换到R类型的值。
最简单的理解就是:通过std::function对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象;让我们不再纠结那么多的可调用实体。一切变的简单粗暴。
std::thread:c++11之后的标准的线程库
=default和=delete:
=default:显示缺省,告诉编译器生成函数默认的缺省版本;
=delete:显示删除,告诉编译器不生成函数默认的缺省版本。

35、C++ map 常用方法
count函数:返回容器中某一个元素出现的次数,即返回key出现的次数,但是map中的key是不能重复的,所以只能是返回1(存在)和0(不存在)
find函数:返回被查找元素的位置,没有则返回map.end()

36、C++ typeid关键字
typeid是操作符,不是函数,类似于sizeof操作符。用于判断表达式的类型
typeid(变量).name();// 运行时获取变量的类型名称

37、C++ 11锁管理器
互斥锁(mutex):用于控制多个线程对他们之间共享资源互斥访问的一个信号量,也就是避免了多个线程在同一时刻同时操作一个线程资源。
条件锁:就是条件变量,某一个线程因为某个条件为满足时可以使用条件变量使改变程序处于阻塞状态
锁管理器在构造函数中自动绑定它的互斥体,并在析构函数中释放它,这大大减少了死锁的风险,因为运行时会处理互斥体。
c++11锁管理器有两种:std::lock_guard 和 std::unique_lock
std::mutex m;
std::lock_guardstd::mutex lockGuard(m);
在对象构建时将mutex加锁,析构时解锁
当生命周期离开临界区时,它的生命周期就结束了,确切的说在这个时间点,std::lock_guard的析构函数被调用,互斥体被释放了。
而std::unique_lock的用法比std::lock_guard更加强大

38、智能指针:是模板类,而不是指针
https://blog.csdn.net/shaosunrise/article/details/85158249
自动管理内存的思路:
①一个专门类管理指针信息
②类被析构的时候自动释放管理的指针
share_ptr和unique_ptr:
share_ptr的意思是这个指针是共享的,大家都可以使用,允许多个指针指向同一个对象;这个里面使用一个引用计数来管理使用者的数目。
两种初始化方式:
①构造函数初始化:
std::shared_ptr pointer(new int(1));
std::shared_ptr pointer1 = pointer; // 拷贝构造
std::shared_ptrstd::string ss(new std::string(“AAA”));
std::shared_ptrstd::string ptr = std::shared_ptrstd::string(new std::string(“AAA”));
②std::make_shared 初始化:
std::shared_ptr p3 = std::make_shared();
std::shared_ptr p2 = std::make_shared(“hello”);
//auto关键字代替std::shared_ptr,p5指向一个动态分配的空vector
auto p5 = make_shared<vector>();
std::shared_ptr foo = std::make_shared (10); or auto bar = std::make_shared (20);
③rest初始化:
std::shared_ptr pointer = nullptr;
pointer.reset(new int(1));
使用get()函数向智能指针索要所指向对象的拥有权:
auto p = make_shared(42);
int* iPtr = p.get(); // 所有权转移给普通指针之后,此时p所指向的内存已经被iPtr释放
unique_ptr的意思是这个指针只有我自己能够使用,别人都不能用,独占所指向的对象,这种所有权可以通过std::move()来转移;一个指针只能被一个类管理,所以unique_ptr不支持拷贝和赋值(底层是浅拷贝)。
当定义一个unique_ptr指针时,需要将其绑定到一个new返回的指针上,初始化unique_ptr必须采用直接初始化的方式;c++14引入了make_unique
std::unique_ptrMxStream::MxStreamManager mxStreamManager_; // 创建一个空的智能指针
mxStreamManager_ = std::unique_ptr (new MxStreamManager);
MxStreamManager *p = mxStreamManager_ .get(); // 通过get()函数获取管理对象的指针。
mxStreamManager_.reset(); // 在 unique_ptr 对象上调用reset()函数将重置它,即它将释放delete关联的原始指针并使unique_ptr对象为空。
std::unique_ptr taskPtr2(new Task(55)); // 通过原始指针创建 taskPtr2
std::unique_ptr taskPtr4 = std::move(taskPtr2); // 把taskPtr2中关联指针的所有权转移给taskPtr4
Task * ptr = taskPtr5.release(); // 释放关联指针的所有权 ,ptr拥有对象的所有权,unique_str为空
智能指针转换:
std::static_pointer_cast():静态指针类型转换
限制条件:①c++11、②share_ptr、③一般只用在子类父类的继承关系中,当子类中要获取父类中的一些属性时(子类通过多态拥有自己的父类继承来的属性和行为,但是还想知道父类相应的属性和行为,这时,将父类的shared_ptr通过static_pointer_cast转化为子类的shared_ptr,这样就可以使得子类可以访问到父类的方法);
当指针是智能指针的时候,需要向上转换,用static_cast 则转换不了,此时需要使用static_pointer_cast。
基类和派生类的智能指针转换要使用std::dynamic_pointer_cast和std::static_pointer_cast

39、C++多线程编程
#include <pthread.h>
pthread_t threadId_;
// create thread
int createThreadErr = pthread_create(&threadId_, nullptr, ThreadFunc, (void *)this); // 参数含义:线程id;线程参数;线程运行函数的起始地址(函数指针);运行的函数参数
pthread_exit( NULL ); //等待各个线程退出后,进程才结束,否则进程强制结束,线程处于未终止的状态
当线程调用到的函数在一个类中时,那必须将该函数声明为静态函数,因为静态成员函数属于静态全局区,线程可以共享这个区域,所以可以各自调用
异步调用和并发执行:异步调用指的是某个函数在某个其他地方被调用,而并发执行是在执行的时候一起执行,一个是编译的概念,一个是运行时
c++ 11 std::thread
join():主调线程阻塞,等待子线程终止,然后主调线程回收被调线程资源,并继续运行;
detach():主调线程继续运行,子线程驻留后台运行,主调线程无法再取得该被调线程的控制权。当主线程结束时,子线程也会退出。所以通常情况下主线程等待子线程,
一旦detach之后不能在用join,最常用的是要控制线程的生命周期。
joinable():判断是否可以成功使用join()或者detach(),复杂的代码时候需要用到、
类thread创建方式:
1.类静态成员函数
thread_ = std::thread(CallbackFunc, this);
2.lamdba表达式
thread_ = std::thread(& {});
3.类成员函数bind
thread_ = std::thread(&CThread::CallbackFunc, this);

40、C++11 tuple和pair
tuple是一个固定大小的不同类型值的集合,是泛化的std::pair,通常当成一个结构体来使用,不需要创建结构体又获取结构体的特征,在某些情况下可以取代结构体使程序更简洁,直观。std::tuple理论上可以有无数个任意类型的成员变量,而std::pair只能是2个成员,因此在需要保存3个及以上的数据时就需要使用tuple元组了。
tuple的创建和初始化:
std::tuple<string &, int> tpRef(name, 30);
std::get<0>(tpRef) = “Sven”; // 对tpRef第一个元素赋值,同时name也被赋值 - 引用
std::pair:主要的作用是将两个数据组合成一个数据,俩数据的数据类型可以是同一个,也可以是不同的类型。pair的实质是结构体
初始化方式:
std::pair<int, float> pair_test(1, 1.1);
pair_test.first = 1; // 直接使用first和second
pair_test.second = 1.1;
pair_test = std::make_pair(1, 2.1); // 可以使用make_pair对已存在的两个数据构造一个新的pair类型

41、回调函数
回调函数也是普通函数,为什么叫回调函数呢?是因为程序员通过参数把该函数的函数指针传递给了其他函数,在哪个函数里面调用这个函数指针就相当于调用这个函数,这个过程
就叫作回调,而被调用的函数就叫回调函数,回调的本质是函数指针传递

42、c++宏定义函数
宏函数的优点在于避免函数调用,提高程序效率
#define MX_PY_PLUGIN_GENERATE (class_name)
MxPluginBase *CreatePluginInstance()
{
MxPluginBase *pBase = newclass_name();
return pBase;\ //此处的return用于
}

/* There is some amount of overlap with <sys/types.h> as known by inet code */
#ifndef __int8_t_defined

define __int8_t_defined

typedef signed char int8_t; INT8_MAX=127 INT8_MIN=-128
typedef short int int16_t; INT16_MAX=32767 INT16_MIN=-32768
typedef int int32_t; INT32_MIN=-2147483648 INT32_MAX=2147483647

if __WORDSIZE == 64

typedef long int int64_t; INT64_MAX=9223372036854775807 INT64_MIN=-9223372036854775808

else

extension
typedef long long int int64_t;

endif

#endif

/* Unsigned. */
typedef unsigned char uint8_t; UINT8_MAX=255
typedef unsigned short int uint16_t; UINT16_MAX=65535
#ifndef __uint32_t_defined
typedef unsigned int uint32_t; UINT32_MAX=4294967295

define __uint32_t_defined

#endif
#if __WORDSIZE == 64
typedef unsigned long int uint64_t; UINT64_MAX=18446744073709551615
#else
extension
typedef unsigned long long int uint64_t;
#endif

这篇关于C++总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!