C++ 引入了四种不同的强制转换运算符以进行强制转换:
C语言强制类型转换缺点:
主要是为了克服C语言强制类型转换的以下三个缺点。
#include
#include
int main(int argc, char const *argv[])
{
const int n = 5;
const std::string s = "inception";
// 错误
std::string t = const_cast(s);
// 错误
int k = const_cast(n);
return 0;
}
可以利用const_cast转换为同类型的非const引用或者指针
#include
#include
int main(int argc, char const *argv[])
{
const int n = 5;
// const_cast只针对指针,引用,this指针
int* k1 = const_cast(&n);
*k1 = 22;
std::printf("n:%d k1:%d\n", n, *k1);
int& k2 = const_cast(n);
k2 = 33;
std::printf("n:%d k1:%d, k2:%d\n", n, *k1, k2);
}
常成员函数中去除this指针的const属性
#include
#include
class CTest
{
public:
CTest(): m_nTest(2){}
// 常成员函数(不能修改成员变量值)
void foo(int nTest) const{
// m_nTest = nTest; 错误
const_cast(this)->m_nTest = nTest;
}
public:
int m_nTest;
};
int main(int argc, char const *argv[])
{
CTest t;
t.foo(1);
return 0;
}
基于等价于隐式转换的一种转换运算符,可使用于需要明确隐式转换的地方。可以用于低风险的转换。
不可以用于风险高的转换
#include
class CInt{
public:
operator int(){
return m_nInt;
}
public:
int m_nInt;
};
int main(int argc, char const *argv[])
{
int n = 5;
float f = 10.30f;
// 本质上,发生了隐式转换
f = n;
std::printf("f:%d, n:%d\n", f, n);
// 整型和浮点型
f = static_cast(n);
std::printf("f:%d, n:%d\n", f, n);
// 字符与整型
char ch = 'a';
n = static_cast(ch);
std::printf("ch:%c, n:%d\n", ch, n);
// void* 指针的转换
void* p = nullptr;
int* pN = static_cast(p);
// 转换运算符的方式
CInt nObj;
int k1 = nObj;
int k2 = static_cast(nObj);
return 0;
}
static_cast 用于基类与派生类的转换过程中,但是没有运行时类型检查。
#include
class CFather{
public:
CFather(){
m_nTest = 2;
}
virtual void foo(){
std::cout << "CFather()::void foo()" << std::endl;
}
public:
int m_nTest;
};
class CSon: public CFather
{
virtual void foo(){
std::cout << "CSon()::void foo()" << std::endl;
}
};
int main(int argc, char const *argv[])
{
CFather* pFather = nullptr;
CSon* pSon = nullptr;
// 父类转子类型(不安全)
// pSon = pFather; // 不能通过
pSon = static_cast(pFather); // 能通过,不安全,没有提供运行时的检查
// 子类转父类 (安全)
pFather = pSon;
pFather = static_cast(pSon);
return 0;
}
用于具有虚函数的基类与派生类之间的指针或引用的转换。
常见的转换方式:
#include
class CFather{
public:
CFather(){
m_nTest = 2;
}
virtual void foo(){
std::cout << "CFather()::void foo()" << std::endl;
}
public:
int m_nTest;
};
class CSon: public CFather
{
virtual void foo(){
std::cout << "CSon()::void foo()" << std::endl;
}
public:
int m_nSon;
};
int main(int argc, char const *argv[])
{
CFather f;
CSon s;
CFather* pFather = &f;
CSon* pSon = &s;
// 向上转换 子类装父类
// pFather = static_cast(pSon);
// 向下转换 父类转子类 (不安全)
// pSon = static_cast(pFather);
// pSon->m_nSon = 2; // 越界
// 检查出这种转换是不安全的
// 运行时,检测出被转换的指针的类型(依赖RTTI)
// 有额外的开销,一般而言只有向下转换是,才必须使用
pSon = dynamic_cast(pFather);
if(pSon != nullptr){
pSon->m_nSon = 2;
}
具有多态类型的向下转换是必须使用,其余情况可以不使用
return 0;
}
用于进行各种
lambda表达式 是一个源自阿隆佐.邱奇的术语。
lambda表达式是C++11中最重要的性特性之一,而lambda表达式,实际上就是提供了一个类似匿名函数的特性,而匿名函数则是需要一个函数,但是又不想费力去命名一个函数情况下去使用的。这样的场景其实很多很多,所以匿名函数几乎是现代编程语言的标配。
lambda表达式的基础语法如下:
[捕获列表](参数列表)mutable(可选)异常属性->返回类型{
//函数体
}
[caputrue](params)opt->ret{body;};
#include
int main(int argc, char const *argv[])
{
//lambda表达式就是匿名函数
//[]捕获列表 ()参数列表 -> 返回值
int c = [](int a, int b)->int{
return a + b;
}(1, 2);
std::printf("c:%d\n", c);
auto f = [](int a, int b)->int{
return a + b;
};
c = f(4, 9);
std::printf("c:%d\n", c);
// 函数式编程 多线程,并发
c = [](int a)->int{
return [a](int b)->int{
return a + b;
}(19);
}(3);
std::printf("c:%d\n", c);
auto f1 = [](int a){
return [a](int b){
return a + b;
};
};
c = f1(11)(22);
std::printf("c:%d\n", c);
return 0;
}
#include
int main(int argc, char const *argv[])
{
int t = 10;
// 按值捕获
auto f = [t]() mutable{
return ++t;
};
auto f1 = [t]() mutable{
return ++t;
};
std::cout << f() << std::endl;
std::cout << f1() << std::endl;
std::cout << f() << std::endl;
std::cout << f1() << std::endl;
std::cout << t << std::endl;
return 0;
}
所谓捕获列表,其实可以理解为参数的一种类型,lambda表达式内部函数体在默认情况下是不能够使用函数体外的变量的,这时候捕获列表可以起到传递外部数据的作用,根据传递的行为,捕获列表也分为以下几种
总结一下,捕获提供了lambda表达式对外部值进行使用的功能,捕获列表的最常用的四种形式可以是:
#include
int main(int argc, char const *argv[])
{
int t = 11;
// t = 33;
// 按值捕获,捕获的是声明匿名函数时,捕获列表参数的值
auto f = [t]{
std::cout << t << std::endl;
};
t = 33;
f();
// 按引用捕获
auto f2 = [&t](){
std::cout << t << std::endl;
t = 99;
};
f2();
std::cout << t << std::endl;
int a = 1;
int b = 2;
// 捕获多个变量
[a, b](){
std::printf("a+b=%d\n", a + b);
}();
// 按值捕获所有变量
[=](){
std::printf("a+b=%d\n", a + b);
}();
// 按引用捕获所有变量
[&](){
a++;
b++;
}();
return 0;
}
#include
#include
#include
int main(int argc, char const *argv[])
{
std::vector v = {1, 3, 4, 5, 6};
for(int i = 0; i < v.size(); i++){
if(v[i] % 2 == 0){
std::printf("偶数:%d\n", v[i]);
}else{
std::printf("奇数:%d\n", v[i]);
}
}
std::printf("=================\n");
std::for_each(v.begin(), v.end(), [](int n){
if(n % 2 == 0){
std::printf("偶数:%d\n", n);
}else{
std::printf("奇数:%d\n", n);
}
});
return 0;
}
(1) std::function
#include
#include
#include
class CTest{
public:
CTest(){}
int MyTest(int n){
std::printf("CTest n:%d\n", n);
return n;
}
// 防函数
int operator()(int n){
std::printf("operator n:%d\n", n);
return n;
}
};
int test(int n){
std::printf("test n:%d\n", n);
return n;
}
void test2(int a, int b, int c){
std::cout << a << b << c << std::endl;
}
int main(int argc, char const *argv[])
{
// 函数对象包装器
// 为了函数提供了一种容器(封装)
// 支持四种函数的封装
// 1 普通函数
// 2 匿名函数
// 3 类成员函数
// 4 防函数(重载了()运算符的函数)
test(1);
// 普通函数
std::function f = test;
f(33);
// 匿名函数
std::function f2 = [](int n)->int{
std::printf("f2 n:%d\n", n);
return n;
};
f2(13);
// 类成员函数
std::function f3 = &CTest::MyTest;
CTest t;
f3(&t, 66);
// 防函数调用 重载()运算符
t(999);
std::function f4 = &CTest::operator();
f4(&t, 888);
// bind 机制
auto f5 = std::bind(test2, 1, 2, 4);
f5();
auto f6 = std::bind(test2, std::placeholders::_2, std::placeholders::_1, 0);
f6(5, 4);
return 0;
}
C++ 中最令人头疼的问题是强迫程序员对申请的资源(文件、内存)进行管理,一不小心就会出现泄漏(忘记对申请的资源进行释放)的问题
auto prt = new std::vector();
C++ 的解决方法:RAII
在传统C++里我们只好使用new和delete去对资源进行释放,而C++11引入智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。
解决思路:
RAII,完整的英文是Resource Acquisition Initialization,是C++所有持有的资源管理方式。
具体而言,C11的stl中为大家带来了3种智能指针,正确合理的使用可以有效地帮助大家管理资源,当然,在C++的使用智能指针没有像Jave,python这种具备垃圾回收机制的语言那么舒适,毕竟,程序员还需要做一些额外的事情,但是,这远比传统的C或C++更加优雅,3种智能指针分别是:
在早期有一个auto_ptr,这个四种指针在使用有区别
#include
class CTest{
public:
CTest();
~CTest();
private:
int* m_pInt;
};
CTest::CTest(){
m_pInt = new int;
std::printf("CTest finish \n");
}
CTest::~CTest(){
if(m_pInt != nullptr){
delete m_pInt;
}
std::printf("~CTest finish \n");
}
int main(int argc, char const *argv[]){
// 通过了new 在堆上分配了4个字节的空间
// C++ 需要自己取管理堆内存的申请和释放
int* p = new int;
CTest t;
return 0;
}
要正确的理解智能指针,首先必须理解引用计数技术。
深拷贝、浅拷贝的概念
鉴于深拷贝和浅拷贝的优缺点,可采用引用计数技术,即减小了内存开销,又避免了堆的重复释放或内存泄漏问题。
举例说明
#include
#include
#include
class CStudent{
public:
CStudent(const char* pszName);
CStudent(CStudent& obj);
CStudent& operator= (CStudent& obj);
void release();
void Show(){
std::cout << "Show " << (int&)m_pszName << " " << m_pszName << std::endl;
}
private:
char* m_pszName;
};
CStudent::CStudent(const char* pszName){
m_pszName = new char[256];
strcpy(m_pszName, pszName);
}
CStudent::CStudent(CStudent& obj){
m_pszName = obj.m_pszName;
}
CStudent& CStudent::operator=(CStudent& obj){
m_pszName = obj.m_pszName;
return *this;
}
void CStudent::release(){
if(m_pszName != nullptr){
delete m_pszName;
m_pszName = NULL;
}
}
int main(int argc, char const *argv[]){
CStudent stu1("stu1");
CStudent stu2("stu2");
CStudent stu3 = stu2;
stu1.Show();
stu2.Show();
stu2.Show();
stu2.release();
stu3.Show();
return 0;
}
这样代理的问题
#include
#include
#include
class CStudent{
public:
CStudent(const char* pszName);
CStudent(CStudent& obj);
CStudent& operator= (CStudent& obj);
~CStudent();
void release();
void Show(){
std::cout << "Show " << (int&)m_pszName << " " << m_pszName << std::endl;
}
private:
char* m_pszName;
//资源计数器, 当资源计数器减为0时,那么表示该资源可以被重复的释放
int * m_pCount;
};
CStudent::CStudent(const char* pszName){
m_pszName = new char[256];
m_pCount = new int;
strcpy(m_pszName, pszName);
*m_pCount = 1;
}
CStudent::CStudent(CStudent& obj){
// 浅拷贝
m_pszName = obj.m_pszName;
m_pCount = obj.m_pCount;
(*m_pCount)++;
}
CStudent& CStudent::operator=(CStudent& obj){
if(obj.m_pszName == m_pszName){
return *this;
}
if(--(*m_pCount) == 0){
delete m_pszName;
m_pszName = NULL;
delete m_pCount;
}
m_pszName = obj.m_pszName;
m_pCount = obj.m_pCount;
(*m_pCount) ++;
return *this;
}
CStudent::~CStudent(){
release();
}
void CStudent::release(){
if(m_pszName != nullptr && --*m_pCount == 0){
delete m_pszName;
m_pszName = NULL;
delete m_pCount;
m_pCount = NULL;
}
}
int main(int argc, char const *argv[]){
CStudent stu1("stu1");
CStudent stu2("stu2");
CStudent stu3 = stu2;
stu1.Show();
stu2.Show();
stu3.Show();
std::cout << "====================" << std::endl;
stu2.release();
stu3.Show();
stu3.release();
stu3.Show();
return 0;
}
最后,我们将该引用计数做一个简易的封装,也就是把引用计数作为一个新的类来使用:
#include
#include
#include
struct RefValue{
RefValue(const char* pszName);
~RefValue();
void AddRef();
void Release();
char* m_pszName;
int m_nCount;
};
RefValue::RefValue(const char* pszName){
m_pszName = new char[strlen(pszName) + 1];
strcpy(m_pszName, pszName);
m_nCount = 1;
}
RefValue::~RefValue(){
if(m_pszName!= nullptr){
delete m_pszName;
m_pszName = NULL;
}
}
void RefValue::AddRef(){
m_nCount++;
}
void RefValue::Release(){
if(--m_nCount == 0){
delete this;
}
}
class CStudent{
public:
CStudent(const char* pszName);
CStudent(CStudent& obj);
CStudent& operator= (CStudent& obj);
~CStudent();
void release();
void Show(){
if(m_pValue->m_nCount > 0){
std::cout << "Show " << (int&)m_pValue->m_pszName << " " << m_pValue->m_pszName << std::endl;
}
}
private:
RefValue* m_pValue;
};
CStudent::CStudent(const char* pszName){
m_pValue = new RefValue(pszName);
}
CStudent::CStudent(CStudent& obj){
// 浅拷贝
m_pValue = obj.m_pValue;
m_pValue->AddRef();
}
CStudent& CStudent::operator=(CStudent& obj){
if(obj.m_pValue == m_pValue){
return *this;
}
m_pValue->Release();
m_pValue = obj.m_pValue;
m_pValue->AddRef();
return *this;
}
CStudent::~CStudent(){
release();
}
void CStudent::release(){
m_pValue->Release();
}
int main(int argc, char const *argv[]){
CStudent stu1("stu1");
CStudent stu2("stu2");
CStudent stu3 = stu2;
stu1.Show();
stu2.Show();
stu3.Show();
std::cout << "====================" << std::endl;
stu2.release();
stu3.Show();
stu3.release();
stu3.Show();
return 0;
}
问题
上面的做法能在一定程度上解决资源多次重复申请的浪费,但是仍然存在两个核心的问题。
写时拷贝
问题:如果共享资源中的值发生了变化,那么其他使用该共享资源的值如何保持不变?
解决思路:使用引用计数时,当发生共享资源值改变的时候,需要对其资源进行重新的拷贝,这样改变的拷贝的值,而不影响原有的对象中的共享资源。
写时拷贝(COW copy on write),在所有需要改变值的地方,重新分配内存。
#include
#include
#include
struct RefValue{
RefValue(const char* pszName);
~RefValue();
void AddRef();
void Release();
char* m_pszName;
int m_nCount;
};
RefValue::RefValue(const char* pszName){
m_pszName = new char[strlen(pszName) + 1];
strcpy(m_pszName, pszName);
m_nCount = 1;
}
RefValue::~RefValue(){
if(m_pszName!= nullptr){
delete m_pszName;
m_pszName = NULL;
}
}
void RefValue::AddRef(){
m_nCount++;
}
void RefValue::Release(){
if(--m_nCount == 0){
delete this;
}
}
class CStudent{
public:
CStudent(const char* pszName);
CStudent(CStudent& obj);
CStudent& operator= (CStudent& obj);
~CStudent();
void release();
void Show(){
if(m_pValue != nullptr && m_pValue->m_nCount > 0){
std::cout << "Show " << (int&)m_pValue->m_pszName << " " << m_pValue->m_pszName << std::endl;
}
}
void SetName(const char* pszName);
private:
RefValue* m_pValue;
};
CStudent::CStudent(const char* pszName){
m_pValue = new RefValue(pszName);
}
CStudent::CStudent(CStudent& obj){
// 浅拷贝
m_pValue = obj.m_pValue;
m_pValue->AddRef();
}
CStudent& CStudent::operator=(CStudent& obj){
if(obj.m_pValue == m_pValue){
return *this;
}
m_pValue->Release();
m_pValue = obj.m_pValue;
m_pValue->AddRef();
return *this;
}
CStudent::~CStudent(){
release();
}
void CStudent::release(){
m_pValue->Release();
}
void CStudent::SetName(const char* pszName){
m_pValue->Release();
m_pValue = new RefValue(pszName);
}
int main(int argc, char const *argv[]){
CStudent stu1("stu1");
CStudent stu2("stu2");
CStudent stu3 = stu2;
stu1.Show();
stu2.Show();
stu3.Show();
std::cout << "====================" << std::endl;
stu2.SetName("stu222");
stu1.Show();
stu2.Show();
stu3.Show();
std::cout << "====================" << std::endl;
stu2.release();
stu3.Show();
stu3.release();
stu3.Show();
return 0;
}
前面,我们学会了如何使用引用计数及写时拷贝,这是理解智能指针必不可少的方法。但是,在实际写代码中,我们其实更倾向于让程序员对于资源的管理没有任何的感知,也就是说,最后让程序员只需要考虑资源的何时申请,对于何时以及资源内部如何计数等问题,统统交给编译器内部自己处理。
智能指针另外一点就是在使用上要像真正的指针一样可以支持取内容*,指针访问成员->等操作,因此,就需要对这些运算符进行重载。
#include
// 智能指针:
// 1. 用起来像指针
// 2. 会自己对资源进行释放
class CStudent{
public:
CStudent(){};
void test(){
std::cout << "CStudent test" << std::endl;
m_nSex = 1;
}
private:
char * m_pszBuf;
int m_nSex;
};
// 创建一个类,利用该类的构造和析构(进出作用域自动被编译调用)的机制
// 来解决资源自动释放的问题
// 智能指针雏形 需要管理资源
class CSmartPtr{
public:
// 一定要是一个堆对象
CSmartPtr(CStudent* pObj){
m_pObj = pObj;
}
~CSmartPtr(){
if(m_pObj != nullptr){
delete m_pObj;
}
}
// 让对象用起来像一个指针 重载->运算符
CStudent* operator ->(){
return m_pObj;
}
CStudent& operator * (){
return *m_pObj;
}
operator bool(){
return m_pObj != nullptr;
}
// 不允许=号运算符重载,拷贝构造
// CSmartPtr& operator = (CSmartPtr&) = delete;
// CSmartPtr(CSmartPtr&) = delete;
// 使用拷贝移动
// CSmartPtr& operator = (CSmartPtr& obj){
// if(m_pObj != nullptr){
// delete m_pObj;
// }
// m_pObj = obj.m_pObj;
// obj.m_pObj = nullptr;
// return *this;
// }
private:
CStudent * m_pObj;
};
int main(int argc, char const *argv[]){
// 这里可以完成资源的自动释放
// 但是,用起来不像一个指针
CSmartPtr sp(new CStudent);
// sp->m_pObj->test();
sp->test();
(*sp).test();
if(sp){
std::cout << "sp is true" << std::endl;
}
// 1. 不允许=号运算符重载,拷贝构造
// 2. 使用拷贝移动,auto_ptr 98
// 3. 结合前面的引用计数以及写时拷贝 新的智能指针的写法
// CSmartPtr sp2 = sp;
// CSmartPtr sp3(new CStudent);
// sp3 = sp;
return 0;
}
#include
class CStudent{
public:
CStudent(){};
void test(){
std::cout << "CStudent test" << std::endl;
m_nSex = 1;
}
private:
char * m_pszBuf;
int m_nSex;
};
class CrefCount{
friend class CSmartPtr;
CrefCount(CStudent* pStu){
m_pObj = pStu;
m_nCount = 1;
}
~CrefCount(){
delete m_pObj;
m_pObj = NULL;
}
void AddRef(){
m_nCount ++;
}
void Release(){
if(--m_nCount == 0){
// 这么写,就表示自己一定要是一个堆对象
delete this;
}
}
private:
CStudent* m_pObj;
int m_nCount;
};
// 致命问题,CSmartPtr中表示的类型是固定的,是CStudent
// 需要添加模版
class CSmartPtr{
public:
CSmartPtr(){
m_pRef = NULL;
}
// 一定要是一个堆对象
CSmartPtr(CStudent* pStu){
m_pRef = new CrefCount(pStu);
}
~CSmartPtr(){
m_pRef->Release();
}
CSmartPtr(CSmartPtr& obj){
m_pRef = obj.m_pRef;
m_pRef->AddRef();
}
CSmartPtr& operator=(CSmartPtr& obj){
if(m_pRef == obj.m_pRef){
return *this;
}
m_pRef->AddRef();
if(m_pRef != NULL){
m_pRef->Release();
}
m_pRef = obj.m_pRef;
return *this;
}
CStudent* operator->(){
return m_pRef->m_pObj;
}
CStudent** operator&(){
return &m_pRef->m_pObj;
}
CStudent& operator*(){
return *m_pRef->m_pObj;
}
operator CStudent*(){
return m_pRef->m_pObj;
}
private:
CrefCount* m_pRef;
};
int main(int argc, char const *argv[]){
CStudent* pStu = new CStudent();
CSmartPtr sp1(pStu);
CSmartPtr sp2 = sp1; // 拷贝构造
// sp2 = sp1; // 运算符重载
return 0;
}
#include
class CStudent{
public:
CStudent(){};
void test(){
std::cout << "CStudent test" << std::endl;
m_nSex = 1;
}
private:
char * m_pszBuf;
int m_nSex;
};
template
class CSmartPtr;
template
class CrefCount{
friend class CSmartPtr;
public:
CrefCount(T* pStu){
m_pObj = pStu;
m_nCount = 1;
}
~CrefCount(){
delete m_pObj;
m_pObj = NULL;
}
void AddRef(){
m_nCount ++;
}
void Release(){
if(--m_nCount == 0){
// 这么写,就表示自己一定要是一个堆对象
delete this;
}
}
private:
T* m_pObj;
int m_nCount;
};
// 致命问题,CSmartPtr中表示的类型是固定的,是CStudent
// 需要添加模版
template
class CSmartPtr{
public:
CSmartPtr(){
m_pRef = NULL;
}
// 一定要是一个堆对象
CSmartPtr(T* pStu){
m_pRef = new CrefCount(pStu);
}
~CSmartPtr(){
m_pRef->Release();
}
CSmartPtr(CSmartPtr& obj){
m_pRef = obj.m_pRef;
m_pRef->AddRef();
}
CSmartPtr& operator=(CSmartPtr& obj){
if(m_pRef == obj.m_pRef){
return *this;
}
m_pRef->AddRef();
if(m_pRef != NULL){
m_pRef->Release();
}
m_pRef = obj.m_pRef;
return *this;
}
T* operator->(){
return m_pRef->m_pObj;
}
T** operator&(){
return &m_pRef->m_pObj;
}
T& operator*(){
return *m_pRef->m_pObj;
}
operator T*(){
return m_pRef->m_pObj;
}
private:
CrefCount* m_pRef;
};
int main(int argc, char const *argv[]){
CStudent* pStu = new CStudent();
// CrefCount ref(pStu);
CSmartPtr sp1(pStu);
CSmartPtr sp2(new CStudent);
return 0;
}