C++ primer 复习 第四章 表达式 复习

简介: C++ primer 复习 第四章 表达式 复习

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"; //输出 01 (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 一样


相关文章
|
28天前
|
算法 编译器 C++
【C++11】lambda表达式
C++11 引入了 Lambda 表达式,这是一种定义匿名函数的方式,极大提升了代码的简洁性和可维护性。本文详细介绍了 Lambda 表达式的语法、捕获机制及应用场景,包括在标准算法、排序和事件回调中的使用,以及高级特性如捕获 `this` 指针和可变 Lambda 表达式。通过这些内容,读者可以全面掌握 Lambda 表达式,提升 C++ 编程技能。
67 3
|
6月前
|
编译器 数据安全/隐私保护 C++
c++primer plus 6 读书笔记 第十三章 类继承
c++primer plus 6 读书笔记 第十三章 类继承
|
3月前
|
算法 编译器 程序员
C++ 11新特性之Lambda表达式
C++ 11新特性之Lambda表达式
19 0
|
5月前
|
安全 编译器 C++
C++一分钟之-泛型Lambda表达式
【7月更文挑战第16天】C++14引入泛型lambda,允许lambda接受任意类型参数,如`[](auto a, auto b) { return a + b; }`。但这也带来类型推导失败、隐式转换和模板参数推导等问题。要避免这些问题,可以明确类型约束、限制隐式转换或显式指定模板参数。示例中,`safeAdd` lambda使用`static_assert`确保只对算术类型执行,展示了一种安全使用泛型lambda的方法。
73 1
|
6月前
|
C++
C++ Primer Plus (第6版)中文版 (使用XMind整理)
C++ Primer Plus (第6版)中文版 (使用XMind整理)
C++ Primer Plus (第6版)中文版 (使用XMind整理)
|
6月前
|
算法 编译器 C++
C++一分钟之—Lambda表达式初探
【6月更文挑战第22天】C++的Lambda表达式是匿名函数的快捷方式,增强函数式编程能力。基本语法:`[capture](params) -&gt; ret_type { body }`。例如,简单的加法lambda:`[](int a, int b) { return a + b; }`。Lambda可用于捕获外部变量(值/引用),作为函数参数,如在`std::sort`中定制比较。注意点包括正确使用捕获列表、`mutable`关键字和返回类型推导。通过实践和理解这些概念,可以写出更简洁高效的C++代码。
57 13
|
6月前
|
C++
C++语言的lambda表达式
C++从函数对象到lambda表达式以及操作参数化
|
6月前
|
C++
C++一分钟之-理解C++的运算符与表达式
【6月更文挑战第18C++的运算符和表达式构成了编程的基础,涉及数学计算、逻辑判断、对象操作和内存管理。算术、关系、逻辑、位、赋值运算符各有用途,如`+`、`-`做加减,`==`、`!=`做比较。理解运算符优先级和结合律至关重要。常见错误包括优先级混淆、整数除法截断、逻辑运算符误用和位运算误解。解决策略包括明确优先级、确保浮点数除法、正确使用逻辑运算符和谨慎进行位运算。通过实例代码学习,如 `(a &gt; b) ? &quot;greater&quot; : &quot;not greater&quot;`,能够帮助更好地理解和应用这些概念。掌握这些基础知识是编写高效、清晰C++代码的关键。
45 3
|
6月前
|
C++
c++primer plus 6 读书笔记 第十四章 C++中的代码重用
c++primer plus 6 读书笔记 第十四章 C++中的代码重用
|
6月前
|
SQL 人工智能 算法
技术心得记录:模板函数函数模板FunctionTemplate(C++Primer
技术心得记录:模板函数函数模板FunctionTemplate(C++Primer