一.继承
1.继承的概念
继承是面型对象程序设计使代码可以复用的最重要的手段,它允许使用者在保持原有类特性的基础上进行扩展,增加功能,这样可以产生新的类,称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
2.继承的格式
class Person
{
public:
void Print()
{
std::cout << "id" << _id << std::endl;
std::cout << "name" <<_name << std::endl;
}
private:
int _id = "123";//学号
string _name = "yj"//姓名
};
class Student : public Person
{
public:
int _major;//专业
};
继承基类的访问权限
- 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是 被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
- 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能 访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
- 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类 的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的 写出继承方式。
- 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用 protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中 扩展维护性不强
3.基类和派生类对象之间的赋值转换
-
1.派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法就是切割或者切片。就是把派生类中父类的那部分切来赋值过去。
-
2.基类对象不能赋值给派生类对象
-
3.基类的指针可以通过强制类型转换赋值给派生类的指针,但是必须使基类的指针式指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI的dynamic_cast来进行识别后进行安全转换。
4.继承中的作用域
-
1.在继承体系中基类和派生类都有独立的作用域
-
2.子类和父类中有同名成员,子类成员将屏蔽父类成员对同名成员的直接访问,这种情况叫做隐藏,也叫重定义。
-
3.需要注意如果是成员函数的隐藏,只需函数名相同就构成隐藏。
-
4.需要注意的是在实际继承体系里面最好不要定义同名的成员。
5.派生类的默认成员函数

-
1.派生类的构造函数必须调用基类的构造函数初始化基类的那一部分。如果基类没有构造函数就必须在派生类的构造函数列表中显示调用。
-
2.派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
-
3.派生类的operator=必须要调用基类的operator=完成基类的复制。
-
4.派生类的析构函数在被调用完成后自动调用基类的析构函数清理基类长远。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
-
5.派生类对象初始化先调用基类构造再调用派生类构造。
-
6.派生类对象析构清理先调用派生类析构再调用基类的析构。
6.实现一个不能被继承的类
方法:将类的构造函数私有化,派生类中就调用不到基类的构造函数。则无法继承
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInHerit();
}
private:
NonInherit()
{}
};
class NonInherit final{};
7.继承和友元
友元函数
-
友元函数是一种特殊的函数,它需要在类体内进行说明,可以访问类的私有成员和保护成员,但又不是类的成员函数。
-
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员但是,基类的友元函数而可以访问基类的所有成员
-
友元函数的声明如下:
friend 数据类型 函数名(参数)
其中,friend是说明友元函数的关键字,友元声明可以出现在类中的任何地方。通常将友元声明成组地放在类定义地开始或者结束是个好主意。归纳起来,友元函数是一种能够访问类中私有成员地非类成员函数,友元函数在定义上和调用上与普通函数一样。友元函数地作用是在于提高程序地运行效率,但是它破坏了类地封装性和隐藏性,使得非成员函数可以访问类地私有成员。因此,只有在十分需要地时候才使用友元函数。
1.求两点之间地距离
class TPoint
{
private:
double x, y; //私有数据成员
public:
TPoint(double a, double b)
{
x = a, y = b;
cout << "点:(" << x << "," << y << ")" << endl;
}
friend double distanceoftwopoints(TPoint &a, TPoint &b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
};
int main()
{
TPoint p1(2, 2), p2(2, 5);
cout << "上述两点之间地距离" << distanceoftwopoints(p1, p2) << endl;
//调用友元函数
system("pause");
return 0;
}
2.求点到直线地距离
class Line;
class Point
{
private:
int x, y;
public:
Point(int x1, int y1)
{
x = x1, y = y1;
}
friend double dist(Line l, Point p);
};
class Line
{
int a, b, c;//y = a * x + b * y + c
public:
Line(int a1, int b1, int c1)
{
a = a1, b = b1, c = c1;
}
friend double dist(Line l, Point p);
};
double dist(Line l, Point p)
{
double d;
d = abs(l.a * p.x + l.b * p.y + l.c) / (sqrt(l.a * l.a + l.b * l.b));
return d;
}
int main()
{
Point p(10, 10);
Line l(2, 4, -3);
cout << "d=" << dist(l, p) << endl;
system("pause");
return 0;
}
友元类
- 友元类是一个类,即一个类可以作为另一个类的友元。当一个类作为另一个类的友元时,就意味着这个类的所有成员函数都是另一个类的友元函数。例如:下面程序说明类B是类A的友元类:
class A
{
int x;
public:
A()
{
x = 5;
}
friend class B;//B是A的友元类
};
class B
{
public:
void disp1(A temp)
{
temp.x++;
cout << "disp1:x = " << temp.x << endl;
}
void disp2(A temp)
{
temp.x--;
cout << "disp:x = " << temp.x << endl;
}
};
int main()
{
A obj1;
B obj2;
obj2.disp1(obj1);
obj2.disp2(obj1);
}
上述程序中类B是类A的友元类,类B中包括两个成员函数,这两个成员函数可以当成类A自己的友元函数一样使用。
-
当希望一个类可以存取另一个类的私有成员的时候,可以将该类声明为另一个类的友元类。
-
(1)友元关系是不能被继承的
-
(2)友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
-
(3)友元关系不具有传递性。
8.继承和静态成员,静态函数
-
基类定义了static静态成员,则整个继承体系里面只要一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
-
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态数据成员是所有对象所共享的,而不是某个对象的成员,对所有对象来说,静态成员只存在在一处,供所有对象使用。
-
静态成员函数和静态成员数据一样,他们都属于类的静态成员,他们都不是对象成员。因此,对静态成员的引用不需要使用对象名。
-
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。如果静态成员要引用非静态成员时,可以通过对象来引用。
9.菱形继承和菱形虚拟继承
- 多继承:是在一个子类有两个或以上直接父类时称这个继承为多继承。
- 菱形继承是多继承的特殊情况。
- 菱形继承的问题:它最开始的基类成员在最后派生类成员中的数据会有两份。这就导致了数据冗余和二义性
- 通过显示指定访问哪个父类的成员可以解决二义性的问题,但是数据冗余的问题没有解决。
这是,对于菱形继承所存在的数据冗余和二义性的问题,虚拟继承就得到了好的解决。
class A
{
public:
int _a;
};
class B : virtual public A
{
public:;
int _b;
};
class C : virtual public A
{
public:
int _c;
};
class D : virtual public B,public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
}

上面这张表对数据冗余和二义性进行分析。通过B和C两个指针,指向了一张表。这两个指针叫做虚基表指针,这两个表叫做虚基表,虚基表中存在的是偏移量,通过偏移量就可以找到A。
10.继承和组合
- 1.public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
- 2.组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
- 3.优先使用对象组合,而不是类继承 。
- 4.继承允许你根据基类类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类类有很大的影响。派生类和基类间的依 赖关系很强,耦合度高。
- 5 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对 象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse), 因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系, 耦合度低。优先使用对象组合有助于你保持每个类被封装。
- 6.实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适 合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就 用组合。
1930

被折叠的 条评论
为什么被折叠?



