C++笔记(三)类和对象2

C++笔记(三)类和对象2

C++运算符重载
  • 重新给那些已经有定义的运算符,设置新的定义
  • 而原来运算符的定义依然不会修改,因此,就是重载
加号运算符重载
  • 实现两个自定义数据类型相加的运算
  • 运算符重载可以发生函数重载,就是函数名称相同,参数不同
  • 分别有两种方式:成员函数重载和全局函数重载
//假设我们要将两个Person类的数据相加
class Person{
  public:
    int m_a;
    int m_b;
    //成员函数的运算符重载
    Person operator+(Person &p){   //运算符+重载
        Person temp;         //这个函数的名字就是operator+
        temp.m_a = this->m_a + p.m_a;
        temp.m_b = this->m_b + p.m_b;
        return temp;
    }
};
//全局函数的运算符重载
Person operator+(Person &p1, Person &p2){
    Person temp;
    temp.m_a = p1.m_a + p2.m_a;
    temp.m_b = p1.m_b + p2.m_b;
    return temp;
}
int main(){
    Person p1;
    p1.m_a = 20;
    p1.m_b = 20;
    Person p2;
    p2.m_a = 10;
    p2.m_b = 10;
    Person p3 = p1 + p2;    //因为重载了+运算符,所以这里可以运行
    //其实,这里的计算时编译器其实改写了如下方式:
    //对于成员函数的运算符重载:
    //Person p3 = p1.operator+(p2);简化为p3=p1+p2
    //对于全局函数的运算符重载:
    //Person p3 = operator+(p1,p2);简化为p3=p1+p2
    cout << p3.m_a << p3.m_b <, endl;  //输出都是30
}

image-20220314143339482

左移运算符重载
  • 左移符号是<<,就是cout << 中的运算符
  • 只能使用全局函数做重载,不能使用成员函数重载,因为会反了:平常都是cout << p;如果使用成员函数重载,那么就变成p<<cout;反了
  • 左移运算符重载,可以输出自定义数据类型
//假设我们要cout << p就能够将类中成员打印出来
class Person{
  public:
    Person(int a, int b){
        this->m_a = a;
        this->m_b = b;
    }
    int m_a;
    int m_b;
    //如果这么写,那么调用的时候,就是p.operator<<(cout)
    //那么形成的就是p<<cout,就反了,因此只能使用全局函数的重载
    //void operator<<(Person &p){};
};
//如果这么写,不能做成链式编程
//void operator<<(ostream &out, Person &p){
//返回cout本身,就能形成链式编程
ostream & operator<<(ostream &out, Person &p){
    //如果类中的成员是私有的,那么我们可以将此函数设定为友元函数
    out << p.m_a << p.m_b;
    return out;
}
void test(){
    Person p(10,10);
    //如果运算符重载函数不返回值,那么只能这么写,不能连续的写
    cout << p;
    //如果运算符重载函数返回了cout本身,那么就可以链式的编程
    cout << p << endl;
}
递增运算符重载
  • 通过重载递增运算符,实现自己的整型数据++
//假设打印类中的整型数据递增
class MyInteger {
	friend ostream& operator<<(ostream& out, MyInteger myint);
public:
	MyInteger() {
		m_Num = 0;
	}
	MyInteger& operator++() {    //前置++
		m_Num++;
		return *this;
	}
	MyInteger operator++(int) {  //后置++,添加一个占位参数
		//先返回
		MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
		m_Num++;
		return temp;
	}
private:
	int m_Num;
};
ostream& operator<<(ostream& out, MyInteger myint) {
	out << myint.m_Num;   //重载<<运算符
	return out;
}
void test01() {        //前置++ 先++ 再返回
	MyInteger myInt;
	cout << ++myInt << endl;
	cout << myInt << endl;
}
void test02() {        //后置++ 先返回 再++
	MyInteger myInt;
	cout << myInt++ << endl;
	cout << myInt << endl;
}
赋值运算符重载
  • C++编译器至少会给一个类添加下面4个函数:
    • 默认构造函数(无参,函数体为空)
    • 默认析构函数(无参,函数体为空)
    • 默认拷贝构造函数,对成员变量进行值拷贝
    • 赋值运算符operator=,对成员变量进行值拷贝(因此,也会出现浅拷贝的问题)
