记录一些作业及思考,提示:全文共计24205字。
目录
1.类 构造函数
等有空,把相关的整一下
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
class Cmytime {
private:
int hour, min, sec;
public:
Cmytime(int h = 0, int m = 0, int s = 0) {
hour = h;
min = m;
sec = s;
}
void setTime(int h, int m, int s) {
hour = h;
min = m;
sec = s;
}
void showTime() {
cout << hour << ":" << min << ":" << sec;
}
};
int main() {
int h, m, s;
cin >> h >> m >> s;
Cmytime t1(3, 2, 1), t2, t3(5), t4(t1);
t1.showTime();
cout << "\n";
t1.setTime(h, m, s);
t1.showTime();
cout << "\n";
t2.showTime();
cout << "\n";
t3.showTime();
cout << "\n";
t4.showTime();
return 0;
}
2.关于const
#include <iostream>
using namespace std;
const double PI = 3.1415926;
class Circle {
public:
double getarea() const;
Circle(float y, float r);
private:
const float pointX;
float pointY;
float radius;
};
double Circle::getarea() const {
return PI * radius * radius;
}
Circle::Circle(float y, float r) : pointX(y), pointY(0), radius(r) {
// 在构造函数的初始化列表中初始化pointX,
}
int main() {
Circle myc(1, 2);//其实我感觉这个题目出得不好
cout << myc.getarea() << endl;
return 0;
}
3.静态成员
/*程序输出为
1000's fee is 4500
2000's fee is 4500
the total number of students is 2
*/
#include<iostream>
#include <cstring>
using namespace std;
class Student {
private:
static int fee;//学费
char no[10];//学号
public:
static int total;//学生总人数
Student();
Student(char *);
static void raisefee(int i); //调整学费,不会因为对象不同而不同,共享这个数据
void show();
};
// 初始化静态成员变量,类是抽象的,不能有值
int Student::fee = 4000;
int Student::total = 0;
Student ::Student() {
strcpy(no, "1000");//字符型的赋值
total++;
}
Student ::Student(char * a) {
// no[10]=&a;
total++;
strcpy(no, a);
}
void Student ::raisefee(int i) {
fee += i;
} //调整学费
void Student ::show() {
cout << no << "'s fee is " << fee << endl;
}
int main() {
char sn[10] = "2000";
Student a;
a.raisefee(500);
a.show();
Student b(sn);
b.show();
cout << "the total number of students is " << Student::total << endl;
return 0;
}
4.友元函数
//StudybarCommentBegin
#include<iostream>
#include <math.h>
using namespace std;
// 前向声明 Rect 类
class Rect;
const double PI = 3.142;
class Circle {
int x, y, r;
public:
Circle(int a, int b, int c) {
x = a;
y = b;
r = c;
}
double area() {
return PI * r * r;
}
friend double Totalarea(Circle&c, Rect&r);
};
class Rect {
int x, y, width, length;
public:
Rect(int a, int b, int c, int d) {
x = a;
y = b;
width = c;
length = d;
}
double area() {
return width * length;
}
friend double Totalarea(Circle&c, Rect&r);
};
double Totalarea(Circle&c, Rect&r) {
return c.area() + r.area();
}
int main() {
Circle c(10, 10, 6);
Rect r(5, 5, 10, 15);
cout << Totalarea(c, r) << endl;
return 0;
}
5.类的组合
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
class Pad {
int price;
public:
Pad(int a=0) {
price=a;
cout << price << " Pad cons........"<< endl;
}
~Pad() {
cout << price << " Pad decons........"<< endl;
}
};
class Human {
private:
Pad p;
int age;
public:
Human(int a, Pad pa): p(pa), age(a) {
cout << age << " Human cons........" << endl;
}
~Human() {
cout << age << " Human decons........" << endl;
}
};
int main() {
int a, p;
cin >> a >> p;
Human h(10, 1000);
return 0;
}
/*输入10 100时,
1000 Pad cons........
10 Human cons........
1000 Pad decons........
10 Human decons........
1000 Pad decons........*/
6.深拷贝浅拷贝
#include <iostream>
using namespace std;
class Test {
int a;
int *p;
public:
Test (int aa) {
a = aa;
p = new int(41); //分配内存用new,用了new必须用delete(在析构函数里)
cout << a << " is cons....." << endl;
}
/*浅拷贝
Test (Test &aa) { //拷贝构造函数,用 已有对象 对 对象初始化 的时候使用
a = aa.a; //this->a=aa.a;
p = aa.p;
cout << a << " is copy....." << endl;
}*/
//深拷贝
Test (Test &aa) {
a = aa.a;
p = new int;
*p = *aa.p;
cout << a << " is copy....." << endl;
}
~Test () { //析构函数
delete p;
cout << a << " is decons....." << endl;
}
};
int main() {
Test t1(12);
Test t2 = t1; //拷贝(浅/深)
}
/*12 is cons.....
12 is copy.....
12 is decons.....
12 is decons.....*/
/*t1和t2指向同一个内存,析构时已经把那块内存给释放了
因此t2再去指向的时候早就已经释放了,找不到了
因此一旦涉及到内存分配,不能是是浅拷贝,一定要是深拷贝*/
7.关于继承(作用域)
#include<iostream>
using namespace std;
class Human {
private:
int age;
public:
void setAge(int a) {
age = a;
}
int getAge() {
return age;
}
void showAge() {
cout << "the human age is " << getAge() << endl;
}
};
class student : public Human {
public:
void showAge() {
cout << "the student age is " << getAge() << endl;
}
};
int main() {
Human *hp;
student jessic;
hp = &jessic;
jessic.setAge(12);
jessic.showAge(); //the student age is 12
//按照同名覆盖的原则,调用的是自己本类的showage(),
// 如果你想调用基类的showage(),写这个jessic.Human::showAge();
jessic.Human::showAge();//the human age is 12
hp->showAge(); //the human age is 12,hp指针的类型是Human类的指针,因此会调用从Human类继承过来的showAge()函数,
}
/*the student age is 12
the human age is 12
the human age is 12*/
继承机制 :
在保持已有类的特性的基础上进行
更具体、更详细的类的定义
继承的意义 :
代码重用
代码可扩充
派生类的构成
1.全盘吸收基类成员,但不包括构造函数和析构函数
2.改造基类成员:改变基类成员的访问控制,对基类成员进行同名隐藏
3.添加新的成员加入新的构造函数和析构函数(特别的初始化),派生出新的功能
注意派生类成员的访问属性
三种继承方式
公有继承
私有继承
保护继承
不同继承方式的影响主要体现在:
派生类新增成员对基类成员的访问权限
派生类对象对基类成员的访问权限
派生类成员的访问属性按访问属性划分为:
一、不可访问的成员: 从基类私有成员继承而来。派生类内成员及派生类对
象均不能访问。继续派生新类,也无法访问
二、私有成员: 新增成员中包括内嵌的其他类的对象。继承或新增而来。类
内可访。进一步派生,变成不可访问成员
三、保护成员: 继承或新增而来 。派生类内成员能访问。继续派生新类,
能成为私有或保护成员。
四、公有成员:继承或新增而来 。派生类内成员及类外均能访问。继续派生
新类,能成为新派生类的私有、保护或公有成员
同名覆盖原则——当派生类与基类中有相同成员时
一、若未强行指明,则通过派生类对象或类内使用的是派生类中的同名成员
二、如要通过派生类对象访问基类中被覆盖的同名成员,应使用基类名限定。
8.先父后子+小尾巴
#include<iostream>
using namespace std;
class Human {
int age;
public:
Human(int a ) {
age = a;
cout << age << " a person is constructed!!!!\n";
}
~Human() {
cout << age << " a person is deconstructed!!!!\n";
}
};
class Score {
int i;
public:
Score(int ii) {
i = ii;
cout << i << " a score is constructed!!!!\n";
}
~Score() {
cout << i << " a score is deconstructed!!!!\n";
}
};
class Student : public Human {
private:
int id;
Score s;
public:
Student(int i, int a, int y): Human(a), s(y) {
//有时候写类名,有时候写对象名,你需要知道为什么
//写类名是需要继承Human,写对象名是构造时候需要传参,因此要写对象的名
//因为要去调用基类构造函数,因此要留个小尾巴去搞一下基类的构造函数,但是基类构造函数又有参数
//因此,Student(int a)里面也要增加一个参数,来传给基类的参数
//如果基类构造函数带参数,那么派生类构造函数里面必须带一个“想要传入基类构造函数的参数”,而且派生类构造函数外面必须带一个小尾巴
id = i;//本类成员初始化赋值语句;
cout << id << " student is constructed!!!!\n";
}
~Student() {
cout << id << " student is deconstructed!!!!\n";
}
};
int main() {
Student jessic(1234, 12, 99);
//该派生类对象的构造顺序:先基类再成员对象再自己(human score student)
//带参,给基类传参,小尾巴非常重要
//先调用基类的构造函数,然后(创建自己的过程中)调用 本类里面 “需要用到的其他类”的构造函数,若该构造函数有参,参考上面,最后再调用本类的构造函数,
cout << "this is main function\n";
}
/*12 a person is constructed!!!!
99 a score is constructed!!!!
1234 student is constructed!!!!
this is main function
1234 student is deconstructed!!!!
99 a score is deconstructed!!!!
12 a person is deconstructed!!!!
*/
注意派生类的构造函数和析构函数
基类的构造函数不被继承,派生类中需要声明自己的构造函数。
声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成。
派生类的构造函数需要给基类的构造函数传递参数
派生类名::派生类名(基类所需形参,本类所需形参):基类名(基类参数表)
{ 本类成员初始化赋值语句;};
例如:Student(int i,int a):Human(a)
构造函数的执行顺序
-
基类构造函数
-
成员对象的构造函数
-
派生类的构造函数
析构函数的调用次序与构造函数严格相反
划重点
1 继承方式
必须掌握采用不同的继承方式而派生出来的子类中成员的访问控制权限
2 派生类的构造函数
如果基类中定义了带参数的构造函数,要求派生类须定义带参数构造函数,并为基类构造函数传递参数
9.多继承(虚拟继承解决二义性)
#include<iostream>
using namespace std;
class Human {
protected:
int id;
public:
Human() {
id = 0;
}
void setId(int i) {
id = i;
}
};
class Student: virtual public Human {
//设置虚拟继承,解决二义性的问题:在有祖父类和父类的情况下,保证孙子类只有一套东西;
//难点:虚拟继承解决什么问题,在哪里使用。
public:
void study() {
cout << "Studying ...\n";
}
};
class Teacher: virtual public Human {
//设置虚拟继承,解决二义性的问题
public:
void teach() {
cout << "Teaching...\n";
}
};
class Counsellor: public Student, public Teacher {
public:
Counsellor() {}
void work() {
cout << "working...\n";
}
};
int main() {
Counsellor ss;
ss.setId(12);
ss.study();
ss.teach();
ss.work();
}
/*Studying ...
Teaching...
working...*/
说明1: 多继承时派生类的声明
class 派生类名:继承方式1 基类1,继承方式2 基类2,...
{成员声明;}
注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承
说明2: 继承时的模糊性
Counsellor 类有两个id数据成员,那么:ss.setId(20);//设置的是哪个id的值
——名称冲突(name collision),编译会出错
解决方案:加上基类名加以说明来源
ss.Teacher::setId (20);//设置Teacher的id值
10.多继承的调用顺序
#include<iostream>
using namespace std;
class OBJ1 {
public:
OBJ1() {
cout << "OBJ1\n";
}
};
class OBJ2 {
public:
OBJ2() {
cout << "OBJ2\n";
}
};
class Base1 {
public:
Base1() {
cout << "Base1\n";
}
};
class Base2 {
public:
Base2() {
cout << "Base2\n";
}
};
class Base3 {
public:
Base3() {
cout << "Base3\n";
}
};
class Base4 {
public:
Base4() {
cout << "Base4\n";
}
};
class Derived : public Base1, virtual public Base2, public Base3, virtual public Base4 {
protected:
OBJ1 obj1;
OBJ2 obj2;
public:
Derived() : Base4(), Base3(), Base2(), Base1(), obj2(), obj1() {
cout << "Derived ok.\n";
}
int main() {
Derived aa;
cout << "主函数结束.\n";
}
/*输出:
Base2
Base4
Base1
Base3
OBJ1
OBJ2
Derived ok.
主函数结束.
顺序说明:
先调用基类构造函数:按照虚拟继承virtual public(小尾巴的先后顺序),其他三个普通继承的方式public(小尾巴的先后顺序)的顺序来调用
然后,在调用当前类里面的其他类对象的构造函数:注意这时不是按照小尾巴的顺序,而是按照当前类里面设置其他类对象的那个顺序。
最后,调用自己的构造函数
Derived ok.
*/
};
一个具有多继承的派生类对象的构造顺序:
任何基类(虚拟或非虚拟)的构造函数按照被继承的顺序构造
任何成员对象的构造函数按照声明的顺序构造
派生类构造函数
11.毕业生类
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
Student(int n, string nam, float s) {
num = n;
name = nam;
score = s;
}
void display() {
cout << "num:" << num << "\nname:" << name << endl;
}
protected:
int num;
float score;
string name;
};
class Graduate: public Student {
public:
Graduate(int n, string nam, float s, float w): Student(n, nam, s), wage(w) {}
void display() {
Student::display();
cout << "wage" << wage << endl;
}
private:
int wage;
};
int main() {
Student studl(1001, "Li", 87.5);
Graduate gradl(2001, "Wang", 98.5, 1000);
Student*pt = &studl;
pt-> display();
pt = &gradl;
pt-> display();//指针 pt 是 Student 类型的指针,指向的是 Student 对象的地址,所以在通过指针调用 display() 函数时,只会调用 Student 类的 display() 函数,
//而不会调用 Graduate 类的 display() 函数,因此 wage 没有被输出。
//如果想要输出 wage,可以将 pt 的类型改为 Graduate*,即指向 Graduate 类型的指针,
//这样通过指针调用 display() 函数时,就会调用 Graduate 类的 display() 函数,从而输出 wage。
Graduate* pt1 = &gradl;
pt1->display();
return 0;
}
/*num:1001
name:Li
num:2001
name:Wang
num:2001
name:Wang
wage1000g*/
延申
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
Student(int n, string nam, float s) {
num = n;
name = nam;
score = s;
}
void display() {
cout << "num:" << num << "\nname:" << name << "\nscore:" << score << "\n\n";
}
protected:
int num;
string name;
float score;
};
class Graduate: public Student {
public:
Graduate(int n, string nam, float s, float p): Student(n, nam, s), pay(p)
{}
void display() {
cout << "num:" << num << "\nname:" << name << "\nscore:" << score << "\npay=" << pay << endl;
}
private:
float pay;
};
class Student2 {
public:
Student2(int n, string nam, float s) {
num = n;
name = nam;
score = s;
}
virtual void display() {
cout << "num:" << num << "\nname:" << name << "\nscore:" << score << "\n\n";
}
protected:
int num;
string name;
float score;
};
class Graduate2: public Student2 {
public:
Graduate2(int n, string nam, float s, float p): Student2(n, nam, s), pay(p)
{}
void display() {
cout << "num:" << num << "\nname:" << name << "\nscore:" << score << "\npay=" << pay << endl;
}
private:
float pay;
};
int main() {
// Student stud1(1001, "Li", 87.5);
// Graduate grad1(2001, "Wang", 98.5, 563.5);
// Student *pt = &stud1;
// pt->display();
// pt = &grad1;
// pt->display();
//下面是使用虚函数的,同一接口,多种方法
Student2 stud1(1001, "Li", 87.5);
Graduate2 grad1(2001, "Wang", 98.5, 563.5);
Student2 *pt = &stud1;
pt->display();
pt = &grad1;
pt->display();
return 0;
}
/*num:1001
name:Li
score:87.5
num:2001
name:Wang
score:98.5
pay=563.5
*/
基类与派生类的转换
通过公有继承,派生类具备了基类所有的功能,即凡是基类能够解决的问题,派生类均能解决
在需要基类对象的地方,均可以用公有派生类对象来代替
具体表现在:
派生类的对象可以被赋值给基类对象
派生类的对象可以初始化基类的引用
指向基类对象的指针也可以指向派生类对象
11.例题农民类
#include<iostream>
#include <cstring>
using namespace std;
class Tree {
int age;
int height;
public:
void showTree() {
cout << "the tree's age=" << age << ", height=" << height << endl;
}
void growing(int y, int d) {
age += y;
height += d;
}
Tree(int a, int d) {
age = a;
height = d;
}
};
class Human {
int age;
char name[20];
Tree t;
public:
int getAge() {
return age;
}
char *getName() {
return name;
}
Human(int a, char *n, int ta, int td): t(ta, td) {
age = a;
strcpy(name, n);
}
};
class Farmer : public Human {
int field;
public:
Farmer(int a, char *n, int ta, int td, int fd): Human(a, n, ta, td) {
field = fd;
}
int getField() {
return field;
}
};
int main() {
Farmer f(30, "jessic", 5, 2, 100);
cout << "the farmer's name is " << f.getName() << endl;
cout << "and she is " << f.getAge() << " years old!" << endl;
cout << "she has " << f.getField() << " fields" << endl;
return 0;
}
/*the farmer's name is jessic
and she is 30 years old!
she has 100 fields*/
12.例题教授类
#include<iostream>
#include<string>
using namespace std;
class Teacher {
private:
int num;
string name;
char sex;
public:
int getNum() {
return num;
};
string getName() {
return name;
};
char getSex() {
return sex;
};
Teacher(int n, string na, char s): num(n), name(na), sex(s) {};
};
class BirthDate {
private:
int year;
int month;
int day;
public:
int getY() {
return year;
};
int getM() {
return month;
};
int getD() {
return day ;
};
BirthDate(int y, int m, int d): year(y), month(m), day(d) {};
};
class Professor: public Teacher {
private:
BirthDate birthday;
public:
Professor(int n, string na, char s, int y, int m, int d): Teacher(n, na, s), birthday(y, m, d) {};
void setBirthday(int y, int m, int d) {
birthday = BirthDate(y, m, d);
}
void display() {
cout << "工号:" << getNum() << endl;
cout << "姓名:" << getName() << endl;
cout << "性别:" << getSex() << endl;
cout << "生日:" << birthday.getY() << "-" << birthday.getM() << "-" << birthday.getD() << endl;
}
};
int main() {
Professor prof1(2022308, "cyh", 'M', 1999, 9, 12);
prof1.display();
prof1.setBirthday(2023, 4, 15);
cout << endl << "修改后的信息:" << endl;
prof1.display();
return 0;
}
14.多态
面向对象程序设计的基本特点
◆ 1.抽象
数据抽象:描述某类对象的属性或状态(对象相互区别的物理量
方法抽象:描述某类对象的共有的行为特征或具有的功能
抽象通过类来实现
◆2. 封装
把对象的属性和行为结合成一个独立的单元,并尽可能隐蔽对象的
内部细节。
1. 一个清楚的边界。对象所有成员都在这个边界内
2. 一个或多个接口。外部通过接口访问对象的内部成员
◆ 3.继承,特殊类具有普通类的所有特性和行为
◆ 4.多态,是类之间的一种关系
多态性是指发出同样消息被不同对象接收时有可能导致完全不同行为
多态的实现:
1. 函数重载
2. 运算符重载
3. 虚函数,动态的多态
14.运算符重载
方法一:运算符重载函数作为类的友元函数进行使用
1. 如果需要重载一个运算符,使之能够用于操作某类对象的私有成员,可以此将运算符重载为该类的友元函数
2. 函数的形参代表依自左至右次序排列的各操作数
例如,friend Complex operator +(Complex & , Complex & );
方法二:运算符重载函数作为类的成员函数
重载为类成员函数时 参数个数=原操作数个数-1 (后置++、--除外)
双目运算符、单目运算符的重载设计均有所不同
例如,c3=c1+c2;//等价于c3=c1. operator +(c2);
注意
运算符重载函数本身就是函数,那么在函数体内部可以做任何事情的
从不违背常规思维的角度来说,不要让运算符重载函数来做与其重载的符号意义上完全不相符的工作
大多数系统预定义运算符都能重载,除以下:. 、 :: 、 *(当乘法运算符时可重载)、 sizeof 、 ?:
重载时的注意事项:
不能改变优先级;
不能改变结合性;
不能改变运算符所需操作数的个数;
重载后,可按这些运算符的表达方式使用
小结
运算符重载是对已有的运算符赋予多重含义
必要性:C++中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)
实现机制:将指定的运算表达式转化为对运算符重载函数的调用,运算对象转化为运算符重载函数的实参。编译系统对重载运算符的选择,遵循函数重载的选择原则
实现方式:1. 类的友元函数2. 类的成员函数
以前做的cmycomplex有空再整理一下,要记得哦
#include<string>
using namespace std;
#include <iostream>
class Complex {
public :
Complex(double a = 0.0, double b = 0.0) {
r = a;
i = b;
}
void display() {
cout << "Real=" << r << '\t' << "Image=" << i << '\n';
}
Complex operator +(Complex & t);
private:
double r, i ;
};
Complex Complex::operator +(Complex & t) {
Complex result(r + t.r, i + t.i);
return result;
}
int main() {
Complex c1(3.2, 3.2), c2(1.2, 1.2), c3;
c3 = c1 + c2;
c3.display();
return 0;
}
另外
#include <iostream>
using namespace std;
class incount {
int c1, c2;
public:
incount(int a = 0, int b = 0) {
c1 = a;
c2 = b;
}
void show() {
cout << "the object of incount : c1=" << c1 << '\t' << "c2=" << c2 << endl;
}
friend istream & operator>>(istream &, incount &);
};
istream & operator>>(istream &my, incount &a) {
my >> a.c1 >> a.c2;
return my;
}
int main() {
incount x1, x2;
cin >> x1;
cin >> x2;
x1.show ();
x2.show ();
return 0;
}
/*1 2 3 4
the object of incount : c1=1 c2=2
the object of incount : c1=3 c2=4*/
15.虚函数、抽象类
virtual指示编译器,函数调用pt->display()要在运行时确定所要调用的函数,即要对该调用进行动态联编。因此,程序在运行时根据指针pt所指向的实际对象,调用该对象的成员函数。
实现在基类定义派生类所拥有的通用接口,而在派生类定义具体的实现方法,即常说的“同一接口,多种方法”,实现越来越复杂的程序。
#include<iostream>
using namespace std;
class Figure {
public:
Figure(double a, double b) {
x = a;
y = b;
}
virtual void area() {
cout << "No area computation defined\n";
}
//virtual void area()=0; //纯虚函数
protected:
double x, y;
};
class Triangle: public Figure {
public:
Triangle(double a, double b): Figure(a, b) { }
void area() {
cout << "Triangle : " << x*y * 0.5 << endl;
}
};
class Square: public Figure {
public:
Square(double a, double b): Figure(a, b) { }
void area() {
cout << "Square :" << x*y << endl;
}
};
class Circle: public Figure {
public:
Circle(double a): Figure(a, a) { }
void area() {
cout << "Circle: " << x*x * 3.1416 << endl;
}
};
int main() {
Figure *p;
Triangle t(10.0, 6.0);
Square s(10.0, 6.0);
Circle c(10.0);
p = &t;
p->area();
p = &s;
p->area();
p = &c;
p->area();
return 0;
}
/*Triangle : 30
Square :60
Circle: 314.16*/
Figure是一个基类,表示具有封闭图形的东西
从Figure派生出三角形、矩形和圆,这个类等级中的基类Figure体现了一个抽象的概念,在Figure中定义一个求面积的函数显然是无意义的,
可以将其说明为虚函数,为派生类提供一个公共的接口,各派生类根据所表示的图形的不同重定义这些虚函数,以提供求面积的各自版本。
引入纯虚函数的概念
带有纯虚函数的类称为抽象类
class 类名
{virtual 类型 函数名(参数表)=0; //纯虚函数...}
抽象类作用
对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现
抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个
继承层次结构中,保证派生类具有要求的行为
注意
抽象类只能作为基类来使用
不能声明抽象类的对象
构造函数不能是虚函数,析构函数可以是虚函数
虚函数是动态绑定的基础,是非静态的成员函数
在类的声明中,在函数原型之前写virtual
virtual 只用来说明类声明中的原型,不能用在函数实现时
具有继承性,基类中声明虚函数,派生类中无论是否说明,同原型函数都自
动为虚函数
本质:不是重载声明而是覆盖。
调用方式:通过基类指针或引用,执行时会根据指针指向的对象的类,决定
调用哪个函数
例题,bo
#include<iostream>
#include<string>
using namespace std;
class B0 {
public:
virtual void display( ) = 0; //纯虚函数成员
};
class B1: public B0 {
public:
void display() {
cout << "B1::display()" << endl;
}
};
class D1: public B1 {
public:
void display() {
cout << "D1::display()" << endl;
}
};
void fun(B0 *ptr) {
ptr->display();
}
int main() {
B0 *p;
B1 b1;
D1 d1;
p = &b1;
fun(p);
p = &d1;
fun(p);
return 0;
}
/*B1::display()
D1::display()*/
例题,奶奶类
#include<iostream>
#include<string>
using namespace std;
class Grandam {
public:
virtual void introduce_self() {
cout << "I am grandam." << endl;
}
Grandam() {
cout << "Grandam\n";
}
virtual ~Grandam() {
cout << "~Grandam." << endl;
}
};
class Mother: public Grandam {
public:
void introduce_self() {
cout << "I am mother." << endl;
}
Mother() {
cout << "Mother\n";
}
~Mother() {
cout << "~Mother()." << endl;
}
};
class Daughter: public Mother {
public:
void introduce_self() {
cout << "I am daughter." << endl;
}
};
void test0() {
//这里原先是没有构造析构函数的,是后面加上去的,所以显得有些复杂
Grandam *ptr;
Grandam g;
Mother m;
Daughter d;
ptr = &g;
ptr->introduce_self();
ptr = &m;
ptr->introduce_self();
ptr = &d;
ptr->introduce_self();
/*Grandam
Grandam
Mother
Grandam
Mother
I am grandam.
I am mother.
I am daughter.
~Mother().
~Grandam.
~Mother().
~Grandam.
~Grandam.
*/
}
void test1() {
Grandam *f;
f = new Mother;
delete f;
/*Grandam
Mother
~Mother().
~Grandam.
*/
}
int main() {
// test0();
test1();
return 0;
}
小结
1. 多态性——不同对象收到相同的消息时,产生不同的动作
2. 从实现的角度,多态可划分为两类:
编译时的多态(函数重载)
运行时的多态(虚函数)
3. 纯虚函数,说明在基类中,无定义,须赋初值为0。
4. 一个类至少有一个纯虚函数,则该类为抽象类。
5. 抽象类只能作为其他类的基类来使用,不能建立抽象类对象
例一
//
//程序运行结果为:
//
//1321 samsung message send
#include <iostream>
#include<string>
using namespace std;
class Telephone {
protected :
int number;
public:
Telephone() {
number = 1234;
}
Telephone(int n) {
number = n;
}
void showNumber() {
cout << "my phone number is: " << number << endl;
}
void call() {
cout << "the phone is calling \n";
}
};
class Mobile: public Telephone {
char *type;
int cost;
public:
Mobile();
Mobile(char *t, int c, int n);
void message() {
cout << number << "\t" << type << "\tmessage send\n ";
}
friend int operator<(Mobile &x1, Mobile &x2);
};
Mobile::Mobile() {
type = "nullptr";
cost = 3600;
}
Mobile::Mobile(char *t, int c, int n) : type(t), cost(c), Telephone(n) {
}
int operator<(Mobile &x1, Mobile &x2) {
if (x1.cost < x2.cost) {
return 1;
} else {
return 0;
}
}
int main() {
Mobile m1;
Mobile m2("samsung", 3500, 1321);
if (m1 < m2)
m1.message();
else
m2.message();
return 0;
}
/*1321 samsung message send*/
例二.动物类
#include <iostream>
using namespace std;
class Animal {
public:
virtual void bark() const {
cout << "Animal::bark()" << endl;
}
};
class Cat: public Animal {
public:
void bark() const {
cout << "miaomiao" << endl;
//在子类中,如果一个函数被声明为虚函数,则其重载函数会自动成为虚函数,无需再次声明为虚函数
}
};
class Dog: public Animal {
public:
virtual void bark() const {
cout << "wangwang" << endl;
}
};
class Duck: public Animal {
public:
void bark() {//没有 const 限定符,没有标记为虚函数。
cout << "gaga" << endl;
//当我们通过Animal类型的指针或引用来调用Duck实例的bark()函数时,实际上是调用了Animal类中的bark()函数,而不是Duck类中的。
//Duck类中的bark()函数并没有重载Animal类中的虚函数Animal::bark(),而是定义了一个新的函数bark(),
//该函数与Animal类中的非虚函数void bark() const有相同的函数名但是不同的函数签名(即不同的参数列表或者常属性不同)
//在Duck类中,这个函数bark()是一个新的、非虚的函数,而不是覆盖Animal类中的虚函数。
//它是非虚函数。在调用时,如果使用基类Animal类型的指针或引用指向Duck类对象,
//那么它将会调用Animal类中的虚函数bark()而不是Duck类中的非虚函数bark()。
}
};
void AnimalBark_Ref(Animal & animal) {//接受一个Animal类型的引用参数
animal.bark();
//Duck的bark()函数并没有被调用。这是因为Duck类的bark()函数并没有标记为virtual,
//所以当我们传递Duck类型的实例作为参数时,Animal类中的bark()函数被调用了。
}
void AnimalBark_Pointer(Animal * animal) {//接受一个Animal类型的指针参数
animal->bark();
//因为通过指针来调用虚函数时,会根据指针所指向的对象的实际类型来确定调用哪个函数
}
void AnimalBark_var(Animal animal) {//接受一个Animal类型的变量参数
animal.bark();
//AnimalBark_var()函数中的animal参数是按值传递的,这意味着它接受的参数实际上是Animal类的一个拷贝,而不是实际的Cat、Dog或Duck类型
}
int main() {
Cat c;
Dog d;
Duck du;
cout << "This sizeof(Animal):" << sizeof(Animal) << endl;
cout << "This sizeof(c):" << sizeof(c) << endl;
cout << "This sizeof(d):" << sizeof(d) << endl;
cout << "This sizeof(du):" << sizeof(du) << endl;
cout << "This sizeof(int *):" << sizeof(int *) << endl;
cout << "--by Reference:" << endl;
AnimalBark_Ref(c);
AnimalBark_Ref(d);
AnimalBark_Ref(du);
cout << "--by Pointer:" << endl;
AnimalBark_Pointer(&c);
AnimalBark_Pointer(&d);
AnimalBark_Pointer(&du);
cout << "--by Variable:" << endl;
AnimalBark_var(c);
AnimalBark_var(d);
AnimalBark_var(du);
return 0;
}
/*This sizeof(Animal):4
This sizeof(c):4
This sizeof(d):4
This sizeof(du):4
This sizeof(int *):4
--by Reference:
miaomiao
wangwang
Animal::bark()
--by Pointer:
miaomiao
wangwang
Animal::bark()
--by Variable:
Animal::bark()
Animal::bark()
Animal::bark()
*/
例三汽车类
#include<iostream>
#include<cstring>
using namespace std;
class vehicle {
protected:
char carname[20]; //车名
char name[20]; //车主名
char date[20]; //购买日期
public:
vehicle(char cn[20], char na[20], char d[20]) { //汽车的构造函数
strcpy(carname, cn);
strcpy(name, na);
strcpy(date, d);
}
virtual void show() = 0;//纯虚函数
};
class car: public vehicle { //派生类小汽车的定义
protected:
int zws; //座位数
public:
car(char cn[20], char na[20], char d[20], int zw): vehicle(cn, na, d) {
zws = zw;
}
void show() {
cout << name << " " << carname << " " << date << " " << zws << endl;
}
};
class truck: public vehicle { //派生类货车的定义
protected:
double dw; //吨位
public:
truck(char cn[20], char na[20], char d[20], double z): vehicle(cn, na, d) {
dw = z;
}
void show() {
cout << name << " " << carname << " " << date << " " << dw << endl;
}
};
int main() {
car A("Benz", "Kaka", "2016-5-5", 5);
truck B("Dongfeng", "Yua", "2015-1-20", 5.5);
vehicle *p;
p = &A;
p->show();
p = &B;
p->show();
return 0;
}
/*Kaka Benz 2016-5-5 5
Yua Dongfeng 2015-1-20 5.5*/
例四形状
#include<iostream>
#include<math.h>
using namespace std;
class shape {
//StudybarCommentEnd
public:
virtual double area() = 0;
friend double total(shape *s[], int n) ;
};
class triangle: public shape {
private:
int width, length;
public:
triangle(int c = 10, int d = 5) {
width = c;
length = d;
}
double area() {
return width * length * 0.5;
}
};
class rectangle: public shape {
private:
int width, length;
public:
rectangle(int c = 10, int d = 5) {
width = c;
length = d;
}
double area() {
return width * length;
}
};
double total(shape *s[], int n) {
double sum = 0.0;
for (int i = 0; i < n; i++)
sum = sum + s[i]->area();
return sum;
}
int main() {
shape *sp[2] ;
int x, y;
cin >> x >> y;
triangle one(x, y);
rectangle two;
sp[0] = &one;
sp[1] = &two;
cout << "the total area is " << total(sp, 2) << endl;
return 0;
}
例五电器
#include <iostream>
using namespace std;
class IElectricalEquipment {
public:
virtual void PowerOn() = 0; //每种电器都能打开
virtual void PowerOff() = 0; //每种电器都能关闭
virtual ~IElectricalEquipment() {
cout << "\nThis is Virtual ~IElectricalEquipment()";
};
};
class Switch {
public:
IElectricalEquipment *Elec;
virtual void On() = 0;
virtual void Off() = 0;
virtual ~Switch() {
cout << "\nThis is Virtual ~Switch()";
}
};
class Fan : public IElectricalEquipment {
public:
void PowerOn() {
cout << "风扇打开" << endl;
}
void PowerOff() {
cout << "风扇关闭" << endl;
}
};
class Light : public IElectricalEquipment {
public:
void PowerOn() {
cout << "电灯打开" << endl;
}
void PowerOff() {
cout << "电灯关闭" << endl;
}
};
class FancySwitch : public Switch {
public:
void On() {
cout << "Fancy Switch On" << endl;
Elec->PowerOn();
}
void Off() {
cout << "Fancy Switch Off" << endl;
Elec->PowerOff();
}
};
class NormalSwitch : public Switch {
public:
void On() {
cout << "Normal Switch On" << endl;
Elec->PowerOn();
}
void Off() {
cout << "Normal Switch Off" << endl;
Elec->PowerOff();
}
};
int main(int argc, char** argv) {
//构造电器设备:风扇,开关
IElectricalEquipment *fan = new Fan();
IElectricalEquipment *light = new Light();
//构造开关
Switch * fancySwitch = new FancySwitch();
Switch * normalSwitch = new NormalSwitch();
//把风扇连接到时尚开关
fancySwitch->Elec = fan;
//开关连接到电器,那么当开关打开或关闭时电器应该打开/关闭
fancySwitch->On();
fancySwitch->Off();
//把电灯连接到普通开关
normalSwitch->Elec = light;
normalSwitch->On(); //打开电灯
normalSwitch->Off(); //关闭电灯
//把电灯连接到时尚开关
fancySwitch->Elec = light;
fancySwitch->On();
fancySwitch->Off();
delete fan;
delete light;
delete fancySwitch;
delete normalSwitch;
return 0;
}
/*Fancy Switch On
风扇打开
Fancy Switch Off
风扇关闭
Normal Switch On
电灯打开
Normal Switch Off
电灯关闭
Fancy Switch On
电灯打开
Fancy Switch Off
电灯关闭
This is Virtual ~IElectricalEquipment()
This is Virtual ~IElectricalEquipment()
This is Virtual ~Switch()
This is Virtual ~Switch()*/
例六 宠物
#include <iostream>
using namespace std;//提交时注销
class Animal {
protected:
int weight; // 重量
static int count; // 动物个数
public:
Animal(int w) : weight(w) {
count++;
}
int getCount() {
return count;
}
virtual void spark() = 0; // 纯虚函数
friend int totalWeight(Animal& a1, Animal& a2);
};
int Animal::count = 0; // 初始化静态成员count
class Dog : public Animal {
public:
Dog(int w) : Animal(w) {}
void spark() {
cout << "the dog is wa wa wa" << endl;
}
};
class Cat : public Animal {
public:
Cat(int w) : Animal(w) {}
void spark() {
cout << "the cat is miao miao" << endl;
}
};
int totalWeight(Animal& a1, Animal& a2) {
return a1.weight + a2.weight;
}
int main() {
Dog d(2);
d.spark();
Cat c(3);
c.spark();
cout << "共有 " << c.getCount() << " 个动物" << endl;
cout << "动物共 " << totalWeight(d, c) << " kg" << endl;
return 0;
}
/*the dog is wa wa wa
the cat is miao miao
共有 2 个动物
动物共 5 kg*/
其他笔记
下面是看黑马视频时的一些笔记。
继承
继承的好处:可以减少重复的代码
class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。
继承的语法:class 子类 : 继承方式 父类
继承方式一共有三种:
-
公共继承
-
保护继承
-
私有继承
父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
总结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
//继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
-
访问子类同名成员 直接访问即可
-
访问父类同名成员 需要加作用域
cout << "Son下的m_A = " << s.m_A << endl; cout << "Base下的m_A = " << s.Base::m_A << endl;
-
子类对象可以直接访问到子类中同名成员
-
子类对象加作用域可以访问到父类同名成员
-
当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
静态成员和非静态成员出现同名,处理方式一致
-
访问子类同名成员 直接访问即可
-
访问父类同名成员 需要加作用域
//通过对象访问 cout << "通过对象访问: " << endl; Son s; cout << "Son 下 m_A = " << s.m_A << endl; cout << "Base 下 m_A = " << s.Base::m_A << endl;
//通过类名访问 cout << "通过类名访问: " << endl; cout << "Son 下 m_A = " << Son::m_A << endl; cout << "Base 下 m_A = " << Son::Base::m_A << endl;
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)
C++允许一个类继承多个类
语法:class 子类 :继承方式 父类1 , 继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
菱形继承概念:(事实上多继承使用少,菱形继承更少)
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承
菱形继承问题:
-
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
-
草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
总结:
-
菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
-
利用虚继承可以解决菱形继承问题
//继承前加virtual关键字后,变为虚继承 //此时公共的父类Animal称为虚基类
多态分为两类
-
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
-
动态多态: 派生类和虚函数实现运行时多态
//动态多态满足条件: //1、有继承关系 //2、子类重写父类中的虚函数(函数名 参数列表 返回值类型 完全相同) //动态多态使用:父类指针或引用指向子类对象
静态多态和动态多态区别:
-
静态多态的函数地址早绑定 - 编译阶段确定函数地址
-
动态多态的函数地址晚绑定 - 运行阶段确定函数地址
//Speak函数就是虚函数 //函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
virtual void speak() { cout << "动物在说话" << endl; }
//1个字节。加上virtual变成四个字节
多态的优点:
-
代码组织结构清晰
-
可读性强
-
利于前期和后期的扩展以及维护
开闭原则:对拓展进行开发,对修改进行关闭
总结:C++开发提倡利用多态设计程序架构,因为多态优点很多
前面的类和const等,以及后面的文件异常等内容都没有写,就先这样吧,有空再整理,祝考试顺利