02 黑马C++_核心编程

本文将接着上文(01 黑马C++_基础语法入门-CSDN博客),继续更新本人在学习黑马C++过程所整理的笔记(本文序号将继续承接上文)。

本文的使用方法:可以将本文作为一个工具书来使用,当编写程序或者阅读程序遇到不熟悉的知识点的时候,可以用 CTRL+F 按键 搜索关键词,查看该知识点。如:当忘记结构体定义方法,需不需要在括号后加分号,就可以 CTRL+F 搜索 “结构体”关键字,即可跳转到所需内容处。相较于使用 C++ Primer 感觉更加快速、便捷,希望对大家有所帮助。

3.1 程序的内存模型

  • C++程序执行时,将内存分为 代码区、全局区、栈区、堆区
  • 不同区域存放的数据赋予不同的生命周期,给我们更大的灵活编程。
  • 在程序编译后,生成了.exe的可执行程序,未执行该.exe之前(即程序运行之前)分为代码区和全局区。

3.1.1 代码区

  • 存放函数体的二进制代码,有操作系统进行管理
  • 写的所有代码都会放到代码区中
    请添加图片描述

3.1.2 全局区

  • 存放全局变量、静态变量以及常量,该区域数据结束后由操作系统释放。
    1. 全局变量:不在函数体中的普通变量
    2. 静态变量:static修饰的变量
    3. 常量: 字符串常量、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
  • 注意
    1. 如果某个位置有默认参数,则从该位置往后都必须有默认参数
    2. 如果函数声明有默认参数,则函数实现就不能有默认参数了。如果实现有默认参数,声明就不能有默认参数。声明和实现只能一个有默认参数

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 函数重载

  1. 基本语法
    • 函数名相同,函数参数 类型不同个数不同顺序不同
    • 函数的返回值不可以作为重载的条件
    • 可以让函数名相同,提高复用性
  2. 函数重载遇到默认参数
	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 构造类型

  1. 参数分类 - 无参构造
Person() {  }
  1. 参数分类 - 有参构造
Person(int a) {  } 
  1. 类型分类 - 普通构造
// 上述即为普通构造
  1. 类型分类 - 拷贝构造
Person(const Person& p) { }   
  • 必须用引用的形式传入参数,可以避免值传递时需要再生成一份临时空间
  • 并且一般加上 const ,防止本体被修改
void func(Person p);   // 函数的==值传递==也会调用拷贝构造,注意引用传递不行
Person test();         // 函数的值返回也会调用拷贝构造

3.5.2 调用方法

  1. 括号法
Person p1;       // 无参
Person p1(10);   // 有参
Person p2(p1);   // 拷贝
  1. 显示法
Person p1;                  // 无参
Person p1 = Person(10);     // 有参
Person p1 = Person(p2);     // 拷贝
  1. 隐式转换法
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 静态成员

  1. 静态成员变量
    • 所有对象共享同一份数据,在一个对象中发生改变,另一个对象中也会变
    • 在编译阶段分配内存(即程序运行之前,没点击 .exe 程序前)
    • 类内声明,类外初始化
class Person()
{
	static int num;
}
int Person::num = 100;
cout << Person::num << endl;  // 本身不属于某个对象,因此可以通过类名进行访问,无需创建对象
  1. 静态成员函数
    • 所有对象共享一个函数
    • 静态成员函数只能访问静态成员变量
class Person()
{
	static void func(); 
}

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

  • 在C++中,类内成员变量和成员函数分开存储
  • 只有非静态成员变量才属于类的对象上,静态成员变量、非静态成员函数、静态成员函数都不属于类的对象上
  1. 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 修饰常函数

  1. 常函数
    • 成员函数后加上 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;
}
  1. 常对象
    • 声明对象前加 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;

好处:

  1. 组织结构清晰。如果要修改或者添加某一个运算,不用访问其他所有的运算源码,对于后期拓展和维护性高
  2. 可读性强
    纯虚函数
  3. 又称抽象类,无法实例化对象
  4. 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。

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
  • 分类
    1. 文本文件:以 ASCLL 码形式存储
    2. 二进制文件:以二进制形式存储
  • 操作文件的三大类
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++ 提高编程。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_小猪沉塘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值