//假设我们要实现一个对类的赋值操作
class Person{
public:
	Person(int age){
		m_Age = new int(age);
	} 
	Person& operator=(Person &p){   //重载赋值运算符
		if (m_Age != NULL){
			delete m_Age;
			m_Age = NULL;
		}
		//编译器提供的代码是浅拷贝
		//m_Age = p.m_Age;
		//提供深拷贝 解决浅拷贝的问题
		m_Age = new int(*p.m_Age);
		return *this;                 //返回自身,实现链式编程
	}
	~Person(){
		if (m_Age != NULL){
			delete m_Age;
			m_Age = NULL;
		}
	}
	int *m_Age;
};
void test01(){
	Person p1(18);
	Person p2(20);
	Person p3(30);
	p3 = p2 = p1; //赋值操作
	cout << "p1的年龄为:" << *p1.m_Age << endl; //10
	cout << "p2的年龄为:" << *p2.m_Age << endl; //10
	cout << "p3的年龄为:" << *p3.m_Age << endl;  //10
}
关系运算符重载
  • 就是重载==,!=这种运算符
//假设我们对比两个类中成员是否相等
class Person{
public:
	Person(string name, int age){
		this->m_Name = name;
		this->m_Age = age;
	};
	bool operator==(Person & p){   //重载==运算符
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age){
			return true;
		}
		else{
			return false;
		}
	}
	string m_Name;
	int m_Age;
};
void test01(){
	Person a("孙悟空", 18);
	Person b("孙悟空", 18);
	if (a == b){            //重载!=运算符也是相同的写法
		cout << "a和b相等" << endl;
	}
}
函数调用运算符重载
  • 函数调用运算符(),也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
