实验4

一、实验任务1

源代码GradeCalc.hpp

 1 #pragma once
 2 #include<vector>
 3 #include<array>
 4 #include<string>
 5 class GradeCalc{
 6 public:
 7     GradeCalc(const std::string &cname);
 8     void input(int n);
 9     void output() const;
10     void sort(bool ascending=false);
11     int min() const;
12     int max() const;
13     double average() const;
14     void info();
15 private:
16     void compute();
17 private:
18     std::string course_name;
19     std::vector<int> grades;
20     std::array<int,5> counts;
21     std::array<double,5> rates;
22     bool is_dirty;
23 };
GradeCalc.hpp

源代码GradeCalc.cpp

 1 #include<algorithm>
 2 #include<array>
 3 #include<cstdlib>
 4 #include<iomanip>
 5 #include<iostream>
 6 #include<numeric>
 7 #include<string>
 8 #include<vector>
 9 #include"GradeCalc.hpp"
10 GradeCalc::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true}{
11     counts.fill(0);
12     rates.fill(0);
13 }
14 void GradeCalc::input(int n){
15     if(n<0){
16         std::cerr<<"无效输入!人数不能为负数\n";
17         std::exit(1);
18     }
19     grades.reserve(n);
20     int grade;
21     for(int i=0;i<n;){
22         std::cin>>grade;
23         if(grade<0||grade>100){
24             std::cerr<<"无效输入!分数须在[0,100]\n";
25             continue;
26         }
27         grades.push_back(grade);
28         ++i;
29     }    
30     is_dirty=true;
31 }
32 void GradeCalc::output() const{
33     for(auto grade:grades)
34         std::cout<<grade<<' ';
35     std::cout<<std::endl;
36 }
37 void GradeCalc::sort(bool ascending){
38     if(ascending)
39         std::sort(grades.begin(),grades.end());
40     else
41         std::sort(grades.begin(),grades.end(),std::greater<int>());
42 }
43 int GradeCalc::min() const{
44     if(grades.empty())
45         return -1;
46     auto it=std::min_element(grades.begin(),grades.end());
47     return *it;
48 }
49 int GradeCalc::max() const{
50     if(grades.empty())
51         return -1;
52     auto it=std::max_element(grades.begin(),grades.end());
53     return *it;
54 }
55 double GradeCalc::average() const{
56     if(grades.empty())
57         return 0.0;
58     double avg=std::accumulate(grades.begin(),grades.end(),0.0)/grades.size();
59     return avg;
60 } 
61 void GradeCalc::info(){
62     if(is_dirty)
63         compute();
64     std::cout<<"课程名称:\t"<<course_name<<std::endl;
65     std::cout<<"平均分:\t"<<std::fixed<<std::setprecision(2)<<average()<<std::endl;
66     std::cout<<"最高分:\t"<<max()<<std::endl;
67     std::cout<<"最低分:\t"<<min()<<std::endl;
68     const std::array<std::string,5> grade_range{"[0,60)","[60,70)","[70,80)","[80,90)","[90,100]"};
69     for(int i=grade_range.size()-1;i>=0;i--)
70         std::cout<<grade_range[i]<<"\t: "<<counts[i]<<"人\t"
71                  <<std::fixed<<std::setprecision(2)<<rates[i]*100<<"%\n";
72 }
73 void GradeCalc::compute(){
74     if(grades.empty())
75         return;
76     counts.fill(0);
77     rates.fill(0.0);
78     for(auto grade:grades){
79         if(grade<60)
80             ++counts[0];
81         else if(grade<70)
82             ++counts[1];
83         else if(grade<80)
84             ++counts[2];
85         else if(grade<90)
86             ++counts[3];
87         else
88             ++counts[4];
89     }
90     for(int i=0;i<rates.size();i++)
91         rates[i]=counts[i]*1.0/grades.size();
92     is_dirty=false;
93 }
GradeCalc.cpp

源代码task1.cpp

 1 #include<iostream>
 2 #include<string>
 3 #include"GradeCalc.hpp"
 4 void test(){
 5     GradeCalc c1("OOP");
 6     std::cout<<"录入成绩:\n";
 7     c1.input(5);
 8     std::cout<<"输出成绩:\n";
 9     c1.output();
10     std::cout<<"排序后成绩:\n";
11     c1.sort();c1.output();
12     std::cout<<"*************成绩统计信息*************\n";
13     c1.info();
14 }
15 int main(){
16     test();
17 }
task1.cpp

