感谢黑马开源!
一,重载函数
(同一作用域,相同名称,不同参数的函数)
注意事项:
1,(引用/指针)也可以作为重载的条件
传递方式 | 是否可以区分 const ? | 原因 |
---|---|---|
值传递 | ❌ 不能 | const 只影响函数内部,不影响调用时的匹配。 |
引用传递 | ✅ 能 | int& 和 const int& 是不同的类型,可以绑定不同的实参。 |
指针传递 | ✅ 能 | int* 和 const int* 是不同的类型,可以指向不同的数据。 |
关于左值,右值另有高论,参考:深入浅出C++左值与右值 —— 一个视频彻底搞懂左值与右值!_哔哩哔哩_bilibili
2,注意默认参数导致的报错
二,类与封装
访问权限相关18
class和struct区别19
默认访问权限,s公有,c私有。
读写控制set/get20
立方体实例21
文件关系22
点和圆关系为例子,将类的声明放在头文件中,将类的实现放在主文件中!
方式 | 优点 | 缺点 |
---|---|---|
#pragma once | 简洁,不易出错 | 非标准(但几乎所有现代编译器都支持) |
#ifndef 宏保护 | 标准 C/C++,兼容性强 | 需要手动确保宏名唯一,代码稍显冗长 |
初识构造函数与析构函数23
构造函数:
1,名同类名,可以有参数,可以重载,对象实例化时自动调用,编译器会在没有设置构造函数时,自动添加一个空构造函数。
2,只会调用一次。
析构函数:
1,~名同类名,无参无构造,不可重载,对象销毁前(出栈)自动调用。编译器会在没有设置析构函数时,自动添加一个空析构函数。
2,只会调用一次,写在main函数中时,会在main函数结束时销毁。
构造函数的分类和调用24
1,按照有无参数分类:有参构造,无参构造
2,按照参数类型分类:普通构造,拷贝构造
拷贝构造传参为常量类对象的引用,可以拷贝出一个和该对象相同的对象。
构造函数的调用方法:
1,括号法
2,显示法
3,隐式转换法
简单写下代码
class Person{
private:
int age;
public:
//constructor
//Parameterless Construction
Person(){
age = 0;
}
//Parametrical Construction
Person(int a){
age = a;
}
//Copy Construction
Person(const Person &p1){
age = p1.age;
}
};
void test01{
//bracket call
Person p;
Person p1(10);
Person p2(p);
//tips:
(X)Person p1();
//In this way , the compiler will evaluates "Person p1()" to a func return type Person.
//display call
Person p3;
Person p4 = Person(10);
Person p5 = Person(p4);
//anonymous object will destroy after this line call.
Person(12);
//tips:
(x)Person(p5);
//We can't initialize anonymous object in Copy Construction.
//Implicit Conversion
Person p6;
Person p7 = 10;
Person P8 = p7;
}
拷贝构造时机25
1,使用一个创建完成的类对象来初始化一个新对象
2,值传递给函数参数传值
3,类对象做返回值(以值方式返回局部对象)
tesr01{
Person p1(10);
Person p2(p1);
}
test02(Person p){
Person p3(p);
}
Person test03(){
//
return Person p;
}
构造函数调用规则26
一个类定义时,系统自动提供三种构造函数。
1,用户自定义有参,系统不自动提供无参
2,用户自定义拷贝,系统不自动提供其他
拷贝构造的浅拷贝与深拷贝27
类默认提供的拷贝构造是浅拷贝,只将类成员变量的值复制给要实例化的对象。在类中有指针成员变量时,浅拷贝只会传递内存地址,在析构函数中会触发堆区内存重复释放的错误,
class Person{
public:
int age;
int *h;
Person(const Person p){
age = p.age;
//deep copy
int h = new int(*p.h);
}
~Person(){
if(h != NULL){
delete h;
}
h = NULL;
}
};
构造函数初始化列表28
int ca;
int cb;
int cc;
Person(int a,int b,int c) : aa(a),cb(b),cc(c){}
其他类对象做类的成员,构造/析构顺序29
先调用其他类的构造,最后调用其他类的析构
class Phone{
string pname;
phone (string name):pname(name){}
} ;
class Person{
string hname;
Phone pname;
//Phone pname = phone
//Implicit Conversion
Person(string person, string phone):hname(person),pname(phone){}
}
静态成员函数与静态成员变量30&31
1,静态成员函数和静态成员变量都有访问权限
静态成员变量:
1,所有类对象公用的变量(共用一份数据)
2,在编译阶段分配内存
3,类内声明,类外初始化
对于第三点,我进行了一些尝试,结果是,必须在类外初始化。
如果尝试在定义时初始化,则语法报错提示,如果需要初始化静态成员变量,则该变量必须是常量。
如果只在构造时初始化,则编译后(build error)报错:
LNK2001 unresolved external symbol “public: static int Student::Sch_s”
所以在类外做初始化是必要的!
class Student {
public:
static int Sch_s ;
static string Sch_n;
int Xh = 10000;
string stu_name = "Lim";
Student() {
Sch_s++;
}
Student(int xh, string name) :Xh(xh), stu_name(name) {
Sch_s++;
}
static void ChangeSchName(string name){
Sch_n = name;
cout<<"This School change name to "<<name<<endl;
}
void show() {
cout<<"The school name is "<<Sch_n<<endl;
cout << "Name :" << stu_name << " XueHao :" << Xh << endl;
cout << "Now School has " << Sch_s << "Students" << endl;
}
};
int Student::Sch_s = 0;
string Student::Sch_n = "XiWang";
int main() {
Student s1;
Student s2(10001, "miss");
s1.show();
s1.ChangeSchName("GuangMing");
s2.show();
}
静态成员函数:
1,所有类对象共用一个静态成员函数。
2,静态成员函数只能访问静态成员变量。
静态成员函数不能访问非静态成员变量的原因是:
静态成员函数分不清非静态成员变量到底是哪一个对象的变量。
C++对象特性(模型)和this指针之章
成员变量和成员函数分开存储32
通过sizeof检查大小发现,C++编译器会给每个空对象分配一个字节空间,为了区分空对象占用内存的位置。只有类的非静态成员变量在类的对象上,用sizeof可以检查到空间占用。
也就是说,类的非静态成员函数,类的静态成员变量,类的静态成员函数都不在类的对象上。
this指针用途(链式编程思想)33
this指针用途:
1,解决名称冲突
解决形参列表和类成员变量重名的冲突
2,返回对象本身引用
this指针指向被调用的成员函数所属的对象,可以用于在类的非静态成员函数中返回对象本身(引用方式)。
如果在下面的例子中不使用引用的方式,则每次调用add函数只会返回一个对于篇
的拷贝构造,实际上后续的修改没有修改到p2上,所以最终的值为20。
class Person{
int age;
person(int age){
//age = age;
this->age = age;
}
//Person
Person& personAddAge(Person &p){
this->age += p.age;
return *this;
}
};
void test01(){
Person p1(18);
}
void test02(){
Person p1(10);
Person p2(10);
p2.personAddAge(p1).personAddAge(p1).personAddAge(p1);
}
空指针访问类的成员函数34
空类指针(同this指针)可以访问类的成员函数。
类成员函数中使用到类成员变量//时,会自动在前面加上this指针,所以空指针无法取到该成员变量的值会报错。
class Person{
int m_age;
showPersonAge(){
//cout<<this->age;
cout<<m_age;
}
};
const修饰成员函数35
this补充:
1,this 指针隐含在每一个非静态成员函数之中
2,this 指针不需要定义,直接使用即可
常函数:
1,成员函数后加const后是常函数
2,常函数内不能修改成员属性
3,成员属性声明时加关键字mutable后,在常函数中可以修改
常对象:
1,声明对象前加const的对象是常对象
2,常对象只能调用常函数
class Person{
public:
(x)void theEssenceOfThis(){
this = NULL;
}
//Can't change point to(address) also can't change the point to value
//const Person * const this
void constantFunc() const{
//Variables that are not mutable cannot be modified.
//this->m_a = 100;
this->m_b = 100;
}
int m_a;
mutable int m_b;
};
void test01(){
Person p;
//this is Pointer Constant,Can't change point to
//this(Person * const this)
p.theEssenceOfThis();
}
void test02(){
Person p;
p.constantFunc();
}
//constant obj
void test03(){
const Person p;
//p.m_a = 100;
p.m_b = 100;
//constant obj can just call constant func
p.constantFunc();
}
友元36
1,全局函数做友元,可以访问类的私有部分。
class Building{
//pass by address , avoid the cost of memory
friend void frifunc(Building * building);
public:
Building(){
this->livingroom = "keting";
this->bedroom = "woshi";
}
public:
string livingroom;
private:
string bedroom;
};
void frifunc(Building* building){
cout<<building.livingroom;
cout<<building.bedroom;
}
int main(){
Building b;
frifunc(&b);
return 0;
}
关于类指针的思考
表达式 | 含义 | 地址类型 |
---|---|---|
l1 | 指针存储的地址(指向堆上的 Liter 对象) | 堆地址 |
&l1 | 指针变量 l1 本身的地址(在栈上) | 栈地址 |
l1
是动态分配的对象的地址(堆)。&l1
是指针变量本身的地址(栈)。delete l1
释放堆内存,但不影响l1
的栈地址。
#include<iostream>
using namespace std;
class Liter {
public:
char a, b, c;
Liter(char a, char b, char c) {
this->a = a;
this->b = b;
this->c = c;
}
};
int main() {
Liter *l1 = new Liter('a','b','c');
cout << l1 << endl;
cout << &l1 << endl;
//000002143D4A7EC0
//0000009942EFFB18
delete l1;
l1 = nullptr;
return 0;
}
友元类37
注意,友元关系是一种 “访问授权”,而不是 “继承授权”。所以没有继承友元类这样的说法。
类做友元,可以访问类的私有部分。
class Friend{
public:
Friend();
void visit();
Building * building;
}
class Building{
//
friend class Friend;
//pass by address , avoid the cost of memory
friend void frifunc(Building * building);
public:
Building(){
this->livingroom = "keting";
this->bedroom = "woshi";
}
public:
string livingroom;
private:
string bedroom;
};
//member func realize out of class
Friend::Friend(){
building = new Building;
}
void Friend::visit(){
building->livingroom = "ke";
building->bedroom = "wo";
}
成员函数做友元38
class Building{
friend void Friend::visit();
};
运算符重载之章
加法运算符重载39
1,成员函数重载
2,全局函数重载
运算符重载主要用于自定义类型运算,也可以使用函数重载。
class Person{
public:
int m_a;
int m_b;
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;
}
Person operator+ (Person &p ,int num){
Person temp;
temp.m_a = this -> m_a + num;
temp.m_b = this -> m_b + num;
return temp;
}
}
Person operator+ (Person &p1, Person &p2){
Person temp;
temp.m_a = p1 -> m_a + p2.m_a;
temp.m_b = p2 -> m_b + p1.m_b;
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;
}
左移运算符重载40
不用成员函数做左移运算符重载的原因是格式不好看(无法实现cout在左侧),用去全局函数做重载,传入ostream对象cout,和类对象,再应用一些链式编程思想,即可完成连续输出。
class Person{
public:
int age;
string name;
//p.operator<<(p)
(x)Person operator<< (Person &p);
//p << cout
(x)Person operator<< (ostream &cout);
}
//standard out stream object only one in whole situation
ostream & operator<< (ostream & cout , Person & p){
cout<<"age : "<<p->age<<" Name : "<< o->name <<endl;
return cout;
}
自增/自减运算符重载41
要是没有黑马的教程我根本就注意不到这二者的区别:
int a = 10;
--(--a);
(x)(a--)--;
表达式 | 返回类型 | 是否合法 | 原因 |
---|---|---|---|
--(--a) | 左值 | 合法 | 可以对左值连续递减 |
(a--)-- | 右值 | 非法 | 不能对右值递减 |
- 核心区别:前置递减返回左值,后置递减返回右值。
- 设计逻辑:后置递减需要返回原始值(临时值),因此不能支持连续递减。
class Czpp {
friend ostream& operator<< (ostream& cout, Czpp c01);
public:
Czpp() {
o = 0;
}
//the reason of add & becaues ++op is design for continuous output.
//So we can call ++(++a) trustingly
Czpp& operator++ () {
o++;
return *this;
}
Czpp operator++ (int) {
Czpp temp = *this;
o++;
return temp;
}
private:
int o;
};
ostream& operator<< (ostream& cout, Czpp c01) {
cout << c01.o << endl;
return cout;
}
void testcase(){
Czpp c01;
cout << ++(++c01) << endl;
cout << c01 << endl;
cout << ((c01++)++)++ << endl;
cout << c01 << endl;
}
class Czmm {
friend ostream& operator<< (ostream& cout, Czmm c01);
public:
Czmm() {
oi = 100;
}
Czmm(int n) {
oi = n;
}
Czmm & operator-- () {
oi
--;
return *this;
}
Czmm operator--(int) {
Czmm temp = *this;
oi--;
return temp;
}
private:
int oi;
};
ostream& operator<< (ostream& cout, Czmm c01) {
cout << c01.oi << endl;
return cout;
}
void test02() {
Czmm c02;
cout << --c02 << endl;
cout << --(--c02) << endl;
Czmm c03(20);
cout << c03-- << endl;
cout << c03 << endl;
}
赋值运算符重载42
C++编译器会给一个类添加四个函数:
1,默认构造函数,函数体为空。
2,默认析构函数,函数体为空。
3,默认拷贝构造函数,对属性进行值拷贝。
4,赋值运算符 operator= 对属性进行值拷贝。
我们在类外进行类的等号赋值操作时,进行的是浅拷贝,容易触发地址重复释放问题。可以通过重载赋值运算符进行深拷贝规避问题。
class Person{
public:
int* m_age;
Person(int age){
m_age = new int(age);
}
//add operator= heavyload
//chain programming ideas
Person & operator= (Person& p){
//Compiler offer shallow copy
//m_age = p.m_age;
//Defensive programming
//Chenking if this property is in heap area
if(m_age!=NULL){
delete m_age;
m_age = NULL;
}
m_age = new int (*p.m_age);
return *this;
}
~Person(){
if(m_age != NULL){
delete m_age;
m_age = NULL;
}
}
};
void test01(){
Person p1(18);
Person p2(20);
//assignment operatation will trigger shallow copy
//Repetition release heap area memory
p1 = p2;
cout<<*p1.m_age<<endl;
cout<<*p2.m_age<<endl;
}
关系运算符重载43
涉及>,<,==,!=等,返回bool类型值。
函数调用重载运算符&匿名函数对象44
函数调用()也可以重载
class Myprint{
public:
void operator()(string rest){
cout<<rest<<endl;
}
};
void test01(){
Myprint mp;
mp("hello world");
//anonymous func object
Myprint()("hello world");
}
继承之章
初识继承45
公共部分抽象为父类,子类继承
继承方式46
继承中的对象模型47
子类继承父类,会得到所有父类成员变量,只是会自动隐藏父类的私有成员变量。通过vs Developer Command Prompt查看(报告单个类的布局)
cl /d1 reportSingleClassLayout类名 “cpp文件名”
继承中的构造以及析构顺序48
父类先构造,后析构
同名成员处理49
如果子类中出现和父类同名的成员函数,子类的同名成员函数会自动隐藏掉父类中所有的同名成员函数,在有子类的情况下,(即使有能明显区分的重载也不行)访问父类中被隐藏的同名成员函数,需要加作用域。
同名静态成员处理50
1,通过对象访问
2,通过类名访问
class father {
public:
int fapuint;
static int fapusint;
protected:
int faprint;
static int faprsint;
private:
int fapriint;
static int faprisint;
public:
father() {
fapuint = 10;
//I forget how to init static ^-^
/*fapusint = 100;*/
faprint = 20;
fapriint = 30;
}
void pint() {
cout << "father func" << endl;
}
static void spint() {
cout << "father static func" << endl;
}
};
int father::fapusint = 100;
class son : public father {
public:
static int fapusint;
void pint() {
cout << "son func" << endl;
}
static void spint() {
cout << "son static func" << endl;
}
};
int son::fapusint = 100;
void tcinher() {
son s1;
//access from obj
cout << "son ' s varible access with obj" << s1.fapuint << endl;
cout << "fa ' s varible access with obj" << s1.father::fapuint << endl;
//access from action scope(class name)
//This access way is only ok to static varible / func.
cout << "son ' s static varible access with class name" << son::fapusint << endl;
cout << "fa ' s static varible access with class name" << son::father::fapusint << endl;
//son ' s func access with obj
s1.pint();
//fa ' s func access with obj
s1.father::pint();
//son ' s static func access with class name
son::spint();
//fa ' s static func access with class name
son::father:: spint();
}
继承语法51
多继承,注意以作用域区分同名。
class son : public base1, public base2{
};
菱形继承52
虚基类,虚继承
情况 | 继承方式 | grandfa 子对象数量 | 访问 m_age 是否二义 |
---|---|---|---|
普通继承 | class fath : public grandfa | 2 份(fath + math ) | 是(需指定路径) |
虚继承 | class fath : virtual public grandfa | 1 份(共享) | 否(可直接访问) |
fath
和math
共享同一个grandfa
子对象。sons
只包含 一个grandfa
实例,避免了二义性。
底层实现(编译器如何支持虚继承?)
虚继承的实现通常依赖 虚基类表(Virtual Base Table, vbtable):
- 虚基类指针(vbptr):
- 当某个类虚继承自另一个类时,编译器会为该类添加一个 虚基类指针(vbptr),指向虚基类表(vbtable)。
- 虚基类表(vbtable):
- 存储了虚基类相对于当前对象的偏移量。
- 这样,即使
fath
和math
在sons
中的位置不同,它们也能正确找到共享的grandfa
子对象。
class grandfa {
public:
int m_age;
};
//class uncle : virtual public grandfa
class uncle : public grandfa {
public:
};
//class math : virtual public grandfa
class math : public grandfa {
public:
};
//class fath : virtual public grandfa
class fath : public grandfa {
public:
};
class sons :public fath, public math , public uncle {
public:
};
void testv() {
sons son1;
son1.uncle::m_age = 77;
son1.math::m_age = 19;
/*son1.fath::m_age = 20;*/
son1.grandfa::m_age = 100;
//cout << "son's age = " <<son1.m_age << endl;
cout << "uncle's age = " << son1.uncle::m_age << endl; // 77(未被修改)
/*cout << "fath's age = " << son1.fath::m_age << endl;*/
cout << "math's age = " << son1.math::m_age << endl; // 19(未被修改)
cout << "gf's age = " << son1.grandfa::m_age << endl; // 100(修改的是 fath 的版本)
}
实际上我在看到多态原理剖析时,用sizeof测试了各个类的大小:
cout << "size of gf " << sizeof(grandfa) << endl;//4
cout << "size of uncle " << sizeof(uncle) << endl;//16
cout << "size of math " << sizeof(math) << endl;//16
cout << "size of fath " << sizeof(fath) << endl;//16
cout << "size of sons " << sizeof(sons) << endl;//32
Deepseek声称可能存在内存对齐,虚基类指针占用8位,对齐4位,继承的int4位。
在不使用虚继承时出现的情况:
son1.grandfa::m_age = 100;
是 模糊访问,但某些编译器(如 MSVC)会 隐式选择第一个基类(fath
)的grandfa
,因此:- 它修改的是
fath
的grandfa::m_age
(但fath::m_age
之前未被赋值,可能是随机值或 0)。 uncle::m_age
和math::m_age
未被影响,仍然是77
和19
。
- 它修改的是
- 输出时:
son1.uncle::m_age
是77
(未被grandfa::m_age = 100
影响)。son1.math::m_age
是19
(未被影响)。son1.grandfa::m_age
是100
(修改的是fath
的版本)。
问题:为什么 grandfa::m_age = 100
不影响 uncle::m_age
和 math::m_age
?
因为:
sons
有 3 份独立的grandfa
子对象(fath
、math
、uncle
各一份)。son1.grandfa::m_age
只能修改其中一份(具体是哪份取决于编译器,这里是fath
的)。uncle::m_age
和math::m_age
是另外两份,不受影响。
EX:继承中的父子类思考:
为什么子类构造时要求父类必须有默认构造函数?
在 C++ 中,子类对象的构造过程一定会先构造父类部分。如果父类没有默认构造函数(无参构造函数),而子类的构造函数又没有显式调用父类的某个构造函数,编译器就无法自动构造父类,从而导致编译错误。
1. 构造顺序:父类 → 子类
当创建一个子类对象时,构造顺序是:
- 父类的构造函数(如果父类还有父类,则继续向上递归)
- 子类成员变量的构造函数(按声明顺序)
- 子类自己的构造函数
如果父类没有默认构造函数,而子类又没有显式指定调用父类的哪个构造函数,编译器就不知道如何构造父类部分,因此会报错。
2. 示例分析
❌ 错误情况:父类没有默认构造函数,子类未显式调用父类构造函数
class Parent {
public:
Parent(int x) { } // 只有带参数的构造函数,没有默认构造函数
};
class Child : public Parent {
public:
Child() { } // 错误!编译器不知道如何构造 Parent
};
int main() {
Child c; // 编译失败
return 0;
}
报错信息:
error: no matching function for call to 'Parent::Parent()'
原因:
Child()
构造函数没有显式指明如何构造 Parent
,编译器尝试调用 Parent::Parent()
(默认构造函数),但 Parent
没有默认构造函数,因此报错。
✅ 正确做法:
(1)父类提供默认构造函数
class Parent {
public:
Parent() { } // 提供默认构造函数
Parent(int x) { } // 也可以有其他构造函数
};
class Child : public Parent {
public:
Child() { } // 现在可以编译,自动调用 Parent()
};
(2)子类显式调用父类的某个构造函数
class Parent {
public:
Parent(int x) { } // 只有带参数的构造函数
};
class Child : public Parent {
public:
Child() : Parent(42) { } // 显式调用 Parent(int)
};
int main() {
Child c; // 正确,Parent 被构造为 Parent(42)
return 0;
}
关键点:
Child() : Parent(42)
是 成员初始化列表(Member Initializer List),在这里显式调用了Parent(int)
。- 如果不写
: Parent(42)
,编译器会尝试调用Parent()
,但Parent
没有默认构造函数,导致编译错误。
3. 更深层次的情况:多重继承
如果子类继承多个父类,且某些父类没有默认构造函数,必须在子类构造函数中显式调用它们的构造函数:
class Parent1 {
public:
Parent1(int x) { }
};
class Parent2 {
public:
Parent2(int y) { }
};
class Child : public Parent1, public Parent2 {
public:
Child() : Parent1(10), Parent2(20) { } // 必须显式调用
};
如果不写 : Parent1(10), Parent2(20)
,编译器会报错!
4. 为什么 C++ 这样设计?
- 明确性:强制程序员显式指定如何构造父类,避免意外行为。
- 灵活性:允许父类的构造依赖子类传递的参数。
- 避免二义性:如果父类有多个构造函数,编译器不知道默认用哪个,必须由程序员指定。
总结
情况 | 父类构造函数 | 子类构造函数写法 | 是否合法 |
---|---|---|---|
父类有默认构造函数 | Parent() 存在 | Child() { } | ✅ 合法 |
父类无默认构造函数 | 只有 Parent(int) | Child() { } | ❌ 编译错误 |
父类无默认构造函数 | 只有 Parent(int) | Child() : Parent(42) { } | ✅ 合法 |
关键规则:
- 如果父类没有默认构造函数,子类必须显式调用父类的某个构造函数。
- 如果父类有默认构造函数,子类可以不显式调用(编译器自动调用
Parent()
)。
多态的基本语法53
1,静态多态:
地址早绑定,编译阶段确定函数地址。例如函数重载,运算符重载都属于静态多态,复用函数名。
2,动态多态:
地址晚绑定,运行阶段确定函数地址。用派生类和虚函数实现运行时多态。
虚函数:子类重写父类函数(返回值,参数列表,函数名相同),调用时父类对象的引用作为形参,在父类被重写函数前加上virtual关键字,使之成为虚函数,则可以根据传入参数的对象类型选择对应类的函数调用。
- 虚函数:使得基类指针或引用指向派生类对象时,可以调用派生类的重写函数,实现多态。
- 动态联编(Dynamic Binding):指的是程序运行时,系统动态决定调用哪个函数,常常与虚函数一起使用。
- 滞后联编(Late Binding):与动态联编是同义的,强调函数调用的决定是在程序运行时,而不是编译时。
两者本质上是相同的,滞后联编更多是对“运行时选择函数”过程的描述,而动态联编则是虚函数机制的一部分,指代了在运行时基于对象的实际类型来选择函数的调用。
子类重写函数前virtual可加可不加。
class animals {
public:
virtual void speak() {
cout << "anilmals' speak" << endl;
}
};
class cats : public animals{
public:
void speak() {
cout << "cats' speak" << endl;
}
};
class dogs : public animals {
public:
void speak() {
cout << "dogs' speak" << endl;
}
};
void dospeak(animals & animal) {
animal.speak();
}
void tc01() {
animals ani;
cats c01;
dogs d01;
dospeak(ani);
dospeak(c01);
dospeak(d01);
}
多态的原理剖析54
使用virtual关键字修饰父类函数后,父类中存储一个虚函数指针(vfptr) 指向虚函数表(vftable),当子类重写出现时,子类的虚函数指针指向子类的重写后的虚函数。sizeof验证都为8
cout << "size of anis " << sizeof(animals) << endl;
cout << "size of ani " << sizeof(ani) << endl;
cout << "size of cat " << sizeof(cats) << endl;
cout << "size of c01 " << sizeof(c01) << endl;
cout << "size of dog " << sizeof(dogs) << endl;
cout << "size of d01 " << sizeof(d01) << endl;
多态案例之计算器55
注意,多态的应用方式有两种:父类的引用/指针指向子类对象
利用多态的特性,我们可以在扩展程序功能时,尽可能不修改源码,也就是“开放扩展,关闭修改”。
多态的代码组织下,结构清晰,代码可读性高。多态的应用方法为父类的指针/引用指向子类对象。
class calculator {
public:
int num_1;
int num_2;
int getRes(string oper) {
if (oper == "+") {
return num_1 + num_2;
}
else if (oper == "-") {
return num_1 - num_2;
}
}
};
class abstractCalc {
public:
int num_1;
int num_2;
virtual int getRes() {
return 0;
}
};
class addcalc : public abstractCalc {
public:
int getRes() {
return num_1 + num_2;
}
};
class subcalc : public abstractCalc {
public:
int getRes() {
return num_1 - num_2;
}
};
void test() {
calculator c;
c.num_1 = 10;
c.num_2 = 10;
cout << c.getRes("+") << endl;
cout << c.getRes("-") << endl;
//Method of application of polymorphic
//父类指针/引用指向子类对象
abstractCalc *ac = new addcalc;
ac->num_1 = 10;
ac->num_2 = 10;
cout << ac->getRes() << endl;
delete ac;
abstractCalc* ac2 = new subcalc;
ac2->num_1 = 10;
ac2->num_2 = 10;
cout << ac2->getRes() << endl;
delete ac2;
}
纯虚函数和抽象类56
之前coding的时候我就有这样的疑问,实例化父类和子类的关系,在这里有了答案。我们可以将父类成员函数变为纯虚函数,将父类变为抽象类,这样父类无法实例化。子类必须重写父类的纯虚函数,否则也会变成纯虚函数。
class animals{
public:
int age;
virtual void name() = 0;
};
class dogs {
public:
void name(){
age = 10;
}
}
制作饮品57
class make{
public:
virtual procedure01() = 0;
virtual procedure02() = 0;
virtual procedure03() = 0;
virtual procedure04() = 0;
};
class teamake: public make{
public:
procedure01(){
cout<<"tea01"<<endl;
}
procedure02(){
cout<<"tea02"<<endl;
}
procedure03(){
cout<<"tea03"<<endl;
}
procedure04(){
cout<<"tea04"<<endl;
}
};
void test(){
teamake tm;
tm.procedure01();
}
虚析构函数和纯虚析构函数58
和虚函数,纯虚函数类似,虚析构可以让父类指针释放子类对象(根据子类的析构函数),纯虚析构需要在类外初始化,并且一旦定义纯虚析构,该类即为抽象类,无法实例化对象。
class Animals{
public:
int age;
virtual ~Animals() = 0;
};
Animals::~Animals(){
cout<<"pure virtual func call"<<endl;
}
class dogs{
public:
string* dname;
~dogs(){
if(dname != NULL){
delete dname;
dname = NULL;
}
}
}
多态案例组装电脑59/60
//polymorphic practice
class CPU {
public:
virtual void cpuwork() = 0;
};
class GPU {
public:
virtual void gpuwork() = 0;
};
class computers {
public:
void dowork() {
m_cpu->cpuwork();
m_gpu->gpuwork();
}
computers(CPU* cpu, GPU* gpu) : m_cpu(cpu),m_gpu(gpu) {}
~computers() {
if (m_cpu != NULL) {
delete m_cpu;
m_cpu = NULL;
}
if (m_gpu != NULL) {
delete m_gpu;
m_gpu = NULL;
}
}
private:
CPU* m_cpu;
GPU* m_gpu;
};
class intelCPU : public CPU {
public:
void cpuwork() {
cout << "InterCPU is working" << endl;
}
};
class AMDCPU : public CPU {
public:
void cpuwork() {
cout << "AMDCPU is working" << endl;
}
};
class AMDGPU : public GPU {
public:
void gpuwork() {
cout << "AMDGPU is working" << endl;
}
};
class NvidaGPU : public GPU {
public:
void gpuwork() {
cout << "NvidaGPU is working" << endl;
}
};
void polytest() {
intelCPU* intelc01 = new intelCPU;
AMDCPU* AMDc01 = new AMDCPU;
AMDGPU* AMDg01 = new AMDGPU;
NvidaGPU* Ng01 = new NvidaGPU;
CPU* intelc = new intelCPU;
CPU* AMDc = new AMDCPU;
GPU* AMDg = new AMDGPU;
GPU* Ng = new NvidaGPU;
computers * com01= new computers(AMDc01, AMDg01);
computers* com02 = new computers(intelc, AMDg);
com01->dowork();
com02->dowork();
delete com01;
delete com02;
computers* com03 = new computers(new intelCPU, new NvidaGPU);
com03->dowork();
delete com03;
}
ChatGPT评价道:
在 polytest()
中,com01
和 com02
使用了不同的方式来创建 computers
对象,其区别主要体现在多态性和内存管理两个方面。
1. 父类指针指向子类对象(com02)
CPU* intelc = new intelCPU;
GPU* AMDg = new AMDGPU;
computers* com02 = new computers(intelc, AMDg);
特点:
- 使用 父类指针指向子类对象,体现了多态性。
computers
中的m_cpu
和m_gpu
被声明为CPU*
和GPU*
,因此传入父类指针符合多态的设计。- 在实际运行时,通过虚函数表(vtable)实现动态绑定,会执行
intelCPU::cpuwork()
和AMDGPU::gpuwork()
。 - 推荐这种方式,符合面向对象的多态原则,使代码更加灵活。
内存管理注意:
delete com02;
时,会执行computers
的析构函数,调用delete m_cpu
和delete m_gpu
。- 父类指针删除子类对象时,必须确保基类有虚析构函数,否则可能会造成内存泄漏或未完全析构子类对象。
2. 子类指针指向子类对象(com01)
AMDCPU* AMDc01 = new AMDCPU;
AMDGPU* AMDg01 = new AMDGPU;
computers* com01 = new computers(AMDc01, AMDg01);
特点:
- 使用 子类指针指向子类对象,在对象传入
computers
的构造函数时,会发生隐式向上转型。 - 这种方式虽然也能实现多态行为,但丧失了父类指针的灵活性。
- 在程序中不太推荐直接使用子类指针来管理对象,因为它不具备多态的优势。
内存管理注意:
computers
析构函数中依然会调用delete
删除对象。虽然子类对象会正确析构,但这种管理方式容易导致代码的可维护性下降。- 如果后期需要更换不同的
CPU
或GPU
,这种写法的灵活性较差。
总结
区别 | 父类指针指向子类对象 (com02 ) | 子类指针指向子类对象 (com01 ) |
---|---|---|
多态性 | 支持多态,运行时动态绑定 | 不支持多态,失去灵活性 |
内存管理 | 安全性较高,方便统一管理 | 容易出错,不便于维护 |
扩展性 | 便于扩展其他CPU/GPU类 | 扩展性差,不利于维护 |
推荐程度 | ✅ 推荐使用 | ❌ 不推荐 |
因此,在实际开发中,父类指针指向子类对象是更优的选择。
C++写/读文本文件61/62
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化。C++中对文件读写需要包含头文件
#include<fstream>
#include<iostream>
#include<string>
using namespace std;
void writet() {
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "名称:张三" << endl;
ofs << "年龄:18" << endl;
ofs<< "性别:男"<<endl;
ofs.close();
return;
}
void readt() {
ifstream ifs1;
ifstream ifs2;
ifstream ifs3;
ifstream ifs4;
ifs1.open("test.txt", ios::in);
ifs2.open("test.txt", ios::in);
ifs3.open("test.txt", ios::in);
ifs4.open("test.txt", ios::in);
/*if (!ifs.is_open()) {
cout << "The file open filled" << endl;
}*/
//method 1
char buf[1024];
while (ifs1 >> buf) {
cout << buf << endl;
}
cout << endl;
//method 2
char buf2[1024];
while (ifs2.getline(buf2, sizeof(buf2))) {
cout << buf2 << endl;
}
cout << endl;
//method 3
string buf3;
while (getline(ifs3, buf3)) {
cout << buf3 << endl;
}
cout << endl;
//method 4
char buf4;
while ((buf4 = ifs4.get())!=EOF ) {
cout << buf4;
}
ifs1.close();
ifs2.close();
ifs3.close();
ifs4.close();
}
int main() {
writet();
readt();
return 0;
}
读取方法1遇到空格会自动换行。
C++写/读二进制文件63/64
#include<iostream>
#include<fstream>
using namespace std;
class Person {
public:
int m_age;
char m_name[64];
};
void test() {
ofstream ofs("Person.txt", ios::out | ios::binary);
Person p1 = { 89, "张三" };
ofs.write((const char*)&p1, sizeof(Person));
ofs.close();
ifstream ifs("Person.txt", ios::in | ios::binary);
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << p.m_age << endl<<p.m_name;
ifs.close();
}
int main() {
test();
}
注意事项:
1,sizeof(Person) / sizeof§ / sizeof(p1) 没有区别,这里在类中定义了char的大小为64字节。
2,在写文件时,我们不希望p数据被修改。write
用于写入数据到文件,它不会修改传入的数据,所以参数类型是 const char*
(表示“只读”)。在读文件时,read
用于从文件读取数据,它会修改传入的缓冲区(写入数据到 s
),所以参数类型是 char*
(表示“可修改”)。
实现职工管理系统
EMS.h(EmployerManagerSystem)
#pragma once
#include<iostream>
using namespace std;
#include "worker.h"
#include<fstream>
#define FILENAME "Worker.txt"
class EmployerManager {
public:
bool m_fileIsEmpty;
int m_workerNum;
Worker** m_workerArray;
EmployerManager();
void add_worker();
void init_workerArray();
int get_workerNum();
void show_worker();
void delete_worker();
void modify_worker();
void find_worker();
int isExist(int id);
void showMenu();
void sort_worker();
void clean_file();
void exitSystem();
void save();
~EmployerManager() ;
};
worker.h
#pragma once
#include<iostream>
#include<string>
using namespace std;
class Worker {
public:
virtual void showInfo() = 0;
virtual string getDeName() = 0;
int m_Id; //职工编号
string m_Name;
int m_DeId; //部门编号
/*string m_DeName;*/
};
employee.h
#pragma once
#include<iostream>
using namespace std;
#include "worker.h"
class Employee : public Worker {
public:
Employee(int id, string name, int dId);
virtual void showInfo();
virtual string getDeName();
};
manager.h
#pragma once
#include<iostream>
using namespace std;
#include "worker.h"
class Manager : public Worker {
public:
Manager(int id, string name, int dId);
virtual void showInfo();
virtual string getDeName();
};
boss.h
#pragma once
#include<iostream>
using namespace std;
#include "worker.h"
class Boss : public Worker {
public:
Boss(int id, string name, int dId);
virtual void showInfo();
virtual string getDeName();
};
EMS.cpp
#include "EMS.h"
#include<fstream>
#include "employee.h"
#include"manager.h"
#include"boss.h"
int getValidInt(const string& prompt) {
int value;
while (true) {
cout << prompt << endl;
cin >> value;
if (cin.fail()) { // 检测输入是否失败
cin.clear(); // 清除错误标志
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 丢弃错误输入
cout << "Invalid input! Please enter a number." << endl;
}
else {
cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 清除缓冲区
return value;
}
}
}
string getValidString(const string& prompt) {
string value;
cout << prompt << endl;
cin >> value;
return value;
}
EmployerManager::EmployerManager() {
ifstream ifs;
ifs.open(FILENAME, ios::in);
if (!ifs.is_open()) {
cout << "FILE is not Exist!" << endl;
this->m_workerNum = 0;
this->m_fileIsEmpty = true;
this->m_workerArray = NULL;
ifs.close();
return;
}
char ch;
ifs >> ch;
if (ifs.eof()) {
cout << "FILE is empty!" << endl;
this->m_workerNum = 0;
this->m_fileIsEmpty = true;
this->m_workerArray = NULL;
ifs.close();
return;
}
int num = this->get_workerNum();
cout << "Worker Count: " << num << endl;
this->m_workerNum = num;
this->m_workerArray = new Worker * [this->m_workerNum];
init_workerArray();
for (int i = 0; i < m_workerNum; i++) {
cout << "Worker serial number : " << this->m_workerArray[i]->m_Id
<< "\tWorker name : " << this->m_workerArray[i]->m_Name
<< "\tWorker Department ID : " << this->m_workerArray[i]->m_DeId << endl;
}
}
void EmployerManager::showMenu() {
cout << R"(
职工管理系统已启动!
0,退出程序
1,增加职工
2,显示职工
3,删除职工
4,修改职工
5,查找职工
6,职工排序
7,清空数据
)" << endl;
}
void EmployerManager::add_worker() {
cout << "Put in the number of worker you want add" << endl;
int addNum = 0;
cin >> addNum;
if (addNum > 0) {
int newSize = this->m_workerNum + addNum;
Worker** newSpace = new Worker * [newSize];
if (this->m_workerArray != NULL) {
for (int i = 0; i < this->m_workerNum; i++) {
newSpace[i] = this->m_workerArray[i];
}
}
for (int i = 0; i < addNum; i++) {
int id = getValidInt("Please put in the " + to_string(i + 1) + " worker serial number");
string name = getValidString("Please put in the " + to_string(i + 1) + " worker name");
int dId;
/*cout << "Please put in the " << i + 1 << " worker serial number" << endl;
cin >> id;*/
/*cout << "Please put in the " << i + 1 << " worker name" << endl;
cin >> name;*/
cout << R"(Please choose the worker's job
1,Employee
2,Manager
3,Boss)" << endl;
cin >> dId;
Worker* worker = NULL;
switch (dId) {
case 1:
worker = new Employee(id, name, dId);
break;
case 2:
worker = new Manager(id, name, dId);
break;
case 3:
worker = new Boss(id, name, dId);
break;
default:
cout << "Invalid choice! Please enter 1, 2, or 3." << endl;
break;
}
//i
newSpace[this->m_workerNum + i] = worker;
cout << "Worker " << id << " (" << name << ") added successfully with job ID: " << dId << endl;
}
delete[] this->m_workerArray;
this->m_workerArray = newSpace;
this->m_workerNum = newSize;
//Update File is not empty
this->m_fileIsEmpty = false;
cout << "Add Succeed " << addNum << " new worker" << endl;
this->save();
}
else {
cout << "Error put in" << endl;
}
//清屏,回到上级目录
system("pause");
system("cls");
}
void EmployerManager::init_workerArray() {
ifstream ifs;
ifs.open(FILENAME, ios::in);
int id;
string name;
int dId;
int index = 0;
while (ifs >> id && ifs >> name && ifs >> dId) {
Worker* worker = NULL;
if (dId == 1) {
worker = new Employee(id, name, dId);
}
else if (dId == 2) {
worker = new Manager(id, name, dId);
}
else {
worker = new Boss(id, name, dId);
}
this->m_workerArray[index] = worker;
index++;
}
}
int EmployerManager::get_workerNum() {
ifstream ifs;
ifs.open(FILENAME, ios::in);
int id;
string name;
int dId;
int num = 0;
while (ifs >> id && ifs >> name && ifs >> dId) {
num++;
}
ifs.close();
return num;
}
void EmployerManager::save() {
ofstream ofs;
ofs.open(FILENAME, ios::out);
for (int i = 0; i < this->m_workerNum; i++) {
ofs << this->m_workerArray[i]->m_Id <<" "
<< this->m_workerArray[i]->m_Name <<" "
<< this->m_workerArray[i]->m_DeId << endl;
}
ofs.close();
}
void EmployerManager::show_worker() {
if (this->m_fileIsEmpty) {
cout << "File is not exist or is empty !" << endl;
}
else {
for (int i = 0; i < m_workerNum; i++) {
this->m_workerArray[i]->showInfo();
}
}
system("pause");
system("cls");
}
int EmployerManager::isExist(int id) {
int index = -1;
for (int i = 0; i < this->m_workerNum; i++) {
if (this->m_workerArray[i]->m_Id == id) {
index = i;
break;
}
}
return index;
}
void EmployerManager::delete_worker() {
if (this->m_fileIsEmpty) {
cout << "File is not exist or is empty" << endl;
}
else {
cout << "Put in the ID you want to delete" << endl;
int id = 0;
cin >> id;
int index = this->isExist(id);
if (index != -1) {
for (int i = index; i < this->m_workerNum - 1; i++) {
this->m_workerArray[i] = this->m_workerArray[i + 1];
}
this->m_workerNum--;
this->save();
cout << "Delete Succeed" << endl;
}
else {
cout << "Delete failed , can't find the id" << endl;
}
}
system("pause");
system("cls");
}
void EmployerManager::exitSystem() {
cout << "Welcome use next time" << endl;
system("pause");
exit(0);
}
void EmployerManager::modify_worker() {
if (this->m_fileIsEmpty) {
cout << "File is not exist or is empty" << endl;
}
else {
cout << "Put in the ID you want to modify" << endl;
int id;
cin >> id;
int ret = this->isExist(id);
if (ret != -1) {
delete this->m_workerArray[ret];
int newId = 0;
string newName = "";
int newdId = 0;
cout << "Find id: " << id << " worker,Please put in newId" << endl;
cin >> newId;
cout << "Please put in newName" << endl;
cin >> newName;
cout << R"(Please choose the worker's job
1,Employee
2,Manager
3,Boss)" << endl;
Worker* worker = NULL;
cin >> newdId;
switch (newdId) {
case 1:
worker = new Employee(newId, newName, newdId);
break;
case 2:
worker = new Manager(newId, newName, newdId);
break;
case 3:
worker = new Boss(newId, newName, newdId);
break;
default:
cout << "Invalid choice! Please enter 1, 2, or 3." << endl;
break;
}
this->m_workerArray[ret] = worker;
cout << "Modify succeed " << this->m_workerArray[ret]->m_DeId << endl;
this->save();
}
else {
cout << "Modify Failed , There is no such man" << endl;
}
}
system("pause");
system("cls");
}
void EmployerManager::find_worker() {
if (this->m_fileIsEmpty) {
cout << "File is not exist or is empty" << endl;
}
else {
cout << "Please put in the way to look up" << endl;
cout << R"(1,Look up from id
2,Look up from name)" << endl;
int select;
cin >> select;
if (select == 1) {
int id;
cout << "Please put in the worker id want find" << endl;
cin >> id;
int ret = isExist(id);
if (ret != -1) {
cout << "Find succeed! The worker info :" << endl;
this->m_workerArray[ret]->showInfo();
}
else {
cout << "Find failed! No man is this id ." << endl;
}
}
else if (select == 2) {
string name;
cout << "Please put in the worker name want find" << endl;
cin >> name;
bool flag = false;
for (int i = 0; i < m_workerNum; i++) {
if (m_workerArray[i]->m_Name == name) {
cout << "Find succeed, worker id is: "
<< m_workerArray[i]->m_Id
<< "The detail infomation is : " << endl;
flag == true;
this->m_workerArray[i]->showInfo();
}
}
if (flag == false) {
cout << "Find failed! No man is this name ." << endl;
}
}
else {
cout << "Put in error!" << endl;
}
}
system("pause");
system("cls");
}
void EmployerManager::sort_worker() {
if (this->m_fileIsEmpty) {
cout << "File is not exist or is empty" << endl;
system("pause");
system("cls");
}
else {
cout << "Please choose sort way :" << endl;
cout << R"(1,id ascending order
2,id descending order
)" << endl;
int select = 0;
cin >> select;
for (int i = 0; i < m_workerNum; i++) {
int minOrMaxId = i;
for (int j = i + 1; j < this->m_workerNum; j++) {
if (select == 1) {
if (m_workerArray[minOrMaxId]->m_Id > m_workerArray[j]->m_Id) {
minOrMaxId = j;
}
}
else {
if (m_workerArray[minOrMaxId]->m_Id < m_workerArray[j]->m_Id) {
minOrMaxId = j;
}
}
}
if (i != minOrMaxId) {
Worker* temp = m_workerArray[i];
m_workerArray[i] = m_workerArray[minOrMaxId];
m_workerArray[minOrMaxId] = temp;
}
}
cout << "Sort succeed, sorted result is :" << endl;
this->save();
this->show_worker();
}
}
void EmployerManager::clean_file() {
cout << "Confirm clear? " << endl;
cout << "1,Yes" << endl;
cout << "2,No" << endl;
int select = 0;
cin >> select;
if (select == 1) {
ofstream ofs(FILENAME, ios::trunc);
ofs.close();
if (this->m_workerArray != NULL) {
for (int i = 0; i < this->m_workerNum; i++) {
if (this->m_workerArray[i] != NULL) {
delete this->m_workerArray[i];
}
}
this->m_workerNum = 0;
delete[] this->m_workerArray;
this->m_workerArray = NULL;
this->m_fileIsEmpty = true;
}
cout << "Clean succeed!" << endl;
}
system("pause");
system("cls");
}
EmployerManager::~EmployerManager() {
if (this->m_workerArray != NULL) {
delete[] this->m_workerArray;
this->m_workerArray = NULL;
}
}
employee.cpp
#include "employee.h"
#include<string>
Employee::Employee(int id, string name, int dId) {
this->m_Id = id;
this->m_Name = name;
this->m_DeId = dId;
}
void Employee::showInfo() {
cout << "职工编号: " << this->m_Id
<< "\t职工姓名: " << this->m_Name
<< "\t岗位: "<<this->getDeName()
<< "\t岗位职责: 完成经理交给的任务" << endl;
}
string Employee::getDeName() {
return "员工";
}
Manager.cpp
#include "manager.h"
Manager::Manager(int id, string name, int dId) {
this->m_Id = id;
this->m_Name = name;
this->m_DeId = dId;
}
void Manager::showInfo() {
cout << "职工编号: " << this->m_Id
<< "\t职工姓名: " << this->m_Name
<< "\t岗位: " << this->getDeName()
<< "\t岗位职责: 完成boss交给的任务,hand out undertake to employee" << endl;
}
string Manager::getDeName() {
return "Manager";
}
Boss.cpp
#include "boss.h"
Boss::Boss(int id, string name, int dId) {
this->m_Id = id;
this->m_Name = name;
this->m_DeId = dId;
}
void Boss::showInfo() {
cout << "职工编号: " << this->m_Id
<< "\t职工姓名: " << this->m_Name
<< "\t岗位: " << this->getDeName()
<< "\t岗位职责: Control interprize everything" << endl;
}
string Boss::getDeName() {
return "Boss";
}
EMSmain.cpp
#include<iostream>
#include<string>
#include "EMS.h"
using namespace std;
#include "employee.h"
#include"manager.h"
#include"boss.h"
void test() {
//使用父类指针指向子类对象,体现多态性
Worker* worker = NULL;
worker = new Employee(1, "zhangsan", 1);
worker->showInfo();
delete worker;
worker = new Manager(1, "LiSi", 1);
worker->showInfo();
delete worker;
worker = new Boss(1, "WangWu", 1);
worker->showInfo();
delete worker;
/*Employee e1(1, "张十三", 2);
e1.showInfo();*/
}
int main() {
/*test();*/
EmployerManager em;
int choice = 0;
while (true) {
em.showMenu();
cout << "请输入你的选择操作" << endl;
cin >> choice;
switch (choice) {
case 0:
em.exitSystem();
break;
case 1:
em.add_worker();
break;
case 2:
em.show_worker();
break;
case 3:
em.delete_worker();
break;
case 4:
em.modify_worker();
break;
case 5:
em.find_worker();
break;
case 6:
em.sort_worker();
break;
case 7:
em.clean_file();
break;
default:
system("cls");
break;
}
}
system("pause");
return 0;
}
相关代码上传至github:https://github.com/kair998/EmployerManagerSystem