1、类与类之间的关系有哪些?
与类之间的关系分为纵向和横向两种:
纵向就是继承;横向包括:依赖、关联、聚合和组合。(这里不进行解释,详解链接:https://blog.csdn.net/u014694510/article/details/88316605.
2、什么是继承?继承有什么作用?
所谓继承就是从先辈出得到属性和行为特征。类的继承就是新的类从已有类那里得到已有的特征;从另一个角度来看,类的继承和派生机制使程序员无需修改已有类,只需在此类的基础上,通过少量代码或修改少量代码的方法得到新的类,从而很好的解决了代码重用的问题。由已有类产生新类时,新类便包含了已有类的特征,同时也可以加入自己的新特征。已有类被称为基类或者父类,产生的新类被称为派生类或者子类。
3、继承有哪些分类?
派生类的继承方式有私有继承(private),公有继承(public),保护继承(protect)
4、基类成员在派生类的访问属性是怎样的?
基类中的成员 | 在公有派生类的访问属性 | 在私有派生类中的访问属性 | 在保护派生类中的访问属性 |
---|---|---|---|
私有成员 | 不可直接访问 | 不可直接访问 | 不可直接访问 |
公有成员 | 公有 | 私有 | 保护 |
保护成员 | 保护 | 私有 | 保护 |
5、基类的成员函数都能被继承吗?
不是。基类中的构造函数和析构函数不能被继承,在派生类中需要定义新的构造函数和析构函数,私有成员不能被继承。
6、派生类对基类成员的访问规则是怎样的?
内部访问:由派生类中新增的成员函数对基类继承来的成员的访问。
对象访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问。
私有继承的访问规则:
基类中的成员 | 私有成员 | 公有成员 | 保护成员 |
---|---|---|---|
内部访问 | 不可访问 | 可访问 | 可访问 |
对象访问 | 不可访问 | 不可访问 | 不可访问 |
公有继承的访问规则:
基类中的成员 | 私有成员 | 公有成员 | 保护成员 |
---|---|---|---|
内部访问 | 不可访问 | 可访问 | 可访问 |
对象访问 | 不可访问 | 可访问 | 不可访问 |
保护成员的访问规则:
基类中的成员 | 私有成员 | 公有成员 | 保护成员 |
---|---|---|---|
内部访问 | 不可访问 | 可访问 | 可访问 |
对象访问 | 不可访问 | 不可访问 | 不可访问 |
先看个简单的代码了解一下:
class Person
{
public:
void work()
{
cout << "work()" << endl;
}
void eat()
{
cout << "eat()" << endl;
}
protected:
private:
string _name;
int _age;
string _sex;
};
class Student :public Person
{
public:
void show()
{
cout << _name << endl;
cout << _age << endl;
cout << _sex << endl;
}
};
这里会报错:
但如果把这些私有属性改为保护或者公有的话就不会报错:
class Person
{
public:
void work()
{
cout << "work()" << endl;
}
void eat()
{
cout << "eat()" << endl;
}
protected:
string _name;
int _age;
string _sex;
private:
};
class Student :public Person
{
public:
void show()
{
cout << _name << endl;
cout << _age << endl;
cout << _sex << endl;
}
};
这就是因为私有成员不可以被继承。
总结:子类继承的父类成员,在自身中的权限不能高于继承权限()
再看这个代码:
class Person
{
public:
Person(string name,int age,string sex,string wife=string())
{
_name=name;
_age=age;
_sex=sex;
_wife=wife;
}
~Person()
{
cout<<"~Person()"<<endl;
}
void work()
{
cout << "work()" << endl;
}
void eat()
{
cout << "eat()" << endl;
}
protected:
string _name;
int _age;
string _sex;
private:
string wife;
};
class Student :public Person
{
public:
void show()
{
cout << _name << endl;
cout << _age << endl;
cout << _sex << endl;
}
};
int main()
{
Student s;//报错,无法引用默认的构造函数
s.show();
}
这样改正之后错误消失(给派生类定义构造函数):
class Student :public Person
{
public:
Student(string name, int age, string sex, string num, string wife = string())
:Person(name,age,sex,wife)
{
cout << "Student(string name, int age, string sex, string num, string wife = string())" << endl;
_num = num;
}
void show()
{
cout << _name << endl;
cout << _age << endl;
cout << _sex << endl;
cout << _num << endl;
}
private:
string _num;
};
int main()
{
Student s("zjh",11,"man","1111");
s.show();
}
错误原因:C++规定,当基类的构造函数没有参数,或没有显示定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数。当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给构造函数的途径。
7、派生类构造函数和析构函数的执行顺序是怎样的?
通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数;当撤销派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。
8、派生类构造函数的参数列表是怎样构成的?
Student(string name, int age, string sex, string num, string wife = string())
:Person(name,age,sex,wife)
从上面列出的派生类Student
构造函数首行中可以看到,派生类构造函数名后边括号内的总参数表中包括了参数的类型和参数名,而基类构造函数参数表中只有参数名而不包括参数类型,因为在这里不是定义基类构造函数,而是调用基类构造函数
(这里的调用和在主函数中的调用是一样的,只是为了说明这部分参数需要用基类的构造函数初始化),因此这些参数是实参而不是形参。它们可以是派生类构造函数总参数列表中的参数,也可以是常量和全局变量。
9、如果有多层继承,参数列表又怎样构成?
这里我们给上边的Student
类再写一个派生类来看看
class High_Student :public Student
{
public:
High_Student(string name, int age, string sex, string num, string high, string wife = string())
:Student(name, age, sex, num,wife)
{
cout << "High_Student()" << endl;
_high = high;
}
~High_Student()
{
cout<<"High_Student()"<<endl;
}
protected:
private:
string _high;
};
int main()
{
High_Student a = { "zjh",21,"nan","1010","sss","aaa"};
a.eat();
return 0;
}
我们可以看出,这里依旧是类名后边是总参数列表,但是冒号后边是前两个父类的实参,我们可以将Student(name, age, sex, num,wife)
理解为嵌套调用,即执行该语句之后,还是先调用Person
类的构造函数初始化name、sge、sex、wife
四个参数,再调用Student
类的构造函数初始化num
.
10、C++中的隐藏是怎样的?
还是先看代码:
class Base
{
public:
void fun1(int a)
{
cout << "Base::void fun1()" << endl;
}
protected:
private:
int _a;
};
class Derive :public Base
{
public:
void fun1()
{
fun1(10);//这里会报错
cout << "Derive::void fun1()"<<endl;
}
void fun1(int a,int b)
{
cout << "Derive::void fun1(int a)" << endl;
}
protected:
private:
int _b;
};
int main()
{
Derive d;
d.fun1(10);//这里会报错,显示没有匹配的函数
}
问题:明明子类继承了父类只有一个参数的构造函数,为什么还不能用?
C++中规定,当父类和子类有同名参数时,子类会隐藏父类的同名函数,导致子类对象和子类成员函数不能调用。
解决办法:d.Base::fun1();
Base::fun1()
给函数加作用域
接下来我们再看一段代码,通过这段代码引出虚基类的概念:
class Base
{
public:
Base()
{
a = 5;
cout << "Base()" << endl;
}
protected:
int a;
};
class Base1 :public Base
{
public:
int b1;
Base1()
{
a = a + 10;
cout << "Base1()" << endl;
}
};
class Base2 :public Base
{
public:
int b2;
Base2()
{
a = a + 20;
cout << "Base2()" << endl;
}
};
class Derive :public Base1, public Base2
{
public:
int d;
Derive()
{
cout<<"Derive a="<<a<<endl;//会报错
}
};
int main()
{
Derive d;
return 0;
}
上边报错语句需要改成这样才能成功运行:
cout << "Base1::a=" << Base1::a << endl;
cout << "Base2::a=" << Base2::a << endl;
执行结果如下:
11、为什么这里加上作用域就能成功运行?
在上述程序中,类Derive
是从类Base1和Base2
公有派生来的,而类Base1和Base2又
都是从类Base
公有派生而来的。虽然在类Base1和Base2
中没有定义数据成员a
,但是它们分别都从类Base
继承了数据成员a
,这样在类Base1和Base2
中同时存在着数据成员a
,它们都是类Base
成员的复制。但是类Base1和Base2
中的数据成员a
具有不同的存储单元,可以存放不同的数据。在程序中可以通过类Base1和Base2
去调用基类Base
的构造函数,分别对类Base1和Base2
的数据成员a
初始化。因此在Derive
的构造函数中输出a
的值,必须加上类名,指出是哪一个数据成员a
,否则就会出现二义性。(即类中的数据成员a
的值可能是Base1
中的a
,也可能是Base2
中的a
)。
图解:
为了解决这个问题,从而有了虚基类:
先看图解,了解虚基类是怎样解决的
虚基类的本质其实就是:当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次。
下面来看代码:
class Base
{
public:
Base()
{
a = 5;
cout << "Base()a=" <<a<< endl;
}
protected:
int a;
};
class Base1 :virtual public Base
{
public:
int b1;
Base1()
{
a = a + 10;
cout << "Base1()a=" <<a<< endl;
}
};
class Base2 :virtual public Base
{
public:
int b2;
Base2()
{
a = a + 20;
cout << "Base2()a=" <<a<< endl;
}
};
class Derive :public Base1, public Base2
{
public:
int d;
Derive()
{
cout << "Derive a=" << a << endl;
}
};
int main()
{
Derive d;
return 0;
}
执行结果如下:
关于虚基类初始化的几点说明:
- 建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数书进行初始化的。虚基类的其它派生类对虚基类构造函数的调用可以忽略
- 若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后再调用派生类的构造函数。
- 对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下。
- 对于非虚基类,构造函数的执行顺序仍是先左后右,自上而下。
- 若虚基类是由非虚基类派生而来,则仍然是先调用基类构造函数,再调用派生类的构造函数。
如:将上述程序改成这样:
则执行结果为: