目录

  • 1.封装
  • 2.对象的初始化和清理
    • 2.1构造函数的分类
    • 2.2构造函数的使用
    • 2.3拷贝构造函数调用时机
    • 2.4构造函数调用规则
    • 2.5深拷贝与浅拷贝(未看)
    • 2.6初始化列表
    • 2.7类作为成员
    • 2.8静态成员
  • 3. C++对象模型和this指针
    • 3.1成员变量和成员函数分开存储
    • 3.2 this指针
    • 3.3空指针访问成员函数
    • 3.4 const修饰成员函数
  • 4.友元friend
    • 4.1 全局函数做友元
    • 4.2 类做友元
    • 4.3 成员函数做友元
  • 5.运算符重载
    • 5.1 加号运算符重载
    • 5.2 左移运算符重载
  • 6.继承
    • 6.1 继承的基本语法
    • 6.2 继承方式
    • 6.3 继承中的对象模型
    • 6.4 继承中构造和析构顺序
    • 6.5 继承同名成员处理方式
    • 6.6 继承同名静态成员处理方式
    • 6.7 多继承语法
    • 6.8 菱形继承
  • 7.多态
    • 7.1多态的基本语法
    • 7.2多态的深入剖析
    • 7.3 多态案例:计算机类
    • 7.4 纯虚函数和抽象类
    • 7.5 虚析构和纯虚析构
  • 8. struct和class区别
  • 9.文件操作
    • 9.1文本文件
      • 9.1.1写文件
      • 9.1.2读文件
    • 9.2二进制文件
      • 9.2.1写文件
      • 9.2.2读文件

C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为

1.封装

  • 将属性和行为作为一个整体,表现生活中的事物
  • 将属性和行为加以权限控制

语法:class 类名{ 访问权限: 属性 / 行为 };

const double pi=3.14;
class circle
{public://访问权限  公共的权限int r;void c(){cout<<"圆周长为:" << 2 * pi * this->r<<endl;}
};
void main()
{circle c;//与Java不同c.r = 4;c.c();
}

注意public权限与实例化

类在设计时,可以把属性和行为放在不同的权限下,加以控制。
访问权限有三种:

  • public:公共权限,类内可以访问 类外可以访问
  • protected:保护权限,类内可以访问 类外不可以访问,子类可以访问
  • 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;}
};
void main()
{Person p;p.m_Name = "李四";//p.m_Car = "奔驰";  //保护权限类外访问不到//p.m_Password = 123; //私有权限类外访问不到
}

将所有成员属性设置为私有

  • 可以自己控制读写权限
  • 对于写权限,我们可以检测数据的有效性
class Person {public://姓名设置可读可写void setName(string name) {m_Name = name;}string getName(){return m_Name;}//获取年龄 int getAge() {return m_Age;}//设置年龄void setAge(int age) {if (age < 0 || age > 150) {cout << "你个老妖精!" << endl;return;}m_Age = age;}//情人设置为只写void setLover(string lover) {m_Lover = lover;}private:string m_Name; //可读可写  姓名int m_Age; //只读  年龄string m_Lover; //只写  情人
};
void main()
{Person p;//姓名设置p.setName("张三");cout << "姓名: " << p.getName() << endl;//年龄设置p.setAge(50);cout << "年龄: " << p.getAge() << endl;//情人设置p.setLover("苍井");//cout << "情人: " << p.m_Lover << endl;//只写属性,不可以读取
}

2.对象的初始化和清理

使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供,但是编译器提供的构造函数和析构函数是空实现。

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

构造函数语法:类名(){}

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

析构函数语法: ~类名(){}

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

2.1构造函数的分类

  • 按参数分为: 有参构造和无参构造
  • 按类型分为: 普通构造和拷贝构造

拷贝构造函数语法

Person(const Person& p)
{//拷贝一个personA出来,复制到要创建的B身上age = p.age;cout << "拷贝构造函数!" << endl;
}

构造函数:

class Person {public://无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int a) {age = a;cout << "有参构造函数!" << endl;}//拷贝构造函数Person(const Person& p) {//拷贝一个personA出来,复制到要创建的B身上age = p.age;cout << "拷贝构造函数!" << endl;}

2.2构造函数的使用

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

Person p1(10);调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明person p1();

显式法:


Person(10)单独写就是匿名对象 当前行结束之后,马上析构回收匿名对象
不要利用拷贝构造函数初始化一个匿名对象person(P3);编译器会认为是`person(P3)===person P3对象实例化

隐式转换法:

2.3拷贝构造函数调用时机

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象
class Person {public:Person() {cout << "无参构造函数!" << endl;mAge = 0;}Person(int age) {cout << "有参构造函数!" << endl;mAge = age;}Person(const Person& p) {cout << "拷贝构造函数!" << endl;mAge = p.mAge;}//析构函数在释放内存之前调用~Person() {cout << "析构函数!" << endl;}
public:int mAge;
};//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {Person man(100); //p对象已经创建完毕Person newman(man); //调用拷贝构造函数Person newman2 = man; //拷贝构造//Person newman3;//newman3 = man; //不是调用拷贝构造函数,赋值操作
}//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {Person p; //无参构造函数doWork(p);
}//3. 以值方式返回局部对象
Person doWork2()
{Person p1;cout << (int *)&p1 << endl;return p1;
}void test03()
{Person p = doWork2();cout << (int *)&p << endl;
}void main() {  //test01();//test02();test03();
}

2.4构造函数调用规则

c++编译器至少给一个类添加3个函数:

1.默认构造函数(无参,函数体为)
2.默认析构函数(无参,函数体为)
3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
class Person {public://无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int a) {age = a;cout << "有参构造函数!" << endl;}//拷贝构造函数Person(const Person& p) {age = p.age;cout << "拷贝构造函数!" << endl;}//析构函数~Person() {cout << "析构函数!" << endl;}
public:int age;
};void test01()
{Person p1(18);//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作Person p2(p1);cout << "p2的年龄为: " << p2.age << endl;
}void test02()
{//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造Person p1; //此时如果用户自己没有提供默认构造,会出错Person p2(10); //用户提供的有参Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供//如果用户提供拷贝构造,编译器不会提供其他构造函数Person p4; //此时如果用户自己没有提供默认构造,会出错Person p5(10); //此时如果用户自己没有提供有参,会出错Person p6(p5); //用户自己提供拷贝构造
}void main() {test01();
}

2.5深拷贝与浅拷贝(未看)

P110

2.6初始化列表

语法:构造函数():属性1(值1),属性2(值2)... {}
类里有ABC三个变量,构造函数:

初始化列表:注意冒号位置

2.7类作为成员

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

class A {}
class B
{A a;
}
  • 当类中成员是其他类对象时,我们称该成员为 对象成员
  • 构造的顺序是 :先调用对象成员的构造,再调用本类构造
  • 析构顺序与构造相反

2.8静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

  • 静态成员变量
    所有对象共享同一份数据
    在编译阶段分配内存
    类内声明,类外初始化
  • 静态成员函数
    所有对象共享同一个函数
    静态成员函数只能访问静态成员变量

静态成员变量不属于某个对象,所有对象都共享同一份数据,因此静态成员变量有两种访问方式

  • 通过对象进行访问
  • 通过类名进行访问

静态函数有两种访问方式

  • 通过对象进行访问
  • 通过类名进行访问
  • 静态成员函数可以访问静态成员变量,不可以访问非静态成员变量
    静态成员函数也有访问权限的
class Person
{   public:static int m_A; //静态成员变量private:static int m_B; //静态成员变量也是有访问权限的
};
int Person::m_A = 10;
int Person::m_B = 10;
void test01()
{//静态成员变量两种访问方式//1、通过对象Person p1;p1.m_A = 100;cout << "p1.m_A = " << p1.m_A << endl;Person p2;p2.m_A = 200;cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据cout << "p2.m_A = " << p2.m_A << endl;//2、通过类名cout << "m_A = " << Person::m_A << endl;//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}int main() {test01();return 0;
}

3. C++对象模型和this指针

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

在C++中,类内的成员变量和成员函数分开存储,只有非静态成员变量(不包括函数)才属于类的对象上


C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置


是4不是5,说明非静态成员变量属于类的对象上,同理可验证静态成员变量不属于类的对象上

3.2 this指针

P115
this指针指向被调用的成员函数所属的对象

this指针的用途:

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

  • 引用方式返回的是本身,值的方式返回的是复制的另一个对象
  • 引用指向本身内存,不用引用就是拷贝了,而拷贝指向另一个内存,不加引用的话返回的就不是p2了,而是别的Person 对象(这个对象值会一直加,最后值是40)
  • 如果不用引用的方式返回,相当于返回与p2不同的另一个Person(只是age都是20),那么后续的加年龄操作与p2就没有关系了
  • this指向p2的指针,而*this指向的就是p2这个对象本体,返回本体要用引用+*this

3.3空指针访问成员函数

类:

调用:

空指针调用成员函数:第一个可以运行,第二个函数程序出错

报错原因:传入的指针里面为空,第二个函数调用了成员属性,可以修改为:为空就直接返回,

3.4 const修饰成员函数

常函数:就是 const修饰的成员函数
语法:void ShowPerson() const {}const加在括号后面

  • 成员函数后加const后我们称为这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改
void ShowPerson() const {mA = 100; //但是this指针指向的对象的数据是可以修改的
  • 隐含在每一个成员函数内部都有一个this指针,mA = 100; 相当于this->mA = 100;this指针的本质是一个指针常量(Person* const this;):指针的指向不可修改(创建对象,调用指针,this指针指向对象),故不可以修改this指向(但是可以修改this指向的值this->mA = 100;):
  • 如果想让this指针指向的值也不可以修改,需要声明常函数
  • 在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
  • const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量。声明:mutable int m_B;
class Person
{public:mutable int m_B; //声明
public:void ShowPerson() const {this->m_B = 100;//修改}
};

常对象:

  • 声明对象前加const称该对象为常对象const Person person;
  • 常对象只能调用常函数,不能调用普通函数
  • 常对象不能修改成员变量的值,但是可以访问,但是常对象可以修改mutable修饰成员变量
void test01() {const Person person; //常量对象  cout << person.m_A << endl;//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
}

4.友元friend

在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术

友元的目的就是让一个函数或者类 访问另一个类中私有成员
友元的三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

4.1 全局函数做友元

只需要把类外的全局函数的声明粘贴到类的首行,加上friend和分号,之后全局函数就能访问私有变量了

class Building
{//告诉编译器goodGay全局函数,是 Building类的好朋友,可以访问类中的私有内容friend void goodGay(Building * building);public:Building(){this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";}public:string m_SittingRoom; //客厅private:string m_BedRoom; //卧室
};void goodGay(Building * building)
//引用或指针都可以传
//目的访问Building 中属性
{cout<<"好基友正在访问:"<<building->m_SittingRoom<< endl;cout << "好基友正在访问:"<<building->m_BedRoom<< endl;
}

4.2 类做友元

一个类可以访问另一个类的私有成员

类外写成员函数:

class Building
{public:Building();
}
Building::Building()
{this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";
}

注意成员属性前面没有类名

class goodGay
{public:goodGay();void visit();
private:Building *building;
};class Building
{//goodGay类可以访问到Building类中私有内容friend class goodGay;
public:Building();
public:string m_SittingRoom; //客厅
private:string m_BedRoom;//卧室
};Building::Building()
{this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";
}goodGay::goodGay()//类外写成员函数
{building = new Building;//创建建筑物对象
}void goodGay::visit()
{cout << "好基友正在访问" << building->m_SittingRoom << endl;cout << "好基友正在访问" << building->m_BedRoom << endl;
}void test01()
{goodGay gg;gg.visit();
}

先创建一个goodGay对象,构造函数里面创建一个building对象,调用了building构造函数,把内部属性赋值,调用visit函数,访问sittingroom属性,卧室属性私有,无法访问
只需要把class class goodGay 放在另一个类首行,加上friend和分号即可

4.3 成员函数做友元

在被访问的类的首行:friend void 访问的类::成员函数();

class goodGay
{public:goodGay(){building = new Building;}void visit(){cout<<"正在访问"<<building->m_SittingRoom<< endl;cout<<"正在访问"<<building->m_BedRoom<< endl;}//只让visit函数作为Building的好朋友,可以访问Building中私有内容void visit2(){cout << "好基友正在访问" << building->m_SittingRoom << endl;//cout << "好基友正在访问" << building->m_BedRoom << endl;}//visit2函数不可以访问Building中私有内容
private:Building *building;
};class Building
{//goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容friend void goodGay::visit();
public:Building();public:string m_SittingRoom; //客厅
private:string m_BedRoom;//卧室
};Building::Building()
{this->m_SittingRoom = "客厅";this->m_BedRoom = "卧室";
}
void test01()
{goodGay  gg;gg.visit();}

5.运算符重载

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

5.1 加号运算符重载

作用:实现两个自定义数据类型相加的运算

定义一个person类,有A、B两属性,实例化2个对象p1,p2。person p3=p1+p2;是让p1和p2的A、B属性分别相加后返回新的对象p3

成员函数:

//成员函数实现 + 号运算符重载
Person operator+(const Person& p) {Person temp;temp.m_A = this->m_A + p.m_A;temp.m_B = this->m_B + p.m_B;return temp;}

本质:Person p3 =p1.operator+(p2);简化为Person p3 =p1 + p2;

全局函数:

//全局函数实现 + 号运算符重载
Person operator+(const Person& p1, const Person& p2) {Person temp(0, 0);temp.m_A = p1.m_A + p2.m_A;temp.m_B = p1.m_B + p2.m_B;return temp;
}

本质:Person p3 =operator+(p1,p2);简化为Person p3 =p1 + p2;

运算符重载,也可以发生函数重载:

//运算符重载 可以发生函数重载
Person operator+(const Person& p2, int val)
{Person temp;temp.m_A = p2.m_A + val;temp.m_B = p2.m_B + val;return temp;
}
  • 对于内置的数据类型的表达式的的运算符是不可能改变的
  • 不要滥用运算符重载

5.2 左移运算符重载

可以输出自定义数据类型:P122-P126

6.继承

定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码

6.1 继承的基本语法

语法:class 子类 : 继承方式 父类
子类:派生类
父类:基类

//父类
class BasePage
{public:void header(){}
};//子类
class SpecialPage: public BasePage
{public:void content(){}
};

派生类中的成员,包含两大部分:

  • 一类是从基类继承过来的,一类是自己增加的成员
  • 从基类继承过过来的表现其共性,而新增的成员体现了其个性

6.2 继承方式

继承方式一共有三种:

  • 公共继承
  • 保护继承
  • 私有继承


如图:
父类:private——>子类什么情况都无法访问,此外

  • 公共继承public:继承后访问权限不变
  • 保护继承protected:都变为保护权限protected
  • 私有继承private:都变为私有权限private

6.3 继承中的对象模型

class Base {public:  int m_A;
protected:int m_B;
private:int m_C;
};
class Son :public Base
{public:int D;
};
void main()
{cout << "son大小:" << sizeof(Son) << endl;
}

结论:父类中所有非静态成员属性都会被子类继承下去, 父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到

PS:利用工具查看:
1.找到开发人员命令提示符

2.右键打开文件夹,复制路径

3.在步骤1的工具里进入类所在的cpp的路径

4.报告单个类的布局
cl /d1 reportSingleClassLayoutXXX(类名) cpp文件名,善用tab键,补全代码,注意是CL 和 D1

父类为Base类,继承了父类的ABC属性,以及自身的D属性

6.4 继承中构造和析构顺序

P48跳过
继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

6.5 继承同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数,如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域

 s.func();//子类func函数s.Base::m_A;//子类访问父类同名属性s.Base::func();//父类func函数s.Base::func(10);//父类func重载函数

总结:

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

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

问题:继承中同名的静态成员在子类对象上如何进行访问?
同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

通过对象访问同名静态函数:

 Son s;s.func();s.Base::func();

通过类名访问同名静态函数:出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问

 Son::func();Son::Base::func();Son::Base::func(100);

通过对象访问同名静态属性:

 Son s;cout << "子类的m_A = " << s.m_A << endl;cout << "父类的m_A = " << s.Base::m_A << endl;

通过类名访问同名静态属性:

 cout << "子类的m_A = " << Son::m_A << endl;cout << "父类的m_A = " << Son::Base::m_A << endl;

第一个::表示通过类名方式访问,第二个::表示访问父类作用域下,类似Son::(Base::m_A)

6.7 多继承语法

C++允许一个类继承多个类
语法: class 子类 :继承方式 父类1 , 继承方式 父类2...

class Son : public Base2, public Base1
{};

C++实际开发中不建议用多继承
多继承容易产生成员同名的情况
通过使用类名作用域可以区分调用哪一个基类的成员

 Son s;cout << s.Base1::m_A << endl;cout << s.Base2::m_A << endl;

6.8 菱形继承

菱形继承(钻石继承):两个派生类继承同一个基类,又有某个类同时继承者两个派生类

  • 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性
  • 羊驼继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以
class Animal
{public:int m_Age;
};

当菱形继承,两个父类拥有相同数据,需要加作用域区分

上面的数据(年龄)只要一份即可,子类继承两份相同的数据,导致资源浪费以及毫无意义

利用虚继承可以解决菱形继承问题:继承前加virtual关键字后,变为虚继承,此时公共的父类Animal称为虚基类

class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};


现在数据都是28了,虚继承之后数据只有1个了,先改为18,再改为28,甚至可以直接通过对象访问

7.多态

7.1多态的基本语法

多态分为两类:

  • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
class animal
{public :void speak(){cout << "动物在说话" << endl;}
};
class cat :public animal
{public:void speak(){cout << "喵~" << endl;}
};
//执行说话的函数
void doSpeak(animal & dongwu)
{dongwu.speak();
}
void main()
{cat  mao;doSpeak(mao);//动物在说话//相当于animal & dongwu=mao//父类的引用指向子类的对象//c++中允许父子之间的类型转换,不需要做强制类型转换//父类的指针或引用可以直接指向子类对象
}

地址早绑定:在编译阶段确定函数地址,doSpeak()函数无论传什么都会走animal里面的成员函数
如果想要执行cat类的函数,函数地址不能提前绑定,需要在运行阶段绑定,需要地址晚绑定

class Animal
{public://Speak函数就是虚函数virtual void speak(){cout << "动物在说话" << endl;}
};

函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了
执行结果为:

动态多态满足条件:

  • 1.有继承关系
  • 2.子类重写父类的虚函数(父类有virtual,子类virtual可写可不写)
  • 3.重写:函数返回值类型、函数名、参数列表完全相同

动态多态的使用:父类的指针或引用,指向父子类对象animal &an=cat

7.2多态的深入剖析

class Animal
{public:void speak(){cout << "动物在说话" << endl;}
};

此时为空类,大小为1

class Animal
{public:virtual void speak(){cout << "动物在说话" << endl;}
};

此时大小为4字节,此时是指针

vfptr:virtual function pointer虚函数指针/表
指针指向虚函数表(vftable),表内记录虚函数的地址&Animal::speak(成员函数的函数地址要加作用域)


class Cat :public Animal
{public:
};

当没有重写只有继承时:

class cat :public animal
{public:virtual void speak(){cout << "喵~" << endl;}
};

当子类重写父类的虚函数时:
子类中的虚函数表内部会替换成子类的虚函数地址
父类中的虚函数表指向不变

当父类的指针或引用指向子类对象时,发生多态:他会从cat的虚函数表中寻找函数,在运行阶段发生动态多态是哪个对象就走哪个虚函数表

animal &ani=cat;
ani.speak();

7.3 多态案例:计算机类

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

常规实现:

//普通实现
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;}//如果要提供新的运算,需要修改源码}
public:int m_Num1;int m_Num2;
};void test01()
{//普通实现测试Calculator c;c.m_Num1 = 10;c.m_Num2 = 10;cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}

多态实现:

//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{public:virtual int getResult(){return 0;}int m_Num1;int m_Num2;
};//加法计算器
class AddCalculator :public AbstractCalculator
{public: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 main()
{//创建加法计算器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;delete abc;//创建乘法计算器abc = new MulCalculator;abc->m_Num1 = 10;abc->m_Num2 = 10;cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;delete abc;
}

销毁释放堆区数据,指针指向没有变

提倡用多态,虽然代码量大

7.4 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名(参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:

  • 只要有一个纯虚函数,这个类称为抽象类
  • 无法实例化对象
  • 抽象类的子列必须重写纯虚函数,否则也属于抽象类
class drink {public:void step1(){cout<<"煮水" << endl;}virtual void step2() = 0;void step3(){cout << "倒入杯中" << endl;}virtual void step4() = 0;void step(){step1();step2();step3();step4();}
};
class coffee :public drink
{virtual void step2(){cout << "冲泡咖啡" << endl;}virtual void step4(){cout << "加糖和牛奶" << endl;}
};
class tea :public drink
{virtual void step2(){cout << "冲泡茶叶" << endl;}virtual void step4(){cout << "加柠檬" << endl;}
};
void dodrink(drink * dr)
{dr->step();//制作delete dr;
}void main()
{cout<<"煮茶步骤" << endl;dodrink(new tea);cout << "煮咖啡步骤" << endl;dodrink(new coffee);
}

7.5 虚析构和纯虚析构

class Animal
{public:Animal(){cout<<"Animal构造函数" << endl;}~Animal(){cout << "Animal析构函数" << endl;}virtual void speak() = 0;
};
class  Cat :public Animal
{public :Cat(){cout << "Cat构造函数" << endl;}~Cat(){cout << "Cat析构函数" << endl;}virtual void speak(){cout<<"喵~" << endl;}
};
void main()
{Animal* animal = new Cat;animal->speak();delete animal;
}


应该先释放子类,后释放父类,但是没有。

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在析构释放时无法调用到子类的析构函数,导致子类如果有堆区属性,出现内存泄漏

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构:

virtual ~Animal()
{cout << "Animal析构函数" << endl;
}


纯虚析构:需要声明,也需要代码实现(与纯虚函数(不需要代码实现)不同),有了纯虚析构之后,这个类也属于抽象类,无法实例化对象

class Animal
{public:Animal(){cout<<"Animal构造函数" << endl;}virtual ~Animal() = 0;virtual void speak() = 0;
};
Animal ::~Animal()
{cout << "Animal纯虚析构函数" << endl;
}

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

类名::~类名(){}

8. struct和class区别

C++中 struct和class唯一的区别就在于 默认的访问权限不同

  • struct 默认权限为公共
  • class 默认权限为私有

9.文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化
C++中对文件操作需要包含头文件 < fstream >

文件类型分为两种:

  1. 文本文件:文件以文本的ASCII码形式存储在计算机中
  2. 二进制文件 :文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

操作文件的三大类:

  1. ofstream:写操作
  2. ifstream: 读操作
  3. fstream : 读写操作

9.1文本文件

9.1.1写文件

写文件步骤如下:

  1. 包含头文件:#include <fstream>
  2. 创建流对象:ofstream ofs;
  3. 打开文件:ofs.open("文件路径",打开方式);见下图
  4. 写数据:ofs << "写入的数据";
  5. 关闭文件:ofs.close();

文件打开方式:

文件打开方式可以配合使用,用|操作符
例如:用二进制方式写文件 ios::binary | ios:: out

#include <fstream>
void main()
{ofstream ofs;ofs.open("51.txt",ios::out);ofs << "20220718"<<endl;ofs << "学习P143" << endl;ofs.close();
}

文件存储位置:

总结:

  • 文件操作必须包含头文件 fstream
  • 读文件可以利用 ofstream ,或者fstream类
  • 打开文件时候需要指定操作文件的路径,以及打开方式
  • 利用<<可以向文件中写数据
  • 操作完毕,要关闭文件

9.1.2读文件

读文件步骤如下:

  1. 包含头文件:#include <fstream>
  2. 创建流对象:ifstream ifs;
  3. 打开文件并判断文件是否打开成功:ifs.open("文件路径",打开方式);
  4. 读数据: 四种方式读取
  5. 关闭文件:ifs.close();

四种读取方式:

第一种方式:

char buf[1024] = { 0 };//字符数组,全为0
while (ifs >> buf)//把ifs中的数据全放入(>>)数组中
{cout << buf << endl;
}

第二种方式:

char buf[1024] = { 0 };
while (ifs.getline(buf,sizeof(buf)))
//getline成员函数获取一行,getline(数据放入的地址,最多读取的字节数)
{cout << buf << endl;
}

第三种方式:

string buf;//把文件读入字符串里面
while (getline(ifs, buf))
//getline(基础输入流, 准备好的字符串)
{cout << buf << endl;
}

第四种方式:不推荐

char c;
while ((c = ifs.get()) != EOF)//get函数,每次只读一个字节,放入c里面,没有读到文件尾EOF,输出字符
{cout << c;
}

9.2二进制文件

以二进制的方式对文件进行读写操作
打开方式要指定为 ios::binary
除了内置数据类型,还能操作自定义数据类型

9.2.1写文件

通过流对象操作文件
写文件:通过输出流对象ostream,调用write函数

函数原型:ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,写入的数据地址。len是读写的字节数

#include <fstream>
#include <string>class Person
{public:char m_Name[64];//string类型可能会报错int m_Age;
};//二进制文件  写文件
void main()
{//1、包含头文件//2、创建输出流对象ofstream ofs("person.txt", ios::out | ios::binary);//构造函数,构造时进行步骤3//3、打开文件//ofs.open("person.txt", ios::out | ios::binary);//4、写文件  Person p = {"张三"  , 18};ofs.write((const char *)&p, sizeof(p));//5、关闭文件ofs.close();
}

重点:把数据地址转换为const char *

9.2.2读文件

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

#include <fstream>
#include <string>class Person
{public://成员属性顺序和读的文件要一致char m_Name[64];int m_Age;
};void main()
{//2.创建流对象ifstream ifs;//3.打开文件,判断文件是否打开成功ifs.open("person.txt", ios::in | ios::binary);if (!ifs.is_open()){cout << "文件打开失败" << endl;}//4.读文件Person p;ifs.read((char*)&p, sizeof(p));cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;ifs.close();
}

1.包含头文件
2.创建流对象
3.打开文件,判断文件是否打开成功
4.读文件
5.关闭文件

文件输入流对象 可以通过read函数,以二进制方式读数据

C++:3类和对象、文件操作相关推荐

  1. python类生成对象的操作叫做( )_Python——类和对象(一)

    一.定义类 在面向对象的程序设计中有两种重要概念: 类:可以理解为一个种类,一个模型,是一种抽象的东西. 实例.对象:可以理解为一种具体制作或者存在的东西. 定义类的语法格式如下: class 类名: ...

  2. java jni 方法描述,五、JNI提供的函数介绍(一):类和对象操作

    如果你要开始JNI编程,你还需要了解JNI提供了哪些函数供你调用. 这些函数都定义在了jni.h文件,包括上一篇文章介绍的数据类型,也都在这个头文件中. 类和对象操作 假设你要在JNI层使用C代码创建 ...

  3. 【Java】(二十四)File类(文件操作,文件夹操作,FileFilter接口);Properties属性集合

    继续上一章,学习JavaIO框架 [Java](二十三)IO框架:流的概念与分类,字节流(对象流[序列化.反序列化]),编码方式,字符流(打印流,转换流) 上一节的学习(字节流,字符流)都是对文件内容 ...

  4. c++:文件操作1 文件的打开

    在程序中,要使用一个文件,先要打开文件后才能读写,读写完后要关闭.创建一个新文件也要先执行打开(open)操作,然后才能往文件中写入数据.C++ 文件流类有相应的成员函数来实现打开.读.写.关闭等文件 ...

  5. [Python从零到壹] 三.语法基础之文件操作、CSV文件读写及面向对象

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  6. Java-File文件操作

    File 类概述 Java IO API中的FIle类可以让你访问底层文件系统,通过File类,你可以做到以下几点: 检测文件是否存在 读取文件长度 移动或复制或内容修改 (基于I/o流) 重命名 删 ...

  7. 【C++学习笔记】C++文件操作

    文章目录 计算机文件到底是什么(通俗易懂)? C++文件类(文件流类)及用法详解 C++ open 打开文件(含打开模式一览表) 使用 open 函数打开文件 使用流类的构造函数打开文件 文本打开方式 ...

  8. C/C++/Qt 文件操作 效率比较

    C/C++/Qt 文件操作 & 效率比较 1 介绍 2 比较结果 2.1 Linux平台上运行程序普遍比Windows上快:Windows下VC编译的程序一般运行比MINGW(MINimal ...

  9. Java文件操作的基本概念和简单操作

                                       Java文件的基本操作知识点 提纲: 一:基本概念汇总:     1.学习文件操作的必要性:    2.文件操作的概念:    3 ...

  10. c++学习笔记之多文件操作

    每天进步一点点,努力奋斗的小菜鸟. 曾经搞了好多次的C语言多文件操作,都没搞成功,昨天晚上终于搞成功了,虽然是简单到爆的操作,但我还是挺高兴的,哈哈哈.贴出来一方面怕自己忘,一方面若有初学者看到希望能 ...

最新文章

  1. 设置linux初始root密码
  2. 编译phonetisaurus时configure找不到openfst的问题解决
  3. 一般家用监控多少钱_家用煤气灶价格一般是多少 燃气灶安装的流程
  4. PyCharm与git/GitHub取消关联
  5. Marshal类的简单使用
  6. python3怎么使用qstring_请问PyQt的QString和python的string的区别?
  7. 麻省理工、微软为AI量身打造了一套leetcode编程题
  8. session和cookie_JSP学习
  9. 词向量算法—Word2Vec和GloVe
  10. 零基础学启发式算法(3)-禁忌搜索 (Tabu Search)
  11. paip.ollydbg 常用流程以及找到子程序调用地址
  12. Spring的注入方式中,官方推荐哪种方式
  13. visio画图复制粘贴到word_怎么将visio绘图导入Word(visio绘图插入word后如何调整大小)...
  14. pdf转换成word转换器免费下载
  15. 大文件编辑查看工具推荐:ultraedit、logviewer。文件太大notepad++等编辑工具无法打开解决办法
  16. 川大计算机学院李川,川大计算机学院硕导名单_跨考网
  17. 安装darknet报libQt5Core.so.5: undefined reference
  18. linux swap空间不足,swap空间不足问题解决
  19. 万字长文带你轻松了解LSTM全貌
  20. log4j/log4e的使用

热门文章

  1. C++ typeid详解
  2. php easyswoole --e,EasySwoole
  3. linux docker ps没有东西,Docker ps 命令
  4. 如何在 Kali Linux 桌面上安装 KDE Plasma GUI
  5. 懒人必备:通俗易懂的114个springboot计算机毕业设计程序
  6. 开源SWD脱机烧录器-前言
  7. Metasploit 基础
  8. ARFoundation系列讲解 - 73 第三视角技术一
  9. 【3D游戏编程与设计-HW1】游戏分类与当前热游分析
  10. python入门学习:多态