Linux/C++系统编程 day7

类与对象

对象组织

const对象

  • 类对象可以声明为const对象

  • const对象只能调用构造函数、析构函数、const成员函数

指向对象的指针

  • 类名 * 指针名 = 初始化表达式;
  • 初始化表达式可以通过&对象名初始化,也可以通过动态申请内存,或者干脆不初始化在程序中再对该指针赋值

对象数组

  • 类名 数组名[对象个数];

  • Point pts[]={Point pt1(1,2) , Point pt2(3,4)};

    (不能写为Point pts[]={(1,2) , (3,4)};

    逗号表达式会让后面的覆盖前面,而后又缺省会变成0

    可以写为Point pts[]={{1,2} , {3,4};

    大括号可以初始化对象数组成员

    临时对象就是匿名对象,创建使用后会立即被析构)

堆对象

void test(){
	Point* pt1 = new Point(1,2);
	pt1->print();
	delete pt1;
	pt1 = nullptr;
	
	Point* pt2 = new Point[5]();//注意小括号里面不能写数字
	pt2->print();
	(pt2+1)->print();
	
	delete [] pt2;//注意中括号位置
}

单例模式

  • 23种GOF模式中最简单的设计模式之一,它提供了一种创建对象的方式,确保一个类只创建一个对象
  • 全局唯一资源可以这样设计,比如说log日志

思考历程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WQxRm7XA-1610351185470)(image-20210110201310951.png)]

设计方式

  • 使用类的一个私有静态指针变量指向类的唯一实例
  • 用一个公有的静态成员函数获取该实例

实现原理

  • 对于数据成员

    • 私有是为了让它不能在类外被访问
    • 静态是为了让它被所有该类成员共享,并可通过类名访问,注意要在类外初始化
  • 对于构造函数

    • 私有是为了防止在类外面被访问
    • 静态是因为只有静态成员函数才能调用静态数据成员
  • 对于公有静态函数

    • 用来操作构造函数,为保证只生成一个实例,new之前加上判空,创建之后返回一个指针,

      故可以通过这个指针来操作我们创建的对象

  • 对于析构函数

    • 若在里面调用delete会进入死循环之后栈溢出,因为delete自己类对象本身会访问析构函数,所以只能间接调用,而调用其他类的对象不会出问题

    • 不能直接在destroy里面调用私有析构函数,可以通过this->~Student()

      (销毁对象delete this)

代码示例

class Singleton{
  public:
      //静态成员函数创建,静态只能调用静态,类必须自己创建自己的唯一实例,不加static调用不了
      static Singleton *getInstance(){
  		//创建之前判是否已创建
        if(nullptr==_pInstance){
            //构造函数已私有,只能自己new一个
  			_pInstance = new Singleton();
  		}
        //给其他所有对象提供这个实例
  			return _pInstance;
      }
      //间接调用析构函数,与构造函数保持一致且可全局调用,设为static
      static void destroy(){
          if(_pInstance){
              delete _pInstance;
              _pInstance = nullptr;
          }
      }
  private:
      //构造函数设为私有,使类外无法创建栈、堆、全局对象
      Singleton(){ }
      //私有保证唯一
      static Singleton * _pInstance;
      //析构函数不能直接调用,也可公有
      ~Singleton(){ }
  }
  //静态需要在类外部初始化
  Singleton * Singleton::_pInstance = nullptr;
  
  int main(){
      Singleton *ps = Singleton::getInstance();
      //不建议通过指针delete,比较突兀,而且如果没有用指针接受创建的对象,可与构造保持一致形式
      Singleton::destory();
  }

new/delete表达式

  • 对象的销毁和析构函数的执行不等价:对于堆对象而言,析构函数的执行只是对象销毁的一个步骤
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GXijVShQ-1610351185471)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20210108151857516.png)]

new工作步骤

  • 1.调用名为operator new的标准库函数,分配足够大的原始的为类型化内存,以保存指定类型的一个对象

  • 2.运行该类型的一个构造函数并初始化对象

  • 3.返回指向新分配并构造的构造函数对象的指针

delete工作步骤

  • 1.调用析构函数,回收对象中数据成员所申请的资源

  • 2.调用名为operator delete的标准库函数释放该对象所用的内存

