
目录
前言:
今天我们来了解一些类型转化和一些特殊类。
一.设计一个只可以在堆上创建变量的类
一共有两种思路下面先带大家看一下常规的思路:
cpp
class LimitClass
{
public:
LimitClass(const LimitClass& ) = default;
LimitClass& operator=(const LimitClass& ) = default;
static LimitClass* Creat()
{
return new LimitClass();
}
private:
LimitClass() {
cout << "22" << endl;
}
};
第一种就是在创建时进行封锁,不过单纯的将构造函数封锁还是不能够完全的达到效果,类依然可以利用赋值构造和重载=在栈上创建空间,因此需要将赋值构造以及重载=也给封掉。
cpp
class LimitClass1 {
private:
~LimitClass1(){}
};
第二张则是将析构函数直接给封锁,这样在栈上创建的空间无法释放,依然可以达到目标。
下面是测试代码:
cpp
int main()
{
LimitClass* l1 = LimitClass::Creat();
LimitClass l2 = *l1;
LimitClass l3(*l1);
//LimitClass1* l4 = new LimitClass1;
//LimitClass1 l5 = l4;
//LimitClass1 l6(l4);
return 0;
}
二.设计一个只可以在栈上创建变量的类
基本思路与在对上创建类的第一个方法类似,将可以走的一些路线给封死:
cpp
class stackonly
{
public:
static stackonly creat()
{
return stackonly();
}
private:
stackonly()
:_a(0)
{
}
int _a;
};
当然这也面临着跟在堆上创建空间的同样问题,赋值构造还可以使用,那么解决办法是否能像在堆上创建空间那样呢?
我们注意看我们创建变量的方法,他本质上不就是一个赋值构造吗?如果把赋值构造给禁用掉的话构造函数也没有办法使用。以下给出了两种解决方案,不过各自都有各自的缺陷:
cpp
class stackonly
{
public:
static stackonly creat()
{
return stackonly();
}
void* operator new(size_t size) = delete;
private:
stackonly()
:_a(0)
{
}
int _a;
};
第一种方法就是将operator new给禁用,但是呢这种情况仍然可以创建stactic的变量。
static stackonly s3(s1);
cpp
class stackonly
{
public:
static stackonly creat()
{
return stackonly();
}
stackonly(stackonly&& a)
{}
stackonly(const stackonly&) = delete;
private:
stackonly()
:_a(0)
{}
int _a;
};
第二个是这样的方法,将赋值构造给禁用掉然后增加一个右值引用的构造函数.不过呢这个有着一个致命的缺陷 stackonly s4(move(s1));只需要将变量move一下就可以正常调用。
总体来说还是更加推荐第一种方法。
三.设计一个不可以被继承的类
1.将父类的构造函数私有化:
cpp
class Nofa {
private:
Nofa()
{
}
};
这样子类无法调用父类的构造函数就可以实现不能被继承的目的。
不过在C++11中新加了一个final关键字,加上之后这个类就不可以被继承了。
cpp
class Nofa final
{
private:
int _a;
};
第二种方法优于第一种,第一种需要在创建变量的时候才可以被发现,但是第二种在调用的时候就会报错。
四.请设计一个类,只能创建一个对象**(单例模式)**
在讲正文内容前我们先了解一下什么是设计模式
设计模式:
设计模式( Design Pattern )是一套 被反复使用、多数人知晓的、经过分类的、代码设计经验的 总结 。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打 仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路 的,后 来孙子就总结出了《孙子兵法》。孙子兵法也是类似。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
那么什么是单例模式呢?
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享 。比如在某个服务器程序中,该服务器的配置 信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再 通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
为了实现这一特性我们设计出了两种模式
1.饿汉模式
cpp
class InfoMgr
{
public:
static InfoMgr& Getintefe()
{
return _st;
}
static void Print()
{
cout << _st._ip << endl;
cout << _st._count << endl;
cout << _st._name << endl;
}
private:
InfoMgr(const InfoMgr&) = delete;
InfoMgr& operator=(const InfoMgr&) = delete;
InfoMgr(){}
private:
string _ip = "123.123.123";
int _count = 12;
string _name = "user";
static InfoMgr _st;
};
InfoMgr InfoMgr:: _st;
不过饿汉模式有着许多的缺陷比如:
当有多个饿汉模式的类时,启动时会导致速度变慢以及当两个饿汉模式的类相关联时无法保证启动顺序。为此又开发出了懒汉模式。
cpp
class InfoMgr
{
public:
static InfoMgr& Getintefe()
{
if (_st == nullptr)
{
_st = new InfoMgr;
}
return *_st;
}
void Print()
{
cout << _ip << endl;
cout << _count << endl;
cout << _name << endl;
}
private:
InfoMgr(const InfoMgr&) = delete;
InfoMgr& operator=(const InfoMgr&) = delete;
InfoMgr() {}
private:
string _ip = "123.123.123";
int _count = 12;
string _name = "user";
static InfoMgr* _st;
};
InfoMgr* InfoMgr::_st=nullptr;
只需要将静态变量换为一个指针就可以了,这样只有当进行调用的时候才会初始化,也就不存在程序启动慢的情况了,当然调用顺序也可以由自己决定。
cpp
class InfoMgr
{
public:
static InfoMgr& Getintefe()
{
static InfoMgr _st;
return _st;
}
void Print()
{
cout << _ip << endl;
cout << _count << endl;
cout << _name << endl;
}
private:
InfoMgr(const InfoMgr&) = delete;
InfoMgr& operator=(const InfoMgr&) = delete;
InfoMgr() {}
private:
string _ip = "123.123.123";
int _count = 12;
string _name = "user";
};
这种是比较简单的一种懒汉模式的创建方法,在C++11之后更加的推荐使用这一种方法。
五.类型转换
在C语言中我们学习了内置类型转化,其中包括隐式类型转换以及显示类型转换。
在我们学习C++之后又新加了新的1自定义类型,那么自定义类型与内置类型,内置类型与自定义类型之间能否相互转换呢?
1.内置类型转自定义类型
其实本质来讲还是利用的隐式类型转换,不过主要的应用的是构造函数的知识点。
cpp
class Date
{
public:
Date(int a)
:_a(a){}
private:
int _a=1;
};
int main()
{
Date d1 = 1;
Date d2='a';
Date d3=(int)nullptr;
string s1 = "china";
return 0;
}
我们也可以利用explicit来禁止隐式类型转化。
cpp
class Date
{
public:
explicit Date(int a)
:_a(a){}
private:
int _a=1;
};
2.自定义类型转内置类型
这种转换方式直接转的话肯定是不行的我们只能通过重载()->强制转换来搞定。不过在之前我们学习过仿函数。也是operator()所以强制类型转换也是不可以的,因此C++委员会开展了新的方式为operator +内置类型不需要写返回类型。后边的内置类型是什么返回类型就是什么。
cpp
class Date
{
public:
Date(int a)
:_a(a),_b(a){}
operator int()
{
return _a + _b;
}
private:
int _a=1;
int _b = 1;
};
int main()
{
Date d1 = 1;
int i = d1;
int h = d1.operator int();
return 0;
}
这两个其实是等价的
3.自定义类型和自定义类型之间的转化
同样是需要借助我们的构造函数
cpp
class Date
{
public:
Date(int a)
:_a(a),_b(a){}
operator int()
{
return _a + _b;
}
int get()
{
return _a + _b;
}
private:
int _a=1;
int _b = 1;
};
class B {
public:
B(Date d1)
:_a(d1.get())
{}
private:
int _a;
};
int main()
{
Date d1 = 1;
B b1 = d1;
return 0;
}
4.C++中新增的几种转换的关键字
1.static_cast
对应的就是C语言中的隐式类型转化,并没有实际的意义,只不过是祖师爷想要语法更加的规范。
cpp
int main()
{
int a = static_cast < int> (1.1);
return 0;
}
2.reinterpret_cast
对应的就是C语言中的强制类型类型转化
cpp
int main()
{
int a = static_cast < int> (1.1);
int* p = reinterpret_cast<int*>(a);
return 0;
}
3.const_cast
同样也是强制类型转换,不过最主要的特性是消除const属性,使变量可以被赋值
cpp
int main()
{
const int a = 1;
int* b = const_cast<int*> (&a);
(*b)++;
cout << a << endl;
cout << *b << endl;
return 0;
}

