C++多态和文件读写

news2024/11/16 9:26:12

C++黑马,每天1.5倍速2个视频(1小时),看到9月1日完成314个视频

目录

🔑多态

🌳基本语法

🌳原理剖析

🌳案例1 -- 计算器类

🌳纯虚函数和抽象类

🌳案例2 -- 制作饮品

🌳虚析构和纯虚析构

🌳案例3 -- 电脑组装需求分析

🌳电脑组装具体实现

🔑文件操作

🌳文本文件 -- 写文件

🌳文本文件 -- 读文件

🌳二进制文件 -- 写文件

🌳二进制文件 -- 读文件


🔑多态

多态是C++面向对象三大特性之一

🌳基本语法

分两类

1,静态多态:函数重载 和 运算符重载属于静态多态,复用函数名
2,动态多态:派生类(子类)和虚函数实现运行时多态 

静态和动态区别

1,静态多态的函数地址早绑定  --  编译阶段确定函数地址
2,动态多态的函数地址晚绑定  --  运行阶段确定函数地址

晚绑定,通过派生类和虚函数实现,具体就是

继承的前提下,子类中重写父类虚函数

重点

动态多态满足条件
1, 继承关系

class Dog:public Animal //狗类继承自动物类
class Cat:public Animal //猫类继承自动物类

2, 子类重写父类虚函数  

virtual void speak() //父类中虚函数
void speak() //子类中重写

重写  1,函数返回值类型 2,函数名 3,参数列表 (完全相同)
重写speak函数,可实现不同输出

补充

//父类指针或引用  指向子类对象
void doSpeak(Animal &animal) //Animal & animal = cat;

void test01()
{
    Cat cat;
    doSpeak(cat); 
}
#include <iostream>
using namespace std;

//多态

//动物类
class Animal
{
public:
    //父类中函数前加virtual, 即可实现晚绑定
    virtual void speak() //定义一个公共的speak函数, 用于所有子类的重写
    {
        cout<<"动物在说话"<<endl;
    }
};

//猫类
class Cat:public Animal //猫类继承自动物类
{
public:
    //重写  函数返回值类型 函数名 参数列表 完全相同
    void speak() //重写speak函数, 实现不同输出
    {
        cout<<"小猫在说话"<<endl;
    }
};

//执行说话的函数, 传入动物类引用

//地址早绑定  编译阶段确定函数地址
//如果想执行猫说话  这个函数地址得晚绑定

//动态多态满足条件
//1, 继承关系
//2, 子类重写父类虚函数

//动态多态使用

//父类指针或引用  指向子类对象
void doSpeak(Animal &animal) //Animal & animal = cat;
{
    animal.speak();
}

class Dog:public Animal
{
public:
    void speak()
    {
        cout<<"狗在说话"<<endl;
    }
};

void test01()
{
    Cat cat;
    //doSpeak可以接受所有派生于Animal类的对象, Cat类是Animal类的子类
    //在doSpeak()中被视为Animal类的对象
    doSpeak(cat); //通过doSpeak函数调用传入的动物类引用的speak函数, 实现多态

    Dog dog;
    doSpeak(dog);
}

int main()
{
    test01();

    return 0;
}
小猫在说话
狗在说话

🌳原理剖析

当子类重写父类虚函数
子类中的虚函数表  内部  会替换成  子类中的虚函数地址

虚函数指针指向虚函数表

子类未重写父类虚函数时👇

子类重写父类虚函数后👇

父类虚函数地址   就被替换成了  子类虚函数地址

这就是动态多态的原理,实现了地址的晚绑定

🌳案例1 -- 计算器类

利用普通写法和多态,设计实现两个操作数进行运算的计算器类

多态优点:
1,代码组织结构清晰
2,可读性强
3,利于前期和后期的扩展和维护

#include<iostream>
using namespace std;

//普通写法
class Calculator
{
public:
    int getResult(string oper)
    {
        if(oper == "+")
            return m_Num1 + m_Num2;
        else if(oper == "-")
            return m_Num1 - m_Num2;
        else if(oper == "*")
            return m_Num1 * m_Num2;
        //扩展新功能, 需要修改源码
        //真实开发中, 提倡开闭原则,
        //开闭原则: 对扩展开发,对修改关闭
    }

    int m_Num1, m_Num2;
};

void test01()
{
    //创建计算器对象
    Calculator c;
    c.m_Num1 = 10;
    c.m_Num2 = 13;

    cout<<c.getResult("+")<<endl;
    cout<<c.getResult("-")<<endl;
    cout<<c.getResult("*")<<endl;
}

//利用多态实现计算器

//实现计算器抽象类
class AbstractCalculator
{
public:
    //多态: 父类的虚函数便于子类重写
    virtual int getResult()
    {
        return 0;
    }

    int m_Num1, m_Num2;
};

