面向对象

1 内存分区模型

  • 代码区 : 由操作系统管理,存放函数体的二进制代码
  • 全局区 : 存放全局变量和静态变量、常量
  • 栈区 : 由编译器自动分配释放,存放函数的参数值、局部变量等
  • 堆区 : 由程序员分配释放,若程序员不释放,程序结束时由os回收 new

意义:不同区域存放的数据,赋予不同的生命周期,给予更大的灵活性

程序运行前,对于编译后生成的exe文件,内存中分为两部分:
代码区: 存放CPU执行的机器指令(二进制)、共享的、只读的;
全局区:存放全局变量和静态变量static、常量区(字符串常量、const修饰的全局变量),该区域的数据在程序结束后由os释放。

const修饰的 全局变量在全局区,局部变量不在。

程序运行后:
栈:由编译器自动分配释放,存放函数的参数值、局部变量等, 注意不要返回局部变量的地址。
堆:利用new在堆区开辟内存 int * p = new int(10); //new创建的数据会返回该数据对应类型的指针。
指针也是变量,他放在栈上,但他存放的数据 放在堆上。
记得要释放,delete p;

  1. int * p = new int(10);
  2. delete p;
  3. //数组,堆
  4. int * arr = new int[10];
  5. for(int i=0;i<10;i++){
  6. arr[i]=i+100;
  7. }
  8. delete[] p;

2 引用
给变量起别名 int &a=b; //a就是别名,b是原名
引用必须要初始化,一旦初始化后就不可更改了。
引用做函数参数:(引用传递)形参的改变也会影响实参

void swap(int &a,int &b){
  int temp=a;
    a=b;
    b=temp;
}

引用做函数返回值:不要返回局部变量的引用,函数的调用可以作为左值。

#include<iostream>
using namespace std;

int& test01(){
   static int a=10;
    return a;
}
int main(void){
    int &ref=test01();
    cout<<ref<<endl;
    cout<<ref<<endl;

    test01()=100;
    cout<<ref<<endl;

    system("pause");
    return 0;
}

引用本质是在C++内部实现一个指针常量。(由编译器做的)

 int &ref=a;
//会自动转换为 int* const ref=&a; 指针指向不可变,这也说明了为什么引用不可变。
*ref=20; //指向的值可以改变,解引用。

常量引用:修饰形参,防止误操作,防止形参改变实参。

void test(const int &val){
  val=100;
    cout<<val<<endl;
}
val=20;
test(val);    //val=20;
cout<<val<<endl;   //val=20;

3 函数高级
函数的默认参数 : 从一个位置有默认参数后,他之后的位置都要有默认参数。
函数声明有默认参数,函数实现就不能有,只能有一个有。

int func(int a=10,int b=10);
int func(int a,int b){
}

占位参数 放在形参列表上,调用时必须填补。还可以有默认参数。

int func(int a=10,int){
    cout<<"a"<<endl;
}
func(10,2);

函数重载 函数名可以相同,提高复用性。
要满足:同一个作用域下;函数名称相同;函数参数类型、或个数、或顺序不同。
引用作为重载条件时,注意

void fun(int &s){
    cout<<"int &a"<<endl;
}
void fun(const int &s){
    cout<<"const int &a"<<endl;
}

int main(void){
    int a=10;
    fun(a);  //int &a;  int &a=10;不合法
    fun(10);  //const int &a  const创建一个临时的数据 temp=10;int &a=temp;合法
    system("pause");
    return 0;
}
    函数重载时碰到默认参数,  
void fun(int a,int b=10){
    cout<<"int a,int b=10"<<endl;
}
void fun(int a){
    cout<<"int a,int b=10"<<endl;
}

int main(void){
    int a=10;
    fun(a); //导致二义性,编译不成功
    system("pause");
    return 0;
}

4 类与对象
三大特性:封装、继承、多态。
C++认为万事万物皆为对象,对象上有其属性和行为。
具有相同性质的对象,抽象为类。

4.1 封装
意义: 将属性和行为作为一个整体,表现生活中的事物; 将属性和行为加以权限控制。
实例化一个对象,通过一个类创建对象。
类中的属性和行为都成为成员,属性——成员变量,成员属性; 行为——成员函数、成员方法。

#include<iostream>
using namespace std;

const double PI=3.14;
class Circle{
    //访问权限
    public:
        //属性——半径
        int m_r;
        //行为:计算周长
        double calZC(){
            return 2*PI*m_r;
        }
};