不过需要注意的是这样写的话会导致a和*b的值不一样,这是编译器的优化导致的。如果我们不想要出现这种情况我们可以加一个volatile关键字:
cpp
int main()
{
volatile const int a = 1;
int* b = const_cast<int*> (&a);
(*b)++;
cout << a << endl;
cout << *b << endl;
return 0;
}

4.dynamic_cast
dynamic_cast 用于将一个父类对象的指针 / 引用转换为子类对象的指针或引用 ( 动态转换 )
向上转型:子类对象指针 / 引用 -> 父类指针 / 引用 ( 不需要转换,赋值兼容规则 )
向下转型:父类对象指针 / 引用 -> 子类指针 / 引用 ( 用 dynamic_cast 转型是安全的 )
dynamic_cast 会先检查是否能转换成功,能成功则转换,不能则返回null
其他3种也是可以成功转换但是是有风险的,如果只读的话还好,写的话会导致程序崩溃。
dynamic_cast只能用于父类含有虚函数的类
cpp
class A
{
public:
virtual void f() {}
int _a;
};
class B : public A
{
public:
int _b;
};
void fun(A* pa)
{
// dynamic_cast会先检查是否能转换成功(指向子类对象),能成功则转换,
// (指向父类对象)不能则返回NULL
B* pb1 = dynamic_cast<B*>(pa);
if (pb1)
{
cout << "pb1:" << pb1 << endl;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
pb1->_a++;
pb1->_b++;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
}
else
{
cout << "转换失败" << endl;
}
}
int main()
{
A a;
B b;
fun(&a);
fun(&b);
return 0;
}