C++面向对象

30 篇文章 2 订阅
25 篇文章 0 订阅

C++面向对象

十二、 类和对象

  1. 什么是对象
    万物皆对象,任何一种事物都可以看做是对象.

  2. 如何描述对象
    通过对象的属性和行为来描述对象.

  3. 面向对象程序设计
    对自然世界中对象观察和描述引入到编程中一种理念和方法,这种方法称为"数据抽象",即在描述对象时把细节东西玻璃出去,只考虑一般性的、有规律性的、统一性的东西.

  4. 什么是类
    类就是将多个对象共性提取出来定义的一种新的数据类型,是对 对象 属性和行为的抽象描述.

现实世界 类 虚拟世界
具体对象–抽象–>属性/行为–实例化–>具体对象

十三、 类的定义和实例化

  1. 类定义的一般语法形式
    struct/class 类名:继承方式 基类,…{
    访问控制限定符:
    类名(形参表): 初始化列表{} //构造函数
    ~类名(void){} //析构函数
    返回类型 函数名(形参表){} //成员函数
    数据类型 变量名; //成员变量
    };

  2. 访问控制限定符
    1)public:公有成员,任何位置都可以访问。
    2)private:私有成员,只有类自己的成员函数才能访问
    3)protected:保护成员(后面讲)

注意:
如果struct定义类默认的访问控制属性是public;而如果是class定义类默认的访问控制属性是private.
**eg**:
struct/class XX{
         int a;       //默认访问属性
      public:
         int b;       //公有成员
      private:
         int m_c;  //私有成员
      public:
         int m_d;       //公有成员
         int m_e;       //公有成员
      private:
          int f;           //私有成员
};
参考代码:10class.cpp
#include <iostream>
using namespace std;
//原来定义结构体,现在定义类
struct Student{
  //成员函数:描述对象的行为
  void eat(const string &food){
    cout <<"我在吃"<<food<<endl;
  }
  void sleep(int hour){
    cout<<"我睡了"<<hour<<"小时"<<endl;
  }
  void learn(const string &course){
    cout<<"我在学"<<course<<endl;
  }
  void who(void){
    cout<<"我叫"<<m_name<<",今年"<<m_age<<"岁,学号是"<<m_no<<endl;
  }
  //成员变量:描述对象的属性
  string m_name;
  int m_age;
  int m_no;
};
int main(void){
  Student s;  //原来创建结构变量,现在成为创建对象/实例化对象/构造对象
  s.m_name = "张三";
  s.m_age = 25;
  s.m_no = 10011;
  s.who();
  s.eat("兰州拉面");
  s.sleep(8);
  s.learn("C++编程");
  return 0;
}

参考代码2:使用class
#include <iostream>
using namespace std;
//原来定义结构体,现在定义类
//struct Student{
class Student{
public:
  //成员函数:描述对象的行为
  void eat(const string &food){
    cout <<"我在吃"<<food<<endl;  
  }
  void sleep(int hour){
    cout<<"我睡了"<<hour<<"小时"<<endl;
  }
  void learn(const string &course){
    cout<<"我在学"<<course<<endl;
  }
  void who(void){
    cout<<"我叫"<<m_name<<",今年"<<m_age<<"岁,学号是"<<m_no<<endl;
  }
public:
  /*类中的私有成员不能在外部直接访问,但是可以通过类似如下的公有成员函数
 * 来间接访问,在函数体重可以对非法数据加以限定,控制业务逻辑的合理性。
 * 这种编程思想就是“封装”。*/
  void setName(const string &newName){
     if(newName == "二")
       cout<<"你才二"<<endl;
     else
       m_name = newName;
  }
  void setAge(int newAge){
     if(newAge<0)
       cout<<"无效年龄"<<endl;
     else
       m_age = newAge;
  }
  void setNo(int newNo){
     if(newNo<0)
       cout<<"无效的学号"<<endl;
     else
       m_no = newNo;
  }

private:
  //成员变量:描述对象的属性
  string m_name;
  int m_age;
  int m_no;
};
int main(void){
  Student s;  //原来创建结构变量,现在成为创建对象/实例化对象/构造对象
  /*s.m_name = "张三";
 *s.m_name = "二";
  s.m_age = 25;
  s.m_no = 10011;*/
  s.setName("张三丰");
  s.setName("二");
  s.setAge(26);
  s.setAge(-1);
  s.setNo(10086);
  s.setNo(-2);
  
  s.who();
  s.eat("兰州拉面");
  s.sleep(8);
  s.learn("C++编程");
  return 0;
}

员工管理系统版本v.1

第一版(1.0)
知识点:类的定义、访问控制属性
需求:
1)实现一个员工类(Employee),包括打印信息和计算工资的函数
2)工号>10000,姓名字符串长度小于 20 个字符,基本工资大于 0
提示:
class Employee{
行为:打印信息,计算工资
属性:工号、姓名、薪资
};
工资计算方式:基本工资+绩效工资
总工资=基本工资+绩效工资
基本工资=基础工资*出勤率
绩效工资=基本工资一半
参考代码: employee.cpp

#include <iostream>
using namespace std;

class Employee{
public:
    void printInfo(void){
        cout << "姓名:" << m_name << endl;
        cout << "工号:" << m_id << endl;
        cout << "基础工资:"  << m_salary << endl;
    }
    void calSalary(void){
        cout << "请输入出勤天数:";
        int days;
        cin >> days;
        double basic = m_salary * (days/23.0);
        double merit = basic / 2;
        cout << (basic + merit) << endl;
    }
public:
    void setId(int id){
        if(id<10000)
            cout << "无效的工号" << endl;
        else
            m_id = id;
    }
    void setName(const string& name){
        if(name.size() > 20)
            cout << "无效的姓名" << endl;
        else
            m_name = name;
    }
    void setSalary(double salary){
        if(salary<0)
            cout << "无效的工资" << endl;
        else
            m_salary = salary;
    }
private:
    int m_id;//工号
    string m_name;//姓名
    double m_salary;//工资
};
int main()
{
    Employee emp;
    emp.setId(10011);
    emp.setName("张三");
    emp.setSalary(6600);

    emp.printInfo();
    emp.calSalary();
    return 0;
}
  1. 构造函数(constructor)
    1)语法
    class 类名{
    类名(参数表){
    主要负责初始化对象,即初始化成员变量。
    }
    };
    2)函数名和类名一致,没有返回类型。
    3)构造函数在创建对象时自动被调用,不能像普通的成员函数一样显式的调用.
    4)在每个对象的生命周期,构造函数一定会被调用,且仅会被调用一次。
    参考代码:01constructor.cpp
#include <iostream>
using namespace std;
class Student{
public:
  Student(const string &name,int age,int no){
     cout<<"构造函数"<<endl;
     //member简写m
     m_name = name;
     m_age = age;
     m_no = no;
  }
  void who(void){
    cout<<"我叫"<<m_name<<",今年"<<m_age<<"岁,学号是"<<m_no<<endl;
  }
private:
  string m_name;
  int m_age;
  int m_no;
};
int main(void){
  //创建对象,这时将会自动调用Student的构造函数
  //(...): 指明构造函数调用时需要的构造实参
  Student s("张三",26,10001);
  s.who();
  
  //构建函数不能通过对象显式的调用
  //s.Student("张三丰",27,10002);  //error
  return 0;
}

练习:实现一个电子时钟类,使用构造函数初始化时钟的时间为当前的系统时间,并可以以秒为单位运行
提示:参考代码

#include <iostream>
#include <cstdio>
#include <ctime>
#include <unistd.h>
class Clock{
public:
  Clock(time_t t){
    tm *local = localtime(&t);
    m_hour = local->tm_hour;
    m_min = local->tm_min;
    m_sec = local-> tm_sec;
  }
  void run(void){
    while(1){
      printf("\r%02d:%02d:%02d",m_hour,m_min,m_sec);
      fflush(stdout); //刷新标准输出缓冲区
      if(60 == ++m_sec){
        m_sec = 0;
        if(60 == ++m_min){
          m_min = 0 ;
          if(24 == ++m_hour){
            m_hour = 0;
          }
        }
      }
      sleep(1);
    }
  }
private:
  int m_hour;
  int m_min;
  int m_sec;
};
int main(void){
  Clock clock(time(NULL));
  clock.run();
  return 0;
}

  1. 对象的创建和销毁
    1)在栈区创建单个对象 //重点掌握
    类名 对象(构造实参表); //直接初始化,注意:如果创建对象不需要构造实参,不要写空"()"
    类名 对象=类名(构造实参表); //拷贝初始化(实际和上面等价),注意:如果构造实参只有一个,可以简化为 “类名 对象 = 构造实参”
    eg:
    string s;
    string s(“hello”);
    string s = string(“hello”); //string s = “hello”;
2)**在栈区创建多个对象(对象数组)**
类名 对象数组[元素个数] = {类名(构造实参表),类名(构造实参表),...};

3)**在堆区创建/销毁单个对象**      //重点掌握
创建: 类名 *对象指针 = new 类名(构造实参表);
注意: new操作符会先分配内存再自动调用构造函数,完成对象的创建和初始化;而如果是malloc函数只能分配内存,不会调用构造函数,不具备创建对象能力.
销毁: delete 对象指针;

4)**在堆区创建/销毁多个对象**  
创建:类名* 对象指针 = new 类名[元素个数] { 类名(构造实参表),类名(构造实参表),...};       
销毁:delete[]  对象指针; 

参考代码.03object.cpp
#include <iostream>
using namespace std;
class Student{
public:
  Student(const string &name,int age,int no){
     cout<<"构造函数"<<endl;
     //member简写m
     m_name = name;
     m_age = age;
     m_no = no;
  }
  void who(void){
    cout<<"我叫"<<m_name<<",今年"<<m_age<<"岁,学号是"<<m_no<<endl;
  }
private:
  string m_name;
  int m_age;
  int m_no;
};
int main(void){
  //在栈区创建单个对象
  Student s("张三",26,10001);
  s.who();
  Student s2 = Student("李四",25,10002);
  s2.who();

  //在栈区创建对象数组
  Student sarr[3] = {Student("张飞",27,10003),Student("赵云",24,10004),Student("马超",26,10005)};
  sarr[0].who();
  sarr[1].who();
  sarr[2].who();

  //在堆区创建单个对象
  Student *ps = new Student("貂蝉",22,10006);
  ps->who();
  delete ps;
  ps = NULL;

  //在堆区创建多个对象(对象数组)
  Student *parr = new Student[3]{Student("小乔",23,10007),Student("大乔",25,10008),Student("孙尚香",20,10009)};
  parr[0].who();  //*(parr+0)->who();
  parr[1].who();  //*(parr+1)->who();
  parr[2].who();  //*(parr+2)->who();
  delete[] parr; 
  parr = NULL;
  return 0;
}

  1. 多文件编程:类的声明和定义可以分别放在不同的文件中
    1)类的声明一般放在头文件中(xx.h)
    2)类的实现一般放在源文件中(xx.cpp)
    3)类的使用一般会在其他文件中(main.cpp)
    参考:project
    Clock.h //类的声明
    Clock.cpp //类的定义
    main.cpp //类的使用
参考代码:Clock.h
#ifndef __CLOCK__H
#define __CLOCK__H

#include <iostream>
#include <cstdio>
#include <ctime>
#include <unistd.h>

//类的声明:不包含函数体的实现
class Clock{
public:
  Clock(time_t t);
  void run(void);
private:
  int m_hour;
  int m_min;
  int m_sec;
};
#endif

参考代码:Clock.cpp

#include "Clock.h"
//类的实现:将类中的成员函数实现部分写在当前文件中
//注意:需要再函数名字前面加上“类名::" ,显式指明它们是属于类中的成员函数
  Clock::Clock(time_t t){
    tm *local = localtime(&t);
    m_hour = local->tm_hour;
    m_min = local->tm_min;
    m_sec = local-> tm_sec;
  }
  void Clock::run(void){
    while(1){
      printf("\r%02d:%02d:%02d",m_hour,m_min,m_sec);
      fflush(stdout); //刷新标准输出缓冲区
      if(60 == ++m_sec){
        m_sec = 0;
        if(60 == ++m_min){
          m_min = 0 ;
          if(24 == ++m_hour){
            m_hour = 0;
          }
        }
      }
      sleep(1);
    }
  }

参考代码:main.h

#include "Clock.h"

int main(void){
  Clock clock(time(NULL));
  clock.run();
  return 0;
}

十四、 构造函数和初始化列表

  1. 构造函数可以重载,也可以带有缺省参数
    string s; //匹配string的无参构造函数
    string s(“hello”); //匹配string的有参(const char*)构造函数
    标准C++库帮助手册网站:
    http://www.cplusplus.com/
    参考代码:
#include <iostream>
using namespace std;
class A{
public:
  A(void){
    cout<<"A(void)"<<endl;
    m_i = 0;
  }
  A(int i){
    cout<<"A(int)"<<endl;
    m_i = i;
  }
  int m_i;
};
int main(void){
  A a1;  //匹配A(void)
  cout<<a1.m_i<<endl;  //0
  A a2(123);  //匹配A(int)
  cout<<a2.m_i<<endl;  //123
  return 0;
}

  1. 缺省构造函数(无参构造函数)
    1)如果类中没有定义任何构造函数,编译器会为该类提供一个缺省(无参)构造函数:
    –》对于基本类型成员变量不做初始化
    –》对于类 类型的成员变量(成员子对象),将会自动调用相应类的无参构造函数来初始化
    2)如果自己定义了构造函数,无论是否有参数,那么编译器都不会再提供缺省的无参构造函数了.

  2. 类型转换构造函数(单参构造函数)
    class 类名{
    //可以将源类型变量转换为当前类类型对象.
    类名(源类型){…}
    };

class 类名{
//加“explicit”关键字修饰,可以强制要求这种类型
//转换必须显式的完成.
    explicit 类名(源类型){...}
};
注意:
可以使用explicit关键字修改类型转换构造函数,可以强制要求使用它进行类型转换必须要显示的完成。
参考代码:06conInteger.cpp
#include <iostream>
using namespace std;
class Integer{
public:
  Integer(void){
    cout<<"Integer(void)"<<endl;
    m_i = 0;
  }
  /*int -> Integer:类型转换构造函数*/
  /*explicit*/ Integer(int i){
    cout<<"Integer(int)"<<endl;
    m_i = i;
  }
  void print(void){
    cout<<m_i<<endl;
  }
private: 
  int m_i;
};
int main(void){
  Integer i;
  i.print();  //0
  //1)使用Integer类型转换构造函数,将123转换为Integer临时对象
  //2)在使用临时对象对i进行赋值
  i = 123;
  i.print(); //123

  //上面隐式的类型转换可读性差,不推荐使用
  //实际开发中推荐使用下面形式的显式类型转换
  // i = (Integer)321; //c风格  
  i = Integer(321); //c风格  
  i.print();  

  return 0;
}
  1. 拷贝构造函数(复制构造函数)
    1)用一个已存在的对象作为同类对象的构造实参,创建新的副本对象时,会调用该类拷贝构造函数。
    class 类名{
    类名(const 类名&){//拷贝构造 …}
    };
eg:
class A{...};
A a1(...);
A a2(a1);      //匹配A的拷贝构造函数

2)如果一个类没有自己定义拷贝构造函数,那么编译器会为该类提供一个缺省的拷贝构造函数:
--》对于基本类型的成员变量,按字节复制
--》对于类类型的成员变量(成员子对象),将自动调用相应类的拷贝构造函数来初始化
参考代码:07cpCons.cpp
#include <iostream>
using namespace std;
class A{
public:
   A(int i=0){
      cout<<"A(int =0)"<<endl;
      m_i = i;
   }
   A(const A& that){
      cout<<"A(const A&)"<<endl;
      m_i = that.m_i;
   }

   int m_i;
};

int main(void){
  A a1(123);
  A a2(a1);  
  cout<<a1.m_i<<endl;  //123
  cout<<a2.m_i<<endl;  //123
  return 0;
}

注意:
一般不需要自己定义拷贝构造函数函数,因为编译器缺省提供的已经很好用了.
class A1{}; //缺省无参,缺省拷贝
class A2{//缺省拷贝
A(void){}
};
class A3{//缺省拷贝
A(int){}
};
class A4{//没有缺省构造
A(const A&){}
};
参考代码:08cpcons.cpp

#include <iostream>
using namespace std;
class A{
public:
   A(int i=0){
      cout<<"A(int =0)"<<endl;
      m_i = i;
   }
   A(const A& that){
      cout<<"A(const A&)"<<endl;
      m_i = that.m_i;
   }

   int m_i;
};

class B{
public:
  A m_a;  //成员子对象
}
int main(void){
  B b1;  //匹配B的无参构造函数
  B b2 = b1; //匹配B的拷贝构造函数
  cout<<b1.m_a.m_i<<endl;  //0
  cout<<b2.m_a.m_i<<endl;  //0

  return 0;   
}

3)拷贝构造函数调用时机
–》用一个已存在对象作为同类对象的构造实参
–》以对象形式向函数传递参数
–》从函数中返回对象(有可能被编译器优化掉)

参考代码:09cpCons.cpp

#include <iostream>
using namespace std;
class A{
  A(void){
    cout<<"A的无参构造"<<endl;
  }
  A(const A& that) {cout<<"A的拷贝构造"<<endl;}
};
void foo(A a){}
A bar(void){
  A a;   //无参
  cout<<"&a="<<&a<<endl;
  return a; //拷贝(临时对象)
}
int main(void){
  A a1;  //无参
  A a2 = a1; //拷贝
  foo(a1);  //拷贝
  /*正常情况,bar()返回a拷贝给临时对象,临时对象在拷贝给a3,发生两次拷贝。
 *  但是因为编译器优化,让a3直接引用bar返回的a,不再发生拷贝
 *  gcc -o 09cpCons 09cpCons.cpp -fno-elide-constructors */
  A a3 = bar(); //拷贝
  cout<<"&a3="<<&a3<<endl;
  return 0;
}
//一共会调用多少次构造函数
//A.3次  B.4次  C.5次  D.6次
//正确答案 D
  1. 初始化列表
    1)语法
    class 类名{
    类名(参数表):成员变量1(初值),成员变量2(初值){…}
    };
    例eg:
    class Student{
    public:
    //先定义成员变量,再赋初值
    Student(const string& name,int age,int no){
    m_name = name;
    m_age = age;
    m_no = no;
    }

    //定义成员变量同时初始化
    Student(const string& name,int age,int no):m_name(name),m_age(age),m_no(no){}
    private:
    string m_name;
    int m_age;
    int m_no;
    };

2)大多数情况使用初始化列表和在构造函数体赋初值没有太大区别
两种形式可以任选;但是有以下特殊场景必须要使用初始化列表:
--》如果有类 类型的成员变量(成员子对象),并希望以有参方式对其进行初始化,则必须使用初始化列表显式指明成员子对象需要的构造实参。
--》如果类中包含"const"或"引用"成员变量,则必须在初始化列表显式的初始化。
参考代码:10initlist.cpp

#include <iostream>
using namespace std;
class A{
public:
  A(int i){
    cout<<"A的构建函数"<<endl;
    m_i = i;
  }
  int m_i;
};
class B{
  //首先根据初始化列表完成成员子对象的创建
  //再执行B自己的构造函数代码
  B(void):m_a(123){
    cout<<"B的构造函数"<<endl;
  }
  A m_a; //成员子对象
};

int main(void){
  B b;
  return 0;
}

参考代码2:11initlist.cpp

#include <iostream >
using namespace std;
int num = 200;
class A{
public:
  /*A(void){
    ci = 100;
    ri = num;
  }*/
  A(void):ci(100),ri(num){}
  const int ci;
  int & ri;
};
int main(void){
  A a;
  cout<<a.ci<<","<<a.ri<<endl;
  return 0;
}

注意
成员变量的初始化顺序由声明顺序决定,而与初始化列表的顺序无关,所以不要使用一个成员变量去初始化另一个成员变量.
练习:使用初始化列表为时钟类增加计时器功能,如果使用系统时间构造对象,则表现为时钟功能;如果使用无参方式构造对象,则使用初始化列表将时间初始化为"00:00:00",则表现为计时器功能.
参考代码:timer.cpp

