C++进阶知识

文章目录

  • 1. 内存分区模型
    • 1.1 程序运行前
    • 1.2 程序运行后
    • 1.3 new操作符
  • 2.引用
    • 2.1 基本使用
    • 2.2 引用做函数参数
    • 2.3 引用做函数返回值
    • 2.4 引用的本质
    • 2.5 常量引用
  • 3.函数提高
    • 3.1 函数默认参数
    • 3.2 函数占位参数
    • 3.3 函数重载
  • 4.类和对象
    • 4.1 封装
      • 4.1.1 封装的意义
      • 4.1.2 struct和class的区别
      • 4.1.3 成员属性设置为私有
      • 4.1.4 案例
    • 4.2 对象的初始化和清理
      • 4.2.1 构造函数和析构函数
      • 4.2.2 构造函数的分类及调用
      • 4.2.3 拷贝构造函数调用时机
      • 4.2.4 构造函数调用规则
      • 4.2.5 深拷贝与浅拷贝
      • 4.2.6 初始化列表
      • 4.2.7 类对象作为类成员
      • 4.2.8 静态成员
    • 4.3 C++对象模型和this指针
      • 4.3.1 成员变量和成员函数分开存储
      • 4.3.2 this指针
      • 4.3.3 空指针访问成员变量
      • 4.3.4 const修饰成员函数
    • 4.4 友元
      • 4.4.1 全局函数做友元
      • 4.4.2 类做友元
      • 4.4.3 局部函数做友元
    • 4.5 运算符重载
      • 4.5.1 加号运算符重载
      • 4.5.2 左移运算符重载
      • 4.5.3 递增运算符重载
      • 4.5.4 赋值运算符重载
      • 4.5.5 关系运算符重载
      • 4.5.6 函数调用运算符重载
    • 4.6 继承
      • 4.6.1 继承方式
      • 4.6.2 继承中的对象模型
      • 4.6.3 继承中的构造和析构函数
      • 4.6.4 继承同名成员处理方式
      • 4.6.5 多继承语法
      • 4.6.6 菱形继承
      • 4.6.7 子类调用父类的构造函数
    • 4.7 多态
      • 4.7.1 案例一:计算器类
      • 4.7.2 纯虚函数和抽象类
      • 4.7.3 案例二:制作饮品
      • 4.7.4 虚析构和纯虚析构
      • 4.7.5 案例三:电脑组装
  • 5.文件操作
    • 5.1 文本文件
    • 5.2 二进制文件

C++:面向对象的编程技术

1. 内存分区模型

C++程序在执行时,将内存大方向划分为4个区域

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

内存四区意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程。

1.1 程序运行前

在程序编译后,生成了exe可执行程序,未执行该程序前,分为两个区域:

1.代码区:存放CPU执行的机器指令
特点:
代码是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可;
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
2.全局区:全局变量和静态变量存放在此
全局区还包含了常量区,字符串常量和其他常量也存放在此;
该区的数据在程序结束后由操作系统释放

//栈区
int a = 10;
int b = 10;
cout << "局部变量a的地址为:" << (int)&a << endl;
cout << "局部变量b的地址为:" << (int)&b << endl;

//全局区
//全局变量
int g_a = 10;
int g_b = 10;
cout << "全局变量g_a的地址为:" << (int)&g_a << endl;
cout << "全局变量g_b的地址为:" << (int)&g_b << endl;
//静态变量
static int s_a = 10;
static int s_b = 10;
cout << "静态变量s_a的地址为:" << (int)&s_a << endl;
cout << "静态变量s_b的地址为:" << (int)&s_b << endl;
//常量:字符串常量,const修饰的变量
cout << "字符串常量的地址为:" << (int)&"hello world" << endl;
//const修饰的全局变量,const修饰的局部变量
cout << "全局常量c_g_a地址为:" << (int)&c_g_a << endl;
cout << "全局常量c_g_b地址为:" << (int)&c_g_b << endl;
cout << "局部常量c_l_a地址为:" << (int)&c_l_a << endl;
cout << "局部常量c_l_b地址为:" << (int)&c_l_b << endl;

C++进阶知识_第1张图片
可以看出:全局变量,静态变量,字符串常量,全局常量的地址比较接近;而局部变量和局部常量的地址比较接近。由此可以得到下图:
C++进阶知识_第2张图片

1.2 程序运行后

栈区:
由编译器自动分配释放,存放函数的参数值,局部变量等

【注】:不要返回局部变量的地址,因为栈区开辟的数据由编译器自动释放

int* func(){
	int a = 10;
	return &a;
}

//main:
int* p = func();
cout << *p << endl;//10
cout << *p << endl;//其他

第一次可以正确的打印数字,是因为编译器做了保留,第二次打印的时候会出错(不再保留)。
堆区:由程序员分配释放,若程序员不释放,程序结束时由操作系统回收,在C++中主要利用【new在堆区开辟内存】。

int* func(){
	//指针,本质是局部变量,放在栈上;指针保存的数据是放在堆区的
	int* p = new int(10);
	return p;
}

//main:
int* p = func();
cout << *p << endl;//10

C++进阶知识_第3张图片

1.3 new操作符

int *func(){
	//在堆区创建整型数据
	//new返回的是 该数据类型的指针
	int* p = new int(10);
	return p;
}
void test01(){
	int* p = func();
	cout << *p <<endl;//10
	//堆区的数据 由程序员管理开辟,释放
	//利用关键字 delete 释放堆区的数据
	delete p;
	cout << *p << endl;//异常:读取访问冲突
}

堆区开辟数组及释放

void test02(){
	int* arr = new int[10];//10个元素
	for(int i = 0; i < 10; i++){
		arr[i] = i + 100;
	}//赋值
	for(int i = 0; i < 10; i++){
		cout << arr[i] << endl;
	}//打印
	//释放数组
	delete[] arr;
}

