表达式 = 运算符 + 运算对象。字面值、变量是最简单的表达式,两个以上运算符的是复合表达式。

  • 一元运算符
    • &取地址符
    • *解引用符
  • 二元运算符
    • ==相等运算符
    • +-*/运算符
  • 三元运算符
    • a ? b : c
  • 多元运算符
    • ()函数调用运算符

运算符其实就是普通函数,也可以重载,也有参数类型转换。但是不能改变运算符的运算对象个数、优先级、结合律等。

  1. // bool fuck = a == b;
  2. bool operator==(Type left, Type right) {
  3. return left.a == left.b;
  4. }

不同运算符对运算对象的左右值有要求,有些要求必须是左值。

  1. // =(赋值运算符)
  2. // 左侧必须是左值,返回的结果也是左值。
  3. // 左值 = ( 左值,左/右值 ) {
  4. // ......
  5. // return 左值
  6. // }
  7. // &(取地址符)
  8. // 参数必须是左值,返回一个右值(指针)。
  9. // 右值 &( 左值 ) {
  10. // ......
  11. // return 左值
  12. // }
  13. //*(解引用运算符)、[](下标运算符)、*(迭代器解引用运算符)
  14. // 参数可以是左/右值,返回是左值。
  15. // 左值 *( 左/右值 ) {
  16. // ......
  17. // return 左值
  18. // }
  19. // --,++(递增/递减运算符)
  20. // 前置版本
  21. // 左值 --/++( 左值 ) {
  22. // ......
  23. // return 左值
  24. }
  25. // 引用类型 = decltype( 左值 ),如果参数是左值,返回引用类型
  26. // 类型 = decltype( 右值 ),如果参数是右值,返回类型。

一、算术运算符

运算符 功能 用法
+ 一元正号 +expr 优先级依次递减
- 一元负号 -expr
* 乘法 expr * expr
/ 除法 expr / expr
% 取余 expr % expr
+ 加法 expr + expr
- 减法 expr - expr
  1. 右值 operator算术运算符( 算术类型的右值1, 算术类型右值2(可选) ) {
  2. // 右值1和右值2类型相同,不同会先进行类型转换,数值提升(小整数提升为较大整数)
  3. return 右值
  4. }
  5. 副本 operator一元+号 ( 算数值/指针 ) {
  6. return 副本
  7. }
  8. 副本 operator一元-号( 算数值 ) {
  9. return 副本
  10. }
  1. // 如何计算取余?
  2. // 15 % 6 = ?
  3. // 15 % -6 = ?
  4. // -15 % 6 = ?
  5. // -15 % -6 = ?
  6. /************取余运算符%定义:************/
  7. // if( m是整 && n是整 && n != 0 )
  8. // (m / n) * n + m % n == m //这是取余的定义
  9. // 所以可以推出,取余公式:
  10. // m % n == m - (m / n)*n // 记住这个取余计算公式。
  11. /************取余运算符%定义:************/
  12. m % (-n) = m - (m /-n)*-n = m - (m/n)*n = m % n;
  13. (-m) % n == -m - (-m / n)*n = -(m - (m/n)*n) = -(m % n);
  14. 21 % 6 == 3; 21 / 6 == 3
  15. 21 % 7 == 0; 21 / 7 == 3
  16. -21 % -8 == -5; -21 / -8 == 2
  17. 21 % -5 == 1; 21 / -5 == -4

二、逻辑、关系运算符

比较 运算对象 返回值
关系运算符 算术类型、指针 bool
逻辑运算符 能转换成bool的表达式 bool

image.png

  1. bool右值 operator关系运算符( 算术/指针右值 ) {
  2. return bool右值
  3. }
  4. bool右值 operator逻辑运算符(可转成bool的类型右值){
  5. return bool右值
  6. }
  7. a && b // a为false,则不求值b,短路求值
  8. a || b // a为true,则不求值b,短路求值
  9. if (i < j < k) // i < j的布尔结果和 k 比较, 若 k 大于 1 则为真!
  10. if(i < j && j < k); // 优先级:算术 > 关系 > 逻辑;一元 > 二元 > 三元
  11. if((i < j) && (j < k)); // 与上等价
  12. if(val); // val非0成立。
  13. if(!val); // val为0成立
  14. if(val == true); // val == 1成立
  15. if(val == 1); // 与上等价。

