IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 类中的成员函数加static修饰与否的区别(decltype视角探究) -> 正文阅读

[C++知识库]类中的成员函数加static修饰与否的区别(decltype视角探究)

0??、小白入手

decltype的基本认识及用法,请各位看官移步笔者的另一篇博文:浅析decltype一些有趣(实用)的用法

一、问题由来

????????为什么会有这样一个问题呢?笔者在完成LeetCode #1224 设计力扣排行榜这一系统设计问题时,偶然发现其中一个成员函数加static修饰和不加static修饰时,使用decltype来解析其函数类型其效果是不一样的

? ? ? ? 而正是因为两者的不一样,若将decltype解析出来的结果作为某些容器初始化时的参数,则有可能出现报错行为。为了方便说明问题以及做实验验证,笔者简化后的代码列在下面:

#include <iostream>
#include <set>
#include <unordered_map>

using namespace std;

class LeaderBoard
{
private:
	using Player = pair<int, int>;  //id, score

	bool cmp(const Player& p1, const Player& p2)
	{
		if (p1.second == p2.second) return p1.first > p2.first;

		else return p1.second > p2.second;
	}

	unordered_map<int, Player> record;     //记录玩家是否出现过
	set<Player, decltype(&cmp)> players;  //对玩家进行排序(按序号和成绩)

public:
	LeaderBoard() : players(cmp) {}
	~LeaderBoard()
	{
		record.clear();
		players.clear();
	}

	void addScore(int playerId, int score)  //O(logn)
	{
		Player p{ playerId, score };

		if (record.count(playerId))
		{
			p.second += record[playerId].second;
			players.erase(record[playerId]);
		}

		//更新记录
		record[playerId] = p;
		players.emplace(p);
	}

	int top(int k)   //O(1)
	{
		int sum = 0;
		for (auto it = players.begin(); it != players.end() && k > 0; ++it, --k)
		{
			sum += it->second;
		}

		return sum;
	}

	void reset(int playerId)  //O(1)
	{
		if (record.count(playerId))
		{
			players.erase(record[playerId]);
			record.erase(playerId);
		}
	}
};

int main()
{
	LeaderBoard lb;

	lb.addScore(1, 20);
	
	return 0;
}

[程序编译结果]

?????????由于set容器的模板参数中传入了自定义的比较函数类型,故在构造函数中也该对set容器进行初始化(圆括号中填入cmp,即比较函数的入口地址)。

? ? ? ? 然而,我们却惊奇的发现这样对players传参初始化会得到一个编译错误,很容易按照编译器的错误提示进一步修改为如下的代码,但编译器依然报错:

? ? ? ? ?正当我们抓耳挠腮之际,笔者悄悄将cmp这个成员函数加上的static修饰,然后,神奇的事情就发生了,这个编译器错误竟然不见了!!! 这背后发生了什么??

二、深入分析1(从函数定义出发)

? ? ? ? 从上面的改动可以看出,问题似乎在于普通成员函数与静态成员函数的区别,那我们先来回顾一下这两者的区别有哪些:

static成员函数普通成员函数
所有对象共享??
隐含this指针??
访问普通成员变量(函数)??
访问静态成员变量(函数)??
通过类名直接调用??
通过对象名直接调用??

? ? ? ? static成员函数最大的特点在于其不隶属于某个对象,而是隶属于整个类。为了说明这普通成员函数与static成员函数的具体区别,笔者特意准备了一个小demo来说明上面的这些结论:

#include <iostream>

using namespace std;

class Test
{
public:
	int func(int i) { return i; }

	static int func1(int i) { return i; }
};

int main()
{
	int(Test:: *pF)(int) = &Test::func;  //创建并初始化一个指向普通成员函数的指针

	Test t;

	cout << (t.*pF)(100) << endl;    //对于普通成员函数指针而言, 必须通过类的实例化对象才能使用, 即直接写pF(100)是会报错的

	int(*pF1)(int) = &Test::func1; //创建并初始化一个指向static成员函数的指针

	cout << pF1(200) << endl;    //对于static成员函数指针而言, 可直接根据指针名来调用static成员函数

	return 0;
}

【程序结果】

? ? ? ? ?这种声明成员函数指针的写法比较少见,部分读者或许没有见过,但就语义来看还是比较好理解的。同时,从这个例子也可以看出,static成员函数就是隶属于整个类的(首先,声明并初始化pF1时无需加上Test::修饰;其次,通过pF1来调用static成员函数时也无需通过具体的Test类对象)。这是否有种静态成员函数好比是“全局函数”的感觉呢?

三、深入分析2(借用boost库的类型推导功能)

? ? ? ? 看到这可能还是有不少读者与我一样,对上面的分析依然朦朦胧胧的,对此笔者想从函数类型可视化这一角度出发,来彻底地解决读者们的疑惑。

? ? ? ? 对原有的代码进行了适当的改造,读者们可把注意力放在cmp、cmp1、cmp2这三个函数上,同时代码中对cmp1的函数类型做了typedef别名定义为CMP1。

#include <iostream>
#include <boost/type_index.hpp>

using namespace std;

int cmp2(int i, int j)
{
	return i + j;
}

class LeaderBoard
{
private:
	using Player = pair<int, int>;  //id, score

	static bool cmp(const Player& p1, const Player& p2)
	{
		if (p1.second == p2.second) return p1.first > p2.first;

		else return p1.second > p2.second;
	}

	bool cmp1(const Player& p1, const Player& p2)
	{
		if (p1.second == p2.second) return p1.first > p2.first;

		else return p1.second > p2.second;
	}

	typedef decltype(&LeaderBoard::cmp1) CMP1;

public:
	void printType()
	{
		using boost::typeindex::type_id_with_cvr;
		cout << "CMP1 = " << type_id_with_cvr<CMP1>().pretty_name() << endl;
		cout << "cmp = " << type_id_with_cvr<decltype(&cmp)>().pretty_name() << endl;
		cout << "cmp1 = " << type_id_with_cvr<decltype(&LeaderBoard::cmp1)>().pretty_name() << endl;
		cout << "cmp2 = " << type_id_with_cvr<decltype(&cmp2)>().pretty_name() << endl;
	}
};

int main()
{
	LeaderBoard lb;

	lb.printType();

	return 0;
}

【程序结果及分析】

? ? ? ? ?从结果中可以清晰地看到:CMP1和cmp1的函数类型相同,均为LeaderBoard::*型的指针,而cmp(对应static成员函数)和cmp2(对应普通全局函数)两者的函数类型均属于同一类,即全局性的函数指针类型,没有任何类限定符修饰

? ? ? ? 这也就正好应证了“深入分析1”处所做的工作,static成员函数真就好比有种“全局函数”的感觉,但不能简单粗暴理解为static成员函数就是普通的全局函数,两者实际上还是有差别的

? ? ? ? 下次碰到类似的使用场景时,读者可自行选择是使用static成员函数还是普通全局函数,来作为decltype的推导参数。

【参阅资料】

狄泰软件学院-C++深度解析教程

C++新经典-王建伟

现代C++语言核心特性解析-谢丙堃

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-17 21:52:22  更:2022-03-17 21:53:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:51:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码