请设计一个类,只能在堆上创建对象
//思路1:将析构函数封了
class HeapOnly
{
public:
void Destroy()
{
delete this;//这种写法也是可以的
//~HeapOnly();
}
private:
~HeapOnly()
{
cout << "~HeapOnly()" << endl;
}
int _x;
};
int main()
{
//HeapOnly ho1;
//static HeapOnly ho2;//这两行代码都没有办法去执行,因为对象的析构函数没办法被显示调用.
HeapOnly* pho3 = new HeapOnly;//可以new,但是没办法delete
pho3->Destroy();
return 0;
}
// 思路2:将构造封了
class HeapOnly
{
public:
static HeapOnly* CreateObj(int x = 0)
{
HeapOnly* p = new HeapOnly(x);
return p;
}
private:
HeapOnly(int x = 0)
:_x(x)
{}
HeapOnly(const HeapOnly& hp) = delete;//否则还可以通过拷贝构造来在栈上创建对象
HeapOnly& operator=(const HeapOnly& hp) = delete;
int _x;
};
int main()
{
HeapOnly* p1 = HeapOnly::CreateObj(1);
//HeapOnly p2(*p1);
return 0;
}
请设计一个类,只能在栈上创建对象
class StackOnly
{
public:
static StackOnly CreateObj(int x = 0)//你想在堆上还是栈上创建构造函数比较灵活,封上构造函数,然后调整该函数的返回值。
{
return StackOnly(x);
}
StackOnly(StackOnly&& st)
:_x(st._x)
{}
private:
StackOnly(int x = 0)
:_x(x)
{}
StackOnly(const StackOnly& st) = delete;
int _x;
};
int main()
{
/*StackOnly st1;
static StackOnly st2;
StackOnly* st3 = new StackOnly;*/
StackOnly st1 = StackOnly::CreateObj(1);
//static StackOnly st2 = st1;//但是还是可以在栈上申请静态成员对象,解决方法将拷贝构造封死,增加移动构造,那么上面的函数就会走移动构造了。
//static StackOnly st2 = move(st1);//但是依旧可以采用这种方式在栈上申请
return 0;
}
//final关键字,final修饰类,表示该类不能被继承。
class A final
{
// ....
};
单例模式
一个类只能创建一个对象,换句话说这个类的对象全局只有唯一一份,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再 通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式的本质就是封装一组数据(也就是类对象的成员),并且保证这组数据是全局唯一一份。
对于单例对象的释放问题,由于全局都要显示使用它,所以一般不需要去显示去释放他,随进程结束而释放。但是有些特征场景向显示释放一下可以采用DelInstance()接口。
饿汉模式
// 饿汉模式:一开始(main函数之前)就创建对象
class Singleton
{
public:
static Singleton* GetInstance()//第二步提供一个public接口来获取对象
{
return _ins;
}
void Add(const string& str)
{
_mtx.lock();
_v.push_back(str);
_mtx.unlock();
}
void Print()
{
_mtx.lock();
for (auto& e : _v)
{
cout << e << endl;
}
cout << endl;
_mtx.unlock();
}
private:
// 第一步构造函数私有化来限制类外面随意创建对象
Singleton()
{}
private:
mutex _mtx;//由于锁这个成员变量的存在,导致该类没办法进行拷贝构造,但是最好还是把拷贝构造和赋值运算符重载这两种方法封一下。
vector<string> _v;//全局唯一的一份数据(人员名单)
static Singleton* _ins;//第三步定义该类型成员目的是为了每一次创建对象都能获取同一个成员
};
Singleton* Singleton::_ins = new Singleton;//虽然构造函数私有化了,但这里相当于声明和定义分离,这里只是在类外定义,还是属于类的,所以在这里可以调用私有函数接口。只有初始化静态才可以这样操作。
int main()
{
Singleton::GetInstance()->Add("张三");
Singleton::GetInstance()->Add("张李四");
Singleton::GetInstance()->Print();
return 0;
}
//多线程访问该名单并且对其添加人员
int main()
{
srand(time(0));
int n = 30;
thread t1([n](){
for (size_t i = 0; i < n; ++i)
{
Singleton::GetInstance()->Add("t1线程:" + to_string(rand()));
}
});
thread t2([n](){
for (size_t i = 0; i < n; ++i)
{
Singleton::GetInstance()->Add("t2线程:" + to_string(rand()));
}
});
t1.join();
t2.join();
Singleton::GetInstance()->Print();
return 0;
}
懒汉模式
// 懒汉模式:第一次访问实例对象时创建
// 分析:懒汉和饿汉的优缺点
// 饿汉的缺点:
// 1、如果单例对象很大,main函数之前就要申请,第一,暂时不需要使用却占用资源,第二程序启动会受影响。
// 2、如果两个单例都是饿汉,并且有依赖关系,要求单例1先创建,单例2再创建,饿汉无法控制顺序,懒汉才可以。
// 饿汉的优点:
// 简单(相对懒汉而言)
// 懒汉完美的解决了上面饿汉的问题,但是就是相对复杂一点点。饿汉不存在线程安全,因为他设计到的是多线程获取这个唯一数据(这个数据在main函数之前就已经自动创建好了),但是懒汉会涉及到对线程创建这个唯一数据的问题
class Singleton
{
public:
static Singleton* GetInstance()//主要变化的地方
{
// 双检查加锁
if (_ins == nullptr)//这里是为了提高效率,不需要每次获取单例都要加解锁。
{
_imtx.lock();
if (_ins == nullptr)//这里还要再检查一遍,因为害怕多个线程都进到上面第三行代码的位置,那么这多个线程每个都活进来new一边对象。这里是为了保证线程安全只new一次即可。
{
_ins = new Singleton;//为空就new一个,这样只有第一次调用才会为空,后面就直接返回即可。
}
_imtx.unlock();
}
return _ins;
}
// 一般全局都要使用单例对象,所以单例对象一般不需要显示释放
// 有些特殊场景,想显示释放一下,比如说在GC类保证单例对象回收调用析构函数的时候想要持久化存储。
static void DelInstance()
{
_imtx.lock();//防止两个线程同时调用
if (_ins)
{
delete _ins;
_ins = nullptr;
}
_imtx.unlock();
}
// 内部类:GC类保证单例对象回收
class GC
{
public:
~GC()
{
DelInstance();
}
};
static GC _gc;//定义一个静态的GC对象,来保证单例可以被回收。
void Add(const string& str)
{
_vmtx.lock();
_v.push_back(str);
_vmtx.unlock();
}
void Print()
{
_vmtx.lock();
for (auto& e : _v)
{
cout << e << endl;
}
cout << endl;
_vmtx.unlock();
}
~Singleton()
{
// 持久化
// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好
}
private:
// 限制类外面随意创建对象
Singleton()
{}
private:
mutex _vmtx;//该锁是保护_v的
vector<string> _v;
static Singleton* _ins;//由于单例对象是一个指针,内置内省,是没办法自动取调用析构函数的。
static mutex _imtx;//该所是保护实例的
};
Singleton* Singleton::_ins = nullptr;//主要变化在这里
mutex Singleton::_imtx;
Singleton::GC Singleton::_gc;//GC类对象保证单例对象回收
类型的转换
void Test()
{
int i = 1;
// 隐式类型转换
double d = i;
printf("%d, %.2f\n", i, d);
int* p = &i;
// 显示的强制类型转换
int address = (int)p;
printf("%x, %d\n", p, address);
}
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符
int main()
{
//static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用 static_cast,但它不能用于两个不相关的类型进行转换
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
//reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换
为另一种不同的类型
// 这里使用static_cast会报错,应该使用reinterpret_cast。因为要对整型a进行重新解释
//int *p = static_cast<int*>(a);
int *p = reinterpret_cast<int*>(a);
return 0;
}
//const_cast最常用的用途就是删除变量的const属性,方便赋值
int main()
{
volatile const int a = 2;//volatile是防止编译器进行优化,每次访问a都要到内存当中去取,这时候cout<<a打印的就是3了。
int* p = const_cast<int*>(&a);
//int* p=(int*)&a;//这种强制类型转换也是可以实现的,但是不太规范。
*p = 3;
// 有没有修改到a
cout << a << endl;
cout << *p << endl;
}
//dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)。
//向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
//向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
//dynamic_cast只能用于父类含有虚函数的类
//dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
class A
{
public:
virtual void f(){}
};
class B : public A
{};
void fun(A* pa, const string& s)
{
cout <<"pa指向"<<s << endl;
// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
B* pb1 = (B*)pa; // 强制类型转换是不安全的,因为不管指针指向的是父类还是子类都可以转换成功。
B* pb2 = dynamic_cast<B*>(pa); // 安全的,因为dynamic_cast会先检查父类指针,如果指向父类就转换失败,如果指向的子类对象就转换成功。
cout << "[强制转换]pb1:" << pb1 << endl;
cout << "[dynamic_cast转换]pb2:" << pb2 << endl << endl;
}
int main()
{
A a;
B b;
fun(&a, "指向父类对象");
fun(&b, "指向子类对象");
cout << typeid(a).name() << endl;
decltype(a) aa;
function<bool(int,int)> gt = [](int x, int y){return x > y; };
set<int, decltype(gt)> s;
return 0;
}
RTTI
RTTI:Run-time Type identification的简称,即:运行时类型识别。
int main()
{
A a;
B b;
cout << typeid(a).name() << endl;//class A
decltype(a) aa;
function<bool(int,int)> gt = [](int x, int y){return x > y; };
set<int, decltype(gt)> s;//decltype可以用于有些对象的类型是拿不到的,比如说lambda对象的类型。
return 0;
}
IO流
int main()
{
string str;
// ctrl+c 信号强杀进程,不推荐使用。
// ctrl+z + 换行 流对象提取到结束标志
// istream& operator>> (istream& is, string& str);
while (cin>>str)//c语言中整型、指针可以转换称为bool类型
{
cout << str << endl;
}
// 凭什么istream的cin对象可以转bool
// 凭什么string的str对象可以转bool
// 因为istream类型重载了bool,explicit operator bool() const;
// 所以istream支持自定义类型转内置类型
while (str)
{
cout << str << endl;
}
return 0;
}
class A
{
public:
A(int a)
:_a(a)
{}
operator int() const//该接口让自定义类型支持转换称为内置内省
{
return _a;
}
private:
int _a;
};
int main()
{
// 内置类型转换成自定义类型
//A aa1 = static_cast<A>(1);//这种写法比下面的更加规范一些。
A aa1 = 1;//单参数构造函数支持隐式类型的转换,先用1构造一个临时对象,在采用一个拷贝构造。
// 自定义类转换成内置类型,默认是不支持的,需要在类里面增加接口:operator int() const
int x = aa1;
return 0;
}
class Date
{
friend ostream& operator << (ostream& out, const Date& d);
friend istream& operator >> (istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
operator bool() const
{
// 这里是随意写的,假设输入_year为0,则结束
if (_year == 0)
return false;
else
return true;
}
private:
int _year;
int _month;
int _day;
};
istream& operator >> (istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day;
return out;
}
int main()
{
// 自定义类型则需要我们自己重载<< 和 >>
Date d(2022, 4, 10);
cout << d;
while (d)
{
cin >> d;//输入三个整型就可以修改对象d的成员变量
cout << d;
}
return 0;
}
#include <fstream>
int main()
{
std::ofstream ofs("test.txt", ofstream::out | ofstream::app);
ofs << "hello world";
ofs << "hello world";//对于文件来说:ostream是写入
return 0;
}
struct ServerInfo
{
char _address[32];
//string _address;//二进制读写,读写对象中,不能有string,得用char数组。因为string对象里面的成员是指针,指针指向的位置才是我们的数据内容,如果我们写入文件的时候,写进去的也只能是string对象里面的指针,程序结束指向的空间就会销毁,再去文件中读取的时候读取string对象,里面的指针就是一个野指针。
int _port;
Date _date;
};
struct ConfigManager
{
public:
ConfigManager(const char* filename)
:_filename(filename)
{}
// 二进制读写,读写对象中,不能有string
void WriteBin(const ServerInfo& info)
{
ofstream ofs(_filename, ofstream::out | ofstream::binary);
ofs.write((char*)&info, sizeof(info));//在内存中是什么样子写入就是什么样子
}
//二进制读
void ReadBin(ServerInfo& info)
{
ifstream ifs(_filename, ofstream::in | ofstream::binary);
ifs.read((char*)&info, sizeof(info));
}
// 文本读写 C++文本读写更简单
// 文本读写本质,内存中任何类型都是转成字符串在写
// c语言文本读写很不方便,因为要不断转字符串
// c++封装了运算符重载以后就有很大的优势
// 文本读写不用考虑stirng还是字符数组了,因为如果是string是把其成员变量的字符数组写出去
void WriteText(const ServerInfo& info)
{
ofstream ofs(_filename);
ofs << info._address<<" ";
ofs << info._port << endl;
ofs << info._date << endl;//并且兼容自定义类型,前提是自定义类型实现了流插入和流提取运算符重载。
}
//io流读数据是要求多个值之间是有分割的,默认分割是空格或者换行,所以写的时候些时候要写空格或者换行,方便读。
void ReadText(ServerInfo& info)
{
ifstream ifs(_filename);
ifs >> info._address;
ifs >> info._port;
ifs >> info._date;
}
private:
string _filename; // 配置文件
};
int main()
{
ServerInfo winfo = { "192.0.0.1xxxxxxxxxxxxxxxxxxxxx", 80, { 2024, 8, 11 } };
string str;
cin >> str;
if (str == "二进制写")
{
ConfigManager cm("test.txt");
cm.WriteBin(winfo);//这时候如果用记事本打开就会看到一堆很乱的字符,因为内存是什么样子,写进来就是什么样子。
}
else if (str == "二进制读")
{
ServerInfo rinfo;
ConfigManager cm("test.txt");
cm.ReadBin(rinfo);
cout << rinfo._address << endl;
cout << rinfo._port << endl;
cout << rinfo._date << endl;
}
else if (str == "文本写")
{
ConfigManager cm("test.txt");
cm.WriteText(winfo);
}
else if (str == "文本读")
{
ServerInfo rinfo;
ConfigManager cm("test.txt");
cm.ReadText(rinfo);
cout << rinfo._address << endl;
cout << rinfo._port << endl;
cout << rinfo._date << endl;
}
return 0;
}
#include <sstream>
int main()
{
//向对象中写入
//ostringstream oss;
stringstream oss;
oss << 100 << " ";
oss << 11.22 << " ";
oss << "hello";
string str = oss.str();
cout << str << endl;
//重对象中提取,前提要给一个string对象
//istringstream iss(str);
stringstream iss(str);
int i;
double d;
string s;
iss >> i >> d >> s;
cout << i << endl;
cout << d << endl;
cout << s << endl;
return 0;
}
//stringstream的意义
// 序列化和反序列化,也就是将结构体对象转换成为字节流。可以转换成二进制流,还可以转换成为序列化流。
struct ChatInfo
{
string _name; // 名字
int _id; // id
Date _date; // 时间
string _msg; // 聊天信息
};
int main()
{
ChatInfo winfo = { "张三", 135246, { 2022, 4, 10 }, "晚上一起看电影吧" };
stringstream oss;
//要加空格或者换行方便提取的时候分割
oss << winfo._name << " ";
oss << winfo._id << " ";
oss << winfo._date << " ";
oss << winfo._msg;
string str = oss.str();
cout << str << endl;
stringstream iss(str);
ChatInfo rinfo;
iss >> rinfo._name;
iss >> rinfo._id;
iss >> rinfo._date;
iss >> rinfo._msg;
cout << "-------------------------------------------------------" << endl;
cout << "姓名:" << rinfo._name << "(" << rinfo._id << ") ";
cout << rinfo._date << endl;
cout << rinfo._name << ":>" << rinfo._msg << endl;
cout << "-------------------------------------------------------" << endl;
return 0;
}