本文将接着上文(01 黑马C++_基础语法入门-CSDN博客),继续更新本人在学习黑马C++过程所整理的笔记(本文序号将继续承接上文)。
本文的使用方法:可以将本文作为一个工具书来使用,当编写程序或者阅读程序遇到不熟悉的知识点的时候,可以用 CTRL+F
按键 搜索关键词,查看该知识点。如:当忘记结构体定义方法,需不需要在括号后加分号,就可以 CTRL+F
搜索 “结构体”关键字,即可跳转到所需内容处。相较于使用 C++ Primer 感觉更加快速、便捷,希望对大家有所帮助。
3.1 程序的内存模型
- C++程序执行时,将内存分为 代码区、全局区、栈区、堆区
- 不同区域存放的数据赋予不同的生命周期,给我们更大的灵活编程。
- 在程序编译后,生成了.exe的可执行程序,未执行该.exe之前(即程序运行之前)分为代码区和全局区。
3.1.1 代码区
- 存放函数体的二进制代码,有操作系统进行管理
- 写的所有代码都会放到代码区中
3.1.2 全局区
- 存放全局变量、静态变量以及常量,该区域数据结束后由操作系统释放。
- 全局变量:不在函数体中的普通变量
- 静态变量:static修饰的变量
- 常量: 字符串常量、const修饰的全局变量(注意,不包含const修饰的局部变量)
cout << &"hello world" << endl; const int a = 10; cout << &a << endl;
- 不在全局区中的变量:局部变量、const修饰的局部变量
3.1.3 栈区
- 由编译器自动分配和释放,不可以返回局部变量的地址
- 存放局部变量、函数的形参
// 返回局部变量的地址
int* func()
{
int a = 10;
return &a;
}
int* p = func();
cout << *p << endl; // 10,第一次可以打印数字,因为编译器做了保留
cout << *p << endl; // 1523886328,第二次则乱码了
3.1.4 堆区
- 由程序员分配和释放,若程序员不释放,则程序结束时由操作系统回收。
- 在C++中使用 new 关键字在堆区开辟内存
int* p = new int(10); // 在堆区创建值为10的整型数据,并返回地址给指针p
cout << *p << endl; // 10
timer1 = new QTimer(this); // 与 Qt 中指针变量初始化同理
Widget::Widget(QWidget* parent) : QWidget(parent)
, ui(new Ui::Widget)
, timer1(new QTimer(this))
- 使用 delete 关键字在堆区释放内存
delete p;
- 在堆区创建整型数组,注意删除时使用 delete[ ]
int* arr = new int[10]; // arr指针接收数组首地址,arr等价于一个普通的数组
for (int i = 0; i < 10; i++)
arr[i] = i; // 通过[]直接索引数组的元素,和栈上的数组没有区别
delete[] arr; // 释放数组
3.2 引用
- 本质:相当于给变量取别名
- 等价于指针指向不可改,但是指针指向的值可以改 int const*
int& ref = a; -> int* const ref = &a; // 当使用引用时,编译器自动创建一个指针
ref = 20 -> *ref = 20; // 编译器发现ref是引用,自动转换*ref = 20
3.2.1 基本用法
int a = 10;
int& b = a; // a = 10; b = 10
b = 100; // a = 100; b = 100
- 引用必须初始化,且初始化后不可修改
3.2.2 引用作函数参数
- 引用传递形参也会修改实参,可以达到和指针一样的效果
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
swap(num1, num2);
3.2.3 引用作为函数返回值
- 如果函数返回值为引用,则函数可以作为左值
int& test()
{
static int a = 10;
return a;
}
int& ref = test(); // 相当于给a起别名为ref
test() = 100; // a = 100, 则 ref = 100
3.2.4 常量引用
- 在函数形参列表中,可以加const,可防止误操作修改了实参的值,同指针形参一样
void show(const int& val) // 相当于 const int* const val
{
cout << val << endl;
}
3.3 函数高级用法
3.3.1 函数的默认参数
- 若缺少参数,则使用默认值
int Func(int a, int b, int c = 10)
{
return a + b + c;
}
cout << Func(10, 20) << endl; // 40
- 注意
- 如果某个位置有默认参数,则从该位置往后都必须有默认参数
- 如果函数声明有默认参数,则函数实现就不能有默认参数了。如果实现有默认参数,声明就不能有默认参数。声明和实现只能一个有默认参数
3.3.2 函数占位参数
- 如果函数参数列表中有占位参数,则调用是必须填补该位置
void func(int a, int)
{
cout << "this is func" << endl;
}
func(10, 10); // 必须补全占位参数
- 占位参数可以用默认参数
void func(int a, int = 10)
{
cout << "this is func" << endl
}
func(10); // 默认参数无须补全
3.3.3 函数重载
- 基本语法
- 函数名相同,函数参数 类型不同 或 个数不同 或 顺序不同
- 函数的返回值不可以作为重载的条件
- 可以让函数名相同,提高复用性
- 函数重载遇到默认参数
void func(int a, int b = 10);
void func(int a);
func(10); // 此时会报错,此时func(10)两边都可以运行(二义性),程序不知道选择哪一个
- 函数重载分析是否可行,即分析函数会不会出现二义性,调用时会不会两边都行得通
3.4 类和对象 - 封装
C++面向对象的三大特性: 封装、继承、多态
3.4.1 封装定义
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
class 类名
{
访问权限:
属性 // 又称 成员属性/成员变量
行为 // 又称 成员函数/成员方法
}
3.4.2 访问权限
- public: 公共权限,成员类内类外都可以访问
- protected: 保护权限,成员类内可以访问,类外不可以访问;
儿子可以访问父亲 - private:私有权限,成员类内可以访问,类外不可以访问;
儿子不可以访问父亲
决定类外能不能访问;
3.4.3 struct 和 class 的区别
- struct 默认权限为公共
- class 默认权限为私有
3.5 构造函数和析构函数
- 构造函数:主要用于创建对象时对对象的成员属性赋值,创建对象时系统自动调用
Widget() { } // 类名() { }, 类内编写
Widget::Widget() { } // 类外编写
- 析构函数:主要用于对象销毁,系统自动调用
~Widget() { } // ~类名() { }, 类内编写
Widget::~Widget() { } // 类外编写
3.5.1 构造类型
- 参数分类 - 无参构造
Person() { }
- 参数分类 - 有参构造
Person(int a) { }
- 类型分类 - 普通构造
// 上述即为普通构造
- 类型分类 - 拷贝构造
Person(const Person& p) { }
- 必须用引用的形式传入参数,可以避免值传递时需要再生成一份临时空间
- 并且一般加上 const ,防止本体被修改
void func(Person p); // 函数的==值传递==也会调用拷贝构造,注意引用传递不行
Person test(); // 函数的值返回也会调用拷贝构造
3.5.2 调用方法
- 括号法
Person p1; // 无参
Person p1(10); // 有参
Person p2(p1); // 拷贝
- 显示法
Person p1; // 无参
Person p1 = Person(10); // 有参
Person p1 = Person(p2); // 拷贝
- 隐式转换法
Person p2 = 10; // 有参
Person p3 = p2; // 拷贝
- Person(10) 也称为一个匿名对象,在当前行执行结束后,系统会立即收回匿名对象,执行析构函数
3.5.3 构造函数调用规则
- C++编译器自动给一个类添加4个函数
默认构造函数(无参,无函数体)
默认析构函数(无参,无函数体)
默认拷贝构造函数(对属性值进行拷贝)
赋值运算符 operator=(对属性进行拷贝)
- 如果定义了有参构造函数,则 C++不提供无参构造,只提供拷贝构造
- 如果定义了拷贝构造函数,则 C++不再提供其他构造函数
3.5.4 深拷贝和浅拷贝
- 浅拷贝:简单的赋值操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
class Person
{
public:
Person(int age, int height) // 有参构造
{
m_age = age;
m_height = new int(height);
}
Person(const Person& p) // 拷贝构造
{
m_age = p.m_age;
//m_height = p.m_height; // 浅拷贝,简单赋值
m_height = new int(*p.m_height); // 深拷贝,在堆区开辟一个相同值的内存空间
}
~Person() // 析构函数,用于释放堆区开辟的内存
{
if (m_height != NULL) // 指针的内存不等于空,则释放
{
delete m_height; // 释放堆区内存
m_height = NULL; // 防止野指针出现,将指针置空
}
}
int m_age;
int* m_height;
};
Person p1(18, 180);
Person p2(p1); // 类中含有指针,若采用浅拷贝,则程序报错
3.5.5 初始化列表
- 类外实现初始化列表
class Person
{
public:
Person(); // 需要在类内进行函数声明
int m_age;
int* m_height;
};
// 类外实现,与 Qt 中的 QWidget 类似
Person::Person()
: m_age(10)
, m_height(new int(185))
{
}
Person p1;
- 变量作为初始化列表,简化有参构造
Person(int age, int height) : m_age(age), m_height(new int(height)) { }
可以用分文件的编写方式
// Person.h
#pragma once
#include <iostream>
using namespace std;
class Person
{
public:
Person();
private:
string name;
int age;
int* height;
};
// Person.cpp
#include "Person.h"
Person::Person()
: name("张三")
, age(18)
, height(new int(185))
{
}
3.6 类和对象 - 对象特性
3.6.1 类中嵌套类
- 构造时,先构造类中的对象,再构造类本身
- 析构的顺序与构造相反,先析构本身,再析构类中对象
3.6.2 静态成员
- 静态成员变量
- 所有对象共享同一份数据,在一个对象中发生改变,另一个对象中也会变
- 在编译阶段分配内存(即程序运行之前,没点击 .exe 程序前)
- 类内声明,类外初始化
class Person()
{
static int num;
}
int Person::num = 100;
cout << Person::num << endl; // 本身不属于某个对象,因此可以通过类名进行访问,无需创建对象
- 静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量
class Person()
{
static void func();
}
3.6.3 成员变量和成员函数分开存储
- 在C++中,类内成员变量和成员函数分开存储
- 只有非静态成员变量才属于类的对象上,静态成员变量、非静态成员函数、静态成员函数都不属于类的对象上
- C++中定义一个类,类内什么声明也没有,创建一个空对象,占用内存空间 1
3.6.4 this 指针
- this 指针指向被调用的成员函数所属的对象
- 当形参和成员变量同名时,可以用 this 来区分
class Person
{
Person(int age)
{
this->age = age;
}
int age;
}
Person p1(18); // 此时调用了Person的构造函数,被调用的构造函数所属的对象是 p1,所以 this 指针 p1, this->age 相当于 p1.age
- 在类的非静态成员函数中返回对象本身,可用 return* this
class Person
{
public:
Person& Addage(const Person& p) // 返回对象本体需要以引用的方式返回
{
this->age += p.age;
return *this; // 返回对象本身
}
int age;
};
Person p1(18), p2(18);
// 链式编程思想,与 cout 类似
p1.Addage(p2).Addage(p2); // 由于返回的是对象本身,可以继续调用Addage函数相加
cout << p1.age << endl; // 54
注意,如果不以 Person& 引用的方式返回,结果将为 36。
原因在于第一次调用 Addage 后 p1.age 成功相加为 36,但是返回的是值,即又拷贝了一个新 Person 对象,来调用下一个 Addage。
如果是以引用方式返回,则一直指向原对象
3.6.5 空指针访问成员函数
- C++中空指针也可以调用成员函数,但是也要注意有没有用到 this 指针,如果用到 this,需要保证代码的健壮性
class Person
{
public:
void show()
{
if (this == NULL) // 需要加上这行代码,检测this指针是否为空,为空则退出
return;
cout << age << endl; // 输出age默认为 this->age,所以使用了this指针
}
int age;
};
Person* p3 = NULL; // 定义空指针
p3->show(); // 加入了上述的 this==NULL 检测,才可以正常运行代码
3.6.6 const 修饰常函数
- 常函数
- 成员函数后加上 const 称为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加上 mutable,则常函数中可以修改
class Person
{
void set() const
{
age = 100; // 此时会报错, age = 100 相当于 this->age = 100;this指针的本质其实是指针常量 Person* const this,而此时相当于又加上个const,让this指向的值也不可以修改
id = 18; // 此时可以修改
}
void func();
int age;
mutable int id;
}
- 常对象
- 声明对象前加 const 称为常对象
- 常对象只能调用常函数,且常对象也可以调用mutable关键字定义的变量
const Person p;
p.func() // 报错
p.age = 18; // 报错
p.id = 100; // 可以正常运行
3.6.7 友元
让一个函数或者类,访问另一个类中的私有成员 friend
- 全局函数做友元
class Building
{
friend void goodfriend(const Building& build); // 友元声明
public:
Building()
{
Bedroom = "卧室";
}
private:
string Bedroom;
};
void goodfriend(const Building& build) // 可以访问Building私有变量
{
cout << build.Bedroom << endl;
}
Building b;
goodfriend(b);
- 类做友元
// 在 Building 类中添加
friend class GoodGay;
class GoodGay
{
public:
GoodGay() : build(new Building) { };
void visit()
{
cout << build->Bedroom << endl;
}
Building* build;
};
GoodGay g;
g.visit();
- 成员函数做友元
// 相当于不让整个 GoodGay 类访问私有变量,只让 visit() 函数访问
friend class GoodGay::visit();
3.7 运算符重载
p1.operator+(p2) -> p1 + p2
p1.operator++() -> ++p1
p1.operator>(p2) -> p1 > p2
print.operator()("hello") -> print("hello")
相当于可以省略 .operator( )
3.7.1 加号运算符重载
实现两个自定义类型相加
- 成员函数重载 + 号
class Person
{
public:
Person() {};
Person(int a, int b)
{
this->a = a;
this->b = b;
}
Person operator+(Person& p) // 重载运算符
{
Person temp;
temp.a = this->a + p.a;
temp.b = this->b + p.b;
return temp;
}
int a;
int b;
};
Person p1(10, 20), p2(15, 25);
// Person p3 = p1.operator+(p2); // 本质应该是这样的
Person p3 = p1 + p2; // 但是可以简化成这个
cout << p3.a << '\t' << p3.b << endl; // 25 45
- 全局函数重载 + 号
Person operator+(Person& p1, Person& p2) // 重载运算符
{
Person temp;
temp.a = p1.a + p2.a;
temp.b = p1.b + p2.b;
return temp;
}
Person p1(10, 20), p2(15, 25);
// Person p3 = operator+(p1, p2); // 本质应该是这样的
Person p3 = p1 + p2; // 但是可以简化成这个
cout << p3.a << '\t' << p3.b << endl; // 25 45
3.7.2 左移运算符重载
输出自定义的数据类型
- 利用成员函数重载左移运算符实现的效果是 p << cout (cout是在右边),因此采用全局函数重载左移运算符。
ostream& operator<<(ostream& cout, Person& p) // 返回引用,可以避免值拷贝,从而一直对同一个数据进行操作
{
cout << p.a << '\t' << p.b << endl;
return cout; // 返回cout,实现链式算法,
}
cout << p3 << endl;
3.7.3 递增运算法重载
自定义类型实现递增运算符
class MyInt
{
public:
MyInt()
{ num = 0; }
// 前置++ 运算符重载
MyInt& operator++() // 返回引用为了一直对同一个数据进行操作
{
num++;
return *this;
}
// 后置++ 运算符重载
MyInt operator++(int) // int - 占位参数,用于重载函数,与前置++区分
{ // 返回值,不能返回引用,因为temp是局部对象
MyInt temp = *this;
num++;
return temp;
}
int num;
};
// << 左移运算符重载
ostream& operator<<(ostream& cout, MyInt myint) // 此时 MyInt 不能用引用,因为后置递增中返回的是局部变量,只能用值传递
{
cout << myint.num;
return cout;
}
MyInt myint, myint1;
cout << ++myint << endl; // 0
cout << myint1++ << endl; // 1
3.7.4 赋值运算符重载
C++编译器自动给类添加赋值运算符重载 operator=,对属性进行浅拷贝。可以通过重载运算符实现深拷贝。
class Person
{
public:
Person(int age)
{
this->age = new int(age);
}
~Person() // 释放堆区变量
{
if (this->age != NULL)
{
delete this->age;
this->age = NULL;
}
}
// 重载赋值运算符
Person& operator=(Person& p)
{ // 返回值并且以引用返回自身,可以实现链式 p1 = p2 = p3
// this->age = p.age; // 浅拷贝
if (this->age != NULL) // 先判断是否有属性在堆区,如果有要先释放,然后再在堆区开辟空间赋值
{
delete this->age;
this->age = NULL;
}
this->age = new int(*p.age); // 深拷贝
return *this;
}
int* age;
};
int main()
{
//Person p1(10, 20), p2(15, 25);
//Person p3 = p1 + p2;
//cout << p3.a << '\t' << p3.b << endl;
//cout << p3 << endl;
MyInt myint, myint1;
cout << ++myint << endl;
cout << myint1++ << endl;
Person p1(18), p2(20), p3(30);
p3 = p2 = p1;
cout << *p1.age << endl;
cout << *p2.age << endl;
cout << *p3.age << endl;
}
3.7.5 关系运算符重载
自定义类型比大小
class Person
{
public:
Person(int a) : age(a) { };
// 重载 > 运算符
bool operator>(Person& p)
{
if (this->age > p.age)
return true;
else
return false;
}
// 重载 == 运算符
bool operator==(Person& p)
{
if (this->age == p.age)
return true;
else
return false;
}
int age;
};
Person p1(18), p2(18);
if (p1 == p2)
cout << "p1和p2年龄相等" << endl;
else
{
cout << (p1 > p2 ? "p1比p2大" : "p2比p1大") << endl;
}
3.7.6 函数调用运算符重载
也称为仿函数
class MyPrint
{
public:
void operator()(string test) // 重载 () 符号
{
cout << test << endl;
}
};
MyPrint myprint;
//myprint.operator()("hello world!"); // 本质应该是这样的
myprint("hello"); // 但是可以简化成这样
MyPrint()("world"); // 匿名函数对象,通过 MyPrint() 创建匿名对象,即用类名构造一个只有值没有名字的对象,然后调用()重载符,和 3.5.2 节的匿名对象类似
3.8 类和对象 - 继承
减少代码的重复性
3.8.1 语法
class A : public B
A 类称为子类 或 派生类
B 类称为父类 或 基类
class BasePage
{
public:
void header()
{
cout << "包含了网页的基本内容" << endl;
}
};
class CPP : public BasePage
{
public:
void context()
{
cout << "网页主体内容" << endl;
}
};
CPP cpp;
cpp.header();
cpp.context();
3.8.2 继承方式分类
- public: 公共继承, 父类公共权限、保护权限到子类权限不变,父类私有权限无法继承
- protected: 保护继承,父类公共权限、保护权限到子类都变为保护权限,父类私有权限无法继承
- private:私有继承,父类公共权限、保护权限到子类都变为私有权限,父类私有权限无法继承
决定从父类继承下来成员的访问权限
实际上父类中所有的非静态成员属性都会被子类继承,私有成员属性只是被编译器给隐藏了
3.8.3 VS 辅助工具查看类的对象模型
- 在电脑中 V开头的软件中找到 visio studio 辅助工具”Developer Command Prompt for VS 2022"工具打开
- 进入工程所在文件夹 : cd C:\C++_Learning\Project_VS\07 Inherit(可以通过dir指令查看当前目录中的文件)
- 查看对象模型 : cl /d1 reportSingleClassLayoutCPP main.cpp(其中的"CPP"是定义的类名;后面的文件名输入main按下Tab即可全部补全)
即可看到类对象中的分布图,即对象模型
3.8.4 继承中构造和析构顺序
- 先构造父类,再构造子类
- 先析构子类,再析构父类
3.8.5 同名成员的访问
son.Base::age;
3.8.6 多继承
class son : public Base1, public Base2
{ }
3.8.7 菱形继承
利用虚继承解决菱形继承的问题
class Animal
{
public:
int age;
};
class Sheep : public Animal {};
class Tuo : public Animal {};
class SheepTuo : public Sheep, public Tuo { };
cout << sizeof(SheepTuo) << endl; // 8,此时所在空间为8个字节,从父类中集成了两次的 age 变量
- 解决菱形继承问题的方法 虚继承,可避免从两个父类中继承了两个一样的成员,可将其合并为同一个成员
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
3.9 类和对象 - 多态
3.9.1 基本语法
class Animal
{
public:
virtual void speak() // 父类虚函数
{
cout << "动物在说话" << endl;
}
};
class Cat : public Animal
{
public:
void speak() // 子类重写父类虚函数
{
cout << "猫在说话" << endl;
}
};
void doSpeak(Animal& animal) // 父类引用指向子类对象
{
animal.speak(); // 若父类函数是虚函数,则调用的函数取决于传入的对象,可以实现猫、狗、猪...在说话
}
Cat cat;
doSpeak(cat);
// 如果父类函数没有 virtual,则地址早绑定,调用动物说话
// 否则,调用 猫在说话
3.9.2 多态案例 - 计算器类
- 普通写法
class Calculator
{
public:
int getResult(string oper)
{
if (oper == "+")
return m_num1 + m_num2;
else if (oper == "-")
return m_num1 - m_num2;
}
int m_num1, m_num2;
};
Calculator c1;
c1.m_num1 = 10;
c1.m_num2 = 20;
cout << c1.getResult("-") << endl;
- 多态写法
class AbstrctCalculator // 实现计算器抽象类
{
public:
virtual int getResult() = 0; // 纯虚函数
int m_num1, m_num2;
};
class AddCalculate :public AbstrctCalculator // 加法类,继承父类
{
public:
int getResult() { // 重写父类虚函数
return m_num1 + m_num2;
}
};
AbstrctCalculator* p = new AddCalculate; // 父类指针指向子类对象
p->m_num1 = 15;
p->m_num2 = 25;
cout << p->getResult() << endl;
好处:
- 组织结构清晰。如果要修改或者添加某一个运算,不用访问其他所有的运算源码,对于后期拓展和维护性高
- 可读性强
纯虚函数: - 又称抽象类,无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
3.9.3 虚析构和纯虚析构
多态使用时,如果子类有属性开辟到堆区,父类指针在释放时是无法调用到子类的析构代码,所以需要将父类的析构函数改为虚析构或纯虚析构。
class Animal
{
public:
virtual ~Animal() { } // 父类只有为虚析构,才会调用子类的析构函数
};
虚析构只是为了父类指针在释放的时候,可以调用子类的析构函数,所以只有当子类有对象开辟到堆区了,此时子类需要在析构中释放堆区数据,所以需要父类调用子类析构函数,则需要虚析构。例:
class Cat: public Animal
{
public:
Cat(string name){
m_name = new string(name); // 子类有数据开辟到堆区
}
~Cat(){
if(m_name != NULL) // 释放堆区开辟的数据
{
delete m_name;
m_name = NULL;
}
}
string *m_name;
}
3.10 文件操作
- 包含头文件 fstream
- 分类
- 文本文件:以 ASCLL 码形式存储
- 二进制文件:以二进制形式存储
- 操作文件的三大类
ofstream // 写操作
ifstream // 读操作
fstream // 读写操作
3.10.1 文件打开方式
- 文件打开方式可以配合使用,如果 ios::binary | ios::out
3.10.2 文本文件 - 写
#include <iostream>
using namespace std;
#include <fstream>
int main()
{
// 创建流对象
ofstream ofs;
// 打开文件
ofs.open("test.txt", ios::out);
// 写数据
ofs << "hello" << endl;
ofs << "world" << endl;
// 关闭文件
ofs.close();
return 0;
}
3.10.3 文本文件 - 读
// 创建流对象
ifstream ifs;
// 打开文件
ifs.open("test.txt", ios::in);
// 判断是否打开成功
if (!ifs.is_open()) {
cout << "文件打开失败!" << endl;
return;
}
// 读数据
具体见下文
// 关闭文件
ifs.close();
读文件数据的四种方式
// 第一种
char buf[1024] = { 0 };
while (ifs >> buf) { // 一个一个读,读到头返回 false
cout << buf << endl;
}
// 第二种
char buf[1024] = { 0 };
while ( ifs.getline(buf, sizeof(buf)) ) { // 一行一行读, 最多读取 1024 个字符
cout << buf << endl;
}
// 第三种
string buf;
while (getline(ifs, buf)) { // 使用全局的getline,需要包含 string 头文件
cout << buf << endl;
}
// 第四种 (一个一个读,效率低,不推荐)
char c;
while ((c = ifs.get()) != EOF) { // 一个一个读,直到 EOF(文件尾标志)
cout << c;
}
3.10.4 二进制文件 - 写
class Person
{
public:
char m_Name[64];
int m_Age;
};
// 创建流对象
ofstream ofs;
// 打开文件
ofs.open("person.txt", ios::out | ios::binary); // 二进制 + 写
// 写数据
Person p = { "张三", 18 };
ofs.write((const char*)&p, sizeof(Person)); // 二进制写,用txt打开是乱码
// 关闭文件
ofs.close();
3.10.5 二进制 - 读
// 创建流对象
ifstream ifs;
// 打开文件
ifs.open("person.txt", ios::in | ios::binary);
// 判断是否打开成功
if (!ifs.is_open()) {
cout << "文件打开失败!" << endl;
return;
}
// 读数据
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << p.m_Name << " " << p.m_Age << endl;
// 关闭文件
ifs.close();
3.10.6 判断文件是否为空
ifstream ifs;
// 打开文件
ifs.open("test.txt", ios::in);
if (!ifs.is_open()) {
return 0;
}
char ch;
ifs >> ch; // 需要先读取一个字符
if (ifs.eof()) // eof检测是否到达文件尾,到达则返回1
cout << "文件为空" << endl;
ifs.close();
至此,C++核心编程文章已更新完毕,下篇将更新 C++ 提高编程。