2.引用

即给变量起别名。

2.1 基本使用

数据类型 &别名 = 原名;

(2个注意)

int a = 10;
int c = 10
//int &b;//错误,1.引用必须要初始化
int &b = a;
//int &b = c;//错误,2.一旦初始化后,就不可以更改了
b = 20;//此时a也是20

2.2 引用做函数参数

函数传参时,可以利用引用的技术让形参修饰实参,可以简化指针修改实参。
效果与地址传递一样。

void swap3(int& a, int& b) {//引用传递
	int t = a;
	a = b;
	b = t;
}

2.3 引用做函数返回值

int& test( ) {//返回局部变量的引用,1.不可返回
	int a = 10;//局部变量,在栈区
	return a;
}

int& test2( ) {
	static int a = 10;//静态变量,在全局区,程序结束后系统释放
	return a;
}
//main函数中:
int &ref1 = test();//会出错,见栈区讲解。
int &ref2 = test2();//ref是a的别名
test2() = 1000;//2.函数的调用作为左值(前提:函数的返回值是引用)

2.4 引用的本质

在C++内部实现是一个指针常量。引用,一旦初始化就不可以更改(指向不可变)。指针常量,指针类型的常量,指向不可变,指向的值可变。
C++进阶知识_第4张图片

2.5 常量引用

主要用来修饰形参,防止误操作。

int& ref = 10;//错误,引用必须引一块合法的内存空间
const int& ref = 10;//正确,编译器将代码修改为 int t=10;const int &ref=t; 变量不可修改

参数不会被修改

void show(const int &a) {
	//a = 100;
	cout << "a=" << a << endl;
}

3.函数提高

3.1 函数默认参数

C++中,函数的形参列表中的形参是可以有默认值的。

返回值类型 函数名 (参数 = 默认值)
int fun(int a,int b = 10, int c = 20){ }
cout << fun(10) < cout << fun(10 , 20) < 【注】:
1.如果自己传入数据,就用自己的数据;如果没有,就用默认值。
2.如果某位置设置了默认值,那么从这个位置往后都要有默认值。
3.如果函数声明 &函数定义中的参数只能选其中一个有默认值。

3.2 函数占位参数

C++函数的形参列表里可以有占位参数,用来占位,调用函数时必须填补该位置。占位参数可以有默认值。

返回值类型 函数名 (数据类型){}
void func(int a, int){}
func(10,10);

3.3 函数重载

作用:函数名可以相同,提高复用性
满足条件:同一作用域下;函数名称相同;参数类型不同或个数不同或顺序不同。
【注】:
函数返回值不可以作为函数重载的条件(只有返回值不同,不满足函数重载条件)

//1.引用作为重载条件
void func(int &a){}
void func(const int &a){}
//main函数中:
int a = 10;
func(a);//调用第一个函数
func(10);//调用第二个函数
//int &a = 10;不合法   const int &a = 10;合法 

//2.函数重载碰到默认参数
void func(int a,int b = 10){}
void func(int a){}
//main函数中:
func(10);//会出错,默认参数中设置了默认值在函数调用中即使缺省也可以正常调用。此时上述两个函数的参数情况均符合。

4.类和对象

C++面向对象的三大特性:封装继承多态
C++认为万事万物皆为对象,对象上有其属性和行为。
具有相同性质的对象,可以称之为

4.1 封装

4.1.1 封装的意义

【意义1】:将属性和行为作为一个整体,表现生活中的事物;

class 类名{访问权限:属性/行为};
//设计一个圆类,求圆的周长
const double PI = 3.14;
class circle{
	//访问权限
public:
	//属性
	int m_r;
	//行为
	double calculateZC(){//通常为函数
		return 2 * PI * m_r;
	}
};
int main(){
	circle c1;//通过圆类,创建具体的圆
	c1.m_r = 10;
	cout << "圆的周长:" << calculateZC() << endl;
}

设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号

class Student{
public:
//类中的属性和行为:统一称为成员
	string m_Name;//成员属性,成员变量
	int m_Id;
	
	void show(){//成员函数 成员方法
		cout << "姓名:" << name << "学号:" << m_Id << endl;
	}
};
//main:
Student s1;//实例化对象
s1.m_Name = "张三";
s1.m_Id = 1;
s1.show();

另一种赋值操作:

class Student{
public:

	string m_Name;
	int m_Id;
	
	void show(){
		cout << "姓名:" << name << "学号:" << m_Id << endl;
	}
	void setName(string name){
		m_Name = name;
	}
	void setId(int id){
		m_Id = Id;
	}
};
//main
Student s1;//实例化对象
s1.setName("张三");
s1.setId(1);
s1.show();

【意义2】:将属性和行为加以权限控制。
类在设计的时候,可以把属性和行为放在不同的权限下,加以控制。

1.公共权限(public):成员在类内可以访问,类外可以访问
2.保护权限(protected):成员在类内可以访问,类外不可以访问。儿子可以访问父亲中的保护内容。
3.私有权限(private):成员在类内可以访问,类外不可以访问。儿子不可以访问父亲的私有内容。

class Person{
public:
	string m_Name;
protected:
	string m_Car;
private:
	int m_Password;

public:
	void func(){//类内都可以访问
		m_Name = "张三";
		m_Car = "拖拉机";
		m_Password = 123456;
	}
};
//main:
Person p1;
p1.m_Name = "里斯";//类内可以访问
//p1.m_Car = "奔驰";//类内不可访问
//p1.Password = 123;//类内不可访问
p1.func();//类内可以访问

4.1.2 struct和class的区别

唯一的区别在于默认的访问权限不同。
struct 默认权限为公共;class默认权限为私有。

4.1.3 成员属性设置为私有

【优点1】:可以自己控制读写权限