#include <iostream>
#include <cstdio>
#include <ctime>
#include <unistd.h>
class Clock{
public:
  Clock(void){
    m_hour=00;
    m_min = 00;
    m_sec = 00;
  }
  Clock(time_t t){
    tm *local = localtime(&t);
    m_hour = local->tm_hour;
    m_min = local->tm_min;
    m_sec = local-> tm_sec;
  }
  void run(void){
    while(1){
      printf("\r%02d:%02d:%02d",m_hour,m_min,m_sec);
      fflush(stdout); //刷新标准输出缓冲区
      if(60 == ++m_sec){
        m_sec = 0;
        if(60 == ++m_min){
          m_min = 0 ;
          if(24 == ++m_hour){
            m_hour = 0;
          }
        }
      }
      sleep(1);
    }
  }
private:
  int m_hour;
  int m_min;
  int m_sec;
};
int main(void){
  //Clock clock(time(NULL));
  Clock clock;
  
  clock.run();
  return 0;
}

笔试题:10initlist.cpp

#include <iostream>
#include <cstring>
using namespace std;

class Dummy{
public:
  //Dummy(const char * str):m_str(str),m_len(m_str.size()){}
  //Dummy(const char * str):m_str(str),m_len(strlen(str)){}
  Dummy(const char * str):m_str(str?str:""),m_len(strlen(str?str:"")){}
  size_t m_len;
  string m_str;
};

int main(void){
  Dummy d("minwei");
  //Dummy d(NULL);
  cout<<d.m_str<<","<<d.m_len<<endl;  //“minwei,6”
  return 0;
}

/*注意:
 * 成员变量的初始化顺序由声明顺序决定,而与初始化列表的顺序无关,所以不要使用一个成员变量去初始化
 * 另一个成员变量。
 *
 */

员工管理系统版本v.2

知识点:类的定义和实例化、构造函数
需求:
1)将类的声明和定义分开来写,创建员工对象时使用两种方式(栈对象、堆对象)
2)为员工类增加构造函数(无参,有参),使用构造函数参数初始化成员变量
扩展:尝试编写 Makefile
参考代码:employee.h、employee.cpp、main.cpp

参考代码1:employee.h

#ifndef __EMPLOYEE_H 
#define __EMPLOYEE_H
#include <iostream>
using namespace std;
class Employee{
public:
    Employee(void);
    Employee(int id,const string& name,double salary);
    void printInfo(void);
    void calSalary(void);
    void setId(int id);
    void setName(const string& name);
    void setSalary(double salary);
private:
    int m_id;//工号
    string m_name;//姓名
    double m_salary;//工资
};

#endif

参考代码2:employee.cpp

#include "employee.h"
   Employee::Employee(void){
    m_id = 0;
    m_name = "无名";
    m_salary = 0.0;
   }
   Employee::Employee(int id,const string& name,double salary){
    m_id = id;
    m_name = name;
    m_salary = salary;
   }
   void Employee::printInfo(void){
        cout << "姓名:" << m_name << endl;
        cout << "工号:" << m_id << endl;
        cout << "基础工资:"  << m_salary << endl;
    }
    void Employee::calSalary(void){
        cout << "请输入出勤天数:";
        int days;
        cin >> days;
        double basic = m_salary * (days/23.0);
        double merit = basic / 2;
        cout << (basic + merit) << endl;
    }
    void Employee::setId(int id){
        if(id<10000)
            cout << "无效的工号" << endl;
        else
            m_id = id;
    }
    void Employee::setName(const string& name){
        if(name.size() > 20)
            cout << "无效的姓名" << endl;
        else
            m_name = name;
    }
    void Employee::setSalary(double salary){
        if(salary<0)
            cout << "无效的工资" << endl;
        else
            m_salary = salary;
    }

参考代码3:main.cpp

#include "employee.h"
int main(void)
{
    Employee emp2;
    emp2.printInfo();
    
    Employee emp(10011,"张三",6600);
    emp.printInfo();
    emp.calSalary();

    Employee* pemp = new Employee(10012,"李四",8800);
    pemp->printInfo();
    pemp->calSalary();
    return 0;
}

Makefile文件

PROJ = employee
OBJS = main.o employee.o 
LINK = g++
RM = rm -rf 
CFLAGS = -c -Wall -I.
$(PROJ):$(OBJS)
	$(LINK) $^ -o $@
.c.o:
	$(LINK) $^
clean:
	$(RM) $(PROJ) $(OBJS) *.gch

十五、 this指针和常成员函数

  1. this指针
    1)this是一个关键字,其本质即使一个指针形式的形参变量。类中的成员函数(包括构造函数、析构函数)都有一个隐藏的当前类类类型的指针参数,名为this。在成员函数中访问类中其它成员,其本质都是通过this来实现的。
    2)对于普通的成员函数,this指针就是指向该成员函数的调用对象;对于构造函数,this指针就指向正在创建的对象。
    参考代码:02this.cpp
#include <iostream>
using namespace std;
class Teacher{
public:
  Teacher(const string& name, int age,double salary):m_name(name),m_age(age),m_salary(salary){
     cout<<"构造函数:"<<this<<endl;
  }
  void print(void){
    //this指针可以在成员函数里面显示使用,和上面等价
    cout<<this->m_name<<","<<m_age<<","<<m_salary<<endl;
  }/*print在编译器处理后,会变成类似如下:
   void print(Teacher *this){
     cout<<this->m_name<<","<<this->m_age<<","<<this->m_salary<<endl; 
   }
   */

private:
  string m_name;
  int m_age;
  double m_salary;
};
int main(void){
  Teacher t1("游成伟",35,3000.5);
  Teacher t2("闵卫",45,4000.5);
  t1.print(); // Teacher::print(&t1);
  t2.print(); // Teacher::print(&t2);
  cout<<"&t1="<<&t1<<endl;
  cout<<"&t2="<<&t2<<endl;
  return 0;
}

3)大多数情况,可以忽略this直接访问类中的成员,但是在以下几个特殊场景必须要显式使用this指针:
–》区分作用域
参考代码:03this.cpp

#include <iostream>
using namespace std;
class A{
public:
  A(void):data(0){}
  //通过this区分作用域:如果函数的参数变量名和成员变量名相同,在成员函数里面
  //将会优先访问参数变量,这时如果希望访问成员的话,需要显示使用this
  void set (int data){
       this->data = data;
    }
  void print(void) {
    cout<<data<<endl;  
  }
private:
  int data;
};
int main(void){
  A a;
  a.print(); //0
  a.set(123); 
  a.print(); //123
  return 0;
}

–》从成员函数返回调用对象自身(返回自引用) //重点掌握
return *this;
参考代码:04this.cpp

#include <iostream>
using namespace std;
class Counter{
public:
  Counter(int count=0):m_count(count){}
  Counter & add(void){ //Counter &add (Counter *this)
    ++m_count;
    //this指向成员函数的调用对象,*this就是调用对象指针
    return *this;  //返回自引用
  }
  void pirnt(void){
     cout<<"计数值:"<<m_count<<endl;
   }
private:
  int m_count;
}
int main(void){
  Counter cn;
  //Counter::add(&cn)
  cn.add().add().add();
  cn.print();  //3
  return 0;
}

–》从类的内部销毁对象自身(对象自销毁) //了解
–》作为成员函数实参,实现对象之间交互 //了解
参考代码:05this.cpp

#include <iostream>
using namespace std;
class Student;
class Teacher{

public:
  void educate(Student *stu);
  void reply(const string &answer);
private:
  string m_answer;
};
class Student{
public:
  void ask(const string &ques,Teacher * teach);
};
void Teacher::educate(Student *stu){
 //通过this指针,将教师对象的地址传给学生类的ask函数
 stu->ask("什么是this指针?",this);  //(1)
 cout<<"学生回答:"<<m_answer<<endl; //(5)
}
void Teacher::reply(const string & answer){
  m_answer = answer; //(4)
}
void Student::ask(const string & ques,Teacher *teach){
  cout<<"问题:"<<ques<<endl;  //(2)
  teach->reply("this就是指向该成员函数的对象");  //(3)
}
int main(void){
  Teacher t;
  Student s;
  t.educate(&s);
  return 0;
}

  1. 常成员函数(常函数)
    1)在一个成员函数参数表后面加上const,这个成员函数就是常成员函数。
    返回类型 函数名(参数表) const {函数体}
    2)常成员函数中的this指针是一个常量指针,不能在常成员函数中修改成员变量的值。
    3)被mutable关键字修饰的成员变量,可以在常成员函数中被修改。
    参量代码:06confunc.cpp
#include <iostream>
using namespace std;
class A{
public:
  A(int data=0):m_data(data){}
  //void print(const A* this)
  void print(void) const{
    //cout<<this->m_data<<endl;
    cout<<m_data<<endl;
    //可以通过const_cast去掉this常属性,修改成员变量
    //const_cast<A*>(this)->m_spec=200;
   
    //被mutable修饰的成员变量,也可以直接修改 
    m_spec = 200;
    cout<<m_spec<<endl;
  }
private:
  int m_data;
  //int m_spec;
  mutable int  m_spec;
};
int main(void){
  A a(100);
  a.print();  //100
  a.print();  //100
  return 0;
}

4)非常对象既可以调用常函数也可以调用非常函数;而常对象只能调用常函数,不能调用非常函数.
注意:常对象也包括常指针和常引用
5)同一个类中,函数名形参表相同的成员函数,其常版本和非常版本可以构成有效的重载关系,常对象匹配常版本,非常对象匹配非常版本.

十六、 析构函数(Destructor)

  1. 语法
    class 类名{
    ~类名(void){
    //主要负责清理对象生命周期中的动态资源
    }
    };
    1)函数名必须是"~类名"
    2)没有返回类型,也没有参数
    3)不能被重载,一个类只能有一个析构函数、

  2. 当对象被销毁时,相应类的析构函数将被自动执行
    1)栈对象当其离开所在作用域时,其析构函数被作用域终止“右花括号”调用
    2)堆对象的析构函数被delete操作符调用
    参考代码:

#include <iostream>
using namespace std;
class Integer{
public:
   Integer(int i = 0):m_pi(new int(i)){
      cout<<"动态分配了资源"<<endl;
     //m_pi = new int(i);
   }
   void print(void) const{
     cout<<*m_pi<<endl;
   }
   ~Integer(void){
   //析构函数
     cout<<"释放动态资源"<<endl;
     delete m_pi;
   }
private:
  int * m_pi;
};
int main(void){
  Integer i(100);  //100
  i.print();

  Integer *pi = new Integer(200);
  pi->print();
  delete pi;  //销毁堆对象,调用析构函数

  return 0;
}//销毁栈对象,调用析构函数
  1. 如果一个类自己没有定义析构函数,那么编译器会为该类提供一个缺省的析构函数:
    1)对基本类型的成员变量,什么也不做
    2)对类 类型的成员(成员子对象),将会自动调用相应类的析构函数.

  2. 对象创建和销毁的过程
    1)创建
    –》分配内存
    –》构造成员子对象(按声明顺序)
    –》执行构造函数代码
    2)销毁
    –》执行析构函数代码
    –》析构成员子对象(按声明逆序)
    –》释放内存

十七、拷贝构造和拷贝赋值

  1. 浅拷贝和深拷贝
    1)如果一个类中包含了指针形式的成员变量,缺省的拷贝构造函数只是赋值了指针变量自身,而没有复制指针所指向的内容,这种拷贝方式被称为浅拷贝.
    2)浅拷贝将会导致不同对象之间的数据共享,如果数据在堆区,析构时还可能会出现"double free"的异常,为此就必须自己定义一个支持复制指针所指向内容的拷贝构造函数,即深拷贝。

  2. 拷贝赋值
    1)当两个对象进行赋值运算时,比如"i3=i2",编译器会将其处理为“i3.operator=(i2)”成员函数调用形式,其中“operator=”被称为拷贝赋值函数,由该函数完成两个对象的赋值运算,该函数的返回结果就是赋值表达式结果.
    2)如果自己没有定义拷贝赋值函数,那么编译器会提供一个缺省的拷贝赋值函数,但是缺省的拷贝赋值和缺省拷贝构造类似,也是浅拷贝,只是赋值指针变量本身,而没有复制指针所指向的内容,有数据共享、double free、内存泄漏的问题。
    3)为了避免浅拷贝的问题,必须自己定义深拷贝赋值函数:
    类名& operator=(const 类名& that){
    if(&that != this){//防止自赋值
    释放旧内存;
    分配新内存;
    拷贝新数据;
    }
    return *this;//返回自引用
    }

练习:实现String类,包括构造函数、析构函数、拷贝构造函数
提示
    class String{
    public:
        //构造函数
        String(const char* str=NULL){
           m_str = new char[strlen(str)+1];
           strcpy(m_str,str)
        }
        //析构函数
        ~String(void){}
        //拷贝构造
        String(const String& that){}
        //拷贝赋值
        String& operator=(const String& that){}
    private:
        char* m_str;
    }

参考代码:12string.cpp

#include <iostream>
#include <cstring>
using namespace std;
class String{
public:
  //构造函数
  String(const char *str = NULL){
    m_str = new char[strlen(str?str:"")+1];
    strcpy(m_str,str?str:"");
  }
  //析构函数
  ~String(void){
    if(m_str){
      delete[] m_str;
      m_str = NULL;
    }
  }
  //拷贝函数
  String(const String &that){
    m_str = new char[strlen(that.m_str)+1];
    strcpy(m_str,that.m_str);
  }
  //测试
  void print(void) const{
    cout<<m_str<<endl;
  }
private:
  char *m_str;
};
int main(void){
  String s = "minwei";
  s.print();
  String s2 = s;  //拷贝构造
  s2.print();  
}

员工管理系统版本v.3

第三版(3.0)
知识点:初始化表、this 指针和常函数、析构函数
需求:优化和改写员工类。
1)构造函数改为使用初始化表,打印函数改为常函数。
2)将员工信息保存到文件中,文件名字以员工工号命名。
提示:
class Employee{
构造函数(){
工号(int)–>工号(char*)//sprintf
file = fopen(“工号”,“w”);
saveInfo();
}
析构函数(){
fclose(file);
}
增加属性:FILE* file;//文件指针
增加行为:保存员工信号函数 saveInfo()
};
参考代码:Employee.h、Employee.cpp、main.cpp、Makefile
代码1:Employee.h

#ifndef __EMPLOYEE_H
#define __EMPLOYEE_H

#include <iostream>
#include <cstdio>
using namespace std;

class Employee{
public:
    Employee(int id,const string& name,double salary);
    Employee(void);
    ~Employee(void);
    void printInfo(void)const ;
    void calSalary(void);
    void setId(int id);
    void setName(const string& name);
    void setSalary(double salary);
    void saveInfo(void)const;
private:
    int m_id;//工号
    string m_name;//姓名
    double m_salary;//工资
    FILE* file;//保存信息文件
};
#endif//__EMPLOYEE_H

代码2: Employee.cpp

#include "Employee.h" 

Employee::Employee(int id,const string& name,double salary)
    :m_id(id),m_name(name),m_salary(salary){
    if(m_id > 0){
        char filename[20] = {0};
        sprintf(filename,"%d",m_id);
        file = fopen(filename,"w");
        if(file==NULL){
            cout << "file open error!" << endl;
        }
    } 
    saveInfo();
}
Employee::~Employee(void){
    fclose(file);
}
void Employee::printInfo(void) const{
    cout << "姓名:" << m_name << endl;
    cout << "工号:" << m_id << endl;
    cout << "基础工资:"  << m_salary << endl;
}
void Employee::calSalary(void){
    cout << "请输入出勤天数:";
    int days;
    cin >> days;
    double basic = m_salary* (days/23.0);
    double merit = basic / 2;
    cout << "总工资为:" << (basic + merit) << endl;
}
void Employee::setId(int id){
    if(id<10000)
        cout << "无效的工号" << endl;
    else
        m_id = id;
}
void Employee::setName(const string& name){
    if(name.size() > 20)
        cout << "无效的姓名" << endl;
    else
        m_name = name;
}
void Employee::setSalary(double salary){
    if(salary<0)
        cout << "无效的工资" << endl;
    else
        m_salary = salary;
}
void Employee::saveInfo(void)const{
    fseek(file,0,SEEK_SET);
    fprintf(file,"%d %s %g",m_id,m_name.c_str(),m_salary);
}

代码3:main.cpp

#include "Employee.h"

int main()
{
    Employee emp(10001,"张三",6600);
    emp.printInfo();
    emp.calSalary();

    Employee* pemp = new Employee(10002,"李四",8800);
    pemp->printInfo();
    pemp->calSalary();
    return 0;
}

Makefile文件

PROJ = employee
OBJS = main.o employee.o 
LINK = g++
RM = rm -rf 
CFLAGS = -c -Wall -I.
$(PROJ):$(OBJS)
	$(LINK) $^ -o $@
.c.o:
	$(LINK) $^
clean:
	$(RM) $(PROJ) $(OBJS) *.gch

十八、 静态成员(static)

  1. 静态成员变量
    1)语法
    class 类名{
    static 数据类型 变量名; //声明
    };
    数据类型 类名::变量名 = 初值; //定义和初始化
    2)普通成员变量属于对象,而静态成员变量不属于对象,静态变量内存在全局区,可以把静态变量理解为被限制在类中使用的全局变量.
    3)普通成员变量在对象构造时定义和初始化,而静态成员变量在类的外部单独定义和初始化。
    4)使用方法
    类名::静态成员变量; //推荐
    对象.静态成员变量; //和上面等价
    参考代码:03static.cpp
#include <iostream>
using namespace std;
class A{
public:
  A(int data):m_data(data){}
  int m_data;  //普通成员变量o
  static int s_data;  //静态成员变量
  static const int spec = 20;  //特殊(了解)
};
//静态成员变量需要再类的外部单独的定义和初始化
int A::s_data = 20;

int main(void){
  A a1(10);
  //普通的成员变量属于对象,静态成员变量不属于对象
  cout<<sizeof(a1)<<endl; //4
  //普通的成员需要通过对象才能访问
  cout<<a1.m_data<<endl;  //10
  //静态成员变量可以通过“类名::”直接访问
  cout<<A::s_data<<endl;  //20
  //静态成员变量也可以通过对象访问
  cout<<a1.s_data<<endl;  //0k

  A a2(10);
  a1.m_data = 100;
  a1.s_data = 200;
  cout<<a2.m_data<<endl;  //10
  cout<<a2.s_data<<endl;  //200
  return 0;
}
  1. 静态成员函数
    1)语法
    class 类名{
    static 返回类型 函数名(参数表){…}
    };
    2)静态成员中没有this指针,也没有const属性,可以把静态成员函数理解为被限制在类作用域使用的全局函数.
    3)使用方法
    类名::静态成员函数(实参表); //推荐
    对象名.静态成员函数(实参表); //和上面等价
    注意:
    在静态成员函数中只能访问静态成员,不能访问非静态成员;在非静态成员函数既可以访问静态成员,也可以访问非静态成员.
    参考代码:04static.cpp
#include <iostream>
using namespace std;

class A{
  A(void):m_data(10){}
  static void func1(void){
    cout<<"静态成员函数"<<endl;
    cout<<s_data<<endl;
    cout<<m_data<<endl;
  }
  void func2(void){
    cout<<"非静态成员函数"<<endl;
    cout<<s_data<<endl;
    cout<<m_data<<endl;
  }
  static int s_data;
  int m_data;
};
int A::s_data = 20;
int main(void){
  A::func1();
  A a;
  a.func2();
  return 0;
}
  1. 单例模式
    1)概念
    一个类只允许存在唯一的对象,并提供它的方法.
2)实现思路
--》禁止在类的外部创建对象:私有化构造函数即可
--》类的内部维护唯一的对象:静态成员变量
--》提供单例对象的访问方法:静态成员函数

3)具体创建方式
--》**饿汉式**:
单例对象无论用或不用,程序启动即创建   //hungry.cpp
优点:代码实现简单、访问效率高、多线程安全
缺点:浪费内存
参考代码:hungry.cpp
#include <iostream>
using namespace std;

