杀鸡焉用牛刀 发表于 2024-10-14 10:17:11

C++继续

一、继续的基本概念

C++中的继续是面向对象编程的核心概念之一,它答应一个类(子类)继续另一个类(父类)的属性和举动。通过继续,子类可以重用父类的代码,还可以添加新的成员,从而实当代码的复用和扩展。
1.语法

     class 子类名 : 继续方式 父类名 {
     子类成员;
     }
2.子类和父类

父类(基类):被继续的类,它包罗了可以被子类继续的成员变量和方法。
子类(派生类):通过继续父类创建出来的新类,它不仅包罗了父类的全部成员,还可以添加新的成员。
3.子类继续的特点



[*]子类包罗了从父类继续过来的成员和自己新增的成员。
[*]子类可以利用父类的方法,还可以对父类的成员函数举行重写。
子类构造函数不能直接初始化继续的成员,必须利用父类的方法。具体地说,子类构造函数必须调用父类构造函数。
创建子类对象时,步伐起首调用父类构造函数,然后再调用子类构造函数。父类构造函数负责初始化继续的数据成员,子类构造函数主要用于初始化新增的数据成员。子类的构造函数总是调用一个父类构造函数。可以利用初始化列表语法指明要利用的父类构造函数,否则将利用默认的父类构造函数。
①子类构造函数的要点:


[*]创建子类对象时,步伐会先创建父类对象
[*]子类构造函数应通过初始化列表将父类信息传递给父类构造函数
[*]子类构造函数应初始化子类新增的数据成员
②哪些不能被继续:
父类的私有成员可以被继续,但子类访问不到,只能通过父类的公有和掩护方法访问。构造函数、析构函数、赋值运算符以及友元关系都不能被继续。
4.构造和析构顺序

构造顺序:先构造父类,再构造子类
析构顺序:先析构子类,再析构父类
class Employee {
private:
        string name;
        double salary;

public:
        Employee(string n,double s)
                :name(n),salary(s){}
        void display() {
                cout << name << " " << salary << " ";
        }

};

class Manager :public Employee {
private:
        string department;

public:
        Manager(string n,double s,string d)
                :Employee(n,s),department(d){}

        void print() {
                display();
                cout << department << endl;
        }

}; Manager m1("Mike",35000,"IT");
m1.print();
//输出Mike 35000 IT  
 
二、继续方式与类关系

1.继续方式



[*]公有继续:父类的公有成员和掩护成员在子类中保持原有状态,而私有成员不可被子类访问。
[*]掩护继续:父类的公有成员和掩护成员在子类中变为掩护成员,而私有成员不可被子类访问。
[*]私有继续:父类的公有成员和掩护成员在子类中变为私有成员,而私有成员不可被子类访问。
https://i-blog.csdnimg.cn/direct/46be78d8f97c42c9b8d5db2145c882a3.png
利用私有继续时,第二代子类将不能利用父类的方法,这是因为父类的公有方法在第一代子类中变成了私有方法;利用掩护继续时,父类的公有方法在第一代子类中变成受掩护的,因此第二代子类可以利用它们。
2.类与类之间的关系

①is-a关系(继续关系):
is-a关系就是继续关系,表现了面向对象编程中的继续性,是实当代码复用和扩展性的紧张本领,且不同的继续方式会影响子类对父类成员的访问权限。
例如,一个Student类可以继续自Person类,表示门生是一类人,具有人的基本属性和举动,同时还可以有门生特有的属性和方法。
class Person {
protected:
        string name;
        int age;

public:
        Person(string n,int a)
                :name(n),age(a) {}

        void introduce() const {
                cout << name << " " << age << endl;
        }

};

class Student :public Person {
private:
        string major;

public:
        Student(string n,int a,string m)
                :Person(n,a),major(m) {}

        void study() const {
                cout << major << endl;
        }

}; Student s1("Tom", 20, "cs");
s1.introduce();
s1.study();
//输出
Tom 20
cs ②has-a关系(组合关系):
has-a关系表示一个类(整体类)包罗另一个类(部分类)的对象作为其成员变量,它们是整体与部分的关系,而且生命周期通常是一致的,整体类负责构建和烧毁部分类的对象。
通常,应利用包罗来建立has-a关系;假如新类需要访问原有类的掩护成员,或需要重新定义虚函数,则应利用私有继续。
 例如,一个Car类可能包罗一个Engine类的对象,表示汽车有一个发动机。
class Engine {
public:
        void start() const {
                cout << "Engine started." << endl;
        }

};