class Person{
public:
	void setName(string name){//设置姓名
		m_Name = name;
	}
	string getName(){//获取权限
		return m_Name;
	}//可读可写
	
	int getAge(){
		m_Age=0;//初始化为0
		return m_Age;
	}//只读

	void setLover(string lover){
		m_Lover = lover;
	}//只写
	
private:
	string m_Name;//可读可写
	int m_Age;//只读
	string m_Lover;//只写
};
//main:
Person p;
p.setName("张三");
cout << "姓名为:" << p.getName() << endl;
//p.m_Age = 18;//出错
cout << "年龄为:" << p.getAge() << endl;
p.setLover ("数据");
//cout << "爱人为:" << p.m_Lover() << endl;//出错,不可访问

【优点2】:对于写,可以检测数据的有效性

//以上述类中的成员m_Age为例,验证优点2
	int getAge(){
		return m_Age;
	}//可读可写 年龄范围在0~150之间
	void setAge(int age){
		if(age < 0 || age > 150){
			cout << "输入的年龄有误" << endl;
			return;
		}
		m_Age = age;
	}

4.1.4 案例

利用成员函数判断两个立方体是否相等。
在class中的函数,就是成员函数。这个成员函数的参数值设置一个即可,因为成员函数可以直接访问成员变量。
点和圆的关系:

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

class Circle{//圆类
public:
	void setR(int r){
		m_R = r;
	}
	void getR(){
		return m_R;
	}
void setCenter(Point center){
		m_Center = center;
	}
	void getCenter(){
		return m_Center;
	}
private:
	int m_R;
	Point m_Center;//在类中,可以让另一个类作为成员
};

void isInCircle(Circle &c, Point &p){//判断点和圆的关系
	int distance = (c.getCenter().m_X - p.getX()) *(c.getCenter().m_X - p.getX()) + (c.getCenter().m_Y - p.getY()) * (c.getCenter().m_Y - p.getY());
	int rDistance = c.getR() * c.getR();
	if(rDistance == distance){
		cout << "点在圆上" << endl;
	}
	else if(rDistance > distance){
		cout << "点在圆内" << endl;
	}
	else{
		cout << "点在圆外" << endl;
	}
}

//main:
Circle c;//创建圆
c.SetR(10);
Point center;
center.SetX(10);
center.SetY(0);
c.SetCenter(center);

Point p;//创建点
p.setX(10);
p.setY(10);

isInCircle(c, p);//判断关系

将类拆分成两部分,声明和实现

//point.h文件中:
#pragam once //防止头文件重复包含
#include 
using namespace std;
class Point{
public:
	void setX(int x);
	int getX();
	void setY(int y);
	int getY();
private:
	int m_X;
	int m_Y;
}

源文件中都是函数实现:

//point.cpp文件中:
#include "point.h"
void Point::setX(int x){//在此文件中,这些函数为全局函数,但这些本来是成员函数,所以应该告知:这是Point作用域下的函数
	m_X = x;
}
void Point::getX(){
	return m_X;
}
void Point::setX(int y){
	m_Y = y;
}
void Point::getY(){
	return m_Y;
}

4.2 对象的初始化和清理

4.2.1 构造函数和析构函数

对象的初始化和清理是两个非常重要的安全问题。

一个对象或是变量没有初始状态,对其使用后果是未知;同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。

C++利用了构造函数和析构函数解决上述问题,这两个杉树将会被编译器自动调用,完成对象初始化和清理工作。

对象的初始化和清理是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。【编译器提供的是空实现】。

【构造函数】:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

【析构函数】:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

构造函数:类名(){ }
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

class Person{
public:
	Person(){//需要有作用域
		cout << "Person构造函数的调用" << endl;
	}//构造函数
	~Person(){
		cout << "Person析构函数的调用" << endl;
	}
}
void test(){
Person p;//在栈上的数据,test执行完毕后,释放这个对象
}

析构函数:~类名(){ }
析构函数,没有返回值也不写void
函数名称与类名相同,在名称前加上符号~
析构函数不可以有参数,因此不可以发生重载
程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

4.2.2 构造函数的分类及调用

两种分类方式:

1.按参数分–有参构造(默认构造)和无参构造;
2.按类型分–普通构造和拷贝构造;

class Person{
	Person(){
		cout << "Person的无参构造函数的调用" << endl
	}
	Person(int a){
		age = a;
		cout << "Person的有参构造函数的调用" << endl
	}
	Person(const Person &p){
		//将传入的人身上的所以属性,拷贝到我身上
		age = p.age;
		cout << "Person的拷贝构造函数的调用" << endl
	}//拷贝构造,其余都是普通构造
}

三种调用方式:括号法,显示法,隐式转换法。

【 注意】:
1.默认构造函数调用,不要加括号否则会被编译器认为是函数的声明。
2.不要利用拷贝构造函数。

初始化匿名对象,编译器会认为Person (p3) 是Person p3;在创建一个新的Person对象

void test(){
	//括号法
	Person p1;//默认构造函数调用,不要加括号否则会被编译器认为是函数的声明
	Person p2(10);//有参构造函数
	Person p3(p2);//拷贝构造函数
	
	//显示法
	Person p4;
	Person p5 = Person(10);//有参构造
	Person p6 = Person(p2);//拷贝构造
	//Person(10);//匿名对象,当前执行结束后,系统会立即回收匿名对象
	
	//隐式转换法
	Person p7 = 10;//相当于 Person p7 = Person(10);有参构造
	Person p8 = p7;//拷贝构造函数
}

4.2.3 拷贝构造函数调用时机

1.使用一个已经创建完毕的对象来初始化一个新对象;
2.值传递的方式给函数参数传值;
3.以值方式返回局部对象;