//加法计算器类
class AddCalculator:public AbstractCalculator
{
public:

    int getResult()
    {
        return m_Num1 + m_Num2;
    }
};

//减法计算器
class SubCalculator:public AbstractCalculator
{
public:

    int getResult()
    {
        return m_Num1 - m_Num2;
    }
};

//乘法计算器
class MulCalculator:public AbstractCalculator
{
public:

    int getResult()
    {
        return m_Num1 * m_Num2;
    }
};

void test02()
{
    //多态使用条件
    //父类  指针或引用  指向子类对象

    //加法运算
    AbstractCalculator * abc = new AddCalculator; //创建一个加法计算器对象
    abc->m_Num2 = 5, abc->m_Num1 = 66;
    cout<<abc->m_Num1<<"+"<<abc->m_Num2<<"="<<abc->getResult()<<endl;

    //堆区数据记得释放(销毁)
    //销毁后只是把堆区数据销毁, 但指针的类型还是父类的指针
    delete abc;

    //减法运算
    abc = new SubCalculator;
    abc->m_Num2 = 5, abc->m_Num1 = 66;
    cout<<abc->m_Num1<<"-"<<abc->m_Num2<<"="<<abc->getResult()<<endl;
    delete abc;

    //乘法运算
    abc = new MulCalculator;
    abc->m_Num2 = 5, abc->m_Num1 = 66;
    cout<<abc->m_Num1<<"*"<<abc->m_Num2<<"="<<abc->getResult()<<endl;
    delete abc;

}

int main()
{
    //test01();
    test02();

    return 0;
}
66+5=71
66-5=61
66*5=330

使用多态,代码量虽然更大了,但我们依然提倡,原因:

1,组织结构清晰(哪里出错了,可以快速定位)

2,可读性强(一眼能看出你写了什么,功能是)

3,前后期扩展和维护方便(比如计算器类,开始写了加减乘除,后期想增加乘方和开放的功能,只需要在除法后追加两个函数即可)

🌳纯虚函数和抽象类

多态,父类中的虚函数经常用不到,主要都是调用子类重写的内容

所以可以将虚函数改为纯虚函数

纯虚函数语法,也就是将原来的 {} 变成了 = 0;

virtual 返回值类型 函数名 (参数列表) = 0;
virtual int getResult() {} //虚函数
virtual int getResult() = 0; //纯虚函数

当类中存在纯虚函数,这个类就成了抽象类

抽象类特点:
1,无法实例化对象
2,子类必须重写抽象类中的纯虚函数,否则也属于抽象类

#include<iostream>
using namespace std;

//纯虚函数和抽象类
class Base
{
public:

    //类中存在纯虚函数, 这个类就是抽象类
    virtual void func() = 0; //纯虚函数
    //抽象类特点
    //1, 无法实例化对象
    //2, 抽象类子类  必须重写父类中纯虚函数, 否则也属于抽象类

};

class Son:public Base
{
public:

    virtual void func()
    {
        cout<<"func() 调用"<<endl;
    }; //重写父类纯虚函数

};

void test01()
{
    //Base b; //抽象类无法实例化对象
    //new Base; //抽象类无法实例化对象

    //Son s; //子类必须重写父类纯虚函数, 否则无法实例化对象

    //多态的意义就是, 通过一个父类指针, 由于new的对象不同, 可以调用不同的函数
    Base * base = new Son; //父类的指针指向子类对象
    //new的是哪个对象, 就调用哪个对象的func()函数
    base->func(); //调用func()
}


int main()
{
    test01();

    return 0;
}
func() 调用

🌳案例2 -- 制作饮品

制作饮品:煮水,泡茶,倒入杯中,加入辅料

利用多态实现,提供抽象制作饮品基类(父类),提供子类制作咖啡和茶叶

#include<iostream>
using namespace std;

//多态案例2 制作饮品

//抽象基类, 制作饮品
class AbstractDrinking
{
public:
    //煮水
    virtual void Boil() = 0; //纯虚函数
    //冲泡
    virtual void Brew() = 0;
    //倒入杯中
    virtual void PourInCup() = 0;
    //加入辅料
    virtual void PutSomething() = 0;
    //制作饮品
    void makeDrink()
    {
        //步骤: 煮水 冲泡 倒入杯中 加入辅料
        Boil();
        Brew();
        PourInCup();
        PutSomething();
    }
};

//制作咖啡
class Coffee:public AbstractDrinking
{
public:
    //煮水
    virtual void Boil()
    {
        cout<<"煮农夫山泉"<<endl;
    }
    //冲泡
    virtual void Brew()
    {
        cout<<"冲泡咖啡"<<endl;
    }
    //倒入杯中
    virtual void PourInCup()
    {
        cout<<"倒入杯子里"<<endl;
    }
    //加入辅料
    virtual void PutSomething()
    {
        cout<<"加入方糖和牛奶"<<endl;
    }
};

