《C++ primer》第四章
一、基础
- 表达式由一个或多个运算对象组成。字面值和变量是最简单的表达式。
- 一元运算符、二元运算符、三元运算符,分别由一个、两个、三个运算对象
- 对于含有多个运算对象的复杂表达式,要理解运算符的优先级、结合率和运算对象的求值顺序。如果优先级不同,则先算优先级高的运算;如果优先级相同,则按照结合律确定如何计算。在大多数情况下,不会明确指定求值顺序。括号无视优先级和结合律
/*有四种运算符规定嘞运算对象的求值顺序 *&& //先求左侧运算对象的值,只有当左侧运算对象为真时才会继续求右侧运算对象的值 *|| //先求左侧运算对象的值,只有当左侧运算对象为假时才会继续求右侧运算对象的值 *?: // *, //从左到右*/ /*int i = f1() * f2(); //如果f1和f2之间相互影响,那么求值顺序会影响i的结果 *std::cout << i << " " << ++i << endl; //先求++i,还是先输出,对输出结果有影响* /*两条经验规则: *拿不准优先级、结合律时,使用括号强制让表达式的组合关系符合程序逻辑要求 *如果改变了运算对象的值,在表达式的其它地方不要再使用这个运算对象。当然,*++iter这种情况除外*/
- 在表达式求值时,运算对象的类型也许并不相同,此时要转化成同一种类型,转换规则有哪些?
- 重载运算符时,运算对象的类型和返回值类型可以自定义,但是运算对象个数、运算符优先级和结合率是无法改变的
- 右值是取不到地址的表达式;左值是能取到地址的表达式。
- 一个对象被用作右值时,用的是对象的值;一个对象被用作左值时,用的是对象的身份(内存中的位置)。当一个左值被当成右值时,实际上使用的是它的内同。
- 内置类型和迭代器的递增递减运算符作用域左值运算对象,前置所得的也是左值,而后置所得是右值(可以理解为得到的一个变化前的副本)
- 对于int *p,decltype(&P)得到的是int**,是左值;decltype(*P)得到的是int&,是左值。
二、运算符
2.1、算数运算符
/*算术运算符包括: * +(一元正号)、-(一元负号) * * *、/、% * * +(加号)、-(减号) * *算术运算符是从左向右结合 *加、减可以运用于指针,加减单位为指针指向的数据单元的类型所占的空间大小*/ /*算术表达式可能产生未定义结果: *Ⅰ、数学性质本身,例如除0操作 *Ⅱ、计算机表示溢出*/ /*取模操作,两个运算对象必须是整数,如何理解有的运算对象是负数的情况呢? *如果m、n皆为整数,并且n非0时,满足(m/n)*n + m%n即可 *-21%-8 = -5; -21/-8 = 2; *-21%8 = -5; -21/8 = -2;*/
2.2、逻辑和关系运算符
/*逻辑与关系元素运算符包括:(除!外,均为从左向右结合) *!,逻辑非, !expr * *<,小于, expr1 < expr2 *<=,小于等于, expr1 <= expr *>,大于, expr1 > expr2 *>=,大于等于, expr1 >= expr * *==,等于, expr1 == expr2 *!=,不等于, expr1 != expr2 * *&&,逻辑与, expr1 && expr2 *||,逻辑或, expr1 || expr2*/ /*逻辑与和逻辑或,用来判断若干条件是否同时成立\部分成立 *逻辑非,会将运算对象的值取反后返回 *关系元素符,会返回布尔值,所以几个运算符连写在一起会产生意想不到的结果 *进行比较时,除非比较的对象时布尔类型,否则尽量不用true和false与之比较*/
2.3、赋值运算符
/*赋值运算符的左侧运算对象必须是一个可修改的左值 *若赋值运算符的左右两个运算对象类型不一致,则右侧运算对象转化为左侧运算对象的类型 *C++11允许使用花括号括起来的初始值列表作为右侧的运算对象,此时,如果存在窄化转化,将会报错 *赋值运算符满足从右向左的运算顺序,对于多重赋值语句,左侧类型要么与右侧一致,要么可有右侧转化得到 *复合运算符:对对象施以某种运算,然后把计算结果再赋给该对象 *+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=均为复合运算符*/ int k{3.12}; //错误,浮点数的表达范围比int要大,窄化转换 int val, *pval; val = pval = 0; //错误,pval是int*,val是int,类型不同并且int无法通过int*转化得到 string s1, s2; s1 = s2 = "ok"; //正确,虽然ok是字面值常量,但是可以转化成string
2.4、递增和递减运算符
/*递增和递减分为前置和后置两种: *前置:对对象进行自增或自减之后,返回对象,可以作为左值 *后置,对对象进行自增或自减,返回对象修改之前的副本,是右值 *在不需要用到修改前的值时,选用前置,可以减少副本的开销*/ vector<int> vec; …… //往vec中加入一些对象 auto pbeg = vec.begin(); while (pbeg != v.end() && *beg >= 0) std::cout << *pbeg++ << std::endl; /*后置递增优先级高于解引用,故而*pbeg++相当于*(pbeg++) *如果使用前置,那么无法输出第一个元素,而且如果vec中没有负值,程序可能试图访问一个不存在的元素*/
2.5、成员访问运算符
/*点运算符和箭头运算符均可用于访问成员 *箭头运算符作用于一个指针类型的对算对象,结果是左值 *点运算符的成员所属的对象是左值,则结果为左值,否则为右值*/ struct Point { int x; int y; }; Point getPoint() { return Point{1, 2}; // 返回一个临时对象(右值) } int a = getPoint().x; // getPoint().x 是右值,因为 getPoint() 返回的是右值 getPoint().x = 10; // 错误:getPoint().x 是右值,不能赋值
2.6、条件运算符
/*条件运算符的(?:)允许我们把简单的if-else逻辑嵌入单个表达式 *嵌套条件运算符中,条件运算符是从右向左开始结合,所以靠右的条件运算构成靠左条件运算的:分支 *条件运算符优先级非常低,通常在其两侧加上括号*/ std::cout << ((grade < 60) ? "fail" : "pass") << std::endl; //正确 std::cout << (grade < 60) ? "fail" : "pass"; //输出0或者1 std::cout << grade < 60 ? "fail" : "pass" << std::endl; //错误,试图比较cout和60
2.7、位运算符
/*位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合 *位运算提供检查和设置二进制位的功能*/ /*位运算符包括:满足左结合率 *~,位取反 ~expr * *<<,左移 expr1 << expr2 *>>,右移 expr1 >> expr2 * *&,位与 expr1 & expr2 * *^,位异或 expr1 ^ expr2 * *|,位或 expr1 | expr2*/ /*关于符号位的处理没有明确规定,因此建议仅将位运算符用于处理无符号数 *若运算对象为“小整型”,则其值会被自动提升成int型 *在移位运算符中,右侧的数必须严格小于结果的位数,否则将产生未定义行为 *移位运算符的优先级比算术运算符优先级低,比关系、赋值、条件运算符高*/ unsigned long qiuz = 0; //将长整型数置为0 quiz != 1UL << 27; //将quiz的位置27置为1 quiz &= ~(1UL << 27); //将quiz的位置27置为0 bool flag = quiz & (1UL << 27) //得到quiz的位置27的状态
2.8、sizeof运算符
/*sizeof运算符的两种形式: *Ⅰ、sizeof(type),即sizeof(类型),要求某个类型所占空间大小时,一定要用括号括住类型名,否则会报错 *Ⅱ、sizeof expr,即sizeof(表达式) * *sizeof运算符的结果: *Ⅰ、对char或者char类型表达式执行sizeof运算,结果为1 *Ⅱ、对引用类型执行sizeof,得到被引用类型所占空间的大小 *Ⅲ、对指针执行sizeof,返回指针所占空间的大小 *Ⅳ、对解引用指针执行sizeof,得到指针指向的对象所占空间大小,指针不需要有效 *Ⅴ、对数组执行sizeof,不会把数组转换成指针进行处理,得到数组所有元素所占的空间大小 *Ⅵ、对string和vector进行sizeof,没有太大意义,不能得到对象中元素占用的空间大小*/ int arr[3]; int *p; int &ref = arr[0]; sizeof(arr); //大小为3个int型大小 sizeof(*p); //大小为1个int型大小 sizeof(p); //大小为1个int*型大小 sizeof(ref); //大小为1个int型大小
2.9、逗号运算符
- 逗号运算符含有两个运算对象,按照从左到右一次求值,逗号运算符的最终结果是右侧表达式的值
2.10、类型转换
- 隐式类型转化
/*隐式类型转换: *Ⅰ、在大多数表达式中,比int型小的整型值首先提升为较大的整型 *Ⅱ、在条件中,非布尔值转化为布尔值 *Ⅲ、初始化和赋值时,右侧对象转换成左侧运算对象的类型 *Ⅳ、若算数运算或关系运算的运算对象有多种类型时,需要转化成一种类型,尽量不损失精度 *Ⅴ、函数调用时,设计的类型转换*/ /*算术转换: *运算符的运算对象将转换成最宽的类型 *Ⅰ、对于bool、char、signed char、unsigned char、short、unsigned short,转换成较大的整型 *Ⅱ、一个对象是无符号,一个是有符号,如果无符号类型不小于有符号类型,则将带符号类型转化成无符号 *Ⅲ、如果有符号类型大于无符号,如果无符号类型的所有值均可存于有符号类型,则无->有,否则有->无*/ 3.14159L + 'a'; //'a'被提升为int,然后int转化为long double dval + ival; //ival转化为double dval + fval; //fval转化为double ival = dval' //dval转化成int flag = dval; //dval转化成bool cval + fval; //cval提升为int,int转化为float sval + cval; //两者均被提升为int cval + lval; //cval被转化为long ival + ulval; //ival被转化为unsigned long usval + ival; //根据unsigned short和int所占空间大小进行转化 uival + lval; //根据unsigned int和long所占空间大小进行转化 /*Ⅰ、数组转化成指针:大多数情况下,数组自动转化成指向数组首元素的指针 *数组被当作decltype的参数,作为取地址符、sizeof以及typeid等的运算对象时,不转化成指针 *Ⅱ、指针转化:0和nullptr可以转换成任意指针类型,任意类型的非常量指针能转化成void*,任意类型指针可以转化成const void* *Ⅲ、类类型定义的转化,如while(cin >> s) {},istream类型的值被转化成布尔值*/
- 显示转化
/*强制类型转化形式:cast-name<type>(expression); *其中cast-name有static-cast、dynaic-cast、const-cast、reinterpret-cast四种*/ /*static-cast:任何具有明确定义的类型转化,只要不包含底层const,均可使用该方式*/ int i, j; double dval = static_cast<double>(j) / i; void *p = &dval; //正确,任何非常量对象的地址都能存入void* double *dp = static_cast<double*>(p); //正确,将void*转化回初始的指针类型 /*const-cast:只能改变运算对象底层的const*/ const char *pc; char *p = const-cast<char*>(pc); //正确,但是通过p写值是未定义行为 string *ps = const-cast<string>(pc); //错误,const-cast只能改变常量属性 /*reinterpret-cast:为运算对象的位模式提供较底层次上的重新解释*/ 不建议使用,不学了,啊哈哈哈哈哈 /*能不进行强制类型转化就不进行强制类型转换*/ /*旧时的强制类型转化: *Ⅰ、type (expr) *Ⅱ、(type) expr*/
2.11、运算符优先级
太多了,不记了,不知道咋办就用括号
提示:
- void*是一种无类型指针,编译器无法知道它指向的具体数据类型,无法直接解引用