int main(void){
    //通过类创建对象
    Circle c1;
    c1.m_r=10;
    cout<<"周长"<<c1.calZC()<<endl;

    system("pause");
    return 0;
}
#include<iostream>
using namespace std;

class Student{
    public:
        string m_Name;
        int m_Id;
        void showStudent(){
            cout<<m_Name<<"\t"<<m_Id<<endl;
        }
        void setName(string name){
            m_Name=name;
        }
        void setId(int64_t id){
            m_Id=id;
        }
};

int main(void){
    Student s;
    s.setId(1);
    s.setName("Zhou");
    s.showStudent();

    system("pause");
    return 0;
}

访问权限: public protected private
stcuct 和 class : 区别在于访问权限不同,struct默认为public class默认为private
将成员属性设为私有,优点:可以自己控制读写权限;对于写权限,可以检测数据的有效性。
案例: 立方体类 Cube,求面积和体积,分别用全局函数 和成员函数 判断两个立方体是否相等。

#include<iostream>
using namespace std;

class Cube{
private:
    int m_H;
    int m_W;
    int m_L;
public:
    void setL(int l){ m_L=l;}
    int getL(){ return m_L;}
    void setW(int w){ m_W=w;}
    int getW(){ return m_W;}
    void setH(int h){ m_H=h;}
    int getH(){ return m_H;}
    int cal_S(){return m_L*m_W*2+m_L*m_H*2+m_H*m_W*2;}
    int cal_V(){return m_L*m_W*m_H;}
    bool isSameByClass(Cube c){
        if(c.getL()==m_L&&c.getW()==m_W&&c.getH()==m_H){
        return true;
        }else{
            return false;
        }
    }
};

bool isSame(Cube &c1,Cube &c2){
    if(c1.getL()==c2.getL()&&c1.getW()==c2.getW()&&c1.getH()==c2.getH()){
        return true;
    }else{
        return false;
    }
}
int main(void){
    Cube c1,c2;
    c1.setL(10);
    c1.setW(10);
    c1.setH(10);

    c2.setL(10);
    c2.setW(10);
    c2.setH(10);

    cout<<"V="<<c1.getL()*c1.getW()*c1.getH()<<"\t";
    cout<<"S="<<c1.getL()*c1.getW()*2+c1.getL()*c1.getH()*2+c1.getW()*c1.getH()*2<<endl;
    cout<<"V="<<c1.cal_V()<<"\tS="<<c1.cal_S()<<endl;
    if(isSame(c1,c2)){
        cout<<"相等_全局函数"<<endl;
    }else{
        cout<<"不相等_全局函数"<<endl;
    }
    if(c1.isSameByClass(c2)){
        cout<<"相等_成员函数"<<endl;
    }else{
        cout<<"不相等_成员函数"<<endl;
    }    

    system("pause");
    return 0;
}

案例:点和圆, Circle 和 Point 计算其关系

#include<iostream>
using namespace std;

class Point{
    private:
    int m_X;
    int m_Y;
    public:
    void setX(int x){m_X=x;}
    int getX(){return m_X;}
    void setY(int y){m_Y=y;}
    int getY(){return m_Y;}
};

class Circle{
    private:
    Point m_Center;
    int m_R;
    public:
    void setCenter(Point center){m_Center=center;}
    Point getCenter(){return m_Center;}
    void setR(int r){m_R=r;}
    int getR(){return m_R;}
};

void isInCircle(Circle &c,Point &p){
    int d1=(c.getCenter().getX()-p.getX())*(c.getCenter().getX()-p.getX())+
            (c.getCenter().getY()-p.getY())*(c.getCenter().getY()-p.getY());
    int d2=c.getR()*c.getR();
    if(d1==d2){
        cout<<"点在圆上"<<endl;
    }else if(d1<d2){
        cout<<"点在圆内"<<endl;
    }else{
        cout<<"点在圆外"<<endl;
    }
}

int main(void){ 
    Circle c;
    Point r;
    r.setX(10);
    r.setY(0);
    c.setCenter(r);
    c.setR(10);

    Point p;
    r.setX(10);
    r.setY(2);
    isInCircle(c,r);

    system("pause");
    return 0;
}

分开编写 ,.h .cpp文件。

