面向对象
1 内存分区模型
- 代码区 : 由操作系统管理,存放函数体的二进制代码
- 全局区 : 存放全局变量和静态变量、常量
- 栈区 : 由编译器自动分配释放,存放函数的参数值、局部变量等
- 堆区 : 由程序员分配释放,若程序员不释放,程序结束时由os回收 new
意义:不同区域存放的数据,赋予不同的生命周期,给予更大的灵活性
程序运行前,对于编译后生成的exe文件,内存中分为两部分:
代码区: 存放CPU执行的机器指令(二进制)、共享的、只读的;
全局区:存放全局变量和静态变量static、常量区(字符串常量、const修饰的全局变量),该区域的数据在程序结束后由os释放。
const修饰的 全局变量在全局区,局部变量不在。
程序运行后:
栈:由编译器自动分配释放,存放函数的参数值、局部变量等, 注意不要返回局部变量的地址。
堆:利用new在堆区开辟内存 int * p = new int(10); //new创建的数据会返回该数据对应类型的指针。
指针也是变量,他放在栈上,但他存放的数据 放在堆上。
记得要释放,delete p;
int * p = new int(10);
delete p;
//数组,堆
int * arr = new int[10];
for(int i=0;i<10;i++){
arr[i]=i+100;
}
delete[] p;
2 引用
给变量起别名 int &a=b; //a就是别名,b是原名
引用必须要初始化,一旦初始化后就不可更改了。
引用做函数参数:(引用传递)形参的改变也会影响实参
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
引用做函数返回值:不要返回局部变量的引用,函数的调用可以作为左值。
#include<iostream>
using namespace std;
int& test01(){
static int a=10;
return a;
}
int main(void){
int &ref=test01();
cout<<ref<<endl;
cout<<ref<<endl;
test01()=100;
cout<<ref<<endl;
system("pause");
return 0;
}
引用本质是在C++内部实现一个指针常量。(由编译器做的)
int &ref=a;
//会自动转换为 int* const ref=&a; 指针指向不可变,这也说明了为什么引用不可变。
*ref=20; //指向的值可以改变,解引用。
常量引用:修饰形参,防止误操作,防止形参改变实参。
void test(const int &val){
val=100;
cout<<val<<endl;
}
val=20;
test(val); //val=20;
cout<<val<<endl; //val=20;
3 函数高级
函数的默认参数 : 从一个位置有默认参数后,他之后的位置都要有默认参数。
函数声明有默认参数,函数实现就不能有,只能有一个有。
int func(int a=10,int b=10);
int func(int a,int b){
}
占位参数 放在形参列表上,调用时必须填补。还可以有默认参数。
int func(int a=10,int){
cout<<"a"<<endl;
}
func(10,2);
函数重载 函数名可以相同,提高复用性。
要满足:同一个作用域下;函数名称相同;函数参数类型、或个数、或顺序不同。
引用作为重载条件时,注意
void fun(int &s){
cout<<"int &a"<<endl;
}
void fun(const int &s){
cout<<"const int &a"<<endl;
}
int main(void){
int a=10;
fun(a); //int &a; int &a=10;不合法
fun(10); //const int &a const创建一个临时的数据 temp=10;int &a=temp;合法
system("pause");
return 0;
}
函数重载时碰到默认参数,
void fun(int a,int b=10){
cout<<"int a,int b=10"<<endl;
}
void fun(int a){
cout<<"int a,int b=10"<<endl;
}
int main(void){
int a=10;
fun(a); //导致二义性,编译不成功
system("pause");
return 0;
}
4 类与对象
三大特性:封装、继承、多态。
C++认为万事万物皆为对象,对象上有其属性和行为。
具有相同性质的对象,抽象为类。
4.1 封装
意义: 将属性和行为作为一个整体,表现生活中的事物; 将属性和行为加以权限控制。
实例化一个对象,通过一个类创建对象。
类中的属性和行为都成为成员,属性——成员变量,成员属性; 行为——成员函数、成员方法。
#include<iostream>
using namespace std;
const double PI=3.14;
class Circle{
//访问权限
public:
//属性——半径
int m_r;
//行为:计算周长
double calZC(){
return 2*PI*m_r;
}
};
int main(void){
//通过类创建对象
Circle c1;
c1.m_r=10;
cout<<"周长"<<c1.calZC()<<endl;
system("pause");
return 0;
}
#include<iostream>
using namespace std;
class Student{
public:
string m_Name;
int m_Id;
void showStudent(){
cout<<m_Name<<"\t"<<m_Id<<endl;
}
void setName(string name){
m_Name=name;
}
void setId(int64_t id){
m_Id=id;
}
};
int main(void){
Student s;
s.setId(1);
s.setName("Zhou");
s.showStudent();
system("pause");
return 0;
}
访问权限: public protected private
stcuct 和 class : 区别在于访问权限不同,struct默认为public class默认为private
将成员属性设为私有,优点:可以自己控制读写权限;对于写权限,可以检测数据的有效性。
案例: 立方体类 Cube,求面积和体积,分别用全局函数 和成员函数 判断两个立方体是否相等。
#include<iostream>
using namespace std;
class Cube{
private:
int m_H;
int m_W;
int m_L;
public:
void setL(int l){ m_L=l;}
int getL(){ return m_L;}
void setW(int w){ m_W=w;}
int getW(){ return m_W;}
void setH(int h){ m_H=h;}
int getH(){ return m_H;}
int cal_S(){return m_L*m_W*2+m_L*m_H*2+m_H*m_W*2;}
int cal_V(){return m_L*m_W*m_H;}
bool isSameByClass(Cube c){
if(c.getL()==m_L&&c.getW()==m_W&&c.getH()==m_H){
return true;
}else{
return false;
}
}
};
bool isSame(Cube &c1,Cube &c2){
if(c1.getL()==c2.getL()&&c1.getW()==c2.getW()&&c1.getH()==c2.getH()){
return true;
}else{
return false;
}
}
int main(void){
Cube c1,c2;
c1.setL(10);
c1.setW(10);
c1.setH(10);
c2.setL(10);
c2.setW(10);
c2.setH(10);
cout<<"V="<<c1.getL()*c1.getW()*c1.getH()<<"\t";
cout<<"S="<<c1.getL()*c1.getW()*2+c1.getL()*c1.getH()*2+c1.getW()*c1.getH()*2<<endl;
cout<<"V="<<c1.cal_V()<<"\tS="<<c1.cal_S()<<endl;
if(isSame(c1,c2)){
cout<<"相等_全局函数"<<endl;
}else{
cout<<"不相等_全局函数"<<endl;
}
if(c1.isSameByClass(c2)){
cout<<"相等_成员函数"<<endl;
}else{
cout<<"不相等_成员函数"<<endl;
}
system("pause");
return 0;
}
案例:点和圆, Circle 和 Point 计算其关系
#include<iostream>
using namespace std;
class Point{
private:
int m_X;
int m_Y;
public:
void setX(int x){m_X=x;}
int getX(){return m_X;}
void setY(int y){m_Y=y;}
int getY(){return m_Y;}
};
class Circle{
private:
Point m_Center;
int m_R;
public:
void setCenter(Point center){m_Center=center;}
Point getCenter(){return m_Center;}
void setR(int r){m_R=r;}
int getR(){return m_R;}
};
void isInCircle(Circle &c,Point &p){
int d1=(c.getCenter().getX()-p.getX())*(c.getCenter().getX()-p.getX())+
(c.getCenter().getY()-p.getY())*(c.getCenter().getY()-p.getY());
int d2=c.getR()*c.getR();
if(d1==d2){
cout<<"点在圆上"<<endl;
}else if(d1<d2){
cout<<"点在圆内"<<endl;
}else{
cout<<"点在圆外"<<endl;
}
}
int main(void){
Circle c;
Point r;
r.setX(10);
r.setY(0);
c.setCenter(r);
c.setR(10);
Point p;
r.setX(10);
r.setY(2);
isInCircle(c,r);
system("pause");
return 0;
}
分开编写 ,.h .cpp文件。
4.2 对象的初始化和清理
构造函数(赋值)和析构函数(清理),由编译器自动调用。
构造函数 Student(){ } 自动调用,可以有参数,可以重载,只会调用一次。
析构函数 ~Student(){ } 自动调用,没有参数,不可以重载,只会调用一次。 先进后出。
构造函数
可分为:有参构造(默认构造)和无参构造; 普通构造和拷贝构造 拷贝一样的,不能更改。
//拷贝构造函数
Person( const Person & p){
age=p.age;
}
调用: 括号法、显示法、隐式转换法
使用默认构造函数时,不要加()。如person p() ; 这时编译器会认为他是一个函数声明。
不要用拷贝构造函数初始化匿名对象,编译器会认为这是一个对象的声明。
//括号法
Person p1;
Person p2(20);
Person p3(p3);
//显示法
Person p1;
Person p2=Person(10);
Person p3=Person(p2);
Person(10); 这是一个匿名对象,执行完后会立即释放
//隐式转换法
Person p4=10; // 相当于 Person p4=Person(10); 有参构造
Person p5=p4; //拷贝构造
拷贝构造函数使用时机:
- 用已创建完毕的对象来初始化一个新对象;
- 值传递给函数传参;
- 以值返回局部对象
构造函数调用规则:
- 默认情况下:C++自动添加至少 默认构造函数、默认析构函数、默认拷贝构造函数。
- 如果写了有参构造函数,编译器就不再提供默认构造,但依然提供拷贝构造函数。如果拷贝构造函数,不在提供其他构造函数。
深拷贝和浅拷贝:
- 浅拷贝:赋值拷贝操作——默认拷贝构造函数是浅拷贝 ,new
- 深拷贝:在堆中重新申请空间
如果属性有在堆区拷贝的,就一定要自己实现拷贝构造函数。
class Person{
public:
Person(){
cout<<"默认构造函数"<<endl;
}
Person(int age,int height){
m_Age=age;
m_height=new int(height);
cout<<"有参构造函数"<<endl;
}
Person(const Person &p){
m_Age=p.m_Age;
// m_height=p.m_height; //默认拷贝构造函数,浅拷贝
m_height=new int(*p.m_height); //深拷贝
cout<<"拷贝构造函数"<<endl;
}
~Person(){
if(m_height!=NULL){
delete m_height;
m_height=NULL;
}
cout<<"析构函数"<<endl;
}
int m_Age;
int *m_height;
};
初始化列表:用来初始化属性
class Person{
public:
//Person(int a,int b){m_A=a;m_B=b;}
//Person():m_A(10),m_B(20){
//}
Person(int a,int b):m_A(a),m_B(b){
}
int m_A;
int m_B;
};
类对象作为类成员: 类成员作为另一个类的对象,称该成员为对象成员。
子类的构造函数先执行,析构函数后执行。
静态成员: 变量或函数。
静态成员变量:所有对象共享同一份数据,在编译阶段分配内存,类内声明,类外初始化。也有访问权限
静态成员函数:所有对象共享同一个函数,只能访问静态成员变量,不可访问非…。静态成员函数有两种调用方式,通过对象访问,或 类名。
静态成员函数也有访问权限。
class Person{
public:
static void func(){
cout<<"static void func"<<endl;
}
};
Person p;
p.func();
Person::func();
4.3 对象模型和this指针
成员变量和成员函数分开存储。
C++编辑器会给每个空对象分配一个字节空间,为了区分其占内存的位置。
- 非静态成员变量属于类的对象上。
- 静态成员变量不属于类的对象上。非静态成员函数 也不属于。静态成员函数 也不属于。
每个非静态成员函数 只有一份函数实例, 多个同类型的对象会共用一块代码。这块代码如何区分对象呢? 通过对象指针,即this指针,指向了被调用的成员函数所属的对象。
this指针无需定义,直接使用即可。本质是指针常量。
用途: 形参和成员变量同名时得以区分,解决名称冲突; 返回对象本身时,可用return *this。(链式编程思想)
//解决名称冲突
class Person{
public:
Person(int age){
this->age=age;
}
int age;
};
#include<iostream>
#include<string>
using namespace std;
class Person{
public:
Person(int age){
this->age=age;
}
//不是引用返回,返回的是拷贝体,返回值,拷贝构造函数。
// Person PersonAddAge(Person &p){
// this->age+=p.age;
// return *this;
// }
//返回本体,要以引用的方式返回
Person& PersonAddAge(Person &p){
this->age+=p.age;
return *this;
}
int age;
};
void test(){
Person p1(17);
cout<<p1.age<<endl;
}
void test02(){
Person p1(17);
Person p2(10);
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout<<p2.age<<endl;
}
int main(void){
test02();
system("pause");
return 0;
}
空指针调用成员函数,访问成员变量时,指针若为空,会报错。判断预防一下。
class Person{
public:
void showClass(){
cout<<"class"<<endl;
}
void showAge(){
if(this==NULL)
return;
cout<<"age"<<endl;
}
int age;
};
Person *p =NULL;
p->showClass();
p->showAge();
const修饰成员函数
常函数: void showPersion() const { } 相当于 const Person * const this;
- 不可以修改成员属性
- 成员属性声明时加上关键字mutable后,在常函数中可修改。
常对象: const Person p;
只能调用常函数。
4.4 友元 friend
让一个函数或类 访问零一个类中的私有成员,三种实现:
//1. 全局函数做友元;
//在类中 声明友元
class Building{
friend void func();
}
void func(){
访问私有;
}
//2. 类做友元
类A中声明友元类B,B可访问A
//3. 成员函数做友元
//在要访问的类声明访问者的 friend 的成员函数。
Day 3
4.5 运算符重载
加号 operator+ ,这其实是一个函数名
可以通过成员函数、也可以通过全局函数。
左移<<
#include<iostream>
#include<string>
using namespace std;
class Person{
public:
friend ostream& operator<<(ostream &out,Person &p);
Person(){}
Person(int a,int b):m_A(a),m_B(b){}
//成员函数 重载+号
Person operator+(Person &p){
Person temp;
temp.m_A=m_A+p.m_A;
temp.m_B=m_B+p.m_B;
return temp;
}
//无法利用成员函数 重载<<号,左移,
private:
int m_A;
int m_B;
};
//全局函数 重载+号
// Person operator+(Person &p1,Person &p2){
// Person temp;
// temp.m_A=p1.m_A+p2.m_A;
// temp.m_B=p1.m_B+p2.m_B;
// return temp;
// }
//全局函数 重载<<号,左移
ostream& operator<<(ostream &out,Person &p){
out<<"m_A="<<p.m_A<<"\tm_B="<<p.m_B;
return out;
}
void test01(){
Person p1(10,10);
Person p2(30,30);
Person p=p1+p2;
cout<<p<<endl;
cout<<p1<<endl;
}
int main(void){
test01();
system("pause");
return 0;
}
重载++a 和 a++
#include<iostream>
#include<string>
using namespace std;
class MyInteger{
public:
friend ostream& operator<<(ostream& out,const MyInteger &m);
MyInteger(){
m_Num=0;
}
//前置递增,返回引用是为了一直对一个数据进行前置递增,返回引用
MyInteger& operator++(){
++m_Num;
return *this;
}
//后置递增,int 表示占位参数,可以用来区分前置和后置递增。 返回值,而非引用。
MyInteger operator++(int){
//先 返回结果
MyInteger temp=*this;
//后递增
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& out,const MyInteger &m){
out<<m.m_Num<<endl;
return out;
}
void test01(){
MyInteger myint;
cout<<++myint;
//cout<<(myint++);
cout<<myint;
}
void test02(){
MyInteger my;
cout<<my++;
cout<<my;
}
int main(void){
test02();
system("pause");
return 0;
}
赋值运算符
编译器至少给一个类添加四个函数 :还有一个是 赋值运算符 operator= ,对属性进行值拷贝。(浅拷贝)
如果类中有属性指向堆区,赋值时要深拷贝。
#include<iostream>
#include<string>
using namespace std;
class Person
{
private:
/* data */
public:
Person(int age){
m_Age=new int(age);
}
~Person(){
if(m_Age!=NULL){
delete m_Age;
m_Age=NULL;
}
}
Person& operator=(Person &p){
//先判断是否有属性在堆区,有则先释放干净,然后深拷贝
if(m_Age!=NULL){
delete m_Age;
m_Age=NULL;
}
m_Age=new int(*p.m_Age);
return *this;
}
int *m_Age;
};
void test01(){
Person p1(18);
Person p2(20);
Person p3(30);
cout<<*p1.m_Age<<endl;
cout<<*p2.m_Age<<endl;
p3=p2=p1;
cout<<*p3.m_Age<<endl;
}
int main(void){
test01();
system("pause");
return 0;
}
重载关系运算符 == !=
class Person
{
private:
/* data */
public:
Person(string name,int age){
m_Name=name;
m_Age=age;
}
bool operator==(Person &p){
if(this->m_Name==p.m_Name&&this->m_Age==p.m_Age){
return true;
}
return false;
}
bool operator!=(Person &p){
if(this->m_Name==p.m_Name&&this->m_Age==p.m_Age){
return false;
}
return true;
}
int m_Age;
string m_Name;
};
函数调用运算符 ()
由于重载后使用方式非常像函数的调用,因此称为仿函数。没有固定写法,很灵活。
匿名函数对象
#include<iostream>
#include<string>
using namespace std;
class MyPrint{
public:
void operator()(string test){
cout<<test<<endl;
}
};
class MyAdd{
public:
int operator()(int a,int b){
return a+b;
}
};
void test01(){
MyPrint myPrint;
myPrint("Hi");
}
void test02(){
MyAdd myadd;
cout<<myadd(10,10)<<endl;
//匿名函数对象
cout<<MyAdd()(100,100)<<endl;
}
int main(void){
test01();
test02();
system("pause");
return 0;
}
4.6 继承
好处:减少重复代码
子类也称派生类,父类也称基类。
class BasePage{
public:
void head(){
cout<<"BasePage"<<endl;
}
};
class Java:public BasePage{
};
基类可以自己增加成员(个性), 继承过来的为共性。
继承方式有 公共继承 public 、protected 、 private。
总结一下三个关键字的访问范围:
public: 能被类成员函数、子类函数、友元访问,也能被类的对象访问。
private: 只能被类成员函数及友元访问,不能被其他任何访问,本身的类对象也不行。
protected: 只能被类成员函数、子类函数及友元访问,不能被其他任何访问,本身的类对象也不行
[
](https://blog.csdn.net/yao5hed/article/details/81119256)
继承中的对象模型:
父类中所有的非静态成员属性都会被子类继承,私有成员属性被编译器隐藏了,访问不到,但是也被继承了。
父类和子类构造、析构函数 执行顺序:
父类构造——子类构造——子类析构——父类析构
父类、子类同名成员处理:
- 访问子类时,直接访问
- 父类 则加作用域 , 在变量前加 作用域
成员函数也是如此。静态成员变量也是如此。
多继承:
class Son: public Base1, public Base2{
};
父类中出现同名成员时,加作用域区分。
菱形继承:
两个父类有相同数据时,需要加作用域区分。
利用虚继承来解决,在继承之前加上virtual
class sheep:virtual public animal{}; 这时 sheeptuo类中只有一份m_Age.
vbptr 虚基类指针,指向 vbtable 虚基类表
4.7 多态
分为静态多态(函数重载和运算符重载)、动态多态(派生类和虚函数实现运行时多态)
区别:
- 静态多态: 函数地址早绑定——编译阶段确定函数地址
- 动态多态: 晚绑定——运行时确定。
允许父子之间的类型转换,父类可以调用子类。
虚函数 virtual void func(){ }, 里边存一个指针,其实是一个虚函数(表)指针vfptr ,指向一个虚函数表 vftable,表内部记录着函数的入口地址。
当子类重写父类的虚函数,子类的虚函数表内部会替换成子类的虚函数地址,当父类指针指向子类对象时,发生多态。
动态多态要满足:继承关系; 子类重写父类的虚函数;
使用:父类的指针 或者引用 指向子类对象。 Base * s= new Son;
优点: 代码结构清晰; 可读性强;利于扩展和维护
在真实开发中,提倡开闭原则,对扩展进行开放,对修改进行关闭。
案例:Calculator
纯虚函数和抽象类
父类的虚函数没有用时,将其写为纯虚函数 virtual void func ()=0;
此时这个类称为抽象类。特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类,无法实例化对象。
案例: 制作饮品:煮水—冲泡—倒入杯中—加入辅料,利用多态,抽象 饮品基类,子类 制作咖啡和茶叶。
#include<iostream>
#include<string>
using namespace std;
class AbsDrinking{
public:
virtual void Boil()=0;
virtual void Brew()=0;
virtual void PourInCup()=0;
virtual void PutSth()=0;
void MakeDrink(){
Boil();
Brew();
PourInCup();
PutSth();
}
};
class Coffee:public AbsDrinking
{
public:
void Boil(){
cout<<"煮水"<<endl;
}
void Brew(){
cout<<"冲泡咖啡"<<endl;
}
void PourInCup(){
cout<<"倒入杯中"<<endl;
}
void PutSth(){
cout<<"加入糖和牛奶"<<endl;
}
};
class Tea:public AbsDrinking
{
public:
void Boil(){
cout<<"煮水"<<endl;
}
void Brew(){
cout<<"冲泡茶叶"<<endl;
}
void PourInCup(){
cout<<"倒入杯中"<<endl;
}
void PutSth(){
cout<<"加入枸杞"<<endl;
}
};
void doWork(AbsDrinking * abd){
abd->MakeDrink();
delete abd;
}
void test01(){
doWork(new Coffee);
cout<<"---------------------------"<<endl;
doWork(new Tea);
}
int main(void){
test01();
system("pause");
return 0;
}
虚析构函数和纯虚析构函数 virtual
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构函数。
解决:将父类中的析构函数改为虚析构函数和纯虚析构函数。
虚析构函数和纯虚析构函数的共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现。
区别:纯虚析构函数 —->抽象类,无法实例化对象。
父类在析构的时候不会调用子类的析构函数,导致如果子类有堆区属性,出现内存泄漏。
纯虚析构也要进行实现才不会报错,需要声明也需要实现。
Day 4
案例
5 文件
文件流头文件
类型 文本文件(ASCII)和二进制文件
操作类: ofstream(写),ifstream(读)、fstream(读写)
5.1 文本文件
写步骤: 头文件——创建流对象——打开文件——写数据——关闭
打开方式: ios::in(读) ios::out (写) ios::ate初始位置文件尾) app(追加) trunc(若存在先删除) binary(二进制) 配合使用 |
//写文件
#include<iostream>
#include<string>
#include<fstream>
using namespace std;
void test01(){
ofstream ofs;
ofs.open("test.txt",ios::out);
ofs<<"姓名:唐馨"<<endl;
ofs<<"性别:女"<<endl;
ofs<<"年龄:22"<<endl;
ofs.close();
}
int main(void){
test01();
system("pause");
return 0;
}
读文件:头文件——流对象——打开并判断——读数据——关闭文件
#include<iostream>
#include<string>
#include<fstream>
using namespace std;
void test01(){
ifstream ifs;
ifs.open("test.txt",ios::in);
if(!ifs.is_open()){
cout<<"文件打开失败"<<endl;
return ;
}
//读:第1种方法
// char buf[1024]={0};
// while (ifs>>buf){
// cout<<buf<<endl;
// }
//读:第2种方法
// char buf[1024]={0};
// while (ifs.getline(buf,sizeof(buf)))
// {
// cout<<buf<<endl;
// }
//读:第3种方法
// string buf;
// while(getline(ifs,buf)){
// cout<<buf<<endl;
// }
//读:第4种方法、
char c;
while( (c=ifs.get())!=EOF)
{
cout<<c;
}
ifs.close();
}
int main(void){
test01();
system("pause");
return 0;
}
5.2 二进制文件
读写方式 ios::binary
写用成员函数 write,函数原型 ostream& write(const char buffer, int len);
读用成员函数 read,函数原型 ostream& read(char buffer, int len);
#include<iostream>
#include<string>
#include<fstream>
using namespace std;
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01(){
//写入
ofstream ofs("person.txt",ios::out|ios::binary);
Person p={"唐馨",18};
ofs.write((const char *)&p,sizeof(Person));
ofs.close();
//读取
ifstream ifs("person.txt",ios::in|ios::binary);
if(!ifs.is_open()){
cout<<"文件打开失败"<<endl;
return;
}
Person p1;
ifs.read((char *)&p1,sizeof(Person));
cout<<p1.m_Name<<"\t"<<p1.m_Age<<endl;
}
int main(void){
test01();
system("pause");
return 0;
}
综合案例:职工管理系统 基于多态