Learn C++学习笔记:第八章—this指针、const和static的类成员和类函数、friend函数或类

隐藏的this指针

现在回想一下类实例的的调用方法,比如:test.func(),返过来思考一下,这个func()函数是怎么被调用的,比如,如果建立类实例的时候,从test的内存里取出func()这个函数,但是实际上这样会导致下图的存储方式:

很明显,这会导致函数代码的重复,对于追求效率的C++,这显然是可以优化的地方,并且优化的形式大概也能猜出来,就是合并函数,函数只在创建类的时候存储一次,类实例对其调用就行,如图所示:

所以,对于类实例调用函数test.func(),有时候函数需要更改类实例的参数,它又是怎么知道实例的地址呢?
这就是本节要讲的内容,隐藏的this指针。这个指针代表了类实例的地址。
具体举一个例子进行说明:

class Simple
{
private:
    int m_id;
 
public:
    Simple(int id)
    {
        setID(id);
    }
 
    void setID(int id) { m_id = id; }
    int getID() { return m_id; }
};

int main()
{
    Simple simple(1);
    simple.setID(2);
    std::cout << simple.getID() << '\n';
 
    return 0;
}

对于这一行代码:

    simple.setID(2);

实际运行的时候会被转化为:

setID(&simple, 2);

但是在setID函数里面这个地址是怎么用的呢?
原类里面的setID函数定义实际会被转化为:

void setID(Simple* const this, int id) { this->m_id = id; }

清楚地展示了类的成员变量是通过this->进行地址调用和更改的。

链接成员函数

有时让类成员函数返回其使用的对象作为返回值有时会很有用。这样做的主要原因是允许将一系列成员函数“链接”在一起,因此可以在同一对象上调用多个成员函数!

class Calc
{
private:
    int m_value;
 
public:
    Calc() { m_value = 0; }
 
    void add(int value) { m_value += value; }
    void sub(int value) { m_value -= value; }
    void mult(int value) { m_value *= value; }
 
    int getValue() { return m_value; }
};

对于这个类,如果要实现加减乘除,则需要这样写:

#include <iostream>
int main()
{
    Calc calc;
    calc.add(5); // returns void
    calc.sub(3); // returns void
    calc.mult(4); // returns void
 
    std::cout << calc.getValue() << '\n';
    return 0;
}

但是由上面的可知,.func之前的只需要接受类实列的地址就行了,所以可以把函数改成返回地址类型:

    Calc& add(int value) { m_value += value; return *this; }
    Calc& sub(int value) { m_value -= value; return *this; }
    Calc& mult(int value) { m_value *= value; return *this; }

这样就可以实现这样的功能:

calc.add(5).sub(3).mult(4);

一行代码就可以搞定。这在类重载运算符时最常用。

Const 类对象和成员函数

“万物皆对象”,如果把定义好的类跟intfloat这些等同为类型的话,就很容易理解常量类的概念。

常量类的定义及初始化,是通过构造函数初始化的:

const Date date3 { 2020, 10, 16 };

一旦通过构造函数初始化了const类对象,就不允许尝试修改该对象的成员变量,因为这会违反该对象的const-ness。这包括直接更改成员变量(如果它们是公共的)或调用设置成员变量值的成员函数。其实const类对象只能显式调用const成员函数

但是构造函数不能标记为const。这是因为构造函数需要能够初始化其成员变量,而const构造函数则不能这样做。因此,C++不允许使用const构造函数。

如下代码就是常量类调用非常量函数会报错的错误示例。

class Something
{
public:
    int m_value;
 
    Something(): m_value(0) { }
 
    void setValue(int value) { m_value = value; }
    int getValue() { return m_value ; }
};
 
int main()
{
    const Something something; // 定义const 类
 
    something.m_value = 5; // compiler error: violates const
    something.setValue(5); // compiler error: violates const
 
    return 0;
}

const成员函数

const成员函数是一个成员函数保证它不会修改对象或调用任何非const成员函数(它们可能会修改对象)。
const加的位置是函数名之后,函数结构体之前。

class Something
{
public:
    int m_value;
 
    Something(): m_value(0) { }
 
    void resetValue() { m_value = 0; }
    void setValue(int value) { m_value = value; }
 
    int getValue() const { return m_value; } // note addition of const keyword after parameter list, but before function body
};

对于在类定义之外定义的成员函数,必须在类定义中的函数原型和函数定义上都使用const关键字:

class Something
{
public:
    int m_value;
 
    Something(): m_value(0) { }
 
    void resetValue() { m_value = 0; }
    void setValue(int value) { m_value = value; }
 
    int getValue() const; // note addition of const keyword here
};
 
int Something::getValue() const // and here
{
    return m_value;
}

静态类成员和类成员函数

对象与对象之间的成员变量是相互独立的。如果对象之间需要通信怎么办?或者共享一个参数怎么办?这时候就需要使用静态成员和静态函数

① 定义及使用的一个示例:
static关键字放在最前面

class Something
{
private:
    static int s_idGenerator;
    int m_id;
 
public:
    Something() { m_id = s_idGenerator++; } // grab the next value from the id generator
 
    int getID() const { return m_id; }
};
 
