zoukankan      html  css  js  c++  java
  • C++类的访问控制

    私有和公有

    一个类里有方法和成员变量,public关键字标识后,public下的方法和变量都变为公有函数。private关键字标识后,private关键字下的方法和成员变量都变为私有。默认情况下,如果不声明public,class中所有的方法和成员都是私有的。如果不声明private, struct中所有的方法和成员都是公有的。

    友元

    上一篇,我们将print,read等非Sales_data类的全局函数声明为Sales_data类的友元函数,所以print,read可以访问Sales_data类的私有成员。我们再次回忆一下Sales_data类。

    class Sales_data
    {
    public:
        //通过default实现默认构造
        // Sales_data() = default;
        //显示实现默认构造
        Sales_data() : bookNo(""), units_sold(0), revenue(0.0) {}
        // copy构造,根据Sales_data类型对象构造一个新对象
        Sales_data(const Sales_data &sa);
        Sales_data(const std::string &s) : bookNo(s) {}
        Sales_data(const std::string &s, unsigned n, double p)
            : bookNo(s), units_sold(n), revenue(p * n) {}
        Sales_data(std::istream &is);
        //返回图书号
        std::string isbn() const { return bookNo; }
        //获取平均单价
        double avg_price() const;
        //将一个Sales_data对象合并到当前类对象
        Sales_data &combine(const Sales_data &);
        friend std::ostream &print(std::ostream &, const Sales_data &);
        friend std::istream &read(std::istream &, Sales_data &);
    
    private:
        //图书编号
        std::string bookNo;
        //销量
        unsigned units_sold = 0;
        //收入
        double revenue = 0.0;
    };
    

    封装有两个重要的优点:
    · 确保用户代码不会无意间破坏封装对象的状态。
    · 被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。

    友元的声明

    友元的声明友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们就必须在友元声明之外再专门对函数进行一次声明。为了使友元对类的用户可见,我们通常把友元的声明与类本身放置在同一个头文件中(类的外部)。因此,我们的Sales_data头文件应该为read、print和add提供独立的声明(除了类内部的友元声明之外)。所以我在Sales_data类的头文件里声明了这些函数

    // Sales_data的非成员接口
    extern Sales_data add(const Sales_data &, const Sales_data &);
    extern std::ostream &print(std::ostream &, const Sales_data &);
    extern std::istream &read(std::istream &, Sales_data &);
    

    隐藏类型定义

    我们可以在类中定义一种新的类型,这种类型对于外部是隐藏内部实现的,外部不知道该类型是什么。

    class Screen
    {
    public:
        typedef std::string::size_type pos;
    
    private:
        pos cursor = 0;
        pos height = 0, width = 0;
        std::string contents;
    };
    

    用来定义类型的成员必须先定义后使用,这一点与普通成员有所区别,具体原因将在7.4.1节(第254页)解释。因此,类型成员通常出现在类开始的地方。

    inline成员函数

    所谓内联函数就是在编译时展开,减少运行时开销的一种手段,可以通过inline关键字声明,也可以在类的cpp文件里定义函数时前面指明inline,当然一个类的成员函数在类的头文件实现了,那它也是内联函数,我们完善Screen类,用以上三种方式实现内联函数

    class Screen
    {
    public:
        typedef std::string::size_type pos;
        //因为Screen有另一个构造函数
        //所以要实现一个默认构造函数
        Screen() = default;
        // cursor被初始化为0
        Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}
        //读取光标处字符
        char get() const
        {
            //隐式内联
            return contents[cursor];
        }
    
        //显示内联
        inline char get(pos ht, pos wd) const;
        //能在之后被设为内联
        Screen &move(pos r, pos c);
    
    private:
        pos cursor = 0;
        pos height = 0, width = 0;
        std::string contents;
    };
    

    我们可以在类的内部把inline作为声明的一部分显式地声明成员函数,同样的,也能在类的外部用inline关键字修饰函数的定义:虽然我们无须在声明和定义的地方同时说明inline,但这么做其实是合法的。不过,最好只在类外部定义的地方说明inline,这样可以使类更容易理解。

    重载成员函数

    类的成员函数同样支持重载。只要函数名相同,参数列表不同即可。

    mutable属性

    如果一个成员变量被指明mutable属性,则无论对象是否为const,无论成员函数是否为const,该成员变量都可以被修改。
    我们给Screen定义一个mutable成员变量access_ctr,以及一个const成员函数some_member,并在该函数中修改access_ctr变量。

    class Screen
    {
    public:
        typedef std::string::size_type pos;
        //因为Screen有另一个构造函数
        //所以要实现一个默认构造函数
        Screen() = default;
        // cursor被初始化为0
        Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}
        //读取光标处字符
        char get() const
        {
            //隐式内联
            return contents[cursor];
        }
    
        //显示内联
        inline char get(pos ht, pos wd) const;
        //能在之后被设为内联
        Screen &move(pos r, pos c);
        void some_member() const;
    
    private:
        pos cursor = 0;
        pos height = 0, width = 0;
        std::string contents;
        //即使在一个const对象里access_ctr也可被修改
        mutable size_t access_ctr;
    };
    

    实现some_member的一个成员变量

    void Screen::some_member() const
    { //在const函数中也可以修改access_ctr
        ++access_ctr;
    }
    

    链式调用

    当我们通过成员函数内部返回*this,也就是类对象本身,则可以继续链式调用其内部的成员函数,比如我们通过重载实现两个set函数

    Screen &Screen::set(char c)
    {
        contents[cursor] = c;
        return *this;
    }
    Screen &Screen::set(pos r, pos col, char ch)
    {
        //给定位置设置新值
        contents[r * width + col] = ch;
        return *this;
    }
    

    链式调用

        Screen::pos row = 3;
        Screen::pos col = 4;
        Screen screen(3, 4, 'c');
        screen.move(2, 3).set('#');
    

    从const成员返回的*this是常量指针,如果我们实现一个display的const函数,

    const Screen &Screen::display(ostream &os) const
    {
        os << "width is " << width << " "
           << "height is " << height << endl;
        return *this;
    }
    
    

    如果按照如下链式调用编译器将报错,因为display返回const Screen& 类型

     screen.display(cout).move(2, 3).set('#');
    

    所以我们可以通过重载实现链式调用,实现一个返回const Screen &类型的display函数和一个Screen &类型的display函数,
    这两个display函数内部调用do_display函数,因为const函数只能调用const函数,所以我们先实现display函数,他是一个const函数

    void Screen::do_display(ostream &os) const
    {
        os << "width is " << width << " "
           << "height is " << height << endl;
    }
    

    再实现两个重载的display函数

    const Screen &Screen::display(ostream &os) const
    {
        do_display(os);
        return *this;
    }
    
    Screen &Screen::display(ostream &os)
    {
        do_display(os);
        return *this;
    }
    

    这样编译器就会根据类型动态选择display的版本

        Screen screen(3, 4, 'c');
        screen.display(cout).move(2, 3).set('#');
        const Screen cscreen(2, 1, ' ');
        cscreen.display(cout);
    

    类类型和声明

    class First
    {
        int memi;
        int getMem();
    };
    
    struct Second
    {
        int memi;
        int getMem();
    };
    

    如上我们定义了两个类型,下面的赋值会报错,因为类内的成员虽然一致,但是不同的类就是不同的类型

        First obj1;
        //编译报错,obj1和obj2不是一个类型
         Second obj2 = obj1;
    

    我们可以不定义类,先进行类的声明

    class Bags;
    

    这种声明有时被称作前向声明(forward declaration),它向程序中引入了名字Screen并且指明Bags是一种类类型。对于类型Bags来说,在它声明之后定义之前是一个不完全类型(incomplete type),也就是说,此时我们已知Bags是一个类类型,但是不清楚它到底包含哪些成员。

    不完全类型只能在非常有限的情景下使用:
    可以定义指向这种类型的指针或引用,也可以声明(但是不能定义)以不完全类型作为参数或者返回类型的函数。

    直到类被定义之后数据成员才能被声明成这种类类型。我们必须首先完成类的定义,然后编译器才能知道存储该数据成员需要多少空间。
    因为只有当类全部完成后类才算被定义,所以一个类的成员类型不能是该类自己。
    然而,一旦一个类的名字出现后,它就被认为是声明过了(但尚未定义),因此类允许包含指向它自身类型的引用或指针:

    class Link_screen
    {
        Screen window;
        Link_screen *next;
        Link_screen *prev;
    };
    

    友元类和成员函数

    可以将一个类A声明为另一个类B的友元,则A类对象可以访问B类对象的私有成员。

    class Screen
    {
    public:
        // Window_mgr可以访问Screen类的私有部分
        friend class Window_mgr;
        //Screen类的其他部分......
    };
    

    Window_mgr类可以访问Screen类的私有成员,通过class前向声明了Window_mgr类。
    接下来我们定义Window_mgr类

    class Window_mgr
    {
    public:
        //窗口中每个屏幕的编号
        using ScreenIndex = std::vector<Screen>::size_type;
        //按照编号将指定的Screen重置为空白
        void clear(ScreenIndex);
    
    private:
        std::vector<Screen> screens{Screen(24, 80, ' ')};
    };
    
    void Window_mgr::clear(ScreenIndex i)
    {
        // s是一个Screen的引用,指向我们想清空的屏幕
        Screen &s = screens[i];
        //清空屏幕
        s.contents = string(s.height * s.width, ' ');
    }
    

    也可以让成员函数作为友元

    class Screen
    {
    public:
        // Window_mgr可以访问Screen类的私有部分
        friend void Window_mgr::clear(ScreenIndex);
        //Screen类的其他部分......
    };
    

    · 首先定义Window_mgr类,其中声明clear函数,但是不能定义它。在clear使用Screen的成员之前必须先声明Screen。
    · 接下来定义Screen,包括对于clear的友元声明。
    · 最后定义clear,此时它才可以使用Screen的成员。

    类和非成员函数的声明不是必须在它们的友元声明之前。当一个名字第一次出现在一个友元声明中时,我们隐式地假定该名字在当前作用域中是可见的。然而,友元本身不一定真的声明在当前作用域中

    源码链接https://gitee.com/secondtonone1/cpplearn
    我的公众号,谢谢关注
    https://cdn.llfc.club/gzh.jpg

    重剑无锋,大巧不工
  • 相关阅读:
    算法笔记_225:数字密码发生器(Java)
    LVS专题-(1)LVS基本介绍
    Mysql加锁过程详解(7)-初步理解MySQL的gap锁
    java实现二叉树的构建以及3种遍历方法
    java设计模式-菜鸟网络
    数据结构与算法(周鹏-未出版)-第六章 树-习题
    数据结构与算法(周鹏-未出版)-第六章 树-6.5 Huffman 树
    数据结构与算法(周鹏-未出版)-第六章 树-6.4 树、森林
    数据结构与算法(周鹏-未出版)-第六章 树-6.3 二叉树基本操作的实现
    数据结构与算法(周鹏-未出版)-第六章 树-6.2 二叉树
  • 原文地址:https://www.cnblogs.com/secondtonone1/p/15737138.html
Copyright © 2011-2022 走看看