有一些平时编程中经常遇到的,逻辑比较简单实际中也经常被需要的功能,在刚刚成为程序员阶段却因为不知道有什么合适的函数(一般是比较基本的,跟系统连接紧密的函数),成了阻碍进度的大山。所以想在平时做一些整理,等下次要用的时候可以很快回忆起来。
按键跳出循环
常用的设计一个菜单(即便是命令行下简单的菜单)然后根据指令跳出程序对任何初学者来说也都不难,但有时候,我们希望程序能循环的执行某项功能,只在我需要的时候跳出(或者跳转),这时候,一般的函数就不管用了。因为无论是c的scanf
、getchar
还是c++的cin
都是阻塞式IO。也就是说,程序运行到这里,会被挂起等到IO的输入——如果没有输入,则会停滞在输入的代码段,无法实现一直循环某项函数的功能。当然,这里多线程也是个办法,可以让需要的循环在子线程里跑,然后在主线程里通过阻塞式IO进行控制——说白了就是控制部分程序单独占一个线程。但这本身是个非常微不足道的功能,为它单独启动一个线程感觉实在是没有必要(其实我想说的是——不够优雅,程序员还是很追求程序的简洁和高效的)。因此这里,非阻塞式IO就派上了用场(插一句,我在刚开始想到要实现这个功能的时候,还没有能这么清晰地意识到我需要的函数的特点:非阻塞IO)。
Windows下,在<conio.h>
头文件下,有kbhit()
和getch()
两个函数可以用。特别的,这两个函数在检测输入时,即使不按下回车键也能检测到,就像我们在IDE里run程序的时候,执行完最后有个Press any key to continue,我们随便键盘敲个键就算不回车也能退出命令行。kbhit()
实现键盘按键检测,若有输入则返回一个非0值,否则返回0;而getch()
实现获取输入一个字符。
#include <conio.h>
while( !kbhit() ){ //没检测到键盘输入时循环
do_something;
}
getch(); //吸收掉输入的字符
注意kbhit()
只是检测输入,并不将其从缓冲区读取出来,所以往往要想办法处理掉这个输入。上面代码中是用了getch()
函数,当然有需要也可以考虑有cin.sync()
来清空缓冲区(键盘输入没按下回车前,cin是看不到的,所以也清空不聊),但可能要多做一些工作。
<conio.h>
并不是C/C++的标准函数库,所以Linux下没有。在Linux下,需要自己设置IO的状态。可以通过<fcntl.h>
下的fcntl
来实现控制。具体关于fcntl函数的资料很多,这里就不细说。
讲IO设置为非阻塞状态的办法为:
const int fd=STDIN_FILENO; //文件描述符
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0)|O_NONBLOCK);
想要重新设置为阻塞状态则
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0)&~O_NONBLOCK);
另外还需要注意的一点时,当键盘没有任何输入时,cin
、getchar
得到的输入并非空值,而是-1
所以上述功能的控制应该这么写:
const int fd=STDIN_FILENO;
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0)|O_NONBLOCK);
char s;
while( (s=getchar())==-1 ){ //没有输入时循环
do_something;
}
//有需要的话重新改回IO流的原始状态
修改char*
的值
大部分针对C++的接口,为了兼容C,都会尽量避免使用C++的特性。其中很典型的一个地方就是C风格字符串char*
。所以不得不回来回顾一下这方面的细节知识。
由于默认条件下,通过char* p="xxx"
申明的xxx是常量,保存在内存的常量数据区域。如果想要修改p变量对应的值,可以通过p="yyy"
的方式。使用这个方法不用担心内存问题,因为底层实际是申请内存新创建了一个字符串常量“yyy”然后把p指向它。比如下面这段程序:
char *a = "000";
printf("%x \n", a);
// cout << &a[0] << endl;
a = "123";
printf("%x %s\n", a, a);
// cout << &a<<" " << endl;
结果是
3f4302b0
3f430c10 123
注意到两次输出的地址结果有差异,可以发现第二次字符串是写在是新申请的内存空间上。
注释掉的部分是我一开始用常规非格式化C++的方法输出的,以为输出了的指针变量值(即字符串地址),发现修改前后的结果都一样。后来仔细测试后发现&a输出的是指针变量a的地址(而非值),所以不变化也正常了。
顺便补充一点,从这篇博客学到:通过char[]
申明的字符串数组默认为const类型——虽然它被保存在栈上而非常量区。比如下面两段实例:
char * s1 = "abcd";
s1[0] = 'm';//segment fault
char s1[] = "abcd";
s1[0] = 'm';
如果实在想要修改可以通过memcpy()
或者strcpy()
之类的函数实现。但这些方法也无法修改由char*
定义的字符串常量的值。
单例模式
参考资料:http://www.cnblogs.com/qiaoconglovelife/p/5851163.html 和 http://blog.csdn.net/jason0539/article/details/23297037/
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态。
单例模式一般可以有懒汉模式和饿汉模式两种设计思路。
- 懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,所以上边的经典方法被归为懒汉实现;
- 饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。
特点与选择:
- 由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
- 在访问量较小时,采用懒汉实现。这是以时间换空间。
懒汉模式:
- 方法1:加锁的经典懒汉实现
class singleton
{
private:
static singleton* p;
protected:
singleton(){}; //将构造函数设为私有保证不能被显示构造
public:
static std::mutex mtx;
static singleton* initance()
{
if (p == NULL)
{
lock_guard<std::mutex> lkg(mtx);
if (p == NULL)
p = new singleton();
}
return p;
}
};
singleton* singleton::p = NULL;
上述代码中采用了双重判断的方法,这是因为对信号量加锁是一件开销非常大的工作,所以能避免就避免。多写几行代码来减少计算机的开销。
- 方法2:内部静态变量的懒汉实现
class singleton
{
protected:
singleton(){}; //将构造函数设为私有保证不能被显示构造
static std::mutex mtx;
public:
static singleton* initance()
{
lock_guard<std::mutex> lck(mtx);
static singleton obj;
return &obj;
}
};
static std::mutex mtx;
在instance函数里定义一个静态的实例,也可以保证拥有唯一实例,在返回时只需要返回其指针就可以了。这里利用了静态变量的一大特点:生命周期为整个进程。即使退出了作用域,仍然不会被析构,可以正常访问。
饿汉模式:
class singleton
{
protected:
singleton(){}; //将构造函数设为私有保证不能被显示构造
private:
static singleton* p;
public:
static singleton* initance();
};
singleton* singleton::p = new singleton;
singleton* singleton::initance()
{
return p;
}
利用静态变量在进程启动时就初始化的特点,保证只能产生一个实例。同时用静态函数来获取该指针。
顺便补充:多进程条件下,传统单例模式会失效,因为不同的进程都各自拥有自己的内存空间,无论是静态变量还是全局变量都存在自己的内存空间上。因此需要额外通过进程通信(以及互斥)功能来确保只产生了一个实例。
emplace_back
参考资料:https://www.2cto.com/kf/201512/454625.html
c++11中新增了这个方法,效率一般比push_back
高很多。
重载转换操作符
参考资料:C++重载转型操作符
C++支持将对象按照编程者的想法实现类型转换。具体的成员函数声明如:
operator 类型名 ();
几个注意事项
- 函数没有返回类型;
- 虽然没有返回类型但是函数体中必须有return语句,其返回类型是由类型名来指定的;
- 转型操作符重载函数只能以类的成员函数的形式进行重载,而不能以友元函数或顶层函数的形式进行重载。
这时编译期会按照上下文需求在指定的位置对类对象进行相应的转换。
class test {
double d;
int i;
public:
test(int i1, double d1) :d(d1), i(i1) {};
operator int() {
return i;
}
operator double() {
return d;
}
}
注意编译期无法自动处理有歧义的语句,例如对于上述类定义,如果尝试cout<<t<<endl
(假设t是前文定义的test对象),编译期会报错,因为它不知道该输出(double)t
还是(int)t
。但如果对于int a=t
则不会提示错误。因为编译期很清楚等号右边应该是int类型的(如果不是int才考虑从double的隐式类型转换)。
当然这类语句可能会造成无意识地误用,比如在编程者希望某处被解释为test类型时,却被解释为int/double类型了。因此使用这类成员函数,强烈建议加上explict关键字,这样的话,比如使用static_cast<>
才能将对象转换为需要的值,提高了程序的可读性也减少了出错的可能,如
explicit operator int(){
return d;
}
-未完待续