//制作茶叶
class Tea:public AbstractDrinking
{
public:
    //煮水
    virtual void Boil()
    {
        cout<<"煮矿泉水"<<endl;
    }
    //冲泡
    virtual void Brew()
    {
        cout<<"冲泡茶叶"<<endl;
    }
    //倒入杯中
    virtual void PourInCup()
    {
        cout<<"倒入小茶杯"<<endl;
    }
    //加入辅料
    virtual void PutSomething()
    {
        cout<<"加入柠檬"<<endl;
    }
};

//制作函数
void doWork(AbstractDrinking * abs) //参数是父类指针
{
    abs->makeDrink(); //父类的makeDrink函数

    delete abs; //堆区数据手动释放
}

void test01()
{
    //接口都是makeDrink(), 一个接口多种形态, 传入的对象属于不同子类
    //当需要制作其他饮品, 不需要修改原代码, 只需在后面加

    //制作咖啡
    doWork(new Coffee); //父类指针指向子类对象

    cout<<"--------------"<<endl;
    //制作茶叶
    doWork(new Tea); //这就是多态
    //不同对象的传入
}

int main()
{
    test01();

    return 0;
}
煮农夫山泉
冲泡咖啡
倒入杯子里
加入方糖和牛奶
--------------
煮矿泉水
冲泡茶叶
倒入小茶杯
加入柠檬

🌳虚析构和纯虚析构

多态使用时,子类有属性开辟到堆区,那么父类指针释放时无法调用到子类的析构代码,会造成数据泄露

解决方法:父类中析构函数,改为虚析构或纯虚析构

虚析构和纯虚析构

共性

1,解决父类指针释放子类对象
2,需要具体函数实现

区别

纯虚析构函数,它所在的类属于抽象类,无法实例化对象

虚析构语法

virtual ~类名() {} //类内声明+实现

//比如
virtual ~Animal() {}

纯虚析构语法

virtual ~类名() = 0; //类内声明
类名::~类名() {} //类外实现

//比如
virtual ~Animal() = 0;
Animal::~Animal() {}

代码1  未解决

#include<iostream>
using namespace std;

//虚析构和纯虚析构
class Animal
{
public:

    Animal()
    {
        cout<<"Animal构造"<<endl;
    }

    ~Animal()
    {
        cout<<"Animal析构"<<endl;
    }

    virtual void speak() = 0; //纯虚函数

};

class Cat:public Animal
{
public:

    Cat(string name)
    {
        cout<<"Cat构造"<<endl;
        m_Name = new string(name); //将名字的数据用new创建在堆区
        //new出来的string, 返回的就是string的指针
    }

    virtual void speak() //子类重写父类纯虚函数
    {
        cout<<*m_Name<<"小猫在说话"<<endl; //输出指针所指向的值要解引用
    }

    //在Cat析构函数中, 将堆区数据释放
    ~Cat()
    {
        if(m_Name != NULL) {
            cout<<"Cat析构"<<endl;
            delete m_Name;
            m_Name = NULL; //置空
        }
    }

    string *m_Name; //用指针维护堆区数据
};

void test01()
{
    Animal * animal = new Cat("Jack"); //父类指针指向子类对象, new Cat
    animal->speak(); //父类指针对象调用speak函数
    delete animal; //释放堆区数据
}


int main()
{
    test01();

    return 0;
}
Animal构造
Cat构造
Jack小猫在说话
Animal析构

有个问题,少了Cat析构函数调用,那么Cat析构中delete也没起作用,堆区数据没释放,导致内存泄漏

为什么呢,先看看过程👇

创建Cat对象时

Animal * animal = new Cat("Jack"); //父类指针指向子类对象, new Cat

首先会调用父类的构造函数

Animal()
{
    cout<<"Animal构造"<<endl;
}

接着调用子类自身,Cat的构造函数

Cat(string name)
{
    cout<<"Cat构造"<<endl;
    m_Name = new string(name); //将名字的数据用new创建在堆区
    //new出来的string, 返回的就是string的指针
}

接着执行子类中speak()函数

animal->speak(); //父类指针对象调用speak函数

最后释放堆区数据

delete animal; //释放堆区数据

先看代码

void test01()
{
    Animal * animal = new Cat("Jack"); //父类指针指向子类对象, new Cat
    animal->speak(); //父类指针对象调用speak函数
    delete animal; //释放堆区数据
}

由于我们用父类指针指向子类对象,当 delete 父类指针animal时,程序并不会运行子类Cat的析构函数

解决办法:将父类中~Animal()析构函数,改为虚析构virtual ~Animal()

virtual ~Animal() 

先看看对应代码

1,虚析构

//利用虚析构 解决父类指针释放子类对象时不干净的问题
virtual ~Animal()
{
    cout<<"Animal析构"<<endl;
}