4.2 对象的初始化和清理
构造函数(赋值)和析构函数(清理),由编译器自动调用。
构造函数 Student(){ } 自动调用,可以有参数,可以重载,只会调用一次。
析构函数 ~Student(){ } 自动调用,没有参数,不可以重载,只会调用一次。 先进后出。

构造函数
可分为:有参构造(默认构造)和无参构造; 普通构造和拷贝构造 拷贝一样的,不能更改。

//拷贝构造函数
Person( const Person & p){   
  age=p.age;
}

调用: 括号法、显示法、隐式转换法
使用默认构造函数时,不要加()。如person p() ; 这时编译器会认为他是一个函数声明。
不要用拷贝构造函数初始化匿名对象,编译器会认为这是一个对象的声明。

//括号法
Person p1;
Person p2(20);
Person p3(p3);
//显示法
Person p1;
Person p2=Person(10);
Person p3=Person(p2);
Person(10);  这是一个匿名对象,执行完后会立即释放
//隐式转换法
Person p4=10; // 相当于 Person p4=Person(10); 有参构造
Person p5=p4; //拷贝构造

拷贝构造函数使用时机:

  • 用已创建完毕的对象来初始化一个新对象;
  • 值传递给函数传参;
  • 以值返回局部对象

构造函数调用规则:

  • 默认情况下:C++自动添加至少 默认构造函数、默认析构函数、默认拷贝构造函数。
  • 如果写了有参构造函数,编译器就不再提供默认构造,但依然提供拷贝构造函数。如果拷贝构造函数,不在提供其他构造函数。

深拷贝和浅拷贝:

  • 浅拷贝:赋值拷贝操作——默认拷贝构造函数是浅拷贝 ,new
  • 深拷贝:在堆中重新申请空间

如果属性有在堆区拷贝的,就一定要自己实现拷贝构造函数。

class Person{
    public:
    Person(){
        cout<<"默认构造函数"<<endl;
    }
    Person(int age,int height){
        m_Age=age;
        m_height=new int(height);
        cout<<"有参构造函数"<<endl;
    }
    Person(const Person &p){
        m_Age=p.m_Age;
        // m_height=p.m_height;  //默认拷贝构造函数,浅拷贝
        m_height=new int(*p.m_height);   //深拷贝

        cout<<"拷贝构造函数"<<endl;
    }
    ~Person(){
        if(m_height!=NULL){
            delete m_height;
            m_height=NULL;
        }
        cout<<"析构函数"<<endl;
    }
    int m_Age;
    int *m_height;
};

初始化列表:用来初始化属性

class Person{
    public:
    //Person(int a,int b){m_A=a;m_B=b;}
    //Person():m_A(10),m_B(20){
    //}
    Person(int a,int b):m_A(a),m_B(b){
    }
    int m_A;
    int m_B;
};

类对象作为类成员: 类成员作为另一个类的对象,称该成员为对象成员。
子类的构造函数先执行,析构函数后执行。

静态成员: 变量或函数。
静态成员变量:所有对象共享同一份数据,在编译阶段分配内存,类内声明,类外初始化。也有访问权限
静态成员函数:所有对象共享同一个函数,只能访问静态成员变量,不可访问非…。静态成员函数有两种调用方式,通过对象访问,或 类名。
静态成员函数也有访问权限。

class Person{
    public:
    static void func(){
        cout<<"static void func"<<endl;
    }
};

    Person p;
    p.func();
    Person::func();

4.3 对象模型和this指针
成员变量和成员函数分开存储。

C++编辑器会给每个空对象分配一个字节空间,为了区分其占内存的位置。

  • 非静态成员变量属于类的对象上。
  • 静态成员变量不属于类的对象上。非静态成员函数 也不属于。静态成员函数 也不属于。

每个非静态成员函数 只有一份函数实例, 多个同类型的对象会共用一块代码。这块代码如何区分对象呢? 通过对象指针,即this指针,指向了被调用的成员函数所属的对象。
this指针无需定义,直接使用即可。本质是指针常量。
用途: 形参和成员变量同名时得以区分,解决名称冲突; 返回对象本身时,可用return *this。(链式编程思想)

//解决名称冲突
class Person{
    public:
    Person(int age){
        this->age=age;
    }
    int age;
};
#include<iostream>
#include<string>
using namespace std;

