目录
类的继承
类的继承的出现,是由于实际问题的需要。比如定义了一个男人类和女人类,这2个类之间有属性或者函数的重叠,那代码就有点冗余,所以为了解决这个问题,就将这些共有的属性和函数抽取出来,形成一个新类(比如这2类都属于人,就可以抽取人的共有属性),而这个类的属性和函数能够被男人类和女人类继承,也就是能直接使用这些属性和函数。
我们称这个新类为父类,父类中的所有public成员和函数都可以被子类所使用,private成员和函数不能被继承。
#include "pch.h"
#include <iostream>
#include "string.h"
class Tutorial
{
public:
char name[16];
char author[16];
public:
void ShowInfo()
{
printf("name:%s, author:%s \n", name, author);
}
private:
int my_private; //这个成员是不能被子类所使用的
};
class VideoTutorial : public Tutorial
{
public:
void Play();//播放
public:
char url[128]; //在线观看的url地址
int visits; //播放量
};
int main()
{
VideoTutorial test_inherit;
strcpy(test_inherit.name, "test_cpp"); //访问父类的成员和函数
strcpy(test_inherit.author, "nick");
test_inherit.ShowInfo();
strcpy(test_inherit.url, "http://1123.com"); //访问自己类中的成员
std::cout << "Hello World!\n";
return 0;
}
看了上面的例子,有没有发现public和private的功能有一种情况没有涵盖到,那就是变量能被继承,但不能被外部访问这种情况。那么为了能满足这种需求,就新增了一种访问修饰符protected,制定protected规则如下:
(1)该成员不能被外部访问,同private;
(2)该成员可以被子类继承,同public。
#include "pch.h"
#include <iostream>
#include "string.h"
class Tutorial
{
public:
char name[16];
char author[16];
public:
void ShowInfo()
{
printf("name:%s, author:%s \n", name, author);
}
private:
int my_private; //这个成员是不能被子类所使用的
protected:
int my_protected = 9;
};
class VideoTutorial : public Tutorial
{
public:
void Play()
{
printf("my_protected:%d\n", my_protected); //protected能被继承,但不能被外部访问
}//播放
public:
char url[128]; //在线观看的url地址
int visits; //播放量
};
int main()
{
VideoTutorial test_inherit;
strcpy(test_inherit.name, "test_cpp"); //访问父类的成员和函数
strcpy(test_inherit.author, "nick");
test_inherit.ShowInfo();
strcpy(test_inherit.url, "http://1123.com"); //访问自己类中的成员
test_inherit.Play();
std::cout << "Hello World!\n";
return 0;
}
既然public的成员被继承了,那么就会在编译的时候直接编译,那private既然没有被继承,是否在编译时就不会被编译呢?
其实private也是会被编译的,也能出现在内存中,只不过编译器限制了程序不能访问private成员。
虚拟继承和虚拟函数
函数重写
Child类中重写了父类的Test函数,最终的打印结果就是This is Child.
#include "pch.h"
#include <iostream>
#include "string.h"
class Parent
{
public:
void Test()
{
printf("This is Parent.\n");
}
};
class Child : public Parent
{
public:
void Test()
{
printf("This is Child.\n");
}
};
int main()
{
Child a;
a.Test();
std::cout << "Hello World!\n";
return 0;
}
如果我想在父类某个函数的基础上再增加些功能,该怎么做呢?
在调用父函数的时候前面加上作用域就行了,也就是告诉这个函数你是Parent的。具体情况请看下面的例子。
#include "pch.h"
#include <iostream>
#include "string.h"
class Parent
{
public:
void Test()
{
printf("This is Parent.\n");
}
};
class Child : public Parent
{
public:
void Test()
{
Parent::Test();
printf("This is Child.\n");
}
};
int main()
{
Child a;
a.Test();
std::cout << "Hello World!\n";
return 0;
}
父类指针指向子类对象
可以将一个父类指针指向一个子类的对象,这是完全允许的。例如Tree* p = new AppleTree();
其实在创建子类对象时,是先编译父类的成员,然后再编译子类的成员。
#include "pch.h"
#include <iostream>
#include "string.h"
class Parent
{
public:
void Test()
{
printf("This is Parent.\n");
}
public:
int a = 1;
int b = 2;
};
class Child : public Parent
{
public:
void Test()
{
Parent::Test();
printf("This is Child.\n");
}
public:
int x = 3;
int y = 4;
};
int main()
{
Child a;
a.Test();
Parent* b = &a;
b->a; //这里b只能访问到父类中的成员,子类中的成员他是访问不到的
b->Test(); //还是一样的只能访问到父类的成员
std::cout << "Hello World!\n";
return 0;
}
小结:正如上面代码所示,Parent* b = &a;该代码通过父类指针指向一个子类对象,该父类指针只能访问父类自己的成员,而子类的成员b是访问不了的。
从b->Test();中可以看出,它访问的是父类的Test函数,变相也表明了子类中重写的Test函数并没有覆盖覆盖的Test函数,只不过在子类中重写过后,就只能访问子类的Test函数而已。
虚拟继承
当一个成员函数需要子类重写,那么在父类应该将其申明为virtual。这样你就可以调用子类中的重写的函数了。
注意:
(1)只需要在父类中将函数声明为virtual,子类自动地就是virtual了。
(2)即将被重写的函数添加virtual,是一条应该遵守的编码习惯。
#include "pch.h"
#include <iostream>
#include "string.h"
class Parent
{
public:
virtual void Test()
{
printf("This is Parent.\n");
}
public:
int a = 1;
int b = 2;
};
class Child : public Parent
{
public:
void Test()
{
printf("This is Child.\n");
}
public:
int x = 3;
int y = 4;
};
int main()
{
Child a;
a.Test();
Parent* b = &a;
b->a;
b->Test();
return 0;
}
结果为:
This is Child.
This is Child.
小结:如果你想被重写,而且其实是想调用重写的函数,那么就加virtual。因为加了virtual就只能调用子类中重写的那个函数了。
继承:构造和析构
(1)总是现有父亲,再有儿子。
(2)构造和析构总是相反的。
#include "pch.h"
#include <iostream>
#include "string.h"
class Parent
{
public:
Parent()
{
printf("Parent:创建\n");
}
~Parent()
{
printf("Parent:销毁\n");
}
};
class Child : public Parent
{
public:
Child()
{
printf("Child:创建\n");
}
~Child()
{
printf("Child:销毁\n");
}
};
int main()
{
{
Child a;
}
return 0;
}
结果为:
当父类有多个构造函数时,可以显示的调用某个构造函数(一个类在被创建对象时,只能选择一个构造函数和一个析构函数)。比如下面的例子
#include "pch.h"
#include <iostream>
#include "string.h"
class Parent
{
public:
Parent()
{
printf("Parent:创建\n");
}
Parent(int x, int y)
{
printf("Parent:有参数\n");
this->x = x;
this->y = y;
}
~Parent()
{
printf("Parent:销毁\n");
}
private:
int x, y;
};
class Child : public Parent
{
public:
Child():Parent(1,2)
{
printf("Child:创建\n");
}
~Child()
{
printf("Child:销毁\n");
}
};
int main()
{
{
Child a;
}
return 0;
}
结果为:
virtual析构函数
为什么析构函数需要加virtual呢?
比如Parent* p = new Child(); delete p; //这里到底调用的是谁的析构函数?
#include "pch.h"
#include <iostream>
#include "string.h"
class Parent
{
public:
Parent()
{
printf("Parent:创建\n");
}
Parent(int x, int y)
{
printf("Parent:有参数\n");
this->x = x;
this->y = y;
}
virtual ~Parent()
{
printf("Parent:销毁\n");
}
private:
int x, y;
};
class Child : public Parent
{
public:
Child():Parent(1,2)
{
printf("Child:创建\n");
}
~Child()
{
printf("Child:销毁\n");
}
};
int main()
{
Parent* p = new Child();
delete p;
return 0;
}
结果为:
小结:如果不加virtual,那么程序不会报错。但真实情况是,我真正想调用子类中的析构函数,不然程序析构的对象有问题,以后面程序会出现不可预知的错误。
类的大小和virtual关键字的影响
(1)类的大小由成员变量决定。
类的大小成员函数的个数无关,即使一个类有10000个成员函数,对它所占的内存空间是没有影响的。
(2)但是,如果一个成员函数被声明virtual,那类的大小会有些微的变化。(这个变化由编译器决定,一般是增加4个字节)
(3)构造函数不能加virtual。
多重继承
#include "pch.h"
#include <iostream>
#include "string.h"
class Father
{
public:
int a, b;
void Test()
{
printf("Father");
}
};
class Mother
{
public:
int c, d;
void Test()
{
printf("Mother");
}
};
class Child : public Father, public Mother
{
public:
int e;
};
int main()
{
Child my_child;
my_child.a = 1;
my_child.b = 2;
my_child.c = 3;
my_child.d = 4;
my_child.e = 5;
//my_child.Test();// 这里会直接报错,因为编译器不知道该调用哪个Test
return 0;
}
小结:正如上面例子所示,我在Father和Mother类中都写了Test函数,在主函数中调用Test时,编译器就傻了,它不知道该调用谁,所以直接给你报错。
因此可以得出结论,在实际使用中,尽量不要使用多重继承,万一成员名重复就很麻烦了。