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]