class Person{
public:
		Person(){
			cout << "Person默认构造函数调用" << endl;
		}
		Person(int age){
			m_Age = age;
			cout << "Person有参构造函数调用" << endl;
		}
		Person(const Person &p){
			m_Age = p.age;
			cout << "Person拷贝构造函数调用" << endl;
		}
		~Person(){
			cout << "Person析构函数调用" << endl;
		}

		int m_Age;
};
void test1(){//1.使用一个已经创建完毕的对象来初始化一个新对象
	Person p1(20);//默认
	Person p2(p1);//拷贝
}

void doWork(Person p){//2.值传递的方式给函数参数传值
	//值传递:拷贝一个临时副本(拷贝构造函数)
	//实参传给形参时,调用一个拷贝构造函数
}
void test2(){
	Person p;//默认
	doWork(p);//拷贝
}

Person doWork2(){//3.以值方式返回局部对象
	Person p1;//默认
	return p1;//拷贝
}
void test3(){
	Person p = doWork2(); 
}

4.2.4 构造函数调用规则

默认情况下,C++编译器至少给一个类添加3个函数:

1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
1.如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
2.如果用户定义拷贝构造函数,C++不会再提供其他构造函数

class Person{
public:
	Person(){
		cout << "Person的默认构造函数调用" << endl;
	}
	Person(int age){
		m_Age = age;
		cout << "Person的有参构造函数调用" << endl;
	}
	Person(const Person &age){
		m_Age = p.age;
		cout << "Person的拷贝构造函数调用" << endl;
	}//即使没有这个函数,Person p2(p1);也会
	~Person{
		cout << "Person的析构函数调用" << endl;
	}
	
	int m_Age;
};

void test(){
	Person p;
	p.m_Age = 18;
	Person p2(p);//即使没有Person(const Person &age),也能得到p2的年龄是18。因为C++至少给一个类添加3个函数
	cout << "p2的年龄为:" << p2.m_Age << endl;
}

void test2(){
	//Person p;//没有Person(),会出错。因为我们定义了有参,编译器将不会提供无参
	Person p(28);
	Person p2(p);//没有默认构造函数,但编译器仍会提供拷贝构造函数
	cout << "p2的年龄为:" << p2.m_Age << endl;//28
}

4.2.5 深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作

析构函数:堆区内存的释放

class Person{
public:
	//默认构造函数
	Person(int age, int height){
		m_Age = age;
		m_Height = new int (height);
	}
	~Person(){
		//析构函数,将堆区开辟数据做释放操作
		if(m_Height != NULL){
			delete m_Height;
			m_Height = NULL;//防止野指针出现,置空
		}//会报错
	}

	int m_Age;
	int* m_Height;
};
void test(){
	Person p1(18,160);
	Person p2(p1);
}

C++进阶知识_第5张图片

Person(const Person &p){
	cout << "Person拷贝构造函数调用" << endl;
	m_Age = p.m_Age;
	//m_Height = p.m_Height;//编译器默认实现的代码(浅拷贝)
	//深拷贝
	m_Height = new int(*p.m_Height);
}

4.2.6 初始化列表

语法:
构造函数():属性1(值1),属性2(值2)…{ }

class Person{
public:
	/*//传统初始化操作
	Person(int a, int b, int c){
		m_A = a;
		m_B = b;
		m_C = c;
	}*/
	//初始化列表初始化属性
	Person():m_A(10),m_B(10),m_C(10){}//值固定
	/*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;
};
void test(){
	//Person p(10,20,30);
	Person p;
	//Person p(30,20,10);//30 -> int a -> a -> m_A
}

4.2.7 类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称之为:对象成员

class A{};
class B{
	A a;//B类中有对象A作为成员,A为对象成员
};

创建B对象时,A与B的构造和析构的顺序是谁先是谁后?

class Phone{
public:
	Phone(string pName){
		m_PName = pName;
	}
	string m_PName;
};
class Person{
public: 
	//Phonr m_Phone = pName;隐式转换法
	Person(string name,string pName):m_Name(name),m_Phone(pName){}
	string m_Name;
	Phone m_Phone;
};
void test(){
	Person p("张三""苹果MAX");
}

当其他类对象作为本类成员,构造时候先构造类对象,再构造自身;析构顺序与构造相反。

4.2.8 静态成员

在成员变量和成员函数前加上关键字static,就是静态成员。
1.静态成员变量:
所有对象共享同一份数据;在编译阶段分配内存;类内声明,类外初始化
2.静态成员函数:
所有对象共享同一个函数;静态成员函数只能访问静态成员变量

静态成员变量不属于某个对象,因为所有对象都共享同一份数据

class Person{
public:
	static int m_A;//类内声明
private:
	static int m_B;
};
int Person::m_A = 100;//类外初始化
int Person::m_A = 200;
void test(){
	Person p;
	cout << p.m_A << endl;//100
	Person p2;
	p2.m_A = 200;
	cout << p.m_A << endl;//200,共享同一份数据
}
void test2(){//两种访问方式
	//1.通过对象进行访问
	Person p;
	cout << p.m_A << endl;
	//2.通过类名访问
	cout << Person::m_A << endl;
	//cout << Person::m_B << endl;//出错,静态成员变量有访问权限
}

静态成员函数

class Person{
public:
	static void func(){
		m_A = 100;//静态成员函数可以访问静态成员变量
		//m_B =200;//出错,无法区分是哪个是哪个对象的m_B
		cout << "static void func调用" << endl;
	}
	static int m_A;
	int m_B;
private:
	static void func2(){
		cout << "static void func2调用" << endl;
	}
};
int Person::m_A = 0;
void test(){//两种访问方式
	//1.通过对象访问
	Person p;
	p.func();
	//2.通过类名访问
	Person::func();
	//Person::func2();//出错,静态成员函数有访问权限
}

4.3 C++对象模型和this指针

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

在C++中,类内的成员变量和成员函数分开存储。

只有非静态成员变量才属于类的对象上。

空类:

class Person{ };
void test(){
	Person p;
	//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置,每个空对象也应该有一个独一无二的内存地址
	cout << "size of p = " << sizeof(p) << endl;//1
}

非静态成员变量:

class Person{
	int m_A;非静态成员变量,属于类的对象上
};
void test1(){
	Person p;
	cout << "size of p = " << sizeof(p) << endl;//4
}

非静态成员变量 + 静态成员变量:

class Person{
	int m_A;
	static int m_B;//静态成员变量,不属于类的对象上
};
int Person::m_B = 0;
void test2(){
	Person p;
	cout << "size of p = " << sizeof(p) << endl;//4
}

非静态成员变量 + 静态成员变量 + 非静态成员函数:

class Person{
	int m_A;
	static int m_B;
	void func(){ }//非静态成员函数,不属于类的对象上
};
int Person::m_B = 0;
void test2(){
	Person p;//成员变量和成员函数,是分开存储的
	cout << "size of p = " << sizeof(p) << endl;//4
}

非静态成员变量 + 静态成员变量 + 非静态成员函数 + 静态成员函数:

class Person{
	int m_A;
	static int m_B;
	void func(){ }
	static void func(){}//静态成员函数,不属于类的对象上
};
int Person::m_B = 0;
void test2(){
	Person p;
	cout << "size of p = " << sizeof(p) << endl;//4
}

4.3.2 this指针

每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。
通过提供特殊的对象指针—this指针,来区分是哪个对象调用自己的。【this指针指向被调用的成员函数所属的对象。】
this指针是隐含每一个非静态成员函数内的一种指针。不需要定义,【直接使用】即可。

this指针的用途:
当形参和成员变量同名时,可用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return *this

class Person{
public:
	Person(int age){
		//age = age;//会出错
		this->age = age;//指向的是被调用的成员函数 所属的对象(p1在调用Person(int age)),this指向p1
	}

	/*void PersonAddAge(Person &p){
		this->age += p.age;
	}*/
	//若返回类型为Person,结果是20。第一次调用后,拷贝了新的数据,创建新的对象
	Person& PersonAddAge(Person &p){
		this->age += p.age;
		//this指向p2的指针,而*this指向的就是p2这个对象本体
		return *this;
	}
	int age;
};
//1.解决名字冲突
void test(){
	Person p1(18);
	cout << "p1的年龄:" << p1.age << endl;
}
//链式编程
void test(){
	Person p1(10);
	Person p2(10);
	//p2.PersonAddAge(p1);
	//多次相加p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1)会出错
	//因为PersonAddAge的返回类型是void,返回类型是p2才可
	//链式
	p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
	cout << "p2的年龄:" << p2.age << endl;//20,40
}

