C++ 类的学习(2)

类的友元

友元是C++提供的一种破坏数据封装和数据隐藏的机制,通过将一个模块声明为另一个模块的友元,一个模块能够引用另一个模块中本是被隐藏的信息。

为啥需要友元呢?有时候我们需要不断的提取对象中的成员的数据,而提前成员的数据,而只有类本身的成员函数才可以访问类的成员,在很多次使用成员函数的过程中,会造成时间的浪费,而使用友元的话,我们可以直接访问某个类的成员变量。

友元函数

友元函数是在类声明中由关键词friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问private和protected成员。

例如

class A
{
private:
    int a,b;
public:
    A(int x,int y):a(x),b(y){}
    friend double show(A &x,A &y);
};
double show(A &x,A &y)
{
    double a=x.a-y.a;
    double b=x.b-y.b;
    return sqrt(a*a+b*b);
}
int main()
{
    A b(1,2),b1(2,3);
    cout<<show(b,b1)<<endl;
    return 0;
}

在show函数里我们用了很多次对象的成员,如果用函数来提取对象成员的值的话,会造成时间的浪费。

这里有个隐患,就是show传过来的是两个引用,而引用是双向传递的,如果show函数出了问题,我们所使用的那两个对象的值可能会被改变。可以加一个 const 来消除这个隐患

友元类

在一个类中声明另一个类是它的朋友,举个例子,

在类A里声明B是A的友元,所以在B的函数set里可以直接使用B成员中对象a的成员变量。

类的友元关系是单向的:声明B类是A类的友元 不等于 A类是B类的友元,所以上面这个friend class B可以这么来理解,A授权了B来访问自己,但是B并没有授权A可以访问自己。

共享数据的保护

使用const来定义常类型,起到只能使用不能修改的作用,在定义常类型的时候必须初始化。

常对象:用const修饰的对象

#include<bits/stdc++.h>
using namespace std;
class A
{
private:
    int a;
    const int b;//常成员变量
public:
    A(int i):a(i){}
    void print() const;//常成员函数
};

void A::print() const//编译器在编译时候看到const,就会仔细审查函数中是否有改变状态语句,如果有就报错
{

}
int main()
{
    A const b(1);//常对象
    return 0;
}

常对象只能调用常函数

常变量只能由常指针来指向

前项引用声明

前项引用声明实际上就是声明,适用于下列情况

class B;//前向引用声明
class A
{
public:
    void f(B b);
};
class B
{
 public:
     void g(A a);   
};

A类里有参数是B类对象的函数,B类里有参数是A类对象的函数,必须有一个类先声明一下,所以这里声明了B类

但是如果是这样的话,前项引用声明就失效了

class B;//前向引用声明
class A
{
private:
    B x;
public:
    void f(B b);
};
class B
{
 public:
     void g(A a);   
};

在A类里出现了B 类的对象 x,但是这个B类并没有被定义,编译器在编译这一行的时候,不知道B类里面什么,因而无法定义x,所以会报错

这里可以用 B *x来代替B x;

类的继承与派生

类的继承是指保持已有类的特性而构造新类的过程叫继承

类的派生是指在已有类的基础上新增自己的特性而产生的新类的过程叫派生

被继承的类叫基类(父类)

派生出的类叫派生类(子类)

直接参与派生出某类的基类成为直接基类

基类的基类甚至更高层的基类成为间接基类

继承的目的:实现设计与代码的重用

派生的目的:当新的问题出现是,原有程序无法解决时,需要对原有程序进行改造

单继承时派生类的定义

语法

class 派生类名 :继承方式 基类名

{

      成员声明;

}

例:

class Derived:public Base

{

public:

Derived();

~Derived();

};

多继承时派生类的定义

class 派生类名 :继承方式1 基类名1,继承方式2 基类名2, ....

{

      成员声明;

}

公有继承

简单来讲就是,继承过来的公有成员和保护成员变成派生类的公有成员了。在类的声明外派生类也可以访问基类的公有成员和保护成员,私有成员不可访问

派生类的对象只能访问公有成员

私有继承

基类的公有和保护成员继承过来都变成私有成员了,派生类的对象不能直接访问从基类继承的任何成员。

※ 在派生类的函数里调用基类的成员函数要使用 类名::函数名(参数)的形式来调用

保护继承

基类的公有成员和保护成员都以保护的身份出现在派生类里,派生类的成员函数可以直接访问基类里的公有成员和保护成员

就是说,保护继承和私有继承的区别主要在于,保护继承的派生类的成员函数可以直接使用基类的公有和保护成员,但是在类的声明外,例如在主函数里,对象不能直接使用基类的公有和保护成员。

举个栗子

#include<bits/stdc++.h>
using namespace std;
class A{

protected:

int x;

};
class B:public A
{
public :
    void fun();
};
void B::fun()
{
    x=5;//这是可以的!!!!!!!!!
}
int main()
{
    B a;
    B.x=1;//这是错误哒!!!!!!!!
}

c++11提供了final

用final标记的类无法被继承

语法

class 类名 final{ }