int Something::s_idGenerator = 1; // start our ID generator with value 1
 
int main()
{
    Something first;
    Something second;
    Something third;
 
    std::cout << first.getID() << '\n';
    std::cout << second.getID() << '\n';
    std::cout << third.getID() << '\n';
    return 0;
}

输出:
1
2
3

②类成员必须初始化
由于静态成员和静态函数不依附于类的对象,所以他的定义跟类对象没有关系,在程序编译的时候就已经分配存储空间了,到程序结束时才释放。由于编译阶段就要分配空间,所以必须进行初始化。

上面的例子中,该行代码就是:

int Something::s_idGenerator = 1;

③调用方式
由于静态成员及函数在一开始就已经被分配好,其实性质跟全局变量非常接近,只不过是限定在了这个类的命名空间里,所以就可以把类当成一个命名空间namespace,调用方式也一样。并且由于它不依附于类的实例化,所以即使没有类对象,也可以通过name::member的方式调用静态成员或者函数。

④静态与非静态之间的调用关系
可以总结为这三句话:

  • 静态成员函数可以直接访问其他静态成员(变量或函数),但不能访问非静态成员。这是因为非静态成员必须属于类对象,并且静态成员函数没有可使用的类对象。因为它没有this指针的参数。
  • 类的非静态成员函数可以调用用静态成员函数,但反之不能。因为一个创建的先,一个后,所以后创建可以调用先创建的。

friend 函数和类

前面的static静态成员和函数实现了不同的类对象之间的互相通信,但需要通信的肯定不止类对象之间,比如类与类,类与函数都有存在需要通信访问的需求,这就需要friend功能了。
朋友函数或类是可以访问另一个类的私有成员的函数或类,就像它是该类的成员一样。这允许朋友类与另一个类密切合作,而无需使另一个类公开其私有成员(例如,通过访问功能)。

friend 定义
把一个函数的声明前面加上friend关键字写到想访问的类里面即可。

class Accumulator
{
private:
    int m_value;
public:
    Accumulator() { m_value = 0; } 
    void add(int value) { m_value += value; }
 
    // Make the reset() function a friend of this class
    friend void reset(Accumulator &accumulator);
};
 
// reset() is now a friend of the Accumulator class
void reset(Accumulator &accumulator)
{
    // And can access the private data of Accumulator objects
    accumulator.m_value = 0;
}
 
int main()
{
    Accumulator acc;
    acc.add(5); // add 5 to the accumulator
    reset(acc); // reset the accumulator to 0
 
    return 0;
}

请注意,我们必须将一个Accumulator对象传递给reset()。这是因为reset()不是成员函数。它没有* this指针,也没有可以使用的Accumulator对象,除非给定一个指针。

多个类的共同friend
一个函数可以同时是两个类的friend
需要注意的是,在类Temperature前必须声明class Humidity;,要不然friend函数不知道Humidity是什么。

#include <iostream>
 
class Humidity;
 
class Temperature
{
private:
    int m_temp;
public:
    Temperature(int temp=0) { m_temp = temp; }
 
    friend void printWeather(const Temperature &temperature, const Humidity &humidity);
};
 
class Humidity
{
private:
    int m_humidity;
public:
    Humidity(int humidity=0) { m_humidity = humidity; }
 
    friend void printWeather(const Temperature &temperature, const Humidity &humidity);
};
 
void printWeather(const Temperature &temperature, const Humidity &humidity)
{
    std::cout << "The temperature is " << temperature.m_temp <<
       " and the humidity is " << humidity.m_humidity << '\n';
}
 
int main()
{
    Humidity hum(10);
    Temperature temp(12);
 
    printWeather(temp, hum);
 
    return 0;
}

friend
也有可能使整个类成为另一个类的朋友。这使所有好友类别的成员都可以访问其他类别的私有成员。这是一个例子:

#include <iostream>
 
class Storage
{
private:
    int m_nValue;
    double m_dValue;
public:
    Storage(int nValue, double dValue)
    {
        m_nValue = nValue;
        m_dValue = dValue;
    }
 
    // Make the Display class a friend of Storage
    friend class Display;
};
 
class Display
{
private:
    bool m_displayIntFirst;
 
public:
    Display(bool displayIntFirst) { m_displayIntFirst = displayIntFirst; }
 
    void displayItem(const Storage &storage)
    {
        if (m_displayIntFirst)
            std::cout << storage.m_nValue << " " << storage.m_dValue << '\n';
        else // display double first
            std::cout << storage.m_dValue << " " << storage.m_nValue << '\n';
    }
};
 
int main()
{
    Storage storage(5, 6.7);
    Display display(false);
 
    display.displayItem(storage);
 
    return 0;
}

注意事项:

  • 使用好友函数和类时要小心,因为它会使好友函数或类违反封装。如果班级的详细信息发生变化,朋友的详细信息也将被迫更改。因此,将对朋友功能和类的使用限制为最少

  • 当两个或多个类需要以亲密的方式一起工作时,或者在定义重载运算符时更常见的情况下,通常不使用Friending。

  • 请注意,要使特定的成员函数成为朋友,需要首先看到成员函数类的完整定义

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值