基础代码如下,题目都是根据这个代码翻新出来的。
#include<iostream>
#include<stdlib.h>
#include<fstream>
using namespace std;
class Log{
public:
Log(int id, const std::string& filename):m_id(id){
cout<<"create"<<id<<endl;
}
~Log(){
cout<<"bye bye"<<endl;
}
void log(const std::string& info){}
private:
int m_id;
};
void test(){
static Log log(1,"log.log");
log.log("hello");
}
void test2(){
static Log log(2,"log.log");
log.log("world");
}
int main(){
test();
test2();
return 0;
}
运行结果:
可见,test和test2的调用顺序是——先调用test,然后调用test2的构造函数,先回收test2中的资源,然后回收test中的资源。
在调用test函数时:
先构造了一个log类,然后输出了一个log日志。
注意这里test和test2的filename都是log.log,说明两个函数打开了相同的文件log.log,问题就出在这里。那么输出的结果会是什么样的?有几个备选答案:
- hello
- world
- hello world
- world hello
这四种都可能会出现出来。
上面的代码存在两个问题,一个是出在Log类里面的log函数里,一个是出现在本身的设计上。将上述代码稍作修改如下:
#include<iostream>
#include<stdlib.h>
#include<fstream>
using namespace std;
class Log{
public:
Log( const std::string& filename){
//打开文件
m_f.open(filename.c_str(), fstream::out);
}
~Log(){ }
void log(const std::string& info){
cout<<info;
m_f<<info;
}
private:
fstream m_f;
};
void test(){
static Log log("log.log");
log.log("hello");
}
void test2(){
static Log log("log.log");
log.log("world");
}
int main(){
test();
test2();
return 0;
}
上面的代码中,执行发stream时,会打开一个文件,因此上面的代码在执行test函数时,会打开文件名为log.log的文件,打开文件之后,我们会获得该文件的文件描述符(类似于一个指向该文件的指针),之后输入“hello”; 接下来test2打开相同的文件,输入“world”。此时的运行结果为:
成功的按顺序打印了helloworld,那么我们打开log.log文件看看:
里面只有一个hello,为什么?
这与操作系统有关系。因为fstream是基于缓存的,执行fstream时,会打开一个文件,因此上面的代码在执行test函数时,会打开文件名为log.log的文件,打开文件之后,我们会获得该文件的文件描述符,之后将“hello”写入到缓存中;
接下来test2打开相同的文件,将“world”写入到缓存中。
当程序执行完毕时,会调用析构函数来回收资源,先回收test2的资源,此时发现缓存中存在test2要写入文件的“world”,因此将“world”写入到文件中;
然后调用test的析构函数,此时发现test要将“hello”写入到文件中,因此将“hello”写入到文件中,由于两个文件描述符指向了相同的文件,因此“world”就把“hello”覆盖掉了。
既然如此,那我们在log函数里面加一个flush函数,将缓存中的数据刷到文件中不就解决问题了吗?修改后的log函数如下:
再执行一次程序:
成功输出了helloworld,那么我们打开文件log.log查看一下:
文件里面只有一个world,为什么?
还是因为两个文件描述符打开的是同一个文件的缘故,因此后来者“world”覆盖掉了之前写在文件里的“hello”。
既然如此,我们将log对象作为单例放在函数外,让进程只拥有一个文件描述符,就可以正确的把helloworld输入在文件中了:
#include<iostream>
#include<stdlib.h>
#include<fstream>
using namespace std;
class Log{
public:
Log( const std::string& filename){
//打开文件
m_f.open(filename.c_str(), fstream::out);
}
~Log(){ }
void log(const std::string& info){
cout<<info;
m_f<<info;
m_f.flush();
}
private:
fstream m_f;
};
static Log log("log.log");
void test(){
log.log("hello");
}
void test2(){
log.log("world");
}
int main(){
test();
test2();
return 0;
}
此时在屏幕上和在文件里的输出就都正确了:
补充:
1、因为Log类的对象log是static的,因此,函数test结束并不会导致该对象被析构。static对象只有在程序运行结束是才会被析构。
2、由于flush函数会对性能造成一定的影响,因此一般情况下不调用该函数,但是也因此会有可能造成数据的丢失
(下面代码,亲测输出“helloworld”,没有用flush函数)
#include<iostream>
#include<stdlib.h>
#include<fstream>
using namespace std;
class Log{
public:
Log( const std::string& filename){
//打开文件
m_f.open(filename.c_str(), fstream::out);
}
~Log(){ }
void log(const std::string& info){
cout<<info;
m_f<<info;
//m_f.flush();
}
private:
fstream m_f;
};
static Log log("log.log");
void test(){
log.log("hello");
}
void test2(){
log.log("world");
}
int main(){
test();
test2();
return 0;
}
那么,如何输出“worldhello”呢?
额