class Person{
    public:
    Person(int age){
        this->age=age;
    }
    //不是引用返回,返回的是拷贝体,返回值,拷贝构造函数。
    // Person PersonAddAge(Person &p){
    //     this->age+=p.age;
    //     return *this;
    // }
    //返回本体,要以引用的方式返回
    Person& PersonAddAge(Person &p){
        this->age+=p.age;
        return *this;
    }
    int age;
};
void test(){
    Person p1(17);
    cout<<p1.age<<endl;
}
void test02(){
    Person p1(17);
    Person p2(10);
    p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
    cout<<p2.age<<endl;
}

int main(void){ 
    test02();
    system("pause");
    return 0;
}

空指针调用成员函数,访问成员变量时,指针若为空,会报错。判断预防一下。

class Person{
    public:
    void showClass(){
        cout<<"class"<<endl;
    }
    void showAge(){
        if(this==NULL)
            return;
        cout<<"age"<<endl;
    }
    int age;
};

Person *p =NULL;
p->showClass();
p->showAge();

const修饰成员函数
常函数: void showPersion() const { } 相当于 const Person * const this;

  • 不可以修改成员属性
  • 成员属性声明时加上关键字mutable后,在常函数中可修改。

常对象: const Person p;
只能调用常函数。

4.4 友元 friend
让一个函数或类 访问零一个类中的私有成员,三种实现:

//1. 全局函数做友元;
//在类中 声明友元
class Building{
    friend void func();
}
void func(){
    访问私有;
}

//2. 类做友元 
类A中声明友元类B,B可访问A

//3. 成员函数做友元
//在要访问的类声明访问者的 friend 的成员函数。

Day 3

4.5 运算符重载
加号 operator+ ,这其实是一个函数名
可以通过成员函数、也可以通过全局函数。
左移<<

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

class Person{
    public:
    friend ostream& operator<<(ostream &out,Person &p);
    Person(){}
    Person(int a,int b):m_A(a),m_B(b){}
    //成员函数 重载+号
    Person operator+(Person &p){
        Person temp;
        temp.m_A=m_A+p.m_A;
        temp.m_B=m_B+p.m_B;
        return temp;
    }
    //无法利用成员函数 重载<<号,左移,
    private:
    int m_A;
    int m_B;
};
//全局函数 重载+号
// 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;
// }
//全局函数 重载<<号,左移
ostream& operator<<(ostream &out,Person &p){
    out<<"m_A="<<p.m_A<<"\tm_B="<<p.m_B;
    return out;
}
void test01(){
    Person p1(10,10);
    Person p2(30,30);
    Person p=p1+p2;
    cout<<p<<endl;
    cout<<p1<<endl;
}

int main(void){ 
    test01();
    system("pause");
    return 0;
}

重载++a 和 a++

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

class MyInteger{
public:
    friend ostream& operator<<(ostream& out,const MyInteger &m);
    MyInteger(){
        m_Num=0;
    }
    //前置递增,返回引用是为了一直对一个数据进行前置递增,返回引用
    MyInteger& operator++(){
        ++m_Num;
        return *this;
    }
    //后置递增,int 表示占位参数,可以用来区分前置和后置递增。 返回值,而非引用。
    MyInteger operator++(int){
        //先 返回结果
        MyInteger temp=*this;
        //后递增
        m_Num++;
        return temp;
    }
private: 
    int m_Num;
};

ostream& operator<<(ostream& out,const MyInteger &m){
    out<<m.m_Num<<endl;
    return out;
}

void test01(){
    MyInteger myint;
    cout<<++myint;
    //cout<<(myint++);
    cout<<myint;
}
void test02(){
    MyInteger my;
    cout<<my++;
    cout<<my;
}
int main(void){ 
    test02();
    system("pause");
    return 0;
}

赋值运算符
编译器至少给一个类添加四个函数 :还有一个是 赋值运算符 operator= ,对属性进行值拷贝。(浅拷贝)
如果类中有属性指向堆区,赋值时要深拷贝。

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

class Person
{
private:
    /* data */

public:
    Person(int age){

        m_Age=new int(age);
    }
    ~Person(){
        if(m_Age!=NULL){
            delete m_Age;
            m_Age=NULL;
        }
    }
    Person& operator=(Person &p){
        //先判断是否有属性在堆区,有则先释放干净,然后深拷贝
        if(m_Age!=NULL){
            delete m_Age;
            m_Age=NULL;
        }
        m_Age=new int(*p.m_Age);
        return *this;
    }
    int *m_Age;
};

