首页 > 代码库 > C++ Primer 学习笔记_53_类与数据抽象 --友元、static成员

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;
}

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

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


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将成员设为staticstatic成员遵循正常的公有/私有访问规则:

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

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

   1static成员是类的组成部分但不是任何对象的组成部分,因此,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数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义!在类内部提供初始化式时,成员的定义就不能再指定初始值了;


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

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

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

class Bar
{
public:
    //...

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

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

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;
}