4.3.3 空指针访问成员变量

class Person{
public:
	void showClassName(){
		cout << "this is Person class" << endl;
	}
	void showPersonAge(){
		//因为传入的指针是NULL
		if(this == NULL){
			return;
		}
		cout << "age = " << this->m_Age << endl;
	}
	int m_Age;
};
void test(){
	Person* p = NULL;
	p->showClassName();//没问题
	//p->showPersonAge();//出错,this是空指针
}

4.3.4 const修饰成员函数

常函数:
成员函数后加const,我们称之为常函数(修饰的是this指针,指向的值也不可以修改)
常函数内不可以修改成员属性
成员属性声明时加关键字mutable,在常函数中依然可以修改
常对象:
声明对象前加const,称之为常对象
常对象只能调用常函数

class Person{
public:
	void showPerson(){
		m_A = 100;//可以修改
		this->m_A = 200;//可以修改
	}
	//常函数
	void showPerson1() const{
		this->m_B = 100;
		//this = NULL;//出错,指向不可修改
	}
	/* //this指针本质:指针常量,指针的指向是不可以修改的
	void showPerson() const{//相当于const Person* const this;
		this->m_A = 100;//不可以修改
	}
	*/
	int m_A;
	mutable int m_B;
};
void test1(){
	Person p;
	p.showPerson();
}
//常对象
void test2(){
	const Person p;//常对象
	//p.m_A = 100;//出错
	p.m_B = 100;//可以修改
	p.showPerson1();//常对象只能调用常函数
	//p.showPerson();//出错,普通成员函数可修改属性
}

4.4 友元

关键字:friend
目的:让一个函数或者类 访问另一个类中私有成员。
有3种实现

4.4.1 全局函数做友元

friend void goodGay(Building *building);

class Building{
	friend void goodGay(Building *building);//此时可以访问private成员
public:
	Building(){
		m_SittingRoom = "客厅";
		m_BedRoom ="卧室";
	}
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};
//全局函数
void goodGay(Building *building){
	cout << "好朋友全局函数 正在访问:" << building->m_SittingRoom <<endl;
	cout << "好朋友全局函数 正在访问:" << building->m_bedRoom <<endl;//不可以访问->ok
}
void test(){
	Building building;
	goodGay(&building);
}

4.4.2 类做友元

friend class Building;
类外成员函数:【类名::函数名】

class Building;
class goodGay{
public:
	goodGay();
	void visit();//该函数访问Building中的属性
private:
	Building* building;
};

class Building{
	friend class goodGay;//类做友元,可以访问private
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};

//类外成员函数
Building::Building(){
	m_SittingRoom = "客厅";
	m_BedRoom ="卧室";
}
goodGay::goodGay(){
	building = new Building;//堆区创建建筑物对象,Building* building指向new出的对象
}
goodGay::void visit(){
	cout << "好朋友全局函数 正在访问:" << building->m_SittingRoom <<endl;
	cout << "好朋友全局函数 正在访问:" << building->m_bedRoom <<endl;//出错->ok
 }

4.4.3 局部函数做友元

friend void GoodGay::visit();