运行成果展示:

image

 问题1:组合关系识别

GradeCalc类声明中,逐行写出所有体现"组合"关系的成员声明,并用一句话说明每个被组合对象的功能。 

答:1.std::string course_name;记录课程的名称

2.std::vector<int> grades;记录下每个人的成绩

3.std::array<int,5> counts;记录5个分数段各自的人数

4.std::array<double,5> rates;记录5个分数段各自的人数占比

问题2:接口暴露理解

如在test模块中这样使用,是否合法?如不合法,解释原因。 

image

 答:不合法。因为push_back是标准库函数,它可以在类内被vector成员直接调用。但在类的声明中std::vector<int> grades是私有类型的,所以不能在类外部的test模块中直接访问。

问题3:架构设计分析 

当前设计方案中,compute在info模块中调用:

(1)连续打印3次统计信息,compute会被调用几次?标记is_dirty起到什么作用? 

答:compute会被调用1次。标记is_dirty可以用来判断整体的打印信息有没有被修改过,如果每次打印的信息相同,就不用重复调用compute进行统计,提高效率。

(2)如新增update_grade(index,new_grade),这种设计需要更改compute调用位置吗?简洁说明理由。

答:需要更改。因为新增的接口会改变打印的信息(这时is_dirty标记也会变成true),所以需要调用compute重新统计各项数据。

问题4:功能扩展设计

要增加"中位数"统计,不新增数据成员怎么做?在哪个函数里加?写出伪代码。

答: 可以直接在info函数中加,与平均分之类的一起统计。因为中位数是一组数据中大小在最中间的(奇数情况下是最中间,偶数情况下是取中间两个数的平均值),正常情况需要先对一组成绩数据排序,在根据奇数还是偶数来取中间值,而在test函数中,先对成绩排序后才调用info统计信息,所以可以省去排序的步骤。

image

image

 问题5:数据状态管理

GradeCalc和compute中都包含代码:counts.fill(0);rates.fill(0);。compute中能否去掉这两行?如去掉,在哪种使用场景下会引发统计错误?

答: 不能去掉。因为当is_false标记为true时会再次调用compute进行数据统计,如果不把counts和rates都归零,再次统计时会在原基础上继续加,导致错误。

问题6:内存管理理解

input 模块中代码 grades.reserve(n); 如果去掉:

(1)对程序功能有影响吗?(去掉重新编译、运行,观察功能是否受影响)

答:没有影响。

2)对性能有影响吗?如有影响,用一句话陈述具体影响。 

 答:有影响。reserve函数用来预留容器vector中的元素数量(通常在需要插入大量元素时使用),使用reserve可以避免插入一次新数据就要重新分配内存和复制元素,所以去掉后会降低性能。

 

二、实验任务2

源代码GradeCalc.hpp

 1 #pragma once
 2 #include<vector>
 3 #include<array>
 4 #include<string>
 5 class GradeCalc:private std::vector<int>{
 6 public:
 7     GradeCalc(const std::string &cname);
 8     void input(int n);
 9     void output() const;
10     void sort(bool ascending=false);
11     int min() const;
12     int max() const;
13     double average() const;
14     void info();
15 private:
16     void compute();
17 private:
18     std::string course_name;
19     std::array<int,5> counts;
20     std::array<double,5> rates;
21     bool is_dirty;
22 };
GradeCalc.hpp

源代码GradeCalc.cpp

 1 #include<algorithm>
 2 #include<array>
 3 #include<cstdlib>
 4 #include<iomanip>
 5 #include<iostream>
 6 #include<numeric>
 7 #include<string>
 8 #include<vector>
 9 #include"GradeCalc.hpp"
