c++核心编程

目录

内存四区

引用

函数提高

类和对象

封装

对象的初始化和清理

对象

构造函数的分类及调用

拷贝构造函数调用时机

构造函数调用规则 

深拷贝和浅拷贝

初始化列表 

类对象作为类成员 

静态成员

c++对象和this指针 

成员变量和成员函数分开存储

this指针

空指针访问成员函数 

 const修饰成员函数

友元

运算符重载 

加号运算符重载

左移运算符重载

递增运算符重载

赋值运算符重载

关系运算符重载

函数调用运算符重载

继承

继承方式

继承中的对象模型

继承中构造和析构顺序

继承同名成员处理方式

继承同名静态成员处理方式

多继承

菱形继承

多态

原理剖析

纯虚函数和抽象类

虚析构和纯虚析构

文件操作

文本文件

写文件

读文件

二进制文件

写文件

读文件

内存四区

  • 代码区:存放二进制代码,并且只读、共享
  • 全局区:
    • 全局变量
    • 静态变量     static
    • 常量
      • 字符串常量
      • const修饰的常量
        • const修饰的全局变量
        • const修饰的局部常量-------栈区
  • 栈区:由编译器分配释放
    • 局部变量、形参(勿返回地址
  • 堆区:由程序员分配释放
    // 分配---new关键字
    1、创建变量
    int *p = new int(10);  // 创建一个值为10的变量并返回其地址
    2、创建数组
    int *p = new int[10];  // 创建一个包含10个元素的数组并返回其首地址
    
    // 删除---delete关键字
    delete p;
    delete[] p;

引用

  • 给变量起别名

    int a = 10;
    int b = &a;
  • 注意事项:
    • 引用必须初始化
      • 初始化变量:初始值被拷贝到新建的对象中
      • 初始化引用:程序将引用和它的初始值绑定在一起,而不是将初始值拷贝到引用
    • 引用一旦初始化无法更改 
      • 一直绑定,无法令引用重新绑定到另一个对象
  • 引用作函数参数------简化指针修改实参
    // 值传递
    void swap1(int a, int b) {
        int tmp = a;
        a = b;
        b = tmp;
    }
    
    // 地址传递
    void swap2(int *a, int *b) {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
    
    // 引用传递
    void swap3(int &a, int &b) {
        int tmp = a;
        a = b;
        b = tmp;
    }
    
    int main() {
        int a = 10;
        int b = 20;
        swap1(a, b);    // 值传递------无法修饰形参
        swap2(&a, &b);  // 地址传递------修饰形参
        swap3(a, b);    // 引用传递------修饰形参
        return 0;
    }
    
  • 引用做函数返回值
    • 勿返回局部变量的引用
    • 函数作左值
      int& test() {
          static int a = 10;  // 勿返回局部变量的地址
          return a;
      }
      
      int main() {
          int &ref = test();
          cout << ref << endl;   // 10
       
          test() = 1000;         // 相当于赋值给a,而ref是a的别名,所以输出1000
          cout << ref << endl;   // 1000
      }
  • 引用本质
    • 指针常量
      int a = 10;
      int &b = a;  // 自动转换为int* const b = &a;  指针常量
      b = 20;      // 内部发现b是引用,自动转换为*b = 20
  • 常量引用:修饰形参,防止误操作 

函数提高

  • 函数默认参数
    • 传了参数用传的,没传用默认值
    • 默认值放在后面
    • if函数声明有默认值,函数实现就不能有默认参数------声明和实现只能有一个含默认值
  • 函数占位参数
    • 用于占位,调用函数时必须填补该位置
      // 占位参数
      void func(int a, int )
      func(10, 10);
      
      // 占位参数还可以有默认值
      void func(int a, int = 10)
  • 函数重载:函数名相同,提高复用性
    • 条件:
      • 1、同一作用域下
      • 2、函数名称相同
      • 3、函数参数类型不同/个数不同/顺序不同
    • 函数返回值不可以作为函数重载的条件
  • 注意事项
    • 引用作为重载条件
    • 函数重载碰到函数默认参数------报错,有二义性 
      // 1、引用作为重载条件
      void func(int &a)
      void func(const int &a)
      
      // 2、函数重载碰上函数默认参数
      void func2(int a)
      void func2(int a, int b = 10)
      
      int main() {
          int a = 10;
          func(a);    // 调用无const
          func(10);   // 调用有const
      
          func2(10);  // 报错,有二义性
          return 0;
      }

类和对象

  • 面向对象的三大特性:封装、继承、多态

    • c++认为万事万物皆为对象,对象上有其属性和行为

    • 具有相同性质的对象,抽象为类

封装

  • 意义
    • 将属性和行为作为一个整体,表现生活中的事物
    • 将属性和行为加以权限控制
  • class 类名{ 访问权限:属性/ 行为 };
    • 属性------变量
    • 行为------函数
  • 注意
    • 类中的属性和行为,统称为成员
    • 属性(成员属性、成员变量)
    • 行为(成员函数、成员方法)
  • 属性和行为置于不同权限下加以控制
    • public:公共权限------类内可以访问,类外可以访问
    • protected:保护权限------类内可以访问,类外不可以访问
    • private:私有权限------类内可以访问,类外不可以访问
    • 注:protected和private存在继承方面的区别
      • protected:子类可以访问父类中的保护内容
      • private:子类不可以访问父类中的私有内容
  • struct与class的区别:默认访问权限不同
    • struct:公共权限
    • class:私有权限
  • 应用
    • 将成员属性设为私有
      • 将所有成员属性设为私有,可自己控制读写权限
      • 对于写权限,可检测输入数据的有效性
    • 行为中提供公共接口
      • set------写权限
      • get------读权限

对象的初始化和清理

  • 对象

    • 初始设置------构造函数
      • 没有初始状态,后果未知
    • 对象销毁前的清理数据的设置------析构函数
      • 没有及时清理,造成安全问题
    • 编译器自动调用,完成初始化和清理工作
    • 强制使用,if不提供这两个函数,编译器会提供------但提供的是空实现
  • 构造函数------类名 () {}
    • 没有返回值也不写void
    • 函数名称与类名一致
    • 可以有参数,因此可重载overload
    • 自动调用且只会调用一次------创建对象时
  • 析构函数------ ~类名 () {}
    • 没有返回值也不写void
    • 函数名称与类名一致
    • 不可以有参数,因此不可以overload
    • 自动调用且只会调用一次------对象销毁前
  • 构造函数的分类及调用

    • 分类
      • 按参数
        • 有参构造
        • 无参构造------默认构造
      • 按类型
        • 普通构造
        • 拷贝构造
          Person (const Person &p) {
              // 将传入的人身上的属性copy到本身
              age = p.age;
          }
    • 调用方式
      • 括号法
      • 显示法
      • 隐式转换法
        // 括号法
        Person p1;      // 无参调用
        Person p2(10);  // 有参调用
        Person p3(p2);  // 拷贝调用
        
        // 显示法
        Person p1;                // 无参调用
        Person p2 = Person(10);  // 有参调用
        Person p3 = Person(p2);  // 拷贝调用
        
        // 隐式转换法
        Person p1;        // 无参调用
        Person p2 = 10;   // 有参调用
        Person p3 = p2;   // 拷贝调用
    • 注意:
      • 调用默认构造函数时,不要加 ()  ------ Person p1();
        • 编译器会认为是函数声明
      • Person(10) :匿名对象------当前行执行后立即释放
      • 不要利用拷贝构造函数初始化匿名对象
        • 编译器会认为是对象声明
  • 拷贝构造函数调用时机

    • 三种情况
      // 1、使用一个已经创建完毕的对象来初始化一个新对象
      Person p1(10);
      Person p2(p1);
      
      // 2、值传递的方式给函数参数传值
      void do(Person p) {
      
      }
      void test() {
          Person p;
          do(p);
      }
      
      // 3、以值方式返回局部对象
      Person do() {
          Person p1;
          return p1;
      }
      void test() {
          Person p = do();
      }
  • 构造函数调用规则 

    • 创建一个类------默认情况下至少给该类添加三个函数
      • 1、默认构造函数------空实现
      • 2、析构函数------空实现
      • 3、拷贝构造函数------用于对对象进行值拷贝
    • 规则
      有参构造函数无参构造函数(默认)拷贝构造函数
      情况1用户提供
      c++提供×
      情况2用户提供
      c++提供××
  • 深拷贝和浅拷贝

    • deep copy------在堆区重新申请空间,进行拷贝操作
    • shallow copy------简单的赋值拷贝操作
    • 析构函数
      • 将在堆区开辟的数据释放
        ~Person () {
            if(m_Height != NULL) {
                delete m_Height;
                m_Height = NULL;  // 避免为野指针
            }
        }
    •  if利用编译器提供的拷贝构造函数,会做shallow copy操作------Person p2(p1);
    • 但是shallow copy带来的问题就是堆区的内存重复释放------deep copy解决

  • 执行析构函数时,先释放p2,代码正常运行,堆区内存被释放。因此当析构p1时,由于此时堆区数据不存在,报错。

  • 自己写一个拷贝构造函数------重新在堆区开辟一块内存
  • if属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止shallow copy带来的问题
  • 初始化列表 

    • 用来初始化属性
    • 构造函数 ( ): 属性1(值1), 属性2(值2), ...{ }
      // 1、传统初始化方式
      Person(int a, int b, int c) {
          m_a = a;
          m_b = b;
          m_c = c;
      }
      int m_a;
      int m_b;
      int m_c;
      
      Person p(10, 20, 30);
      
      // 2、初始化列表方式
      // 2.1 赋值更改不方便
      Person():m_a(10), m_b(20), m_c(30) {
      
      }
      int m_a;
      int m_b;
      int m_c;
      
      Person p;
      
      // 2.2 赋值自由
      Person(int a, int b, int c):m_a(a), m_b(b), m_c(c) {
      
      }
      int m_a;
      int m_b;
      int m_c;
      
      Person p(10, 20, 30);
  • 类对象作为类成员 

    • 类中的成员可以是另一个类的对象,该成员称为对象成员
      对象实例
      成员属性+行为
      对象成员类中的成员是另一个类的对象
    • eg:
      • class A { }         class B {A a;}
      • B类中有对象A作为成员,A为对象成员
    • 创建B对象时,A与B的构造和析构顺序?
      • 当其他类对象作为本类成员,构造时先构造类对象,再构造本身;析构顺序与构造相反
  • 静态成员

    • 在成员变量和成员函数前加 static
静态成员变量

1、所有对象共享同一份数据

2、在编译阶段分配内存

3、类内声明,类外初始化

eg:static int a;

        int Person::a = 20;

注:

1、不属于某个对象上,所有对象都共享同一份数据

2、两种访问方式

        通过对象------Person p; p.a;

        通过类名------Person::a;

3、类外访问不到私有静态成员变量

静态成员函数

1、所有对象共享同一个函数

2、静态成员函数只能访问静态成员变量------无法区别到底是哪个对象的属性

注:

1、两种访问方式

        通过对象------Person p; p.func();

        通过类名------Person::func();

2、类外访问不到私有静态成员函数

c++对象和this指针 

  • 成员变量和成员函数分开存储

  • 在c++中,类内的成员变量和成员函数分开存储
  • 只有非静态成员变量才属于类的对象上
// 空对象
class Person{};
Person p;   // sizeof(p) = 1
// 1、空对象上占用内存1字节
// 2、c++编译器会给每个空对象分配一个字节空间---为了区分空对象占内存的位置
// 3、每个空对象应该有一个独一无二的内存地址


// 非静态成员变量
class Person{
    int a;
};
Person p;    // sizeof(p) = 4


// 静态成员变量
class Person{
    int a;
    static int b;  // 不属于类的对象上
};
int Person::b = 10;  // 静态成员变量---类内声明,类外初始化
Person p;    // sizeof(p) = 4


// 非静态成员函数
class Person{
    int a;
    void func() {}  // 不属于类的对象上
};
Person p;    // sizeof(p) = 4


// 静态成员函数
class Person{
    int a;
    static void func() {}  // 不属于类的对象上
};
Person p;    // sizeof(p) = 4
  • this指针

  • question
    • c++成员变量和成员函数分开存储
    • 每个非静态成员函数只会诞生一份函数实例---多个同类型对象共用一块代码
    • 这块代码如何区分哪个对象调用自己?
    • c++提供特殊的对象指针(this指针)---指向被调用的成员函数所属的对象
  • this指针
    • 概念
      • 隐含在每个非静态成员函数内的一种指针
      • 无需定义,直接使用
    • ​​​​​​​用途
      • ​​​​​​​当形参和成员变量同名时,可利用this指针区分---解决名称冲突问题
        class Person {
            Person (int age) {
                age = age;   // 冲突
            }
            int age;
        }
        // 解决方法1
        this->age = age;
        // 解决方法2
        m_age = age;
        int m_age;
      • 在类的非静态成员函数中返回对象本身------return *this;
        void addage(Person &p) {
            this->age += p.age;
        }
        Person P1(10);
        Person P2(20);
        p2.addage(p1); // 可以正确输出
        // 链式编程思想
        p2.addage(p1).addage(p1);  // 报错,返回值为void,即空对象无法调用成员函数
        
        // 解决方法:返回对象本身
        Person& addage(Person &p) {
            this->age = age;
            return *this;
        }
  • 空指针访问成员函数 

  • c++空指针也可调用成员函数,但注意是否用到this指针
  • if用到,需要加以判断来保证代码的健壮性
    class Person {
        void showname() {
            cout << "this is my name!" << endl;
        }
        void showage() {
            cout << "age = " << m_age << endl;
        }
        int m_age;
    }
    void test01() {
        Person *p = NULL;
        p->showname();   // √
        p->showage();    // × 默认有个this->m_age, 空指针访问报错
    }
  •  const修饰成员函数

  • 常函数
    • 定义:成员函数后加const---修饰的是this指针(指针常量:指针指向不可改),因此,再加个const,指针指向值不可改
    • question:不可以修饰成员属性
    • way:成员属性声明时加关键字mutable后,可修改
  • 常对象
    • 定义:声明对象前加const
    • 常对象只能调用常函数
void showage() const {}  // 常函数
const Person p;  // 常对象

友元

  • eg:客厅  public
  •         卧室  private------可允许自己好朋友进去
  • 定义:让一个函数或者类访问另一个类中私有成员    friend(关键字)
  • 实现
    // ​​​​​​​全局函数做友元------全局函数访问类中私有变量
    void goodGay() {}  // 全局函数
    在类中加入friend void goodGay();
    
    // 类做友元------类访问另一个类中私有变量
    class GoodGay {};   // GOodGay可访问Building私有成员
    class Building {
        friend class GoodGay;
    }
    
    // 成员函数做友元------类成员函数访问另一个类中私有变量
    class GoodGay {
        visit();   // 可访问Building私有成员
        visit1();  // 不可访问Building私有成员
    }
    class Building {
        friend void GoodGay::visit();
    }

运算符重载 

  • 对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
  • 加号运算符重载

  • 实现两个自定义数据类型相加的运算
  • 对于内置数据类型,编译器知道如何进行运算
    // eg:
    int a = 10;
    int b = 10;
    int c = a + b;
  • 非内置数据类型
    • question
      class Person {
      public:
          int m_a;
          int m_b;
      }
      Person p1;
      p1.m_a = 10;
      p1.m_b = 10;
      Person p2;
      p2.m_a = 10;
      p2.m_b = 10;
      Person P3 = p1 + p2;  // 编译器不懂怎么计算
    • way
      // 1、自己写成员函数实现
      Person add(Person &p) {
          Person tmp;
          tmp.m_a = this->m_a + p.m_a;
          tmp.m_b = this->m_b + p.m_b;
          return tmp;
      }
      
      // 编译器起了个通用名称
      // 2、通过成员函数重载+号
      Person operator+(Person &p) {
          Person tmp;
          tmp.m_a = this->m_a + p.m_a;
          tmp.m_b = this->m_b + p.m_b;
          return tmp;
      }
      // 调用时可以简化
      Person p3 = p1.operator+(p2);
      Person p3 = p1 + p2;  // 简化版本
      
      // 3、通过全局函数重载+号
      Person operator+(Person &p1, Person &p2) {
          Person tmp;
          tmp.m_a = p1->m_a + p2.m_a;
          tmp.m_b = p1->m_b + p2.m_b;
          return tmp;
      } 
      // 调用时同样可以简化
      Person p3 = operator+(p1, p2);
      Person p3 = p1 + p2;  // 简化版本   
  • 左移运算符重载

    class Person {
    public:
        int m_a;
        int m_b;
        // 1、成员函数重载  << (左移运算符)
        void operator<<(Person &p) {}  // ×  需要两个对象
        void operator<<(cout) {}  // p.operator<<(cout) === p << cout
        // 不会利用成员函数重载<<运算符,因为无法实现cout在左侧
    };
    
    // 2、只能利用全局函数重载<<运算符
    // void operator<<(cout, p)  === cout << p
    // 但是有两个问题
    // 2.1、需要先确定cout数据类型------右键→→→转到定义------ostream(输出流对象)
    // 2.2、返回值类型------链式编程思想,因此返回ostream
    ostream& operator<<(ostream &cout, Person &p) {
        cout << "m_a = " << p.m_a << "m_b = " << p.m_b;
        return cout;
    }
    void test01() {
        Person p;
        p.m_a = 10;
        p.m_b = 10;
        cout << p << endl;
    }
    
  • 注:重载<<运算符配合友元可实现输出自定义数据类型
  • 递增运算符重载

  •  通过重载递增运算符实现自己定义的数据类型
    // 1、重载前置++运算符
    MyIntger& operator++() {
        num++;    // 先进行++运算
        return *this;  // 再将自身返回------返回引用
    }
    
    // 2、重载后置++运算符
    MyIntger operator++(int ) { // int为占位参数,用于区分前置和后置
        MyIntger tmp = *this;  // 先记录
        num++;                 // 再++运算
        return tmp;            // 最后返回记录值------返回值为局部对象
    }
    MyIntger myint;
    ++myint;
    myint++;
  • 注:前置++返回引用------为了对一个数据连续递增    ++(++myint) 
  •        后置++返回值
  • 赋值运算符重载

  • c++至少给一个类添加4个函数
    • 默认构造函数
    • 析构函数
    • 拷贝构造函数
    • 赋值运算符operator=,对属性进行值拷贝
  • ​​​​​​​question
    • ​​​​​​​if类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题------堆区内存重复释放
  • ​​​​​​​way
    Person& operator=(Person &p) {
        // 编译器提供的是shallow copy
        // m_age = p.m_age;
    
        // 应该先判断是否有属性在堆区,if有先释放干净再进行deep copy
        if(m_age != NULL) {
            delete m_age;
            m_age = NULL;
        }
        // deep copy
        m_age = new int(*p.m_age);
        // 返回对象本身
        return *this;   // 为了连续赋值  p1 = p2 = p3
    }
  • 关系运算符重载

  •  实现两个自定义类型对象进行对比操作    <    >   ==   !=
    bool operator==(Person &p) {
        if(this->m_name == p.m_name && this->m_age == p.m_age) {
            return true;
        }
        return false;
    }
    if(p1 == p2) {}
  • 函数调用运算符重载

  • 函数调用运算符()
  • 因重载后使用的方式非常像函数的调用,称为仿函数
  • 仿函数没有固定写法,非常灵活
    void operator()(string test) {
        cout << test << endl;
    }
    
    // 两种方式调用
    // 1、实例化对象
    Person p;
    p("hello");
    
    // 2、匿名函数对象
    cout << Person()("hello");   // Person():匿名对象

继承

  • 下级成员(上一级的共性+自己的特性)
  • 继承目的------减少重复代码
    // class 子类:继承方式 父类
    // eg:
    class BasePage{};
    class java : public BasePage{};
  • 子类---派生类       父类---基类 
  • 继承方式

  1. 公共继承
  2. 保护继承
  3. 私有继承

  • 继承中的对象模型

  •  question
    • 从父类继承过来的成员,哪些属于子类对象中?
  • conclusion
    • 父类中所有非静态成员属性都会被子类继承
    • 私有成员属性依然会被继承,但被编译器隐藏,因此无法访问
      class Base {
      public:
          int a;
      protected:
          int b;
      private:
          int c;
      };
      
      class Son : public Base {
      public:
          int d;
      }
      
      sizeof(Son);  // 16
  • 继承中构造和析构顺序

  • 子类继承父类后,当创建子类对象,也会调用父类的构造函数
  • question
    • 父类和子类的构造和析构顺序
  • conclusion
    • 父类构造-子类构造-子类析构-父类析构
  • 继承同名成员处理方式

  • question
    • 当子类与父类出现同名的成员,如何通过子类对象访问父类或子类中同名数据?
  • way
    Son s;
    s.m_a;        // 访问子类同名成员------直接访问
    s.Base::m_a;  // 访问父类同名成员------加作用域
  • 适用于成员属性和成员函数
  • 继承同名静态成员处理方式

  •  question
    • 继承中同名静态成员在子类对象上如何进行访问?
  • conclusion
    • 与非静态成员处理方式一致
    • 同样适用于静态成员函数
      // 两种访问方式
      // 1、对象
      s.m_a;
      s.Base::m_a;
      
      // 2、类名
      Son::m_a;
      Son::Base::m_a;
  • 多继承

  1. class 子类:继承方式 父类1,继承方式 父类2......
  2. 可能引发父类中同名成员情况出现,需加作用域区分
  3. c++实际开发中不建议多继承
  • 菱形继承

  • 两个派生类继承同一个基类,又有某个类同时继承两个派生类
  • 菱形继承---钻石继承
  • question
  1. 羊继承动物数据,驼同样继承动物数据,当羊驼使用数据时,出现二义性------加作用域
  2. 羊驼继承两份数据,但只需要一份数据------虚继承
    // 虚继承:在继承前加关键字virtual
    // eg:
    class sheep : virtual public Animal {};
    class tuo : virtual public Animal {};
  • 类似共享同一份数据
  • Animal类也称为虚基类

多态

  • 分类
    • 静态多态---函数重载、运算符重载属于静态多态,复用函数名
    • 动态多态---派生类和虚函数实现运行时多态
  • 区别
    • 静态多态的函数地址早绑定---编译阶段确定函数地址
    • 动态多态的函数地址晚绑定---运行阶段确定函数地址 
class Animal {
    virtual void speak() {  // 加virtual变虚函数
        cout << "animal is speaking" << endl;
    }
};

class Cat : public Animal {
    void speak() {
        cout << "cat is speaking" << endl;
    }
}

class Dog : public Animal {
    void speak() {
        cout << "dog is speaking" << endl;
    }
}

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

void test01() {
    Cat cat;
    dospeak(cat);
    Dog dog;
    dospeak(dog);
}
  • 未加virtual(早绑定)
    • animal is speaking
    • animal is speaking
  • 加virtual(晚绑定)
    • cat is speaking
    • dog is speaking
  • 对象不同调用不同
  • 条件
    • 继承关系
    • 子类重写父类虚函数
      • 重写:函数返回值类型、函数名称、参数列表完全相同
    • 动态多态使用需要父类指针或引用指向子类对象
  • 原理剖析

  • 未加virtual---sizeof(Animal) = 1
  • 加virtual---sizeof(Animal) = 4  (int、float、指针

  • 当父类指针或引用指向子类对象时,发生多态
  • 优点
    • 代码结构清晰
    • 可读性强
    • 利于前期和后期的扩展及维护
  • 纯虚函数和抽象类

  • question
    • 在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
  • way
    • 将虚函数改为纯虚函数
    • virtual 返回值类型 函数名 (参数列表) = 0;
  • 当类中有了纯虚函数,这个类也称为抽象类
  • 特点
    • 无法实例化对象
    • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
  • 虚析构和纯虚析构

  • question
    • 多态使用时,if子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
  • way
    • 将父类中的析构函数改为虚析构或纯虚析构
    • 共性
      • 可解决父类指针释放子类对象
      • 都需具体的函数实现
    • 区别
      • if是纯虚析构,该类属于抽象类,无法实例化对象
// 虚析构
virtual ~类名() {};

// 纯虚析构---声明+实现
virtual ~类名() {} = 0;
// 具体函数实现写在类外
类名::~类名 () {
    // ...
}

文件操作

  • question
    • 程序运行时产生的数据(临时数据),运行结束即被释放
  • way
    • 通过文件将数据持久化   #include <fstream>
  • 文件类型
    • 文本文件---以文本的ASCII码形式存储在计算机中
    • 二进制文件---以文本的二进制形式存储在计算机中
  • 操作文件
    • ofstream     写     o-output
    • ifstream      读      i-input
    • fstream       读写

文本文件

  • 写文件

  1. 包含头文件     #include <fstream>
  2. 创建流对象     ofstream ofs;
  3. 打开文件         ofs.open("路径", 打开方式);
  4. 写数据             ofs << "写入的数据";
  5. 关闭文件         ofs.close();
  • 打开方式
    • ios::in               为读文件而打开
    • ios::out             为写文件而打开
    • ios::ate             初始位置:文件尾
    • ios::app            追加写
    • ios::trunc          文件存在删除再创建
    • ios::binary        二进制形式
  • 打开方式可配合使用,利用 | 操作符
  • eg:ios::binary | ios::out
  • 读文件

  1. 包含头文件                                        #include <fstream>
  2. 创建流对象                                        ifstream ifs;
  3. 打开文件并判断文件是否打开成功    ifs.open("路径", 打开方式);
  4. 读数据                                               四种方式读取
  5. 关闭文件                                            ifs.close();
  • 文件是否打开成功
    if(!ifs.is_open()) {
        cout << "文件打开失败" << endl;
    }
  • 四种读数据方式
    // 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) {  // EOF:End of file
        cout << c;
    }

    ​​​​​​​

二进制文件

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

    • 二进制方式写文件主要利用流对象调用成员函数write
      ostream& write(const char * buffer, int len);
      // 第一个参数:字符指针buffer指向内存中一段存储空间
      // 第二个参数:读写的字节数
  • 读文件

    •  二进制方式写文件主要利用流对象调用成员函数read
      istream& read(char * buffer, int len);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值