三、赋值运算符=

  1. a operator(可修改的左值a,可转成右侧对象类型值b){
  2. // b与a类型不同,则b将转成类型a。
  3. return a;
  4. }
  5. int i = 0; j = 0; k = 0; // 初始化非赋值
  6. const int ci = i; // 初始化非赋值
  7. 1024 = k; // 错误:=左侧值必须是左值,1024是右值
  8. (i+j) = k; // 错误;+返回右值,=左侧要求左值。
  9. ci = k; // 错误,ci是特殊类型左值,也算是右值,不能出现在=左侧。
  10. k = 0;
  11. k = 3.14159; // k=3
  12. k = {3.14}; // 错误,窄化转换,这是{}括号的检测功能,多多使用。
  13. vector<int> vi;
  14. vi = {0, 1, 2, 3, 4, 5, 6}; // vector重载了=运算符。
  15. ival = jval = 0; // 右结合定律,
  16. int ival , *pval ;
  17. ival = pval = 0 ; // 错误:pval = 0的返回值是int*类型。
  18. /***********复合运算符***********/
  19. += -= *= /= %= // 算术运算
  20. <<= >>= &= ^= |= // 位运算

四、递增++、递减—运算符

  1. // 前缀递增/递减,++a, --b
  2. // 返回对象本身
  3. T& T::operator++() {
  4. *this += 1;
  5. return *this;
  6. }
  7. // 后缀递增/递减,a--, b++
  8. // 返回对象的副本
  9. T T::operator++(int){
  10. T ret = *this;
  11. *this += 1;
  12. return ret;
  13. }
  1. int i, *p = 1, &i;
  2. *p++; // 等价于*(p++),递增 > *(解引用)
  3. *p + 1; // 等价于(*p)+1,一元(*) > 二元(+)
  4. cout << *iter++ << endl;
  5. //简洁为美,且少错。多使用上面,代替下面,在C++中很常见。
  6. cout << *iter << endl; ++iter;

五、成员访问运算符

  1. T T::operator.( ){ // 点运算符
  2. }
  3. 左值 T::operator->( T* ){ // ->运算符
  4. return 左值;
  5. }

六、条件运算符

  1. auto fuck = cond ? expr1 : expr2;
  2. // 当cond为true时,对expr1求值并返回。
  3. // 当cond为false时,对expr2求值并返回。
  4. // 当expr1、expr2都是左值或可以转换成左值时,返回左值,否则返回右值。

七、位运算符

运算符 功能 用法
~ 位求反 ~expr
<< 左移 expr1 << expr2
>> 右移 expr1 >> expr2
& 位与 expr & expr
^ 位异或 expr ^ expr
| 位或 expr | expr

移位运算符( <<、>>)也叫IO运算符。

  1. int i = 1; //
  2. char c = 2; //
  3. int i1 = -1; // 有符号负数。
  4. c >> 1; // 小整型会提升到大整型:char会提升到int。
  5. auto k = i & c; // c会数值提升成int类型,k的类型就变成了int。
  6. auto f = i1 >> 1; // 行为未定义,i1是有符号位,右移结果依赖于机器。
  7. auto h = ((unsigned int) i1) >> 1; // 2^32 - 1,h的类型是unsigned int
  8. // 无符号的右移最左边补齐0,因此位运算符的运算对象最好是无符号。
  9. // 无符号i1的二进制: 11111111 11111111 11111111 11111110
  10. // i1 >> 1: 01111111 11111111 11111111 11111111
  11. unsigned char bits = 0227; // 10010111
  12. // bits会数值提升成int
  13. ~bits; // 11111111 11111111 11111111 01101000

八、sizeof运算符

返回sizeof(…),括号里面…所代表的数据类型占用的字节数。在编译阶段完成,因此如果…是表达式,不会计算其结果,更不会检查表达式的逻辑错误(空指针,未初始化)。

  1. // 括号里面可以是type某种数据类型(内置类型、类类型)、任意表达式
  2. size_t num = sizeof(type) // 类型的字节数。
  3. size_t num = sizeof(expr) // 返回表达式结果类型的字节数,不计算。
  4. sizeof(char) == 1
  5. sizeof(*p) == 指针对象的类型大小
  6. sizeof(string) == string的大小
  7. sizeof(vector) == vector的大小
  8. sizeof(引用) == 引用对象的大小
  9. sizeof(指针) == 指针值的大小(4字节、8字节)
  10. sizeof(数组) == 数组大小
  11. A a; // 类型A可以是任意类型
  12. A& b = a; // 引用
  13. A* c = &a; // 指针
  14. A array[10]; // 数组
  15. int size = sizeof(A) // A类型的字节数
  16. int size = sizeof(a); // A类型的字节数
  17. int size = sizeof(b); // A类型的字节数
  18. int size = sizeof(c); // 一个指针的字节数,4/8个字节,平台各异。
  19. int size = sizeof(array); // 数组的大小,10 * sizeof(A)
  20. void fuck(A a[], int length)
  21. {
  22. int size = sizeof(a); // 这是指针大小,数组是转成了指针进行参数传递。
  23. }
  24. Sales_data data , *p;
  25. sizeof(Sales_data); // 存储 Sales_data 类型的对象所占的空间大小
  26. sizeof(data); // 与上等价
  27. sizeof(p); // 指针所占的空间大小
  28. sizeof(*p); // 等价于sizeof(data)
  29. sizeof(data.revenue); // Sales_data 的 revenue 成员对应类型 的大小
  30. sizeof(Sales_data::revenue); // 成员revenue的类型的大小