class Car {
public:
        Engine engine;

        void drive() const {
                cout << "Driving the car." << endl;
                engine.start();
        }

}; Car my_car;
my_car.drive();
//输出
Driving the car.
Engine started. ③use-a关系(依赖关系):
use-a关系表示一个类(利用者类)利用另一个类(被利用者类)的对象作为其函数的参数,或者在函数中创建和利用被利用者类的对象。这种关系表现了类之间的依赖,但利用者类并不负责被利用者类对象的生命周期管理。
例如,一个Police类的方法可能需要接收一个Car范例的参数来举行超速检查,这时Police类和Car类之间就是use-a关系。
class Car {
public:
        int speed;
       
        Car(int s = 0) :speed(s) {}

        void displaySpeed() const {
                cout << "The car's speed is " << speed << " km/h." << endl;
        }

};

class Police {
public:
        void checkSpeed(const Car& car) const {
                car.displaySpeed();
                if (car.speed > 100) {
                        cout << "Speeding. Please slow down." << endl;
                }
        }

}; Car my_car(105);
Police p;
p.checkSpeed(my_car);
//输出
The car's speed is 105 km/h.
Speeding. Please slow down.  
 
三、子类和父类之间的特殊关系

1.赋值转换

 在继续中,子类对象可以赋给父类对象、父类指针或父类的引用,但反之不行,父类对象不能赋值给子类对象。
父类指针可以在不举行显示范例转换地环境下指向子类对象,父类引用也可以在不举行显示范例转换地环境下引用子类对象。
父类的指针或引用可以通过欺凌范例转换赋值给子类的指针或引用,但这种转换必须是安全的,即父类指针确实指向子类对象。
class Person {
private:
        int age;
        string name;
};

class Student :public Person{
private:
        int id;
}; Student student;
Person person;
person = student;
Person* p1 = &student;
Person& p2 = student; 此外,函数参数为父类的引用对象或指针时,函数将答应子类对象的传入。
void func1(const Person& p1) {
}
void func2(Person* p1) {
} Student student;
Person person;
func1(student);
func1(person);
func2(&student);
func2(&person); 2.拷贝构造函数和赋值运算符

当一个子类对象被复制或赋值时,假如没有显式指定怎样举行,编译器会自动调用父类的拷贝构造函数和赋值运算符。
class Base {
private:
        int data;

public:
        Base(int value = 0)
                :data(value) {}

        Base(const Base& other) {
                cout << "拷贝构造函数调用" << endl;
                data = other.data;
        }

        Base& operator=(const Base& other) {
                cout << "赋值运算符调用" << endl;
                data = other.data;
                return *this;
        }

};

class Derived :public Base {
private:
        int number;

public:
        Derived(int d = 0, int n = 0)
                :Base(d), number(n) {}

};
Derived d1(10, 20);
Derived d2(d1);//拷贝构造函数调用
Derived d3;
d3 = d1;//赋值运算符调用 3.同名成员处理方式

当子类与父类出现同名的成员时,子类对象可以直接访问到子类的同名成员,通过添加作用域可以访问到父类中的同名成员。同名静态成员也是如此。
class Base {
public:
        int data = 10;
        static void print() {
                cout << "10" << endl;
        }

};

class Derived :public Base {
public:
        int data = 20;
        static void print() {
                cout << "20" << endl;
        }
}; Derived d;
cout << d.data << endl;//20
cout << d.Base::data << endl;//10
d.print();//20
d.Base::print();//10
 
 
四、继续和动态内存分配

1.父类不利用new,子类利用new

此时子类需要显式定义拷贝构造函数和赋值运算符,且子类的拷贝构造函数中需要显式调用父类的拷贝构造函数;子类的赋值运算符也要显式调用父类的赋值运算符,并加上作用域。
class Person {
protected:
        string name;
        int age;

public:
        Person(string n = "", int a = 0)
                :name(n), age(a) {
        }

};

class Student :public Person {
private:
        string* major;

public:
        Student(string n = "", int a = 0, string m = "")
                :Person(n, a) {
                major = new string(m);
        }

        ~Student()
        {
                delete major;
        }

        Student(const Student& s) :Person(s) {
                major = new string(*s.major);
        }

        Student& operator=(const Student& s) {
                if (this != &s) {
                        delete major;
                        major = new string(*s.major);
                        Person::operator=(s);
                }
                return *this;
        }