派生类的构造函数

 默认情况下,基类的构造函数不被继承。

在c++ 11里可以使用 using B::B的形式继承基类中的构造函数,但是有一个问题,派生类的继承基类构造函数后,基类的构造函数只能初始化基类的成员。

如果没有继承基类的构造函数:

需要手动定义构造函数初始化,对于继承来的成员,程序将自动调用基类构造函数进行初始化,并且派生类的构造函数需要给基类的构造函数传递参数。

单继承时构造函数的定义语法

派生类名::派生类名(基类所需形参,本类成员所需形参):基类名(参数表),本类成员初始化列表

{

};

例子

#include<bits/stdc++.h>
using namespace std;
class A
{
public:
    A();
    A(int i):x(i){cout<<"A"<<endl;}
private:
    int x;
};
class B:public A
{
public:
    B();
    B(int i,int j):A(i),a(j){cout<<"B"<<endl;}
private:
    int a;
};
int main()
{
    B c(1,2);
    return 0;
}

输出结果

A

B

多继承时构造函数的定义语法

派类名::派类名(参数表):

基类名1(基类1初始化参数表),

基类名2(基类2初始化参数表),

。。。。。。

基类n(基类n初始化参数表),

本类成员初始化列表

{

};

如果派生类的构造函数没有给基类的构造函数传递参数,那么程序将调用基类的默认构造函数。

多继承且有对象成员时派生的构造函数定义

派生类名::派生类名(形参表):

基类名1(参数),基类名2(参数)。。。。基类名n(参数),

本类成员(含对象成员)初始化列表

{

};

构造函数的调用顺序是

1.调用基类构造函数:顺序按照它们被继承时声明的顺序(从左向右)

2.对初始化列表中的成员进行初始化。

顺序按照它们在类中的定义顺序

举个例子

class A:public Base2,public Base1,public Base3
{
public:
    A(int a,int b,int c,int d):Base1(a),member2(d),member1(c),Base3(b){}
   //注意基类名的个数与顺序,注意成员对象名的个数与顺序
private://派生类的私有成员对象
    Base1 member1;
    Base2 member2;
    Base3 member3;
};

初始化列表里列出来的次序并不是调用顺序,Base1(a),member2(d),member1(c),Base3(b)

复制构造函数

复制构造函数只有一个参数,对于派生类来说,我们可以把要复制的那个对象的引用传给派生类的复制构造函数和基类的复制构造函数

   class Base { /* ........*/ };

    class Derived : public Base {

        public : 

                 //Base :: Base( const Base & ) not Invoked automatically 

                Derived ( const Derived & d ) : Base( d ) /* other member initialization */ { /* .......*/ }

    };

        初始化函数Base( d ) 将派生类对象d转换为他的基类部分的引用,并调用基类复制构造函数
基类的复制构造函数是基类对象的引用,但实参可以是派生类对象的引用

派生类的析构函数

析构函数不被继承,派生类如果需要,要自行声明析构函数

派生类对象过期时,先执行派生类函数的函数体,在调用基类的析构函数

访问从基类继承的成员

1.当派生类与基类中有相同成员时

若未特别限定,则通过派生类对象使用的是派生类中的同名成员。

此时如果要访问基类中同名成员时,应使用基类名和::来限定

#include<bits/stdc++.h>
using namespace std;
class A
{
public:
    int a;
};
class B:public A
{
public:
    int a;
};
int main()
{
    B x;
    x.A::a=1;
    x.B::a=2;
    cout<<x.a<<endl;
}

输出结果

2.二义性问题

如果从不同基类中继承了同名成员,但是在派生类中没有定义同名成员,则访问成员存在二义性问题

解决方法:加类名和作用域符来限定

虚基类

虚基类是什么呢?当类之间的继承关系出现下图这种关系时候,就可以用虚基类

正常情况下,Derived类里会有两部分从Base0的成员,这样就出现了二义性问题,需要用Base1::var或Base2::var的方式来使用,但是很多情况下会带来冗余,不仅仅回来带空间的浪费,而且会带来不一致性。(同一个数据在不同地方出现了重复存储现象叫做冗余,同一个数据在不同地方出现了不相同的值叫做不一致性)

我们在继承的时候使用 virtual说明基类为虚基类,为最远派生类提供唯一的基类成员,而不产生重复复制,简单来说就是Derived类里只有一部分Base0。

要求是在第一类继承的时候就使用virtual,如上图中,在声明Base1和Base2的时候就要使用class Base1 virtual public Base0的语句来继承虚基类。

上图这种继承关系还存在一个构造函数传参的问题。

将Base0作为虚基类后,我们需要在声明Derived类的时候给Base0的构造函数传参数,程序在调用Base1和Base2的构造函数时候会忽略调用虚基类的构造函数。简单来讲就是在声明Derived类的时候直接给Base1,Base2,Base0传参数就行

Derived::Derived(int a,int b,int c):Base1(a),Base2(b),Base0(c){}

发布了37 篇原创文章 · 获赞 3 · 访问量 2388

猜你喜欢

转载自blog.csdn.net/Stillboring/article/details/104431102
今日推荐