class Singleton{
public:
  void print(void) const{
     cout<<m_data<<endl;
  }
private:
  //1)私有化构造函数(包含拷贝构造函数)
  Singleton(int data=0):m_data(data){
     cout<<"单例对象被创建了"<<endl;
  }
  Singleton(const Singleton&);
  //2)使用静态成员变量维护单例对象
  static Singleton s_instance;
public:
  //3)提供单例对象的方法
  static Singleton & getInstance(void){
     return s_instance;
  }
private:
  int m_data;
};
Singleton Singleton::s_instance(123);
int main(void){
  cout<<"main开始运行"<<endl;
  Singleton & s1 = Singleton::getInstance();
  Singleton & s2 = Singleton::getInstance();
  Singleton & s3 = Singleton::getInstance();
  s1.print();
  s2.print();
  s3.print();
  cout<<"&s1:"<<&s1<<endl;
  cout<<"&s2:"<<&s2<<endl;
  cout<<"&s3:"<<&s3<<endl;

  //Singleton s4(123);  //error
  //Singleton * s5 = new Singleton(321); //error
  //Singleton s6 = s1;  //应该error
  //cout<<"&s6:"<<&s6<<endl;
  return 0;
}

–》懒汉式
单例对象用时再创建,不用即销毁 //lazy.cpp
优点:节省内存
缺点:代码实现复杂,访问效率略低、多线程需要加锁保护

#include <iostream>
using namespace std;

//单例模式:懒汉式
class Singleton{
public:
  void print(void) const{
     cout<<m_data<<endl;
  }
private:
  //1)私有化构造函数(包含拷贝构造函数)
  Singleton(int data=0):m_data(data){
     cout<<"单例对象被创建了"<<endl;
  }
  Singleton(const Singleton&);
  ~Singleton(void){
     cout<<"单例对象别销毁了"<<endl;
  }
  //2)使用静态成员变量维护单例对象
  static Singleton* s_instance;
public:
  //3)提供单例对象的方法
  static Singleton & getInstance(void){
     if(s_instance == NULL){
        s_instance = new Singleton(123);
     } 
     ++s_count;
     return * s_instance;
  }
  //单例对象不用即销毁,销毁时机?所有的使用者都不再使用才能销毁。
  void release(void){
     if(--s_count == 0){
        delete s_instance;
        s_instance = NULL;
     } 
  }
private:
  int m_data;
  //计数:记录单例对象使用者的个数
  static int s_count;
};
Singleton * Singleton::s_instance=NULL;
int Singleton::s_count = 0;
int main(void){
  cout<<"main开始运行"<<endl;
  Singleton & s1 = Singleton::getInstance();   //++s_count:1 ,new
  Singleton & s2 = Singleton::getInstance();   //++s_count:2
  Singleton & s3 = Singleton::getInstance();   //++s_count:3
  s1.print();
  s1.release(); //--s_count 
  s2.print();
  s3.print();
  cout<<"&s1:"<<&s1<<endl;
  cout<<"&s2:"<<&s2<<endl;
  cout<<"&s3:"<<&s3<<endl;

  s2.release();
  s3.release();
  return 0;
}

解决懒汉式多线程问题扩展,
多线程需要加锁保护:07lazy_thread.cpp

#include <iostream>
#include <pthread.h>
using namespace std;

//单例模式:懒汉式
class Singleton{
public:
  void print(void) const{
     cout<<m_data<<endl;
  }
private:
  //1)私有化构造函数(包含拷贝构造函数)
  Singleton(int data=0):m_data(data){
     cout<<"单例对象被创建了"<<endl;
  }
  Singleton(const Singleton&);
  ~Singleton(void){
     cout<<"单例对象别销毁了"<<endl;
  }
  //2)使用静态成员变量维护单例对象
  static Singleton* s_instance;
public:
  //3)提供单例对象的方法
  static Singleton & getInstance(void){
     pthread_mutex_lock(&mutex);  //加锁
     if(s_instance == NULL){
        s_instance = new Singleton(123);
     } 
     ++s_count;
     pthread_mutex_unlock(&mutex);
     return * s_instance;
  }
  //单例对象不用即销毁,销毁时机?所有的使用者都不再使用才能销毁。
  void release(void){
     pthread_mutex_lock(&mutex);
     if(--s_count == 0){
        delete s_instance;
        s_instance = NULL;
     } 
     pthread_mutex_unlock(&mutex);
  }
private:
  int m_data;
  //计数:记录单例对象使用者的个数
  static int s_count;
  //互斥锁
  static pthread_mutex_t mutex;
};
Singleton * Singleton::s_instance=NULL;
int Singleton::s_count = 0;
pthread_mutex_t Singleton::mutex= PTHREAD_MUTEX_INITIALIZER;
int main(void){
  cout<<"main开始运行"<<endl;
  Singleton & s1 = Singleton::getInstance();   //++s_count:1 ,new
  Singleton & s2 = Singleton::getInstance();   //++s_count:2
  Singleton & s3 = Singleton::getInstance();   //++s_count:3
  s1.print();
  s1.release(); //--s_count 
  s2.print();
  s3.print();
  cout<<"&s1:"<<&s1<<endl;
  cout<<"&s2:"<<&s2<<endl;
  cout<<"&s3:"<<&s3<<endl;

  s2.release();
  s3.release();
  return 0;
}

十九、 成员指针

本章节了解

  1. 成员变量指针
    1)定义
    类型 类名::*成员指针变量名 = &类名::成员变量;
    2)使用
    对象.成员指针变量名;
    对象指针->成员指针变量名;
    注:“.
    ”和“->
    ”是一个符号,不能写分家了
    参考代码:08memptr.cpp
#include <iostream>
#include <cstdio.h>
using namespace std;
class A{
  A(int i=0):m_i(i){}
  int m_i;
};
int main(void){
  //成员变量指针
  int A::*m_pi = &A::m_i;
  A a(100);
  A* pa = new A(200);
  cout<<a.*m_pi<<endl;
  cout<<pa->*m_pi<<endl;

  printf("&a = %p\n",&a);
  printf("m_pi=%p\n",m_pi);
  printf("&a.m_i=%p\n",&a.m_i);
  return 0;              
}
  1. 成员函数指针
    1)定义
    返回类型 (类名::*成员函数指针)(参数表)
    = &类名::成员函数名;
    2)使用
    (对象.*成员函数指针)(实参表);
    (对象指针->*成员指针变量名)(实参表);
    参考代码:09memptr.cpp
#include <iostream>
using namespace std;
class A{
public:
  A(int i=0):m_i(i){}
  void print(void){
     cout<<m_i<<endl;
  }
private:
  int m_i;
};
int main(void){
  //成员函数指针
  void (A::*pfunc)(void) = &A::print;
  A a(123);
  A *pa = new A(321);
  (a.*pfunc)();
  (pa->*pfunc)();
  return 0;
}

员工管理系统版本V.4

第四版(4.0)
知识点:拷贝构造和拷贝赋值、静态成员
需求:优化和改写员工类
1)禁止拷贝构造和拷贝赋值操作
2)创建对象时,从指定文件(id.txt)获取工号,保证 ID 唯一不能重复
3)增加静态成员变量(m_count)保存员工人数,并写入文件(count.txt)
4)增加静态成员函数(getEmployeeCount),从文件中获取员工人数
提示:
class Employee{
构造函数(const string& name,double salary):m_name(…),m_salary(…){
打开 id.txt 文件;//fopen(“id.txt”,“r+”);
从 id.txt 文件中读取 ID 信息并初始化 m_id;//fscanf
ID 加+1,再保存回 id.txt//fprintf
关闭 id.txt//fclose()

根据 m_id 创建同名的文件//file = fopen(m_id,“w”)
保存员工信息到文件//save();
记录员工人数//++m_count;
保存员工人数到 count.txt//fopen(“w”)->fprintf()->fclose()
}
FILE* file;//保存文件信息
}
void init(void){
//检查记录 ID 号的文件"id.txt"是否存在
if(不存在){
创建,并初始化工号为:10001
}
//检查记录员工人数的文件"count.txt",是否存在
if(不存在){
创建,并初始化计数为:0
}
else{
存在则调用 getEmployeeCount 加载员工人数
}
}
int main(){
init();
}

参考代码:Employee.h、Employee.cpp、main.cpp 、Makefile
代码1:Employee.h

#ifndef __EMPLOYEE_H
#define __EMPLOYEE_H
#include <iostream>
#include <cstdio>
using namespace std;

class Employee{
public:
    Employee(const string& name,double salary);
    ~Employee(void);
    void printInfo(void)const;
    void calSalary(void);
    void setId(int id);
    void setName(const string& name);
    void setSalary(double salary);
    void saveInfo(void)const;
private:
    //禁止拷贝构造和拷贝复制
    Employee& operator=(const Employee&);
    Employee(const Employee&);
private:
    int m_id;//工号
    string m_name;//姓名
    double m_salary;//工资
    FILE* file;
public:
    //获取员工人数
    static const int& getEmployeeCount(void);
private:
    static int m_count;//记录员工人数
};

#endif//__EMPLOYEE_H

代码2:Employee.cpp

#include "Employee.h" 

Employee::Employee(const string& name,
        double salary):m_name(name),m_salary(salary){
    //读取ID
    FILE* fp = fopen("id.txt","r+");
    fscanf(fp,"%d",&m_id);
    //将文件读写指针定位到文件头
    fseek(fp,0,SEEK_SET);
    //ID加+1,再保存回id.txt
    fprintf(fp,"%d",m_id+1);
    //关闭id.txt
    fclose(fp);
    
    //将ID转换为字符串
    char filename[20] = {0};
    sprintf(filename,"%d",m_id);
    //根据id创建文件保存当前员工信息
    file = fopen(filename,"w");
    //保存员工信息
    saveInfo();

    //记录员工人数
    ++m_count;
    //保存员工人数
    FILE* fp2 = fopen("count.txt","w");
    fprintf(fp2,"%d",m_count);
    fclose(fp2);
}
Employee::~Employee(void){
    fclose(file);
    file = NULL;
}

void Employee::printInfo(void)const{
    cout << "姓名:" << m_name << endl;
    cout << "工号:" << m_id << endl;
    cout << "基础工资:"  << m_salary << endl;
}

void Employee::calSalary(void){
    cout << "请输入出勤天数:";
    int days;
    cin >> days;
    double basic = m_salary* (days/23.0);
    double merit = basic / 2;
    cout << "总工资:" << (basic + merit) << endl;
}
void Employee::setId(int id){
    if(id<10000)
        cout << "无效的工号" << endl;
    else
        m_id = id;
}
void Employee::setName(const string& name){
    if(name.size() > 20)
        cout << "无效的姓名" << endl;
    else
        m_name = name;
}
void Employee::setSalary(double salary){
    if(salary<0)
        cout << "无效的工资" << endl;
    else
        m_salary = salary;
}

void Employee::saveInfo(void)const{
    //将文件读写指针定位到文件头
    fseek(file,0,SEEK_SET);
    //保存id、name、salary
    fprintf(file,"%d %s %g",m_id,m_name.c_str(),m_salary);
}

//静态成员需要在类的外部单独定义和初始化
int Employee::m_count = 0;

const int& Employee::getEmployeeCount(void){
    FILE* fp = fopen("count.txt","r");
    //加载员工人数
    fscanf(fp,"%d",&m_count);
    fclose(fp);
    return m_count;
}

代码3:main.cpp

#include "Employee.h"
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
void init(void){
    if(access("id.txt",F_OK)==-1){
        FILE* fp = fopen("id.txt","w");//记录工号
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",10001);
        fclose(fp);
    }
    if(access("count.txt",F_OK)==-1){
        FILE* fp = fopen("count.txt","w");//记录人数
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",0);
        fclose(fp);
    }
    else{
        //加载员工人数
        Employee::getEmployeeCount();
    }
}

int main()
{
    init();
    cout << "员工人数:" << Employee::getEmployeeCount() << endl;; 
    
    Employee emp("张三",6600);
    emp.printInfo();
    emp.calSalary();
    cout << "员工人数:" << Employee::getEmployeeCount() << endl;; 

    Employee* pemp = new Employee("李四",8800);
    pemp->printInfo();
    pemp->calSalary();
    cout << "员工人数:" << Employee::getEmployeeCount() << endl;; 
    return 0;
}

Makefile文件:Makefile

PROJ = employee
OBJS = main.o employee.o 
LINK = g++
RM = rm -rf 
CFLAGS = -c -Wall -I.
$(PROJ):$(OBJS)
	$(LINK) $^ -o $@
.c.o:
	$(LINK) $^
clean:
	$(RM) $(PROJ) $(OBJS) *.gch count.txt id.txt 1000*

二十、 操作符重载(operator)

  1. 双目操作符重载 L#R (left # right)
    1.1 计算类双目操作符: + - …
    1)表达式结果是右值,不能对表达式结果再赋值
    2)左右操作数既可以是左值也可以是右值
    3)两种具体实现方式
    –》成员函数形式(左调右参)
    L#R的表达式可以被编译器处理为"L.operator#®"成员函数调用形式,该函数的返回就是表达式结果。
    参考代码:complex.cpp
#include <iostream>
using namespace std;
//复数
class Complex{
public:
  Complex(int r,int i):m_r(r),m_i(i){}
  void print(void) const{
    cout<<m_r<<"+"<<m_i<<"i"<<endl;
  }
#include <iostream>
using namespace std;
//复数
class Complex{
public:
  Complex(int r,int i):m_r(r),m_i(i){}
  void print(void) const{
    cout<<m_r<<"+"<<m_i<<"i"<<endl;
  }
  //重载+:实现自定义的复数对象相加
  //注意:匹配的左右操作数既可以是左值也可以是右值,不能对表达式结果再赋值
  //C1+C2 ==> C1.operation+(c2)
  //注:三个const作用:
  // (1)修饰返回值,禁止对表达式结果再赋值
  // (2)常引用参数,支持常量型的右操作数
  // (3)常成员函数,支持常量型的左操作数
  const Complex operator+(const Complex &c) const {
    Complex res(m_r+c.m_r,m_i+c.m_i);
    return res;
  }
  const Complex operator-(const Complex &c) const {
    Complex res(m_r-c.m_r,m_i-c.m_i);
    return res;
  }
private:
  int m_r;  //实部
  int m_i;  //虚部
};
int main(void){
  const Complex c1(1,2);
  const Complex c2(3,4);
  c1.print();  //1+2i;
  c1.print();  //3+4i;
  //Complex c3 = c1.operation+(c2)
  Complex c3 = c1 + c2;
  c3.print();  //4+6i

  //练习:利用操作符重载机制,支持如下运算 
  //Complex c4 = c2.operation-(c1)
  Complex c4 = c2 -c1;
  c4.print();  //2+2i
  return 0;
}

–》全局函数形式(左右都参)
L#R的表达式可以被编译器处理为"operator#(L,R)"全局函数调用形式,该函数的返回就是表达式结果。
注:通过friend关键字可以把一个全局函数声明为某个类的友元,对于友元函数可以访问类中的任何成员。
参考代码:02complex.cpp

#include <iostream>
using namespace std;
//复数
class Complex{
public:
  Complex(int r,int i):m_r(r),m_i(i){}
  void print(void) const{
    cout<<m_r<<"+"<<m_i<<"i"<<endl;
  }
private:
  int m_r;  //实部
  int m_i;  //虚部
  //友元
  friend const Complex operator+(const Complex& left,const Complex & right);
  friend const Complex operator-(const Complex& left,const Complex & right);
};
//重载+/-:全局函数形式
const Complex operator+(const Complex& left,const Complex & right){
  Complex res(left.m_r+right.m_r,left.m_i+right.m_i);
  return res;
}
const Complex operator-(const Complex& left,const Complex & right){
  Complex res(left.m_r-right.m_r,left.m_i-right.m_i);
  return res;
}

int main(void){
  const Complex c1(1,2);
  const Complex c2(3,4);
  c1.print();  //1+2i;
  c1.print();  //3+4i;
  //Complex c3 = c1.operation+(c2,c2);
  Complex c3 = c1 + c2;
  c3.print();  //4+6i

  //Complex c4 = c2.operation-(c2,c1)
  Complex c4 = c2 -c1;
  c4.print();  //2+2i
  return 0;
}

1.2 赋值类双目操作符: + = - = …
1)表达式结果是左值,就是左操作数的自身
2)左操作数一定是左值,右操作数可以是左值也可以是右值
3)两种具体实现方式
–》成员函数形式(左调右参),L#R == > L.operator#®
–》全局函数形式(左右都参),L#R = = > operator#(L,R)
参考代码:03complex.cpp

#include <iostream>
using namespace std;
//复数
class Complex{
public:
  Complex(int r,int i):m_r(r),m_i(i){}
  void print(void) const{
    cout<<m_r<<"+"<<m_i<<"i"<<endl;
  }
  //+=:成员函数形式
  //c1+=c2; ==> c1.operator+=(c2);
  Complex & operator+=(const Complex& c){
     m_r += c.m_r;
     m_i += c.m_i;
     return *this;
  }
  //-=:全局函数形式(可以使用friend将其定义在类的内部,但本质haunt是全局函数)
  //c1-=c2==>operator-=(c1,c2)
  friend Complex& operator-=(Complex& left,const Complex& right){
    left.m_r -= right.m_r;
    left.m_i -= right.m_i;
    return left;
  }
private:
  int m_r;  //实部
  int m_i;  //虚部
};

int main(void){
  Complex c1(1,2);
  Complex c2(3,4);
  c1.print();  //1+2i;
  c1.print();  //3+4i;
  c1+= c2;  //c1.operator+=(c2);
  c1.print();  //4+6i
  Complex c3(5,6);
  (c1+=c2) = c3; //ok
  c1.print(); //5+6i

  c1 -= c2;  //operator-=(c1,c2)
  c1.print(); //2+2i
  return 0;
}
  1. 单目操作符重载 #O
    1.1 计算类单目操作符:-(负) ~(位反) …
    1)表达式结果是右值,不能对表达式结果再赋值
    2)操作数既可以是左值也可以是右值
    3)两种具体实现方式
    –》成员函数形式:#O = => O.operator#()
    –》全局函数形式:#O = => operator#(O)
    参考代码04Integer.cpp
#include <iostream>
using namespace std;
class Integer{
public:
  Integer(int i=0):m_i(i){}
  void print(void) const{
     cout<<m_i<<endl;
  }
  //-(负):成员函数形式
  const Integer operator-(void) const {
     Integer res(-m_i);
     return res;
  }
  //~:全局函数形式(自定义表示平方功能)
  friend const Integer operator~(const Integer & i){
     Integer res(i.m_i * i.m_i);
     return res;
  }
private:
  int m_i;
};
int main(void){
  Integer i(100);
  Integer j = -i;  //i.operator-()
  j.print(); //-100
  j = ~i;  //operator~(i);
  j.print();   //10000
  return 0;
}

1.2 自增减单目操作符:++ – …
1)前++、–
–》表达式结果是左值,就是操作数自身
–》操作数一定是左值
–》两种具体实现方式
成员函数形式:#O = => O.operator#()
全局函数形式:#O = => operator#(O)

2)后++、–
–》表达式结果是右值,是操作数自增减前副本,不能对表达结果再赋值
–》操作数一定是左值
–》两种具体实现方式
成员函数形式:O# ==> O.operator#(int/哑元/)
全局函数形式:O# ==> operator#(O,int/哑元/)
注意:后缀自增减为了前缀区分,语法规定增加了哑元参数
参考代码:05Integer.cpp