10 GradeCalc::GradeCalc(const std::string &cname):course_name{cname},is_dirty{true}{
11     counts.fill(0);
12     rates.fill(0);
13 }
14 void GradeCalc::input(int n){
15     if(n<0){
16         std::cerr<<"无效输入!人数不能为负数\n";
17         return;
18     }
19     this->reserve(n);
20     int grade;
21     for(int i=0;i<n;){
22         std::cin>>grade;
23         if(grade<0||grade>100){
24             std::cerr<<"无效输入!分数须在[0,100]\n";
25             continue;
26         }
27         this->push_back(grade);
28         ++i;
29     }
30     is_dirty=true;
31 }
32 void GradeCalc::output() const{
33     for(auto grade:*this)
34         std::cout<<grade<<' ';
35     std::cout<<std::endl;
36 }
37 void GradeCalc::sort(bool ascending){
38     if(ascending)
39         std::sort(this->begin(),this->end());
40     else
41         std::sort(this->begin(),this->end(),std::greater<int>());
42 }
43 int GradeCalc::min() const{
44     if(this->empty())
45         return -1;
46     return *std::min_element(this->begin(),this->end());
47 }
48 int GradeCalc::max() const{
49     if(this->empty())
50         return -1;
51     return *std::max_element(this->begin(),this->end());
52 }
53 double GradeCalc::average() const{
54     if(this->empty())
55         return 0.0;
56     double avg=std::accumulate(this->begin(),this->end(),0.0)/this->size();
57     return avg;
58 } 
59 void GradeCalc::info(){
60     if(is_dirty)
61         compute();
62     std::cout<<"课程名称:\t"<<course_name<<std::endl;
63     std::cout<<"平均分:\t"<<std::fixed<<std::setprecision(2)<<average()<<std::endl;
64     std::cout<<"最高分:\t"<<max()<<std::endl;
65     std::cout<<"最低分:\t"<<min()<<std::endl;
66     const std::array<std::string,5> grade_range{"[0,60)",
67     "[60,70)","[70,80)","[80,90)","[90,100]"};
68     for(int i=grade_range.size()-1;i>=0;i--)
69         std::cout<<grade_range[i]<<"\t: "<<counts[i]<<"人\t"
70                  <<std::fixed<<std::setprecision(2)<<rates[i]*100<<"%\n";
71 }
72 void GradeCalc::compute(){
73     if(this->empty())
74         return;
75     counts.fill(0);
76     rates.fill(0.0);
77     for(int grade:*this){
78         if(grade<60)
79             ++counts[0];
80         else if(grade<70)
81             ++counts[1];
82         else if(grade<80)
83             ++counts[2];
84         else if(grade<90)
85             ++counts[3];
86         else
87             ++counts[4];
88     }
89     for(int i=0;i<rates.size();i++)
90         rates[i]=counts[i]*1.0/this->size();
91     is_dirty=false;
92 }
GradeCalc.cpp

源代码task2.cpp

 1 #include<iostream>
 2 #include<string>
 3 #include"GradeCalc.hpp"
 4 void test(){
 5     GradeCalc c1("OOP");
 6     std::cout<<"录入成绩:\n";
 7     c1.input(5);
 8     std::cout<<"输出成绩:\n";
 9     c1.output();
10     std::cout<<"排序后成绩:\n";
11     c1.sort();c1.output();
12     std::cout<<"*************成绩统计信息*************\n";
13     c1.info();
14 }
15 int main(){
16     test();
17 }
task2.cpp

 运行成果展示:

image

 问题1:继承关系识别

写出GradeCalc类声明体现"继承"关系的完整代码行。

答:class GradeCalc:private std::vector<int>

问题2:接口暴露理解

当前继承方式下,基类vector的接口会自动成为GradeCalc的接口吗?

如在test模块中这样用,能否编译通过?用一句话解释原因。

image

 答:基类vector的接口会自动成为GradeCalc的接口,但在test模块中编译不能通过。因为是私有继承,所以基类的公有和保护成员在派生类中变为私有,只能在派生类内部访问。

问题3:数据访问差异

对比继承方式与组合方式内部实现数据访问的一行典型代码。说明两种方式下的封装差异带来的数据访问接口差异。

image

 答:组合方式是在内部通过组合类中的vector成员对象grades的接口进行访问,继承方式是在内部用this指针直接通过使用基类的接口来进行访问。在组合中,封装性更强一些,组合类只能通过特定接口访问,看不到内部细节;在继承中,当公有继承时,内部、外部可访问基类接口,当保护和私有继承时,只能在内部对基类接口进行访问,继承可以访问基类内部成员。

 

问题4:组合 vs. 继承方案选择

你认为组合方案和继承方案,哪个更适合成绩计算这个问题场景?简洁陈述你的结论和理由。 

