1.设计一个类,无法被拷贝。
方法:c++98,通过私有且只申明不实现拷贝构造与赋值函数,从而实现该类不能被拷贝。c++11引入关键字delete后,可以使构造构造与赋值函数等于delete。效果也是无法被拷贝。
2.设计一个类只能在堆上创建对象。
方法一,析构私有化
//实现一个类,智能在堆上创建对象
class HeapCreat
{
public:
HeapCreat()
{
cout << "调用构造" << endl;
}
void release()
{
delete this;
}
private:
//方法一 析构函数私有化
~HeapCreat()
{
cout << "调用析构" << endl;
}
方法二,构造私有化
class HeapCreat
{
public:
~HeapCreat()
{
cout << "调用析构" << endl;
}
void release()
{
delete this;
}
static HeapCreat* Creat()
{
return new HeapCreat;
}
//但是要禁用拷贝构造,拷贝出都在栈上,不在堆上
HeapCrea(const HeapCrea& p)=delete;
private:
HeapCreat()
{
cout << "调用构造" << endl;
}
//方案二 构造函数私有化
};
int main()
{
//构造函数私有化,最开始都初始化不了,因此只能在类里初始化,并且申明为全局,之后调用类里的初始化
HeapCreat* p = HeapCreat::Creat();
p->release();
return 0;
}
3.设计一个类只能在栈上创建对象。
方法一:
还是构造私有化,但是注意拷贝构造,我们拷贝构造可以new,但拷贝构造不能禁用,因为我们需要调用拷贝构造,故有缺陷
//构造私有化
//还是控制构造函数,在类中实现只能在栈上创建。
class HeapCreat
{
public:
~HeapCreat()
{
cout << "调用析构" << endl;
}
static HeapCreat Creat()
{
return HeapCreat();
}
HeapCreat( HeapCreat& p)=delete;
private:
HeapCreat()
{
cout << "调用构造" << endl;
}
};
int main()
{
HeapCreat p = HeapCreat::Creat();
return 0;
}
方法二,直接不让使用new,申明出new并私有化,或delete.
class HeapCreat
{
public:
~HeapCreat()
{
cout << "调用析构" << endl;
}
static HeapCreat Creat()
{
return HeapCreat();
}
void* operator new(size_t t) = delete;
private:
//void* operator new(size_t t)
HeapCreat()
{
cout << "调用构造" << endl;
}
};
4.设计一个类,不能被继承
同上,构造函数私有化,调不了就无法被继承。
c++11提供了关键字final,可以是这个类不能为继承,即最终类。
一,何为设计模式
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。设计模式代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。就是前人总结下来的一些经验。
设计模式分为三大类:创建型模式、结构型模式和行为型模式。
创建型模式包括工厂方法模式、抽象工厂模式、单例模式、建造者模式和原型模式。结构型模式包括适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式和代理模式。行为型模式包括责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。
而创作型中常用的就是我们的单例模式,我们以学习单例模式来了解一下设计模式。
二,单例模式
所谓的单例模式其实本质上就是一个特殊类的设计。对于单例模式,在整个进程中,在整个全局环境中,只有这一个实例化的对象,即单实例,对于这样的类的设计就是单例模式。对于大一点的项目,内存池等都会用到单例模式。
那么如是设计出全局下只有一个实例化对象?参考前面的博客,http://t.csdnimg.cn/bA37O,特殊类的设计,那么要想只能实例化出一个对象,且是在全局的环境下,首先,我们要控制构造函数,让其私有化,这样在外面就无法实例化,对于构造提供了两种方式,也就是单例模式的两种模式。
其次就是要求在全局环境下的对象,直接在全局下创建首先无法实例化,构造函数已经私有化了,
且全局的对象是存在弊端的:对象是容易被修改的,多线程下会出现链接问题等。
之后了我们在类里创建对象,但直接创建显然不可行,自己创建自己,但是当我们使用ststic时,就不是自己创建自己,该对象是在静态区中。
因此最终我们选用静态全局的方式实现单例模式。
饿汉模式
顾名思义,该模式很"饥饿",我们在进入主程序前,就要把这个特殊类设计好给我,也就是在此之前,把对象给我来用。
class TEST
{
public:
//一般我们通过接口Getinstance 来获取这个对象
static TEST* GetInstance() //对象是静态对象
{
return &test;
}
void ADD(const string key, const string val)
{
dict.insert(make_pair(key, val));
}
void Print()
{
for (auto it : dict)
{
cout << it.first << ":" << it.second<<endl;
}
}
private:
//首先类的成员一般都是私有的,这里以一个字典为例
map <string, string> dict;
//私有构造并禁用拷贝与赋值
TEST()
{
}
TEST(const TEST& p) = delete;
TEST& operator=(const TEST& p) = delete;
static TEST test;//声明 在静态区当中
};
//定义
TEST TEST::test; //定义了一个类的对象test
int main()
{
//程序启动 饿汉模式
TEST::GetInstance()->ADD("冒泡", "排序");//通过GetInstance获取唯一的对象test
TEST::GetInstance()->ADD("希尔", "排序");
TEST::GetInstance()->Print();
return 0;
}
优缺点:
优点:实现简单
缺点:可能导致进程启动 ,如果有两个单例启动具有先后顺序,控制不了饿汉。
懒汉模式
顾名思义,现吃现做,只有当我们需要使用这个对象的时候,我们才提供对象。即第一次使用的时候才创建。
//懒汉模式
class TEST
{
public:
static TEST* GetInstance()
{
//主程序要调用使用对象了此时我们在创建
//这里主要介绍懒汉的思想,实际上这里的代码还存在线程安全问题
if (test == nullptr)
{
test = new TEST;
}
return test;
}
void ADD(const string key, const string val)
{
dict.insert(make_pair(key, val));
}
void Print()
{
for (auto it : dict)
{
cout << it.first << ":" << it.second<<endl;
}
}
static void Delete()
{
if (test)
{
delete test;
test = nullptr;
}
}
private:
//这里以一个字典为例
map <string, string> dict;
~TEST()
{
delete test;
}
TEST()
{
}
TEST(const TEST& p) = delete;
TEST& operator=(const TEST& p) = delete;
static TEST *test;//声明 在静态区当中
class gc
{
public:
~gc()
{
Delete();
}
};
static gc _gc;
};
//定义
TEST* TEST::test=nullptr;
TEST::gc TEST::_gc;
int main()
{
//程序启动
//懒汉模式
TEST::GetInstance()->ADD("冒泡", "排序");//通过GetInstance获取唯一的对象test
TEST::GetInstance()->ADD("希尔", "排序");
TEST::GetInstance()->Print();
}
释放的时候,可以使用智能指针来管理这个指针,也可以在搞一个类用来处理析构,在该对象中,只要释放,就会调用里面的类的的析构使得释放指针。
类型转换
c语言中的类型转换
c语言的类型转换分为两种:
1.隐式类型转换 :int i=1;double b=i;
对于能相互转换的类型,可以隐式类型转换。
2.显式类型转换 : int j=1;doule ret=double(j)/0.1;
有关联性的类型可以强制类型转换。
c++的类型转换
对于c语言的类型转换,c++认为不太规范,因此c++提出了四种强制类型转换的类型,只有这四类的类型才能强转。
C++提供了四种强制类型转换的函数:static_cast、dynamic_cast、const_cast和reinterpret_cast。
下面对这四种转换操作的适用场景分别进行说明:
static_cast(静态转化): 该运算符把 expression 转换为 type 类型,主要用于基本数据类型之间的转换,如把 uint 转换为 int,把 int 转换为 double 等。此外,还可用于类层次结构中,基类和派生类之间指针或引用的转换。
主要用于相近类型的转化(对应c语言的隐式类型转换的类型):
double i = 3.14159265;
int j = static_cast<int> (i) ;
dynamic_cast:(动态转化) 主要用于类层次间的上行转换或下行转换。在进行上行转换时dynamic_cast 和 static_cast 的效果是一样的,但在下行转换时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全。
dynamic_cast 用于将一个父类对象的指针 / 引用转换为子类对象的指针或引用 ( 动态转换 )
对于向下转型(即父类给给子类),强制类型转换理论上对象是不可以的,但指针和引用可以。 但是向下转换,存在越界访问的问题,是不安全的。
因此c++提供了dynamic_cast:
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意:
1. dynamic_cast 只能用于父类含有虚函数的类
i 2. dynamic_cast 会先检查是否能转换成功,能成功则转换,不能则返回 0
注意使用dynamic_cast前提是多态。
const_cast:(常态转化) 该运算符用来修改 expression 的 const 或 volatile 属性。
去调const属性,取地址在强转为普通指针类型。
const int a = 10;
int* p = const_cast<int*>(&a);
*p = 3;
这里的a可能直接放寄存器了,也可能宏定义了。(不再去内存找这个值)
因此虽然这里&a与p的地址一样,但是值不一样。利用关键字volatile使得强制去内存取值,我们就会发现两个值是一样的。其次在打印&a时,注意用printf,c++中的cout的输出流在打印时没有对应的函数。
reinterpret_cast: (重新诠释转化)该运算符可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针。这个转换是"最不安全"的,不推荐使用
有一定关联,但是意义不相似的类型之间的转换
int a = 1;
int* ptr = reinterpret_cast<int*> (a);
注意:类型转换中间会产生临时变量,二临时变量具有常性,是不能被修改的(引用)。
RTTI**(了解)**
RTTI : Run-time Type identification 的简称,即:运行时类型识别。
C++ 通过以下方式来支持 RTTI :
- typeid 运算符
- dynamic_cast 运算符
- decltype