I 补充:c系语言的编译过程
预编译器把头文件和宏定义无脑插入到文件里面
编译器把它翻译成汇编语言
汇编器把所有文件翻译为二进制文件
链接器是把各模块文件(已经是二进制文件)链接起来,比如其他文件内部定义的函数,使之成为exe可执行文件
II 模版与元编程
模版作用:
可以使得类型参数化,根据后面传入的参数确定类型
写模版需要先告诉编译器我要写模版函数了,关键字template
函数模版
基本语法
template<class T> 只对下面一个函数起作用
Void myswap(T&a,T&b){
T temp;
Temp=a;
A=b;
B=temp;
}
这时候该函数就成为一个模版函数,你在传参时编译器会自动替换T类型为你的参数类型,就很省心
其中的任何一个T都可以用具体的类型替换,在模版里就是那个类型,这样可以实现某些形参根据用户输入决定,另外一些则固定,实现部分模版
例如
Int a=10;
Int b=20;
Myswap(a,b);
Double aa=10.0;
Double bb=2,1;
Myswap(aa,bb);
Myswap就可以正常运行
辨析函数重载和函数模版
重载是同名内部不同逻辑实现,手动书写内部逻辑
模版是同名内部逻辑相同参数列表不同自动生成对应版本的代码
函数模版和普通函数的区别
函数模版不可以自动类型转化(两个必须同一个类,因为都是class T)
普通函数则可以自动类型转化(函数类型时int char 传char int会自动转化为int char)
编译器会优先选择普通函数
模版函数可以重载
###
函数模版与函数真正调用
函数模版不是一个函数,只是一个函数模版,需要具体函数去调用
函数模式实现的原理
根据模版编译器给你生成一个函数定义并且调用,编译器先看函数声明,从中得到T的类型,接着生成对应的函数代码,然后到调用处替换
第一次编译是把函数模版的声明进行编译,避免声明都写错了
第二次编译是在main函数里调用的时候编译器会根据你调用的类型去编译出一个函数定义来,并且直接调用
类模版
基本语法
模版必须显式指定成员类型
也就是T依赖于你手动指定
类派生普通类
B是已定义的类模版
Class a:public b<int>{
}
类派生类模版
Animal 是类模版
Template<class T>
Class Cat:public Animal<T>{};
类模版的不同位置实现
类模版有类内实现和类外实现
类内是函数都在类内
类外是使用友元
类模版类外实现
注意作用域,类模版指定类型,
函数模版不能和普通函数做声明和定义
类模版在类外派生类模版和函数模版:需要注意派生的东西本身也是模版,需要加模版关键句
类模版派生的类模版属于的类型 需要加<T> 儿子类模版的参数是person &T; person是爸爸类模版
例子二
当你把类成员函数放到类外面都要加作用域以及类模版类型
不要滥用友元
类模版头文件和cpp文件分离编写
在调用的main函数里,编译器发现诶你有一个函数调用,但是main函数没找到,那肯定在其他cpp文件了,于是留下一个符号让连接器找,另外的一个cpp文件里是模版函数的定义,里面有main函数需要的定义,但是他只是一个模版,没有生成具体函数定义,还晒满足不了main函数需求,模版机制是只有调用的时候才生成,但main函数调用需要定义,偏偏定义需要调用才生成,于是矛盾了,无法运行
把模版定义文件改名为hpp后缀,再在main函数里include“hpp”文件
h文件不动
类模版中有static
类模版生成一个具体类,具体类有多个对象,a被这个具体类的多个对象所共有
III 值类别与std:move移动语义
move本身并不移动,而是标记上这个需要移动,底层开发者写好移动函数并且写好看到标记就触发移动语义的逻辑,这样一起才能实现移动语义
值类别:
1.纯左值(lvalue):能够明确通过地址访问或直接访问(可被修改),有明确地址的量,比如一个变量,一个栈上的对象
2.纯右值(prvalue):只关心它的值,而不关心访问它或地址的量,不能被赋值修改,比如函数返回值,字面量
3.将亡值(xvalue):返回右值引用的函数的值就是将亡值,既有左值的特点也有右值的特点
本质上是左值,只是贴上一个语义标签
右值引用和左值引用在本质上是一样的都可以通过他们访问和修改变量是因为右值引用也能接受本质上是左值的xvalue
但是在语法上右值引用可以接受xvalue(将亡值)也可以接受纯右值,但要触发移动语义必须接受将亡值
但是左值引用只能接受纯左值
ra和rra本质上一样都是a的别名,都可以访问a 但是两者语义标签不同
但是创建时rra只能接受move返回的a,因为右值引用需要右值或者将亡值通过move获得的xvalue
ra则只能接受a这个左值
下图结果是
10
11
12
如何理解ra(左值引用)和rra(右值引用)的语义标签不同
rra这个右值引用和ra这个左值引用分别交给构造函数时本身各自就带有语义,构造函数看到右值引用(接受了xvalue的右值引用)就会触发移动语义,调用移动构造移动资源所有权(注意是所有权)
而看到左值引用则调用拷贝构造,两个量都有资源所有权
move是一种承诺,调用者通过调用move把变量设为将亡值,构造函数提前在底层做好两种情况的准备,这样构造函数看到将亡值就知道可以移动资源所有权了
std:move移动语义
是一种协议,底层构造函数要写好两个构造一个移动,一个拷贝
上层调用者参数写入将亡值(调用move)触发移动构造
这样总体实现了移动语义,move本身并不移动,而是标记上这个需要移动,底层开发者写好移动函数并且写好看到标记就触发移动语义的逻辑,这样一起才能实现移动语义
move
这个方法返回的是右值引用,内部源码只做了一件事,把传入的指针/引用强制转换为右值引用并返回,除此之外什么也没做
移动语义即是把所有权移动走,不改变底层资源的位置,只是把谁能访问的权限给了别人,给了谁,给了左边的接收者
如果使用移动语义去构造对象(例如容器)在底层他会把原对象访问底层资源的权限交给新对象,这里的权限就是内部的指针比如vector的begin迭代器的内容交给新对象,原来对象的迭代器则置空,这就相当于原来的容器对象无法访问底层资源了,但底层资源还在原来那里。-----这也对应了move除了返回原指针右值引用什么也没做
那为什么返回右值引用就能让构造函数把权限转移呢,这是因为移动语义会触发移动构造函数而非拷贝构造函数,原来的对象权限就被转移走了
也就是移动构造函数会在传入右值引用时触发,move方法正好返回的是右值引用,所以触发了移动构造
移动构造函数的实现法则
移后源是指被移动的源对象,也就是移动构造函数中一定要写上把原来的对象变成可析构状态,即析构后程序安全状态在vector中就是把源对象的begin等指针置为空,因为它是实际开发中是实打实的将亡值(个人练习中可能不会销毁),是移动后很快会被销毁被析构的,如果在析构时没有begin置空,就会导致底层资源也被释放,从而程序崩溃,移动也没意义了,资源丢失。
IV 智能指针
定义:
智能指针是裸指针的封装,可以在一定程度上 帮程序员释放资源,不用担心内存安全问题
但是并不能完全代替裸指针(如果追求效率和性能,有时候仍需要使用裸指针),同时也无法完全解决内存安全问题
分类:
unique_ptr 独占指针
shared_ptr 计数指针
weak_ptr 弱指针
独占指针
创建方式
不推荐通过已有裸指针创建,如果用,建议之后把裸指针释放
官方推荐通过std::make_unique创建
get方法返回的是指向的内存的地址
直接对指针取地址是指针的地址
可以用get方法得到地址
创建对象推荐通过大括号创建
独占指针与函数调用
注意:独占指针只能Move不能copy,再做函数参数或者返回值时一定要注意所有权
这里先创建了一个独占指针指向一个cat类的对象ff
接着要把这个独占指针传给函数调用,注意不能直接把c1作为参数,在c++的默认语法中,函数传参是值的拷贝,但是独占指针不允许值拷贝,因为 “独占性” :这个内存空间只能由一个独占指针指向,如果发生值拷贝,就会再次创建一个独占指针指向原来的内存空间,破坏了独占性,因此必须把这块内存空间的所有权交给形参的那个独占指针,也就是把c1放入move方法作为参数,此时move将所有权交给形参
再往下细看,此时c1不具有那块内存空间的所有权,因此不能用c1调用成员函数,编译可过但运行时即崩溃
注意这样的对象是一个匿名对象,在函数结束后生命周期就结束了,会执行析构函数
这样调用函数就可以直接传智能指针了
这里的意思是建议写参数类型是const,则这个指针无法修改指向,因此就可以避免用reset(),因为不加const,一旦误用reset,智能指针会被清空,不再指向原来的内存并且会顺带着释放掉指向的内存
计数指针(共享指针)
与独占指针不同的是他可以共享数据,也就是一个内存可被多个指针指向
并且可以被值拷贝,也就是多个指针指向同一个内存
并且内置了一个计数器记录着这个内存被多少个共享指针指向着,销毁一个同类就是计数器减一,增加一个同类就是计数器加一
想知道计数指针有几个,指针.调用use_count()即可
使用make_shared<T>(内容);
想获得指向的对象,用解引用
在有多个指针存在的前提下,置空其中一个,调用被置空的指针的计数方法,会返回0,其他的指针调用则会返回当前仍存的指针的数量,即减去被置空的那一个
指向一个cat对象,默认构造
调用reset方法,会置空该指针,但不会释放内存
程序结束后自动释放内存,无论有多少个技术指针,只释放一次,因为只有一个内存区
计数指针与函数调用
可直接传值,即copy,在函数内部有一个局部的计数指针
在函数内部调用use_count 计数器会加一,出函数后则不会算上函数内部copy的
可以加const,这样在内部就不可以改变指针的朝向
技术指针与独占指针辨析
辨析1:
不可将计数指针转换为独占指针,这样破坏了独占指针的独占性
辨析2:
可通过std:move将独占指针转换为计数指针
move方法是一个转移所有权的函数,参数是要被转移的指针,返回值是新的拥有所有权的指针,可用其他指针接收
常见的设计模式:函数返回独占指针
这里编译器用的是移动语义,会判断返回值能否move给接收值,如果可以,自动执行move
weak_ptr
注意:所有权问题
weak_ptr不具有内存所有权,无法调用箭头函数和解引用
他一般与计数指针共生
为什么会有弱引用指针:
循环依赖:person类,1对象中存储了他的朋友2 2对象中也要存储他的朋友1,如果用计数指针指向朋友,离开作用域后两个计数指针都无法变为0,无法销毁,析构函数无法执行
弱引用指针用来观察对象是否存活,而不拥有对象的管理权
创建方式:
无法单独创建即无make函数,必须先创建计数指针,然后用自身构造函数里传入计数指针才行
std::weak_ptr<A> p1(p0);
use_count方法
弱引用可以调用use_count方法,但是无法使计数器加1
lock方法:
ORIGINAL CITATION & REFERENCES
评论交流
0实践经验 · 技术讨论
登录读者账号后,可参与交流与回复。
登录后评论↗注册账号 ↗围绕本文内容分享经验或展开讨论。