C++ Primer 学习笔记_53_类与数据抽象 --友元、static成员

--友元、static成员

一、友元

友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类(对未被授权的函数或类,则阻止其访问);友元的声明以关键字friend开始,但是它只能出现在类定义的内部。友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受其声明出现部分的访问控制影响。

【最佳实践】

通常,将友元声明成组的放在类定义的开始或结尾是个好主意!

1、友元关系:一个例子

假设一个窗口管理类Window_Mgr可能需要访问由其管理的Screen对象的内部数据。Screen应该允许其访问自己的私有成员:

class Screen
{
    friend class Window_Mgr;
};

Window_Mgr的成员可以直接引用Screen的私有成员:

Window_Mgr &
Window_Mgr::relocate(Screen::index x,Screen::index c,Screen &s)
{
    s.height += r;
    s.width += c;
    return *this;
}

如果缺少友元声明,这段代码将会出错:将不允许使用形参s的height和width成员。

注:友元可以是普通的非成员函数,或前面定义的其他类的成员,或整个类。

2、使其他类的成员函数成为友元

class Screen
{
    //Screen指定只允许relocate成员访问:
    //函数名必须用该函数所属的类名字加以限定!
    friend Window_Mgr &Window_Mgr::relocate(Window_Mgr::index,
                                            Window_Mgr::index,
                                            Screen &);
};

3、友元声明与作用域

需要注意友元声明与友元定义之间的互相依赖。在前面的例子中,类Window_Mgr必须先定义。否则,Screen类就不能将一个Window_Mgr函数指定为友元。然而,只有在定义类Screen之后,才能定义relocate函数—— 毕竟,它被设为友元是为了访问类Screen的成员。

更一般地讲,必须先定义包含成员函数的类,才能将成员函数设为友元。另一方面,不必预先声明类和非成员函数来将它们设为友元。

友元声明将已命名的类或非成员函数引入到外围作用域中。此外,友元函数可以在类的内部定义,该函数的作用域扩展到包围该类定义的作用域。

class X
{
    friend class Y;
    friend void f()
    {

    }
};

class Z
{
    Y * ymem;
    void g()
    {
        return ::f();
    }
};

4、重载函数与友元关系

类必须将重载函数集中每一个希望设为友元的函数都声明为友元:

class Screen
{
    friend std::ostream& storeOn(std::ostream &, Screen &);
    // ...
};

//将该函数作为Screen的友元
extern std::ostream& storeOn(std::ostream &, Screen &);

//该函数对Screen没有特殊的访问权限
extern BitMap& storeOn(BitMap &, Screen &);

//P398 习题12.34/35
class Sales_item
{
    friend Sales_item add(const Sales_item &item1,
                          const Sales_item &item2);
    friend std::istream &input(Sales_item &,std::istream &);
public:

    bool same_isbn(const Sales_item &rhs) const
    {
        return rhs.isbn == isbn;
    }

    Sales_item():units_sold(0),revenue(0) {}

private:
    std::string isbn;
    unsigned units_sold;
    double revenue;
};

Sales_item add(const Sales_item &item1,const Sales_item &item2)
{
    if (item1.same_isbn(item2))
    {
        Sales_item item;
        item.isbn = item1.isbn;
        item.units_sold = item1.units_sold + item2.units_sold;
        item.revenue = item1.revenue + item2.revenue;
        return item;
    }

    return item1;
}

std::istream &input(Sales_item &item,std::istream &in)
{
    in >> item.isbn >> item.units_sold >> item.revenue;

    return in;
}

二、static类成员

通常,static数据成员存在于类类型的每个对象中,static数据成员独立于该类的任意对象而存在:每个static数据成员是与类关联的对象,并不与该类的对象相关联!

正如类可以定义共享的static数据成员一样,类也可以定义static成员函数。static成员函数没有this形参,它可以直接访问所属类的static成员,但是不能直接使用static成员!

1、使用类的static成员的优点

1)static成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突

2)可以实施封装。static成员可以是私有成员,而全局对象不可以。

3)通过阅读程序容易看出static成员是与特定类关联的。这种可见性可清晰地显示程序员的意图。

2、定义static成员

在成员声明前加上关键字static将成员设为static。static成员遵循正常的公有/私有访问规则:

class Account
{
public:
    void applyint()
    {
        amount += amount * interestRate;
    }

    static double rate()
    {
        return interestRate;
    }
    static void rate(double);

private:
    std::string owner;
    double amount;

    //interestRate 对象的值,为Account类型的全体对象所共享
    static double interestRate;
    static double initRate();
};

3、使用类的static成员

可以通过作用域操作符从类直接调用static成员,或者通过对象、引用或指向该类类型对象的指针间接调用。

    Account ac;
    Account *pac = ?
    double rate;
    rate = ac.rate();
    rate = pac -> rate();
    rate = Account::rate();