库函数示例

  • operator new/delete这两个库函数是静态的,里面调用malloc和free

    (没有this指针)

  • 如果将其放在类外赋值时会被调用2次,一次在new创建的对象一次在构造函数中,一次在delete创建的对象一次在析构函数中

    (放在里面是成员函数只调用一次,在构造函数和析构函数里看不出来)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZdL0Saeh-1610351185472)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20210108142848089.png)]

一个类只能创建栈/堆对象

C++中类对象建立的两种方式
  • 静态建立:A a;

    (全局、静态、栈,在编译阶段直接调用构造函数)

  • 动态建立:A*p = new A;

    (堆,在运行时new)

只在栈上对象的方法
  • 栈对象创建的条件:构造函数和析构函数都不能私有

  • 考虑禁用new运算符,步骤3无法控制,步骤2构造函数不能私有否则无法调用,故将步骤1operator new设为私有

class Student{
public:
	Student(int id, const char *name)
	:_id(id), _name(new char[strlen(name)+1]())
	{
		cout<<"Student(int, char*)"<<endl;
		strcpy(_name,name);
	}
	void print() const
	{
		cout<<"id = "<<_id<<endl
			<<"_name = "<<_name<<endl;
	}
	~Student()
	{
		cout<<"~Student()"<<endl;
		if(_name)
		{
			delete [] _name;
			_name = nullptr;
		}
	}
private:
    static void *operator new(size_t sz)
    {
		cout<<"void *operator new(size_t)"<<endl;
      void *pret = malloc(sz);
      return pret;
    }
    static void operator delete(void *pret)
    {
		cout<<"void operator delete(void *)"<<endl;
      free(pret);
    }
    int _id;
    char *_name;
}
只在堆上对象的方法
  • 考虑禁用静态建立,栈对象在使用完后会调用析构函数释放,考虑将析构函数设为私有,就无法在栈上分配空间,之后提供一个公共的调用析构函数的入口
class Student
{
public:
	Student(int id,const char* name)
	:_id(id),_name(new char[strlen(name)+1]())
	{
		cout<<"Student(int,char *)"<<endl;
		strcpy(_name,name);
	}
	void print() const
	{
		cout<<"_id = "<<_id<<endl
			<<"_name = "<<_name<<endl;
	}
	static void *operator new(size_t sz)
	{
		cout<<"void *operator new(size_t)"<<endl;
		void *pret = malloc(sz);
		return pret;
	}
	static void operator delete(void *pret)
	{
		cout<<"void operator delete(void *)"<<endl;
		free(pret);
	}
	void destroy()
	{
		this->~Student();
		delete this;
	}
private:
	~Student()
	{
		cout<<"~Student()"<<endl;
		if(_name)
		{
			delete [] _name;
			_name = nullptr;
		}
	}
	int _id;
	char *_name;
}

C++输入输出流

什么是输出输出流

  • C++的I/O发生在流中,流是字节序列。

  • 字节流从设备到内存是输入,从内存到设备是输出

常用流类型与关系

  • 标准I/O,从键盘输入到显示器输出
  • 文件I/O,从磁盘输入输出
  • 字符串I/O,从指定字符数组空间输入输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pCPtwO4U-1610351185474)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20210108155324667.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jqKRpIBu-1610351185474)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20210109112646972.png)]

流的状态与管理函数

  • C++中用iostate表示流的状态

    • badbit,系统级错误,不可恢复的读写错误,流不可再用
    • failbit,可恢复的错误
    • eofbit和failbit,达到文件结束位置
    • goodbit,流正常
  • C++标准库定义了一组成员函数查询或操作状态

    • bool badbit() const;
    • bool failbit() const;
    • bool eofbit() const;
    • bool goodbit() const;
    • void clear(std::ios_base::iostate state = std::ios_base::goodbit);
    • 使用示例
      cout<<cin.bad()<<endl
         <<cin.fail()<<endl
         <<cin.eof()<<endl
         <<cin.good()<<endl;
      printStreamStatus();//打印流的状态
      cin.clear();//重置流的状态
      cin.ignore(1024,'\n');//清空缓冲区
      //之后可以重新cin
      
      //循环输入
      int num=0;
      while(cin>>num ,!cin.eof()){//ctrl+d正常退出,+c异常退出,+z退到后台
          if(cin.bad()){
      		cerr<<"The stream is bad"<<endl;
              return;
          }
          else if(cin.fail()){
      		cin.clear();
            cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
          }
          else{
      		cout<<"number = "<<num<<endl;
          }
      }
    