class Building;
class GoodGay{
public:
	GoodGay();
	void visit();//可以访问private
	void visit2();//不可以
	Building* building;
};
class Building{
	friend void GoodGay::visit();//成员函数做友元,可以访问
public:
	Building();
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};
Building::Building(){
	m_SittingRoom = "客厅";
	m_BedRoom ="卧室";
}
goodGay::goodGay(){
	building = new Building;//堆区创建建筑物对象,Building* building指向new出的对象
}
goodGay::void visit(){
	cout << "好朋友全局函数 正在访问:" << building->m_SittingRoom <<endl;
	cout << "好朋友全局函数 正在访问:" << building->m_bedRoom <<endl;//出错->ok
}
 goodGay::void visit2(){
	cout << "好朋友全局函数 正在访问:" << building->m_SittingRoom <<endl;
	cout << "好朋友全局函数 正在访问:" << building->m_bedRoom <<endl;//出错
}

4.5 运算符重载

对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
1.不要滥用运算符。
2.对于内置的数据类型的表达式的运算符是不可能改变的(自定义类型可以,但int或double之类的不可以)。

4.5.1 加号运算符重载

实现两个【自定义】数据类型相加。

class Person{
public:
	//1.成员函数重载+
	Person operator+(Person &p){
		Person temp;//Person p3 = p1.operator+(p2);本质
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}
	int m_A;
	int m_B;
};
//2.全局函数重载+
Person operator+(Person &p1, Person &p2){
	Person temp;//Person p3 = operator+(p1, p2);本质
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
//函数重载
Person operator+(Person &p1, int num){
	Person temp;//Person p3 = operator+(p1, num);本质
	temp.m_A = p1.m_A + num;
	temp.m_B = p1.m_B + num;
	return temp;
}
void test(){
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;
	Person p2;
	p2.m_A = 10;
	p2.m_B = 10;
	Person p3 = p1 + p2;//简化形式
	cout << "p3 = " << p3.m_A << endl;//20
	cout << "p3 = " << p3.m_B << endl;//20
}

4.5.2 左移运算符重载

可以输出自定义的数据类型。

class Person{
public:
	void operator<<(cout){}//p.operator<<(cout) 简化为 p<
	//所以不会用成员函数来重载
	int m_A;
	int m_B;
};
ostream &operator<<(ostream &cout, Person p){//operator(cout, p)简化为cout<
	cout << "m_A = " << p.m_A << "m_B = " << p.m_B;
	return cout;
}//ostream只能有一个,所以加&
void test(){
	Person p;
	p.m_A = 10;
	p.m_B = 10;
	cout << p.m_A << endl;
}

4.5.3 递增运算符重载

实现自己的整型数据。

//自定义整型
class MyInteger{
	friend ostream &operator<<(ostream &cout, MyInteger myint);
public:
	MyInteger(){
		m_Num = 0;
	}
	//重载前置++运算符
	MyInteger &operator++(){//返回引用是为了一直对一个数据进行递增操作
		m_Num++;//先++运算,再返回自身
		return *this;
	}
	//重载后置++运算符,temp是局部变量,若返回引用后续会非法操作
	MyInteger operator++(int){//int:占位参数,用于区分前置后置递增
		MyInteger temp = *this;//先记录当前结果,再递增,最后将记录结果返回
		m_Num++;
		return temp;
	}
private:
	int m_Num;
};
ostream &operator<<(ostream &cout, MyInteger myint){
	cout << myint.m_Num;
	return cout;
}
void test(){
	MyInteger myint;//自定义类型,需要重载 左移运算符
	cout << ++(++myint) << endl;
	cout << myint << endl;
}
void test2(){
	MyInteger myint;
	cont << myint++ << endl;
	cout << myint << endl;
}

4.5.4 赋值运算符重载

C++编译器至少给一个类添加4个函数:
默认构造函数,析构函数,拷贝构造函数。
赋值运算符operator=,对属性进行值拷贝

class Person{
public:
	Person(int age){
		m_Age = new int(age);//开辟到堆区,手动释放
	}
	~Person(){
		if(m_Age !=NULL){
			delete m_Age;
			m_Age = NULL;
		}
	}
	int* m_Age;
};
void test(){
	Person p1(18);
	cout << "p1的年龄:" << *p1.m_Age << endl;//18
	Person p2(20);
	cout << "p2的年龄:" << *p2.m_Age << endl;//20
	p2 = p1;//赋值操作
	cout << "p2的年龄:" << *p2.m_Age << endl;//18
}

C++进阶知识_第6张图片

//重载 赋值运算符,在class Person中
Person& operator=(Person &p){
	//应该先判断是否有属性在堆区,如果有,先释放干净,然后再深拷贝
	if(m_Age != NULL){
		delete m_Age;
		m_Age = NULL;
	}
	m_Age = new int(*p.m_Age);//深拷贝
	return *this;//返回对象本身
}

4.5.5 关系运算符重载

可以让两个自定义类型对象进行对比操作。

class Person{
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;
		}
		else{
			return false;
		}
	}
	string m_Name;
	int m_Age;
};
void test(){
	Person p1("Tom", 18);
	Person p1("Tom", 18);
	if(p1 == p2){
		cout << "相等" << endl;
	}
	else{
		cout << "不相等" << endl;
	}
}

4.5.6 函数调用运算符重载

函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数。

class MyPrint{
public:
	//重载函数调用运算符
	void operator()(string test){
		cout << test << endl;
	}
};
void test(){
	MyPrint myPrint;
	myPrint("hello world");//由于重载后使用的方式非常像函数的调用,因此成为仿函数
}

仿函数没有固定写法,非常灵活。

class MyAdd{
public:
	int operator()(int num1, int num2){
		return num1 + num2;
	}
};
void test(){
	MyAdd myAdd;
	int ret = myAdd(100,100);
	//匿名函数对象
	cout << MyAdd()(100,100) << endl;
}

