C++学习
强制转换运算符
C++ 引入了四种不同的强制转换运算符以进行强制转换:
- const_cast
- static_cast
- reinterpret_cast
- dynamic_cast
C语言强制类型转换缺点:
主要是为了克服C语言强制类型转换的以下三个缺点。
- 没有从形式上体现转换功能和风险的不同。
- 例如,将int强制转换成double是没有风险的,而将常量指针转换成非常常量指令, 将基类指针转换成派生类指针都是高风险的,而且后两者带来的风险不同(即可能引发不同种类的错误),C语言的强制类型转换形式对这些不同并不加以区分。
- 将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象。
- 难以在程序中寻找到底什么地方进行了强制类型转换。强制类型转换是引起程序运行是错误的一个原因,因此在程序出错时,可能就会想到是不是有那些强制类型转换出了问题。
const_cast
- 仅用于进行去除const属性的转换,它也是四个强制类型转换运算符中唯一能够去除const属性的运算符。
- 常量对象或者是基本数据类型不允许转化为非常常量对象,只能通过指针和引用来修改
#include <iostream>
#include <string>int main(int argc, char const *argv[])
{const int n = 5;const std::string s = "inception";// 错误std::string t = const_cast<std::string>(s);// 错误int k = const_cast<int>(n);return 0;
}
可以利用const_cast转换为同类型的非const引用或者指针
#include <iostream>
#include <string>int main(int argc, char const *argv[])
{const int n = 5;// const_cast只针对指针,引用,this指针int* k1 = const_cast<int*>(&n);*k1 = 22;std::printf("n:%d k1:%d\n", n, *k1);int& k2 = const_cast<int&>(n);k2 = 33;std::printf("n:%d k1:%d, k2:%d\n", n, *k1, k2);
}
常成员函数中去除this指针的const属性
#include <iostream>
#include <string>
class CTest
{
public:CTest(): m_nTest(2){}// 常成员函数(不能修改成员变量值)void foo(int nTest) const{// m_nTest = nTest; 错误const_cast<CTest*>(this)->m_nTest = nTest;}
public:int m_nTest;
};int main(int argc, char const *argv[])
{CTest t;t.foo(1);return 0;
}
static_cast
基于等价于隐式转换的一种转换运算符,可使用于需要明确隐式转换的地方。可以用于低风险的转换。
- 整型和浮点型
- 字符与整型
- 转换运算符
- 空指针转换为任何目标类型的指针
不可以用于风险高的转换
- 不同类型的指针之间的相互转换
- 整型和指针之间的相互转换
- 不同类型的引用之间的转换
#include <iostream>
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<float>(n);std::printf("f:%d, n:%d\n", f, n);// 字符与整型char ch = 'a';n = static_cast<int>(ch);std::printf("ch:%c, n:%d\n", ch, n);// void* 指针的转换void* p = nullptr;int* pN = static_cast<int*>(p);// 转换运算符的方式CInt nObj;int k1 = nObj;int k2 = static_cast<int>(nObj);return 0;
}
static_cast 用于基类与派生类的转换过程中,但是没有运行时类型检查。
#include <iostream>
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<CSon*>(pFather); // 能通过,不安全,没有提供运行时的检查// 子类转父类 (安全)pFather = pSon;pFather = static_cast<CFather*>(pSon);return 0;
}
dynamic_cast
用于具有虚函数的基类与派生类之间的指针或引用的转换。
- 基类必须具备虚函数
- 原因:dynamic_cast是运行时类型检查,需要运行时类型信息(RTTI),而这个信息是存储与类的虚函数表关系紧密,只有一个类定义了虚函数,才会有虚函数表。
- 运行时检查,转型不成功则返回一个空指针
- 非必要不要使用dynamic_cast,有额外的函数开销
常见的转换方式:
- 基类指针或引用转派生类指针(必须使用dynamic_cast)
- 派生类指针或引用转基类指针(可以使用dynamic_cast,但是更推荐使用static_cast)
#include <iostream>
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<CFather*>(pSon);// 向下转换 父类转子类 (不安全)// pSon = static_cast<CSon*>(pFather);// pSon->m_nSon = 2; // 越界// 检查出这种转换是不安全的// 运行时,检测出被转换的指针的类型(依赖RTTI)// 有额外的开销,一般而言只有向下转换是,才必须使用pSon = dynamic_cast<CSon*>(pFather);if(pSon != nullptr){pSon->m_nSon = 2;}具有多态类型的向下转换是必须使用,其余情况可以不使用return 0;
}
reinterpret_cast
用于进行各种
lambda表达式/匿名函数
lambda表达式 是一个源自阿隆佐.邱奇的术语。
lambda表达式是C++11中最重要的性特性之一,而lambda表达式,实际上就是提供了一个类似匿名函数的特性,而匿名函数则是需要一个函数,但是又不想费力去命名一个函数情况下去使用的。这样的场景其实很多很多,所以匿名函数几乎是现代编程语言的标配。
lambda表达式基础
lambda表达式的基础语法如下:
[捕获列表](参数列表)mutable(可选)异常属性->返回类型{//函数体
}
[caputrue](params)opt->ret{body;};
- lambda表达式以一对中括号开始。
- 跟函数定义一样,我们有参数列表
- 跟正常的函数定义一样,我们会有一个函数体,里面会有return语句
- lambda表达式一般不需要说明返回值(相当于auto),有特殊情况需要说明时,则应使用箭头语法的方式
- 每个lambda表达式都有一个全局唯一的类型,要精确捕捉lambda表达式到一个变量中,只能通过auto声明的方式
#include <iostream>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;
}
mutable
#include <iostream>
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表达式被创建时拷贝,而非调用时才拷贝
- 应用捕获
- 与引用传参类似,引用捕获保存的是引用,值会发生变化。
- 隐式捕获
- 手动书写捕获列表有时候是非常复杂的,这种机械的工作可以交给编译器来处理,这时候可以在捕获列表中写一个&或=向编译器声明采用引用或者值捕获
总结一下,捕获提供了lambda表达式对外部值进行使用的功能,捕获列表的最常用的四种形式可以是:
- [] 空捕获列表
- [name1, name2, …] 捕获一系列变量
- [&] 引用捕获,让编译器自行推导捕获列表
- [=] 值捕获,让编译器执行推导应用列表
#include <iostream>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 <iostream>
#include <vector>
#include <algorithm>int main(int argc, char const *argv[])
{std::vector<int> 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;
}
函数对象包装器function与bind
(1) std::function
- lambda 表达式的本质是一个函数对象,当lambda表达式的捕获列表为空时,lambda表达式还能够作为一个函数指针进行传递
- 两种不同的调用方式,一种是将lambda作为函数指针传递进行调用,而另外一种则是直接调用lambda表达式,在C++11中,统一了这些概念,将能够被调用的对象的类型,统一称之为可调用类型,而这种类型,便是通过std::function引入的。
- C++11 std::function 是一种通用、多态的函数封装,它的实例可以对任何可以调用的目标进行存储、复制和调用操作,它也是对C++中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的),换句话说,就是函数的容器。当我们有了函数的容器之后便能够更加方便的将函数、函数指针作为对象进行处理。
(2) std::bind/std::placeholder - 而std:bind则是用来绑定函数调用的参数的,它解决的需求是我们有时候可能并不一定能够一次性获得调用某个函数的全部参数,通过这个函数,我们可以将部分调用参数提前绑定到函数身上作为一个新的对象,然后在参数齐全后,完成调用。
#include <iostream>
#include <functional>
#include <algorithm>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<int(int)> f = test;f(33);// 匿名函数std::function<int(int)> f2 = [](int n)->int{std::printf("f2 n:%d\n", n);return n;};f2(13);// 类成员函数std::function<int(CTest*, int)> f3 = &CTest::MyTest;CTest t;f3(&t, 66);// 防函数调用 重载()运算符t(999);std::function<int(CTest*, int)> 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;
}
智能指针与RAII
C++ 中最令人头疼的问题是强迫程序员对申请的资源(文件、内存)进行管理,一不小心就会出现泄漏(忘记对申请的资源进行释放)的问题
auto prt = new std::vector<int>();
C++ 的解决方法:RAII
在传统C++里我们只好使用new和delete去对资源进行释放,而C++11引入智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。
解决思路:
- 利用C++中一个对象出了其作用域会被自动析构,因此我们只需要在构造函数的时候申请空间,而在析构函数(在离开作用域时调用)的时候释放空间,这样,就减轻了程序员在编码过程中,考虑资源释放的问题,这就是RAII。
RAII,完整的英文是Resource Acquisition Initialization,是C++所有持有的资源管理方式。
- 有少量其他语言,如果D、Ade和Rust也采纳了RAII,但主流的变成语言中,C++是唯一一个依赖RAII来做资源管理的。
- RAII依托栈和析构函数,来对所有的资源一一包括堆内存在内一一进行管理。对RAII的使用,使得C++不需要类似于Jave那样的垃圾收集方法,也能有效地对内存进行管理。
具体而言,C11的stl中为大家带来了3种智能指针,正确合理的使用可以有效地帮助大家管理资源,当然,在C++的使用智能指针没有像Jave,python这种具备垃圾回收机制的语言那么舒适,毕竟,程序员还需要做一些额外的事情,但是,这远比传统的C或C++更加优雅,3种智能指针分别是:
- std::shared_ptr 强指针
- std::unique_ptr
- std::weak_ptr 弱指针
在早期有一个auto_ptr,这个四种指针在使用有区别
- auto_ptr 有缺陷是过时的产物
- std::unique_ptr对auto_ptr的问题进行了修正。
- std::shared_ptr使用了引用计数,但是会出现循环引用的问题需要配合后面的weak_ptr一起使用。
#include <iostream>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;
}
引用计数
要正确的理解智能指针,首先必须理解引用计数技术。
深拷贝、浅拷贝的概念
- 深拷贝优缺点:
- 优点:每一个的对象(哪怕是通过拷贝构造函数实例化的对象)的指针都有指向的内存空间,而不是共享,所以在对象析构的时候就不存在重复释放或内存泄漏的问题了。
- 缺点:内存开销打
- 浅拷贝优缺点
- 优点:通过拷贝构造函数实例化的对象的指针数据变量指向的共享内存空间,因此内存开销较小。
- 缺点:对象析构的时候就可能会重复释放或造成内存泄漏。
鉴于深拷贝和浅拷贝的优缺点,可采用引用计数技术,即减小了内存开销,又避免了堆的重复释放或内存泄漏问题。
举例说明
- 在深拷贝的情况下,通过拷贝构造或者赋值构造的对象均各自包含自己的指针指向"Helllo"。
- 但是,显然这样比较浪费内存,存在冗余,那么下面的版本更好:
#include <iostream>
#include <string>
#include <string.h>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 <iostream>
#include <string>
#include <string.h>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 <iostream>
#include <string>
#include <string.h>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;
}
问题
上面的做法能在一定程度上解决资源多次重复申请的浪费,但是仍然存在两个核心的问题。
- 当前对其中某一个类对象中的资源进行了修改,那么所有引用该资源的对象全部会被修改,这显然是错误的。
- 当前的计数器作用于Student类,在使用时候,需要强行加上引用计数类,这样复用性不好,使用不方便。
写时拷贝
问题:如果共享资源中的值发生了变化,那么其他使用该共享资源的值如何保持不变?
解决思路:使用引用计数时,当发生共享资源值改变的时候,需要对其资源进行重新的拷贝,这样改变的拷贝的值,而不影响原有的对象中的共享资源。
写时拷贝(COW copy on write),在所有需要改变值的地方,重新分配内存。
#include <iostream>
#include <string>
#include <string.h>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 <iostream>// 智能指针:
// 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 <iostream>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 <iostream>class CStudent{
public:CStudent(){};void test(){std::cout << "CStudent test" << std::endl;m_nSex = 1;}
private:char * m_pszBuf;int m_nSex;
};
template<typename T>
class CSmartPtr;template<typename T>
class CrefCount{friend class CSmartPtr<T>;
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<typename T>
class CSmartPtr{
public:CSmartPtr(){m_pRef = NULL;}// 一定要是一个堆对象CSmartPtr(T* pStu){m_pRef = new CrefCount<T>(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<T>* m_pRef;
};
int main(int argc, char const *argv[]){CStudent* pStu = new CStudent();// CrefCount<CStudent> ref(pStu);CSmartPtr<CStudent> sp1(pStu);CSmartPtr<CStudent> sp2(new CStudent);return 0;
}
相关文章:
C++学习
强制转换运算符 C 引入了四种不同的强制转换运算符以进行强制转换: const_caststatic_castreinterpret_castdynamic_cast C语言强制类型转换缺点: 主要是为了克服C语言强制类型转换的以下三个缺点。 没有从形式上体现转换功能和风险的不同。 例如&am…...

JavaEE简单示例——依赖注入
简单介绍: 首先我们要知道,依赖注入就是赋值,带着这句话去理解依赖注入就非常的简单了,将文中所有的依赖注入全部脑部替换成给属性赋值,再去理解依赖注入的概念。 依赖注入(DI)是指IoC容器在运…...
大数据框架之Hive: 第7章 综合案例练习(初级)
第7章 综合案例练习(初级) 一 环境准备 1.1 建表语句 hive> -- 创建学生表 DROP TABLE IF EXISTS student; create table if not exists student_info(stu_id string COMMENT 学生id,stu_name string COMMENT 学生姓名,birthday string COMMENT 出…...

kafka:linux 安装 kafka集群
kafka运行依赖于 jdk、zookeeper,kafka可视化工具选择kafka-eagle。所以要装的组件有:jdk、zookeeper、kafka、kafka-eagle一、安装jdk下载linux版本的jdk包,比如:jdk-8u192-linux-x64.tar.gz。将其复制到 /opt 目录下并解压&…...
springboot实现Hessian协议的RPC服务
背景 这段时间在公司接手了一个新项目,看到一段代码非常奇怪的,研究了好久。内容大概是这样 public void getUser (int id) {userService.getById(id); }当我点击这个方法进去的时候,我发现这个UserService类居然是导入jar包的一个接口&…...

2-6 SpringCloud快速开发入门: Eureka 服务注册中心发现与消费服务
接上一章节向Eureka 服务注册中心注册服务,这里讲讲Eureka 服务注册中心发现与消费服务 Eureka 服务注册中心发现与消费服务 我们已经搭建一个服务注册中心,同时也向这个服务注册中心注册了服务,接下来我们就可以发现和消费服务了࿰…...

Java-Web之s2-001与CommonsCollections
本文源自我个人入坑Java-Web安全的一点小经验,献给那些看得懂java代码但不知道从哪里入手代审的师傅们:) Struts2之s2-001 环境配置 说说环境配置的问题,大多数人对漏洞复现的恐惧感还是来自于环境的配置,也许配了大…...

【JavaSE】数组的定义和使用(下)
数组的定义和使用(下)4. 数组练习4.1 模拟实现toString4.2 数组拷贝4.3 比较两个数组是否相同4.4 填充数组4.3 求数组中元素的平均值4.4 查找数组中指定元素(顺序查找)4.5 查找数组中指定元素(二分查找)4.6…...
Oracle 实现对全局错误语句的审计监控 ORA- alert
--将所有数据库ora-错误写入表create table error_tab (username varchar2(4000), d_current_nr_error varchar2(4000), ora_server_error_msg varchar2(4000), full_text varchar2(4000),errdate date);create or replace trigger error_triggerafter servererror on database…...

React解决样式冲突问题的方法
React解决样式冲突问题的方法 前言: 1、React最终编译打包后都在一个html页面中,如果在两个组件中取一样类名分别引用在自身,那么后者会覆盖前者。 2、默认情况下,只要导入了组件,不管组件有没有显示在页面中&#x…...
Go项目(用户操作微服务)
简介 用户留言、收藏、修改收货地址等,统一放在用户操作微服务这里按照业务类型划分微服务表设计,三张表// 用户收藏 type UserFav struct {BaseModel// 联合索引 idx_user_goods,且唯一User int32 gorm:"type:int;index:idx_user_goo…...

Spring Boot统一功能处理
目录 一、统一用户登录权限验证 1.1 自定义拦截器 1.2 将自定义拦截器加入到系统配置 1.3 统一访问前缀 二、统一异常处理 三、统一数据格式返回 一、统一用户登录权限验证 1.1 自定义拦截器 拦截器是一个普通的类,需要实现HandlerInterceptor接口并重写pre…...
ETCD多次出现CONTEXT DEADLINE EXCEEDED
roothqa-master-01:~# etcdctl --endpoints$ETCD_ENDPOINTS member list --write-outtable {“level”:“warn”,“ts”:“2020-03-23T14:19:45.0330800”,“caller”:“clientv3/retry_interceptor.go:61”,“msg”:“retrying of unary invoker failed”,“target”:“endpoi…...

git 提交 多人开发避免冲突
代码正常提交 git add . git commit -m ‘备注信息’ git status 查看本地提交状态 git pull 拉取代码 git push origin master 指定远程仓库名和分支名 ‘’ 如果多人开发 A和B 提交避免冲突 B拉取代码修改内容直接提交后 A也修改了内容在git add / git commit / git pull / g…...

求职复盘:干了四年外包出来,面试5次全挂
我的情况 大概介绍一下个人情况,男,毕业于普通二本院校非计算机专业,18年跨专业入行测试,第一份工作在湖南某软件公司,做了接近4年的外包测试工程师,今年年初,感觉自己不能够再这样下去了&…...
AXI总线核心解读---基于官方文档
AXI总线 何处使用AXI ZYNQ异构芯片,内部总线使用的AXI总线纯FPGA的IP接口也要用高速接口,DDR(AXI、传统)等模块都有涉及到 什么是AXI总线 AXI的三种形式: AXI-FULL:高性能的存储器映射需求—可以256个以内发送 存储器…...

【Linux修炼】15.进程间通信
每一个不曾起舞的日子,都是对生命的辜负。 进程间通信进程间通信一.理解进程间通信1.1 什么是通信1.2 为什么要有通信1.3 如何进行进程间通信二.管道2.1 匿名管道2.2 匿名管道编码部分2.3 管道的特点2.4 如何理解命令行中的管道2.5 进程控制多个子进程三.命名管道3.…...

每天一道大厂SQL题【Day15】微众银行真题实战(五)
每天一道大厂SQL题【Day15】微众银行真题实战(五) 大家好,我是Maynor。相信大家和我一样,都有一个大厂梦,作为一名资深大数据选手,深知SQL重要性,接下来我准备用100天时间,基于大数据岗面试中的经典SQL题&…...
如何优化查询大数据量的表
给你100万条数据的一张表,你将如何查询优化?1.两种查询引擎查询速度(myIsam 引擎 )InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多…...

卷麻了,00后Jmeter用的比我还熟练,简直没脸见人......
经常看到无论是刚入职场的新人,还是工作了一段时间的老人,都会对测试工具的使用感到困扰?前言性能测试是一个全栈工程师/架构师必会的技能之一,只有学会性能测试,才能根据得到的测试报告进行分析,找到系统性…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...

Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...