class MyPrint{
public:
	void operator()(string text){    //重载()运算符
		cout << text << endl;
	}
};
void test01(){
	//重载的()操作符 也称为仿函数
	MyPrint myFunc;
	myFunc("hello world");   //会直接打印hello world
}
//重载()运算符非常灵活,可以如下这么写:
class MyAdd{
public:
	int operator()(int v1, int v2){  //可以设置自己想要的功能
		return v1 + v2;
	}
};
void test02(){
	MyAdd add;
	int ret = add(10, 10);
	cout << "ret = " << ret << endl;
	//匿名对象调用  其中下面的MyAdd()就是匿名对象,没有名字的对象
    //执行完这行代码,匿名对象就释放了
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
继承
基本语法
  • 语法:class 子类 : 继承方式 父类
    • 子类也叫派生类,父类也叫基类
  • 继承的好处:减少重复代码
class BasePage{
public:
	void header(){
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}
	void footer(){
		cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
	}
	void left(){
		cout << "Java,Python,C++...(公共分类列表)" << endl;
	}
};
class Java : public BasePage{   //继承BasePage类
public:
	void content(){
		cout << "JAVA学科视频" << endl;
	}                 
    //就相当于把基类的成员复制粘贴到这里了,所以使用的时候,也是跟正常调用没有区别
};
继承方式

image-20220314191720206

  • 继承方式:
    • 公有继承:
      • 父类的公共成员->子类的公共成员
      • 父类的保护成员->子类的保护成员
      • 父类的私有成员x>子类,不可访问
    • 保护继承:
      • 父类的公共成员->子类的保护成员
      • 父类的保护成员->子类的保护成员
      • 父类的私有成员x>子类,不可访问
    • 私有继承:
      • 父类的公共成员->子类的私有成员
      • 父类的保护成员->子类的私有成员
      • 父类的私有成员x>子类,不可访问
继承中的对象模型
  • 从父类继承的成员,都有哪些属于子类中?
  • 答:都在子类中,而不可访问的部分成员,其实是编译器给隐藏了
class Base{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};
class Son :public Base{     //公共继承
public:
	int m_D;
};
void test01(){
	cout << "sizeof Son = " << sizeof(Son) << endl;
    //得到16,就是所有成员都被继承了,包括私有成员
}
构造和析构
  • 子类继承父类后,那么构造函数和析构函数的顺序是什么?
  • 答:构造函数:先执行父类的,再执行子类的;析构函数:先执行子类的,再执行父类的
class Base{
public:
	Base(){
		cout << "Base构造函数!" << endl;
	}
	~Base(){
		cout << "Base析构函数!" << endl;
	}
};
class Son : public Base{
public:
	Son(){
		cout << "Son构造函数!" << endl;
	}
	~Son(){
		cout << "Son析构函数!" << endl;
	}
};
void test01(){
	//继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
	Son s;        
    //尽管只创建了子类的对象,但是子类是继承于父类的,
    //所以父类也一定会创建新的对象
}
继承同名成员的处理
  • 就是当子类和父类出现了同名的成员时,需要怎么访问子类或者父类的同名数据
    • 访问子类同名成员,直接访问即可
    • 访问父类同名成员,需要加作用域::
class Base{
public:
	Base(){
		m_A = 100;     
	}
	void func(){      //建立同名的成员函数
		cout << "Base - func()调用" << endl;
	}
	void func(int a){   //建立同名的成员重载函数
		cout << "Base - func(int a)调用" << endl;
	}
public:
	int m_A;        //建立同名的成员变量
};
class Son : public Base {
public:
	Son(){
		m_A = 200;    //与父类同名
	}
	//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数,因此这时候父类中有同名的重载函数,也是不生效的
	//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
	void func(){     //与父类同名
		cout << "Son - func()调用" << endl;
	}
public:
	int m_A;
};
void test01(){
	Son s;
    //直接访问,就是访问的子类成员
	cout << "Son下的m_A = " << s.m_A << endl;
    //加作用域::,就是访问对应作用域的成员
	cout << "Base下的m_A = " << s.Base::m_A << endl;
	s.func();
	s.Base::func();
	s.Base::func(10)}
继承同名静态成员的处理
  • 大体上,跟非静态成员的处理相同
class Base {
public:
	static void func(){
		cout << "Base - static void func()" << endl;
	}
	static void func(int a){
		cout << "Base - static void func(int a)" << endl;
	}
	static int m_A;
};
int Base::m_A = 100;
class Son : public Base {
public:
	static void func(){
		cout << "Son - static void func()" << endl;
	}
	static int m_A;
};
int Son::m_A = 200;
void test01(){
	//通过对象访问,跟非静态成员一样
	cout << "通过对象访问: " << endl;
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;
	//通过类名访问,因为静态成员是共享的,可以直接用类访问
	cout << "通过类名访问: " << endl;
	cout << "Son  下 m_A = " << Son::m_A << endl;
    //这个特殊,需要注意
	cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
void test02(){
	//通过对象访问,跟非静态成员一样
	cout << "通过对象访问: " << endl;
	Son s;
	s.func();
	s.Base::func();
    
	cout << "通过类名访问: " << endl;
    //出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
    //就是说,必须要使用Base的作用域,否则,子类的func会隐藏父类的所有func函数(包括重载)
    //第一个作用域::符号指的是通过类名访问,第二个::符号指的是找父类中的func
	Son::func();
	Son::Base::func();
	//想找到父类中的func重载函数,需要要使用Base指定
	Son::Base::func(100);
}
多继承
  • 就是一个类,继承了多个父类
  • 多继承可能会引发父类中同名成员的出现,需要加作用域区分
  • C++实际开发中不建议使用多继承
//假设写好了两个父类
class Son : public Base2, public Base1 {
public:
	int m_C;
	int m_D;
};
//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01(){
	Son s;
    //这个sizeof就是计算了所有父类和子类中的非静态 成员 变量的占用
	cout << "sizeof Son = " << sizeof(s) << endl;
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}
菱形继承(面试)
  • 两个子类继承同一个夫类,又有某个类同时继承两个子类,这种继承被称为菱形继承,或者钻石继承
  • 菱形继承的问题:
    • 数据的二义性,中间层的两个类,都继承了同一个类,就会都有同一个成员,在最底层的子类继承这两个中间层的类时,出现数据的二义性,就是分不清是哪个类的成员,可以使用作用域::区分
    • 这份同名的数据,最底层的子类继承了两个同名的成员,但是实际上我们只需要一份,造成了资源浪费,可以使用虚继承
class Animal{
public:
	int m_Age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
//虚继承的底层思想:同名成员不再继承原来的数据,而是变为了一个指针,
//他们都指向了同一片区域,因此,虚继承就形成了只有一份数据
class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};
void test01(){
    //这是第一个问题,使用作用域就可以访问
	SheepTuo st;
	st.Sheep::m_Age = 100;
	st.Tuo::m_Age = 200;
	//如果使用了虚继承,那么此时底下输出的都是200
    //因为使用虚继承后,实际上你访问的不管那个作用域,都是只有一份数据
	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;
}
多态
基本概念
  • 多态分为两类

    • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
    • 动态多态: 派生类和虚函数实现运行时多态
  • 静态多态和动态多态区别:

    • 静态多态的函数地址早绑定 - 编译阶段确定函数地址

    • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

  • 多态满足条件:

    • 有继承关系
    • 子类重写父类中的虚函数
  • 多态的使用:父类指针或者引用指向子类对象

class Animal{
public:
	//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
	virtual void speak(){
		cout << "动物在说话" << endl;
	}
};
class Cat :public Animal{
public:
	void speak(){        //子类重写虚函数,加不加virtual都行
		cout << "小猫在说话" << endl;
	}
};
class Dog :public Animal{
public:
	void speak(){             //子类重写虚函数,加不加virtual都行
		cout << "小狗在说话" << endl;
	}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void DoSpeak(Animal & animal){  //这个函数的形参是父类对象
	animal.speak();        //但是实际上传入的实参是子类对象
}                   //满足多态的要求
//多态满足条件: 
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
void test01(){
	Cat cat;
	DoSpeak(cat);     //想要调用cat对象的函数,就传入cat
	Dog dog;
	DoSpeak(dog);        //想要调用dog对象的函数,就传入dog
}
  • 多态的底层理解(以上面的代码为例)
  1. 对于animal父类,如果没有加virtual,也就是说,没有虚函数只有普通成员函数,那么sizeof后就占用1字节,而使用了虚函数之后,就占用了4字节,因为虚函数的存在,使得父类产生了一个指针,指向了&animal::speak(就是animal作用域下的speak函数的地址)
  2. 对于cat子类,如果没有重写父类中的虚函数(就是speak函数),那么就是一个普通的继承,会把指向了&animal::speak(就是animal作用域下的speak函数的地址)继承过来。而重写了父类中的虚函数(就是speak函数),那么继承过来的指针,就会指向&cat::speak(就是cat作用域下的speak函数的地址)
  3. 因此,形成了父类的指针或者引用指向了哪个子类的对象,那么程序就会运行哪个子类的对象,形成了多态。

image-20220314205942163

多态的优点
  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护
//多态实现计算器
class AbstractCalculator{   //抽象计算器类
public :
	virtual int getResult(){ 
        //可以不写里面的东西,毕竟主要运行的都是后面要开发的部分
		return 0;
	}
	int m_Num1;
	int 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;
	}
};
void test(){
	//创建加法计算器
    //多态的条件:需要有父类的指针或者引用指向子类对象
	AbstractCalculator *abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  //用完了记得销毁
	//创建减法计算器
	abc = new SubCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  
}
纯虚函数和抽象类
  • 在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
  • 纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
  • 当类中只要有了纯虚函数,这个类就称为抽象类(可以有很多纯虚函数)
  • 抽象类的特点:
    • 抽象类无法实例化对象,就是说不能创建对象
    • 子类必须重写抽象类中的纯虚函数,否则该子类也属于抽象类
class Base{
public:
	//纯虚函数
	//类中只要有一个纯虚函数就称为抽象类
	//抽象类无法实例化对象
	//子类必须重写父类中的纯虚函数,否则也属于抽象类
	virtual void func() = 0;
    //其实这里就是接口了,就是我准备要写这个功能了,先占个坑
};
class Son :public Base{
public:
	virtual void func() {   //子类的virtual写不写都行
		cout << "func调用" << endl;
	};
};
void test01(){
	Base * base = NULL;
	//base = new Base;     // 错误,抽象类无法实例化对象
	base = new Son;       //父类的指针必须指向子类的对象
	base->func();        //形成多态
	delete base;//记得销毁
}
虚析构和纯虚析构
  • 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,可以将父类的析构函数改为虚析构或者纯虚析构

  • 虚析构的语法:virtual ~类名(){}

  • 纯虚析构的语法:声明:virtual ~类名() = 0;;实现:类名::~类名(){}

  • 虚析构和纯虚析构的特点:

    • 可以解决父类指针释放子类对象
    • 都需要有具体的函数实现
    • 如果是纯虚析构,该类属于抽象类,无法实例化对象
class Animal {
public:
	Animal(){
		cout << "Animal 构造函数调用!" << endl;
	}
	virtual void Speak() = 0;
	//析构函数加上virtual关键字,变成虚析构函数
	//virtual ~Animal()
	//{
	//	cout << "Animal虚析构函数调用!" << endl;
	//}
    //虚析构和纯虚析构只能二选一
    //下面是纯虚析构,这只是声明,还需要有实现函数
	virtual ~Animal() = 0;
};
//纯虚析构的实现函数
Animal::~Animal(){
	cout << "Animal 纯虚析构函数调用!" << endl;
}
//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。
class Cat : public Animal {
public:
	Cat(string name){
		cout << "Cat构造函数调用!" << endl;
		m_Name = new string(name);    //把数据建立在堆区
	}
	virtual void Speak(){
		cout << *m_Name <<  "小猫在说话!" << endl;
	}
	~Cat(){
        //如果只是使用普通的析构函数,这里是运行不到的
		cout << "Cat析构函数调用!" << endl;
		if (this->m_Name != NULL) {
			delete m_Name;
			m_Name = NULL;
		}
	}
public:
	string *m_Name;
};
void test01(){
	Animal *animal = new Cat("Tom");
	animal->Speak();
	//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
	//怎么解决?给基类增加一个虚析构函数
	//虚析构函数就是用来解决通过父类指针释放子类对象
	delete animal;
}
文件操作
  • 对文件操作需要包含的头文件====
  • 文件类型:
    • 文本文件:以文本的ASCII码形式储存在计算机中
    • 二进制文件:以文本的二进制形式储存在计算机中,一般用户看不懂
  • 操作文件三大类:
    • ofstream:写操作
    • ifstream:读操作
    • fstream:读写操作
文本文件
  • 写文件步骤:
    • 包含头文件:#include<fstream>
    • 创建流对象:ofstream ofs;
    • 打开文件:ofs.open("文件路径",打开方式);
    • 写数据:ofs << "写入的数据";
    • 关闭文件:ofs.close();
  • 文件打开方式:
    • 可以多个方式一起使用:如ios::binary | ios::out
打开方式解释
ios::in为读文件而打开文件
ios::out为写文件而打开文件
ios::ate打开文件后,直接指向文件尾
ios::app在文件最后追加的方式写文件
ios::trunc如果文件存在则先删除,再创建
ios::binary二进制方式
#include <fstream>    //添加头文件
void test01(){
	ofstream ofs;   //创建流对象
	ofs.open("test.txt", ios::out);  //打开文件
	ofs << "姓名:张三" << endl;     //写数据
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;
	ofs.close();              //关闭文件
}
  • 读文件步骤:
    • 包含头文件:#include<fstream>
    • 创建流对象:ifstream ifs;
    • 打开文件:ifs.open("文件路径",打开方式);
    • 读数据:四种方式
    • 关闭文件:ifs.close();
#include <fstream>              //包含头文件
#include <string>
void test01(){
	ifstream ifs;         //创建流对象
	ifs.open("test.txt", ios::in);  //打开文件
    //判断是否打开文件,is_open()判断文件是否打开,返回bool类型
	if (!ifs.is_open()){     
		cout << "文件打开失败" << endl;
		return;
	}
	//第一种方式
	//char buf[1024] = { 0 };
	//while (ifs >> buf){
	//	cout << buf << endl;
	//}
	//第二种
	//char buf[1024] = { 0 };    这是char类型
    //getline()获取一行数据
    //ifs.getline(读取数据存放处,多大)
	//while (ifs.getline(buf,sizeof(buf))){
	//	cout << buf << endl;
	//}
	//第三种
	//string buf;           这是string类型
	//while (getline(ifs, buf)){
	//	cout << buf << endl;
	//}
	char c;
    //ifs.get()一个字符一个字符获取文件数据
    //EOF指的是文件末尾,end of file
	while ((c = ifs.get()) != EOF){
		cout << c;
	}
	ifs.close();                  //关闭文件
}
二进制文件
  • 写文件主要使用流对象成员函数write
    • 函数原型 :ostream& write(const char * buffer,int len);
    • 参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
//假设我们将一个类数据放入二进制文件
#include <fstream>
#include <string>
class Person{
public:
	char m_Name[64];
	int m_Age;
};
//二进制文件  写文件
void test01(){
	//创建输出流对象
    //可以像如下这种,创建流对象时直接打开文件
    //也可以一步一步来,像文本文件那样
	ofstream ofs("person.txt", ios::out | ios::binary);
	//打开文件
	//ofs.open("person.txt", ios::out | ios::binary);
	Person p = {"张三"  , 18};
	//写文件
	ofs.write((const char *)&p, sizeof(p));
	//关闭文件
	ofs.close();
}
  • 读文件主要使用流对象成员函数read
    • 函数原型:istream& read(char *buffer,int len);
    • 参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
//假设我们按照类的结构读取二进制文件
#include <fstream>
#include <string>
class Person{
public:
	char m_Name[64];
	int m_Age;
};
void test01(){
    //创建流对象并打开
	ifstream ifs("person.txt", ios::in | ios::binary);
	if (!ifs.is_open()){
		cout << "文件打开失败" << endl;
	}
	Person p;
    //按照类的结构读取数据
	ifs.read((char *)&p, sizeof(p));  
	cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值