        void print() {
                cout << name << " " << age << " " << *major << endl;
        }

};
2.父类利用new,子类不利用new

此时父类需要显式定义拷贝构造函数和赋值运算符。
class Person {
protected:
        string name;
        int* age;

public:
        Person(string n = "", int a = 0) {
                name = n;
                age = new int(a);
        }

        ~Person() {
                delete age;
        }

        Person(const Person& p) {
                name = p.name;
                age = new int(*p.age);
        }

        Person& operator=(const Person& p) {
                if (this->age != p.age) {
                        delete age;
                  name = p.name;
                  age = new int(*p.age);
                }

                return *this;
        }

};

class Student :public Person {
private:
        string major;

public:
        Student(string n = "", int a = 0 , string m = "")
                :Person(n,a) {
                major = m;
        }

        void print() {
                cout << name << " " << *age << " " << major << endl;
        }

};
3.父类和子类都利用new

此时父类和子类都需要显式定义拷贝构造函数和赋值运算符,且子类的拷贝构造函数中需要显式调用父类的拷贝构造函数;子类的赋值运算符也要显式调用父类的赋值运算符,并加上作用域。
class Person {
protected:
        string name;
        int* age;

public:
        Person(string n = "", int a = 0) {
                name = n;
                age = new int(a);
        }

        ~Person() {
                delete age;
        }

        Person(const Person& p) {
                name = p.name;
                age = new int(*p.age);
        }

        Person& operator=(const Person& p) {
                if (this->age != p.age) {
                        delete age;
                        name = p.name;
                        age = new int(*p.age);
                }
                return *this;
        }

};

class Student :public Person {
private:
        string* major;

public:
        Student(string n = "", int a = 0, string m = "")
                :Person(n, a) {
                major = new string(m);
        }

        ~Student() {
                delete major;
        }

        Student(const Student& s):Person(s) {
                major = new string(*s.major);
        }

        Student& operator=(const Student& s) {
                if (this != &s) {
                        delete major;
                        major = new string(*s.major);
                        Person::operator=(s);
                }
                return *this;
        }

        void print() {
                cout << name << " " << *age << " " << *major << endl;
        }

};
测试:
Student s1("Mike", 20, "cs");
Student s2(s1);
Student s3;
s3 = s1;
s1 = s1 = s1;
s1.print();//Mike 20 cs
s2.print();//Mike 20 cs
s3.print();//Mike 20 cs 总结:子类和父类哪个利用new,哪个就要显式定义拷贝构造函数和赋值运算符。当子类显式定义时,子类要显式调用父类的拷贝构造函数和赋值运算符。
 
 
五、多继续

1.多继续的基本概念

C++答应一个类同时继续多个类。这种特性可以看作是单继续的扩展,使得子类可以或许从多个父类中获取属性和方法。
①语法:
class 子类名 : 继续方式 父类1,继续方式 父类2 { }
②多继续的构造函数执行顺序:
先执行父类的构造函数,再执行子类本身的构造函数。父类构造函数的执行顺序取决于定义子类时父类出现的顺序,与子类构造函数中成员初始化列表的顺序无关。
析构函数的执行顺序与构造函数的执行顺序相反。
③定名冲突:
当多个父类中存在同名成员时,直接访问该成员会产生定名冲突。此时需要加上作用域来区分。
class Base1 {
public:
        int value;

        Base1() {
                value = 10;
        }
};

class Base2 {
public:
        int value;

        Base2() {
                value = 20;
        }
};

class Derived :public Base1, public Base2 {
}; Derived d1;
//cout << d1.value << endl;//错误
cout << d1.Base1::value << endl;//10
cout << d1.Base2::value << endl;//20 ④多继续的优缺点:
虽然多继续提供了灵活性,但它也增长了代码的复杂度。在C++实际开辟中不建议利用多继续。
2.菱形继续

菱形继续是指两个子类继续同一个父类,而这两个子类又被一个共同的子类继续。这种继续结构会导致数据冗余和二义性问题,因为子类会从两个不同的路径继续雷同的数据。
C++引入虚继续来办理这个问题。利用虚继续,即使两个子类都继续了某个父类,该父类也只会在最终的子类中出现一次。要实现虚继续,需要在父类的继续声明前加上virtual关键字。
class A {
public:
        int value;

        A() {
                value = 10;
        }
};

class B1 :virtual public A {
};

class B2 :virtual public A {
};

class C :public B1, public B2 {
}; C c1;
cout << c1.value << endl;//10  
 
 
 
 
 
 
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: C++继续