1、构造函数是什么
答:类里面定义一个函数, 和类名一样, 这样在我们生成一个对象之后,就会默认调用这个函数,初始化这个类。
子类B继承父类A的情况, 当你调用子类的对象,构造函数会怎么样运行?
-
1、创建子类对象时,程序首先调用父类的构造函数,然后再调用子类的构造函数
- 父类构造函数负责初始化继承的数据成员
- 子类构造函数主要用于初始化新增的数据成员
-
2、子类构造函数总是需要调用一个父类构造函数;默认就是调用父类无参数构造函数,如果父类没有这个构造函数,程序会默认生成一个无参数构造的,如果父类又 有参数构造函数,但是没有无参数的构造函数,就会报错。
- 所以当父类没有无参数的构造函数时,就必须明确指明调用哪一个构造函数,需要用到初始化列表这个方式。
Person(int age, char *address): age(age), address(address)
2、final用于禁止被重写和继承,确保自己的唯一,是最后一个。
- 1 、禁止类自己被继承
final 用于禁止 虚函数被重写和 类被继承class Person final {}
,然后这个Person就不可以被继承了, - 2、 一个类, 自己继承了别人后,禁止别人继承自己。
class Person {
};
class Student final: public Person {
};
class Middel_School_Student : public Student{ // 失败,因为父类Student是final修饰的
};
就是意味着继承了Person。但是Student不能被别人继承。
- 3、final用于禁止函数被重写
class Person {
virtual void run() final { // 表示该方法是最终方法,无法被重写
}
};
class Student : public Person{
// 编译错误,方法无法被重写。
void run(){
}
};
final总是放在定义类的后面,和方法的后面, 类是 class A final {}. 和 class A :final public B {}.方法是
class Person {
virtual void C() final { // 表示该方法是最终方法,无法被重写
}
};
也是在后面,但是在()前。
3、 友元 friend ,目的是为了使用私有属性的函数和变量。 类似一个白名单
如果一个类A里面有私有的方法和属性,当类生成一个对象 a ,肯定是无法调用这些方法和属性的,如果在类A里面,加上 友元函数 或者友元类 之后,这个友元函数内部可以无视private的影响。 友元类里也可以无视private的影响。
5、重载
C++中,一个类里,如果有两个函数名一样的函数,但是入参不同,就属于重载
4、关于 继承
1、继承是什么
继承就是把一个类里的变量和方法 “继承” 给另一个类。如果B类 继承 A类,我们就叫B类是子类, A类是B类的父类,B类有A类的所有功能和方法(先这样粗浅的理解)。
2、继承的写法
继承有三种方式 pubulic prtected private 和类的封装时候的三种方式一样。不写默认就是private
class 子类名:[继承方式]父类名{
子类新增加的成员
};
2、继承的用法
- 子类继承父类之后,拥有了一套和父类一样的变量和方法,即可以使用父类的方法和变量的,如果想要使用父类中继承下来的变量和函数。就在子类中使用 this-> 【方法和变量】 ,就可以调用。
3、继承需要注意
-
类本身是一个抽象的概念,类本身不占用内存空间;它仅仅是一个蓝图或模板,用于创建对象。只有当类被实例化为对象时,才会分配内存空间。每个对象都有其独立的地址。所以当我们用类生成对象时,每个对象都是一个独立的个体。
-
比如:如果C++中,一个类B,继承于类A, 则类A的对象中的变量var_a, 在类B的对象中也会有,如果类B生成一个对象obj_b, 那么这个对象obj_b里面也有var_a。 这个时候,使用类A的方法调用var_a和 使用类B的方法调用var_a,都可以调用到var_a, 并且这两个var_a是同一个,即地址是一样的。
#include <iostream>
using namespace std;
class A {
public:
int var_a = 4;
void get_addr() {
cout << "IN A : var_a ==" << var_a << "\n";
cout << "IN A : var_a address ==" << &var_a;
}
};
class B : public A {
public:
int var_b;
void get_addr1() {
cout << "IN B : var_a ==" << var_a << "\n";
cout << "IN B : var_a address ==" << &(this->var_a) << "\n";
}
void get_addr2() {
A::get_addr();
}
};
int main() {
B obj_b;
obj_b.var_a = 10;
//打印在对象obj_b里, 调用类B变量 var_a的值和地址
obj_b.get_addr1();
cout<<"================================================="<<"\n";
//打印在对象obj_b里, 调用类A变量 var_a的值和地址
obj_b.get_addr2();
return 0;
}
- 4、继承之后的调用(等重写,多态知道了之后再看)
继承之后,子类的对象,自然可以调用父类的一切(在子类的函数中,调用从父类继承而来的成员变量或成员函数,直接使用this),那么父类的对象,可以调用子类么 ?
-
子类对象可以调用父类的方法:
当一个类(子类)继承自另一个类(父类)时,子类对象可以调用从父类继承来的所有公有(public)和保护(protected)成员(包括方法和属性)。如果父类的方法或属性是私有的(private),则子类无法直接访问它们,但可以通过父类的公有或保护方法来间接访问。 -
父类对象不能调用子类的方法:
父类对象只能调用自己定义的公有和保护成员,不能调用子类特有的方法或属性。这是因为父类对象在内存中的布局并不包含子类的任何额外成员或方法。换句话说,父类对象对子类扩展的部分一无所知。 -
多态性:
虽然父类对象不能直接调用子类的方法,但可以通过指向子类对象的父类指针或引用来实现多态性。这通常是通过在父类中定义虚函数(virtual function)来实现的。当子类重写了父类的虚函数时,通过父类指针或引用调用的将是子类版本的函数。
示例代码:
#include <iostream>
class Parent {
public:
void parentMethod() {
std::cout << "Parent method called" << std::endl;
}
virtual void virtualMethod() {
std::cout << "Parent virtual method called" << std::endl;
}
};
class Child : public Parent {
public:
void childMethod() {
std::cout << "Child method called" << std::endl;
}
void virtualMethod() override {
std::cout << "Child virtual method called" << std::endl;
}
};
int main() {
Parent parent;
Child child;
// 子类对象调用父类方法
child.parentMethod();
// 子类对象调用自己的方法
child.childMethod();
// 通过父类指针调用子类重写的虚函数
Parent* parentPtr = &child;
parentPtr->virtualMethod(); // 输出:Child virtual method called
// 父类对象不能调用子类方法
// parent.childMethod(); // 编译错误
return 0;
}
在这个例子中:
child
对象可以调用parentMethod()
和childMethod()
。- 通过父类指针
parentPtr
调用virtualMethod()
时,实际调用的是子类的virtualMethod()
,展示了多态性。 - 尝试通过父类对象
parent
调用childMethod()
会导致编译错误,因为父类对象不知道子类的任何扩展。
总结:子类对象可以调用父类的公有和保护成员,但父类对象不能调用子类的成员。多态性允许通过父类指针或引用来间接调用子类的重写方法。
4 、 那么 ,是不是通过父类指针,调用子类的成员,一定是父类也拥有的成员才可以
是的,通过父类指针调用子类的成员时,只能调用父类也拥有的成员。这是因为多态是基于父类指针或引用来实现的,编译器在编译时期会根据指针或引用的类型来确定要调用的成员函数。如果子类有额外的成员,且这些成员在父类中没有对应的声明,那么通过父类指针或引用是无法调用这些成员的。
5、重写
重写就是子类继承父类之后,子类中,定义了父类相同名字的函数。
重写之后,子类直接调用,就是调用的子类自己的函数。 父类中所有的同名的函数(为啥说所有,是因为父类中可能有重载的函数,所以会有同名的函数 ),都被子类的同名函数覆盖了,即父类中所有的同名函数,子类都不会继承,子类只用自己重写的那个,如果你想在子类中调用这个被你重写的,就需要用父类名去调用 【父类名】::【父类的重写的方法】 。
#include <iostream>
using namespace std;
class A {
public:
void display() {
cout << "A::display()" << "\n";
}
// 1、重载一个display函数
void display(int num) {
cout << "A::display(int num)" << "\n";
}
};
class B : public A {
public:
// 2、重写一个display. 重写之后,自然而然的,父类的void display() 方法被顶替了,并且 void display(int num)也被顶替了(重载的全被顶替了)
// 综上 父类的两个display都不不能直接调用了。
void display() {
cout << "B::display()" << "\n";
//3、 this->display(100); // 编译失败,原因如上面解释的一样 ,但是如果想使用父类的函数也是可以的, 方法1,需要用父类,类A来调用方式如下
cout <<"---------------------------------------------------------"<< "\n";
A::display();
A::display(100);
}
};
int main() {
class B b;
b.display(); // 调用子类的函数
//4、同2表示的一样
// b.display(100); // 编译失败
return 0;
}
6、 多层继承和多继承
- 多层继承(Hierarchical Inheritance)就是 继承了 很多层,比如爷爷继承给父亲,父亲继承给儿子。
- 多继承(Multiple Inheritance)就是 同时继承了多个, 比如 父亲母亲,同时继承给儿子。
7、 多态
1、多态就是,有多个相同名字的函数,当调用时,会根据调用时的方式不同,调用不同的函数。同样是说话,对有的人态度好,对有的人态度差。
2、C++中通过visual来实现多态。
-
通过virtual可以实现真正的多态
虚函数可以在父类的指针指向子类对象的前提下,通过父类的指针调用子类的成员函数
这种技术让父类的指针或引用具备了多种形态,这就是所谓的多态 -
最终形成的功能:
- 如果父类指针指向的是一个父类对象,则调用父类的函数
- 如果父类指针指向的是一个子类对象,则调用子类的函数
3、怎么使用visual
- 定义虚函数非常简单,只需要在函数声明前,加上 virtual 关键字即可
- 在父类的函数上添加 virtual 关键字,可使子类的同名函数也变成虚函数,可以子类父类都写上visual
#include<iostream>
using namespace std;
class Father {
public:
virtual void show() {
cout << "father show" << endl;
}
};
class Children : public Father {
public:
virtual void show() {
cout << "children show" << endl;
}
};
int main() {
Father *father = new Father();
father->show(); // 调用父类的show函数
Children *children = new Children();
children->show(); // 调用子类的show函数
Father *p = new Children();
p->show(); // 调用哪个类中的show函数? 就看父类的指针,指向了哪个对象,指向的是子类,所以调用的就是子类的函数
return 0;
}
总结: 多态需要以下三个条件
1、有继承关系
2、子类重写了父类的函数
3、定义一个父类的指针变量,指向子类调用子类,指向父类,调用父类。