2,纯虚析构

//纯虚析构  需要声明也需要实现
//有了纯虚析构  这个类也属于抽象类 抽象类无法实例化对象
virtual ~Animal() = 0; 

Animal::~Animal() //Animal作用域下的纯虚析构, 类外实现
{
    cout<<"Animal 纯虚析构"<<endl;
}

不论是虚析构,还是纯虚析构,都为了解决多态中,子类析构函数无法调用的问题 

代码2  虚析构与纯虚析构

#include<iostream>
using namespace std;

//虚析构和纯虚析构
class Animal
{
public:

    Animal()
    {
        cout<<"Animal构造"<<endl;
    }

    //利用虚析构 解决父类指针释放子类对象时不干净的问题
//    virtual ~Animal()
//    {
//        cout<<"Animal析构"<<endl;
//    }

    //纯虚析构  需要声明也需要实现
    //有了纯虚析构  这个类也属于抽象类 抽象类无法实例化对象
    virtual ~Animal() = 0;

    virtual void speak() = 0; //纯虚函数

};

Animal::~Animal() //Animal作用域下的纯虚析构, 类外实现
{
    cout<<"Animal 纯虚析构"<<endl;
}


class Cat:public Animal
{
public:

    Cat(string name)
    {
        cout<<"Cat构造"<<endl;
        m_Name = new string(name); //将名字的数据用new创建在堆区
        //new出来的string, 返回的就是string的指针
    }

    virtual void speak() //子类重写父类纯虚函数
    {
        cout<<*m_Name<<"小猫在说话"<<endl; //输出指针所指向的值要解引用
    }

    //在Cat析构函数中, 将堆区数据释放
    ~Cat()
    {
        if(m_Name != NULL) {
            cout<<"Cat析构"<<endl;
            delete m_Name;
            m_Name = NULL; //置空
        }
    }

    string *m_Name; //用指针维护堆区数据
};

void test01()
{
    Animal * animal = new Cat("Jack"); //父类指针指向子类对象, new Cat
    animal->speak(); //父类指针对象调用speak函数

    //父类指针析构时 不会调用子类中析构函数 所以子类如果有堆区数据 内存会泄露

    delete animal; //释放堆区数据
}


int main()
{
    test01();

    return 0;
}

总结

1,虚析构 / 纯虚析构,用以解决通过父类指针释放子类对象
2,若子类中没有堆区数据,不用写虚析构 / 纯虚析构
3,类中有纯虚析构函数,这个类也是抽象类

🌳案例3 -- 电脑组装需求分析

电脑组成部件,CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并提供不同厂商生产的不同零件,例如Intel喝Lenovo
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时,组装3台不同电脑进行工作

🌳电脑组装具体实现

具体解释下第48行

m_vc->display(); //父类指针调用接口函数, 实现多态

在这里,接口指的是虚函数calculate()、display()和storage()。在Computer类中,通过父类指针调用这些接口函数,实现了对不同零件类的多态调用。父类指针指向其子类对象,在运行时可以根据具体对象类型,动态绑定具体的实现函数,实现多态行为

#include<iostream>
using namespace std;

//抽象不同零件类

//抽象CPU类
class CPU
{
public:
    //抽象计算函数
    virtual void calculate() = 0;
    //父类中纯虚函数不需要写任何实现
};

//抽象显卡类
class VideoCard
{
public:
    //抽象显示函数
    virtual void display() = 0;
};

//抽象内存条类
class Memory
{
public:
    //抽象存储函数
    virtual void storage() = 0;
};

//电脑类
class Computer
{
public:
    Computer(CPU * cpu, VideoCard * vc, Memory * mem) //传入三个指针
    {
        //三个指针接收3个不同零件
        m_cpu = cpu, m_vc = vc, m_mem = mem; 
    }

    //提供工作函数
    void work()
    {
        //父类指针m_cpu等在调用接口时, 就是多态了

        //让零件工作起来, 调用接口(函数)
        m_cpu->calculate(); //父类中纯虚函数在子类的实现
        m_vc->display(); //父类指针调用接口函数, 实现多态
        m_mem->storage(); //传入不同子类的对象
    }

    //提供析构函数  释放3个电脑零件的数据
    ~Computer()
    {
        //释放CPU零件
        if(m_cpu != NULL) {
            delete m_cpu;
            m_cpu = NULL;
        }

        //释放显卡零件
        if(m_vc != NULL) {
            delete m_vc;
            m_vc = NULL;
        }

        //释放CPU零件
        if(m_mem != NULL) {
            delete m_mem;
            m_mem = NULL;
        }
    }

private:
    //三个父类的指针
    CPU * m_cpu; //CPU零件指针
    VideoCard * m_vc; //显卡零件指针
    Memory * m_mem; //内存条零件指针
};

