C++ primer 复习 第四章 表达式
4.1 表达式基础
表达式由一个或多个运算对象组成,字面值和变量是最简单的表达式
把运算符和运算对象结合起来可产生较复杂的表达式
一元运算符:作用于一个运算对象的运算符,例 取地址符和解引用符
二元运算符:作用于两个运算对象,例相等(==)和乘法
三元运算符:只有一个
运算符重载:当运算符作用于类类型的运算对象时,用户可以自定义其含义。例如,IO库的 >> 和 << 运算符,但是运算对象的个数,运算符的优先级是无法改变的
C++表达式非左值,即右值
C 语言中:左值可以位于赋值语句的左侧,右值不能
C++ 中左右值概念比 C 语言复杂
左值:可取到地址的表达式,当对象被作为左值时,使用的是对象的身份(内存中的位置)
右值:取不到地址的表达式,使用的是对象的值(内存中的内容)
如,常量对象是左值(可取到地址),但是它不能放到赋值语句的左边
//和C的不同之处在于,C++的左值定义是可取到地址 const int a =1; a =2;//编译报错,因为a是常量。
C++中左右值和是否可放在赋值语句左右无关
某些表达式的求值结果是对象,但它们是右值
如,取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是右值
//代码会更好理解 int a =1; int* p = &(&a); //报错,表达式必须为左值,即&a的返回值是右值,无法对其取地址 int* p = &a; int** p1 = &p;//编译通过,因p是一个左值,可对其取地址
内置解引用运算符,下标运算符,迭代器解引用运算符,string 和 vector 的下标运算符的求值结果都是左值
std::string s1 ="Hello"; char* char_1 = &s1[0];
如果表达式的求值结果是左值,decltype作用于该表达式,将得到一个引用类型,例如
int* p decltype(*p) 结果为 int& decltype(&p) 结果为 int**
表达式的计算结果,依赖运算符的优先级,结合律(左还是右),和运算对象的求值顺序
大多数情况下,不会明确指定求值顺序
//f1() 和 f2() 将在乘法之前被调用,但不知道谁先后 //因此改变了某个运算对象的值,在表达式其它地方不要使用它 int i = f1()*f2();
4.2 算术运算符
//一元负号运算符:对运算对象取负后,返回其(取负)的副本 int i =1024; int K =-i; // K是-1024 bool b1 =true; bool b2 =-b1; //true 0为false,非0为true
//取模运算符(%),即求余数:计算两个整数相除的余数 int ival =42; double dval =3.14; ival % 12; //ival % dval; 报错
//如果m和n是整数且非0,表达式(m/n)*n+m%n的求值结果和m相等 //(-m)/n 和 m/(-n) 都等于 -(m/n) //m%(-n) 等于 m%n, (-m)%n等于 -(m%n) 21%6,21/6 //3,3 21%7,21/7 //0,3 -21%-8,-21/-8 //-5,2 21%-5,21/-5 //1,-4
4.3 逻辑和关系运算符
/* 遇到空字符或以句号结束的字符进行换行 */ std::string text[3] = { "Hello", "", "World." }; for (auto& s : text){ std::cout << s; if (s.empty() || s[s.size() -1] =='.'){ std::cout << std::endl; } }
float i =1, j =0, k =0.5; // < 返回一个 bool值结果和 K 比较 if (i < j < k) { std::cout << "true" << std::endl; } else { std::cout << "true" << std::endl; } if (i < j && j < k) { std::cout << "true" << std::endl; } else { std::cout << "true" << std::endl; }
4.4 赋值运算符,递增和递减运算符
int i=0,j=0,k=0; //初始化而非赋值 const int ci = i; //初始化而非赋值 1024= k; //错误 i+j = k; //错误,算术表达式也是右值 ci = k; //const 无法修改 k =0; k =3.14; // k 为 3//c++11 允许使用初始化列表作为赋值语句右侧的运算对象 k = {3.14}; //错误,窄化转换 vector<int> vi; //初始化为空 vi= {0,1,2,3};
赋值运算满足右结合率
int ival,jval; ival = jval =0; //正确,都被赋值为 0int* pval; ival = pval =0; //错误,不能把指针赋值给 int string s1,s2; s2 = s1 ="OK"; //字符串字面值 OK 转成 string 对象(实际上是隐式转换)
赋值运算优先级较低
//不断循环,直到遇到 42int i = get_value(); while(i !=42){ //其它处理... i = get_value(); //得到下一个值 } //更好的写法,条件部分表达更清晰 int i; while( (i = get_value()) !=42){ //其它处理... }
递增和递减运算符
**注意:++ 的优先级高于 ***
int i =0,j; j =++i; //j=1,i=1,得到递增之后的值 j = i++; //j=1,i=2,得到递增之前的值 auto pbeg = v.begin(); //输出元素直到遇到第一个负值 while(pbeg != v.end() && *pbeg>=0) cout << *pbeg++ << endl; //输出当前元素,并将pbeg移动到下一个元素
运算对象可按任意顺序求值
//该循环的行为未定义 while(beg != s.end() && !isspace(*beg)) *beg = toupper(*beg++);
等号两侧的执行顺序不一定(在一个表达式里,既使用它又修改,是一个严重的错误)
编译期可能按照下面的任意一种思路处理:
先求左侧:*beg = toupper(*beg); 先求右侧:*beg+1= toupper(*beg);
也可能按别的方法处理
4.5 成员访问运算符,条件运算符和位运算符
点运算符合箭头运算符都可以访问成员
ptr -> mem 等价于 (*ptr).mem
string s1 ="a string",*p = &s1; auto n = s1.size(); //运行 string 对象 s1 的 size 成员 n = (*p).size(); //运行 p 所指对象的 size 成员 n = p->size();
条件运算符:cond ? expr1 : expr2
string finalgrade = (grade < 60) ? "fail" : "pass"; //允许嵌套(右结合率) finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass"; cout << ((grade < 60) ? "fail" : "pass"); //输出fail或pass cout << (grade < 60) ? "fail" : "pass"; //输出 0 或 1 (cout?返回cout) cout << grade < 60 ? "fail" : "pass"; //错误,试图比较cout和60
位运算符
& 两个 1 为 1,| 一个 1 为 1 ,^ 异或 一样为0,不一样为 1
/* 0UL 表示 无符号长整型 0 1UL 表示 无符号长整型 1*/ unsigned long quizl =0; //把这个值当做位的集合,在任何机器都至少32位 quizl |= 1UL << 27; //学生 27 通过了测试 quizl &= ~(1UL << 27); //学生27 没有通过考试 bool status = quizl & (1UL << 27); //学生27是否通过了考试
4.6 sizeof 运算符
struct Demo{ static int m_a; } sizeof(Demo); // Demo类型的对象所占空间大小 sizeof demo; //demo类型的大小,即sizeof(Demo) sizeof p; //指针所占空间的大小 //sizeof和*的优先级一样,并且满足右结合率(先和右边结合) //所以下面等价于 seizeof(*p) //p可以是无效指针,并不会执行解引用 sizeof *p; //p所指类型的空间大小,即sizeof(Demo) sizeof demo.a; //Demo的成员类型大小 sizeof Demo::a; //sizeof 运算能够得到整个数组的大小 constexpr size_t sz = sizeof(ia)/sizeof(*ia); int arr[sz]; //正确,sizeof返回一个常量表达式
逗号运算符
含有两个运算对象,按照从左到右的顺序依次求值
返回结果为右侧表达式的值
//逗号运算符经常被用在for循环中 vector<int> ivec = {1,2,3}; vector<int>::size_type cnt = ivec.size(); //将从size到1的值赋给ivec的元素 for(vector<int>::size_type ix =0; ix != ivec.size(); ++ix,--cnt){ ivec[ix] = cnt; }
4.7 类型转换
隐式转换:底层自动转换
int ival =3.541 +3; //为避免精度损失,编译器先执行 3.541 +3.0
算术转换:理解算术转换的办法就是研究大量例子
bool flag; char cavl; short sval; unsigned short unsval; int ival; unsigned int uival; long lval; unsigned long ulval; float fval; double dval; 3.14L +'a'; //'a'提升成int,然后该int值转换成long double dval + ival; //ival转换为double dval + fval; //fval转换为double ival = dval; //dval转换成(切除小数部分后) int flag = dval; //0为false,非0为true cval + fval; //cval提升为int,再将值转换为float sval + cval; //sval和cval都提升为int cval + lval; //cval转换为long ival + uval; //ival unsigned long unsval + ival; //根据 unsigned short 和 int 所占空间的大小进行类型提升 uival + lval; //根据unsigned int 和 long 所占空间的大小进行转换
其它隐式转换
指针的转换:
0 或字面值 nullptr 能够转换成任意指针
指向任意非常量的指针能够转换成 void*
指向任意对象的指针可以转换为 const void*
/*数组转换成指针*/ int ia[0]; //含有十个整数的数组 int *ip = ia; //ia转换成指向数组首元素的指针 /*转换成布尔类型*/ char* cp= get_string(); if(cp) //如果指针cp不是0,条件为true /*转换为常量*/ int i; const int& j = i; //非常量转换成 const int 的引用 const int*p = &i; //非常量的地址转换为 const 的地址 int &r = j,*q = p; //错误:不允许const转换为非常量,因为试图删除底层const /*类 类型定义的转换*/ string s,t ="a value"; //字符串字面值转换为string类型 while(cin >> s) //while的条件部分把 cin 转换成布尔值
显式转换
强制转换 cast-name<type> (expression)
cast-name 是static_cast,dynamic_cast,const_cast 和 reinterpret_cast 中的一种
const_cast:只能改变运算对象的底层const
/*static_cast:只要不包含底层 const,都可以使用 */ //进行强制类型转换以便指向浮点数除法 int i,j; double slope = static_cast<double>(j)/i; double d; void *p = &d; //正确:任何非常量对象的地址都能存入 void* 中 double *dp = static_cast<double*>(p); //正确 /*const_cast:只能改变运算对象的底层const*/ const char *pc; char*p = const_cast<char*>(pc); //语法正确,但是通过p写值是未定义行为 const char *cp; char*p = static_cast<char*>(cp); //static 不能转掉 const 性质 static_cast<string>(cp); //正确 const_cast<string>(cp); //错误 /*reinterpret_cast: 为运算对象的位运算提供较低层次上的重新解释*/ int *ip; char* pc = reinterpret_cast<char*>(ip); //效果类型C风格的强制类型转换
旧式强制转换
char* pc = (char*)ip; //ip是指向整型的指针,在这里和 reinterpret_cast 一样 ); //正确 /*const_cast:只能改变运算对象的底层const*/ const char *pc; char*p = const_cast<char*>(pc); //语法正确,但是通过p写值是未定义行为 const char *cp; char*p = static_cast<char*>(cp); //static 不能转掉 const 性质 static_cast<string>(cp); //正确 const_cast<string>(cp); //错误 /*reinterpret_cast: 为运算对象的位运算提供较低层次上的重新解释*/ int *ip; char* pc = reinterpret_cast<char*>(ip); //效果类型C风格的强制类型转换
旧式强制转换
char* pc = (char*)ip; //ip是指向整型的指针,在这里和 reinterpret_cast 一样