这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战
c++的新功能:模板、面向对象
inline与defind区别
inline是声明内联函数,在编译的时候会直接把调用该函数的地方直接编译替换掉 ,避免频繁使用栈
defind声明的变量在使用的地方相当于字符串替换
\
new、delete、malloc、free
new、delete、free:
- free,只是会进行内存的操作,即释放内存。new会调用构造函数,delete会调用析构函数
- new、delete是c++运算符,会执行对应的构造与析构函数
- malloc、free为库函数,只涉及内存的分配问题
构造函数调用顺序:基类-》子类
析构函数调用顺序:子类-》基类
\
多态、虚函数、纯虚函数
参考:zhuanlan.zhihu.com/p/41309205
- 虚函数:声明时前面加virtual,子类可以(或不)进行overwrite
- 纯虚函数:虚函数声明时在结尾加上=0,子类一定要进行overwrite
- 子类和父类都实现某个相同的成员函数的时候,默认调用的是父类的成员函数
- 多态,调用的虚函数取决于指针对象指向的类型,即使向下类型转换后也是一样
- 析构函数最好在基类中声明为虚函数,否则在进行析构的时候只会调用基类的析构函数
函数、虚表是放在一个全局的代码区,为类公有
成员变量、虚表指针是在堆区,属于每一个对象,因此
int main()
{
B bObject;
A *p = & bObject;
}
时,p对象的虚表指针指向的是b的虚表,如果在函数调用中使用这种机制,只有在运行时才能够知道当前对象是什么类,在运行时进行的动态绑定
\
epoll
\
智能指针
传统指针在堆上分配内存使用之后,整个的生命周期就交给了用户,需要用户在恰当的时候进行内存的释放
智能指针为用户提供了自动销毁机制
使用计数的方式来判断释放的时机,当计数为时进行资源的释放
基础
- 为一个模板类,同时具有指针的基本功能
- 目的是管理堆对象,那些不能够自己释放资源的对象
类别
- auto_ptr
在拷贝、赋值构造的时候,进行对象的传递,原来的智能指针变成null
- unique_ptr
有唯一拥有权,计数永远为1
禁止了复制、赋值语义
使用move函数将一个对象的堆内容转给另外一个
可以指定析构内容,默认是调用对象的析构函数,在初始化指针的时候可以传对应的释放规则进去
- share_ptr
shared_ptr<T> p(q) // p和q指向同一个内存区域,相互关联
多个智能指针之间可以共享对象,相应的共享的时候,计数值会+1,每个指向该对象的指针析构的时候,计数-1,最后一个对象析构的时候会全部释放
跳出作用域的时候会进行相关对象的析构
赋值、初始化使用到的对象会对应的+1计数
enable_shared_from_this可能会造成循环引用的情况,它会返回一个share_ptr
- weak_ptr
weak_ptr不控制对象的生命周期
不会引起计数器的变化
可以由weak_ptr以及share_ptr进行初始化
不具有普通指针的访问操作功能
\
stack overflow
堆栈溢出,在进行递归调用的时候,函数内部的局部变量会一直保存直到这个过程结束
\
static
- 隐藏多个文件里面声明的变量,变为非全局变量,只有当前文件可见
- 修饰局部变量的时候,该局部变量只初始化一次,默认值为0,分配在全局数据区
\
STL
- vector:底层为数组
- list:底层为链表
- map:红黑树,
- unordered_map:hash table
实现
\
模板(泛型编程)
参考:
r00tk1ts.github.io/2018/12/03/…
函数模板
-
编译器遇到一个模板的时候,并不生成代码,只有在实例化模板(进行类型确定与执行)的时候,编译器才生成代码
-
支持非类型模板参数\
-
- 表示一个值而非一个类型
- 实例化时,由编译器推断或者用户给出的值所代替,这些值为常量表达式
\
-
编译过程\
-
- 第一阶段:编译模板本身,错误较少
- 第二阶段:使用模板的部分会进行调用参数的检查
- 第三阶段:模板实例化,包括推断的类型是否支持某一些操作
\
类模板
类模板:如STL中的vector等,需要显式指定模板类型
函数模板:支持推断的同时也支持显式指定模板类型
-
实例化:\
-
- 会对特定类型实例化出一个类
- 不同的类型会实例化出不同的类
\
- 内部进行成员声明,可以不带上模板类型,在外部进行声明,需要带上模板类型
- 成员函数如果不被调用是不会被实例化的
成员模板
类内部包含了为模板的成员(这些成员被称为成员模板),不能是虚函数,在使用上和模板函数没有太的区别
- 普通类的成员模板
- 模板类的成员模板
控制实例化
当多个源文件使用了同一个模板,并且提供了相同的参数,在进行实例化的时候,会实例化出多份相同的类代码
c++11后可以通过显式声明的形式避免这种开销
extern template class Blob<string>; //声明
template int compare(const int&, const int&); //定义
重载与模板
函数模板可以被其他函数模板或者普通函数重载,在实际调用的时候,优先取匹配较好的函数
当所有匹配都一样的时候
- 优先选择普通函数
- 优先选择特殊化的模板
\
++
i++与++i
i++:先返回引用再增加
++i:先增加,再返回引用
i++优先级更高
效率
对于内建类型(int、float、double)而言,效率相同转换成的汇编代码数量、操作命令都相同,只是执行顺序不一样
对于自建类型,包括STL,i++效率更低,++i可以直接返回自己本身,而i++需要返回中间的临时变量(是增加之前的一个备份,右值)
\
左值与右值
所有表达式非左值即右值
- 右值只能放在等号右边,左值则是两者皆可
- 左值可以取地址,代表内存中的某个位置(如变量)
- 右值只是一个值,不能够去取地址,如常量值(数字、字符串)、返回的函数内部的变量(临时变量,在函数结束后会进行释放操作,将值压入栈中返回)
引用折叠
在c++中引用的引用是不合法的,任何含有一个&(左值引用的多次引用)都会坍缩为左值引用,除非多次引用都为&&(右值引用)
左值引用、常引用与右值引用
左值引用:简单地对左值进行获取存储位置的操作(别名引用)
int a = 22;
int& j = a;
j = 4;
常量引用:会在内存中产生右值的临时对象,对该临时对象进行操作
const int& j = 22;
右值引用(在c++11引入):常量引用无法进行修改等操作,可以通过右值引用的方式进行修改
int&& j = 22;
j = 4;
万能引用:可以接受左值,也可以接受右值
-
T类型可以是引用,加上引用折叠,万能引用可以是左值引用,也可以是右值引用\
-
- 传入右值引用,T推导为MyClass
- 传入左值引用,T推导为MyClass&(运用引用折叠)
- 在内部进行调用其他函数的时候,以上两种情况,都会作为左值引用(左值)的方式进行传参调用,如果强制转为右值引用,又只会调用右值对应的传参
- 当需要根据传进来的引用类型进行函数调用匹配的时候,可以使用完美转发
\
#include <iostream>
using namespace std;
template<typename T>
void func(T&& val)
{
cout << val << endl;
}
int main()
{
int year = 2020;
func(year);
func(2020);
return 0;
}
完美转发
void print(const int& t) //左值版本
{
cout <<"void print(const int& t)" << endl;
}
void print(int&& t) //右值版本
{
cout << "void print(int&& t)" << endl;
}
template<typename T>
void func(T&& val)
{
print(std:forward<T> val);
}
实际上也是一个强制类型转换的过程,将val转换为T类型,相当于
static_cast<T&& &&>(val)
参考:www.cnblogs.com/5iedu/p/113…
std::move()函数
(c++11):将左值、右值转为右值引用,内部以强制类型转换的方式进行实现,以用于移动语义。
三目运算符根据内部返回类型决定是否是左值函数
\
默认的几种类函数
样例类:
class MyClass{
public:
MyClass(): chars(nullptr), ints(nullptr), doubles(nullptr);
~MyClass(){
if(this.chars != nullptr)free(chars);
if(this.ints != nullptr)free(ints);
if(this.doubles != nullptr)free(doubles);
}
private:
char *chars;
int *ints;
double *doubles;
}
包括构造函数、拷贝构造函数、拷贝赋值函数、移动赋值函数、移动构造函数、析构函数
参数传递是调用拷贝构造函数,如果是const则是同一个变量,而不是拷贝
移动语义的作用:避免额外内存的创建,减少不必要的拷贝操作
这里主要对拷贝构造函数、拷贝赋值函数、移动赋值函数以及移动构造函数进行分析
-
移动构造函数\
-
- 将参数中对象的数据转移给自己,并且解除参数中对象与其数据的关系
- 不会进行内存的操作,只是进行简单的资源转移,一般是指针的转移
- 转移后原本的对象并没有销毁,销毁的时候还是调用对应的析构函数
\
MyClass(MyClass&& item): chars(item.chars), ints(item.ints), doubles(item.doubles){
item.chars = item.ints = item.doubles = nullptr;
};
- 拷贝构造函数
MyClass(const MyClass& item): chars(item.chars), ints(item.ints), doubles(item.doubles);
当两者中有一者不存在的时候,移动构造函数可以降级为拷贝构造函数,而拷贝构造函数不可降级(即隐式删除拷贝构造函数)。
- 移动赋值函数
MyClass& operator=(MyClass&& item){return *this};
- 拷贝赋值函数
MyClass& oprator=(const MyClass& item){return *this};
参考:
\