1. C++运算符
- C++运算符分两大类:单目运算符与双目运算符
- 从语法层面看,除使用关键字operator外,运算符与函数几乎没有差别
// 其中operator_symbol可以是+、&&等
return_type operator operator_symbol (...parameter list...);
- C++在支持函数的情况下为何还要提供运算符呢?
- 如果要将这个Date对象指向下一天,下面两种方法哪种更方便、更直观呢?
- 显然,方法1优于方法2:基于运算符的机制更容易使用,也更直观。
Date holiday (12, 25, 2016); ++ holiday; // 方法1:使用递增运算符 holiday.Increment(); // 方法2:使用成员函数Increment()
2. 单目运算符
- 单目运算符只对一个操作数进行操作
2.1 单目运算符的类型
运算符 | 名称 | 运算符 | 名称 |
---|---|---|---|
++ | 递增 | & | 取址 |
– | 递减 | ~ | 求反 |
* | 解除引用 | + | 正 |
-> | 成员选择 | - | 负 |
! | 逻辑非 | 转换运算符 | 转换为其他类型 |
2.2 单目递增与单目递减运算符
- 如果只想执行递增运算,可使用++object,也可使用object++,但应选择前者,这样避免创建一个未被使用的临时拷贝
#include <iostream>
using namespace std;
class Date {
private:
int day, month, year;
public:
Date (int inMonth, int inDay, int inYear)
: month (inMonth), day(inDay), year (inYear) {};
Date& operator ++ () { // 前置递增
++day;
return *this;
}
Date& operator -- () { // 前置递减
--day;
return *this;
}
Date operator ++ (int) { // 后置递增
Date copy(month, day, year);
++day;
return copy;
}
Date operator -- (int) { // 后置递减
Date copy(month, day, year);
--day;
return copy;
}
void DisplayDate() {
cout << month << " / " << day << " / " << year << endl;
}
};
int main () {
Date holiday (12, 25, 2016);
holiday.DisplayDate ();
++holiday;
holiday.DisplayDate ();
--holiday;
holiday.DisplayDate ();
return 0;
}
2.3 转换运算符
- 要让cout能够显示Date对象,只需添加一个返回const char*的运算符
#include <iostream>
#include <sstream> // new include for ostringstream
#include <string>
using namespace std;
class Date {
private:
int day, month, year;
string dateInString;
public:
Date(int inMonth, int inDay, int inYear)
: month(inMonth), day(inDay), year(inYear) {};
// 声明转换运算符时,务必使用关键字explicit,以禁止隐式转换
explicit operator const char*() {
ostringstream formattedDate; // 将整型成员转换成了一个std::string对象
formattedDate << month << " / " << day << " / " << year;
// 因为formattedDate是一个局部变量,将在运算符返回时被销毁,
// 因此运算符返回时,通过str()获得的指针将无效。将其拷贝存储在私有成员Date::dateInString中可避免
dateInString = formattedDate.str();
return dateInString.c_str();
}
};
int main () {
Date Holiday (12, 25, 2016);
cout << "Holiday is on: " << Holiday << endl;
string strHoliday (static_cast<const char*>(Holiday));
cout << "Holiday is on: " << Holiday << endl;
strHoliday = static_cast<const char*>(Date(11, 11, 2016));
cout << "Holiday is on: " << Holiday << endl;
return 0;
}
2.4 解除引用运算符(*)和成员选择运算符(->)
- 解除引用运算符(*)和成员选择运算符(->)在智能指针类编程中应用最广
- 智能指针是封装常规指针的类,旨在通过管理所有权和复制问题以简化内存管理
- 智能指针能够在指针离开作用域后释放其占用的内存
// 使用智能指针 std::unique_prt 管理动态分配的 Date 对象
#include <iostream>
#include <memory> // new include to use unique_ptr
using namespace std;
class Date {
private:
int day, month, year;
public:
Date(int inMonth, int inDay, int inYear)
: month(inMonth), day(inDay), year(inYear) {};
void DisplayDate() {
cout << month << " / " << day << " / " << year << endl;
}
};
int main() {
// 智能指针类 std::unique_ptr 实现了运算符*和->
unique_ptr<int> smartIntPtr(new int); // 智能指针类unique_ptr的模板初始化
*smartIntPtr = 42;
cout << "Integer value is: " << *smartIntPtr << endl;
unique_ptr<Date> smartHoliday (new Date(12, 25, 2016));
cout << "The new instance of date contains: ";
smartHoliday->DisplayDate();
return 0;
}
3. 双目运算符
- 对两个操作数进行操作的运算符称为双目运算符
3.1 双目运算符的类型
运算符 | 名称 | 运算符 | 名称 |
---|---|---|---|
, | 逗号 | < | 小于 |
!= | 不等于 | << | 左移 |
% | 求模 | <<= | 左移并赋值 |
%= | 求模并赋值 | <= | 小于或等于 |
& | 按位与 | = | 赋值、复制赋值和移动赋值 |
&& | 逻辑与 | == | 等于 |
&= | 按位与并赋值 | > | 大于 |
* | 乘 | >= | 大于或等于 |
*= | 乘并赋值 | >> | 右移 |
+ | 加 | >>= | 右移并赋值 |
+= | 加并赋值 | ^ | 异或 |
− | 减 | ^= | 异或并赋值 |
−= | 减并赋值 | 按位或 | |
->* | 指向成员的指针 | 按位或并赋值 | |
/ | 除 | 逻辑或 | |
/= | 除并赋值 | [] | 下标运算符 |
3.2 双目加法与双目减法运算符
#include <iostream>
using namespace std;
class Date {
private:
int day, month, year;
public:
Date (int inMonth, int inDay, int inYear)
: month(inMonth), day(inDay), year(inYear) {};
Date operator + (int daysToAdd) {
Date newDate (month, day + daysToAdd, year);
return newDate;
}
Date operator - (int daysToSub) {
return Date (month, day - daysToSub, year);
}
void DisplayDate() {
cout << month << " / " << day << " / " << year << endl;
}
};
int main() {
Date Holiday (12, 25, 2016);
cout << "Holiday on: ";
Holiday.DisplayDate ();
Date PreviousHoliday (Holiday - 19);
cout << "Previous holiday on: ";
PreviousHoliday.DisplayDate();
Date NextHoliday(Holiday + 6);
cout << "Next holiday on: ";
NextHoliday.DisplayDate ();
return 0;
}
3.3 实现运算符+=与−=
void operator+= (int daysToAdd) {
day += daysToAdd;
}
void operator-= (int daysToSub) {
day -= daysToSub;
}
3.4 重载等于运算符(==)和不等运算符(!=)
bool operator== (const Date& compareTo) {
return ((day == compareTo.day)
&& (month == compareTo.month)
&& (year == compareTo.year));
}
bool operator!= (const Date& compareTo) {
return !(this->operator==(compareTo));
}
3.5 重载运算符<、>、<=和>=
bool operator< (const Date& compareTo) {
if (year < compareTo.year)
return true;
else if ((year == compareTo.year) && (month < compareTo.month))
return true;
else if ((year == compareTo.year) && (month == compareTo.month)
&& (day < compareTo.day))
return true;
else
return false;
}
bool operator<= (const Date& compareTo) {
if (this->operator== (compareTo))
return true;
else
return this->operator< (compareTo);
}
bool operator > (const Date& compareTo) {
return !(this->operator<= (compareTo));
}
bool operator== (const Date& compareTo) {
return ((day == compareTo.day)
&& (month == compareTo.month)
&& (year == compareTo.year));
}
bool operator>= (const Date& compareTo) {
if(this->operator== (compareTo))
return true;
else
return this->operator> (compareTo);
}
3.6 重载复制赋值运算符(=)
- 有时候,需要将一个类实例的内容赋给另一个类实例
- 与复制构造函数一样,为确保进行深复制,需要提供复制赋值运算符
Date holiday(12, 25, 2016);
Date anotherHoliday(1, 1, 2017);
anotherHoliday = holiday;
ClassType& operator= (const ClassType& copySource) {
if(this != ©Source) {
// copy assignment operator implementation
}
return *this;
}
- 如果编写的类管理着动态分配的资源(如使用new分配的数组),除构造函数和析构函数外,请务必实现复制构造函数和复制赋值运算符
- 要创建不允许复制的类,可将复制构造函数和复制赋值运算符都声明为私有的
3.7 下标运算符
- 下标运算符让您能够像访问数组那样访问类
return_type& operator [] (subscript_type& subscript);
- 编写封装了动态数组的类(如封装了char* buffer的MyString)时,通过实现下标运算符,可轻松地随机访问缓冲区中的各个字符
// 第一个const,可禁止从外部通过运算符[]直接修改成员
// 第二个const,可禁止修改类的成员属性
class MyString {
// ... other class members
public:
const char& operator [] (int index) const {
// return the char at position index in buffer
}
};
一般而言,应尽可能使用const,以免无意间修改数据,并最大限度地保护类的成员属性
4. 函数运算符operator()
#include <iostream>
#include <string>
using namespace std;
class Display {
public:
void operator () (string input) const { // 这个运算符称为operator()函数
cout << input << endl;
}
};
int main () {
Display displayFuncObj; // Display对象称为函数对象
// displayFuncObj.operator () ("Display this string!"); // 与下行代码等价
displayFuncObj ("Display this string!"); //编译器隐式地将它转换为对函数operator()的调用
return 0;
}
5. 用于高性能编程的移动构造函数和移动赋值运算符
- 旨在避免复制不必要的临时值(当前语句执行完毕后就不再存在的右值)
- 对于那些管理动态分配资源的类,如动态数组类或字符串类很有用
5.1 不必要的复制带来的问题
- 这个加法运算符(+)让您能够式轻松地拼接字符串,但也可能导致性能问题
- 创建sayHello时,需要执行加法运算符两次,而每次都将创建一个按值返回的临时拷贝,导致执行复制构造函数,复制构造函数执行深复制,而生成的临时拷贝在该表达式执行完毕后就不再存在
MyString Hello("Hello ");
MyString World("World");
MyString CPP(" of C++");
MyString sayHello(Hello + World + CPP); // operator+, copy constructor
解决方法:编译器意识到需要创建临时拷贝时,将转而使用移动构造函数和移动赋值运算符
5.2 声明移动构造函数和移动赋值运算符
- 在需要创建临时右值时,遵循C++的编译器将使用移动构造函数和移动赋值运算符
- 移动构造函数和移动赋值运算符的实现中,只是将资源从源移到目的地,而没有进行复制
class Sample {
private:
Type* ptrResource;
public:
// 由于输入参数是要移动的源对象,因此不能使用const进行限定,因为它将被修改
// 输入参数的类型为 Sample&&
Sample(Sample&& moveSource) { // 移动构造函数
ptrResource = moveSource.ptrResource; // take ownership, start move
moveSource.ptrResource = NULL; // free move source of ownership
}
Sample& operator= (Sample&& moveSource) { // 移动赋值运算符
if(this != &moveSource) {
delete [] ptrResource; // free own resource
ptrResource = moveSource.ptrResource;
moveSource.ptrResource = NULL;
}
}
Sample(); // 默认构造函数
Sample(const Sample& copySource); // 复制构造函数
Sample& operator= (const Sample& copySource); // 复制赋值运算符
};
- 在没有移动构造函数时,将调用复制构造函数,它对指向的字符串进行深复制
- 移动构造函数避免了不必要的内存分配和复制步骤,从而节省了大量的处理时间
- 移动构造函数和移动赋值运算符是可选的
- 如果没有提供移动构造函数和移动赋值运算符,编译器并不会添加默认实现
6. 用户定义的字面量
- 在下述代码中,10000、3.14、‘a’和“Hello!”都是字面量常量
int bankBalance = 10000;
double pi = 3.14;
char firstAlphabet = 'a';
const char* sayHello = "Hello!";
C++增大了对字面量的支持力度,让您能够自定义字面量
- 参数ValueType 只能是下面几个之一,具体使用哪个取决于用户定义字面量的性质
- unsigned long long int:用于定义整型字面量
- long double:用于定义浮点字面量
- char、wchar_t、char16_t 和char32_t:用于定义字符字面量
- const char*:用于定义原始字符串字面量
- const char*和size_t:用于定义字符串字面量
- const wchar_t*和size_t:用于定义字符串字面量
- const char16_t*和size_t:用于定义字符串字面量
- const char32_t*和size_t:用于定义字符串字面量
ReturnType operator "" YourLiteral(ValueType value) { // conversion code here }
#include <iostream>
using namespace std;
struct Temperature {
double Kelvin;
Temperature(long double kelvin) : Kelvin(kelvin) {}
};
//
Temperature operator"" _C(long double celcius) {
return Temperature(celcius + 273);
}
Temperature operator "" _F(long double fahrenheit) {
return Temperature((fahrenheit + 459.67) * 5 / 9);
}
int main() {
Temperature k1 = 31.73_F;
Temperature k2 = 0.0_C;
cout << "k1 is " << k1.Kelvin << " Kelvin" << endl;
cout << "k2 is " << k2.Kelvin << " Kelvin" << endl;
return 0;
}
7. 不能重载的运算符
运算符 | 名称 | 运算符 | 名称 |
---|---|---|---|
. | 成员选择 | ?: | 条件三目运算符 |
.* | 指针成员选择 | sizeof | 获取对象/类类型的大小 |
:: | 作用域解析 |
Q&A
-
自己编写智能指针类时,至少需要实现哪些函数和运算符?
- 智能指针必须能够像常规指针那样使用,如 *pSmartPtr 或pSmartPtr->Func()。为此,需要实现运算符 *和->。要确保它足够智能,还需合理地编写析构函数,以自动释放/归还资源;另外,还需实现复制构造函数和复制赋值运算符,以明确定义复制和赋值的方式(也可将复制构造函数和复制赋值运算符声明为私有的,以禁止复制和赋值)
-
编写的类封装了一个动态整型数组,请问我至少应该实现哪些函数和方法?
- 编写这样的类时,必须明确定义下述情形下的行为:通过赋值直接复制对象或通过按值传递给函数间接复制对象。通常,应实现复制构造函数、复制赋值运算符和析构函数。另外,如果想改善这个类在某些情况下的性能,还应实现移动构造函数和移动赋值运算符。要能够像访问数组一样访问类实例存储的元素,可重载下标运算符operator[]