//具体厂商

//Intel厂商
class IntelCPU:public CPU
{
public:
    virtual void calculate() //父类虚函数子类重写
    {
        cout<<"Intel的CPU开始计算了!"<<endl;
    }
};

class IntelVideoCard:public VideoCard
{
public:
    virtual void display() //父类虚函数子类重写
    {
        cout<<"Intel的显卡!"<<endl;
    }
};

class IntelMemory:public Memory
{
public:
    virtual void storage() //父类虚函数子类重写
    {
        cout<<"Intel的内存条!"<<endl;
    }
};

//这就是多态的好处, 扩展性强, 只需要往后追加代码, 不需要修改原代码
//Lenovo厂商
class LenovoCPU:public CPU
{
public:
    virtual void calculate() //父类虚函数子类重写
    {
        cout<<"Lenovo的CPU开始计算了!"<<endl;
    }
};

class LenovoVideoCard:public VideoCard
{
public:
    virtual void display() //父类虚函数子类重写
    {
        cout<<"Lenovo的显卡!"<<endl;
    }
};

class LenovoMemory:public Memory
{
public:
    virtual void storage() //父类虚函数子类重写
    {
        cout<<"Lenovo的内存条!"<<endl;
    }
};

//测试, 组装不同电脑
void test01()
{
    //第1台电脑组装

    //第一台电脑零件
    CPU * intelCpu = new IntelCPU; //父类指针指向子类零件
    VideoCard * intelCard = new IntelVideoCard;
    Memory * intelMem = new IntelMemory;

    cout<<"第1台电脑开始工作"<<endl;
    //创建第一台电脑
    //这里new, 调用构造函数来初始化新对象
    Computer * computer1 = new Computer(intelCpu, intelCard, intelMem);
    computer1->work(); //computer类里的work()函数, 类创建对象, 对象调用类中函数
    delete computer1;


    //第2台电脑组装
    cout<<endl<<"-----------------------"<<endl;
    cout<<"第2台电脑开始工作"<<endl;
    //下一行的用法, 匿名函数, 直接在new语句创建一个临时对象作为参数传递给Computer()
    Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);
    computer2->work(); //computer类里的work()函数, 类创建对象, 对象调用类中函数
    delete computer2;

    //第3台电脑组装
    cout<<endl<<"-----------------------"<<endl;
    cout<<"第3台电脑开始工作"<<endl;

    Computer * computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new IntelMemory);
    computer3->work(); //computer类里的work()函数, 类创建对象, 对象调用类中函数
    delete computer3;
}


int main()
{
    test01();

    return 0;
}
第1台电脑开始工作
Intel的CPU开始计算了!
Intel的显卡!
Intel的内存条!

-----------------------
第2台电脑开始工作
Lenovo的CPU开始计算了!
Lenovo的显卡!
Lenovo的内存条!

-----------------------
第3台电脑开始工作
Lenovo的CPU开始计算了!
Intel的显卡!
Intel的内存条!

🔑文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束,都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件#include<fstream> -- 文件流

文件类型分2种:
1,文本文件 -- 以文本的ASCII码形式存储,能看懂
2,二进制文件 -- 以文本的二进制形式存储,不能读懂它们

操作文件3个
1,ofstream:写
2,ifstream:读
3,fstream:读写

🌳文本文件 -- 写文件

写文件步骤
1,头文件
#include<fstream>
2,创建流对象
ofstream ofs;    --- ofstream是输出流这个类
3,打开文件
ofs.open("文件路径", 打开方式);
4,写数据
ofs << "写入的数据";
5,关闭文件
ofs.close();

#include <iostream>
#include<fstream> //头文件
using namespace std;

//文本文件  写文件
void test01()
{
    //1, 头文件
    //2, 创建流对象
    //创建对象
    ofstream ofs; //output写文件  stream流

    //3, 指定打开方式
    ofs.open("text.txt", ios::out); //路径 + 打开方式 - 写
    //4, 写内容
    ofs << "姓名: 张三"<<endl; //左移运算符<<
    ofs << "性别:难"<<endl;
    ofs << "年龄:18"<<endl;

    //5, 关闭文件
    ofs.close();
}

int main()
{
    test01(); //默认在当前目录下创建.txt文件

    return 0;
}

默认在同一目录下,创建.txt文本文件

姓名: 张三
性别:难
年龄:18

🌳文本文件 -- 读文件

1,头文件
#include<fstream>
2,创建对象
ifstream ifs;
3,打开文件并判断是否成功
ifs.open("路径", 打开方式);
4,读数据

四种方式
5,关闭文件
ifs.close();

#include <iostream>
#include<fstream> //头文件
using namespace std;