#include <iostream>
using namespace std;
class Integer{
public:
  Integer(int i=0):m_i(i){}
  void print(void) const{
     cout<<m_i<<endl;
  }
  //前++:成员函数形式,++i==> i.operator++()
  Integer& operator++(void){
    ++m_i;
    return *this;
  }
  //前--:全局函数形式,--i==> operator--(i)
  friend Integer& operator--(Integer& i){
    --i.m_i;
    return i;
  }
  //后++:成员函数形式,i++ ==> i.operator++(0/*哑元*/)
  const Integer operator++(int/*哑元*/){
     Integer old = *this;
     ++m_i;
     return old;
  }
  //后--:全局函数形式,i-- ==>operator--(i,0/*哑元*/)
  friend const Integer operator--(Integer &i,int/*哑元*/){
    Integer old =i;
    --i.m_i;
    return old;
  }
private:
  int m_i;
};
int main(void){
  Integer i(100);
  Integer j = ++i;
  i.print();  //101
  j.print(); //101;
  j = ++++i;
  i.print(); //103
  j.print(); //103
  
  j = --i;
  i.print();  //102
  j.print();  //102
  j = ----i;  
  i.print();  //100
  j.print();  //100

  j = i++;
  i.print();  //101
  j.print();  //100
  return 0;
}

  1. 输出(插入)和输入(提取)操作符重载: << >>
    功能:实现自定义类型对象的直接输出或输入
    注:只能使用全局函数形式,不能使用成员函数形式
    #include <iostream>
    ostream   //标准输出流类,cout就是该类实例化的对象
    istream    //标准输入流类,cin就是该类实例化的对象	

    //全局函数形式:operator<<(cout,a)
    cout << a;
    //全局函数形式:operator>>(cin,a)
    cin << b;

    //cout<<a ==>operator<<(cout,a)
    friend ostream& operator<<(ostream& os,const Right& right){
    ...
      return os;
    }

    //cin>>a ==>operator>>(cin,a)
    friend istream& operator>>(istream& is,Right& right){
    ...
      return is;
    }


    参考代码:
#include <iostream>
using namespace std;
class Student{
public:
  Student(const string & name,int age,int no):m_name(name),m_age(age),m_no(no){}
  //重载<<
  //cout<<s ==> operator<<(cout,s)
  friend ostream& operator<<(ostream& os,const Student& s){
    os<<"我叫"<<s.m_name<<",今年"<<s.m_age<<"岁,学号"<<s.m_no;
    return os;
  }
  //重载>>,cin>>s ==> operator>>(cin,s)
  friend istream & operator>>(istream & is,Student & s){
    cout<<"请输入姓名:";
    is>>s.m_name;
    cout<<"请输入年龄:";
    is>>s.m_age;
    cout<<"请输入学号:";
    is>>s.m_no;
    return is;
  }
private:
  string m_name;
  int m_age;
  int m_no;
};
int main(void){
  Student stu("张飞",26,10011);
  cout<<stu<<endl;
  Student stu2("赵云",24,10012);
  cout<<stu<<";"<<stu2<<endl;
  return 0;
}

综合练习:实现3*3矩阵类,支持下面操作符重载
+ - * += -= -(负) 前++、-- 后++、-- <<(输出) * *=
提示:

     m1          +          m2          =1 2 3		9 8 7		10 10 10
    4 5 6	+	6 5 4	=	10 10 10
    7 8 9		3 2 1		10 10 10
  
    m1           -           m2         =                 ?
    1 2 3		9 8 7		-8 -6 -4
    4 5 6	    -           6 5 4	=	 2  0  2
    7 8 9		3 2 1		 4  6  8
  
    m1           *           m2            =            ?   
    1 2 3		9 8 7		30  24  18   
    4 5 6	   *            6 5 4	=	84  69  54
    7 8 9		3 2 1		128 114 90 
			
    A 1~2
    B 3~4
    C 5~6
    D 其它

参考代码:07M33.cpp

#include <iostream>
using namespace std;
class M33{
public:
  M33(void){
     for(int i=0;i<3;i++)
       for(int j=0;j<3;j++)
          m_a[i][j] = 0;
  }
  explicit M33(int a[][3]){
    for(int i = 0;i<3;i++)
       for(int j=0;j<3;j++)
          m_a[i][j] = a[i][j];
  }
  //重载<<
  friend ostream& operator<<(ostream& os,const M33& m){
    for(int i =0;i<3;i++){
      for(int j =0;j<3;j++){
        os<<m.m_a[i][j]<<" ";
      }
      cout<<endl;
    }
    return os;
  }
  //重载 +
  const M33 operator+(const M33& m) const{
     int a[3][3] = {0};
     for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
           a[i][j] = m_a[i][j]+m.m_a[i][j];
     //M33 res(a);
     //return res;
     return M33(a);
  }
  //重载 -
  const M33 operator-(const M33& m) const{
     int a[3][3] = {0};
     for(int i=0;i<3;i++)
        for (int j=0;j<3;j++)
           a[i][j] = m_a[i][j] - m.m_a[i][j];
     M33 res(a);
     return res;
  }

  //重载* 乘号
  const M33 operator*(const M33& m) const{
     int a[3][3] = {0};
     for(int i=0;i<3;i++)
        for (int j=0;j<3;j++)
           for(int k=0;k<3;k++)
           a[i][j] += m_a[i][k] - m.m_a[k][j];
     M33 res(a);
     return res;
  }

  //重载+=, "m1+=m2" <=等价=> "m1=m1+m2"
  M33 & operator+=(const M33& m){
     *this = *this + m;
     return *this;
  }

  //-=
  M33 & operator-=(const M33& m){
     *this = *this - m;
     return *this;
  }

  //*= 
  M33 & operator*=(const M33& m){
     *this = *this * m;
     return *this;
  }

  //-(负)
  const M33 operator-(void) const{
     //M33 m;
     //return m - *this;
     return M33() - *this;
  }

  //前++
  M33 & operator++(void){
    for(int i=0;i<3;i++)
       for(int j=0;j<3;j++)
          ++m_a[i][j];
    return *this;
  }
 
  //前--
  M33 & operator--(void){
    for(int i=0;i<3;i++)
       for(int j=0;j<3;j++)
          --m_a[i][j];
    return *this;
  }

  //后++
  const M33 operator++(int /*哑元*/){
    M33 old = *this;
    ++*this;
    return old;
  }

  //后--
  const M33 operator--(int /*哑元*/){
    M33 old = *this;
    --*this;
    return old;
  }

private:
  int m_a[3][3];
};
int main(void){
  int a1[3][3] = {1,2,3,4,5,6,7,8,9};
  M33 m1(a1);
  int a2[3][3] = {9,8,7,6,5,4,3,2,1};
  M33 m2(a2);
  cout<<m1<<endl;
  cout<<m2<<endl;
  cout<<m1+m2<<endl;
  cout<<m1-m2<<endl;
  cout<<m1*m2<<endl;
  cout<<(m1+=m2)<<endl;
  cout<<(m1-=m2)<<endl;
  cout<<(m1*=m2)<<endl;

  cout<<-m2<<endl;
  cout<<++m2<<endl;
  cout<<--m2<<endl;
  cout<<m2++<<endl;
  cout<<m2--<<endl;
  
  return 0;
}
  1. 下标操作符重载 []
    功能:实现自定义类型对象能够像数组一样去使用
    注:非常对象返回左值,常对象返回右值
```
string s = "minwei";
s[0] = 'M';       //s.operator[](0) = 'M'
s[3] = 'W';      //s.operator[](3) = 'W'
cout << s << endl;    //"MinWei"

const string s2 = "youchengwei";
cout << s2[0] << endl;//y
s2[0] = 'Y';          //error
```

参考代码:08array.cpp
#include <iostream>
using namespace std;
//容器类:里面可以存放若干个int数据
class Array{
public:
  Array(size_t size){
     m_arr = new int[size];
  }
  ~Array(void){
     delete[] m_arr;
  }
  //重载[]
  int& operator[](size_t i){ //返回左值
     return m_arr[i];
  }
  const int & operator[](size_t i) const { //返回右值
    return m_arr[i];
  }
private:
  int * m_arr; 
};
int main(void){
  Array arr(5);
  arr[0] = 123;  //arr.operator[](0) = 123
  const Array& carr = arr;
  //carr[0] =321;  //error
  cout<<carr[0]<<endl;  //123,0k

  return 0;
}
  1. 函数操作符重载 ()
    功能:实现让自定义类型对象像函数一样使用 //仿函数
    注:对于参数个数、参数类型和返回类型没有任何限制
    A a(…);
    a(100,1.23); //a.operator()(100,1.23)
    参考代码:09func.cpp
#include <iostream>
using namespace std;
class Func{
public:
  int operator()(int a,int b){
     return a* b;
  }
  int operator()(int a){
     return a *a;
  }
};
int main(void){
  Func func;
  //func.operator()(100,200)
  cout<<func(100,200)<<endl; //20000
  //func.operator()(200)
  cout<<func(200)<<endl; //20000

}
  1. 动态内存管理操作符重载new/delete
    //了解
    class 类名{
    static void* operator new(size_t size){…}
    static void operator delete(void* p){…}
    };
    参考代码:09new.cpp
#include <iostream> 
using namespace std;
class A{
public:
  A(void)(cout<<"A的构造函数"<<endl;)
  ~A(void)(cout<<"A的析构函数"<<endl;)
  static void * operator new(size_t size){
     cout<<"分配内存"<<endl;
     void *pv = malloc(size);
     return pv;
  }
  static void operator delete(void *pv){
     cout<<"释放内存"<<endl;
     free(pv);
  }
};
int main(void){
  //1)A* pa = (A*)A::operator new(sizeof(A))
  //2)pa ->构造函数
  A* pa = new A;

  //1)pa ->析构函数
  //2)A::operator delete(pa)
  delete pa;

  return 0;
}
  1. 操作符重载限制
    1)不是所有操作符都能重载,下面几个操作符不能重载
    –》作用域限定操作符 “::”
    –》直接成员访问操作符 “.”
    –》直接成员指针解引用操作符 “.*”
    –》条件操作符 “?:”
    –》字节长度操作符 “sizeof”
    –》类型信息操作符 “typeid”//后面讲
    2)如果一个操作符所有的操作数都是基本类型,则无法重载
    3)操作符重载不会改变预定的优先级
    4)操作符重载不会改变操作数个数
    5)不能通过操作符重载发明新的操作符
    6)只能使用成员函数形式重载的操作符
    = [] () ->

员工管理系统版本v.5

知识点:操作符重载(5.0)
需求:优化和改写员工类
1)为员工类增加输出操作符重载 <<
2)为员工类增加比较操作符重载“= =” “!=”
提示:
ostream& operator<<(ostream& os,const Employee& emp){…}
//根据员工对象的 id 和参数 id 比较,是否相等
bool operator==(int id)const{…}
bool operator!=(int id)const{…}
参考代码:Employee.h、Employee.cpp、main.cpp、Makefile
代码1:Employee.h

#ifndef __EMPLOYEE_H
#define __EMPLOYEE_H

#include <iostream>
#include <cstdio>
using namespace std;

class Employee{
public:
    Employee(const string& name,double salary);
    ~Employee(void);
    void printInfo(void)const;
    void calSalary(void);
    void setId(int id);
    void setName(const string& name);
    void setSalary(double salary);
    void saveInfo(void);

    //根据员工对象的id和参数id比较,判断是否为同一个员工
    bool operator==(int id)const;
    bool operator!=(int id)const;    
    //重载插入操作符,实现员工信息打印输出
    friend ostream& operator<<(
            ostream& os,const Employee& emp);

private:
    //禁止拷贝构造和拷贝复制
    Employee& operator=(const Employee&);
    Employee(const Employee&);
private:
    int m_id;//工号
    string m_name;//姓名
    double m_salary;//工资
    FILE* file;//保存员工信息的文件
public:
    //获取员工人数
    static const int& getEmployeeCount(void);
private:
    static int m_count;//记录员工人数
};

#endif

代码2:Employee.cpp

#include "Employee.h" 

Employee::Employee(const string& name,
    double salary):m_name(name),m_salary(salary){
    //读取ID
    FILE* fp = fopen("id.txt","r+");
    fscanf(fp,"%d",&m_id);
    //将文件读写指针定位到文件头
    fseek(fp,0,SEEK_SET);
    //ID加+1,再保存回id.txt
    fprintf(fp,"%d",m_id+1);
    //关闭id.txt
    fclose(fp);
    
    //将ID转换为字符串
    char filename[20] = {0};
    sprintf(filename,"%d",m_id);
    //根据id创建文件保存当前员工信息
    file = fopen(filename,"w");
    //保存员工信息
    saveInfo();

    //记录员工人数
    ++m_count;
    //保存员工人数
    FILE* fp2 = fopen("count.txt","w");
    fprintf(fp2,"%d",m_count);
    fclose(fp2);
}
Employee::~Employee(void){
    fclose(file);
    file = NULL;
}
void Employee::printInfo(void)const{
    cout << "姓名:" << m_name << endl;
    cout << "工号:" << m_id << endl;
    cout << "基础工资:"  << m_salary << endl;
}

void Employee::calSalary(void){
    cout << "请输入出勤天数:";
    int days;
    cin >> days;
    double basic = m_salary* (days/23.0);
    double merit = basic / 2;
    cout << "总工资为:" <<(basic + merit) << endl;
}
void Employee::setId(int id){
    if(id<10000)
        cout << "无效的工号" << endl;
    else
        m_id = id;
}
void Employee::setName(const string& name){
    if(name.size() > 20)
        cout << "无效的姓名" << endl;
    else
        m_name = name;
}
void Employee::setSalary(double salary){
    if(salary<0)
        cout << "无效的工资" << endl;
    else
        m_salary = salary;
}

void Employee::saveInfo(void){
    //保存id、name、salary
    fprintf(file,"%d %s %g",m_id,m_name.c_str(),m_salary);
}

//静态成员需要在类的外部单独定义和初始化
int Employee::m_count = 0;

const int& Employee::getEmployeeCount(void){
    FILE* fp = fopen("count.txt","r");
    //加载员工人数
    fscanf(fp,"%d",&m_count);
    fclose(fp);
    return m_count;
}
//根据员工对象的id和参数id比较,是否相等
bool Employee::operator==(int id)const{
    if(m_id == id){
        return true;
    }
    else{
        return false;
    }
}
bool Employee::operator!=(int id)const{
    return  !(*this == id);
}

//全局函数,在类的外部定义时不要再写friend
ostream& operator<<(ostream& os,const Employee& emp){
    os << "Employee(" << emp.m_id << "," << emp.m_name << 
        "," << emp.m_salary << ")";
    return os;
}

代码3:main.cpp

#include "Employee.h"
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
void init(void){
    if(access("id.txt",F_OK)==-1){
        FILE* fp = fopen("id.txt","w");//记录工号
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",10001);
        fclose(fp);
    }
    if(access("count.txt",F_OK)==-1){
        FILE* fp = fopen("count.txt","w");//记录人数
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",0);
        fclose(fp);
    }
    else{
        //加载员工人数
        Employee::getEmployeeCount();
    }
}

int main()
{
    init();
    Employee emp("张三",6600);
    //emp.printInfo();
    cout << emp << endl;
    emp.calSalary();

    if(emp == 10001){
        cout << "true:工号为10001的员工存在" << endl;
    }
    else{
        cout << "false:工号为10001的员工不不在" << endl;
    }

    return 0;
}

参考代码: Makefile

PROJ = employee
OBJS = main.o employee.o 
LINK = g++
RM = rm -rf 
CFLAGS = -c -Wall -I.
$(PROJ):$(OBJS)
	$(LINK) $^ -o $@
.c.o:
	$(LINK) $^
clean:
	$(RM) $(PROJ) $(OBJS) *.gch count.txt id.txt 1000*

二十一、 继承(Inheritance)

  1. 继承的概念 //了解
    通过一种机制描述类型之间共性和特性的方式,利用已有的数据类型定义新的数据类型,这种机制就是继承.
    eg:
    人 类:姓名、年龄、吃饭、睡觉
    学生类:姓名、年龄、吃饭、睡觉、学号、学习
    教师类:姓名、年龄、吃饭、睡觉、工资、讲课
人  类:姓名、年龄、吃饭、睡觉
学生类 继承 人类:学号、学习
教师类 继承 人类:工资、讲课
...

人类(基类/父类)
/    \

学生类 教师类(派生类/子类)

基类–派生–>子类
子类–继承–>基类

  1. 继承的语法
    class 子类:继承方式 基类1,继承方式 基类2,…{

    };
    继承方式:
    –> 公有继承(public)
    –> 保护继承(protected)
    –> 私有继承(private)
    参考代码:01inherit.cpp
#include <iostream>
using namespace std;

class Human{
public:
  Human(const string& name,int age):m_name(name),m_age(age){}
  void eat(const string &food){
     cout<<"我在吃"<<food<<endl;
  }
  void sleep(int time){
    cout<<"我睡了"<<time<<"小时"<<endl;
  }
protected:
  //保护成员:在当前类的内部和子类中可以使用
  string m_name;
  int m_age;
};

class Student:public Human{
  //学生类(人类派生的子类)
public:
  //Human(name,age):指明基类部分的初始化方式
  Student(const string& name,int age,int no):Human(name,age),m_no(no){}
  void learn(const string& course){
     cout<<"我在学"<<course<<endl;
  }
  void who(void){
     cout<<"我叫"<<m_name<<",今年"<<m_age<<"岁,学号是"<<m_no<<endl;
  }
private:
  int m_no;
};

class Teacher:public Human{
  //教师类(人类派生的子类)
public:
  Teacher(const string& name,int age,int salary):Human(name,age),m_salary(salary){}
  void teach(const string& course){
    cout<<"我在讲"<<course<<endl;
  }
  void who(void){
     cout<<"我叫"<<m_name<<",今年"<<m_age<<"岁,工资为"<<m_salary<<endl;
  }
private:
  int m_salary;
};
int main(void){
  Student s("关羽",30,10086);
  s.who();
  s.eat("面包");
  s.sleep(8);
  s.learn("孙武兵法");
  Teacher t("孙悟空",35,50000);
  t.who();
  t.eat("桃子");
  t.sleep(6);
  t.teach("unix系统编程");
  return 0;
}

  1. 公有继承(public)的语法特性
    1)子类对象会继承基类的属性和行为,通过子类对象可以访问基类中的成员,如同是基类对象在访问它们一样。
    注:子类对象中包含的基类部分被称为“基类子对象”
    2)向上造型(upcast) //重点掌握
    将子类类型的指针或引用转换为基类类型的指针或引用;这种操作性缩小的类型转换,在编译器看来是安全的,可以直接隐式转换.
    基 类

    子 类
    eg: class A{};
    class B:public A{};
    class C:public A{};
    class D:public A{};

    void func1(A* pa){}
    void func2(A& ra){}
    int main(void){
    func1(&B/&C/&D…);//向上造型
    func2(B/C/D…); //向上造型
    }
    3)向下造型(downcast)
    将基类类型的指针或引用转换为子类类型的指针或引用;这种操作性放大的类型转换,在编译器看来是危险的,不能隐式转换,但可以显式转换(推荐static_cast)。
    基 类

    子 类
    例:
    Student *ps = static_cast<Student *>(ph);
    ps->who();
4)**子类继承基类的成员**
--》在子类中,可以直接访问基类中的公有和保护成员,就如同是子类自己的成员一样。
--》基类中的私有成员,子类也可以继承,但是受到访问控制属性的影响,无法直接访问;
如果希望访问基类中的私有成员,可以让基类提供公有或保护的成员函数,来间接访问。
参考代码:02inherit.cpp
#include <iostream>
using namespace std;

class Base{
public:
  int m_public;
protected:
  int m_protected;
private:
  int m_private;
protected:
  //基类私有在子类中无法直接访问,但是可以提供类似如下的成员函数来间接访问
  void setPrivate(int data){
     m_private = data;
  }
  const int & getPrivate(void){
     return m_private;
  }
};

class Derived:public Base{
public:
  void func(void){
     m_public = 10;
     m_protected = 20;
     cout<<m_public<<endl;
     cout<<m_protected<<endl;
     setPrivate(30);
     cout<<getPrivate()<<endl;
  }
};
int main(void){
  Derived d;
  d.func();
  cout<<"size="<<sizeof(d)<<endl; //12
  return 0;
}

5)子类隐藏基类的成员
–》如果子类和基类定义了同名的成员函数,因为所属作用域不同,不能构成有效的重载关系,而是一种隐藏关系;通过子类对象将会优先访问子类自己的成员,基类的成员无法被使用。
–》如果希望访问基类中被隐藏的同名成员,可以通过"类名::"显式指明 //推荐
–》如果同名的成员函数参数不同,也可以通过using声明,将基类中的成员函数引入子类的作用域中,让它们形成重载,通过函数重载的参数匹配来解决。
参考代码:03inherit.cpp

