文章目录
直接上代码
// #define int long long
#ifndef MOD
#define MOD 1e9+7
class Num {
private:
static const int mod = MOD;
int val = 0;
protected:
static inline int norm(int x) {
if (x < 0) {
int cnt = abs(x)/mod + 1;
x += mod*cnt;
}
return x%mod;
}
static int binPow(int base, int expo, int p) {
if (expo == 0) { // 防止 mod 1
return 1 % p;
}
base %= p;
int half = binPow(base, expo>>1, p);
if (expo & 1) {
return half*half%p * base%p;
} else {
return half*half%p;
}
}
public:
Num() {}
Num(int val):val(norm(val)) {}
Num(const Num& num) {
this->val = num.val;
}
~Num() {}
int getVal() {
return this->val;
}
void setVal(const int val) {
this->val = norm(val);
}
int& operator()() {
return this->val;
}
public:
friend std::istream& operator>>(std::istream& cin, Num& num) {
cin >> num.val;
num.val = num.norm(num.val);
return cin;
}
friend std::ostream& operator<<(std::ostream& cout, Num& num) {
cout << num.val;
return cout;
}
friend std::ostream& operator<<(std::ostream& cout, Num&& num) {
cout << num.val;
return cout;
}
public:
Num& operator+=(const Num& rhs) {
val = norm(val + rhs.val);
return *this;
}
Num& operator-=(const Num& rhs) {
val = norm(val - rhs.val);
return *this;
}
Num& operator*=(const Num& rhs) {
val = norm(val * rhs.val);
return *this;
}
Num& operator/=(const Num& rhs) {
int inverseElement = binPow(rhs.val, mod-2, mod);
val = norm(val * inverseElement);
return *this;
}
public:
Num operator+() {
Num tmp = val;
return tmp;
}
Num operator-() {
Num tmp = -val;
return tmp;
}
friend Num operator+(const Num& lhs, const Num& rhs) {
Num tmp = lhs;
tmp += rhs;
return tmp;
}
friend Num operator-(const Num& lhs, const Num& rhs) {
Num tmp = lhs;
tmp -= rhs;
return tmp;
}
friend Num operator*(const Num& lhs, const Num& rhs) {
Num tmp = lhs;
tmp *= rhs;
return tmp;
}
friend Num operator/(const Num& lhs, const Num& rhs) {
Num tmp = lhs;
tmp /= rhs;
return tmp;
}
public:
Num operator++(signed) { // 后置
Num tmp = *this;
val = norm(val+1);
return tmp;
}
Num operator--(signed) { // 后置
Num tmp = *this;
val = norm(val-1);
return tmp;
}
Num& operator++() { // 前置
val = norm(val+1);
return *this;
}
Num& operator--() { // 前置
val = norm(val-1);
return *this;
}
public:
bool operator!() const {
return val == 0;
}
bool operator==(const Num& rhs) const {
return val == rhs.val;
}
bool operator!=(const Num& rhs) const {
return val != rhs.val;
}
bool operator>(const Num& rhs) const {
return val > rhs.val;
}
bool operator>=(const Num& rhs) const {
return val >= rhs.val;
}
bool operator<(const Num& rhs) const {
return val < rhs.val;
}
bool operator<=(const Num& rhs) const {
return val <= rhs.val;
}
bool operator&&(const Num& rhs) const {
return val && rhs.val;
}
bool operator||(const Num& rhs) const {
return val || rhs.val;
}
};
#endif // MOD
前言
在一些题目或情景中,需要我们对一个数进行取模运算,但是每次手动写取模会比较麻烦,因此就有了本文。
在C++,C#等语言中支持重载运算符,那么我们就可以自己写一个工具类,来满足自动取模的要求。
各部分介绍
宏定义
定义一个用来取模的宏 MOD 定义的时候可以灵活一点,按照需求定义
注意有时在算法竞赛中会习惯无脑开long long
这里和上文虽然注释了,但在实际中最好默认开启,防止乘法直接溢出
本文默认开启了long long
// #define int long long
#ifndef MOD
#define MOD 1e9+7
class Num {};
#endif // MOD
成员变量
mod 可以定义为静态常量
val 设不设初始值其实都可以
private:
static const int mod = MOD;
int val = 0;
辅助函数
static 静态成员方法,所有对象公用一个
inline 内联函数,小代码展开
static inline int norm(int x);
norm函数保证数值在每次变化时自动计算取模,不然每次重载也要手动写取模也会比较麻烦
static int binPow(int base, int expo, int p);
binPow快速幂,在除法取模中需要用到。这个涉及数论的知识,不过多介绍。
protected:
static inline int norm(int x) {
if (x < 0) {
int cnt = abs(x)/mod + 1;
x += mod*cnt;
}
return x%mod;
}
static int binPow(int base, int expo, int p) {
if (expo == 0) { // 防止 mod 1
return 1 % p;
}
base %= p;
int half = binPow(base, expo>>1, p);
if (expo & 1) {
return half*half%p * base%p;
} else {
return half*half%p;
}
}
构造和析构
注意:
拷贝构造的时候一定要
传引用
,否则会无限递归,在编译时还不会警告普通构造要调用
norm()
,下文不再多次强调norm()
public:
Num() {}
Num(int val):val(norm(val)) {}
Num(const Num& num) {
this->val = num.val;
}
~Num() {}
空类的默认成员函数
- 缺省构造函数 (无参)
- 拷贝构造函数
- 析构函数
- 赋值运算符
- 取址运算符
- 取值运算符const
get/set
其实用的不多,但为了规范就写上了
其实可以等号直接赋值,但用等号会强制类型转换,生成一个临时变量
public:
int getVal() {
return this->val;
}
void setVal(const int val) {
this->val = norm(val);
}
仿函数
目的是为了方便快速直接获取值
在一些必要情况下,如:数组下标等
同时解决上面getVal()返回的是普通值而不是引用的情况。
public:
int& operator()() {
return this->val;
}
标准输入输出
注意:
一个cpp程序只有一个cin,cout 因此必须传入引用,且为了达到链式编程的效果,需要重新传引用回去
在
friend std::ostream& operator<<(std::ostream& cout, Num num)
时为了避免频繁的创造临时变量,可以分成两个左值和右值都写一下。
public:
friend std::istream& operator>>(std::istream& cin, Num& num) {
cin >> num.val;
num.val = num.norm(num.val);
return cin;
}
friend std::ostream& operator<<(std::ostream& cout, Num& num) {
cout << num.val;
return cout;
}
friend std::ostream& operator<<(std::ostream& cout, Num&& num) {
cout << num.val;
return cout;
}
重载+= -= *= /=
由于这是对操作符左边的本身的操作,因此需要返回本身。
这里可以用到this指针,取其值并用返回引用
return *this;
public:
Num& operator+=(const Num& rhs) {
val = norm(val + rhs.val);
return *this;
}
Num& operator-=(const Num& rhs) {
val = norm(val - rhs.val);
return *this;
}
Num& operator*=(const Num& rhs) {
val = norm(val * rhs.val);
return *this;
}
Num& operator/=(const Num& rhs) {
int inverseElement = binPow(rhs.val, mod-2, mod);
val = norm(val * inverseElement);
return *this;
}
重载+ - * /
无参的表示正负号,其实用处不大
注意普通的±*/是生成一个临时的右值,且不会对数值本身有任何改变,因此我们临时创建一个变量
由于二元运算符的重载只能有一个参数或者无参
因此有两个参数时只能定义为友元函数
public:
Num operator+() {
Num tmp = val;
return tmp;
}
Num operator-() {
Num tmp = -val;
return tmp;
}
friend Num operator+(const Num& lhs, const Num& rhs) {
Num tmp = lhs;
tmp += rhs;
return tmp;
}
friend Num operator-(const Num& lhs, const Num& rhs) {
Num tmp = lhs;
tmp -= rhs;
return tmp;
}
friend Num operator*(const Num& lhs, const Num& rhs) {
Num tmp = lhs;
tmp *= rhs;
return tmp;
}
friend Num operator/(const Num& lhs, const Num& rhs) {
Num tmp = lhs;
tmp /= rhs;
return tmp;
}
重载自增,自减运算符++ --
注意前置和后置的区别
++i 先加后用 需要返回引用,链式的思想
i++ 先用后加 是一个临时值
重载后置时,需要用一个int占位符表示
由于
#define int long long
的存在这里的占位符可以写成
signed
public:
Num operator++(signed) { // 后置
Num tmp = *this;
val = norm(val+1);
return tmp;
}
Num operator--(signed) { // 后置
Num tmp = *this;
val = norm(val-1);
return tmp;
}
Num& operator++() { // 前置
val = norm(val+1);
return *this;
}
Num& operator--() { // 前置
val = norm(val-1);
return *this;
}
重载比较、逻辑运算符
注意:
在重载比较运算符的时候最好将函数写成
const
,否则会在一些#include<algorithm>
的算法中会报错且这些函数均是返回布尔值,不会改变内部的成员变量,因此全写成const
public:
bool operator!() const {
return val == 0;
}
bool operator==(const Num& rhs) const {
return val == rhs.val;
}
bool operator!=(const Num& rhs) const {
return val != rhs.val;
}
bool operator>(const Num& rhs) const {
return val > rhs.val;
}
bool operator>=(const Num& rhs) const {
return val >= rhs.val;
}
bool operator<(const Num& rhs) const {
return val < rhs.val;
}
bool operator<=(const Num& rhs) const {
return val <= rhs.val;
}
bool operator&&(const Num& rhs) const {
return val && rhs.val;
}
bool operator||(const Num& rhs) const {
return val || rhs.val;
}
一些使用注意点
Num 本质是一个类
不能直接在if等语句中用非0即1的思想,要像java一样老老实实的用符号判断出布尔值
不能直接写成数组下标,可以用getVal()或者仿函数来写
和普通数值进行运算的时候,一定要注意:必须Num 左, 普通数值 右
若想要 普通数值 左, Num 右
则需要以友元函数的形式重载操作符,上文代码中未写出(又要写一堆函数,代码量太大了)
signed main () {
Num num = 100;
// 普通数值在左边不行
// if (0 < num)
if (num > 0) {
vector<Num> arr(num()+1);
/**
num * 114514
这里实际上是进行了类型转化
先将114514转化成Num类型的数据,再计算(if 中的 num > 0 也是)
可以在构造和析构中输出检验
*/
iota(arr.begin(), arr.end(), num * 114514);
cout << arr[num.getVal()] << endl;
}
system("pause");
return 0;
}