void test01(){
    Person p1(18);
    Person p2(20);
    Person p3(30);
    cout<<*p1.m_Age<<endl;
    cout<<*p2.m_Age<<endl;

    p3=p2=p1;
    cout<<*p3.m_Age<<endl;
}

int main(void){ 
    test01();
    system("pause");
    return 0;
}

重载关系运算符 == !=

class Person
{
private:
    /* data */

public:
    Person(string name,int age){
        m_Name=name;
        m_Age=age;
    }
    bool operator==(Person &p){
        if(this->m_Name==p.m_Name&&this->m_Age==p.m_Age){
            return true;
        }
        return false;
    }
    bool operator!=(Person &p){
        if(this->m_Name==p.m_Name&&this->m_Age==p.m_Age){
            return false;
        }
        return true;
    }
    int m_Age;
    string m_Name;
};

函数调用运算符 ()
由于重载后使用方式非常像函数的调用,因此称为仿函数。没有固定写法,很灵活。
匿名函数对象

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

class MyPrint{
public:
    void operator()(string test){
        cout<<test<<endl;
    }
};
class MyAdd{
public:
    int operator()(int a,int b){
        return a+b;
    }
};

void test01(){
    MyPrint myPrint;
    myPrint("Hi");
}
void test02(){
    MyAdd myadd;
    cout<<myadd(10,10)<<endl;
    //匿名函数对象
    cout<<MyAdd()(100,100)<<endl;
}

int main(void){ 
    test01();
    test02();
    system("pause");
    return 0;
}

4.6 继承
好处:减少重复代码
子类也称派生类,父类也称基类。

class BasePage{
    public:
    void head(){
        cout<<"BasePage"<<endl;
    }
};
class Java:public BasePage{
};

基类可以自己增加成员(个性), 继承过来的为共性。
继承方式有 公共继承 public 、protected 、 private。
image.png

总结一下三个关键字的访问范围:
public: 能被类成员函数、子类函数、友元访问,也能被类的对象访问。
private: 只能被类成员函数及友元访问,不能被其他任何访问,本身的类对象也不行。
protected: 只能被类成员函数、子类函数及友元访问,不能被其他任何访问,本身的类对象也不行
[

](https://blog.csdn.net/yao5hed/article/details/81119256)
继承中的对象模型:
父类中所有的非静态成员属性都会被子类继承,私有成员属性被编译器隐藏了,访问不到,但是也被继承了。

父类和子类构造、析构函数 执行顺序:
父类构造——子类构造——子类析构——父类析构

父类、子类同名成员处理:

  • 访问子类时,直接访问
  • 父类 则加作用域 , 在变量前加 作用域

成员函数也是如此。静态成员变量也是如此。
image.png

多继承:
class Son: public Base1, public Base2{
};
父类中出现同名成员时,加作用域区分。

菱形继承:
image.png
image.png
两个父类有相同数据时,需要加作用域区分。
利用虚继承来解决,在继承之前加上virtual
class sheep:virtual public animal{}; 这时 sheeptuo类中只有一份m_Age.
vbptr 虚基类指针,指向 vbtable 虚基类表

4.7 多态
分为静态多态(函数重载和运算符重载)、动态多态(派生类和虚函数实现运行时多态)
区别:

  • 静态多态: 函数地址早绑定——编译阶段确定函数地址
  • 动态多态: 晚绑定——运行时确定。

允许父子之间的类型转换,父类可以调用子类。
虚函数 virtual void func(){ }, 里边存一个指针,其实是一个虚函数(表)指针vfptr ,指向一个虚函数表 vftable,表内部记录着函数的入口地址。
当子类重写父类的虚函数,子类的虚函数表内部会替换成子类的虚函数地址,当父类指针指向子类对象时,发生多态。
动态多态要满足:继承关系; 子类重写父类的虚函数;
使用:父类的指针 或者引用 指向子类对象。 Base * s= new Son;

优点: 代码结构清晰; 可读性强;利于扩展和维护
在真实开发中,提倡开闭原则,对扩展进行开放,对修改进行关闭。
案例:Calculator

纯虚函数和抽象类
父类的虚函数没有用时,将其写为纯虚函数 virtual void func ()=0;
此时这个类称为抽象类。特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类,无法实例化对象。

案例: 制作饮品:煮水—冲泡—倒入杯中—加入辅料,利用多态,抽象 饮品基类,子类 制作咖啡和茶叶。

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

class AbsDrinking{
public:
    virtual void Boil()=0;
    virtual void Brew()=0;
    virtual void PourInCup()=0;
    virtual void PutSth()=0;
    void MakeDrink(){
        Boil();
        Brew();
        PourInCup();
        PutSth();
    }
};

class Coffee:public AbsDrinking
{
public:
    void Boil(){
        cout<<"煮水"<<endl;
    }
    void Brew(){
        cout<<"冲泡咖啡"<<endl;
    }
    void PourInCup(){
        cout<<"倒入杯中"<<endl;
    }
    void PutSth(){
        cout<<"加入糖和牛奶"<<endl;
    }
};

class Tea:public AbsDrinking
{
public:
    void Boil(){
        cout<<"煮水"<<endl;
    }
    void Brew(){
        cout<<"冲泡茶叶"<<endl;
    }
    void PourInCup(){
        cout<<"倒入杯中"<<endl;
    }
    void PutSth(){
        cout<<"加入枸杞"<<endl;
    }
};

void doWork(AbsDrinking * abd){
    abd->MakeDrink();
    delete abd;
}

void test01(){
    doWork(new Coffee);
    cout<<"---------------------------"<<endl;
    doWork(new Tea);
}

int main(void){ 
    test01();
    system("pause");
    return 0;
}

虚析构函数和纯虚析构函数 virtual
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构函数。
解决:将父类中的析构函数改为虚析构函数和纯虚析构函数。
虚析构函数和纯虚析构函数的共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现。

区别:纯虚析构函数 —->抽象类,无法实例化对象。
父类在析构的时候不会调用子类的析构函数,导致如果子类有堆区属性,出现内存泄漏。
纯虚析构也要进行实现才不会报错,需要声明也需要实现。

Day 4

案例
image.png

5 文件
文件流头文件
类型 文本文件(ASCII)和二进制文件
操作类: ofstream(写),ifstream(读)、fstream(读写)

5.1 文本文件
写步骤: 头文件——创建流对象——打开文件——写数据——关闭
打开方式: ios::in(读) ios::out (写) ios::ate初始位置文件尾) app(追加) trunc(若存在先删除) binary(二进制) 配合使用 |

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