#include <iostream>
using namespace std;

class Base{
public:
  void func(void){
    cout<<"Base::func"<<endl;
  }
};
class Derived:public Base{
public:
  void func(int i){
     cout<<"Derived::func"<<endl;
  }
  //通过using声明,将基类的func引入到子类作用域,让他们形式重载
  //using Base::func;
};
int main(void){
  Derived d;
  d.Base::func();
  d.func(123) ;
  return 0;
}
  1. 访问控制属性和继承方式
    1)访问控制属性:影响类中成员的访问位置
    访问控制限定符 访问控制属性 内部访问 子类访问 外部访问 友元访问
    public 公有成员 ok ok ok ok
    protected 保护成员 ok ok no ok
    private 私有成员 ok no no ok
2)**继承方式**:影响通过子类访问基类中成员的可访问性
基类中的成员    公有继承的子类     保护继承的子类     私有继承的子类
公有成员              公有成员                保护成员              私有成员
保护成员              保护成员                保护成员              私有成员
私有成员              私有成员                私有成员              私有成员

eg:
class _Base{
public:
    void func(void){...}	
};
class Base:private _Base{//ok
    };
class A:public Base{};//no
class B:public A{};//no
class C:public B{};//no
...

eg:
class Base{
public:
    void func(void){...}
};
//class Derived:public Base{};
class Derived:private Base{};
int main(void){
    Derived d;
    Base* pb = &d;//向上造型,error
    pb->func();
}

参考代码:04inherit.cpp
#include <iostream>
using namespace std;
class A{
public:
  int m_pub; //公有成员
protected:
  int m_protected; //保护成员
private:
  int m_private; //私有成员
};
class B:public A{ //公有继承的子类
  void func(void){
     m_public = 123;//0k
     m_protected = 123; //ok
     //m_private =123;  //no
  }
};
class C:protected A{ //保护继承的子类
  void func(void){
     m_public = 123;//0k
     m_protected = 123; //ok
     //m_private =123;  //no
  }
};
class D:private A{ //私有继承的子类
  void func(void){
     m_public = 123;//0k
     m_protected = 123; //ok
     //m_private =123;  //no
  }
};
//通过子类的子类访问基类中的成员
class X:public B{
  void foo(void){
    m_public = 123; //ok
    m_protected = 123; //ok
    //m_private  =123; /no
  }
};
class Y:public C{
  void foo(void){
    m_public = 123; //ok
    m_protected = 123; //ok
    //m_private  =123; //no
  }
};
class Z:public D{
  void foo(void){
    //m_public = 123;  //no
    //m_protected = 123; //no
    //m_private  =123; //no
  }
};
int main(void){
  //通过子类对象访问基类中的成员
  B b;
  b.m_public = 123;
  //b.protected = 123;
  //b.private = 123;

  C c;
  //c.m_public = 123;
  //c.m_protected = 123;
  //c.m_private = 123;
 
  D.d;
  //d.m_public =123; //no 
  //d.m_protected =123; //no
  //d.m_protected = 123;  //no

  return 0;
}

注意:向上造型语法特性在保护继承和私有继承不再适用。
参考代码:

#include <iostream>
using namespace std;
class Base{
public:
  int m_public;
};
//class Derived:public Base{};
//class Derived:protected Base{};  //报错
class Derived:private Base{};
int main(void){
  Derived d;
  Base *pb = &d;  //向上造型
  //Base *pb = static_cast<Base*>(&d);  //向上造型
  //Base *pb = static_cast<Base&>(&d);  //向上造型
  Base &rb = d;  //向上造型 
  return 0;
}
5. 子类的构造函数    //重点掌握
1)如果子类构造函数没有显式指明基类子对象的初始化方式,那么编译器将会自动调用基类的无参构造函数来初始化基类子对象。
2)如果希望基类子对象以有参的方式被初始化,则需要在子类构造函数的初始化列表中指明基类子对象的初始化方式。
参考代码:05constructor.cpp
#include <iostream>
using namespace std;
class Member{
public:
  Member(void):m_j(0){
    cout<<"Member(void)"<<endl;
  }
  Member(int j):m_j(j){
    cout<<"Member(int)"<<endl;
  }
  int m_j;
};
class Base{
public:
  Base(void):m_i(0){
    cout<<"Base(void)"<<endl;
  }
  Base(int i):m_i(i){
     cout<<"Base(int)"<<endl;
  }
  int m_i;
};
class Derived:public Base{ 
public:
   Derived(void){
     cout<<"Derived(void)"<<endl;
   }
   //Base(i):指明基类子对象的初始化方式
   //m_member(j):指明成员子对象的初始化方式
   Derived(int i,int j):Base(i),m_member(j){
     cout<<"Derived(int)"<<endl;
   }
   Member m_member; //成员子对象
};
int main(void){
  Derived d1;
  cout<<d1.m_i<<","<<d1.m_member.m_j<<endl;
  Derived d2(100,200);
  cout<<d2.m_i<<","<<d2.m_member.m_j<<endl; //100,200
  return 0;
}
  1. 子类对象的创建过程
    –》分配内存
    –》构造基类子对象(按继承表顺序)
    –》构造成员子对象 (按声明的顺序)
    –》执行子类构造函数代码
  1. 子类的析构函数
    1)子类的析构函数,无论是自己定义的,还是编译器缺省提供的,都会自动调用基类的析构函数,完成基类子对象的销毁操作。
    2)子类对象的销毁过程
    –》执行子类的析构函数代码
    –》析构成员子对象(按声明的逆序)
    –》析构基类子对象(按继承表逆序)
    –》释放内存
参考代码:07constructor.cpp
#include <iostream>
using namespace std;
class Member{
public:
  Member(void):m_j(0){
    cout<<"Member(void)"<<endl;
  }
  Member(int j):m_j(j){
    cout<<"Member(int)"<<endl;
  }
  ~Member(void){
    cout<<"~Member(void)"<<endl;
  }
  int m_j;
};
class Base{
public:
  Base(void):m_i(0){
    cout<<"Base(void)"<<endl;
  }
  Base(int i):m_i(i){
     cout<<"Base(int)"<<endl;
  }
  ~Base(void){
    cout<<"~Base(void)"<<endl;
  }
  int m_i;
};
class Derived:public Base{ 
public:
   Derived(void){
     cout<<"Derived(void)"<<endl;
   }
   //Base(i):指明基类子对象的初始化方式
   //m_member(j):指明成员子对象的初始化方式
   Derived(int i,int j):Base(i),m_member(j){
     cout<<"Derived(int)"<<endl;
   }
   ~Derived(void){
      cout<<"~Derived(void)"<<endl;
   }
   Member m_member; //成员子对象
};
int main(void){ 
  /*Derived d1;
  cout<<d1.m_i<<","<<d1.m_member.m_j<<endl;
  Derived d2(100,200);
  cout<<d2.m_i<<","<<d2.m_member.m_j<<endl; //100,200
  */
  Base* pb = new Derived; //向上造型
  //...
  delete pb;      //内存泄漏  
  return 0;
}

3)基类的析构函数不能调用子类的析构函数,所以如果delete一个指向子类对象的基类指针,实际被调用将只是基类的析构函数,子类的析构函数执行
不到,有内存泄漏的风险!
class A{…} ;
class B:public A{…};
A* pa = new B; //pa指向子类对象的基类指针
delete pa; //只会调用基类的析构函数,有内存泄漏的风险
解决方法:虚析构函数 //后面讲

  1. 子类拷贝构造和拷贝赋值
  2. 子类的拷贝构造
    (1)如果子类没有定义拷贝构造函数,编译器会为子类提供缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,完成基类子对象的拷贝初始化。
    (2)如果子类自己定义拷贝构造函数,需要使用初始化列表,显式指明基类子对象也要以拷贝的方式进行初始化。
    参考代码:07
#include <iostream>
using namespace std;
class Base{
public:
  Base(void):m_i(0){}
  Base(int i):m_i(i){}
  Base(const Base& that):m_i(that.m_i){
    cout<<"基类的拷贝构造函数"<<endl;
  }
  int m_i;
};
class Derived:public Base{
public:
  Derived(void):m_j(0){}
  Derived(int i,int j):Base(i),m_j(j){}
  //Base(that):指明基类子对象以拷贝的方式进行初始化
  Derived(const Derived& that):Base(that),m_j(that.m_j){}
  int m_j;
};
int main(void){
  Derived d1(100,200);
  cout<<d1.m_i<<","<<d1.m_j<<endl;  //100,200
  Derived d2(d1); //拷贝构造
  cout<<d2.m_i<<","<<d2.m_j<<endl;  //100,200
  return 0;
}
  1. 拷贝赋值
    (1)如果子类没有定义拷贝赋值函数,那么编译器会为了子类提供一个缺省的拷贝赋值函数,该函数会自动调用基类拷贝赋值函数,完成基类子对象的复制。
    (2)如果子类自己定义了拷贝赋值函数,那么需要显式调用基类的拷贝赋值函数,完成基类子对象的复制。
    class Base{};
    class Derived:public Base{
    Derived & operator = (const Derived & that){
    if(&that != this){
    //显式调用基类的拷贝赋值函数,完成基类子对象的复制
    Base::operator = (that);
    }
    return *this;
    }
    }
    参考代码:08copy.cpp
#include <iostream>
using namespace std;
class Base{
public:
  Base(void):m_i(0){}
  Base(int i):m_i(i){}
  Base(const Base& that):m_i(that.m_i){
    cout<<"基类的拷贝构造函数"<<endl;
  }
  Base & operator =(const Base& that){
    cout<<"基类的拷贝赋值函数"<<endl;
    if(&that != this){
      m_i = that.m_i;
    }
    return *this;
  }
  int m_i;
};
class Derived:public Base{
public:
  Derived(void):m_j(0){}
  Derived(int i,int j):Base(i),m_j(j){}
  //Base(that):指明基类子对象以拷贝的方式进行初始化
  Derived(const Derived& that):Base(that),m_j(that.m_j){}
  Derived & operator=(const Derived& that){
    if(&that != this){
      //显式调用基类的拷贝赋值函数,完成基类子对象的复制
      Base::operator = (that);
      m_j = that.m_j;
    }
    return *this;
  }
  int m_j;
};
int main(void){
  Derived d1(100,200);
  cout<<d1.m_i<<","<<d1.m_j<<endl;  //100,200
  Derived d2(d1); //拷贝构造
  cout<<d2.m_i<<","<<d2.m_j<<endl;  //100,200
  Derived d3;
  d3 = d1;   //拷贝赋值,de.operator = (d1)
  cout<<d3.m_i<<","<<d3.m_j<<endl;  //100,200
  return 0;
}
  1. 子类的操作符重载 //了解
    参考代码:
#include<iostream>
using namespace std;
class Human{
public:
  Human(const string& name,int age):m_name(name),m_age(age){}
  friend ostream& operator<<(ostream& os,const Human & h){
    os<<h.m_name<<","<<h.m_age;
    return os;
  }
private:
  string m_name;
  int m_age;
};
class Student:public Human{
public:
 Student(const string& name,int age,int no):Human(name,age),m_no(no){}
 friend ostream & operator<<(ostream &os,const Student& s){
   os<<(Human&)s<<","<<s.m_no<<endl;
 }
private:
  int m_no;
};
int main(void){
  Student s("张三",20,10001);
  cout<<s<<endl;
  return 0;
}

员工管理系统版本v.6

知识点:继承
需求:增加技术员类、经理类
1)技术员增加研发津贴(元/小时),绩效工资=工作小时数研发津贴进度因数(输入)
2)经理类增加绩效奖金(元/月),绩效工资=绩效奖金*绩效因数(输入)
提示:
员工类
/
技术员 经理
//子类中需要使用基类的成员,可以声明为保护(protected)
void printInfo(){
printBasic();//打印公有信息
printExtra();//打印特有信息
}
void calSalary(){
//基本工资()+绩效工资()
calBasic() + calMerit()
}
参考代码:Employee.h、Employee.cpp、Manager.cpp、Manager.h、Technician.cpp、Technician.h 、main.cpp、Makefile
代码1:Employee.h

#ifndef __EMPLOYEE_H_
#define __EMPLOYEE_H_

#include <iostream>
#include <cstdio>
using namespace std;

class Employee{
public:
    Employee(const string& name,double salary);
    ~Employee(void);
    //打印信息
    void printInfo(void)const;
protected:
    void printBasic(void)const;//公有信息
    void printExtra(void)const;//特有信息
public:
    //计算工资
    void calSalary(void);
protected:
    double calBasic(void);//基本工资
    double calMerit(void);//绩效工资
public:
    void setId(int id);
    void setName(const string& name);
    void setSalary(double salary);
    void saveInfo(void);
    bool operator==(int id)const;
    bool operator!=(int id)const;
    friend ostream& operator<<(ostream& os,const Employee& emp);
private:
    //禁止拷贝构造和拷贝复制
    Employee& operator=(const Employee&);
    Employee(const Employee&);
protected://保护成员子类可以直接访问
    int m_id;//工号
    string m_name;//姓名
    double m_salary;//工资
    FILE* file;//保存员工信息指针
    double m_attend;//出勤率
public:
    //获取员工人数
    static const int& getEmployeeCount(void);
private:
    static int m_count;//记录员工人数
};

#endif//__EMPLOYEE_H_

参考代码:Employee.cpp

#include "Employee.h" 

Employee::Employee(const string& name,
        double salary):m_id(0),m_name(name),m_salary(salary){
    //读取ID
    FILE* fp = fopen("id.txt","r+");
    fscanf(fp,"%d",&m_id);
    //将文件读写指针定位到文件头
    fseek(fp,0,SEEK_SET);
    //ID加+1,再保存回id.txt
    fprintf(fp,"%d",m_id+1);
    //关闭id.txt
    fclose(fp);
    
    //将ID转换为字符串
    char filename[20] = {0};
    sprintf(filename,"%d",m_id);
    //根据id创建文件保存当前员工信息
    file = fopen(filename,"w");
    //保存员工信息
    saveInfo();

    //记录员工人数
    ++m_count;
    //保存员工人数
    FILE* fp2 = fopen("count.txt","w");
    fprintf(fp2,"%d",m_count);
    fclose(fp2);
}
Employee::~Employee(void){
    fclose(file);
    file = NULL;
}
void Employee::printInfo(void)const{
    printBasic();
    printExtra();
}
void Employee::printBasic(void)const{
    cout << "姓名:" << m_name << endl;
    cout << "工号:" << m_id << endl;
    cout << "基础工资:"  << m_salary << endl;
}
void Employee::printExtra(void)const{
    cout << "职位:普通员工" << endl;
}

void Employee::calSalary(void){
    double salary = calBasic() + calMerit();
    cout << "总工资为:" << salary << endl;
}

double Employee::calBasic(void){
    cout << "请输入出勤天数:";
    int days;
    cin >> days;
    m_attend = days/23.0;//计算出勤率
    return m_salary* m_attend;
}
double Employee::calMerit(void){
    return m_salary * m_attend / 2;
}
void Employee::setId(int id){
    if(id<10000)
        cout << "无效的工号" << endl;
    else
        m_id = id;
}
void Employee::setName(const string& name){
    if(name.size() > 20)
        cout << "无效的姓名" << endl;
    else
        m_name = name;
}
void Employee::setSalary(double salary){
    if(salary<0)
        cout << "无效的工资" << endl;
    else
        m_salary = salary;
}

void Employee::saveInfo(void){
    fseek(file,0,SEEK_SET);
    fprintf(file,"%d %s %g",m_id,m_name.c_str(),m_salary);
}

//静态成员需要在类的外部单独定义和初始化
int Employee::m_count = 0;

const int& Employee::getEmployeeCount(void){
    FILE* fp = fopen("count.txt","r");
    //加载员工人数
    fscanf(fp,"%d",&m_count);
    fclose(fp);
    return m_count;
}
bool Employee::operator==(int id)const{
    if(m_id == id){
        return true;
    }
    else{
        return false;
    }
}
bool Employee::operator!=(int id)const{
    return !(*this == id);
}

//全局函数
ostream& operator<<(ostream& os,const Employee& emp){
    os << "Employee(" << emp.m_id << "," << emp.m_name << 
        "," << emp.m_salary << ")";
    return os;
}

参考代码:Manager.h

#ifndef __MANAGER_H_
#define __MANAGER_H_

#include "Employee.h"

class Manager:public Employee{
public:
    Manager(const string& name,double salary,double m_bonus);
    void printInfo(void)const;
    void printExtra(void)const;
    void calSalary(void);
    double calMerit(void);
private:
    double m_bonus;//绩效奖金
};

#endif//__MANAGER_H_

参考代码:Manager.cpp

#include "Manager.h"

//通过初始化表说明基本部分(基类子对象)的初始化方式
Manager::Manager(const string& name,double salary,double bonus)
    :Employee(name,salary),m_bonus(bonus){
    //保存经理的绩效奖金
    fprintf(file," %g",m_bonus);    
}
void Manager::printInfo(void)const{
    printBasic();//继承基类的
    printExtra();
}
void Manager::printExtra(void)const{
    cout << "职位:经理" << endl;
    cout << "绩效奖金:" << m_bonus << endl;
}
void Manager::calSalary(void){
    //calBasic():继承基类的
    double salary = calBasic() + calMerit();
    cout << "总工资为:" << salary << endl;
}
double Manager::calMerit(void){
    cout << "请输入绩效因数:";
    double factor;
    cin >> factor;
    return m_bonus * factor;
}

参考代码:Technician.h

#ifndef __EMPLOYEE_H_
#define __EMPLOYEE_H_

#include <iostream>
#include <cstdio>
using namespace std;

class Employee{
public:
    Employee(const string& name,double salary);
    ~Employee(void);
    //打印信息
    void printInfo(void)const;
protected:
    void printBasic(void)const;//公有信息
    void printExtra(void)const;//特有信息
public:
    //计算工资
    void calSalary(void);
protected:
    double calBasic(void);//基本工资
    double calMerit(void);//绩效工资
public:
    void setId(int id);
    void setName(const string& name);
    void setSalary(double salary);
    void saveInfo(void);
    bool operator==(int id)const;
    bool operator!=(int id)const;
    friend ostream& operator<<(ostream& os,const Employee& emp);
private:
    //禁止拷贝构造和拷贝复制
    Employee& operator=(const Employee&);
    Employee(const Employee&);
protected://保护成员子类可以直接访问
    int m_id;//工号
    string m_name;//姓名
    double m_salary;//工资
    FILE* file;//保存员工信息指针
    double m_attend;//出勤率
public:
    //获取员工人数
    static const int& getEmployeeCount(void);
private:
    static int m_count;//记录员工人数
};
#endif//__EMPLOYEE_H_

参考代码:Technician.cpp

#include "Technician.h"

Technician::Technician(const string& name,double salary,double allow)
    :Employee(name,salary),m_allow(allow){
        //保存员工绩效信息
        fprintf(file," %g",m_allow);    
}
void Technician::printInfo(void)const{
    printBasic();
    printExtra();
}
void Technician::printExtra(void)const{
    cout << "职位:技术员" << endl;
    cout << "研发津贴:" << m_allow << endl;
}
void Technician::calSalary(void){
    double salary = calBasic() + calMerit();
    cout << "总工资为:" << salary << endl;
}
double Technician::calMerit(void){
    cout << "请输入进度因数:";
    double factor;
    cin >> factor;
    return 23*8*m_attend * m_allow * factor;
}

参考代码:main.cpp

#include "Employee.h"
#include "Technician.h"
#include "Manager.h"

#include <unistd.h>
#include <cstdio>
#include <cstdlib>
void init(void){
    if(access("id.txt",F_OK)==-1){
        FILE* fp = fopen("id.txt","w");//记录工号
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",10001);
        fclose(fp);
    }
    if(access("count.txt",F_OK)==-1){
        FILE* fp = fopen("count.txt","w");//记录人数
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",0);
        fclose(fp);
    }
    else{
        //加载员工人数
        Employee::getEmployeeCount();
    }
}

