黑马程序员C++学习总结【核心篇】

黑马程序员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++利用了构造函数(主要作用在于创建对象时为对象的成员属性赋值)和析构函数(主要作用在于对象销毁前系统自动调用,执行一些清理工作)完成对象初始化和清理工作,这两个函数将会被编译器自动调用,无须手动调用.
  • 如果我们不提供构造和析构,编译器会提供,编译器提供的构造函数和析构函数是空实现。
  • 构造函数按对象在代码中出现的顺序进行构造,而析构函数的析构顺序与之相反.

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

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

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

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

(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个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  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)继承同名成员处理方式

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

总结:

  1. 子类对象可以直接访问到子类中同名成员变量
  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)菱形继承 / 钻石继承-虚基类

菱形继承概念:
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承


(注:羊和骆驼存在生殖隔离,此处继承仅在名字上)
菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性(可以通过添加作用域解决)。
  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以(使用虚继承)。
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;
}

总结:

  1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  3. 拥有纯虚析构函数的类也属于抽象类

(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>

文件类型分为两种:

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

操作文件的三大类:

  1. ofstream:写操作
  2. ifstream: 读操作
  3. fstream : 读写操作
文件打开方式 解释
ios::in 为读文件而打开文件
ios::out 为写文件而打开文件
ios::ate 初始位置:文件尾 (at end)
ios::app 追加方式写文件
ios::trunc 如果文件存在先删除,再创建
ios::binary 二进制方式

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

1.文本文件

(1)写文件

写文件步骤如下

  1. 包含头文件 #include <fstream>
  2. 创建流对象 ofstream ofs;
  3. 打开文件 ofs.open("文件路径",打开方式);
  4. 写数据 ofs << "写入的数据";
  5. 关闭文件 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)读文件

读文件步骤如下

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

猜你喜欢

转载自blog.csdn.net/qq_52757671/article/details/137949453