答:我觉得组合方案更适合。1.本身vector容器像一个成绩条,而GradeCalc成绩计算器是具有一个成绩条,然后对这些成绩进行处理,符合has-a的关系。2.在组合方式中,虽然不能直接使用this指针进行调用,但我觉得使用grades.begin()反而看起来更加直观,表示是成绩。3.组合的封装性更好,看不到内部的成绩细节,但有接口可以使用,当std::vector<int> grades设为私有时,外部无法直接访问成绩,更安全。

 

三、实验任务3

源代码Graph.hpp

 1 #pragma once
 2 #include<string>
 3 #include<vector>
 4 enum class GraphType{circle,triangle,rectangle};
 5 class Graph{
 6 public:
 7     virtual void draw(){}
 8     virtual ~Graph()=default;
 9 };
10 class Circle:public Graph{
11 public:
12     void draw();
13 };
14 class Triangle:public Graph{
15 public:
16     void draw();
17 };
18 class Rectangle:public Graph{
19 public:
20     void draw();
21 };
22 class Canvas{
23 public:
24     void add(const std::string& type);
25     void paint() const;
26     ~Canvas();
27 private:
28     std::vector<Graph*> graphs;
29 };
30 GraphType str_to_GraphType(const std::string& s);
31 Graph* make_graph(const std::string& type);
Graph.hpp

源代码Graph.cpp

 1 #include<algorithm>
 2 #include<cctype>
 3 #include<iostream>
 4 #include<string>
 5 #include"Graph.hpp"
 6 void Circle::draw(){
 7     std::cout<<"draw a circle...\n";
 8 }
 9 void Triangle::draw(){
10     std::cout<<"draw a triangle...\n";
11 }
12 void Rectangle::draw(){
13     std::cout<<"draw a rectangle...\n";
14 }
15 void Canvas::add(const std::string& type){
16     Graph* g=make_graph(type);
17     if(g)
18         graphs.push_back(g);
19 }
20 void Canvas::paint() const{
21     for(Graph* g:graphs)
22         g->draw();
23 }
24 Canvas::~Canvas(){
25     for(Graph* g:graphs)
26         delete g;
27 }
28 GraphType str_to_GraphType(const std::string& s){
29     std::string t=s;
30     std::transform(s.begin(),s.end(),t.begin(),
31                    [](unsigned char c){return std::tolower(c);});
32     if(t=="circle")
33         return GraphType::circle;
34     if(t=="triangle")
35         return GraphType::triangle;
36     if(t=="rectangle")
37         return GraphType::rectangle;
38     return GraphType::circle;
39 }
40 Graph* make_graph(const std::string& type){
41     switch(str_to_GraphType(type)){
42     case GraphType::circle: return new Circle;
43     case GraphType::triangle: return new Triangle;
44     case GraphType::rectangle: return new Rectangle;
45     default: return nullptr;
46     }
47 }
Graph.cpp

源代码task3.cpp

 1 #include<string>
 2 #include"Graph.hpp"
 3 void test(){
 4     Canvas canvas;
 5     canvas.add("circle");
 6     canvas.add("triangle");
 7     canvas.add("rectangle");
 8     canvas.paint();
 9 }
10 int main(){
11     test();
12 }
task3.cpp

运行成果展示:

image

 问题1:对象关系识别 

(1)写出Graph.hpp中体现"组合"关系的成员声明代码行,并用一句话说明被组合对象的功能。 

答:std::vector<Graph*> graphs; 被组合对象是Graph*类型,代表着指向不同图形类型的指针,Canvas类中用来表示在画布上画不同的图形。

(2)写出Graph.hpp中体现"继承"关系的类声明代码行。 

答:class Circle:public Graph/class Triangle:public Graph/class Rectangle:public Graph

问题2:多态机制观察 

(1) Graph中的draw若未声明成虚函数,Canvas::paint()中g->draw()运行结果会有何不同?

答:虚函数是在积累中声明为virtual并在派生类中可以被重新定义的成员函数,也是实现多态的关键。如果draw没有声明成虚函数,基类中的同名函数draw会被隐藏,Canvas中就会无法使用g->draw(),导致编译能成功但没有输出结果。

image

(2)若Canvas类std::vector<Graph*>改成std::vector<Graph>,会出现什么问题?

答:这样改之后,vector容器中的数据从指针类型到基类对象,Canvas类中没有办法存储各个派生类对应的图形指针,与后续在成员函数中定义的Graph* g也不匹配,出现报错问题。同时只能调用基类中的函数,无法进行绘制操作。