int main()
{
    init();
    cout << "欢迎使用企业员工个信息管理系统:" << endl;
    Employee emp("张三",6600);
    emp.printInfo();
    emp.calSalary();
    cout << "===========================" << endl;

    Technician tech("孔明",8800,30);
    tech.printInfo();
    tech.calSalary();
    cout << "===========================" << endl;
    
    Manager manager("刘备",12000,5000);
    manager.printInfo();
    manager.calSalary();
    cout << "===========================" << endl;
    
    return 0;
}

参考代码:Makefile

PROJ = employee
OBJS = main.o employee.o Technician.o Manager.o
LINK = g++
RM = rm -rf 
CFLAGS = -c -Wall -I.
$(PROJ):$(OBJS)
	$(LINK) $^ -o $@
.c.o:
	$(LINK) $^
clean:
	$(RM) $(PROJ) $(OBJS) *.gch count.txt id.txt 1000*

二十二、 多态(polymorphic)

  1. 虚函数覆盖(函数重写)、多态概念
    1)如果基类中某个成员函数被声明为虚函数,那么子类中和该函数具有相同的成员函数就也是虚函数,并且对基类中版本形成覆盖,即函数重写。
    2)满足虚函数覆盖关系后,这时通过指向子类对象的基类指针或者通过引用子类对象的基类引用,去调用虚函数,实际被执行的将是子类中的覆盖版本,而不是基类中的原始版本,这种语法现象被称为多态。
    class Base{
    public:
    virtual void func(void){}//虚函数
    };
    class Derived:pubilc Base{
    void func(void){}//也是虚函数
    };
    Derived d;
    Base* pb = &d;//pb指向子类对象的基类指针
    Base& rb = d;//rb引用子类对象的基类引用
    pb->func();//实际被执行的将是子类中的覆盖版本
    rb.func();//实际被执行的将是子类中的覆盖版本

  2. 虚函数覆盖(函数重载)条件
    1)只有类中成员函数才能被声明为虚函数,而全局函数、静态成员函数、构造函数都不能声明为虚函数。
    注:析构函数可以为虚函数(特殊,后面讲)
    2)只有在基类中以virtual关键字修饰的成员函数才能作为虚函数被子类中版本覆盖,而与子类中虚函数是否加virtual关键字无关。
    3)虚函数在基类中的原始版本和子类中的覆盖版本必须具有相同的函数签名,即函数名、参数表和常属性一致。
    4)如果基类中的虚函数返回基本类型的数据,那么该函数在子类中覆盖版本必须返回相同类型的数据;而如果基类中的虚函数返回类类型的指针(A*)或引用(A&),那么允许子类中的覆盖版本返回其子类类型的指针(B*)或引用(B&)。
    class A{};
    class B:public A{};

  3. 多态的条件
    1)多态的语法现象除了满足虚函数覆盖,还必须通过指针或引用调用虚函数才能表现出来。
    2)调用虚函数的指针也可以是this指针,如果通过子类对象调用基类中的成员函数,在该成员函数中的this指针将是一个指向子类对象的基类指针,再使用this去调用虚函数,也可以表现多态的语法现象。//重点掌握
    eg:Qt中的多线程
    class QThread{//线程类,官方写好的类
    public:
    void start(void){//开启线程
    this->run();
    }
    protected:
    virtual void run(void){//线程入口函数}
    };
    class MyThread:public QThread{
    protected:
    void run(void){//重写线程入口函数
    //需要放在线程执行的代码
    }
    };
    MyThread thread;
    thread.start();//开启子线程,重写的run函数将在子线程中被执行

  4. 多态原理//了解
    通过虚函数表和动态绑定实现了多态的语法 //参考polymorphic.png
    1)虚函数表会增加内存的开销
    2)动态绑定有时间的开销
    3)虚函数不能被内联优化
    总结:实际开发中如果没有多态的语法要求,最好不要使用虚函数。
    参考代码:

#include <iostream>
using namespace std;
class A{
public:
  virtual void func(void){
    cout<<"A::func" <<endl;
  }
  virtual void foo(void){
    cout<<"A::foo" <<endl;
  }
};
class B:public A{
  void func(void){
    cout<<"B::func"<<endl;
  }
  virtual void bar(void){
    cout<<"A::bar" <<endl;
  }
};
//B虚表:B::func,A::foo,B::bar
int main(void){
  B b;
  cout<<sizeof(b)<<endl;
  for(int i=0;i<3;i++){
    (*(*(void(***)(void))&b+i))();  //B::func,A::foo,B::bar
  } 
}
  1. 纯虚函数、抽象类和纯抽象类
    1)纯虚函数
    virtual 返回类型 函数名(形参表)[const] = 0;
    2)抽象类
    如果类中包含了纯虚函数,那么该类就是抽象类。
    注:抽象类不能创建对象.
    3)纯抽象类(接口、接口类)
    如果类中所有的成员函数都是纯虚函数,那么该类就是纯抽象类。
    参考代码:03abstract.cpp
#include <iostream>
using namespace std;
class PDFParser{
public:
  void parse(const char * pdffile){
     cout<<"解析出一行文本"<<endl;
     onText();
     cout<<"解析出一些图片"<<endl;
     onImage();
  }
private:
  virtual void onText(void) = 0;
  virtual void onImage(void) = 0;
};
class PDFRender:public PDFParser{
private:
   void onText(void){
      cout<<"显示文本数据" <<endl;
   }
   void onImage(void){
     cout<<"绘制图片数据"<<endl;
   }
};
int main(void){
  PDFRender render;
  render.parse("xxx.pdf");
  return 0;
}
  1. 虚析构函数
    问题:基类析构函数不会调用子类的析构函数,如果对一个指向子类对象的基类指针使用delete运算,实际被执行的将是基类的析构函数,子类的析构函数不会被执行,有内存泄漏风险
    解决:
    如果将基类中的析构函数声明为虚函数,那么子类中的析构函数也就是一个虚析构函数,并且可以对基类中版本形成覆盖,可以表现多态的语法;
    这时如果对一个指向子类对象的基类指针使用delete运算符,实际被执行的将是子类的析构函数,子类的析构函数在执行结束后又会自动调用基类的析构函数,从而避免内存泄漏。
    参考代码:04vDestructor.cpp
#include <iostream>
using namespace std;
class Base{
public:
  Base(void){
    cout<<"基类动态资源分配"<<endl;
  }
  ~Base(void){
    cout<<"基类动态资源释放"<<endl;
  }
};
class Derived:public Base{
public:
  Derived(void){
    cout<<"子类动态资源分配"<<endl;
  }
  ~Derived(void){ //自动变成虚函数函数
    cout<<"子类动态资源释放"<<endl;
  }
};
int main(void){
  Base* pb = new Derived;
  //pb ->析构函数
  delete pb;
  return 0;
}

员工管理系统版本v.7

知识点:多重继承、钻石继承、虚继承
需求:增加技术主管类
1)技术主管同时具备技术员属性(研发津贴)和经理属性(绩效奖金)
2)技术主管绩效工资 = (技术员绩效+经理绩效)*0.5
提示:
员工类
/
技术员 经理
\ /
技术主管(CTO)
参考代码:Employee.h、Employee.cpp、Manager.cpp、Manager.h、Technician.cpp、Technician.h 、main.cpp、Makefile
代码1:Employee.h

#ifndef __EMPLOYEE_H_
#define __EMPLOYEE_H_

#include <iostream>
#include <cstdio>
using namespace std;

class Employee{
public:
    Employee(const string& name,double salary);
    ~Employee(void);
    //打印信息
    void printInfo(void)const;
protected:
    void printBasic(void)const;//公有信息
    void printExtra(void)const;//特有信息
public:
    //计算工资
    void calSalary(void);
protected:
    double calBasic(void);//基本工资
    double calMerit(void);//绩效工资
public:
    void setId(int id);
    void setName(const string& name);
    void setSalary(double salary);
    void saveInfo(void);
    friend ostream& operator<<(ostream& os,const Employee& emp);
    bool operator==(int id)const;
    bool operator!=(int id)const;
private:
    //禁止拷贝构造和拷贝复制
    Employee& operator=(const Employee&);
    Employee(const Employee&);
protected://保护成员子类可以直接访问
    int m_id;//工号
    string m_name;//姓名
    double m_salary;//工资
    FILE* file;//保存员工信息指针
    double m_attend;//出勤率
public:
    //获取员工人数
    static const int& getEmployeeCount(void);
private:
    static int m_count;//记录员工人数
};
#endif//__EMPLOYEE_H_

代码2:Employee.cpp

#include "Employee.h" 

Employee::Employee(const string& name,
        double salary):m_name(name),m_salary(salary){
    //读取ID
    FILE* fp = fopen("id.txt","r+");
    fscanf(fp,"%d",&m_id);
    //将文件读写指针定位到文件头
    fseek(fp,0,SEEK_SET);
    //ID加+1,再保存回id.txt
    fprintf(fp,"%d",m_id+1);
    //关闭id.txt
    fclose(fp);
    
    //将ID转换为字符串
    char filename[20] = {0};
    sprintf(filename,"%d",m_id);
    //根据id创建文件保存当前员工信息
    file = fopen(filename,"w");
    //保存员工信息
    saveInfo();

    //记录员工人数
    ++m_count;
    //保存员工人数
    FILE* fp2 = fopen("count.txt","w");
    fprintf(fp2,"%d",m_count);
    fclose(fp2);
}
Employee::~Employee(void){
    fclose(file);
    file = NULL;
}

void Employee::printInfo(void)const{
    printBasic();
    printExtra();
}
void Employee::printBasic(void)const{
    cout << "姓名:" << m_name << endl;
    cout << "工号:" << m_id << endl;
    cout << "基础工资:"  << m_salary << endl;
}
void Employee::printExtra(void)const{
    cout << "职位:普通员工" << endl;
}

void Employee::calSalary(void){
    double salary = calBasic() + calMerit();
    cout << "总工资为:" << salary << endl;
}

double Employee::calBasic(void){
    cout << "请输入出勤天数:";
    int days;
    cin >> days;
    m_attend = days/23.0;//计算出勤率
    return m_salary* m_attend;
}
double Employee::calMerit(void){
    return m_salary * m_attend / 2;
}

void Employee::setId(int id){
    if(id<10000)
        cout << "无效的工号" << endl;
    else
        m_id = id;
}
void Employee::setName(const string& name){
    if(name.size() > 20)
        cout << "无效的姓名" << endl;
    else
        m_name = name;
}
void Employee::setSalary(double salary){
    if(salary<0)
        cout << "无效的工资" << endl;
    else
        m_salary = salary;
}

void Employee::saveInfo(void){
    fprintf(file,"%d %s %g",m_id,m_name.c_str(),m_salary);
}

//静态成员需要在类的外部单独定义和初始化
int Employee::m_count = 0;

const int& Employee::getEmployeeCount(void){
    FILE* fp = fopen("count.txt","r");
    //加载员工人数
    fscanf(fp,"%d",&m_count);
    fclose(fp);
    return m_count;
}
bool Employee::operator==(int id)const{
    if(m_id == id){
        return true;
    }   
    else{
        return false;
    }   
}
bool Employee::operator!=(int id)const{
    return  !(*this == id);
}

//全局函数
ostream& operator<<(ostream& os,const Employee& emp){
    os << "Employee(" << emp.m_id << "," << emp.m_name << 
        "," << emp.m_salary << ")";
    return os;
}

代码3:Technician.h

#ifndef __TECHNICION_H_
#define __TECHNICION_H_

#include "Employee.h"

class Technician:virtual public Employee{
public:
    Technician(const string& name,double salary,double allow);
    void printInfo(void)const;
    void printExtra(void)const;
    void calSalary(void);
    double calMerit(void);
protected:
    double m_allow;//研发津贴
};

#endif//__TECHNICION_H_

代码4:Technician.cpp

#include "Technician.h"

Technician::Technician(const string& name,double salary,double allow)
    :Employee(name,salary),m_allow(allow){
        //保存员工绩效信息
        fprintf(file," %g",m_allow);    
}
void Technician::printInfo(void)const{
    printBasic();
    printExtra();
}
void Technician::printExtra(void)const{
    cout << "职位:技术员" << endl;
    cout << "研发津贴:" << m_allow << endl;
}
void Technician::calSalary(void){
    double salary = calBasic() + calMerit();
    cout << "总工资为:" << salary << endl;
}
double Technician::calMerit(void){
    cout << "请输入进度因数:";
    double factor;
    cin >> factor;
    return 23*8*m_attend * m_allow * factor;
}

代码5:Manager.h

#ifndef __MANAGER_H_
#define __MANAGER_H_

#include "Employee.h"

class Manager:virtual public Employee{
public:
    Manager(const string& name,double salary,double m_bonus);
    void printInfo(void)const;
    void printExtra(void)const;
    void calSalary(void);
    double calMerit(void);
protected:
    double m_bonus;//绩效奖金
};

#endif//__MANAGER_H_

代码6:Manager.cpp

#include "Manager.h"

Manager::Manager(const string& name,double salary,double bonus)
    :Employee(name,salary),m_bonus(bonus){
    //保存经理的绩效奖金
    fprintf(file," %g",m_bonus);    
}
void Manager::printInfo(void)const{
    printBasic();
    printExtra();
}
void Manager::printExtra(void)const{
    cout << "职位:经理" << endl;
    cout << "绩效奖金:" << m_bonus << endl;
}
void Manager::calSalary(void){
    double salary = calBasic() + calMerit();
    cout << "总工资为:" << salary << endl;
}
double Manager::calMerit(void){
    cout << "请输入绩效因数:";
    double factor;
    cin >> factor;
    return m_bonus * factor;
}

代码7:CTO.h

#ifndef __CTO_H_
#define __CTO_H_

#include "Technician.h"
#include "Manager.h"

class CTO:public Technician,public Manager{
public:
    CTO(const string& name,double salary,
        double allow,double bonus);
    void printInfo(void)const;
    void calSalary(void);
private:
    void printExtra(void)const;
    double calMerit(void);
};

#endif//__CTO_H_

代码7:CTO.cpp

#include "CTO.h"

//虚继承时,位于继承链的末端子类负责构造公共基类子对象
CTO::CTO(const string& name,double salary,double allow,double bonus):
    Technician(name,salary,allow),Manager(name,salary,bonus),Employee(name,salary){}

void CTO::printInfo(void)const{
    printBasic();
    printExtra();
}
void CTO::printExtra(void)const{
    cout << "职位:技术主管" << endl;
    cout << "研发津贴:" << m_allow << endl;
    cout << "绩效奖金:" << m_bonus << endl;
}
void CTO::calSalary(void){
    double salary = calBasic() + calMerit();
    cout << "总工资为:" << salary << endl;
}
double CTO::calMerit(void){
    return (Technician::calMerit() + Manager::calMerit()) / 2;
}

代码8:main.cpp

#include "Employee.h"
#include "Technician.h"
#include "Manager.h"
#include "CTO.h"

#include <unistd.h>
#include <cstdio>
#include <cstdlib>

void init(void){
    if(access("id.txt",F_OK)==-1){
        FILE* fp = fopen("id.txt","w");//记录工号
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",10001);
        fclose(fp);
    }
    if(access("count.txt",F_OK)==-1){
        FILE* fp = fopen("count.txt","w");//记录人数
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",0);
        fclose(fp);
    }
    else{
        //加载员工人数
        Employee::getEmployeeCount();
    }
}

int main()
{
    init();
    cout << "<欢迎进入企业员工个信息管理系统>" << endl;
    Employee emp("张三",6600);
    emp.printInfo();
    emp.calSalary();
    cout << "===========================" << endl;

    Technician tech("孔明",8800,30);
    tech.printInfo();
    tech.calSalary();
    cout << "===========================" << endl;
    
    Manager manager("刘备",12000,5000);
    manager.printInfo();
    manager.calSalary();
    cout << "===========================" << endl;

    CTO cto("司马懿",16000,30,5000);
    cto.printInfo();
    cto.calSalary();
    cout << "===========================" << endl;
    cout << "当前员工人数:" << Employee::getEmployeeCount() << endl;; 

    return 0;
}

参考代码:Makefile

PROJ = employee
OBJS = main.o employee.o Technician.o Manager.o CTO.o
LINK = g++
RM = rm -rf 
CFLAGS = -c -Wall -I.
$(PROJ):$(OBJS)
	$(LINK) $^ -o $@
.c.o:
	$(LINK) $^
clean:
	$(RM) $(PROJ) $(OBJS) *.gch count.txt id.txt 1000*

员工管理系统版本v.8

知识点:多态
需求:使用多态语法,改善程序结构
提示:将 printExtra 和 calMerit 函数声明为虚函数
参考代码:Employee.h、Employee.cpp、Manager.cpp、Manager.h、Technician.cpp、Technician.h 、main.cpp、Makefile

代码1:Employee.h

#ifndef __EMPLOYEE_H_
#define __EMPLOYEE_H_

#include <iostream>
#include <cstdio>
using namespace std;

class Employee{
public:
    Employee(const string& name,double salary);
    ~Employee(void);
    //打印信息
    void printInfo(void)const;
protected:
    void printBasic(void)const;//公有信息
    virtual void printExtra(void)const;//特有信息
public:
    //计算工资
    void calSalary(void);
protected:
    double calBasic(void);//基本工资
    virtual double calMerit(void);//绩效工资
public:
    void setId(int id);
    void setName(const string& name);
    void setSalary(double salary);
    void saveInfo(void);
    bool operator==(int id)const;
    bool operator!=(int id)const;
    friend ostream& operator<<(ostream& os,const Employee& emp);
private:
    //禁止拷贝构造和拷贝复制
    Employee& operator=(const Employee&);
    Employee(const Employee&);
protected://保护成员子类可以直接访问
    int m_id;//工号
    string m_name;//姓名
    double m_salary;//工资
    FILE* file;//保存员工信息指针
    double m_attend;//出勤率
public:
    //获取员工人数
    static const int& getEmployeeCount(void);
private:
    static int m_count;//记录员工人数
};
#endif//__EMPLOYEE_H_

代码2:Employee.cpp

#include "Employee.h" 

Employee::Employee(const string& name,
        double salary):m_name(name),m_salary(salary){
    //读取ID
    FILE* fp = fopen("id.txt","r+");
    fscanf(fp,"%d",&m_id);
    //将文件读写指针定位到文件头
    fseek(fp,0,SEEK_SET);
    //ID加+1,再保存回id.txt
    fprintf(fp,"%d",m_id+1);
    //关闭id.txt
    fclose(fp);
    
    //将ID转换为字符串
    char filename[20] = {0};
    sprintf(filename,"%d",m_id);
    //根据id创建文件保存当前员工信息
    file = fopen(filename,"w");
    //保存员工信息
    saveInfo();

    //记录员工人数
    ++m_count;
    //保存员工人数
    FILE* fp2 = fopen("count.txt","w");
    fprintf(fp2,"%d",m_count);
    fclose(fp2);
}
Employee::~Employee(void){
    fclose(file);
    file = NULL;
}

void Employee::printInfo(void)const{
    printBasic();
    printExtra();
}
void Employee::printBasic(void)const{
    cout << "姓名:" << m_name << endl;
    cout << "工号:" << m_id << endl;
    cout << "基础工资:"  << m_salary << endl;
}
void Employee::printExtra(void)const{
    cout << "职位:普通员工" << endl;
}

void Employee::calSalary(void){
    double salary = calBasic() + calMerit();
    cout << "总工资为:" << salary << endl;
}

double Employee::calBasic(void){
    cout << "请输入出勤天数:";
    int days;
    cin >> days;
    m_attend = days/23.0;//计算出勤率
    return m_salary* m_attend;
}
double Employee::calMerit(void){
    return m_salary * m_attend / 2;
}

void Employee::setId(int id){
    if(id<10000)
        cout << "无效的工号" << endl;
    else
        m_id = id;
}
void Employee::setName(const string& name){
    if(name.size() > 20)
        cout << "无效的姓名" << endl;
    else
        m_name = name;
}
void Employee::setSalary(double salary){
    if(salary<0)
        cout << "无效的工资" << endl;
    else
        m_salary = salary;
}