4.6 继承

继承,是面向对象三大特性之一
减少重复代码
class Java:public BasePage(子类/派生类:父类/基类)

//普通页面实现
class Java{
public:
	void header(){
		cout << "首页、公开课、登录...(公共头部)" << endl;
	}
	void footer(){
		cout << "帮助中心、交流合作...(公共底部)" << endl;
	}
	void left(){
		cout << "Java、Python...(公共分类列表)" << endl;
	}
	void content(){
		cout << "Java学科视频" << endl;
	}
};
//不同页面的内容只有content不同,重复代码过多。
void test(){
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
}
class BasePage{//继承
	public:
	void header(){
		cout << "首页、公开课、登录...(公共头部)" << endl;
	}
	void footer(){
		cout << "帮助中心、交流合作...(公共底部)" << endl;
	}
	void left(){
		cout << "Java、Python...(公共分类列表)" << endl;
	}
};
class Java:public BasePage{
public:
	void content(){
		cout << "Java学科视频" << endl;
	}
};

4.6.1 继承方式

公共继承,保护继承,私有继承
C++进阶知识_第7张图片

4.6.2 继承中的对象模型

C++进阶知识_第8张图片

利用【开发人员命令提示工具】查看对象模型:
跳转盘符 F:
跳转文件路径 cd 具体路径
查看命名 cl /d1 reportSingleClassLayout类名 文件名(Tab自动补齐文件名)

4.6.3 继承中的构造和析构函数

构造父类–>构造子类;析构则相反
创建子类对象时,父类对象也会被创建。

4.6.4 继承同名成员处理方式

父类与子类 出现同名的成员:
子类:直接访问;父类:加作用域
若子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数

非静态:

class Base{
public:
	void func(){
		cout << "BASE" << endl;
	}
	void func(int a){
		cout << "base" << endl;
	}
};
class Son:public Base{
public:
	void func(){
		cout << "SON" << endl;
	}
};
void test(){
	Son s;
	s.func();//SON
	s.Base::func();//BASE
	//s.func(100);//出错
	s.Base::func(100);
}

静态成员:与非静态一样
通过【类名】和【对象】访问
类名:Son::Base::m_A(通过类名的方式,访问父类作用域下的m_A)

4.6.5 多继承语法

一个类继承多个类
class 子类:继承方式 父类1,继承方式 父类2,…
多继承可能会引发父类中有同名成员出现,需要加作用域区分
实际开发中不建议用多继承

class Base1{ };
class Base2{ };
class Son:public Base1,public Base2{ };

4.6.6 菱形继承

菱形继承/钻石继承:
两个派生类继承同一个基类,又有某个类同时继承这个派生类。

class Animal{
public:
	int m_Age;
};
class Sheep:public Animal{};
class Tuo:public Animal{};
class YT:public Sheep,public Tuo{};
void test(){
	YT st;
	//st.m_Age = 18;//出错,二义性
	st.Sheep::m_Age = 28;
	st.Tuo::m_Age = 28;//数据有一份即可,但现在有两份
}

利用【虚继承】解决菱形继承的问题【virtual】,Animal类为虚基类。

class Sheep:virtual public Animal{};
class Tuo:virtual public Animal{};
cout << st.age << endl;

4.6.7 子类调用父类的构造函数

在函数调用后面 + “:父类函数名”,即完成了该函数的调用,再写自己的函数即可;

class Base {
	...
public:
	Base(int x, int y) {
    	this->x = x;
    	this->y = y;
	}
    ...
};
class Sub : public Base {
private:
	int z;

public:
	Sub(int x, int y, int z) :Base(x,y ) {//父类函数的调用,xyz的有参构造 
		this->z=z;
	}

    int getZ() {
        return z;
    }

    int calculate() {
        return Base::getX() * Base::getY() * this->getZ();//父类函数的调用 + 作用域
    }
};

4.7 多态

面向对象的三大特性之一。

静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态(有继承关系;子类重写父类函数)
区别:
静态多态的函数地址早绑定,编译阶段确定函数地址
动态多态的函数地址晚绑定,运行阶段确定函数地址

class Animal{
public:
	void speak(){
		cout << "动物在说话" << endl;
	}
};
class Cat:public Animal{
public:
	void speak(){
		cout << "小猫在说话" << endl;
	}
};
void doSpeak(Animal &anmial){//使用:父类的指针或引用 执行子类对象
	animal.speak();
}
void test(){
	Cat cat;
	doSpeak(cat);
}

运行结果:动物在说话。doSpeak,地址早绑定,在编译阶段确定地址。如果想执行猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定。

//虚函数
virtual void speak(){};//加在父类中,小猫在说话

C++进阶知识_第9张图片

4.7.1 案例一:计算器类

普通写法

class Calculator{
public:
	int getResult(string oper){
		if(oper == "+"){
			return m_Num1 + m_Num2;
		}
		else if(oper == "-"){
			return m_Num1 - m_Num2;
		}
		else if(oper == "*"){
			return m_Num1 * m_Num2;
		}//若想扩展新的功能,需要修改源码
	}
	int m_Num1;
	int m_Num2;
};
void test(){
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 10;
	cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+") << endl;
}

多态:代码量变大,但组织结构清晰,可读性强,对于前期和后期的扩展和维护性高

class AbstractCalculator{//计算器抽象类
public:
	int getResult(){
		return 0;
	}
	int m_Num1;
	int m_Num2;
};
class AddCalculator:public AbstractCalculator{
public:
	virtual int getResult(){
		return m_Num1 + m_Num2;
	}
};
class SubCalculator:public AbstractCalculator{
public:
	int getResult(){
		return m_Num1 - m_Num2;
	}
};
class MulCalculator: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;
}