image

(3)若~Graph()未声明成虚函数,会带来什么问题?

答:把基类的析构函数声明为虚函数可以在基类指针指向派生类对象时,用基类指针删除派生类对象,防止在析构时只析构基类而不析构派生类。所以当~Graph()未声明成虚函数时,由于本题中是用基类指针操作派生类对象,所以没有析构派生类,出现内存泄漏。

问题3:扩展性思考

若要新增星形 Star ,需在哪些文件做哪些改动?逐一列出。

答:1.在Graph.hpp中,增加Star类的声明,注意要公有继承Graph类,同时枚举类型也要加上star。

2.在Graph.hpp中,增加Star类内函数draw的定义,输出draw a star...;在str_to_GraphType函数定义中加上对star的枚举转换;在make_graph函数中增加可以返回star图形指针。

3.在task4.cpp中新增canvas.add("star"),就可以实现星形的绘制。

问题4:资源管理 

观察make_graph函数和Canvas析构函数:

(1)make_graph返回的对象在什么地方被释放?

答:make_graph返回的对象在Canvas调用析构函数时被释放。

(2)使用原始指针管理内存有何利弊? 

答:利:原始指针写起来比智能指针更加简单,且没有智能指针的额外开销,节省内存;而且原始指针可以用于更底层的内存管理(直接操控内存硬件资源),比较灵活。

弊:原始指针需要自己写new/delete,手动管理内存,容易出现因未及时delete而内存泄漏的问题。

 

四、实验任务4

源代码Toy.hpp

 1 #pragma once 
 2 #include<string>
 3 #include<vector>
 4 enum class ToyName{Judy,Nick,Gary,Fuzzby};//疯狂动物城2中的朱迪、尼克、盖瑞、狸宝 
 5 class Toy{
 6 public:
 7     Toy(const std::string &name_,const std::string &type_,const std::string &size_):name{name_},type{type_},size{size_}{} 
 8     virtual ~Toy()=default;
 9     std::string getname() const;
10     std::string gettype() const;
11     std::string getsize() const;
12     virtual void greet() const{} 
13     virtual void action() const{}
14 private:
15     std::string name;
16     std::string type;
17     std::string size;
18 }; 
19 class Judy:public Toy{
20 public:
21     Judy();
22     void greet() const;
23     void action() const;
24 };
25 class Nick:public Toy{
26 public:
27     Nick(); 
28     void greet() const; 
29     void action() const;
30 };
31 class Gary:public Toy{
32 public:
33     Gary();
34     void greet() const; 
35     void action() const;
36 };
37 class Fuzzby:public Toy{
38 public:
39     Fuzzby();
40     void greet() const; 
41     void action() const;
42 };
43 class ToyFactory{
44 public:
45     void add(const std::string &name);
46     void display() const;
47     ~ToyFactory();
48 private:
49     std::vector<Toy*> toys;
50 };
51 ToyName str_to_ToyName(const std::string &s);
52 Toy* make_toy(const std::string &name);
Toy.hpp

源代码Toy.cpp

 1 #include<string>
 2 #include<vector>
 3 #include<iostream>
 4 #include<algorithm>
 5 #include<cctype>
 6 #include"Toy.hpp"
 7 std::string Toy::getname() const{
 8     return name;
 9 }