void Employee::saveInfo(void){
    fprintf(file,"%d %s %g",m_id,m_name.c_str(),m_salary);
}

//静态成员需要在类的外部单独定义和初始化
int Employee::m_count = 0;

const int& Employee::getEmployeeCount(void){
    FILE* fp = fopen("count.txt","r");
    //加载员工人数
    fscanf(fp,"%d",&m_count);
    fclose(fp);
    return m_count;
}
//根据员工对象的id和参数id比较,是否相等
bool Employee::operator==(int id)const{
    if(m_id == id){
        return true;
    }
    else{
        return false;
    }
}
bool Employee::operator!=(int id)const{
    return  !(*this == id);
}
//全局函数
ostream& operator<<(ostream& os,const Employee& emp){
    os << "Employee(" << emp.m_id << "," << emp.m_name << 
        "," << emp.m_salary << ")";
    return os;
}

代码3:Technician.h

#ifndef __TECHNICION_H_
#define __TECHNICION_H_

#include "Employee.h"

class Technician:virtual public Employee{
public:
    Technician(const string& name,double salary,double allow);
    void printExtra(void)const;
    double calMerit(void);
protected:
    double m_allow;//研发津贴
};

#endif//__TECHNICION_H_

代码4:Technician.cpp

#include "Technician.h"

Technician::Technician(const string& name,double salary,double allow)
    :Employee(name,salary),m_allow(allow){
        //保存员工绩效信息
        fprintf(file," %g",m_allow);    
}
void Technician::printExtra(void)const{
    cout << "职位:技术员" << endl;
    cout << "研发津贴:" << m_allow << endl;
}
double Technician::calMerit(void){
    cout << "请输入进度因数:";
    double factor;
    cin >> factor;
    return 23*8*m_attend * m_allow * factor;
}

代码5:Manager.h

#ifndef __MANAGER_H_
#define __MANAGER_H_

#include "Employee.h"

class Manager:virtual public Employee{
public:
    Manager(const string& name,double salary,double m_bonus);
    void printExtra(void)const;
    double calMerit(void);
protected:
    double m_bonus;//绩效奖金
};


#endif//__MANAGER_H_

代码6:Manager.cpp

#include "Manager.h"

Manager::Manager(const string& name,double salary,double bonus)
    :Employee(name,salary),m_bonus(bonus){
    //保存经理的绩效奖金
    fprintf(file," %g",m_bonus);    
}
void Manager::printExtra(void)const{
    cout << "职位:经理" << endl;
    cout << "绩效奖金:" << m_bonus << endl;
}
double Manager::calMerit(void){
    cout << "请输入绩效因数:";
    double factor;
    cin >> factor;
    return m_bonus * factor;
}

代码7:CTO.h

#ifndef __CTO_H_
#define __CTO_H_

#include "Technician.h"
#include "Manager.h"

class CTO:public Technician,public Manager{
public:
    CTO(const string& name,double salary,
        double allow,double bonus);
    void printInfo(void)const;
    void calSalary(void);
private:
    void printExtra(void)const;
    double calMerit(void);
};

#endif//__CTO_H_

代码8:CTO.app

#include "CTO.h"

//虚继承时,位于继承链的末端子类负责构造公共基类子对象
CTO::CTO(const string& name,double salary,double allow,double bonus):
    Technician(name,salary,allow),Manager(name,salary,bonus),Employee(name,salary){}
void CTO::printExtra(void)const{
    cout << "职位:技术主管" << endl;
    cout << "研发津贴:" << m_allow << endl;
    cout << "绩效奖金:" << m_bonus << endl;
}
double CTO::calMerit(void){
    return (Technician::calMerit() + Manager::calMerit()) / 2;
}

代码9:main.cpp

#include "Employee.h"
#include "Technician.h"
#include "Manager.h"
#include "CTO.h"

#include <unistd.h>
#include <cstdio>
#include <cstdlib>

void init(void){
    if(access("id.txt",F_OK)==-1){
        FILE* fp = fopen("id.txt","w");//记录工号
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",10001);
        fclose(fp);
    }
    if(access("count.txt",F_OK)==-1){
        FILE* fp = fopen("count.txt","w");//记录人数
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",0);
        fclose(fp);
    }
    else{
        //加载员工人数
        Employee::getEmployeeCount();
    }
}

int main()
{
    init();
    cout << "<欢迎进入企业员工个信息管理系统>" << endl;
    Employee emp("张三",6600);
    emp.printInfo();
    emp.calSalary();
    cout << "===========================" << endl;

    Technician tech("孔明",8800,30);
    tech.printInfo();
    tech.calSalary();
    cout << "===========================" << endl;
    
    Manager manager("刘备",12000,5000);
    manager.printInfo();
    manager.calSalary();
    cout << "===========================" << endl;

    CTO cto("司马懿",16000,30,5000);
    cto.printInfo();
    cto.calSalary();
    cout << "===========================" << endl;
    cout << "当前员工人数:" << Employee::getEmployeeCount() << endl;; 

    return 0;
}

参考代码:Makefile

PROJ = employee
OBJS = main.o employee.o Technician.o Manager.o CTO.o
LINK = g++
RM = rm -rf 
CFLAGS = -c -Wall -I.
$(PROJ):$(OBJS)
	$(LINK) $^ -o $@
.c.o:
	$(LINK) $^
clean:
	$(RM) $(PROJ) $(OBJS) *.gch count.txt id.txt 1000*

员工管理系统版本v.9

知识点:综合练习
补充知识:unlink()删除文件、vector 向量容器
需求:增加企业管理类(单例模式),实现员工 增、删、改、查、结算薪资等操作
提示:
class Company{//企业类
public:
//获取企业单例对象
static Company& getCompany(void);
//获取员工个数
size_t size(void)const;
//运行
void run(void);
//打印菜单选项
void printMenu(void);
//增加新的员工
void addEmployee(void);
//删除员工
void deleteEmployee(void);
//修改员工工资
void updateEmployee(void);
//查询员工信息
void queryEmployee(void);
//结算工资
void paySalary(void);
private:
Company(void);
Company(const Company&);
~Company(void);
//从文件中读取员工信息,创建员工对象
Employee* loadInfo(const string& filename);
private:
static Company company;
//保存员工对象的容器(类似数组)
vector<Employee*> empVector;
};

参考代码:Employee.h、Employee.cpp、Manager.cpp、Manager.h、Technician.cpp、Technician.h 、main.cpp、Company.h 、Company.cpp 、Makefile
代码1:Employee.h

#ifndef __EMPLOYEE_H_
#define __EMPLOYEE_H_

#include <iostream>
#include <cstdio>
#include <unistd.h>
using namespace std;

class Employee{
public:
    //员工级别:L1:普通员工 L2:技术员 L3:经理 L4:技术主管
    enum LEVEL{L1=101,L2=201,L3=301,L4=401};
public:
    //创建新的员工
    Employee(const string& name,double salary,LEVEL level=L1);

    //根据文件信息,创建员工对象
    Employee(const string& filename);

    //虚析构函数
    virtual ~Employee(void);
    
    //打印信息
    void printInfo(void)const;
    
    //计算工资
    void calSalary(void);

protected:
    void printBasic(void)const;//公有信息
    virtual void printExtra(void)const;//特有信息
protected:
    double calBasic(void);//基本工资
    virtual double calMerit(void);//绩效工资
public:
    void setId(int id);
    void setName(const string& name);
    void setSalary(double salary);
    void saveInfo(void);
    //根据员工对象的id和参数id比较,是否相等
    bool operator==(int id)const;
    bool operator!=(int id)const;
    friend ostream& operator<<(ostream& os,const Employee& emp);
    
private:
    //禁止拷贝构造和拷贝复制
    Employee& operator=(const Employee&);
    Employee(const Employee&);
protected://保护成员子类可以直接访问
    int m_id;//工号
    string m_name;//姓名
    double m_salary;//工资
    FILE* file;//保存员工信息指针
    double m_attend;//出勤率
    LEVEL m_level;//员工级别
public:
    //获取员工人数
    static const int& getEmployeeCount(void);
private:
    static int m_count;//记录员工人数
};

#endif//__EMPLOYEE_H_

代码2:Employee.cpp

#include "Employee.h" 

Employee::Employee(const string& name,double salary,LEVEL level):
    m_name(name),m_salary(salary),m_level(level){
    //读取ID
    FILE* fp = fopen("id.txt","r+");
    fscanf(fp,"%d",&m_id);
    
    //将ID转换为字符串
    char filename[20] = {0};
    sprintf(filename,"%d",m_id);

    //如果和id对应的文件不存在
    if(access(filename,F_OK) == -1){
        //根据id创建文件保存当前员工信息
        file = fopen(filename,"w");
        //保存员工信息
        saveInfo();

        //记录员工人数
        ++m_count;
        //保存员工人数
        FILE* fp2 = fopen("count.txt","w");
        fprintf(fp2,"%d",m_count);
        fclose(fp2);

        //将文件读写指针定位到文件头
        fseek(fp,0,SEEK_SET);
        //ID加+1,再保存回id.txt
        fprintf(fp,"%d",m_id+1);
    }
    //关闭id.txt
    fclose(fp);
}
Employee::Employee(const string& filename){
    int level;
    char name[20]={0};
    //将ID转换为字符串
    if(access(filename.c_str(),F_OK) == -1){
        cout << "id=" << filename << "员工不存在!" << endl;
    }
    else{
        //打开对应的员工文件(读写方式)
        file = fopen(filename.c_str(),"r+");
        //从文件中读取员工信息,保存到成员变量中
        fscanf(file,"%d %d %s %lf",&level,&m_id,name,&m_salary);
        //根据level确定员工类型
        switch(level){
        case 101://普通员工
            m_level = L1;
            break;
        case 201://技术员
            m_level = L2;
            break;
        case 301://经理
            m_level = L3;
            break;
        case 401://技术主管
            m_level = L4;
        }
        //初始化员工姓名
        m_name = name;
    }
}
//析构函数
Employee::~Employee(void){
    //员工对象销毁时,关闭对应的文件
    fclose(file);
    file = NULL;
}
//打印员工信息
void Employee::printInfo(void)const{
    printBasic();
    printExtra();
}
//打印员工公有信息
void Employee::printBasic(void)const{
    cout << "姓名:" << m_name << endl;
    cout << "工号:" << m_id << endl;
    cout << "基础工资:"  << m_salary << endl;
}
//打印员工特有信息(virtual)
void Employee::printExtra(void)const{
    cout << "职位:普通员工" << endl;
}
//计算工资
void Employee::calSalary(void){
    double salary = calBasic() + calMerit();
    cout << "总工资为:" << salary << endl;
}
//计算基本工资
double Employee::calBasic(void){
    cout << "请输入出勤天数:";
    int days;
    cin >> days;
    m_attend = days/23.0;//计算出勤率
    return m_salary* m_attend;
}
//计算绩效工资(virtual)
double Employee::calMerit(void){
    return m_salary * m_attend / 2;
}
//修改ID
void Employee::setId(int id){
    if(id<10000)
        cout << "无效的工号" << endl;
    else
        m_id = id;
}
//修改姓名
void Employee::setName(const string& name){
    if(name.size() > 20)
        cout << "无效的姓名" << endl;
    else
        m_name = name;
}
//修改工资
void Employee::setSalary(double salary){
    if(salary<0)
        cout << "无效的工资" << endl;
    else
        m_salary = salary;
}
//保存员工信息到文件
void Employee::saveInfo(void){
    //将文件读写指针定位到文件头
    fseek(file,0,SEEK_SET);
    //级别,工号,姓名,工资
    //-10.2lf:格式化控制,左对齐/10字符宽度/保留2为小数
    int res = fprintf(file,"%d %d %s %-10.2lf",
            m_level,m_id,m_name.c_str(),m_salary);
    //刷新文件流
    fflush(file);
    if(res < 0){
        perror("fprintf");
        cout << "保存信息出错!" << endl;
    }
}

//静态成员需要在类的外部单独定义和初始化
//记录员工人数
int Employee::m_count = 0;

//获取员工人数
const int& Employee::getEmployeeCount(void){
    FILE* fp = fopen("count.txt","r");
    //加载员工人数
    fscanf(fp,"%d",&m_count);
    fclose(fp);
    return m_count;
}

//重载输出操作符(全局函数)
ostream& operator<<(ostream& os,const Employee& emp){
    os << "Employee(" << emp.m_id << "," << emp.m_name << 
        "," << emp.m_salary << ")";
    return os;
}
//根据员工对象的id和参数id比较,返回是否相等的bool值
bool Employee::operator==(int id)const{
    if(m_id == id){
        return true;
    }
    else{
        return false;
    }
}
//根据员工对象的id和参数id比较,返回是否不相等的bool值
bool Employee::operator!=(int id)const{
    return  !(*this == id);
}

代码3:Technician.h

#ifndef __TECHNICION_H_
#define __TECHNICION_H_

#include "Employee.h"
//技术员类,钻石结构的“中间类”,需要使用虚继承
class Technician:virtual public Employee{
public:
    //构造函数1:员工姓名、工资、研发津贴
    Technician(const string& name,double salary,double allow);
    //构造函数2:从文件中获取员工姓名、工资、研发津贴
    Technician(const string& filename);
    //析构函数
    ~Technician(void);
protected:
    //打印技术员特有信息(自动变虚函数)
    void printExtra(void)const;
    //计算技术员的绩效工资(自动变虚函数)
    double calMerit(void);
protected:
    double m_allow;//研发津贴
};

#endif//__TECHNICION_H_

代码4:Technician.cpp

#include "Technician.h"

Technician::Technician(const string& name,double salary,double allow)
    :Employee(name,salary,L2),m_allow(allow){
        //保存员工绩效信息
        fprintf(file," %g",m_allow);    
        fflush(file);
}

Technician::Technician(const string& filename)
    :Employee(filename){
    fscanf(file,"%lf",&m_allow);
}

Technician::~Technician(void){}

void Technician::printExtra(void)const{
    cout << "职位:技术员" << endl;
    cout << "研发津贴:" << m_allow << endl;
}

double Technician::calMerit(void){
    cout << "请输入进度因数:";
    double factor;
    cin >> factor;
    return 23*8*m_attend * m_allow * factor;
}

代码5:Manager.h

#ifndef __MANAGER_H_
#define __MANAGER_H_

#include "Employee.h"
//经理类,钻石结构的“中间类”,需要使用虚继承
class Manager:virtual public Employee{
public:
    //构造函数1:指定员工姓名、工资、绩效工资
    Manager(const string& name,double salary,double m_bonus);
    //构造函数2:从文件中获取员工姓名、工资、绩效工资
    Manager(const string& filiename);
    //析构函数
    ~Manager(void);
protected:
    //打印经理的特有信息(自动变虚)
    void printExtra(void)const;
    //计算经理的绩效工资(自动变虚)
    double calMerit(void);
protected:
    double m_bonus;//绩效奖金
};
#endif//__MANAGER_H_

代码6:Manager.cpp

#include "Manager.h"

Manager::Manager(const string& name,double salary,double bonus)
    :Employee(name,salary,L3),m_bonus(bonus){
    //保存经理的绩效奖金
    fprintf(file," %g",m_bonus);    
    fflush(file);
}

Manager::Manager(const string& filename)
    :Employee(filename){
    fscanf(file,"%lf",&m_bonus);
}

Manager::~Manager(void){}

void Manager::printExtra(void)const{
    cout << "职位:经理" << endl;
    cout << "绩效奖金:" << m_bonus << endl;
}

double Manager::calMerit(void){
    cout << "请输入绩效因数:";
    double factor;
    cin >> factor;
    return m_bonus * factor;
}

代码7:CTO.h

#ifndef __CTO_H_
#define __CTO_H_

#include "Technician.h"
#include "Manager.h"
//技术主管类,钻石结构的“末端子类”
class CTO:public Technician,public Manager{
public:
    //构造函数1:员工姓名,工资,研发津贴,绩效奖金
    CTO(const string& name,double salary,
        double allow,double bonus);
    //构造函数2:从文件中获取员工姓名,工资,研发津贴,绩效奖金
    CTO(const string& filename);
    //析构函数
    ~CTO(void);
private:
    //打印技术主管特有信息(自动变虚函数)
    void printExtra(void)const;
    //计算技术主管的绩效工资(自动变虚函数)
    double calMerit(void);
};

#endif//__CTO_H_

代码8:CTO.cpp

#include "CTO.h"
//钻石继承的末端子类
//虚继承时需要指明公共基类(Employee)子对象的初始化方式
CTO::CTO(const string& name,double salary,double allow,double bonus):
    Technician(name,salary,allow),
    Manager(name,salary,bonus),
    Employee(name,salary,L4){}

CTO::CTO(const string & filename):
    Technician(filename),Manager(filename),Employee(filename){}

CTO::~CTO(void){}

void CTO::printExtra(void)const{
    cout << "职位:技术主管" << endl;
    cout << "研发津贴:" << m_allow << endl;
    cout << "绩效奖金:" << m_bonus << endl;
}
double CTO::calMerit(void){
    return (Technician::calMerit() + Manager::calMerit()) / 2;
}

代码9:company.h

#ifndef __COMPANY_H_
#define __COMPANY_H_

#include "Employee.h"
#include "Technician.h"
#include "Manager.h"
#include "CTO.h"
#include <vector>//向量容器

class Company{//企业类
public:
    //获取企业对象
    static Company& getCompany(void);
    //求员工个数
    size_t size(void)const;
    //运行
    void run(void);
    //打印菜单选项
    void printMenu(void);
    //增加
    void addEmployee(void);
    //删除
    void deleteEmployee(void);
    //修改
    void updateEmployee(void);
    //查询
    void queryEmployee(void);
    void queryAll(void);//查询所有员工信息
    void queryOne(void);//查询指定员工信息
    //结算工资
    void paySalary(void);
private:
    //构造函数
    Company(void);
    //析构函数
    ~Company(void);
    //禁用拷贝构造
    Company(const Company&);
    //加载文件信息,创建对应的员工对象,返回对象指针
    Employee* loadInfo(const string& filename);
private:
    //表示公司的单例对象
    static Company company;
    //保存员工对象的容器(类似数组)
    vector<Employee*> empVector;
};

#endif//__COMPANY_H_

代码10:company.cpp

#include "Company.h"
#include <cstdlib>
#include <unistd.h>

//单例对象是静态成员,这里完成定义和初始化
Company Company::company;//匹配无参构造函数

//用于获取表示公司单例对象的静态成员函数
Company& Company::getCompany(void){
    return company;
}
//构造函数
Company::Company(void){
    //判断记录员工id的文件是否存在,不存在则创建
    if(access("id.txt",F_OK)==-1){
        FILE* fp = fopen("id.txt","w");//记录工号
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",10001);
        fclose(fp);
    }
    //判断记录员工人数的文件是否存在,不存在则创建
    if(access("count.txt",F_OK)==-1){
        FILE* fp = fopen("count.txt","w");//记录人数
        if(fp == NULL){
            perror("fopen"),exit(-1);
        }
        fprintf(fp,"%d",0);
        fclose(fp);
    }
    else{
        //加载员工人数
        //size()==>Employee::getEmployeeCount();
        
        //从文件中加载员工信息
        for(int i=0;i<size();i++){
            int id = 10001+i;
            char filename[20]={0};
            sprintf(filename,"%d",id);
            if(access(filename,F_OK)==-1){
                continue;
            }
            else{
                //cout << "从文件" << filename << "加载员工信息.." << endl;
                empVector.push_back(loadInfo(filename));
            }
        }
    }
}
Company::~Company(void){}

//加载文件信息,创建对应的员工对象,返回对象指针
Employee* Company::loadInfo(const string& filename){
    int id;
    char name[20];
    double salary;
    int level;
    Employee* pret;
    FILE* fp = fopen(filename.c_str(),"r");
    if(fp == NULL){
        perror("fopen");
        return NULL;
    }
    fscanf(fp,"%d",&level);
    switch(level){
        case Employee::L1:
            //cout << "加载普通员工" << endl;
            pret = new Employee(filename);
            break;
        case Employee::L2:
            //cout << "加载技术员" << endl;
            pret = new Technician(filename);
            break;
        case Employee::L3:
            //cout << "加载经理" << endl;
            pret = new Manager(filename);
            break;
        case Employee::L4:
            //cout << "加载技术主管" << endl;
            pret = new CTO(filename);
            break;
        default:
            cout << "加载员工信息错误!" << endl;
            return NULL;
    }
    fclose(fp);
    return pret;
}

