1.多态基础
1.1.约分实现代码
#include <iostream>
using namespace std;
class Fract
{
int n;
int d;
public:
Fract():n(0),d(1){}
Fract(int an,int ad):n(an),d(ad)
{
//reduce定义在后面,在未定义时竟然可以使用???
//解释:编译器在处理类时先看所有函数的声明,处理完后才会看函数体中内容
//所以编译器还是先看到了声明,后看到使用
reduce();
}
void reduce()
{
if(d<0)//分母小于0
{
d=-d;
n=-n;
}
else if(d==0)//分母等于0
{
cout<<"d==0!!"<<endl;
}
int absn = (n<0)?-n:n;
for(int i=d;i>1;i--)
{
if(absn%i==0&&d%i==0)
{
n/=i;
d/=i;
break;
}
}
}
void show()
{
cout<<n <<'/'<<d<<endl;
}
double value()
{
return (double)n/d;
}
};
//子类继承父类
class Dai:public Fract
{
int i;
public:
Dai():i(0){} //无参构造函数
Dai(int ai,int an,int ad) //有参构造函数
:i(ai),Fract(an,ad){}//初始化列表,父类的参数初始化交给父类去处理
void show() //子类和父类中有同名函数,子类中调用父类同名函数调用方法
{
cout<<i<<'(';
Fract::show();
}
double value()
{
return i+Fract::value();
}
};
int main()
{
Fract f1;
Dai d2(2,12,16);
//此处调用的是父类Fract的方法
f1.show();
cout<<"f1="<<f1.value()<<endl;
//此处调用子类Dai 的方法
d2.show();
cout<<"d2="<<d2.value()<<endl;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 02.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
0/1
f1=0
2(3/4
d2=2.75
1.2.子类对象地址赋值给父类指针
#include <iostream>
using namespace std;
class Fract
{
int n;
int d;
public:
Fract():n(0),d(1){}
Fract(int an,int ad):n(an),d(ad)
{
reduce();
}
void reduce()
{
if(d<0)//分母小于0
{
d=-d;
n=-n;
}
else if(d==0)//分母等于0
{
cout<<"d==0!!"<<endl;
}
int absn = (n<0)?-n:n;
for(int i=d;i>1;i--)
{
if(absn%i==0&&d%i==0)
{
n/=i;
d/=i;
break;
}
}
}
void show()
{
cout<<n <<'/'<<d<<endl;
}
double value()
{
return (double)n/d;
}
};
//子类继承父类
class Dai:public Fract
{
int i;
public:
Dai():i(0){} //无参构造函数
Dai(int ai,int an,int ad) //有参构造函数
:i(ai),Fract(an,ad){}//初始化列表,父类的参数初始化交给父类去处理
void show() //子类和父类中有同名函数,子类中调用父类同名函数调用方法
{
cout<<i<<'(';
Fract::show();
}
double value()
{
return i+Fract::value();
}
};
int main()
{
Fract f1;
Dai d2(2,12,16);
Fract* p=NULL;
p = &f1;
p->show();//此处调用父类Fract的show,无疑问
cout<<"f1="<<p->value()<<endl;//此处调用父类Fract的value,无疑问
p = &d2;//改变指针指向,父类类型Fract的指针指向子类
p->show();//此处调用的是父类Fract的show
cout<<"d2="<<p->value()<<endl;//此处调用的是父类Fract的value
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 03.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
0/1
f1=0
3/4
d2=0.75
自我理解:
(1)指针存储的是一个起始地址,指针可以指针类型(例如:结构体指针)取结构体中的数据。
(2)子类对象在内存分布上,低地址存储的是父类模块,接下来才是存储子类特有的成员。
(3)子类对象地址赋值给父类指针,那么相当于通过父类指针只能获取存放在子类低地址的父类模块。
2.多态实现
如果想通过父类指针,调用子类中的函数,则需要使用多态。
多态真正要做的是:通过指针去访问某个成员函数时,根据对象的真实类型执行相应的类中的成员函数(例如通过父类指针去调用子类的方法)。
C++允许把派生类对象的地址赋值给基类的指针;以此为基础,用基类的指针调用任何方法,C++都能找到相应的派生类的方法。通过这种操作,达到用基类(父类)指针调用子类方法的目的。统一管理、统一操作接口。
多态关键字:virtual。有virtual关键字的函数,称作虚函数。在调用时能够使用多态特性。如果声明和定义分开,只在声明中写virtual。
PS:如果声明和定义分开,常量函数的const在声明和定义中都要写。
2.1.指针实现多态
#include <iostream>
using namespace std;
class Fract
{
int n;
int d;
public:
Fract():n(0),d(1){}
Fract(int an,int ad):n(an),d(ad)
{
reduce();
}
void reduce()
{
if(d<0)//分母小于0
{
d=-d;
n=-n;
}
else if(d==0)//分母等于0
{
cout<<"d==0!!"<<endl;
}
int absn = (n<0)?-n:n;
for(int i=d;i>1;i--)
{
if(absn%i==0&&d%i==0)
{
n/=i;
d/=i;
break;
}
}
}
//通过virtual实现多态
virtual void show()
{
cout<<n <<'/'<<d<<endl;
}
//通过virtual实现多态
virtual double value()
{
return (double)n/d;
}
};
//子类继承父类
class Dai:public Fract
{
int i;
public:
Dai():i(0){}
Dai(int ai,int an,int ad)
:i(ai),Fract(an,ad){}
void show()
{
cout<<i<<'(';
Fract::show();
}
double value()
{
return i+Fract::value();
}
};
int main()
{
Fract f1;
Dai d2(2,12,16);
Fract* p=NULL;
p = &f1;
p->show();
cout<<"f1="<<p->value()<<endl;
p = &d2;
p->show();//由于多态,此处调用根据d2的真实类型调用子类的show
cout<<"d2="<<p->value()<<endl;由于多态,此处调用根据d2的真实类型调用子类的value
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 03.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
0/1
f1=0
2(3/4
d2=2.75
2.2.引用实现多态
#include <iostream>
using namespace std;
class Fract
{
int n;
int d;
public:
Fract():n(0),d(1){}
Fract(int an,int ad):n(an),d(ad)
{
reduce();
}
void reduce()
{
if(d<0)//分母小于0
{
d=-d;
n=-n;
}
else if(d==0)//分母等于0
{
cout<<"d==0!!"<<endl;
}
int absn = (n<0)?-n:n;
for(int i=d;i>1;i--)
{
if(absn%i==0&&d%i==0)
{
n/=i;
d/=i;
break;
}
}
}
//通过virtual实现多态
virtual void show()
{
cout<<n <<'/'<<d<<endl;
}
//通过virtual实现多态
virtual double value()
{
return (double)n/d;
}
};
//子类继承父类
class Dai:public Fract
{
int i;
public:
Dai():i(0){}
Dai(int ai,int an,int ad)
:i(ai),Fract(an,ad){}
void show()
{
cout<<i<<'(';
Fract::show();
}
double value()
{
return i+Fract::value();
}
};
int main()
{
Fract f1;
Dai d2(2,12,16);
Fract* p=NULL;
p = &f1;
p->show();
cout<<"f1="<<p->value()<<endl;
p = &d2;
p->show();
cout<<"d2="<<p->value()<<endl;
//引用实现多态
Fract& f=d2;
f.show();//由于多态,此处调用根据d2的真实类型调用子类的show
cout<<"f="<<f.value()<<endl;//由于多态,此处调用根据d2的真实类型调用子类的value
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 03.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
0/1
f1=0
2(3/4
d2=2.75
2(3/4
f=2.75
2.3.多态实现小结
1、有继承关系
2、调用的是虚函数(函数声明中有virtual关键字)
3、访问到的是对象自己(用指针或引用)
3.虚函数表
有虚函数的类,编译器处理时会申请一块内存地址,放所有虚函数的地址,形成虚函数表。每个对象都保存一个虚函数表的地址,都会有一个指向虚函数表的指针。因此都会增加4个字节,不管有多少个虚函数,都只需要一个指针。
3.1.没有虚函数的类大小
#include <iostream>
using namespace std;
#include <string>
class Animal
{
string name;
public:
void eat(){
cout<<"动物吃食物"<<endl;
}
void sleep(){
cout<<"动物休息"<<endl;
}
void shout(){
cout<<"动物叫"<<endl;
}
};
int main()
{
cout<<"sizeof(Animal):"<<sizeof(Animal)<<endl;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 04.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
sizeof(Animal):32
3.2.有一个虚函数的类大小
#include <iostream>
using namespace std;
#include <string>
class Animal
{
string name;
public:
virtual void eat(){
cout<<"动物吃食物"<<endl;
}
void sleep(){
cout<<"动物休息"<<endl;
}
void shout(){
cout<<"动物叫"<<endl;
}
};
int main()
{
cout<<"sizeof(Animal):"<<sizeof(Animal)<<endl;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 04.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
sizeof(Animal):40
3.3.有多个虚函数的类大小
#include <iostream>
using namespace std;
#include <string>
class Animal
{
string name;
public:
virtual void eat(){
cout<<"动物吃食物"<<endl;
}
virtual void sleep(){
cout<<"动物休息"<<endl;
}
virtual void shout(){
cout<<"动物叫"<<endl;
}
};
int main()
{
cout<<"sizeof(Animal):"<<sizeof(Animal)<<endl;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 04.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
sizeof(Animal):40
4.多态举例
4.1.指针实现多态
#include <iostream>
using namespace std;
#include <string>
class Animal
{
string name;
public:
virtual void eat(){
cout<<"动物吃食物"<<endl;
}
virtual void sleep(){
cout<<"动物休息"<<endl;
}
virtual void shout(){
cout<<"动物叫"<<endl;
}
};
class Cat:public Animal
{
public:
//子类中函数前的virtual可以省略
//子类中类型需与父类中返回类型一致,参数表一致
void eat(){
cout<<"猫吃猫粮"<<endl;
}
void sleep(){
cout<<"猫在床上睡觉"<<endl;
}
void shout(){
cout<<"猫喵喵叫"<<endl;
}
};
class Dog:public Animal
{
public:
//子类中函数前的virtual可以省略
//子类中类型需与父类中返回类型一致,参数表一致
void eat(){
cout<<"狗啃骨头"<<endl;
}
void sleep(){
cout<<"狗在狗窝睡觉"<<endl;
}
void shout(){
cout<<"狗汪汪叫"<<endl;
}
};
class JiaFei:public Cat
{
public:
//子类中函数前的virtual可以省略
//子类中类型需与父类中返回类型一致,参数表一致
void eat(){
cout<<"加菲猫吃意大利面"<<endl;
}
void sleep(){
cout<<"加菲猫在沙发睡觉"<<endl;
}
void shout(){
cout<<"加菲猫说下午好"<<endl;
}
};
class Player{
public:
void play(Animal* p){//使用基类指针
p->eat();
p->sleep();
p->shout();
}
};
int main()
{
Cat c;
Dog d;
JiaFei J;
Player p1;
Player p2;
Player p3;
//实际类型是派生类,传派生类地址
p1.play(&c);
p2.play(&d);
p3.play(&J);
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 05.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
猫吃猫粮
猫在床上睡觉
猫喵喵叫
狗啃骨头
狗在狗窝睡觉
狗汪汪叫
加菲猫吃意大利面
加菲猫在沙发睡觉
加菲猫说下午好
4.2.引用实现多态
#include <iostream>
using namespace std;
#include <string>
class Animal
{
string name;
public:
virtual void eat(){
cout<<"动物吃食物"<<endl;
}
virtual void sleep(){
cout<<"动物休息"<<endl;
}
virtual void shout(){
cout<<"动物叫"<<endl;
}
};
class Cat:public Animal
{
public:
//子类中函数前的virtual可以省略
//子类中类型需与父类中返回类型一致,参数表一致
void eat(){
cout<<"猫吃猫粮"<<endl;
}
void sleep(){
cout<<"猫在床上睡觉"<<endl;
}
void shout(){
cout<<"猫喵喵叫"<<endl;
}
};
class Dog:public Animal
{
public:
//子类中函数前的virtual可以省略
//子类中类型需与父类中返回类型一致,参数表一致
void eat(){
cout<<"狗啃骨头"<<endl;
}
void sleep(){
cout<<"狗在狗窝睡觉"<<endl;
}
void shout(){
cout<<"狗汪汪叫"<<endl;
}
};
class JiaFei:public Cat
{
public:
//子类中函数前的virtual可以省略
//子类中类型需与父类中返回类型一致,参数表一致
void eat(){
cout<<"加菲猫吃意大利面"<<endl;
}
void sleep(){
cout<<"加菲猫在沙发睡觉"<<endl;
}
void shout(){
cout<<"加菲猫说下午好"<<endl;
}
};
class Player{
public:
void play(Animal* p){//使用基类指针
p->eat();
p->sleep();
p->shout();
}
void play(Animal& p){//通过引用实现,依然使用基类类型
p.eat();
p.sleep();
p.shout();
}
};
int main()
{
Cat c;
Dog d;
JiaFei J;
Player p1;
Player p2;
Player p3;
//实际类型是派生类,传派生类地址
p1.play(&c);
p2.play(&d);
//p3.play(&J);
//通过引用方式实现,传派生类对象
p3.play(J);
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 05.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
猫吃猫粮
猫在床上睡觉
猫喵喵叫
狗啃骨头
狗在狗窝睡觉
狗汪汪叫
加菲猫吃意大利面
加菲猫在沙发睡觉
加菲猫说下午好
5.纯虚函数、抽象类(不允许直接创建对象)
上例中基类Animal中的三个函数,始终没有输出过信息,是否可以将这三个函数删除呢?
答案是:不能! 因为后面代码中有使用Animal类型指针,例如调用p->eat(),如果Animal中没有这个函数,则无法调用。
那么这三个函数存在没什么用处,又不能删除,可以将这三个函数只保留声明即可,定义可删除。
函数只声明不定义,如果实际被调用了(例如调用p->eat())则编译报错。可以在声明最后增加“=0”,表示这个函数是个一定不会被执行的函数。这样的函数称作纯虚函数。有纯虚函数的类,叫抽象类。
如果直接创建一个抽象类对象,那么就可以通过“对象.成员”的方式调用并执行纯虚函数,而上面讲到纯虚函数是一定不会被执行的函数,所以抽象类不允许直接创建对象。只能用来指向或者引用子类对象。
#include <iostream>
using namespace std;
#include <string>
class Animal
{
string name;
public:
virtual void eat()=0;//纯虚函数,一定不会被真正执行的函数
virtual void sleep()=0;
virtual void shout()=0;
};
class Cat:public Animal
{
public:
//子类中函数前的virtual可以省略
//子类中类型需与父类中返回类型一致,参数表一致
void eat(){
cout<<"猫吃猫粮"<<endl;
}
void sleep(){
cout<<"猫在床上睡觉"<<endl;
}
void shout(){
cout<<"猫喵喵叫"<<endl;
}
};
class Dog:public Animal
{
public:
//子类中函数前的virtual可以省略
//子类中类型需与父类中返回类型一致,参数表一致
void eat(){
cout<<"狗啃骨头"<<endl;
}
void sleep(){
cout<<"狗在狗窝睡觉"<<endl;
}
void shout(){
cout<<"狗汪汪叫"<<endl;
}
};
class JiaFei:public Cat
{
public:
//子类中函数前的virtual可以省略
//子类中类型需与父类中返回类型一致,参数表一致
void eat(){
cout<<"加菲猫吃意大利面"<<endl;
}
void sleep(){
cout<<"加菲猫在沙发睡觉"<<endl;
}
void shout(){
cout<<"加菲猫说下午好"<<endl;
}
};
class Player{
public:
void play(Animal* p){//使用基类指针
p->eat();
p->sleep();
p->shout();
}
void play(Animal& p){//通过引用实现,依然使用基类类型
p.eat();
p.sleep();
p.shout();
}
};
int main()
{
Cat c;
Dog d;
JiaFei J;
Player p1;
Player p2;
Player p3;
//实际类型是派生类,传派生类地址
p1.play(&c);
p2.play(&d);
//p3.play(&J);
//通过引用方式实现,传派生类对象
p3.play(J);
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 05.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
猫吃猫粮
猫在床上睡觉
猫喵喵叫
狗啃骨头
狗在狗窝睡觉
狗汪汪叫
加菲猫吃意大利面
加菲猫在沙发睡觉
加菲猫说下午好
6.虚函数注意事项
6.1.构造函数不能是虚函数
虚函数用来实现多态,调同一个接口,构造函数不会主动调用,都是自动调用。
6.2.类中任何一个成员函数是虚函数,则析构函数应为虚函数
父类 *p = new 子类;
delete p;
如果父类中析构函数不是虚函数,则不会调用子类的析构函数。
6.2.1.父类析构不是虚函数
#include <iostream>
using namespace std;
class A{
public:
A(){cout<<"A()"<<endl;}
~A(){cout<<"~A()"<<endl;}//不是虚函数
};
class B:public A{
public:
B(){cout<<"B()"<<endl;}
~B(){cout<<"~B()"<<endl;}
};
int main()
{
A* p=new B;
delete p;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 06.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
A()
B()
~A()
6.2.2.父类析构是虚函数
#include <iostream>
using namespace std;
class A{
public:
A(){cout<<"A()"<<endl;}
virtual ~A(){cout<<"~A()"<<endl;}//是虚函数
};
class B:public A{
public:
B(){cout<<"B()"<<endl;}
~B(){cout<<"~B()"<<endl;}
};
int main()
{
A* p=new B;
delete p;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 06.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
A()
B()
~B()
~A()
6.3.一个类注定做父类,尽可能使用虚函数,甚至纯虚函数
如果一个类肯定被用作其他派生类的基类(即注定做父类),尽可能使用虚函数,甚至纯虚函数(用"=0;"代替函数体)。
7.友员
用途:直接访问某个类中私有成员的桥梁。
友员:外面的函数或者另一个类。友员不是成员。友员可以声明/定义在类中的任何地方,声明和定义可以分开,也可以不分开。
7.1.友员函数
#include <iostream>
using namespace std;
class A{
int data;
public:
A(int d=0):data(d){}
void show(){
cout<<"data="<<data<<endl;
}
friend A add(A a1,A a2);//声明友员,表示对其授权,允许其访问A类中的所有成员。友员不是A的成员函数
};
A add(A a1,A a2){
int sum = a1.data+a2.data;//访问A中的成员,访问方式:“对象.成员”
return A(sum);
}
int main()
{
A a1(40);
A a2(50);
add(a1,a2).show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 07.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
data=90
7.2.传递对象常用表达方式
凡是向函数中传对象时,几乎都是用引用,只要不需要改变这个对象,几乎都会加const。
#include <iostream>
using namespace std;
class A{
int data;
public:
A(int d=0):data(d){}
void show(){
cout<<"data="<<data<<endl;
}
friend A add(const A& a1,const A& a2);//声明友员,表示对其授权,允许其访问A类中的所有成员。友员不是A的成员函数
};
A add(const A& a1,const A& a2){ //使用引用,由于不改变这个对象,再加const
int sum = a1.data+a2.data;//访问A中的成员,访问方式:“对象.成员”
return A(sum);
}
int main()
{
A a1(40);
A a2(50);
add(a1,a2).show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 07.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
data=90
7.3.友员类
#include <iostream>
using namespace std;
class A{
int data;
public:
A(int d=0):data(d){}
void show(){
cout<<"data="<<data<<endl;
}
friend class B;//声明友员,表示对其授权,允许其访问A类中的所有成员。友员不是A的成员函数
};
class B{
public:
void twice(A& a){ //类的传递使用引用实现,由于要改变这个对象,所以不加const
a.data *= 2;
}
};
int main()
{
A oa(50);
B ob;//定义友员类对象
ob.twice(oa);//使用友员对象将对象oa中data数值加倍
oa.show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 08.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
data=100
8.静态成员
凡是所有对象共用一份的数据,都要声明为静态数据成员;所有对象一致的方法都要声明成静态方法。例如学生是对象,教室是共用的,更换老师是共同的行为。所有的同一类对象都使用一份数据。
静态数据成员又称为类变量;它属于这个类,被该类的全体对象共享;
静态函数成员又称为类方法/类行为;
静态数据成员像全局变量一样在所有函数之外初始化。(java中没有全局变量,拿静态数据成员当全局变量使用)
8.1.静态数据成员(类成员)
#include <iostream>
using namespace std;
class Sd0705{
public:
string name;
static string teacher;//静态数据成员声明
static int room;//静态数据成员声明
Sd0705(const string& n):name(n){}//构造函数
void show(){
cout<<"我是"<<name<<",在"<<room<<"教室听"<<teacher<<"讲课"<<endl;
}
};
string Sd0705::teacher="陈宗权";//静态数据成员初始化
int Sd0705::room=712;//静态数据成员初始化
int main()
{
Sd0705 s1("张三");
Sd0705 s2("李四");
Sd0705 s3("沙雕");
s1.show();
s2.show();
s3.show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 09.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
我是张三,在712教室听陈宗权讲课
我是李四,在712教室听陈宗权讲课
我是沙雕,在712教室听陈宗权讲课
8.2.静态函数成员(类方法)
公共的静态函数成员就是一个全局函数;
对公共的静态函数可以直接调用,不需要通过任何对象;
静态成员函数中不得使用非静态数据成员;
#include <iostream>
using namespace std;
class Sd0705{
public:
string name;
static string teacher;
static int room;
Sd0705(const string& n):name(n){}//构造函数
void show(){
cout<<"我是"<<name<<",在"<<room<<"教室听"<<teacher<<"讲课"<<endl;
}
static void chgTeacher(const string& t){//静态函数成员,更换老师
teacher =t;
}
};
string Sd0705::teacher="陈宗权";//静态数据成员初始化
int Sd0705::room=712;//静态数据成员初始化
int main()
{
Sd0705 s1("张三");
Sd0705 s2("李四");
Sd0705 s3("沙雕");
s1.show();
s2.show();
s3.show();
Sd0705::chgTeacher("谢老师");//调用静态函数成员,调用方法“类名::静态函数成员”,不提倡使用”对象.成员“
s1.show();
s2.show();
s3.show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day10# g++ 10.cpp
root@host:/home/LinuxShare/004.c++/day10# ./a.out
我是张三,在712教室听陈宗权讲课
我是李四,在712教室听陈宗权讲课
我是沙雕,在712教室听陈宗权讲课
我是张三,在712教室听谢老师讲课
我是李四,在712教室听谢老师讲课
我是沙雕,在712教室听谢老师讲课