static成员函数

Account类有两个名为rate的 static成员函数,其中一个定义在类的内部。当我们在类的外部定义static成员时,无须重复指定static保留字,该保留字只出现在类定义体内部的声明处:

void Account::rate(double newRate)
{
        interestRate = newRate;
}

1、static成员是类的组成部分但不是任何对象的组成部分,因此,static成员函数没有this指针。

2、因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为 const。毕竟,将成员函数声明为const就是承诺不会修改该函数所属的对象。

3、最后,static成员函数也不能被声明为虚函数(后面介绍)。

//P400 习题12.38/39/40
class Foo
{
public:
    Foo(int Ival):ival(Ival){}
    Foo():ival(0){}

    int get()
    {
        return ival;
    }

private:
    int ival;
};

class Bar
{
public:
    static Foo FooVal()
    {
        ++ callsFooVal;
        return foo;
    }

private:
    static int ival;
    static Foo foo;
    static int callsFooVal;
};

int Bar::ival = 0;
Foo Bar::foo = Foo();
int Bar::callsFooVal = 0;

static数据成员

static数据成员必须在类定义体的外部定义(正好一次)。不像普通数据成员,static 成员不是通过类构造函数进行初始化,而是应该在定义时进行初始化。

保证对象正好定义一次的最好办法,就是将static数据成员的定义放在包含类非内联成员函数定义的文件中。

double Account::interestRate = initRate();

像使用任意的类成员一样,在类定义体外部引用类的static成员时,必须指定成员是在哪个类中定义的。然而,static关键字只能用于类定义体内部的声明中,定义不能标示为static。

1、特殊的整型conststatic成员

只要初始化式是一个常量表达式,整型conststatic 数据成员就可以在类的定义体中进行初始化:

class Account
{
public:
    static double rate()
    {
        return interestRate;
    }
    static void rate(double);

private:
    //or: static const int period = 30;
    const static int period = 30;
    double daily_tb1[period];
};
const int Account::period;      //OK
const int Account::period = 30; //Error

【注意:】conststatic数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义!在类内部提供初始化式时,成员的定义就不能再指定初始值了;

2、static成员并不是类对象的组成部分

因为static数据成员不是任何对象的组成部分,所以它们的一些使用方式对于非static数据成员而言是不合法的。

1)static数据成员的类型可以是该成员所属的类类型。非static成员被限定声明为其自身类对象的指针或引用:

class Bar
{
public:
    //...

private:
    static Bar mem; //OK
    Bar *mem1;
    Bar &mem2;
    Bar mem3;   //Error
};

2)static数据成员可以用作默认实参:

class Screen
{
public:
    Screen clear(char = bkground);

private:
    static const char bkground = ‘#‘;
};

非static数据成员不能用作默认实参,因为它的值不能独立于所属的对象而使用。

//P402 习题12.42
class Example
{
public:
    static double rate;

    static const int vecSize = 20;
    static vector<double> vec;
};

const int Example::vecSize;
double Example::rate = 6.5;
vector<double> Example::vec(vecSize);

//P400 “拓展”习题12.37
class Account
{
public:

    Account(const std::string &name = "",double Amount = 0):
        owner(name),amount(Amount) {}

    void applyint()
    {
        amount += amount * interestRate;
    }

    static double rate()
    {
        return interestRate;
    }
    static void rate(double);

    double deposit(double Amount)
    {
        amount += Amount;
        return amount;
    }

    bool withdraw(double Amount)
    {
        if (Amount > amount)
        {
            return false;
        }
        else
        {
            amount -= Amount;
            return true;
        }
    }

    double getBalance()
    {
        return amount;
    }

private:
    std::string owner;
    double amount;

    static double interestRate;
    static double initRate();
};

void Account::rate(double newRate)
{
    interestRate = newRate;
}

double Account::interestRate = 2.5;
double Account::initRate()
{
    return 2.5;
}

C++ Primer 学习笔记_53_类与数据抽象 --友元、static成员,布布扣,bubuko.com

时间: 2024-10-08 13:18:17

C++ Primer 学习笔记_53_类与数据抽象 --友元、static成员的相关文章

C++ Primer 学习笔记_22_类与数据抽象(8)--static 成员变量、static 成员函数、类/对象的大小

一.static 每个static数据成员是与类关联的对象,并不与该类的对象相关联!非static数据成员存在于类类型的每个对象中,static数据成员独立该类的任意对象存在. static成员函数没有this形参,它可以直接访问所属类的static成员,但是不能直接使用static成员! 1.static 成员变量 对于特定类型的全体对象而言,有时候可能需要访问一个全局的变量.比如说统计某种类型对象已创建的数量. 如果我们用全局变量会破坏数据的封装,一般的用户代码都可以修改这个全局变量,这时可

C++ Primer 学习笔记_57_类与数据抽象 --管理指针成员