//文本文件  读文件
void test01()
{
    //1,头文件
    //2,创建对象
    ifstream ifs; //通过ifstream这个类创建了ifs对象
    //3,打开文件  判断成功
    ifs.open("text.txt", ios::in); //in表示读

    if(!ifs.is_open()) {
        cout<<"文件打开失败"<<endl; //路径下没有这个文件
        return; //不需要读数据了
    }

    //4,读数据

//    //第一种
//    char buf[1024] = {0};
//    while(ifs >> buf) { //把文件中数据都放在buf字符数组中
//        cout<<buf<<endl;
//    }

//    //第2种
//    char buf[1024] = {0}; //初始化字符数组
//    while(ifs.getline(buf, sizeof(buf))) { //getline获取一行, 第一个参数首地址, 第二个参数大小
//        cout<<buf<<endl;
//    }

//    //第3种
//    string buf;
//    while(getline(ifs, buf)) { //第1个参数是基础输入流, 第二个参数是字符串
//        cout<<buf<<endl;
//    }

    //第4种 -- 不推荐
    char c; //ifs.get()每次只读一个字符, EOF表示文件末尾, end of file
    while( (c = ifs.get()) != EOF) {
        cout<<c;
    }

    //5,关闭文件
    ifs.close();
}

int main()
{
    test01();

    return 0;
}
姓名: 张三
性别:难
年龄:18
 

🌳二进制文件 -- 写文件

首先,二进制文件

以二进制的方式对文件进行读写操作
打开方式要指定为ios::binary

二进制方式写文件,需要流对象调用成员函数write
函数原型

ostream& write(const char * buffer, int len);
//字符指针buffer指向内存中一段存储空间, len是读写的字节数

👇辨析

ostream& 并不是表示地址,而是表示文件输出流的类型。具体来说,ostream& 表示一个引用类型,代表一个指向输出流对象的引用。在 C++ 中,函数可以返回引用类型,这样可以通过函数返回值直接操作函数内部的对象 

write() 函数中,ostream& 表示一个输出流的引用类型。函数的返回值类型是 ostream&,也就是返回一个输出流对象的引用。这里返回的是输出流对象本身(即调用该函数的对象 ofs),这样可以支持链式操作。

具体来说,如果 ofs.write() 函数成功写入了数据,那么它会返回一个指向 ofs 对象的引用,你可以在一个语句中连续调用多个输出操作,例如:

ofs.write((const char *)&p, sizeof(Person)).write((const char *)&q, sizeof(Person));

这段代码将 pq 两个 Person 对象依次写入文件,并且实现了链式操作

👆关于&ostream的解释来自gpt

代码

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

//二进制文件  写
class Person
{
public:

    char m_Name[64]; //文件中 写字符串用char 不要用C++的string 底层是C写的
    int m_Age;
};

void test01()
{
    //1,头文件
    //2,创建流对象
    ofstream ofs("person.txt", ios::out | ios::binary); //写文件

    //3,打开文件
    //ofs.open("person.txt", ios::out | ios::binary); //二进制方式写文件

    //4,写文件
    Person p = {"张三", 18};
    //const char *将数据地址强转类型, &得到地址, 二进制写文件的语法
    ofs.write( (const char *)&p, sizeof(Person));
    //5,关闭文件
    ofs.close();
}

int main()
{
    test01();

    return 0;
}

解释

1,关于第26行

ofs.write( (const char *)&p, sizeof(Person));

这段代码是一个将 Person 对象写入文件的操作。具体而言,代码中 ofs 是一个输出文件流对象,p 是一个 Person 类型的对象。

代码中的 (const char *)&p 表示将 p 对象强制转换成 char 类型的指针,并且使用 & 运算符获取其地址。这也就是说,此处将 Person 类型的数据块转换成了字符指针类型的数据块。

sizeof(Person) 表示 Person 类型占用的字节数,这个数值在编译时就已经确定了。

最后,整个操作使用 ostream::write 函数将数据块写入输出文件流中。由于数据块已经被转换为字符指针类型,所以此处会按照字节数组的方式直接将数据写入文件

2,关于第18行

ofstream ofs("person.txt", ios::out | ios::binary); //写文件

ofs 是一个对象名,而不是指针。

具体来说,ofs 是一个 ofstream 类型的对象,代表一个输出文件流。这个对象是通过调用 ofstream 类的构造函数创建的,在构造函数中指定输出文件的名称和打开文件的模式(这里设置为输出和二进制模式)。

因此,ofs 并不是指针类型,而是一个实际的对象,可以直接使用其成员函数进行文件的读写操作。在你的代码中,使用 ofs.write() 函数将 Person 对象写入文件并保存数据

总结
文件输出流对象,可以通过write函数,以二进制方式写数据

二进制写入的文件,可能出现乱码,但是不影响后续的读入

🌳二进制文件 -- 读文件

二进制读文件:
通过流对象调用成员函数read
函数原型

