类与对象
对象组织
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日志
思考历程
设计方式
- 使用类的一个私有静态指针变量指向类的唯一实例
- 用一个公有的静态成员函数获取该实例
实现原理
-
对于数据成员
- 私有是为了让它不能在类外被访问
- 静态是为了让它被所有该类成员共享,并可通过类名访问,注意要在类外初始化
-
对于构造函数
- 私有是为了防止在类外面被访问
- 静态是因为只有静态成员函数才能调用静态数据成员
-
对于公有静态函数
-
用来操作构造函数,为保证只生成一个实例,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表达式
- 对象的销毁和析构函数的执行不等价:对于堆对象而言,析构函数的执行只是对象销毁的一个步骤
new工作步骤
-
1.调用名为operator new的标准库函数,分配足够大的原始的为类型化内存,以保存指定类型的一个对象
-
2.运行该类型的一个构造函数并初始化对象
-
3.返回指向新分配并构造的构造函数对象的指针
delete工作步骤
-
1.调用析构函数,回收对象中数据成员所申请的资源
-
2.调用名为operator delete的标准库函数释放该对象所用的内存
库函数示例
-
operator new/delete这两个库函数是静态的,里面调用malloc和free
(没有this指针)
-
如果将其放在类外赋值时会被调用2次,一次在new创建的对象一次在构造函数中,一次在delete创建的对象一次在析构函数中
(放在里面是成员函数只调用一次,在构造函数和析构函数里看不出来)
一个类只能创建栈/堆对象
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,从指定字符数组空间输入输出
流的状态与管理函数
-
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<<" ";
}
}