计算类大小

sizeof(类)是计算类对象的大小,考虑因子如下:

  • 内存对齐填充的字节数
  • 非静态成员变量,静态成员变量在全局区,并不在对象内存中。
  • 是否为有虚函数,有虚函数则所有类对象都多了一个虚函数表指针。
  • 是否是虚继承
  • 空类大小为1个字节 ```cpp

// 假设在64位编译器中编译,对齐基数为8字节

// A的成员变量首地址必须被这个数整除,min(对齐基数,A中最宽基本类型大小) = 4字节。 class A { private: static int s_var; // 不影响类对象大小 const int c_var; // 4字节 int var; // 4字节(上) + 4字节(var) = 8字节 char var1; // 8字节(上)+ 1字节(var1)+ 3字节(填充)= 12字节。 public: A(int temp) : c_var(temp){} // 成员函数,不影响 ~A(){} // 成员函数,不影响

  1. virtual void f() { cout << "A::f" << endl; } // 有虚函数,则每个类对象都会有一个虚函数表指针
  2. virtual void g() { cout << "A::g" << endl; } // 多个虚函数,也都是只添加一个虚函数表指针
  3. virtual void h() { cout << "A::h" << endl; }

}

int main(){ A a(4); A *p;

  1. cout << sizeof(p) << endl; // 8字节,指针大小。
  2. cout << sizeof(ex1) << endl; // 24字节
  3. return 0;

}

  1. <a name="SWnsU"></a>
  2. ## 与strlen区别
  3. strlen是c库函数,在<cstring>中声明。返回字符串的长度(以\0结尾,不包含\0字符)。在运行时计算长度。<br />
  4. ```cpp
  5. // strlen源代码
  6. size_t strlen(const char* str){
  7. size_t size_ret = 0;
  8. while(*str++) ++size_ret;
  9. return size_ret;
  10. }

九、逗号运算符

  1. // expr1, expr2,这就是执行了,号运算符
  2. // expr1左侧表达式,expr2右侧表达式
  3. expr2结果 operator,(expr1, expr2) {
  4. 1、对expr1表达式求值,然后丢弃
  5. 2、对expr2表达式求值,并返回。
  6. }

十、运算符优先级

算术 > 关系 > 逻辑。
一元 > 二元 > 三元。

上往下,优先级依次降低
运算符 功能 用法 结合律
:: 全局作用域 ::name
:: 类作用域 class::name
:: 命名空间作用域 namespace::name
. 成员选择 a.mem
-> 成员选择 p->mem
[] 下标 expr[expr]
() 函数调用 func(params)
() 类型构造 type(expr_list)
++ 后置递增 lvalue++
后置递减运算 lvalue—
typeid 类型ID typeid(type)
typeid 运行时类型ID typeid(expr)
explicit cast 类型转换 cast_name(expr)
++ 前置递增 ++lvalue
前置递减 —lvaule
~ 位求反 ~expr
逻辑非 !expr
- 一元负号 -expr
+ 一元正号 +expr
* 解引用 *expr
& 取地址 &expr
() 类型转换 (type)expr
sizeof 对象的大小 sizeof expr
sizeof 类型大小 sizeof(type)
sizeof … 参数包的大小 sizeof…(name)
new 创建对象 new type
new[] 创建数组 new type[size]
delete 释放对象 delete expr
delete[] 释放数组 delete[] expr
noexcept 能否抛出异常 noexcept(expr)
->* 指向成员选择的指针 ptr->*ptr_to_mem
.* 指向成员选择的指针 obj.*ptr_to_mem
* 乘法 expr * expr
/ 除法 expr / expr
% 取模 expr % expr
+ 加法 expr + expr
- 减法 expr - expr
<< 向左移位 expr << expr
>> 向右移位 expr >> expr
< 小于 expr < expr
<= 小于等于 expr <= expr
> 大于 expr > expr
>= 大于等于 expr >= expr
== 等于 expr == expr
!= 不等于 expr != expr
& 位与 expr & expr
^ 位异或 expr ^ expr
| 位或 expr | expr
&& 逻辑与 expr && expr
|| 逻辑或 expr || expr
? : 条件 expr ? expr : expr
= 赋值 lvalue = expr
*=, /= ,%=,+=
-=,<<=,>>=,&=,|=,^=
复合赋值 lvaule += expr
throw 抛出异常 throw expr
, 逗号 expr, expr