1.不能被拷贝的类
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98做法
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
拷贝 赋值只声明不定义,放在私有 防止有人在类外定义使用。
C++11做法
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上
=delete,表示让编译器删除掉该默认成员函数。
2.只能在堆上创建对象的类
对象可以在哪里创建?
class A
{};
int main()
{
static A a1; //静态区
A a2; //栈
A* p = new A;//堆
}
方法一:
只允许new出来的对象,其它生成对象的方式全部禁止。
我们把对象的构造函数私有化,在类外就不能生成对象了
再在类中提供初始化对象的函数供类外调用生成对象,因为只能通过函数调用的方式生成对象,所以该函数必须是静态函数。
但是在类外也可以通过拷贝构造/赋值的方式来初始化,所以我们也要把拷贝/赋值删除。
class HeapOnly
{
public:
static HeapOnly* CreateObject()
{
return new HeapOnly;
}
private:
HeapOnly() {}
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
};
int main()
{
HeapOnly* p=HeapOnly::CreateObject();
//HeapOnly a(*p);
}
方法二:
除了把对象的构造函数私有化,还可以把析构私有化。
析构私有化后在栈 静态区上都不能创建对象,因为不能调用析构。但可以new对象,因为new不会显示调用析构。
再通过一个函数来代替delete,因为已经创建出了对象可以不写成静态函数。
(在类外进行拷贝初始化也是调用不到析构,不用删除也可以)
class HeapOnly
{
public:
HeapOnly() {};
void Delete()
{
delete this;
}
private:
~HeapOnly() {}
};
int main()
{
//HeapOnly p;
HeapOnly* p = new HeapOnly;
p->Delete();
}
3.只能在栈上创建对象的类
我们可以模仿在堆上创建对象的方法一
class HeapOnly
{
public:
static HeapOnly CreateObject()
{
return HeapOnly();
}
private:
HeapOnly() {};
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
};
int main()
{
HeapOnly* p=HeapOnly::CreateObject();
//HeapOnly a(*p);
}
出错的原因是因为是在栈上创建的对象,返回对象是通过值返回的。必须要调用拷贝构造函数。
不封死拷贝构造函数,还有什么办法呢?
我们知道如果在类中重载new的实现,通过new创建对象时会调用重载的new,把重载new封死,就算通过拷贝构造初始化也不能创建对象。
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
private:
StackOnly()
:_a(0)
{}
private:
int _a;
};
int main()
{
StackOnly p = StackOnly:: CreateObj();
StackOnly a = new StackOnly(p);
static StackOnly b = StackOnly(p);
}
但在静态区还是可以通过拷贝构造来初始化对象。
没有办法不通过拷贝构造传对象的方法吗?
有,可以通过移动构造,但在静态区创建对象时也可以通过move走移动构造初始化对象。
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
}
StackOnly(StackOnly&& s)
{}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
StackOnly(const StackOnly& s) = delete;
private:
StackOnly()
:_a(0)
{}
private:
int _a;
};
int main()
{
StackOnly p = StackOnly:: CreateObj();
StackOnly a = new StackOnly(p);
static StackOnly b = StackOnly(move(p));
}
4.设计一个不能被继承的类
C++98
构造函数私有化,派生类中调不到基类的构造函数。则无法继承.
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
C++11
final关键字,final修饰类,表示该类不能被继承。
class A final
{
// ....
};
5.只能创建一个对象的类 (单例模式)
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
怎么实现单例模式呢?
私有化构造函数,不能在类外创建对象。在类中定义一个静态对象(保证只创建一个对象),再定义静态函数来返回对象。
1.饿汉模式
class InfoMgr
{
public:
static InfoMgr& GetInstance()
{
return _ins;
}
void Print()
{
cout << _ip << endl;
cout << _port << endl;
cout << _buffSize << endl;
}
private:
//私有化构造函数
InfoMgr(const InfoMgr&) = delete;
InfoMgr& operator=(const InfoMgr&) = delete;
InfoMgr()
{
cout << "InfoMgr()" << endl;
}
private:
//对象信息
string _ip = "127.0.0.1";
int _port = 80;
size_t _buffSize = 1024 * 1024;
//...
//对象声明
static InfoMgr _ins;
};
//对象定义
InfoMgr InfoMgr::_ins;
不过有没有调用函数获取对象信息,都会创建对象。就像一个饿汉一开始就要吃东西。
懒汉模式
class InfoMgr
{
public:
static InfoMgr& GetInstance()
{
if (p_ins == nullptr)
{
p_ins= p_ins = new InfoMgr;
}
return *p_ins;
}
void Print()
{
cout << _ip << endl;
cout << _port << endl;
cout << _buffSize << endl;
}
private:
//私有化构造函数
InfoMgr(const InfoMgr&) = delete;
InfoMgr& operator=(const InfoMgr&) = delete;
InfoMgr()
{
cout << "InfoMgr()" << endl;
}
private:
//对象信息
string _ip = "127.0.0.1";
int _port = 80;
size_t _buffSize = 1024 * 1024;
//...
//对象声明
static InfoMgr* p_ins;
};
//对象定义
InfoMgr* InfoMgr::p_ins=nullptr;
int main()
{
InfoMgr::GetInstance().Print();
}
懒汉模式是等到要调用函数的时候才会进行初始化对象。
类型转换
内置类型和内置类型
1.隐式转换(不同类型间有连系,int double long 都是计数的)
int i = 1;
// 隐式类型转换
double d = i;
2.显示类型转换(没有绝对连系)
int* p = &i;
// 显示的强制类型转换
int address = (int) p
自定义类型和内置类型
1.内置类型转自定义类型
class A
{
public:
A(int a)
:_a1(a)
,_a2(a)
{}
A(int a,int b)
:_a1(a)
, _a2(b)
{}
private:
int _a1;
int _a2;
};
int main()
{
string s = "1111";
//单参数隐式类型转换
A a = 1;
//多参数隐式类型转换
A b = { 1,2 };
}
内置类型转为自定义类型的关键在于是否有对应的构造函数。
2.自定义类型转为内置类型
可以转,需要进行运算符重载,但()被仿函数重载,所以我们一般写成operator int() 不需要写返回类型,int就可以代替。如果需要返回double,把int 改为double就行。
class A
{
public:
A(double a)
:_a1(a)
,_a2(a)
{}
A(double a, double b)
:_a1(a)
, _a2(b)
{}
operator double()
{
return _a1 + _a2;
}
private:
double _a1;
double _a2;
};
int main()
{
string s = "1111";
//单参数隐式类型转换
A a = 1;
//多参数隐式类型转换
A b = { 1.1,2.2 };
double c = b;
cout << c;
}
explicit可以禁止隐式类型转换
自定义类型和自定义类型
于自定义类型建立联系就要通过构造函数。
class A
{
public:
A(int a)
:_a1(a)
,_a2(a)
{}
A(int a, int b)
:_a1(a)
, _a2(b)
{}
//加explicit禁止隐式类型转换 强制类型转换可以
explicit operator int()
{
return _a1 + _a2;
}
int get() const
{
return _a1 + _a2;
}
private:
int _a1;
int _a2;
};
class B
{
public:
B(const A& a)
:_b(a.get())
{}
private:
int _b;
};
int main()
{
A a = { 1,2 };
B b = a;
B& c = b;
//A隐式类型转换B 产生临时对象 常性+const
const B& d = a;
}
四种命名的强制类型转换操作符:
1.static_cast
static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用
static_cast,但它不能用于两个不相关的类型进行转换。
int main()
{
double d = 12.34;
//相当于int a = d;隐式类型转换
int a = static_cast<int>(d);
cout << a << endl;
return 0;
}
2 reinterpret_cast
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型。(相当于强制类型转换)
3 const_cast
const_cast最常用的用途就是删除变量的const属性,方便赋值。
为什么cout<<a的值为2?
可以看到内存a中的值已经改变,但编译器进行了优化mov进去的值不是a是常数2.
我们可以对a加volatile(
volatile
是 C++ 中的一个类型限定符,主要用于告诉编译器某个变量的值可能会在程序的其他部分被改变,因此编译器不应该对其进行优化。)
加了volatile 编译器不会进行优化,mov进去的值是内存中的a
4 dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
可以看到无论是static_cast隐式/reinterpret显示类型转换,都可以编译通过,为什么还要用dynamic_case进行转换呢?
因为fun(A* pa)中pa可能是父类A传过来,还有可能是子类B传过来的。如果是父类传过来的B*pb访问子类新增的成员变量时就会越界。
、
如果是A* ps是父类传过来的,并且把A* pa转为B*,访问子类的新增成员就会出现越界访问的问题。简单来说就是因为B*pb指向的范围大于A*pa指向的范围导致越界访问。
所以建议用dynamic_cast,转换不成功就返回nullptr。
注意:
dynamic_cast只能用于父类含有虚函数的类。