4.7.2 纯虚函数和抽象类

通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为【纯虚函数】。

virtual 返回值类型 函数名(参数列表)= 0;
当类中有了纯虚函数,这个类也称为抽象类
特点:
1.无法实例化对象(Base b;创建对象)
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类

4.7.3 案例二:制作饮品

class AbstractDrinking{
public:
	virtual void Boil() = 0;
	virtual void Brew() = 0;
	virtual void PourInCup() = 0;
	virtual void PutSomething() = 0;
	void makeDrink(){
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};
class Coffee:public AbstractDrinking{
public:
	virtual void Boil(){
		cout << "煮水" << endl;
	}
	virtual void Brew(){
		cout << "冲泡" << endl;
	}
	virtual void PourInCup(){
		cout << "倒入" << endl;
	}
	virtual void PutSomething(){
		cout << "加入" << endl;
	}
}; 
void doWork(AbstractDrinking *abs){
	abs->makeDrink();//一个接口多种形态
	delete abs;
}
void test(){
	doWork(new Coffee);
}

4.7.4 虚析构和纯虚析构

多态使用时,若子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。因此,要将父类中的析构函数改为虚析构或纯虚析构。

共性:
可以解决父类指针释放子类对象
都要有具体的函数实现
区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构–virtual ~类名(){ }
纯虚析构–virtual ~类名() = 0; 类名::~类名(){ }需要声明和实现

4.7.5 案例三:电脑组装

案例描述:电脑主要组成部件为CPU,显卡,内存条。将每个零件封装出抽象基类,并提供不同放入厂商生产不同的零件。创建电脑类提供让电脑工作的函数,并调用每个零件工作的接口。测试时组装三台不同的电脑进行工作。

//零件类
class CPU{//抽象CPU类
public:
	virtual void calculate() = 0;//抽象计算函数
};
class VideoCard{//抽象显卡类
public:
	virtual void display() = 0;//抽象显示函数
};
class Memory{//抽象内存条类
public:
	virtual void storage() = 0;//抽象存储函数
};
//电脑类
Class Computer{
public:
	Computer(CPU *cpu, VideoCard *vc, Memory *mem){
		m_cpu = cpu;
		m_vc = vc;
		m_mem = mem;
	}
	void work(){//提供工作的函数
		m_cpu->calculate();//让零件工作起来,调用接口
		m_vc->display();
		m_mem->storage();
	}
	~Computer(){
		if(m_cpu !=NULL){
			delete m_cpu;
			m_cpu = NULL;
		}
		if(m_vc !=NULL){
			delete m_vc;
			m_vc = NULL;
		}
		if(m_mem !=NULL){
			delete m_mem;
			m_mem = NULL;
		}
	}
private:
	CPU *m_cpu;//CPU的零件指针
	VideoCard *m_vc;//显卡零件指针
	Memory *m_mem;//内存条的零件指针
};
//具体厂商
class IntelCPU:public CPU{
	virtual void calculate(){
		cout << "Intel的CPU开始计算了" << endl;
	}
};
class IntelVideoCard:public VideoCard{
	virtual void display(){
		cout << "Intel的显卡开始显示了" << endl;
	}
};
class IntelMemory:public Memory{
	virtual void storage(){
		cout << "Intel的内存条开始存储了" << endl;
	}
};
class LenovoCPU:public CPU{
	virtual void calculate(){
		cout << "Lenovo的CPU开始计算了" << endl;
	}
};
class LenovoVideoCard:public VideoCard{
	virtual void display(){
		cout << "Lenovo的显卡开始显示了" << endl;
	}
};
class LenovoMemory:public Memory{
	virtual void storage(){
		cout << "Lenovo的内存条开始存储了" << endl;
	}
};
void test(){
	CPU *intelCPU = new IntelCPU;
	VideoCard *intelCard = new IntelVideoCard;
	Memory *intelMem = new IntelMemory;

	Computer *computer1 = new Computer(*intelCPU, *intelCard, *intelMem);
	computer1->work();
	delete computer1;//第一台电脑
	//第二台电脑组装
	Computer *computer2 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);
	computer2->work();
	delete computer2;
}

5.文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放。通过文件可以将数据持久化。

头文件:
二种类型:
1.文本文件:以文本的ASCII码形式存储在计算机中
2.二进制文件:以文本的二进制形式存储在计算机中,用户一般不能直接读懂
操作文件的三大类:
1.ofstream:写操作
2.ifstream:读操作
3.fstream:读写操作

5.1 文本文件

写文件步骤:
1.包含头文件:#include
2.创建流对象:ofstream ofs;
3.打开文件:ofs.open(“文件路径”,打开方式);//不指定路径,写在项目同一路径下
4.写数据:ofs << “写入的数据”;
5.关闭文件:ofs.close();
2和3可合并:ofstream ofs(“文件路径”,打开方式);

打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文加
ios::trunc 如果文件存在先删除,再创建
ios::binary 二进制方式

文件打开方式可以配合,利用【|】操作符。
用二进制方式写文件:【ios::binary | ios::out】

读文件步骤:
1.包含头文件:#include
2.创建流对象:ifstream ifs;
3.打开文件:ifs.open(“文件路径”,打开方式);
判断是否打开成:if(!ifs.is_open()){cout << “文件打开失败” << endl;return;}
4.读数据:四种方式读取
5.关闭文件:ifs.close();

//读数据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;
}

5.2 二进制文件

以二进制的方式对文件进行读写操作,打开方式要指定为 ios::binary

二进制写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char* buffer, int len);
参考解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

与文本文件的写数据不同,其余都一样。【调用函数,写数据。Person p = { … };】

读文件 read
函数原型:istream& read(char* buffer, int len);
参考解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

读数据:Person p;调用read函数;cout

你可能感兴趣的:(笔记,c++,java,jvm)