黑马程序员C++学习总结【基础篇】
黑马程序员C++学习总结【核心篇】
黑马程序员C++学习总结【进阶篇】
黑马程序员C++学习总结【核心篇】
一、内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
1. 程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
(1)代码区
- 存放 CPU 执行的机器指令.
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可.
- 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
编译后,再次更改源码,不再次编译的话,执行结果仍然是改之前的.
(2)全局区
- 全局变量和静态变量存放在此.
- 全局区还包含了常量区, 字符串常量和其他常量(const修饰的全局变量) 也存放在此.
- 该区域的数据在程序结束后由操作系统释放.
√ 全局变量:定义在函数之外的变量,即它们位于所有函数之外,通常在程序的顶部。
√ 局部变量:定义在函数或方法内部的变量。(定义在main函数中的变量也是局部变量)
2. 程序运行后
(1)栈区
- 由编译器自动分配释放,存放函数的参数值,局部变量等.
- 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放.
int * func()
{
int a = 10;
return &a;
}
int main() {
int *p = func();
//两次输出结果不一样
cout << *p << endl; //输出10,第一次可以正确输出,是因为编译器做了保留
cout << *p << endl;//第二次输出乱码,编译器不再保留
system("pause");
return 0;
}
(2)堆区(new & delete)
- 在C++中主要利用new在堆区开辟内存
//语法: new 数据类型 (数据值)
int* a = new int(10);
//利用new创建的数据,会返回该数据对应的类型的指针
- 由程序员
delete
分配释放,若程序员不释放,程序结束时由操作系统回收
int* func()
{
int* a = new int(10);
return a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
//利用delete释放堆区数据
delete p;
cout << *p << endl; //报错,释放的空间不可访问
system("pause");
return 0;
}
//堆区开辟数组
int main() {
//利用new创建的数据,会返回该数据对应的类型的指针
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 后加 []
delete[] arr;
//若为 delete arr;则只释放数组第一个元素的内存
system("pause");
return 0;
}
二、同呼吸,共命运:引用
引用的作用是给变量起别名,语法格式为数据类型 &别名 = 原名
。定义引用后,别名和变量指向同一个内存空间。
(结构体和类可以看成数据结构,所以结构体和类也能引用)
**引用定义及注意事项**
int a = 10;
int b = 20;
//int &c; //错误,引用必须初始化
int &c = a;
int &c = b;//报错,引用一旦初始化后,就不可以更改
c = b; //这是赋值操作,不是更改引用
1.引用做函数参数
**引用做函数参数**
//函数传参时,可以利用引用的技术让形参修饰实参,可以简化指针修改实参
//通过引用参数产生的效果同按地址传递是一样的。
//引用不会再拷贝数据,节省内存
void mySwap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
mySwap03(a, b);
cout << "a:" << a << " b:" << b << endl;
//输出a=20,b=10;
system("pause");
return 0;
}
引用的在c++内部实现的本质是一个指针常量,所以参数传递效果类似于地址传递
//发现是引用,编译器转换为 int* const ref = &a; void func(int& ref){ ref = 100; // ref是引用,转换为*ref = 100 } int main(){ int a = 10; //自动转换为 int* const ref = &a; 指针常量是指针指向不可改,所以引用指向不可更改,但引用值可以更改 int& ref = a; ref = 20; //内部发现ref是引用,自动帮我们转换为: >*ref = 20; // a和ref都输出20 cout << "a:" << a << endl; cout << "ref:" << ref << endl; // a和ref都为100 func(a); return 0; }
C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
2.引用做函数返回值
**引用做函数返回值**
//引用是可以作为函数的返回值存在的,不要返回局部变量引用
//用法:函数调用作为左值
//返回局部变量(栈区)引用
int& test01() {
int a = 10; //局部变量
return a;
}
//返回静态变量(全局区)引用
int& test02() {
static int a = 20;
return a;
}
int main() {
//不能返回局部变量的引用
int& ref = test01();
//两次输出结果不一样,第一次正确(编译器保留了一次),第二次乱码
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
//返回静态变量(全局区)引用,两次输出结果相同
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
//如果函数做左值,那么必须返回引用
test02() = 1000;
//返回静态变量(全局区)引用,两次输出结果相同
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
system("pause");
return 0;
}
一般情况下,函数返回值可以做左值吗?
在C和C++等语言中,函数的返回值通常不能作为左值(lvalue)。左值指的是那些可以出现在赋值语句左边的表达式,即它们代表一个存储位置,可以被赋予新的值。而函数的返回值通常是一个临时值,它没有固定的存储位置,并且在表达式求值结束后就会被销毁。因此,尝试将函数返回值作为左值并赋值会导致编译错误。
3.常量引用
**常量引用**
//常量引用主要用来修饰形参,防止误操作
//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
//v += 10;
cout << v << endl;
}
int main() {
//int& ref = 10; 引用本身需要一个合法的内存空间,10表示一个临时的、不可寻址的值,因此这行错误;
//加入const就可以了,编译器优化代码: int temp = 10; const int& ref = temp;
const int& ref = 10;
//ref = 100; //报错,加入const后不可以修改变量
cout << ref << endl;
//函数中利用常量引用防止误操作修改实参
int a = 10;
showValue(a);
system("pause");
return 0;
}
- 引用在c++内部实现的本质是一个指针常量,
int& ref = a;
编译器自动转换为int* const ref = &a;
指针 / 引用指向不可以改,指针 / 引用指向的值可以更改.- 常量引用在c++内部实现的本质是一个常量指针常量,
const int& ref = a;
编译器自动转换为const int* const ref = &a;
指针 / 引用指向和指向的值都不可以改.
三、函数提高
1.函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的,语法格式为返回值类型 函数名 (参数= 默认值){}
int func(int a, int b = 10, int c = 10) {
return a + b + c;
}
//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值(如果只是前面有后面没有,例如int func(int a, int b = 10, int c ) ,那么在调用时func(10,20),编译器不知道20是给b还是给c
//2. 如果函数声明有默认值,函数定义的时候就不能有默认参数,即函数声明和函数定义默认值只能占一个.(如果函数声明和函数定义默认值取得不一样,编译器傻了,不知道用哪个,出现二义性)
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
return a + b;
}
int main() {
cout << "ret = " << func(20, 20) << endl;
cout << "ret = " << func(100) << endl;
system("pause");
return 0;
}
2.函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置,语法格式为返回值类型 函数名 (数据类型){}
//函数占位参数
void func(int a, int) {
cout << "this is func" << endl;
}
//占位参数也可以有默认参数void func(int a, int = 3)
int main() {
func(10,10); //占位参数必须填补
system("pause");
return 0;
}
3.函数重载
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 或者 个数不同 或者 顺序不同.
注意: 函数的返回值不可以作为函数重载的条件
//函数重载需要函数都在同一个作用域下
void func()
{
cout << "func 的调用!" << endl;
}
void func(int a) //个数不同
{
cout << "func (int a) 的调用!" << endl;
}
void func(double a) //类型不同
{
cout << "func (double a)的调用!" << endl;
}
void func(int a ,double b) //个数不同
{
cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a ,int b) //顺序不同
{
cout << "func (double a ,int b)的调用!" << endl;
}
//函数返回值不可以作为函数重载条件
//int func(double a, int b)
//{
// cout << "func (double a ,int b)的调用!" << endl;
//}
//因为func(double a, int b){...}、void func(double a ,int b)均能被func(a,b)调用,产生二义性
int main() {
func();
func(10);
func(3.14);
func(10,3.14);
func(3.14 , 10);
system("pause");
return 0;
}
函数重载注意事项
- 引用中是否有const作为重载条件
- 函数默认参数作为重载条件(需要避免)
//函数重载注意事项
//1、引用作为重载条件
void func(int &a)
{
cout << "func (int &a) 调用 " << endl;
}
void func(const int &a)
{
cout << "func (const int &a) 调用 " << endl;
}
//2、函数重载碰到函数默认参数
void func2(int a, int b = 10)
{
cout << "func2(int a, int b = 10) 调用" << endl;
}
void func2(int a)
{
cout << "func2(int a) 调用" << endl;
}
int main() {
int a = 10;
func(a); //调用无const (int & a=a)
func(10);//调用有const (const int & a=10) int & a=10会报错,只能调用有const
//func2(10); //碰到默认参数产生歧义,需要避免
system("pause");
return 0;
}
四、类和对象
C++面向对象的三大特性为:封装、继承、多态
1.封装
在设计类的时候,属性和行为写在一起,表现事物,语法: class 类名{ 访问权限: 属性 / 行为 };
//三种权限
//公共权限 public 类内可以访问 类外可以访问
//保护权限 protected 类内可以访问 类外不可以访问
//私有权限 private 类内可以访问 类外不可以访问
//类内不管啥属性,都可以互相访问
struct和class区别是啥?
在C++中 struct和class唯一的区别就在于默认的访问权限不同:
- struct 默认权限为公共
- class 默认权限为私有
控制读写权限:将所有成员属性设置为私有,可以自己控制读写权限:
class Person { public: //姓名设置可读可写 void setName(string name) { m_Name = name; } string getName() { return m_Name; } //年龄只读 int getAge() { return m_Age; } //idole设置为只写 void setIdole(string idole) { m_Idole = idole; } private: string m_Name; //可读可写 姓名 int m_Age; //只读 年龄 string m_Idole; //只写 偶像 }; int main() { Person p; //姓名设置 p.setName("张三"); cout << "姓名: " << p.getName() << endl; //年龄读取 cout << "年龄: " << p.getAge() << endl; //idole设置 p.setIdole("IU"); //cout << "偶像: " << p.m_Idole << endl; //只写属性,不可以读取 system("pause"); return 0; }
2.构造函数&析构函数
- c++利用了构造函数(主要作用在于创建对象时为对象的成员属性赋值)和析构函数(主要作用在于对象销毁前系统自动调用,执行一些清理工作)完成对象初始化和清理工作,这两个函数将会被编译器自动调用,无须手动调用.
- 如果我们不提供构造和析构,编译器会提供,编译器提供的构造函数和析构函数是空实现。
- 构造函数按对象在代码中出现的顺序进行构造,而析构函数的析构顺序与之相反.
构造函数语法:
类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:
~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
(1)构造函数的分类及调用
两种分类方式:
- 按参数分为: 有参构造和无参构造(默认)
- 按类型分为: 普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
class Person { public: //无参(默认)构造函数 Person() { cout << "无参构造函数!" << endl; } //有参构造函数 Person(int a) { age = a; cout << "有参构造函数!" << endl; } //拷贝构造函数 //拷贝构造函数语法格式 类名(const 类名& 对象名) {...} Person(const Person& p) { age = p.age; cout << "拷贝构造函数!" << endl; } //析构函数 ~Person() { cout << "析构函数!" << endl; } public: int age; }; //2、构造函数的调用 //调用无参构造函数 void test01() { Person p; //调用无参构造函数 //注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明 Person p(); } //调用有参的构造函数 void test02() { //2.1 括号法,常用 Person p1(10); //有参构造 Person p5(p1); //拷贝构造 //2.2 显式法 Person p2 = Person(10); //有参构造 Person p3 = Person(p2); //拷贝构造 //Person(10)单独写就是匿名对象 当前行结束之后,马上析构 //2.3 隐式转换法 Person p4 = 10; // Person p4 = Person(10); Person p5 = p4; // Person p5 = Person(p4); //注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明 Person(p2);等效于Person p2 调用无参构造函数,与上诉有参构造发生重定义,报错 } int main() { test01(); //test02(); system("pause"); return 0; }
(2)拷贝构造函数调用时机
先定义各种构造函数和析构函数
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; };
C++中拷贝构造函数调用时机通常有三种情况:
- 使用一个已经创建完毕的对象来初始化一个新对象
void test01() { Person man(100); //man对象已经创建完毕 Person newman(man); //调用拷贝构造函数 (括号法) Person newman2 = man; //拷贝构造(隐式调用) //Person newman3;//已经调用默认构造函数 //newman3 = man; //不是调用拷贝构造函数,赋值操作(运算符重载) //拷贝构造函数是在创建对象时用来初始化对象的,而赋值运算符则是在对象已经存在时用来改变对象状态的 }
- 值传递的方式给函数参数传值
//相当于Person p1 = p; (隐式调用拷贝构造函数) void doWork(Person p1) { } void test02() { Person p; //无参构造函数 doWork(p); }
- 以值方式返回局部对象
Person doWork2() { Person p1; cout << (int *)&p1 << endl; //强制转换,把p1地址强制转换成整数指针 return p1; } void test03() { Person p = doWork2(); cout << (int *)&p << endl; }
(3)构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
- 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
(4)深拷贝与浅拷贝
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
class Person { public: //无参(默认)构造函数 Person() { cout << "无参构造函数!" << endl; } //有参构造函数 Person(int age ,int height) { cout << "有参构造函数!" << endl; m_age = age; m_height = new int(height); //自己在堆区开辟空间 } //深拷贝构造函数 Person(const Person& p) { cout << "深拷贝构造函数!" << endl; //如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题 m_age = p.m_age; m_height = new int(*p.m_height); } //析构函数 ~Person() { cout << "析构函数!" << endl; if (m_height != NULL) { delete m_height; } } public: int m_age; int* m_height; //注意m_height类型为指针 }; void test01() { Person p1(18, 180); //如果不自定义深拷贝构造函数的话,编译器会自动调用默认拷贝构造函数(浅拷贝) Person p2(p1); cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl; cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl; } int main() { test01(); //执行完test01()后释放空间,先析构p2没啥问题,此时已经把指向内存释放了,再析构p1的时候会报错 system("pause"); return 0; }
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
3.初始化列表
C++提供了初始化列表语法,用来初始化属性,语法格式构造函数():属性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(20), m_C(30) {}
//可以自己设置m_A;m_B;m_C,则需要添加变量a,b,c
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {
}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main() {
Person p(1, 2, 3);
p.PrintPerson();
system("pause");
return 0;
}
4.对象成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员,构造的顺序是 :先调用对象成员的构造,再调用本类构造;析构顺序与之相反。
class A {
}
class B
{
A a;
}
//B类中有A类对象a作为成员,a为对象成员
class Phone
{
public:
Phone(string name)
{
m_PhoneName = name;
cout << "Phone构造" << endl;
}
~Phone()
{
cout << "Phone析构" << endl;
}
string m_PhoneName;
};
class Person
{
public:
//(初始化列表)构造函数
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person构造" << endl;
}
~Person()
{
cout << "Person析构" << endl;
}
void playGame()
{
cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
}
string m_Name;
Phone m_Phone;
};
void test01()
{
//当类中成员是其他类对象时,我们称该成员为 对象成员
//构造的顺序是 :先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
Person p("张三" , "苹果X");
p.playGame();
}
int main() {
test01();
system("pause");
return 0;
}
5.静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。静态成员属于类,不属于该类的任何一个对象而独立存在。
静态成员分为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
**静态成员变量**
class Person
{
public:
static int m_A; //类内声明 静态成员变量
//静态成员变量特点:
//1 在编译阶段分配内存,分配到全局区
//2 类内声明,类外初始化
//3 所有对象共享同一份数据
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();
system("pause");
return 0;
}
**静态成员函数**
class Person
{
public:
//静态成员函数特点:
//1 程序共享一个函数
//2 静态成员函数只能访问静态成员变量
static void func()
{
cout << "func调用" << endl;
m_A = 100;
//m_B = 100; //错误,不可以访问非静态成员变量
//静态成员函数是不属于任何一个对象的,如果在静态成员函数里面调用非静态成员变量,编译器无法识别要改的是哪个对象的成员的非静态成员变量
//静态static成员函数它只属于类本身不属于每一个对象实例,独立存在。非静态成员,仅当实例化对象之后才存在。静态成员函数产生在前,非静态成员产生在后,静态函数无法访问一个不存在的东西。
}
static int m_A; //静态成员变量
int m_B; // 普通成员变量
private:
//静态成员函数也是有访问权限的
static void func2()
{
cout << "func2调用" << endl;
}
};
int Person::m_A = 10; //静态数据成员类外初始化
void test01()
{
//静态成员变量两种访问方式
//1、通过对象
Person p1;
p1.func();
//2、通过类名
Person::func();
//Person::func2(); //私有权限访问不到
}
int main() {
test01();
system("pause");
return 0;
}
6.成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上(即sizeof(一个类的的对象 / 类),假设类中有成员变量,成员函数,静态成员变量,静态成员函数,最终sizeof输出值显示只存储了非静态成员变量)。
class Person {
public:
Person() {
mA = 0;
}
//非静态成员变量占对象空间
int mA;
//静态成员变量不占对象空间
static int mB;
//函数也不占对象空间,所有函数共享一个函数实例
void func() {
cout << "mA:" << this->mA << endl;
}
//静态成员函数也不占对象空间
static void sfunc() {
}
};
int main() {
cout << sizeof(Person) << endl;//输出值为4,int mA;所占用的空间
system("pause");
return 0;
}
空对象占用内存空间为多少?
class person{ };
person p;
cout << sizeof(p)
输出1,空对象占用内存空间为1;
C++会给每个空对象分配1个内存空间,是为了区分不同空对象占用内存的位置.
每个空对象也有独一无二的内存地址
7.this指针
同引用,this指针的本质也是指针常量.(this指针不可以修改指针指向,但this指向的值是可以修改的)
在C++中成员变量和成员函数是分开存储的,每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。
这一块代码是如何区分是哪个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。
this指针是隐含在每一个非静态成员函数内的一种指针,this指针不需要定义,直接使用即可。
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this(this本身是一种指针,*this解引用,返回对象本身)
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分;
//如果不加this,最后会输出乱码,即编译器认为成员变量age和Person构造函数里的3个age(形参+构造函数体内的age=age)不是一个东西
this->age = age;
}
//加了引用&表示PersonAddPerson返回的对象和p2占用同一个内存,最后输出40
//如果不加&,则编译器调用默认拷贝构造函数,进行值返回,然而返回的新对象已经和原来的p2不占同一个内存空间,也就是说下面程序链式调用了3次,最后生成了3个不同的新对象.
//不加&,最后输出20.既然生成3个新对象,为什么输出不是原来的P2,反而还加成功了一次呢?其实,过程是:调用默认拷贝构造函数,this->age += p.age;已经this指针仍然是指向p2,return *this返回一个新的对象
Person& PersonAddPerson(Person p)
{
this->age += p.age;
//返回对象本身
return *this;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
//链式编程思想(cout利用<<一直输出也是这种思想)
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout << "p2.age = " << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
8.空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针(如果用到,程序会崩掉)
//空指针访问成员函数
class Person {
public:
void ShowClassName() {
cout << "我是Person类!" << endl;
}
void ShowPerson() {
//如果用到this指针,需要加以判断保证代码的健壮性 如果为空指针直接返回,程序不会崩
if (this == NULL) {
return;
}
cout << mAge << endl; //这里隐含了this,完整的应该是cout <<this->mAge << endl;编译器找不到具体的对象,因此会崩掉
}
public:
int mAge;
};
void test01()
{
Person * p = NULL;
p->ShowClassName(); //正常输出;空指针,可以调用成员函数
p->ShowPerson(); //程序崩掉;但是如果成员函数中用到了this指针,就不可以了
}
int main() {
test01();
system("pause");
return 0;
}
9.常对象&常函数
常函数:
- 成员函数后加const后我们称为这个函数为常函数
- 常函数内不可以修改成员属性(本来成员函数自带this指针,this指针本质是指针常量,不能修改指针指向,但可以修改指针指向的值,加了const后,指针指向的值也不可以修改)
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改指针指向的值(指针指向依然不可以修改)
常对象:
- 声明对象前加const称该对象为常对象
- 常对象不能修改成员变量值
- 常对象只能调用常函数
但在成员变量前加上
mutable
例如mutable int m_B;
,不管是常函数还是常变量,还是可以调用修改成员变量的值.
class Person {
public:
Person() {
m_A = 0;
m_B = 0;
}
//this指针的本质是一个指针常量,指针的指向不可修改
//如果想让指针指向的值也不可以修改,需要声明常函数
void ShowPerson() const {
//const Type* const pointer(this);
//this = NULL; //不能修改指针的指向 Person* const this;
//this->mA = 100; //但是this指针指向的对象的数据是可以修改的
//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
this->m_B = 100; //报错
}
void MyFunc() {
}
public:
int m_A;
mutable int m_B; //可修改 可变的
};
//const修饰对象 常对象
void test01() {
const Person person; //常量对象
cout << person.m_A << endl;
//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
//常对象访问成员函数
person.MyFunc(); //报错,常对象不能调用非const的函数
}
int main() {
test01();
system("pause");
return 0;
}
10.友元
在程序里,有些私有属性(private)也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。友元的目的就是让一个函数或者类访问另一个类中私有成员。友元的关键字为friend
友元的三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
(1)全局函数做友元
**全局函数做友元**
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)
{
cout << "好基友正在访问: " << building->m_SittingRoom << endl;
cout << "好基友正在访问: " << building->m_BedRoom << endl;
}
void test01()
{
Building b;
goodGay(&b);
}
int main(){
test01();
system("pause");
return 0;
}
(2)类做友元
**类做友元**
class Building;
class goodGay
{
public:
goodGay();
void visit();
private:
Building *building;
};
class Building
{
//告诉编译器 goodGay类是Building类的好朋友,可以访问到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();
}
int main(){
test01();
system("pause");
return 0;
}
(3)成员函数做友元
**成员函数做友元**
class Building;
class goodGay
{
public:
goodGay();
void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
void visit2();
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 = "卧室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void goodGay::visit2()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
//cout << "好基友正在访问" << building->m_BedRoom << endl;为声明friend,访问私有成员报错
}
void test01()
{
goodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}
11.运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
- 对于内置的数据类型的表达式的的运算符是不可能改变的
- 不要滥用运算符重载(比如重载+,输出的结果却是相-)
(1)加号运算符重载
实现两个自定义数据类型相加的运算.
class Person {
public:
Person() {
};
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数实现 + 号运算符重载 operator+
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;
}
public:
int m_A;
int m_B;
};
//全局函数实现 + 号运算符重载 operator+函数名
//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;
//}
//运算符重载 可以发生函数重载 operator+函数名
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;
}
void test() {
Person p1(10, 10);
Person p2(20, 20);
//成员函数方式
Person p3 = p2 + p1; //相当于 p2.operaor+(p1),编译器允许简写为p3 = p2 + p1;
cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
Person p4 = p3 + 10; //相当于 operator+(p3,10)
cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
}
int main() {
test();
system("pause");
return 0;
}
(2)左移运算符重载
class Person {
//重载左移运算符配合友元可以实现输出私有数据类型
friend ostream& operator<<(ostream& out, Person& p);
public:
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数 实现不了 p << cout 不是我们想要的效果
//void operator<<(ostream cout){ }
//p.operator<<(cout) 简化版本为 p << cout,不符合习惯
private:
int m_A;
int m_B;
};
//全局函数实现左移重载
//cout的数据类型为ostream,输出流
//返回值+&表示ostream对象只能有一个,及每次左移后返回同一个cout
ostream& operator<<(ostream& out, Person& p) {
out << "a:" << p.m_A << " b:" << p.m_B;
return out;
}
void test() {
Person p1(10, 20);
cout << p1 << "hello world" << endl; //链式编程(重载函数返回值数据类型为ostream&)
}
int main() {
test();
system("pause");
return 0;
}
(3)递增运算符重载
前置递增返回引用(链式++),后置递增返回值(不能返回局部变量的地址).
class MyInteger {
friend ostream& operator<<(ostream& out, MyInteger myint);
public:
MyInteger() {
m_Num = 0;
}
//前置++
//加&可以链式编程,即++(++myint)
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this; //this本质是指针常量,解引用后为指针所指的对象
}
//后置++
//加int是和前置++重载,int为占位参数
//返回值不加引用,是因为temp为局部变量,用完立马被释放(不能返回局部变量的地址)
//如果想用引用实现链式递增,可以把用new在堆区创建变量temp,然后手动析构delete
MyInteger operator++(int) {
//不能先返回,否则函数直接结束,不+1;可以先用一个temp把加之前的数据存储起来
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& out, MyInteger myint) {
out << myint.m_Num;
return out;
}
//前置++ 先++ 再返回
void test01() {
MyInteger myInt;
cout << ++myInt << endl;
cout << myInt << endl;
}
//后置++ 先返回 再++
void test02() {
MyInteger myInt;
cout << myInt++ << endl;
cout << myInt << endl;
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
(4)赋值运算符重载
c++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题。编译器默认的赋值为浅拷贝,需要赋值运算符重载实现深拷贝.
class Person
{
public:
Person(int age)
{
//将年龄数据开辟到堆区
m_Age = new int(age);
}
//重载赋值运算符
//返回值加引用,是为了可以链式编程,实现连等操作a=b=c
Person& operator=(Person &p)
{
//由于对象在定义时,上述构造函数会自动在堆区分布内存,需要先清空
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//编译器提供的代码是浅拷贝,虽然调用后会正常输出值,但在程序结束调用析构函数时会发生内存重复释放,导致程序崩掉.
//m_Age = p.m_Age;
//提供深拷贝 解决浅拷贝的问题
m_Age = new int(*p.m_Age);
//返回自身
return *this;
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//年龄的指针
int *m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1; //赋值操作
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
(5)关系运算符重载
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->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;
}
}
bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
string m_Name;
int m_Age;
};
void test01()
{
//int a = 0;
//int b = 0;
Person a("孙悟空", 18);
Person b("孙悟空", 18);
if (a == b)
{
cout << "a和b相等" << endl;
}
else
{
cout << "a和b不相等" << endl;
}
if (a != b)
{
cout << "a和b不相等" << endl;
}
else
{
cout << "a和b相等" << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
(6)仿函数
- 函数调用运算符 () 也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
class MyPrint
{
public:
void operator()(string text)
{
cout << text << endl;
}
};
void test01()
{
//重载的()操作符 也称为仿函数
MyPrint myFunc;
myFunc("hello world");
}
class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
};
void test02()
{
MyAdd add;
int ret = add(10, 10);
cout << "ret = " << ret << endl;
//匿名对象调用
//MyAdd()(100,100)中MyAdd()为匿名对象,本行执行完立马释放,(100,100)为重载函数参数
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
12.继承
- 继承的好处:可以减少重复的代码。
class A : public B;
(A 类称为子类 或 派生类;B 类称为父类 或 基类) - 派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员;从基类继承过过来的表现其共性,而新增的成员体现了其个性。
(1) 继承方式
父类中私有成员虽然在子类中不可访问,但也是被子类继承下去了,只是由编译器给隐藏后访问不到.
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};
//公共继承
class Son :public Base
{
public:
int m_D;
};
void test01()
{
cout << "sizeof Son = " << sizeof(Son) << endl;
//最后的输出结果是16,即4 x 4=16
}
(2)子类和父类的构造、析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数(即编译器也会先创建一个父类对象)。继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。
(3)继承同名成员处理方式
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
总结:
- 子类对象可以直接访问到子类中同名成员变量
- 子类对象加作用域可以访问到父类同名成员变量
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中的所有同名成员函数(包括与子类发生重载的同名函数),加作用域可以访问到父类中同名函数
class Base {
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()调用" << endl;
}
void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}
public:
int m_A;
};
class Son : public Base {
public:
Son()
{
m_A = 200;
}
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
void func()
{
cout << "Son - func()调用" << endl;
}
public:
int m_A;
};
void test01()
{
Son s;
cout << "Son下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base::m_A << endl;
s.func(); //输出:Son - func()调用
s.Base::func(); //输出:Base - func()调用
s.Base::func(10);//输出:Base - func(int a)调用
//若为s.func(10); //输出仍是子类的无参函数func():s.Base::func(10);
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
//这里,如果程序中的代码执行没有问题,main 函数会返回 EXIT_SUCCESS,这通常对应于整数值 0。
//如果程序遇到错误或异常情况,可能会返回 EXIT_FAILURE(也是定义在 <stdlib.h> 或 <cstdlib> 中的,通常其值为 1),以表示程序未能成功完成其任务。
}
(4)继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)。
class Base {
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
//静态成员函数重载
static void func(int a)
{
cout << "Base - static void func(int a)" << endl;
}
static int m_A;
};
//静态成员变量类内声明,类外初始化
int Base::m_A = 100;
class Son : public Base {
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A;
};
int Son::m_A = 200;
//同名成员属性
void test01()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
//通过类名访问
cout << "通过类名访问: " << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
//也可以通过Base类名直接访问 cout << "Base 下 m_A = " <<Base::m_A << endl;
//Son::Base::m_A中Son::表示通过Son类名访问,Base::指定访问作用域
}
//同名成员函数
void test02()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
s.func();
s.Base::func();
cout << "通过类名访问: " << endl;
Son::func();
Son::Base::func();
//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
Son::Base::func(100);
//Son::func(100);访问的仍然是Son中的函数func
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
(5)多继承语法
C++允许一个类继承多个类,语法格式class 子类 :继承方式 父类1 , 继承方式 父类2
,多继承中如果父类中出现了同名情况,子类使用时候要加作用域。C++实际开发中不建议用多继承。
class Base1 {
public:
Base1()
{
m_A = 100;
}
public:
int m_A;
};
class Base2 {
public:
Base2()
{
//m_B = 200;
m_A = 200; //开始是m_B 不会出问题,但是改为mA就会出现不明确
}
public:
int m_A;
};
//语法:class 子类:继承方式 父类1 ,继承方式 父类2
class Son : public Base2, public Base1
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
public:
int m_C;
int m_D;
};
//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl; //输出值16
//如果直接s.m_A会报错,编译器不知道访问哪个基类的m_A
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
}
int main() {
test01();
system("pause");
return 0;
}
(6)菱形继承 / 钻石继承-虚基类
菱形继承概念:
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承
(注:羊和骆驼存在生殖隔离,此处继承仅在名字上)
菱形继承问题:
- 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性(可以通过添加作用域解决)。
- 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以(使用虚继承)。
class Animal
{
public:
int m_Age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {
};
class Tuo : virtual public Animal {
};
class SheepTuo : public Sheep, public Tuo {
};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 100;
st.Tuo::m_Age = 200;
//由于使用了虚继承,所以SheepTuo中数据m_Age只有一份(只存在虚基类的m_Age)
//下面st.Tuo::m_Age的赋值会把上面的覆盖掉
//输出值相同,均是200
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << "st.m_Age = " << st.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义, 利用虚继承可以解决菱形继承问题。虚继承内在原理如下:
直接继承:
虚继承:
13.多态-虚函数
多态分为两类
- 静态多态: 函数重载和运算符重载属于静态多态,复用函数名
- 动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
class Animal
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
//不加virtual,sizeof(Animal)=1,即空类占用内存为1(只有非静态成员变量占用类/类的对象的内存空间)
//加上virtual后,sizeof(Animal)=4,出现一个虚函数指针vfptr;
virtual void speak() //此处定义虚函数必须有virtual
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
//子类重写父类中的虚函数,此时vitual可有可无
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
//子类重写父类中的虚函数
void speak()
{
cout << "小狗在说话" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void DoSpeak(Animal & animal)
{
animal.speak();
}
//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用条件:
//父类指针或引用指向子类对象
void test01()
{
//如果不使用虚函数,就是静态联编,在编译时地址已经确定
//不使用虚函数,DoSpeak(cat); DoSpeak(dog);均输出:动物在说话
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
多态(虚函数)满足条件
- 有继承关系
- 子类重写父类中的虚函数 (重写:函数返回值类型 函数名 参数列表 完全一致称为重写)
多态——虚函数使用条件【(纯)虚函数/(纯)虚析构函数理解核心】
- 父类指针或引用指向子类对象
(1)虚函数内在原理
虚函数内在原理
(1)Animal类的void speak()函数未加vitual,此时该空类占用一个byte内存空间
(2)Animal类的void speak()函数加vitual后,多了一个虚函数指针vfptr(占4个byte内存),指向虚函数表vftable,虚函数表vftable中存放地址&Animal::speak.
(3)当Cat中没有重写函数void speak(),由于继承,所以该类占4个占4个byte内存,但虚函数指针vfptr指向的虚函数表vftable中存放地址仍是&Animal::speak
(4)当Cat中重写函数void speak()后,虚函数指针vfptr指向的虚函数表vftable中存放地址变为&Cat::speak
(5)总体流程
(2)纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容。因此可以将虚函数改为纯虚函数。纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类。
抽象类特点:
- 无法实例化对象,但是可以定义一个指向该类的指针
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base
{
public:
//纯虚函数
//类中只要有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};
class Son :public Base
{
public:
virtual void func()
{
cout << "func调用" << endl;
};
};
void test01()
{
Base * base = NULL;
//Base* base = new Base; // 错误,抽象类无法实例化对象(不管在堆区:Base* base = new Base还是栈区:Base base;)
base = new Son; //如果不重定义虚函数,Son也属于抽象类,也无法实例化对象
base->func();
delete base;//记得销毁
}
int main() {
test01();
system("pause");
return 0;
}
(3)虚析构和纯虚析构
- 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码(即程序执行完后只会调用父类析构,不会调用子类析构)。
- 解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现,不能光是一个声明。虚析构virtual ~类名(){};括号里得有东西;纯虚析构virtual ~类名() = 0;`得在类外定义
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
(纯)虚函数和(纯)虚析构区别:
(纯)虚函数函数体内可以没有具体的函数实现,(纯)虚析构必须有具体的函数实现
虚析构语法:
virtual ~类名(){ 具体的函数实现 };
纯虚析构语法:
类内:virtual ~类名() = 0;
类外:类名::~类名(){ 具体的函数实现 };
class Animal {
public:
Animal()
{
cout << "Animal 构造函数调用!" << endl;
}
virtual void Speak() = 0;
//析构函数加上virtual关键字,变成虚析构函数
//virtual ~Animal()
//{
// cout << "Animal虚析构函数调用!" << endl;
//}
//纯虚析构函数
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal 纯虚析构函数调用!" << endl;
}
//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。
class Cat : public Animal {
public:
Cat(string name)
{
cout << "Cat构造函数调用!" << endl;
m_Name = new string(name); //堆区开辟空间
}
virtual void Speak() //虚函数重定义vitual可以省略
{
cout << *m_Name << "小猫在说话!" << endl;
}
~Cat()
{
cout << "Cat析构函数调用!" << endl;
if (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
public:
string *m_Name;
};
void test01()
{
Animal *animal = new Cat("Tom"); //多态(虚函数)使用条件:父类指针或引用指向子类对象
animal->Speak();
//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
//怎么解决?给基类增加一个虚析构函数
//虚析构函数就是用来解决通过父类指针释放子类对象
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
总结:
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
(4)多态案例-电脑组装
#include<iostream>
using namespace std;
//抽象CPU类
class 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();
}
//提供析构函数 释放3个电脑零件
~Computer()
{
//释放CPU零件
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; //内存条零件指针
};
//具体厂商
//Intel厂商
class IntelCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Intel的CPU开始计算了!" << endl;
}
};
class IntelVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Intel的显卡开始显示了!" << endl;
}
};
class IntelMemory :public Memory
{
public:
virtual void storage()
{
cout << "Intel的内存条开始存储了!" << endl;
}
};
//Lenovo厂商
class LenovoCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Lenovo的CPU开始计算了!" << endl;
}
};
class LenovoVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Lenovo的显卡开始显示了!" << endl;
}
};
class LenovoMemory :public Memory
{
public:
virtual void storage()
{
cout << "Lenovo的内存条开始存储了!" << endl;
}
};
void test01()
{
//第一台电脑零件
//Computer对象成员CPU * intelCpu是父类,父类指针构建IntelCPU子类对象——>多态
CPU * intelCpu = new IntelCPU;
VideoCard * intelCard = new IntelVideoCard;
Memory * intelMem = new IntelMemory;
cout << "第一台电脑开始工作:" << endl;
//创建第一台电脑
Computer * computer1 = new Computer(intelCpu, intelCard, intelMem);
computer1->work();
delete computer1;
cout << "-----------------------" << endl;
cout << "第二台电脑开始工作:" << endl;
//第二台电脑组装
Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);;
computer2->work();
delete computer2;
cout << "-----------------------" << endl;
cout << "第三台电脑开始工作:" << endl;
//第三台电脑组装
Computer * computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);;
computer3->work();
delete computer3;
}
五、C++文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放。通过文件可以将数据持久化,C++中对文件操作需要包含头文件<fstream>
文件类型分为两种:
- 文本文件 - 文件以文本的ASCII码形式存储在计算机中.
- 二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们.
操作文件的三大类:
- ofstream:写操作
- ifstream: 读操作
- fstream : 读写操作
文件打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 (at end) |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
注意: 文件打开方式可以配合使用,利用|操作符
例如:二进制方式写文件 ios::binary | ios:: out
1.文本文件
(1)写文件
写文件步骤如下:
- 包含头文件
#include <fstream>
- 创建流对象
ofstream ofs;
- 打开文件
ofs.open("文件路径",打开方式);
- 写数据
ofs << "写入的数据";
- 关闭文件
ofs.close();
#include <fstream>
void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
总结:
- 文件操作必须包含头文件 fstream
- 写文件可以利用 ofstream ,或者fstream类
- 打开文件时候需要指定操作文件的路径,以及打开方式
- 利用<<可以向文件中写数据
- 操作完毕,要关闭文件
(2)读文件
读文件步骤如下:
- 包含头文件
#include <fstream>
- 创建流对象
ifstream ifs;
- 打开文件并判断文件是否打开成功
ifs.open("文件路径",打开方式);
- 读数据
四种方式读取
- 关闭文件
ifs.close();
#include <fstream>
#include <string>
void test01()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//第一种方式
//1024是因为上面文件内容一共占1kB
//char buf[1024] = { 0 };
//while (ifs >> buf)
//{
// cout << buf << endl;
//}
//第二种
//char buf[1024] = { 0 };
//while (ifs.getline(buf,sizeof(buf)))
//{
// cout << buf << endl;
//}
//第三种
string buf;
while (getline(ifs, buf))
{
cout << buf << endl;
}
//第四种 这种方法一个字符一个字符读,比较慢
//char c;
//while ((c = ifs.get()) != EOF) //EOF:end of file
//{
// cout << c;
//}
ifs.close();
}
int main() {
test01();
system("pause");
return 0;
}
总结:
- 读文件可以利用 ifstream ,或者fstream类
- 利用is_open函数可以判断文件是否打开成功
- close 关闭文件
2.二进制文件
以二进制的方式对文件进行读写操作,打开方式要指定为 ios::binary
(1)写文件
二进制方式写文件主要利用流对象调用成员函数write
.函数原型 :ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数.
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
//二进制文件 写文件
void test01()
{
//1、包含头文件
//2、创建输出流对象
ofstream ofs("person.txt", ios::out | ios::binary);
//3、打开文件 可以和上一步合二为一
//ofs.open("person.txt", ios::out | ios::binary);
Person p = {
"张三" , 18};
//4、写文件
ofs.write((const char *)&p, sizeof(p));
//5、关闭文件
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
(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 test01()
{
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}
Person p;
ifs.read((char *)&p, sizeof(p));
cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
六、职工管理系统
本教程主要利用C++来实现一个基于多态的职工管理系统。公司中职工分为三类:普通员工、经理、老板,显示信息时,需要显示职工编号、职工姓名、职工岗位、以及职责:
- 普通员工职责:完成经理交给的任务
- 经理职责:完成老板交给的任务,并下发任务给员工
- 老板职责:管理公司所有事务
管理系统中需要实现的功能如下:
- 退出管理程序:退出当前管理系统
- 增加职工信息:实现批量添加职工功能,将功能信息录入到文件中,职工信息为:职工编号、姓名、部门编号
- 显示职工信息:显示公司内部所有职工的信息
- 删除离职职工:按照编号删除指定的职工
- 修改职工信息:按照编号修改职工个人信息
- 查找职工信息:按照职工的编号或者职工的姓名进行查找相关的人员信息
- 按照编号排序:按照职工的编号,进行排序,排序规则由用户指定
- 清空所有文档:清空文件中记录的所有职工信息(清空前需要确认,防止误删)
代码采用了函数的分文件编写:
**worker.h**
#pragma once
#include<iostream>
using namespace std;
#include<string>
//职工的抽象类
class Worker
{
public:
//显示个人信息
virtual void ShowInfo() = 0;
//获取岗位名称
virtual string GetDeptName() = 0;
//职工编号
int m_Id;
//职工姓名
string m_Name;
//部门编号
int m_DeptId;
};
**employee.h**
#pragma once
#include<iostream>
#include"worker.h"
using namespace std;
//普通职工类
class Employee:public Worker
{
public://子类重写父类的虚函数or纯虚函数时,注意你写的是函数的声明还是函数的定义
//构造函数
Employee(int id, string name, int deptid);//属性初始化
//显示个人信息
virtual void ShowInfo();//子类重写父类的虚函数or纯虚函数,virtual可删可不删
//获取岗位名称
virtual string GetDeptName();
};
**boss.h**
#pragma once
#include<iostream>
#include"worker.h"
#include<string>
using namespace std;
//老板类
class Boss :public Worker
{
public:
//构造函数
Boss(int id, string name, int deptid);
//显示个人信息
virtual void ShowInfo();
//获取岗位名称
virtual string GetDeptName();
};
**manager.h**
#pragma once
#include<iostream>
#include"worker.h"
#include<string>
using namespace std;
//经理类
class Manager :public Worker
{
public:
//构造函数
Manager(int id, string name, int deptid);
//显示个人信息
virtual void ShowInfo();
//获取岗位名称
virtual string GetDeptName();
};
**workerManager.h**
#pragma once//防止头文件重复包含
#include<iostream>
#include<string>
#include"worker.h"
#include"employee.h"
#include"manager.h"
#include"boss.h"
#include<fstream>
#define FILENAME "test.txt"
using namespace std;//使用标准命名空间
class WorkerManager
{
public:
//构造函数
WorkerManager();
//展示菜单
void Show_Menu();
//退出程序
void ExitSystem();
//记录职工人数
int m_EmpNum;
//职工数组指针
Worker** m_EmpArray;
//添加职工
void AddEmp();
//保存文件
void SaveFile();
//判断文件是否为空标志
bool m_FileIsEmpty;
//统计文件中的人数
int Get_EmpNum();
//初始化职工
void Init_Emp();
//显示职工
void Show_Emp();
//判断职工是否存在
int IsExist(int id);
//删除职工
void Del_Emp();
//修改职工
void Mod_Emp();
//查找职工
void Find_Emp();
//员工排序
void Sort_Emp();
//清空数据
void Clean_File();
//析构函数
~WorkerManager();
};
**boss.cpp**
#include"boss.h"
//构造函数
Boss::Boss(int id, string name, int deptid)
{
this->m_Id = id;
this->m_Name = name;
this->m_DeptId = deptid;
}
//显示个人信息
void Boss::ShowInfo()
{
cout << "职工编号:" << this->m_Id
<< "\t职工姓名:" << this->m_Name
<< "\t岗位:" << this->GetDeptName()
<< "\t岗位职责:管理公司所有的事物" << endl;
}
//获取岗位名称
string Boss::GetDeptName()
{
return string("老板");
}
**employee.cpp**
#include"employee.h"
//构造函数
Employee::Employee(int id, string name, int deptid)
{
this->m_Id = id;
this->m_Name = name;
this->m_DeptId = deptid;
}
//显示个人信息
void Employee::ShowInfo()
{
cout << "职工编号:" << this->m_Id
<< "\t职工姓名:" << this->m_Name
<< "\t岗位:" << this->GetDeptName()
<<"\t岗位职责:完成经理交给的任务"<<endl;
}
//获取岗位名称
string Employee::GetDeptName()
{
return string("普通员工");
}
**Manager.cpp**
#include"manager.h"
//构造函数
Manager::Manager(int id, string name, int deptid)
{
this->m_Id = id;
this->m_Name = name;
this->m_DeptId = deptid;
}
//显示个人信息
void Manager::ShowInfo()
{
cout << "职工编号:" << this->m_Id
<< "\t职工姓名:" << this->m_Name
<< "\t岗位:" << this->GetDeptName()
<< "\t岗位职责:完成老板交个任务,并且下发任务给普通员工" << endl;
}
//获取岗位名称
string Manager::GetDeptName()
{
return string("经理");
}
**workerManager.cpp**
#include"workerManager.h"
//构造函数
WorkerManager::WorkerManager()
{
//1.文件不存在
ifstream ifs;
ifs.open(FILENAME, ios::in);
if (!ifs.is_open())
{
//初始化属性
//初始化记录人数为0
this->m_EmpNum = 0;
//初始化数组指针为空
this->m_EmpArray = NULL;
//初始化文件是否为空
this->m_FileIsEmpty = true;
ifs.close();
return;
}
//2.文件存在 数据为空
char ch;
ifs >> ch;
if (ifs.eof())
{
//初始化属性
//初始化记录人数为0
this->m_EmpNum = 0;
//初始化数组指针为空
this->m_EmpArray = NULL;
//初始化文件是否为空
this->m_FileIsEmpty = true;
ifs.close();
}
//3.文件存在不为空
int num = this->Get_EmpNum();
this->m_EmpNum = num;
//开辟空间,当文件中的数据存到数组中
this->m_EmpArray = new Worker * [this->m_EmpNum];
this->Init_Emp();
}
//展示菜单
void WorkerManager::Show_Menu()
{
cout <<"*****************************" << endl;
cout <<"*****欢迎使用职工管理系统*****" << endl;
cout <<"*******0-退出管理程序*********" << endl;
cout <<"*******1-增加职工信息*********" << endl;
cout <<"*******2-显示职工信息*********" << endl;
cout <<"*******3-删除离职职工*********" << endl;
cout <<"*******4-修改职工信息*********" << endl;
cout <<"*******5-查找职工信息*********" << endl;
cout <<"*******6-按照编号排序*********" << endl;
cout <<"*******7-清空所有文档*********" << endl;
cout <<"*****************************" << endl;
}
//退出系统
void WorkerManager::ExitSystem()
{
cout << "欢迎下次使用" << endl;
system("pause");
exit(0);
}
//添加职工
void WorkerManager::AddEmp()
{
cout << "请输入添加职工的数量" << endl;
int addnum = 0;//保存用户输入的数量
cin >> addnum;
if (addnum > 0)
{
//计算添加所需新空间的大小
int NewSize = this->m_EmpNum + addnum;//先在里面的人数等于原来的+新添加的
//开辟新空间——动态数组
Worker** NewSpace = new Worker * [NewSize];
//将原来空间下的数据拷贝到新空间下
if (this->m_EmpArray != NULL)
{
for (int i = 0; i < this->m_EmpNum; i++)
{
NewSpace[i] = this->m_EmpArray[i];
}
}
//添加新的数据
for(int i = 0; i< addnum;i++)
{
int id = 0;//职工编号
string name;//职工姓名
int dselect;//部门选择
cout << "请输入第" <<i+1<<"个新职工的编号"<< endl;
/*
这里的判断输入重复是有缺陷的,例如我们要添加2个新员工,如果输入的第二个人和第一个人的编号一样,
这样就判断不出来重复了,:(
*/
while (cin>>id)
{
int adjust = 0;
for (int j = 0; j <this->m_EmpNum; j++)
{
if (id == this->m_EmpArray[j]->m_Id)
{
cout << "此编号已存在!请重新输入" << endl;
adjust = 1;
}
}
if (adjust == 0)
{
break;
}
}
cout << "请输入第" << i+1<< "个新职工的姓名" << endl;
cin >> name;
cout << "请选择该职工的岗位"<< endl;
cout << "1.普通职工" << endl;
cout << "2.经理" << endl;
cout << "3.老板" << endl;
cin >> dselect;
Worker* worker = NULL;
switch (dselect)
{
case 1:
worker = new Employee(id, name, 1);
break;
case 2:
worker = new Manager(id, name, 2);
break;
case 3:
worker = new Boss(id, name, 3);
break;
default:
break;
}
//将创建的职工指针,保存到数组中
NewSpace[this->m_EmpNum + i] = worker;
}
//释放原有的空间
delete[] this->m_EmpArray;
//更改新空间的指向
this->m_EmpArray = NewSpace;
//更新职工人数
this->m_EmpNum = NewSize;
//更新职工不为空的标志
this->m_FileIsEmpty = false;
//提示
cout << "成功添加" << addnum << "个新职工" << endl;
//保存数据到文件中
this->SaveFile();
}
else
{
cout << "输入有误" << endl;
}
//按任意键后清屏
system("pause");
system("cls");
}
//保存文件
void WorkerManager::SaveFile()
{
ofstream ofs;
ofs.open("test.txt", ios::out);//用输出方式打开文件——写文件
//将每个人的数据写入到文件
for (int i = 0; i < this->m_EmpNum; i++)
{
ofs << this->m_EmpArray[i]->m_Id << " "
<< this->m_EmpArray[i]->m_Name << " "
<< this->m_EmpArray[i]->m_DeptId << endl;
}
}
//统计人数
int WorkerManager::Get_EmpNum()
{
ifstream ifs;
ifs.open(FILENAME, ios::in);//打开文件
int id;
string name;
int did;
//计数器
int num = 0;
while (ifs>>id && ifs>>name && ifs>>did)
{
num++;
}
return num;
}
//初始化职工
void WorkerManager::Init_Emp()
{
//就是把文件里面的内容读进来
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_EmpArray[index] = worker;
index++;
}
ifs.close();
}
//显示职工
void WorkerManager::Show_Emp()
{
//判断文件是否为空
if (this->m_FileIsEmpty)
{
cout << "文件为空或记录为空" << endl;
}
else
{
for (int i = 0; i < this->m_EmpNum; i++)
{
//利用多条调用程序接口
this->m_EmpArray[i]->ShowInfo();
}
}
//按任意键后清屏
system("pause");
system("cls");
}
//判断职工是否存在
int WorkerManager::IsExist(int id)
{
int index = -1;//一看是认定不存在
for (int i = 0; i < this->m_EmpNum; i++)
{
if (this->m_EmpArray[i]->m_Id == id)
{
//找到职工
index = i;
break;
}
}
return index;
}
//删除职工
void WorkerManager::Del_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或者记录为空" << endl;
}
else
{
//按照职工的编号来删除职工
cout << "请输入要删除职工的编号" << endl;
int id = 0;
cin >> id;
int index = this->IsExist(id);
if (index != -1)//存在-删除
{
//在数组中删除数据本质上就是数据前移
for (int i = index; i < this->m_EmpNum -1; i++)
{
this->m_EmpArray[i] = this->m_EmpArray[i + 1];
}
//更新数组中记录人员个数
this->m_EmpNum--;
//数据同步更新到文件中
this->SaveFile();
cout << "删除成功" << endl;
}
else
{
cout << "删除失败,未找到该员工" << endl;
}
//按任意键清屏
system("pause");
system("cls");
}
}
//修改职工
void WorkerManager::Mod_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或记录为空" << endl;
}
else
{
cout << "请输入要修改的职工编号" << endl;
int id = 0;
cin >> id;
int ret = this->IsExist(id);
if (ret != -1)
{
//查找到了
delete this->m_EmpArray[ret];//删除旧的,创建新的
int newid = 0;;
string newname = " ";
int newselect = 0;
cout << "查找到了编号为" << id << "的这个职工," <<"请输入新的职工号"<< endl;
cin >> newid;
cout << "请输入新的姓名" << endl;
cin >> newname;
cout << "请输入新的岗位" << endl;
cout << "1.普通职工" << endl;
cout << "2.经理" << endl;
cout << "3.老板" << endl;
cin >> newselect;
Worker* worker = NULL;
switch (newselect)
{
case 1:
worker = new Employee(newid, newname, newselect);
break;
case 2:
worker = new Manager(newid, newname, newselect);
break;
case 3:
worker = new Boss(newid, newname, newselect);
break;
default:
break;
}
//更新数据到数组中
this->m_EmpArray[ret] = worker;
cout << "修改成功!" << endl;
this->SaveFile();//保存到文件中
}
else
{
cout << "修改失败,查无此人。" << endl;
}
}
system("pause");
system("cls");
}
//查找职工
void WorkerManager::Find_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或记录为空" << endl;
}
else
{
cout << "请输入查找的方式" << endl;
cout << "1.按职工编号查找" << endl;
cout << "2.按职工姓名查找" << endl;
int select = 0;
cin >> select;
if (select == 1)
{
//按照编号查
int id;
cout << "请输入查找的职工编号" << endl;
cin >> id;
int ret = this->IsExist(id);
if (ret != -1)
{
cout << "查找成功!该职工的信息如下:" << endl;
this->m_EmpArray[ret]->ShowInfo();
}
else
{
cout << "查找失败,查无此人!" << endl;
}
}
else if (select == 2)
{
//按照姓名查找
string name;
cout << "请输入要查找的姓名" << endl;
cin >> name;
//加入判断是否查到的标志
bool flag = false;
for (int i = 0; i < m_EmpNum; i++)
{
if (this->m_EmpArray[i]->m_Name== name)
{
cout << "查找成功,职工编号为" << this->m_EmpArray[i]->m_Id << "的职工,他的信息如下:" << endl;
this->m_EmpArray[i]->ShowInfo();
flag = true;
}
}
if (flag == false)
{
cout << "查找失败,查无此人" << endl;
}
}
else
{
cout << "输入选项有误" << endl;
}
//按任意键清屏
system("pause");
system("cls");
}
}
//员工排序
void WorkerManager::Sort_Emp()
{
if (this->m_FileIsEmpty)
{
cout << "文件不存在或记录为空" << endl;
system("system");
system("clsf");
}
else
{
cout << "请选择排序方式" << endl;
cout << "1.按照职工号进行升序" << endl;
cout << "2.按照职工号进行降序" << endl;
int select = 0;
cin >> select;
for (int i = 0; i < this->m_EmpNum; i++)
{
int MinOrMax = i;//声明最大值或最小值下标
for (int j = i + 1; j < this->m_EmpNum; j++)
{
if (select == 1)
{
//升序
if (this->m_EmpArray[MinOrMax]->m_Id > this->m_EmpArray[j]->m_Id)
{
MinOrMax = j;
}
}
else
{
//降序
if (this->m_EmpArray[MinOrMax]->m_Id < this->m_EmpArray[j]->m_Id)
{
MinOrMax = j;
}
}
}
//判断一开始认定的最大值或最小值是不是计算的最大值或最小值,如果不是就交换
if (i != MinOrMax)
{
Worker* temp = this->m_EmpArray[i];
this->m_EmpArray[i] = this->m_EmpArray[MinOrMax];
this->m_EmpArray[MinOrMax] = temp;
}
}
cout << "排序成功!排序后的结果为:" << endl;
this->SaveFile();//将排序后的结果保存到文件中
this->Show_Emp();//展示所有职工
}
}
//清空数据
void WorkerManager::Clean_File()
{
cout << "确认清空吗?" << endl;
cout << "1.确认" << endl;
cout << "2.取消" << endl;
int select = 0;;
cin >> select;
if (select == 1)
{
//清空文件
ofstream ofs(FILENAME,ios::trunc);
ofs.close();
if (this->m_EmpArray != NULL)
{
//删除堆区的每个对象
for (int i = 0; i < this->m_EmpNum; i++)
{
delete this->m_EmpArray[i];
}
//删除堆区数组指针
delete this->m_EmpArray;
this->m_EmpArray = NULL;
this->m_EmpNum = 0;
this->m_FileIsEmpty = true;
}
cout << "清空成功!" << endl;
}
system("pause");
system("cls");
}
//析构函数
WorkerManager::~WorkerManager()
{
if (this->m_EmpArray != NULL)
{
for (int i = 0; i < this->m_EmpNum; i++)
{
delete this->m_EmpArray[i];
}
delete this->m_EmpArray;
this->m_EmpArray = NULL;
}
}
**职工管理系统.cpp**
#include<iostream>
#include"workerManager.h"
#include"worker.h"
#include"employee.h"
#include"manager.h"
#include"boss.h"
using namespace std;
int main(void)
{
//测试
/*Worker* worker = NULL;
worker = new Employee(1, "sb", 1);
worker->ShowInfo();
delete worker;
worker = new Manager(1, "sbb", 2);
worker->ShowInfo();
delete worker;*/
//实例化一个管理者的对象
WorkerManager wm;
int choice = 0;//用来存储用户的选择
while (1)
{
//显示菜单
wm.Show_Menu();
cout << "请输入你的选择" << endl;
cin >> choice;
switch (choice)
{
case 0://退出系统
wm.ExitSystem();
break;
case 1://添加职工
wm.AddEmp();
break;
case 2://显示职工
wm.Show_Emp();
break;
case 3://删除职工
wm.Del_Emp();
break;
case 4://修改职工
wm.Mod_Emp();
break;
case 5://查找职工
wm.Find_Emp();
break;
case 6://排序职工
wm.Sort_Emp();
break;
case 7://清空文件
wm.Clean_File();
break;
default:
system("cls");//清屏
break;
}
}
system("pause");
return 0;
}