istream& read(char *buffer, int len);
//字符指针buffer指向内存中一段存储空间, len是读写的字节数

👆从文件中读取二进制数据并存储到一个类对象 Person👇

  • (char *)&p 表示将 p 对象的地址强制转换为 char * 类型,作为指向缓冲区的指针传递给 read() 函数。由于 read() 函数的第一个参数是一个 char * 指针,所以我们需要对 p 对象的地址进行类型转换。
  • sizeof(Person) 表示要读取的数据的大小,即 Person 类型的大小。这里将读取一个 Person 对象的数据

代码

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

//二进制文件 -- 读文件
class Person
{
public:

    char m_Name[64]; //姓名
    int m_Age; //年龄
};

void test01()
{
    //1,头文件
    //2,创建对象
    ifstream ifs; //i in input输入, 所以是读, in入程序

    //3,打开文件  判断打开成功
    ifs.open("person.txt", ios::in | ios::binary);
    if(!ifs.is_open()) {
        cout<<"文件打开失败"<<endl;
        return; //失败就不读了
    }

    //4,读文件
    Person p;
    ifs.read( (char *)&p, sizeof(Person));
    cout<<"姓名: "<<p.m_Name<<"  年龄: "<<p.m_Age<<endl;

    //5,关闭文件
    ifs.close();

}

int main()
{
    test01();

    return 0;
}

根据上一个代码二进制写文件的操作,会得到如下输出

姓名: 张三  年龄: 18
 

总结

通过read函数,以二进制方式读数据

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/634921.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

区块链世界的大数据入门之zkMapReduce简介

1. 引言 跨链互操作性的未来将围绕多链dapp之间的动态和数据丰富的关系构建。Lagrange Labs 正在构建粘合剂&#xff0c;以帮助安全地扩展基于零知识证明的互操作性。 2. ZK大数据栈 Lagrange Labs 的ZK大数据栈 为一种专有的证明结构&#xff0c;用于在任意动态分布式计算的…

Nginx本地启动前端Vue项目

1.Nginx下载及安装 下载地址&#xff1a;nginx: download 下载stable稳定版本&#xff0c;解压安装包 2.前端项目打包 使用cnpm/npm run serve 或 yarn serve将前端项目打包&#xff0c;记录dist文件夹路径 3.nginx.conf 填写 根目录下conf文件夹有nginx.conf文件&#xf…

Linux——Centos系统中网络连接的三种方式(如何修改VMware的IP地址?)

一、CentOS系统中&#xff0c;三种常见的网络连接模式 我们在前面搭建Linux环境中使用了虚拟机的方式进行&#xff08;后续也会发文演示用Docker来搭建Linux环境&#xff09;。在虚拟机中选择安装CentOS系统&#xff0c;然后当时在安装流程的时候选择了NAT模式&#xff0c;但是…

MySQL基础知识:索引

一、索引基础知识 &#xff08;一&#xff09;辅助索引/二级索引 叶子节点除了包含键值以外&#xff0c;每个叶子节点中的索引行中还包含了一个书签( bookmark) &#xff08;每个索引一颗B树&#xff0c;不包含行记录的全部数据&#xff09; &#xff08;二&#xff09; 回表…

第一章 数据处理篇:数据集读取和构建

说在前面的话 满打满算工作也有三年了&#xff0c;还是没有感觉到自己和刚毕业相比有什么明显的进步。 严格来讲&#xff0c;代码力确实有提升&#xff0c;对各类工具的使用也更加熟练。但是对算法的理解和从0开始编程的能力仍然没有什么长进。归根到底&#xff0c;是因为在工作…

算法学习day18

文章目录 513.找树左下角的值递归迭代 112 .路径总和递归迭代 113.路径总和II递归 106.从中序与后序遍历序列构造二叉树递归 105.从前序与中序遍历序列构造二叉树卡尔递归版本递归优化 总结 513.找树左下角的值 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底…

PPT里文字太多如何排版-一口气教你7种布局瞬间让PPT高大上起来

简介 这是我们学PPT处于初级到中级进化阶段常做的一件事,就是拿了这种纯文字类版面来做布局。而且这种文字都是政企类的、相当苦涩难懂、无法创意。 因此我们会要求使用7种排版来优化这个版面。这和达芳奇画鸡蛋很像,这样的练习需要坚持一段时间,就是拿了纯文字来beautifu…

【Flutter】如何给按钮添加圆角 自定义圆角按钮样式

文章目录 一、前言二、创建基本按钮三、如何在 Flutter 中创建圆角按钮四、自定义圆角按钮1.修改按钮颜色2.修改圆角半径 五、完整代码示例六、总结 一、前言 欢迎来到 Flutter 的世界&#xff01;在这篇文章中&#xff0c;我们将探索 Flutter 的一些基础知识。但是&#xff0…