//获取员工人数
size_t Company::size(void)const{
    return Employee::getEmployeeCount();
}
//员工系统开始运行(循环)
void Company::run(void){
    while(1){
        //system("clear");
        printMenu();//打印选择菜单
        int choose=0;
        cin >> choose;
        switch(choose){
            case 1:
                addEmployee();//增
                break;
            case 2:
                deleteEmployee();//删
                break;
            case 3:
                updateEmployee();//改
                break;
            case 4:
                queryEmployee();//查
                break;
            case 5:
                paySalary();//结算工资
                break;
            case 0:
                cout << "系统退出..." << endl;
                sleep(1);
                exit(0);
            default:
                cout << "输入错误,请重试" << endl;
        }
    }
}
void Company::printMenu(void){
    cout << "*****************************" << endl;
    cout << "《欢迎进入企业员工管理系统》" << endl;
    cout << "<1> 添加员工" << endl;
    cout << "<2> 删除员工" << endl;
    cout << "<3> 修改员工信息" << endl;
    cout << "<4> 查询员工信息" << endl;
    cout << "<5> 结算工资" << endl;
    cout << "<0> 退出" << endl;
    cout << "*****************************" << endl;
}
void Company::addEmployee(void){
    string name;
    double salary = 0.0;
    double allow = 0.0;
    double bonus = 0.0;
    
    //cout << "添加员工" << endl;
    cout << "姓名:";
    cin >> name;

    cout << "工资:";
    cin >> salary;
   
    cout << "选择职位:<1>/<2>/<3>/<4>:" << endl;;
    cout << "<1> 添加普通员工" << endl;
    cout << "<2> 添加技术员" << endl;
    cout << "<3> 添加经理" << endl;
    cout << "<4> 添加技术主管" << endl;
    int type = 0;
    cin >> type;
    switch(type){
        case 1://创建普通员工
            empVector.push_back(new Employee(name,salary));
            break;
        case 2://创建技术员
            cout << "请输入研发津贴:";
            cin >> allow;
            empVector.push_back(new Technician(name,salary,allow));
            break;
        case 3://创建经理
            cout << "请输入绩效奖金:";
            cin >> bonus;
            empVector.push_back(new Manager(name,salary,bonus));
            break;
        case 4://创建技术主管
            cout << "请输入研发津贴:";
            cin >> allow;
            cout << "请输入绩效奖金:";
            cin >> bonus;
            empVector.push_back(new CTO(name,salary,allow,bonus)); 
            break;
        default:
            cout << "输入错误" << endl;
            break;
    }
}
//删除某个员工信息(将id对应文件删除)
void Company::deleteEmployee(void){
    cout << "删除员工" << endl;
    cout << "请输入员工工号:";
    int id = 0;
    cin >> id;
    //根据工号获取对应的员工文件
    char filename[20]={0};
    sprintf(filename,"%d",id);
    
    //如果对应文件不存在,则说明输入工号信息错误
    if(access(filename,F_OK)==-1){
        cout << "该员工不存在!" << endl;
        return;
    }
    else{
        cout << "确定删除吗? <1>确定/<2>取消" << endl;
        int flag = 0;
        cin >> flag;
        if(flag == 1){
            //遍历容器,找到对应的员工对象
            for(int i=0;i<empVector.size();i++){
                //*empVector[i].operator==(id)
                if(*empVector[i] == id){
                    //将员工对象从容器中删除
                    cout << "删除id=" << id << "的对象" << endl;
                    delete empVector[i];
                    empVector.erase(empVector.begin()+i);
                    break;
                }
            }
            //删除员工信息文件
            if(unlink(filename)==0){
                cout << "删除id="<< id << "的文件" << endl;
            }
            else{
                cout << "删除失败!" << endl;
            }
        }
    }
}
//支持修改基本工资
void Company::updateEmployee(void){
    cout << "修改信息" << endl;
    cout << "请输入员工工号:";
    int id = 0;
    cin >> id;
    //根据工号获取对应的员工文件 
    char filename[20]={0};
    sprintf(filename,"%d",id);
    
    //如果对应文件不存在,则说明输入工号信息错误
    if(access(filename,F_OK)==-1){
        cout << "该员工不存在!" << endl;
        return;
    }
    //遍历容器,找到对应的员工对象
    for(int i=0;i<empVector.size();i++){
        if(*empVector[i] == id){
            cout << *empVector[i] << endl;
            cout << "请输入修改后的工资:";
            double salary=0.0;
            cin >> salary;
            empVector[i]->setSalary(salary);
            empVector[i]->saveInfo();
        }
    }
}
void Company::queryEmployee(void){
    cout << "查询信息:<1>全部信息/<2>个人信息" << endl;
    int choose=0;
    cin >> choose;
    switch(choose){
        case 1:
            queryAll();
            break;
        case 2:
            queryOne();
            break;
        default:
            cout << "输入错误!" << endl;
            return;
    }
}
void Company::queryAll(void){
    cout << "全部员工信息如下:" << endl;
    for(int i = 0;i<empVector.size();i++){
        empVector[i]->printInfo();
        cout << "=======================" << endl;
    }
}
void Company::queryOne(void){
    int id;
    while(1){
        cout << "请输入员工的工号:";
        cin >> id;
        //根据工号获取对应的员工文件 
        char filename[20]={0};
        sprintf(filename,"%d",id);
    
        //如果对应文件不存在,则说明输入工号信息错误
        if(access(filename,F_OK)==-1){
            cout << "该员工不存在" << endl;
            goto CHOOSE;
        }
        for(int i=0;i<empVector.size();i++){
            if(*empVector[i] == id){
                cout << "该员工信息如下:" << endl;
                empVector[i]->printInfo();
                cout << "==========================" << endl;
                break;
            }
        }
CHOOSE:
        cout << "继续查询按1,返回按2" << endl;
        int choose = 0;
        cin >> choose;
        if(choose == 1){
            continue;
        }
        else{
            break;
        }
    }
}
void Company::paySalary(void){
    cout << "结算工资:" << endl;
    cout << "请输入员工工号:";
    int id = 0;
    cin >> id;
    char filename[20]={0};
    sprintf(filename,"%d",id);
    if(access(filename,F_OK)==-1){
        cout << "该员工不存在!" << endl;
        return;
    }
    for(int i=0;i<empVector.size();i++){
        if(*empVector[i] == id){
            empVector[i]->printInfo();
            empVector[i]->calSalary();
            break;
        }
    }
}

代码11:main.cpp

#include "Employee.h"
#include "Technician.h"
#include "Manager.h"
#include "CTO.h"
#include "Company.h"

int main()
{
    //获取表示公司的单例对象
    Company& company = Company::getCompany();
    //运行公司管理系统
    company.run();
    return 0;
}

参考代码:Makefile

PROJ = employee
OBJS = main.o employee.o Technician.o Manager.o CTO.o Company.o 
LINK = g++
RM = rm -rf 
CFLAGS = -c -Wall -I.
$(PROJ):$(OBJS)
	$(LINK) $^ -o $@
.c.o:
	$(LINK) $^
clean:
	$(RM) $(PROJ) $(OBJS) *.gch count.txt id.txt 1000*

二十三、 运行时的类型信息

  1. typeid操作符
    #include
    typeid(类型/对象); //返回typeinfo对象,用于描述类型信息,
    (1)可以使用name()成员函数返回字符串形式的类型信息。
    (2)typeinfo提供了比较操作符重载,可以通过它们直接进行类型之间的比较操作,如果类型之间具有多态继承关系,还可以利用多态的有语法特性确定
    实际的目标对象类型。
    参考代码:01typeid.cpp
#include <iostream>
#include <typeinfo>
using namespace std;
class A{virtual void func(void){}};
class B:public A{virtual void func(void){}};
class C:public A{virtual void func(void){}};

void foo(A & ra){
  if(typeid(ra)== typeid(B)){
     cout<<"针对B子类对象的处理"<<endl;
  }
  else{
     cout<<"其它对象的处理"<<endl;
  }
}

int main(void){
  int i =100;
  cout<<typeid(int).name()<<endl;
  cout<<typeid(i).name()<<endl;

  int *a1[5];
  int (*a2)[5];
  cout<<typeid(a1).name()<<endl;
  cout<<typeid(a2).name()<<endl;
  cout<<typeid(void (*[3])(int)).name()<<endl;
  cout<<typeid(i).name()<<endl;
  
  B b;
  foo(b);
  C c;
  foo(c);
  return 0;
}
  1. dynamic_cast操作符
    (1)语法:
    目标变量 = dynamic_cast<目标类型>(源类型变量);
    (2)适用场景:
    主要用于具有多态语法特性父子类指针或引用之间的显式类型转换.
    参考代码02dynamic_cast.cpp
#include <iostream>
#include <typeinfo>
using namespace std;
class A{virtual void func(void){}};
class B:public A{virtual void func(void){}};
class C:public A{virtual void func(void){}};


int main(void){
  B b;
  A *pa = &b;
  //静态类型转换
  // B* pb = static_cast<B*>(pa);   //合理
  //C* pc = static_cast<C*>(pa);   //不合理
  //动态类型转换
  B* pb = dynamic_cast<B*>(pa);   //合理,ok
  C* pc = dynamic_cast<C*>(pa);   //不合理,返回NULL,表示失败
  cout << "pa=" <<pa<<endl;
  cout << "pb=" <<pb<<endl;
  cout << "pc=" <<pc<<endl;  

  A& ra =b;
  B& rb = dynamic_cast<B&>(ra); //合理
  C& rc = dynamic_cast<C&>(ra);  //不合理,抛出“bad_cast”异常表示失败
  return 0;
}

注意:动态类型转换过程中,会检查目标对象类型和期望转换的类型是否一致,如果一致转换成功,否则失败,如果转换是指针,返回NULL表示失败,如果转换是引用,抛出“bad_cast”异常表示失败。

二十四、 C++异常机制(exception)

  1. 软件开发中的常见错误
    1)语法错误
    2)逻辑错误
    3)功能错误
    4)设计缺陷
    5)需求不符
    6)环境异常
    7)操作不当

  2. 传统C语言中的错误处理
    1)通过返回值表示错误
    优点:函数调用路径中的所有栈对象可以得到正确析构,内存管理安全。
    缺点:错误处理流程比较麻烦,需要逐层进行返回判断,代码臃肿。
    参考代码:03error.cpp

#include <iostream>
#include <cstdio>
using namespace std;

class A{
public:
  A(void){cout<<"A的构造函数"<<endl;}
  ~A(void){cout<<"A的析造函数"<<endl;}
};
int func3(){
  A a;
  FILE* fp = fopen("xx.txt","r");
  if(fp == NULL){
    cout<<"文件打开失败"<<endl;
    return -1;
  }
  cout<<"func3正常执行"<<endl;
  fclose(fp);
  return 0;
}
int func2(){
  A a;
  if(func3()==-1){
     return -1;
  }
  cout<<"func2正常执行"<<endl;
  return 0;
}
int func1(){
  A a;
  if(func2()==-1){
    return -1; 
  }
  cout<<"func1正常执行"<<endl;
  return 0;
}
int main(void){
  if(func1()==-1){
     return -1;
  }
  return 0;
}

2)通过远程跳转处理错误(c++中几乎不用)
优点:错误处理流程比较简单,不需要逐层的返回值判断,一步到位的错误处理,代码精炼
缺点:函数调用路径中的栈对象失去了被析构的机制,有内存泄漏的风险
参考代码:04error.cpp

#include <iostream>
#include <cstdio>
#include <csetjmp>
using namespace std;
jmp_buf g_env;
class A{
public:
  A(void){cout<<"A的构造函数"<<endl;}
  ~A(void){cout<<"A的析造函数"<<endl;}
};
int func3(){
  A a;
  FILE* fp = fopen("xx.txt","r");
  if(fp == NULL){
    cout<<"文件打开失败"<<endl;
    longjmp(g_env,-1);
  }
  cout<<"func3正常执行"<<endl;
  fclose(fp);
  return 0;
}
int func2(){
  A a;
  func3();
  cout<<"func2正常执行"<<endl;
  return 0;
}
int func1(){
  A a;
  func2();
  cout<<"func1正常执行"<<endl;
  return 0;
}
int main(void){
  if(setjmp(g_env)==-1){
    cout<<"文件打开失败"<<endl;
    return -1;
  }
  func1();
  return 0;
}
  1. C++异常语法
    1)异常抛出
    throw 异常对象;
    注:异常对象可以是基本类型的数据,也可以是类类型对象.
2)异常检测和捕获
try{
     可能引发异常的语句;    
}
catch(异常类型1){
  针对异常类型1数据的处理.
}

catch(异常类型2){
针对异常类型2数据的处理.
}
注意:
catch子句根据异常对象的类型自上而下顺序匹配,而不是最优匹配,因此对子类异常捕获语句要写在前面,否则会被基类异常捕获语句提前截获。

  1. 函数异常说明
    1)语法
    返回类型 函数名(参数表) throw(异常类型表) {}
    注:“throw(异常类型表)”即为异常说明表,用于说明该函数可能抛出的异常类型
    2)函数异常说明只是一种承诺,不是强制语法要求,如果函数抛出了异常说明以外的其它类型,则无法被函数的调用者正常检测和捕获,而会被系统捕获,导致进程终止。
3)函数异常说明的两种极端形式
--》不写函数异常说明,表示可以抛出任何异常。
--》空异常说明,“throw()”,表示不可以抛出任何异常。

4)如果函数的声明和函数定义分开,要保证异常说明表中的类型一致,但是顺序无所谓。
5)虚函数覆盖条件和补充
如果基类中的虚函数带有异常说明,那么该函数在子类中的覆盖版本不能比基类中原始版本抛出更多的异常,否则将会因为“放松throw限定”而导致编译失败
参考代码:08vfuncExcept.cpp
#include <iostream>
using namespace std;
class FileError{};
class MemoryError{};
class Base{
public:
  virtual void func(void) throw(FileError,MemoryError){
    cout<<"基类的func"<<endl;
  }
};
class Derived:public Base{
public:
  void func(void) throw(FileError,MemoryError){
    cout<<"子类的func"<<endl;
  }
};
int main(void){
  Derived d;
  Base* pb = &d;
  pb->func();
  return 0;
}
  1. 标准异常类exception
    文件所在路径 : /usr/include/c++/编译器版本号/exception
    class exception{
    public:
        exception() throw() { }
        virtual ~exception() throw();

    /* Returns a C-style character string describing the general cause  of the current error.  */
        virtual const char* what() const throw();
    };
注意:“_GLIBCXX_USE_NOEXCEPT”即为空异常说明“throw()”

运用例:
try{
func();

}
catch(exception& ex){ //可以捕获匹配exception所有子类类型异常对象
ex.what(); //利用多态语法,执行到子类中的what函数
}

参考代码:09stdExcept.cpp

#include <iostream>
using namespace std;
class FileError:public exception{
public:
  virtual ~FileError() throw(){}
  virtual const char* what(void) const throw(){
    cout<<"针对文件相关错误处理"<<endl;
    return "FileError";
  }
};
class MemoryError:public exception{
public:
  virtual ~MemoryError() throw(){}
  virtual const char* what(void) const throw(){
    cout<<"针对文件相关错误处理"<<endl;
    return "FileError";
  }
};
int main(void){
  try{
     throw MemoryError();
     throw FileError();
     //new失败抛出异常"std::bad_alloc"
     char *pc = new char[0xffffffff];
  }
  catch(exception& ex){
     cout<<ex.what()<<endl;
  }
  return 0;
}

  1. 构造函数和析构函数中的异常 //了解
    1)构造函数可以抛出异常,但是对象将被不完整构造,这样的对象其析构不能再被自动调用执行,因此在构造函数抛出异常之前,需要手动清理之前所分配的动态资源.
    2)析构函数最好不要抛出异常

二十五、 I/O流

  1. 主要的I/O流类
    ios: 派生 --》 istream ostream
    |
    V
    istream :派生 --》 istrstream ifstream iostream
    ostream:派生 --》 ofstream ostrstream iostream
    详细参考官方手册:http://www.cplusplus.com/reference/iolibrary/

  2. 格式化I/O
    1)格式化函数(本质是成员函数)
    cout << 100/3.0 << endl; //33.3333

cout.precision(10);  //格式化函数
cout << 100/3.0 << endl;//33.33333333

参考代码:os.cpp
#include <iostream>
use namespace std;
int main(void){
  cout<<10/3.0<<endl;
  cout.precision(10); //设置浮点数精度
  cout<<10/3.0<<endl;
  cout<<"[";
  cout.width(10);  //设置域宽
  cout.fill('$'); //设置空白位置用$字符填充
  cout.setf(ios::showpos); //显示正号
  cout.setf(ios::internal); //数据靠右,符号靠左
  cout<<10000<<"]"<<endl;

  return 0;
}

2)流控制符(本质是全局函数)
cout << 100/3.0 << endl;//33.3333

#include
//流控制符
cout << setprecision(10) << 100/3.0 << endl;//33.33333333
参考代码:osstream.cpp

#include <iostream>
#include <iomanip>
using namespace std;

int main(void){
  cout<<10/3.0<<endl;
  //流控制符
  cout<<setprecision(10)<<10/3.0<<endl;
  cout<<"["<<setw(10)<<setfill('$')<<showpos<<internal<<10000<<"]"<<endl;
  
  //设置域宽8个字符,十六进制,左对齐,显示十六进制标识“0x”
  cout<<setw(8)<<hex<<left<<showbase<<100<<endl; //0x64
  return 0;
}
  1. 字符串流
    #include //过时,不推荐使用
    istrstream、ostrstream
#include <sstream> //推荐
istringstream //类似sscanf()
ostringstream //类似sprintf()
参考代码:14string.cpp
#include <iostream>
#include <sstream>
using namespace std;
int main(void){
  int i = 123;
  double d= 4.56;
  /*char buf[1024] = {0};
 * sprintf(buf,"%d %lf,i,d");
 */
  ostringstream oss;
  oss<<i<<' ' <<d;
  cout<<oss.str()<<endl;

  /*char buf[] = "321 6.54";
 *  sscanf(buf,"%d %lf,&i2,&d2");*/
  istringstream iss("321 6.54");
  int i2 = 0;
  double d2 = 0.0;
  iss>>i2>>d2;
  cout<<i2<<","<<d2<<endl;

  return 0;
}
  1. 文件流
    #include
    ifstream //类似fscanf()
    ofstream //类似fprintf()
    参考代码:15file.cpp
#include <iostream>
#include <fstream>
using namespace std;
int main(void){
  //写文件
  ofstream ofs("file.txt");  //fopen("file.txt","w");
  ofs<<123<<' ' <<4.5<<endl; //fprintf(fp,"%d,%lf",123,4.56);
  ofs.close();  //fclose(fp)

  //读文件
  ifstream ifs("file.txt"); //FILE *fp = fopen("file.txt","r")
  int i = 0;
  double d =0.0;
  //fscanf(fp,"%d%lf",&i,&d);
  ifs>>i>>d;
  ifs.close();  //fclose(fp)
  cout<<"i="<<i<<",d="<<d<<endl;

  return 0;
}
  1. 二进制I/O
    //类似fread
    istream& istream::read(char* buffer,streamsize num);
    //类似fwrite
    ostream& ostaream::write(const char* buffer,size_t num);
    参考代码:last.txt
#include <iostream>
#include <fstream>
using namespace std;
int main(void){
  ofstream ofs("last.txt");
  char wbuf[] = "C++终于结束了";
  ofs.write(wbuf,sizeof(wbuf));
  ofs.close();

  ifstream ifs("last.txt");
  char rbuf[100]  = {0};
  ifs.read(rbuf,sizeof(rbuf));
  cout<<"读到数据:"<<rbuf<<endl;
  ifs.close();

  return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值