10 std::string Toy::gettype() const{
11     return type;
12 }
13 std::string Toy::getsize() const{
14     return size;
15 }
16 Judy::Judy():Toy("Judy(朱迪)","毛绒兔子","大号"){}
17 void Judy::greet() const{
18     std::cout<<"你好,我是朱迪警官!欢迎来到动物城\n"; 
19 } 
20 void Judy::action() const{
21     std::cout<<"跳跃,并做出举手动作\n";
22 }
23 Nick::Nick():Toy("Nick(尼克)","毛绒狐狸","大号"){}
24 void Nick::greet() const{
25     std::cout<<"你好,我是尼克狐尼克!这叫智取,宝贝\n"; 
26 } 
27 void Nick::action() const{
28     std::cout<<"歪头,并做出眨眼动作\n";
29 }
30 Gary::Gary():Toy("Gary(盖瑞)","橡胶小蛇","中号"){}
31 void Gary::greet() const{
32     std::cout<<"你好,我是盖瑞!嗯,盖瑞一条蛇\n"; 
33 } 
34 void Gary::action() const{
35     std::cout<<"呈S型在平面上移动\n";
36 }
37 Fuzzby::Fuzzby():Toy("Fuzzby(狸宝)","毛绒河狸","中号"){}
38 void Fuzzby::greet() const{
39     std::cout<<"你好,我是弗兹比!我一定知无不言\n"; 
40 } 
41 void Fuzzby::action() const{
42     std::cout<<"做出啃咬手中木棍的动作\n";
43 }
44 void ToyFactory::add(const std::string &name){
45     Toy* t=make_toy(name);
46     if(t)
47         toys.push_back(t);
48 } 
49 void ToyFactory::display() const{
50     if(toys.empty()){
51         std::cout<<"非常抱歉,本玩具工厂暂时缺货中......";
52         return;
53     }
54     std::cout<<"************欢迎来到疯狂动物城玩具工厂************\n\n";
55     for(Toy* t:toys){
56         std::cout<<"|名称|:"<<t->getname()
57                  <<" |类型|:"<<t->gettype()
58                  <<" |型号|:"<<t->getsize()<<std::endl;
59         std::cout<<"|语音打招呼|:" ;
60         t->greet();
61         std::cout<<"|角色特色动作|:";
62         t->action();
63         std::cout<<"==============喜欢我就快带我回家吧=============\n\n";
64     }
65 }
66 ToyFactory::~ToyFactory(){
67     for(Toy* t:toys)
68         delete t;
69 }
70 ToyName str_to_ToyName(const std::string &s){
71     std::string t=s;
72     std::transform(s.begin(),s.end(),t.begin(),
73                    [](unsigned char c){return std::tolower(c);});
74     if(t=="judy")
75         return ToyName::Judy;
76     if(t=="nick")
77         return ToyName::Nick;
78     if(t=="gary")
79         return ToyName::Gary;
80     if(t=="fuzzby")
81         return ToyName::Fuzzby;
82     return ToyName::Judy;
83 }
84 Toy* make_toy(const std::string &name){
85     switch(str_to_ToyName(name)){
86         case ToyName::Judy: return new Judy;
87         case ToyName::Nick: return new Nick;
88         case ToyName::Gary: return new Gary;
89         case ToyName::Fuzzby: return new Fuzzby;
90         default:return nullptr;
91     }
92 }
Toy.cpp

源代码task4.cpp

 1 #include<string>
 2 #include"Toy.hpp"
 3 void test(){
 4     ToyFactory toyfactory;
 5     toyfactory.add("Judy");
 6     toyfactory.add("Nick");
 7     toyfactory.add("Gary");
 8     toyfactory.add("Fuzzby");
 9     toyfactory.display();
10 } 
11 int main(){
12     test();
13 }
task4.cpp

 运行成果展示:

image

 应用场景描述:我设计的玩具工厂灵感来自最近上映的疯狂动物城2,共有4种角色主题玩偶,在工厂中会展示出每个玩偶的名称、类型、型号、打招呼语音和特色动作,可以帮助消费者进行挑选。

类的设计:1.因为每个角色类都象征着一个玩具,符合“is-a”的关系,所以我用继承的方法,以Toy类为基类,Judy、Nick、Gary、Fuzzby为派生类,继承基类玩具类的基本接口,可以减少代码的书写量,看起来更简洁。

2.玩具工厂里会具有很多玩具,符合“has-a”的关系,所以在ToyFactory类中我选择组合方法std::vector<Toy*> toys;,表示玩具工厂包含有不同的玩具。

 

实验总结:

一、本次实验中主要了解到继承和组合的不同用法,我觉得很多时候好像可以通用,但需要结合题目所给的具体要求选择一个更好的,"is-a"关系用继承,"has-a"关系用继承。

二、在实验四需要自己完成全部的代码编写时,出现了一些细节错误:

1、t->greet()函数返回类型是void,不能直接用std::cout输出void类型的函数值;还有std::endl本身是一个函数,不能单独一行使用。

2、函数声明和定义分开写时要小心不要重定义,在Judy类的声明中我一开始写成void greet() const{},导致打招呼函数被重定义了。

3.基类的构造函数不能是虚函数,因为在创建对象时已知要创建什么类型的;而析构函数和一些功能函数要定义成虚函数,这样才能完成多态。

 

posted @ 2025-12-01 19:34  鱼籽不煮粥  阅读(6)  评论(0)    收藏  举报