【IC设计】Synopsys数字IC设计流程

文章目录 数字IC设计流程前端设计RTL编写和HDL仿真逻辑综合门级仿真形式化验证 后端设计数据准备set mw_phys_refs *set link_library *数据准备 (SDC)数据准备 (RC Techfile) set_tlu_plus_files floor planFloorplan阶段的主要内容&#xff1a;常用命令&#xff1a; placemen…

BiFPN,one-stage,two-stage

目录 BiFPN 语义信息 单stage物体检测算法 双stage skip connection,low-level feature,high level-feature,top-dowm path backbone通常分为几个stage BiFPN BiFPN是双向特征金字塔网络的缩写&#xff0c;是计算机视觉中用于目标检测和分割任务的一种神经网络架构。 …

碳排放预测模型 | Python实现基于传统Holt winter时间序列的碳排放预测模型(预测未来发展趋势)

文章目录 效果一览文章概述研究内容环境准备源码设计学习总结参考资料效果一览 文章概述 碳排放预测模型 | Python实现基于传统Holt winter时间序列的碳排放预测模型(预测未来发展趋势) 研究内容 这是数据集的链接:https://github.com/owid/co2-data/blob/master/owid-co2-d…

一个好的接口自动化测试脚本是怎么写出来的?

目录 前言 1、某个用例的测试目的是什么 2、接口信息的来源 3、一些基本原则 4、断言那些事 5、脚本的后期维护 6、关于测试数据的准备 总结&#xff1a; 前言 谈到接口测试&#xff0c;大家关注更多的是哪个工具更优秀&#xff0c;更好用。但是很少人关注到接口测试用…

Allegor17.2版本WIN11系统CIS配置提示错误解决方案

错误提示&#xff1a; ERROR(ORCIS-6250): Unable to continue. Database access failed. Contact the database administrator to correct the following error(s), and then retry. ODBC Error Code: -1 Description: 在指定的 DSN 中&#xff0c;驱动程序和应用程序之间的体…

架构-系统架构设计模块-2

软件架构风格 #mermaid-svg-daJWV8kQ9nIgH5tZ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-daJWV8kQ9nIgH5tZ .error-icon{fill:#552222;}#mermaid-svg-daJWV8kQ9nIgH5tZ .error-text{fill:#552222;stroke:#55222…

2023年大学计算机专业实习心得14篇

2023年大学计算机专业实习心得精选篇1 20__年已然向我们挥手告别而去了。在20__年初之际&#xff0c;让我们对过去一年的工作做个总结。忙碌的一年里&#xff0c;在领导及各位同事的帮助下&#xff0c;我顺利的完成了20__年的工作。为了今后更好的工作&#xff0c;总结经验&…

【大数据学习篇13】在linux上安装jupyter

下面介绍在liunx怎么安装jupyter&#xff0c;一步到位介绍。 目录 下面介绍在liunx怎么安装jupyter&#xff0c;一步到位介绍。 1、安装Anaconda3​编辑 1.1 自己选择一个位置下载 1.3 配置anaconda的路径 1.3 查看anaconda的版本 2、配置Jupyter Notebook 3、运行Jupy…

Linux UPS配置详解 (山特SANTAK TGBOX-850 )

起因 配置了一台All in One主机&#xff0c;系统是装的PVE&#xff0c;一个linux的虚拟机。里面装了openwrt软路由&#xff0c;还有OMV这个NAS系统。为了防止数据丢失&#xff0c;最好是配置一台UPS来保护数据&#xff0c;毕竟数据无价。于是买了一台山特的TGBOX-850。由于山特…

【群智能算法改进】基于动态折射反向学习和自适应权重的改进麻雀搜索算法[4]【Matlab代码#39】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. SSA算法2. 改进SSA算法2.1 动态折射反向学习策略2.2 自适应权重策略 3. 部分代码展示4. 仿真结果展示5. 资源获取说明 【获取资源请见文章第5节&#xff1a;资源获取】 1. SSA算法 2. 改进SSA算法 2.1 动态折射反向…

局域网内不同网段的设备互相连接设置

目录 介绍1、打开网络连接&#xff0c;找到本地网络->属性->ipv4->属性->高级&#xff1a;2、在高级设置页面&#xff0c;我们添加一个IP&#xff0c;这个IP和板子在一个网段&#xff0c;我这里设置的是192.168.253.101&#xff1a;3、设置完成即可生效&#xff0c…

从0打1 用node express vite搭建一个博客系统系列(完结)

项目使用了Node后端、Express和Vite搭建的全栈博客系统系列&#xff0c;将Vue 2项目重构为Vue 3版本。该系统包含了以下主要模块和功能&#xff1a; 登录和注册功能&#xff1a;用户可以通过注册账号和登录来访问博客系统。 分类列表&#xff1a;展示不同分类的文章&#xff…