复制控制 --管理指针成员 引言: 包含指针的类需要特别注意复制控制,原因是复制指针时只是复制了指针中的地址,而不会复制指针指向的对象! 将一个指针复制到另一个指针时,两个指针指向同一对象.当两个指针指向同一对象时,可能使用任一指针改变基础对象.类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在.指针成员默认具有与指针对象同样的行为. 大多数C++类采用以下三种方法之一管理指针成员: 1)指针成员采取常规指针型行为:这样的类具有指针的所有缺陷但无需特殊的复制控制! 2)类

C++ Primer 学习笔记_24_类与数据抽象(10)--static 与单例模式、auto_ptr与单例模式、const成员函数、const 对象、mutable修饰符

C++ Primer 学习笔记_24_类与数据抽象(10)--static 与单例模式.auto_ptr与单例模式.const成员函数.const 对象.mutable修饰符 前言 [例]写出面向对象的五个基本原则? 解答:单一职责原则,开放封闭原则,依赖倒置原则,接口隔离原则和里氏替换原则 里氏替换原则:子类型必须能够替换他们的基类型. 设计模式分为三种类型:创建型模式.结构型模式和行为型模式 一.static 与单例模式 1.单例模式 单例模式的意图:保证一个类仅有一个实例,并提供一个访问它

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员)、拷贝构造函数

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员).拷贝构造函数  从概念上将,可以认为构造函数分为两个阶段执行: 1)初始化阶段: 2)普通的计算阶段.计算阶段由构造函数函数体中的所有语句组成. 一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 1.对象成员及其初始化 <span style="font-size:14px;">#include <iostream> using namespace std;

C++ Primer 学习笔记_56_类与数据抽象 --消息处理示例

复制控制 --消息处理示例 说明: 有些类为了做一些工作需要对复制进行控制.为了给出这样的例子,我们将概略定义两个类,这两个类可用于邮件处理应用程序.Message类和 Folder类分别表示电子邮件(或其他)消息和消息所出现的目录,一个给定消息可以出现在多个目录中.Message上有 save和 remove操作,用于在指定Folder中保存或删除该消息. 数据结构: 对每个Message,我们并不是在每个Folder中都存放一个副本,而是使每个Message保存一个指针集(set),set中

C++ Primer 学习笔记_55_类与数据抽象 --析构函数

复制控制 --析构函数 引言: 在构造函数中分配了资源之后,需要一个对应操作自动回收或释放资源.析构函数就是这样的一个特殊函数,它可以完成所需的资源回收,作为类构造函数的补充. 1.何时调用析构函数 撤销类对象时会自动调用析构函数: Sales_item *p = new Sales_item; { Sales_item item(*p); //调用复制构造函数 delete p; //调用指针p的析构函数 } //调用对象item的析构函数 动态分配的对象只有在指向该对象的指针被删除时才撤销,

C++ Primer 学习笔记_54_类与数据抽象 --复制构造函数、赋值操作符

复制控制 --复制构造函数.赋值操作符 引言: 当定义一个新类型时,需要显式或隐式地指定复制.赋值和撤销该类型的对象时会发生什么– 复制构造函数.赋值操作符和析构函数的作用!      复制构造函数:具有单个形参,该形参(常用const修饰)是对该类类型的引用.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式的使用复制构造函数:当将该类型的对象传递给函数或者从函数返回该类型的对象时,将隐式使用复制构造函数.     析构函数:作为构造函数的互补,当对象超出作用域或动态分配的对象被删除

C++ Primer 学习笔记_16_类与数据抽象(2)_隐含的this指针

C++ Primer 学习笔记_16_类与数据抽象(2)_隐含的this指针 1.引言 在前面提到过,成员函数具有一个附加的隐含形参,即指向该类对象的一个指针.这个隐含形参命名为this. 2.返回*this 成员函数有一个隐含的附加形参,即指向该对象的指针,这个隐含的形参叫做this指针(编译器自动传递)使用this指针保证了每个对象可以拥有不同数值的数据成员,但处理这些成员的代码可以被所有对象共享.成员函数是只读的代码,由所有对象共享,并不占对象的存储空间,因为this指针指向当前对象,所以

C++ Primer 学习笔记_17_类与数据抽象(3)_类作用域

C++ Primer 学习笔记_17_类与数据抽象(3)_类作用域 引言: 每个类都定义了自己的新作用域与唯一的类型.即使两个类具有完全相同的成员列表,它们也是不同的类型.每个类的成员不同与任何其他类(或任何其他作用域)的成员. 一.类作用域中的名字查找 1)首先,在使用该名字的块中查找名字的声明.只考虑在该项使用之前声明的名字. 2)如果在1)中找不到,则在包围的作用域中查找. 如果找不到任何声明,则出错. 类的定义实际上是在两个阶段中处理: 1)首先,编译器声明: 2)只有在所有成员出现之后