标准I/O

  • 标准输入:cin

  • 标准输出:cout,cerr,clog

缓冲区

  • 内存中的一部分,用来缓解高速CPU和低速I/O设备速度不匹配的问题
类型:
  • 全缓冲:填满才进行实际操作,比如对磁盘文件读写

  • 行缓冲:遇到换行符时进行实际操作,比如键盘输入数据

  • 不带缓冲:cerr/stderr出错信息直接显示

  • 缓冲区刷新就是写入真实的输出设备或文件

输出缓冲区内容被刷新的情况
  • 程序正常结束(有一个收尾操作就是清空缓冲区);

  • 缓冲区满(包含正常情况和异常情况);

  • 使用操纵符显式地刷新输出缓冲区,如:

    • endl 刷新缓冲区并换行

    • flush 刷新缓冲区不换行

      ends 不能刷新,ends输出的是字符’\0’,不同的操作系统对’\0’处理的方式也不同,在Windows系统下把‘\0’当作空格输出,而在Linux系统下把‘\0’当作而什么都不输出;

  • 使用unitbuf 设置流的状态以及nounitbuf回到正常状态;

  • 输出流与输入流相关联,tie,此时在读输入流时将刷新其关联的输出流的输出缓冲区。

    • ubuntu缓冲区大小1024B
    • sleep(秒数)在unistd.h头文件中

文件I/O

  • 头文件fstream
  • &&右值引用
  • explicit防止隐式转换

文件输入流ifstream

  • ifstream ifs(“文件名.类型”)

    (当文件不存在时,打开文件失败)

  • ifs >> word

    (从文件读数据到屏幕,默认空格为分隔符)

  • while(getline(ifs,line))

    (文件一次读一行,可以使用vector存起来)

文件输出流ofstream

  • ofstream ofs(“文件名.类型”)

    (默认情况当文件不存在的时候创建文件,当文件已经存在的时候,先清空再写)

  • std::ios::app

    (追加,每次在文件末尾加入数据)

  • std::ios::ate

    (写入最初在文件的末尾)

文件输入输出流fstream

  • tellp/tellg

    (查找文件指针的位置)

  • seekp/seekg

    (设置文件指针的位置)

    • std::ios::beg

      (起始)

    • std::ios::cur

      (当前)

    • std::ios::end

      (末尾)

代码示例

#include <fstream>
#include <iostream>
#include <string>
using namespace std;
void test1(){
	ifstream ifs("Point.cc");//继承自输入流应有类似cin功能
	if(!ifs.good()){//没有该文件
		cerr << "ifstream is not good"<<endl;
      return;
    }
    ofstream ofs("wuhan.txt");//对于文件继承输出流应有类似cout功能
    if(!ofs.good()){
		cerr << "ofstream is not good"<<endl;
      ifs.close();//前面打开了,此处关闭文件
      return;
    }
    string line;
    while(ifs >> line,!ifs.eof()){
		ofs<<line<<endl;
    }
    ifs.close();
    ofs.close();
}

#include <iostream>
#include <fstream>
using std::fstream;

void test2(){
    //对于文件输入输出流而言,当文件不存在的时候报错
    fstream fs("wd.txt");
    if(!fs.good()){
		cerr<<"fstream is bad"<<endl;
		return;
	}
	int num=0;
	for(size_t idx=0;idx!=5;++idx){
		cin>>num;
		fs<<num<<" ";
	}
	cout<<"pos = "<<fs.tellg()<<endl;//文件指针位置(字节),计算空格,tellg()写,tellp()读,追加会调到文件末尾std::ios::app写/ate读
	fs.seekg(0);//绝对位置
	fs.seekg(0,std::ios::beg);//相对开头偏移2,可用参数beg/cur/end
	for(size_t idx=0;idx!=5;++idx){
		fs>>num;
		cout<<num<<" ";
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值