目录
1 内存分区模型
程序运行前
01 代码区
02 全局区
程序运行后
03 栈区
04 堆区
05 new操作符
2 引用
06 引用的基本使用
07 引用注意事项
08 引用做函数参数
09 引用做函数返回值
10 引用的本质
11 常量引用
3 函数提高
12函数默认参数
13 函数占位参数
14 函数重载
4 类和对象
封装
16 封装的意义 属性和行为作为整体
对象特性:对象的初始化和清理
23 构造函数和析构函数
24 构造函数的分类和调用
25 拷贝构造函数调用时机
26 构造函数调用规则
27 深拷贝和浅拷贝
28 初始化列表
29 类对象作为类成员
30 静态成员
对象特性:C++对象模型和this指针
32 成员变量和成员函数分开存储
33 this指针
34 空指针访问成员函数
35 const修饰成员函数
友元
36 全局函数做友元
37 友元类
38 成员函数做友元
C++运算符重载
39 加号运算符重载
40 左移运算符重载
41 递增运算符重载
42 赋值运算符重载
43 关系运算符重载
44 函数调用运算符重载
继承
45 基本语法
46 继承方式
47 继承中的对象模型
48 构造和析构顺序
49 同名成员处理
50 同名静态成员处理
51 多继承语法
52 菱形继承问题以及解决方法
多态
53 多态的基本语法
54 多态的原理剖析
56 纯虚函数和抽象函数
58 虚析构和纯虚析构
5 文件操作
文本文件
143 写文件
144 读文件
二进制文件
145 写文件
146 读文件
程序运行运行前,编译后:
代码区:存放函数的二进制代码,有操作系统进行管理
【共享,只读】
全局区:存放全局变量,静态变量(static),常量(字符串常量和const修饰的全局常量(全局常量))
【在程序结束后由操作系统释放】
//不在全局区的数据:局部变量、const修饰的局部变量(局部常量)
栈区:由编译器自动分配释放,存放函数的形参,局部变量等
//不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
堆区:由程序员分配和释放,若程序员不释放,系统结束时由操作系统回收
//new可以创建堆区数据,返回的是创建的堆区的地址,要用指针接收
int* arr = new int[10];//堆区开辟数组
delete[] arr;//释放数组
作用:给变量起别名
语法:数据类型 &别名=原名;
引用必须初始化
引用初始化后不可以改变
int a = 10;
//int& b;//err,引用必须初始化
int& b = a;
int c = 20;
b = c;//这是赋值操作,不是更改引用,把b的值赋值成c的值20
cout << a << b << c << endl;//20 20 20
引用传递让形参修饰实参,简化指针修改实参
通过引用参数产生的效果同按地址传递是一样的,引用的语法更清楚简单
//引用传递
void mySwap(int &a,int &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 1;
int b = 2;
cout << "a=" << a << endl;//1
cout << "b=" << b << endl;//2
mySwap(a, b);
cout << "swap a=" << a << endl;//2
cout << "swap b=" << b << endl;//1
system("pause");
return 0;
}
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数返回值是引用时,函数调用可以作为左值
//返回局部变量引用,非法操作
int& test01()
{
int a = 10;//局部变量存放在栈区
return a;
}
//返回静态变量引用,可以执行
int& test02()
{
static int a = 10;//静态变量,存放在全局区,系统释放
return a;
}
int main()
{
//1.不要返回局部变量的引用
int& ref = test01();
cout << ref << endl;//第一次正确,因为编译器做了保留
//cout << ref << endl;//第二次错误,因为a的内存已经释放
int& ref2 = test02();
cout << ref2 << endl;//10
cout << ref2 << endl;//10
cout << ref2 << endl;//10
//2.如果函数的返回值是引用,这个函数的调用可以作为左值进行赋值操作
test02() = 1000;
cout << ref2 << endl;//1000
cout << ref2 << endl;//1000
system("pause");
return 0;
}
引用的本质在C++内部实现是一个指针常量
(指针的指向是不可以修改,指针指向的值是可以改动)
用来修饰形参,防止误操作
(在函数形参列表中,可以加const修饰形参,防止形参改变实参)
void showValue(const int& val)//函数中利用常量引用防止误操作修改实参
{
cout << val;
}
int main()
{
//常量引用:用来修饰形参,防止误操作
int a = 10;
//int& ref = 10;//err,引用必须引一块合法的内存空间
const int& ref = 10;//编译器帮修改为int temp=10;int &ref=temp;
//ref = 20;//加入const之后,变为只读,不可修改
int a = 100;
showValue(a);
system("pause");
return 0;
}
语法:返回值类型 函数名(形参=默认值){ }
C++中函数形参列表里可以有占位参数,用来占位,调用函数时必须填补该位置
语法:返回值类型 函数名(数据类型){}
//占位参数还可以有默认参数 (int=10),有默认值时,调用时可以不用传值
函数名可以相同,提高复用性
满足条件:
函数的返回值不可以作为函数重载的条件
注意事项:
面向对象三大特性:封装、继承、多态
万物是对象,有属性和行为
具有相同性质的对象,可以抽象为类
//设计一个圆类,求周长
const double PI = 3.14;
class Circle
{
//访问权限:公共权限
public :
//属性
int m_r;//半径
//行为:获取圆的周长
double calculateZC()
{
return 2 * PI * m_r;
}
};
//实例化(通过一个类,创建一个对象的过程)
Circle c1;//通过圆类,创建具体的圆(对象)
c1.m_r = 10;//给圆的对象的属性赋值
cout << "圆的周长为:" << c1.calculateZC() << endl;
实例化:通过一个类,创建一个对象的过程
类中的属性和行为,统称为成员
类在设计时,可以把属性和行为放在不同的权限下,加以控制
18 三种访问权限
//公共权限 public 成员类内可以访问,类外可以访问
//保护权限 protected 类内可以访问,类外不可以访问 儿子可以访问父亲中的保护内容
//私有权限 private 类内可以访问,类外不可以访问 儿子不可以访问父亲中的私有内容
19 struct和class的区别:
默认的访问权限不同
struct默认权限是 公共 public
class默认权限是 私有 private
20 成员属性设置为私有的优点
//设计立方体类
//创建立方体类
class Cube {
public:
void setL(int l)
{
m_L = l;
}
void setW(int w)
{
m_W = w;
}
void setH(int h)
{
m_H = h;
}
int getL()
{
return m_L ;
}
int getW()
{
return m_W;
}
int getH()
{
return m_H;
}
int calculateS()
{
return 2 * m_L * m_W + 2 * m_H * m_W + 2 * m_L * m_H;
}
int calculateV()
{
return m_L * m_W * m_H;
}
//利用局部函数判断是否相等
bool isSamebyclass(Cube& c)
{
if (getL() == c.getL() && getW() == c.getW() && getH() == c.getH())
return true;
else
return false;
}
private:
int m_L;
int m_W;
int m_H;
};
//利用全局函数判断两个立方体是否相等
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()
{
Cube c1;
c1.setL(10);
c1.setW(10);
c1.setH(10);
cout<<"面积为"<
//判断点和圆的关系
在类中可以让另一个类 作为本类中的成员
可以把一个类拆在不同文件中 .h声明 .cpp实现
每个对象也会有初识设置以及对象销毁前的清理数据的设置
编译器自动调用,完成对象初始化和清理工作
构造函数:类名(){}
析构函数:~类名(){}
构造函数分类:
按照参数:有参构造和无参构造(默认构造)
按照类型:普通构造和拷贝构造
调用方式:
1.括号法2.显示法3.隐式转换法
class Person
{
public:
//无参构造
Person()
{
cout << "Person的无参(默认)构造函数调用" << endl;
}
//有参构造
Person(int a)
{
age = a;
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数
Person(const Person &p)//const+引用方式传入
{
//将传入的人身上的所有属性,拷贝到当前对象身上
age = p.age;
cout << "Person的拷贝构造函数调用" << endl;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
int age;
};
void test01()
{
//1.括号法
Person p;//默认构造函数的调用,调用默认构造函数的时候,不要加()
Person p1(10);
Person p2(p1);//拷贝构造函数调用
cout << "p2的年龄为" << p1.age << endl;
cout << "p2的年龄为" << p2.age << endl;
//注意:调用默认构造函数的时候,不要加()
//Person p1();//err
//编译器会认为是一个函数的声明,不会认为在创建对象
2.显示法
Person p1;
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10);//匿名对象,特点:当前行执行结束后,系统会立即回收掉匿名对象
注意:不要利用拷贝构造函数 初始化匿名对象
Person(p3);编译器会认为在对象声明==Person p3;
3.隐式转换法
Person p4 = 10;//相当于 Person p4=Person(10);
Person p5 = p4;
}
int main()
{
test01();
system("pause");
return 0;
}
拷贝构造函数调用时机三种情况:
class Person
{
public:
Person()
{
cout << "默认构造函数的调用" << endl;
}
Person(int age)
{
m_Age = age;
cout << "有参构造函数的调用" << endl;
}
Person(const Person &p)
{
m_Age = p.m_Age;
cout << "拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
int m_Age;
};
//1.使用一个已经创建完毕的对象来初始化一个对象
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年龄为:" << p2.m_Age << endl;
}
//2.值传递的方式给函数参数传值
void doWork(Person p)
{
}
void test02()
{
Person p;
doWork(p);
}
//3.以值方式返回局部对象
Person doWork2()
{
Person p1;
return p1;
}
void test03()
{
Person p = doWork2();
}
int main()
{
//test01();
//test02();
test03();
system("pause");
return 0;
}
调用规则:
如果我们写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造
如果我们写了拷贝构造函数,编译器就不再提供其他普通构造函数
浅拷贝:简单的复制拷贝操作
浅拷贝带来的问题就是堆区的内存重复释放
深拷贝:在堆区重新申请空间,进行拷贝操作
//如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age,int height)
{
m_Age = age;
m_Height=new int(height);
cout << "Person的有参构造函数调用" << endl;
}
//自己实现拷贝构造函数,解决浅拷贝带来的问题
Person(const Person& p)
{
cout << "Person拷贝构造函数调用" << endl;
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;
}
cout << "Person的析构函数调用" << endl;
}
int m_Age;//年龄
int* m_Height;
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为:" << p1.m_Age <<"身高为:"<<*p1.m_Height<< endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.m_Age << "身高为:" << *p2.m_Height << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
//初始化列表
Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c)
{
}
//属性赋初值
Person p(31,20,10);
类中的成员可以是另一个类的对象,我们称该成员为对象成员
构造顺序与析构顺序相反
class Phone
{
public:
Phone(string pName)
{
m_PName = pName;
cout << "phone的构造函数调用" << endl;
}
~Phone()
{
cout << "phone的析构函数调用" << endl;
}
string m_PName;
};
class Person
{
public:
Person(string name, string pName):m_Name(name),m_phone(pName)
{
cout << "person的构造函数调用" << endl;
}
~Person()
{
cout << "person的析构函数调用" << endl;
}
string m_Name;
Phone m_phone;
};
静态成员变量:
class Person
{
public:
static int m_A;//类内声明
};
int Person::m_A = 100;//类外初始化
void test01()
{
Person p;
cout << p.m_A << endl;//100
Person p2;
p2.m_A = 200;//所有对象共享同一份数据
cout << p.m_A << endl;//200
}
静态成员变量,不属于某个对象上,所有对象都共享同一份数据
有两种访问方式:
Person p;
cout<
静态成员变量有访问权限,类外访问不到私有静态成员变量
31 静态成员函数
//两种访问方式:通过对象,通过类名
//静态成员函数也是有访问权限的,类外访问不到私有的静态成员函数
class Person
{
public:
//静态成员函数
static void func()
{
m_A = 100;//静态成员函数可以访问静态成员变量,共享的
//m_B = 200;//静态成员函数不可以访问非静态成员变量,因为无法区分到底是哪个对象的m_B
cout << "ststic void func()的调用" << endl;
}
static int m_A;//类内声明,类外初始化
int m_B;//非静态成员变量
};
int Person::m_A=0;//类外初始化
void test01()
{
//静态成员函数有两种访问方式
//通过对象访问
Person p;
p.func();
//通过类名访问,因为不属于某一个对象上,不创建对象也可以访问
Person::func();
}
只有非静态成员变量 属于对象上的数据
空对象占用内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
class Person
{
int m_A;//非静态成员变量 属于对象上的数据
static int m_B;//静态成员变量 不属于类的对象上
void func(){}//非静态成员函数 不属于类的对象上
static void func2() {}//静态成员函数 不属于类的对象上
};
int Person::m_B=0;
void test02()
{
Person p;
cout << "sizeofp=" << sizeof(p) << endl;//4
}
this指针:指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用
用途:
//当形参和成员变量同名时,可用this指针来区分
//在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
Person(int age)
{
//this指针指向被调用的成员函数所属的对象
this->age = age;
}
//如果用Person返回就是用值的方式返回,会用到拷贝构造函数,Person返回的和自身是不一样的数据
//用引用方式返回,不会创建新的对象,会一直返回P2本体
Person& PersonAddAge(Person& p)//Person& 不返回引用就会通过拷贝构造创建新对象,就不是原来的对象了
{
this->age += p.age;
//this是指向p2的指针,*this指向的就是p2这个对象的本体
return *this;
}
int age;
};
void test02()
{
Person p1(10);
Person p2(10);
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);//链式编程思想
cout << "p2的年龄为:" << p2.age << endl;
}
void test01()
{
Person p1(18);
cout << "p1的年龄为:" << p1.age << endl;
}
test01();
test02();
C++中允许空指针调用成员函数,也要注意有没有用到this指针
//如果用到this指针,需要判断保证代码的健壮性
常函数
成员函数后加const称为常函数
//常函数内不可以修改成员属性(指针指向的值不能修改)
//成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象
声明对象前加const称为常对象
//常对象只能调用常函数
class Building
{
friend void goodgay(Building* builiding);//goodgay是Building好朋友,可以访问Builing中私有成员
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
//全局函数
void goodgay(Building *builiding)
{
cout << "全局函数正在访问:" << builiding->m_BedRoom << endl;
cout << "全局函数正在访问:" << builiding->m_SittingRoom << endl;
}
friend class goodgay;//goodgay类是本类的好朋友,可以访问本类中私有的成员
friend void Goodgay::visit01();//goodgay类下的visit成员函数作为本类的好朋友,可以访问私有成员
运算符重载:对已有运算符重新进行定义,赋予另一种功能,以适应不同的数据类型
作用:实现两个自定义数据类型相加的运算
//对于内置的数据类型表达式运算符是不可能改变的
//不要滥用运算符重载
class Person
{
public:
//1.成员函数重载加号: Person p3=p1+p2;成员函数的本质是 Person p3=p1.operator+(p2);
/*Person operator+(Person& p)
{
Person temp;
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 p3=p1+p2;全局函数的本质是 Person p3= operator+(p1,p2);
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;
}
//3.运算符重载也可以发生函数重载
Person operator+(Person& p1, int num)
{
Person temp;
temp.m_A = p1.m_A +num;
temp.m_B = p1.m_B +num;
return temp;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
//运算符重载也可以发生函数重载
Person p4 = p1 + 100;
cout << "p3.m_A=" << p3.m_A << endl;
cout << "p3.m_B=" << p3.m_B << endl;
cout << "p4.m_A=" << p4.m_A << endl;
cout << "p4.m_B=" << p4.m_B << endl;
}
输出左移运算符配合友元可以实现输出自定义数据类型
class Person
{
friend ostream& operator<<(ostream& cout, Person& p);
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
private:
//不会利用成员函数重载左移运算符<<,因为无法实现cout在左侧
int m_A;
int m_B;
};
//只能利用全局函数重载左移运算符
ostream& operator<<(ostream &cout,Person &p)//本质是operator<<(cout,p) 简化cout<
通过重载递增运算符,实现自己的整形数据
前置递增返回参数,后置递增返回值
//++
//自定义整形
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
//重载++运算符
//1.重载前置++
MyInteger& operator++()//返回引用是为了一直对一个数据进行递增操作
{
//先进行++运算
m_Num++;
//再将自身返回
return *this;
}
//2.重载后置++ //返回的是值,因为返回的是局部变量
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 << endl;
return cout;
}
void test01()
{
MyInteger myint;
cout << ++(++myint) << endl;
}
void test02()
{
MyInteger myint;
cout << myint++ << endl;
cout << myint << endl;
}
test01();
test02();
Person& operator=(Person& p)
{
//编译器提供的是浅拷贝
//m_Age=p.m_Age;
//应该先判断有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
m_Age=new int(*p.m_Age);//深拷贝
return *this;
}
//重载==
bool operator==(Person& p)
{
if (this->m_Age == p.m_Age && this->m_Name == p.m_Name)
{
return true;
}
else false;
}
//重载!=
bool operator!=(Person& p)
{
if (this->m_Age == p.m_Age && this->m_Name == p.m_Name)
{
return false;
}
else true;
}
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定的写法,非常灵活
//打印输出
class MyPrint
{
public:
//重载函数调用运算符
void operator()(string test)
{
cout << test << endl;
}
};
void test01()
{
MyPrint myPrint;
myPrint("hello");//由于使用起来类似于函数调用,称为仿函数
}
//仿函数非常灵活,没有固定的写法
//加法类
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd myadd;
int ret = myadd(100, 200);
cout << ret << endl;
//匿名函数对象 //MyAdd()
cout << MyAdd()(100, 200) << endl;
}
int main()
{
test01();
test02();
return 0;
}
继承的好处:减少重复代码
语法: class 子类:继承方式 父类
class A : publib B
A类,子类也称为派生类
B类,父类也称为基类
派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员
从基类继承过来的表现其共性,而新增的成员体现其个性
继承的三种方式:
class Base
{
public:
int a;
protected:
int b;
private:
int c;
};
class Son :public Base
{
public:
int d;
};
void test01()
{
//父类中所有静态成员属性都会被子类继承下去
//父类中私有成员属性是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了
cout << "size of Son=" << sizeof(Son) << endl;//16
}
利用开发人员命令提示工具(Developer Command Prompt for VS 2019)查看对象模型
//跳转盘符 F:
//跳转文件路径 cd 具体路径下
//查看命令 cl /d1 reportSingleClassLayout类名 文件名
F:\C++-code\0421继承>cl /d1 reportSingleClassLayoutSon "129对象基本模型.cpp"
子类继承父类后,当创建子类对象,也会调用父类的构造函数
继承中的构造和析构的顺序:
先构造父类,再构造子类;析构顺序与构造顺序相反
当子类与父类出现同名的成员,如何通过子类对象,访问到其中的同名数据:
1.访问子类同名成员,直接访问即可
2.访问父类同名成员,需要加作用域
Son s;
cout << "Son的m_A=" << s.m_A << endl;
cout << "Base的m_A=" << s.Base::m_A << endl;
//如果通过子类对象,访问到父类中的同名成员,需要加作用域
s.func();//直接调用的是子类中的同名函数
s.Base::func();//调用的是父类的
同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(通过对象和通过类名)
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base 的func的调用" << endl;
}
};
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "Son 的func的调用" << endl;
}
};
int Son::m_A = 200;
void test01()
{
//通过对象访问
Son s;
cout << "Son的m_A=" << s.m_A << endl;
cout << "Base的m_A=" << s.Base::m_A << endl;
//通过类名访问
cout << Son::m_A << endl;
cout << Base::m_A << endl;
cout << Son::Base::m_A << endl;
//第一个::代表通过类名方式访问,第二个::代表访问父类作用域下的m_A
}
void test02()
{
//通过对象访问
Son s;
s.func();
s.Base::func();
//通过类名访问
Son::func();
Son::Base::func();
}
C++允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2
//多继承可能会引发父类中有同名成员出现,需要加作用域
//开发中不建议用多继承
//子类继承两个父类
class Son :public Base1, public Base2
//父类中出现同名成员时,需要加作用域区分
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
菱形继承、钻石继承:
动态多态满足条件:
动态多态使用:父类的指针或引用 指向子类对象
//示例:多态原理剖析
//动物类
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
//猫类
class Cat :public Animal
{
public:
//虚函数
void speak()
{
cout << "小猫在说话" << endl;
}
};
//狗类
class Dog :public Animal
{
public:
//虚函数
void speak()
{
cout << "小狗在说话" << endl;
}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展及维护
在真实的开发中,提倡 开闭原则
开闭原则:对扩展进行开发,对修改进行关闭
当类中有了纯虚函数,这个类就是抽象类
//抽象类无法实例化对象
//子类必须重写抽象类中的纯虚函数,否则也属于抽象类,无法实例化对象
父类指针在析构的时候,不会调用子类中析构函数,导致子类中如果有堆区属性,出现内存泄漏
虚析构和纯虚析构的共性:
虚析构和纯虚析构的区别:
虚析构:virtual ~类名(){}
纯虚析构:virtual ~类名() = 0;//声明
类名 :: ~类名() {} //具体实现
总结:
写文件步骤:
ofstream ofs;
ofs.open("text.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs.close();
ifstream ifs;
ifs.open("text.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;
}
以二进制的方式对文件进行读写操作
打开方式要指定为ios::binary
ofstream ofs;
ofs.open("Person.txt", ios::out | ios::binary);
Person p = { "张三",18 };
ofs.write((const char *)&p,sizeof(Person));
ofs.close();
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();