void test01(){
    ofstream ofs;
    ofs.open("test.txt",ios::out);
    ofs<<"姓名:唐馨"<<endl;
    ofs<<"性别:女"<<endl;
    ofs<<"年龄:22"<<endl;
    ofs.close();
}

int main(void){ 
    test01();
    system("pause");
    return 0;
}

读文件:头文件——流对象——打开并判断——读数据——关闭文件

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

void test01(){
    ifstream ifs;
    ifs.open("test.txt",ios::in);
    if(!ifs.is_open()){
        cout<<"文件打开失败"<<endl;
        return ;
    }
    //读:第1种方法
    // char buf[1024]={0};
    // while (ifs>>buf){
    //     cout<<buf<<endl;
    // }
    //读:第2种方法
    // char buf[1024]={0};
    // while (ifs.getline(buf,sizeof(buf)))
    // {
    //     cout<<buf<<endl;
    // }
    //读:第3种方法
    // string buf;
    // while(getline(ifs,buf)){
    //     cout<<buf<<endl;
    // }
    //读:第4种方法、
    char c;
    while( (c=ifs.get())!=EOF)
    {
        cout<<c;
    }    
    ifs.close();
}

int main(void){ 
    test01();
    system("pause");
    return 0;
}

5.2 二进制文件
读写方式 ios::binary
写用成员函数 write,函数原型 ostream& write(const char buffer, int len);
读用成员函数 read,函数原型 ostream& read(char
buffer, int len);

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

class Person
{
public:
    char m_Name[64];
    int m_Age;
};

void test01(){
    //写入
    ofstream ofs("person.txt",ios::out|ios::binary);
    Person p={"唐馨",18};
    ofs.write((const char *)&p,sizeof(Person));
    ofs.close();

    //读取
    ifstream ifs("person.txt",ios::in|ios::binary);
    if(!ifs.is_open()){
        cout<<"文件打开失败"<<endl;
        return;
    }
    Person p1;
    ifs.read((char *)&p1,sizeof(Person));

    cout<<p1.m_Name<<"\t"<<p1.m_Age<<endl;
}

int main(void){ 
    test01();
    system("pause");
    return 0;
}

综合案例:职工管理系统 基于多态
image.png