C++基础讲解第五期 代码中也有对应知识注释,别忘看,一起学习!
- 一、继承(接第四期)
- 1. const修饰成员函数
- 2. 同名函数
- 3. 继承中的static关键字
- 4. 继承中类型兼容性原则
- 5. 多继承(使用参数初始化列表)
- (1)多继承的概念
- (2)多继承的语法
- (3)多继承中的二义性问题
- (4)虚继承
- 6. 向上转型
- 二、练习
一、继承(接第四期)
1. const修饰成员函数
总结:
常成员函数修饰只读变量,在定义和声明处都需要加上const
#include<iostream>
using namespace std;
class Student
{
private:
char *m_name;
int m_age;
float m_score;
public:
Student(char* name, int age, float socre);
void show();
char* Getname() const;
int Getage() const;
float Getscore() const;
};
Student::Student(char* name, int age, float socre):m_name(name),m_age(age),m_score(socre)
{
}
void Student::show()
{
cout<<this->m_name<<"的年龄是:"<<this->m_age<<"分数是:"<<this->m_score<<endl;
}
char* Student::Getname() const
{
return m_name;
}
int Student::Getage() const //常成员函数必须声明和定义处都需要加上const关键字
{
return m_age;
}
float Student::Getscore() const
{
//m_socre++ 常成员函数修饰只读变量
return m_score;
}
int main()
{
Student s1("小明",18,100);
s1.show();
return 0;
}
2. 同名函数
总结:
- 当基类与派生类有同名的成员函数时,当我们使用派生类对象去调用同名函数时, 默认调用派生类的同名成员函数。
- 想要使用基类的同名函数,需要加上作用域,显式调用。
#include<iostream>
using namespace std;
class TestA
{
private:
int m_a;
public:
void show()
{
cout <<"This is TetsA show"<< endl;
}
};
class TestB:public TestA
{
private:
int m_a;
public:
void show()
{
cout <<"This is TetsB show"<< endl;
}
};
int main()
{
TestB tb;
tb.show();//当同名时,默认调用派生类成员函数
tb.TestA::show();//当同名时,想要调用基类同名函数,需要加上作用域,显式调用
return 0;
}
结果:
This is TetsB show
This is TetsA show
3. 继承中的static关键字
如果在基类中定义了静态成员变量,则该静态成员变量将被所有派生类共享
#include<iostream>
using namespace std;
class Person
{
private:
public:
static int count; //静态成员变量,所有对象共享一个静态成员变量
Person()
{
count ++;
}
};
int Person::count = 0; //静态变量只能在类外初始化
class Student:public Person//继承了Person类中的count,并于Person类共享
{
};
int main()
{
Student s1;
Student s2;
Person p1;
Person p2;
cout<<Person::count<<endl; //派生类和基类共享一个静态成员变量
cout<<Student::count<<endl;
return 0;
}
结果:
4
4
4. 继承中类型兼容性原则
概念:
类型兼容性原则是指在需要基类的地方,对象的地方,都可以使用公有派生类的对象代替,通过公有继承,派生类得到了基类中除构造函数析构函数的所有成员,这样,公有派生类就具备了基类所有的功能。
凡是基类可以完成的事,公有派生类都可以解决
类型兼容性:
- 子类对象可以当作父类对象使用,子类对象是一种特殊的父类。
父类能做的事情,子类对象都可以做。- 父类指针可以直接指向子类对象
基类引用直接引用派生类对象
代码说明:
#include<iostream>
using namespace std;
class Parent
{
public:
Parent()
{
cout<<"parent 无参构造函数"<<endl;
}
Parent(const Parent &p)
{
cout<<"parent 的拷贝构造函数"<<endl;
this->m_a = p.m_a;
this->m_b = p.m_b;
}
void SetAB(int a,int b)
{
this->m_a = a;
this->m_b = b;
}
void printP()
{
cout<<"m_a= "<< m_a <<endl;
cout<<"m_b= "<< m_b <<endl;
}
protected:
int m_a;
int m_b;
};
class Child:public Parent
{
private:
int m_c;
public:
void SetC(int c)
{
this->m_c = c;
}
void printC()
{
cout << "m_c= " << m_c << endl;
}
};
int main()
{
Parent p1;
p1.SetAB(1,2);
p1.printP();
Child c;
c.SetAB(10,20);
c.SetC(30);
c.printC();
Parent *p = &c; //基类指针可以指向派生类对象,但是派生类指针不能指向基类对象,这里不会调用Parent的构造函数,因为只是定义了Parent的指针,没有为其分配空间
p->SetAB(7,8);
//p->SetC(7); 父类指针不能调用派生类成员
//Child *pc = &p1 子类指针不能指向基类对象
Parent &p2 = c;//基类引用可以直接引用派生类对象
p2.printP();
Parent p3(c); //派生类对象可以用来初始化基类对象
p3.printP();
return 0;
}
画图解释
Parent基类只在堆上开发了m_a,m_b的空间,而Child派生类,继承了Parent类,Child派生类包含m_a, m_b, m_c, 比基类多了一个m_c,他们的首地址都是一样的。
- 当基类指针指向派生类时, 它只有m_a,m_b的访问权限,不会访问到m_c. 所有基类指针可以指向派生类对象。
- 当派生类指针指向基类对象时,它可以访问m_c, 但是基类对象没有开发m_c的空间,所有会发生越界。所以派生类指针不能指向基类对象。
- 引用同理
- 派生类对象可以初始化基类对象,同理。
5. 多继承(使用参数初始化列表)
(1)多继承的概念
派生类中只有一个基类,称为单继承
除此之外C++支持多继承,一个派生类可以有多个基类
多继承容易使代码逻辑复杂,思维混乱,一直备受争议,在中小型项目中很少使用,JAVA/C#/PHP干脆取消了多继承
(2)多继承的语法
#include<iostream>
using namespace std;
class Airplane
{
protected:
int high;
public:
Airplane(int h) : high(h)
{
cout<<"飞机的构造函数"<<endl;
//high = 100;
}
void show()
{
cout<<"飞机的高度:"<< high << endl;
}
};
class Ship
{
protected:
int speed;
public:
Ship(int s) : speed(s)
{
cout<<"轮船的构造函数"<<endl;
//speed = 50;
}
void show()
{
cout<<"轮船的速度:"<< speed << endl;
}
};
class WaterPlane :public Airplane, public Ship
{
public:
WaterPlane(int h, int s, int d): Airplane(h), Ship(s), deep(d) //参数初始化列表使用,还有部分使用在第三期
{
cout<<"水上飞机的构造函数"<<endl;
}
private:
int deep;
};
int main()
{
WaterPlane w(100, 50, 10);//先构造基类,再构造派生类,基类构造的顺序与继承的顺序相同
w.Airplane::show();
return 0;
}
(3)多继承中的二义性问题
#include<iostream>
using namespace std;
class TestA
{
public:
int a;
};
class TestB : public TestA
{
public:
int b;
};
class TestC : public TestA
{
public:
int c;
};
class TestD : public TestB , public TestC
{
public:
int d;
};
int main()
{
TestD td;
cout<<sizeof(td) <<endl;
//td.a;产生了二义性,不知道是哪个B类还是C类里的a
td.TestB::a;//加上作用域即可
return 0;
}
(4)虚继承
虚继承的目的:是让某个类做出声明,承诺愿意共享它的基类,这个被共享的基类就被称为虚基类,在这种机制下,不论虚基类在继承体系中出现多少次,在派生类中只会包含一份虚基类的成员
#include<iostream>
using namespace std;
class TestA
{
public:
int a;
};
class TestB :virtual public TestA
{
public:
int b;
};
class TestC :virtual public TestA
{
public:
int c;
};
class TestD : public TestB , public TestC
{
public:
int d;
};
int main()
{
TestD td;
cout<<sizeof(td) <<endl;
cout<<&td<<endl;
cout<<&td.a<<endl;
cout<<&td.b<<endl;
cout<<&td.c<<endl;
cout<<&td.d<<endl;
return 0;
}
结果:
40
0x7ffd616a7600
0x7ffd616a7620
0x7ffd616a7608
0x7ffd616a7618
0x7ffd616a761c
需要解释下,为什么td占40个字节
因为虚基类带有虚基类指针
td的首地址是00,后面接有虚基类指针8个字节,然后是b,占4个字节
然后再接虚基类指针占8个字节,然后是c,占4个字节,然后是d,占4个字节
然后是a,占4个字节,再带上结构体所要求的字节对齐,在b后面补了4个字节,和a后面补了4个字节
对于虚基类的作用在于,a只存在了一次,如果像多继承二义性所写,应该有两个a,因为B类继承了A类中的a,C类也继承了A类中的a,因为是虚继承,所以只有一个a
画图说明
6. 向上转型
只能将派生类对象赋值给基类对象,派生类引用赋值给基类引用,派生类指针赋值给基类指针 ,这就叫向上转型。
二、练习
使用C++实现单链表的功能(头插法)
我就写个简单的实现
#include<iostream>
using namespace std;
class HNode
{
public:
int num;
HNode *next;
};
class HLink
{
HNode *head;
int length;
public:
void CreateLink();
HNode* CreateNode();
void insertNode(HNode* newNode);
void disPlay();
};
void HLink::CreateLink()
{
head = new HNode;
head->num = 0;
head->next = nullptr;
}
HNode* HLink::CreateNode()
{
HNode* node = new HNode;
if(node == nullptr)
{
cout<<"create node error" <<endl;
}
return node;
}
void HLink::insertNode(HNode* newNode)
{
if(head->next == nullptr)
{
head->next = newNode;
newNode->next = nullptr;
}
else
{
newNode->next = head->next;
head->next = newNode;
}
}
void HLink::disPlay()
{
HNode *ptr;
ptr = head->next;
while(ptr == nullptr)
{
cout<<ptr->num<<endl;
ptr = ptr->next;
}
}
int main()
{
HLink link;
link.CreateLink();
for(int i = 0; i < 5; i++)
{
HNode *node = link.CreateNode();
node->num = i;
link.insertNode(node);
}
link.disPlay();
return 0;
}
总结在使用C++时,会有的困扰
当我使用C习惯之后,在写链表的时候会去考虑二级指针,以及链表变了怎么才能反映出来。
其实,在我们实现类的成员函数时,在成员函数里面去对成员变量进行改变,当我们去实例化这个类的时候,这个成员函数和成员变量才真正的产出,比如上面的insertNode(),在它的里面直接用了成员变量head,但是当我实例化出list的时候,调用list.insertNode()的时候,使用的就是list对象的head,改变它,就时有效的,生命周期一直到这个对象销毁。不像C中,生命周期是它的本函数内,所以要传二级指针。