表达式
-
表达式:表达式由一个或多个运算对象组成,对表达式求值可以获得一个结果,字面值和变量是最简单的表达式。
核心是可以求值,字面值和变量都可以获得一个值
-
左值和右值:当一个对象被用作右值时,用的是对象的值;当对象被用作左值的时,用的是对象的内存地址;
在等号右边取的是对象的值,在等号左边取的是对象的地址(联想"对象是具有某种数据类型的内存空间")
-
递增
++和递减--运算符: 前置版本将对象本身作为左值返回,后置版本将对象原始值的副本作为左值返回int c = 0; ++c; // 返回c本身,值为1 c++; // 返回c递增前的副本,值为0;c当前值为1#include <iostream> #include <string> int main() { std::string str = "hello"; auto beg = str.begin(); // ++和--运算符优先级高于解引用运算符 // *beg++ 本质是*(beg++), ++将beg移到下一位,然后返回前一位的副本,再解引用 while (beg != str.end()) { std::cout << *beg++ << std::endl; } return 0; } -
位运算符
-
如果运算对象是小整型,则它的值会被自动提升为较大的整数型,如
char会被提升为int -
移位运算: 左移运算
<<在二进制位右侧补0,右移运算>>在二进制位左侧补0或符号位应该仅将位运算应用于无符号类型
对于有符号类型且为负,符号位的处理依赖于机器,且左移操作可能改变符号位的值,产生未定义的行为
// 8bit char // 1*pow(2,3)+1*pow(2,2)+0*pow(2,1)+1*pow(2,0) = 13 00001101 // 左移2位,提升为32bit的int型,右侧补2个0 // 1*pow(2,5)+1*pow(2,4)+0*pow(2,3)+1*pow(2,2) = 52 00000000 00000000 00000000 00110100 // 右移2位,提升为32bit的int型,左侧补2个0,原左侧位01被丢弃 // 1*pow(2,1)+1*pow(2,0) = 3 00000000 00000000 00000000 00001101 // 32bit 00000000 00000000 00000000 00000011 // 左侧补2个0,01被丢弃 -
位反~:0得1,1得0
00001101 -------- 11110010 -
位与&: 对应位均为1得1,其他得0 (对应位只要有一个0得0)
00001101 11011101 -------- 00001101 -
位或| : 对应位只要有一个1得1,其他得0
00001101 11011101 -------- 11011101 -
位异或^:对应位相异得1,相同得0
00001101 11011101 -------- 11010000
-
-
sizeof运算符:返回一个类型或表达式结果类型的大小(字节数)int n, *p; sizeof(int); //int类型对象空间大小 sizeof n; // n的类型所占空间大小,即sizeof(int) sizeof *p; // p所指对象的类型空间大小,也为sizeof(int) sizeof p; // p是一个指针,返回指针类型所占空间大小 -
隐式类型转换
-
隐式类型转换被设计为尽可能避免损失精度,因此运算对象将转换为最宽的类型,例如浮点数类型和整数类型的运算,整数类型将转换为相应的浮点数类型;
-
符号类型的转换
一般原则是:小的转大的,有符号的转无符号的
- 基本类型相同时,有符号类型优先转换为无符号类型
- 基本类型不同但大小相同时,有符号类型优先转换为无符号类型
- 基本类型不同且大小不同时,小的类型优先转换为大的类型
int a = 100; unsigned int b= 100; long c = 100; // 基本类型相同时,有符号类型优先转换为无符号类型 a + b; // a会转换为unsigned int // 基本类型不同但大小相同时,有符号类型优先转换为无符号类型 b + c; // 如果long和int大小相同,则long优先转换为unsigned int // 基本类型不同且大小不同时,小的类型优先转换为大的类型 b + c; // 如果long和int大小不同,则unsigned int 优先转换为long -
非常量可以隐式转换为常量
int i = 0; const int &ri = i; -
算术类型和指针类型可以隐式转换为布尔类型
int i = 0; if (i) {;}; // i 转换为false int arr[] = {0,1,2}; while (arr){;}; // arr转换为首元素地址,非空指针,再转换为true
-
-
显式类型转换
-
static_cast:转换对象具有明确定义的类型,不含底层constint i = 100; double d = static_cast<double>(i); // i 显式转换为double void *p = &d; double *pd = static_cast<double*>(p); // 从void*中取回类型 -
const_cast: 将常量对象转换为非常量,且只能转换底层constconst char *pc; char *p = const_cast<char*>(pc); // 转换可以,但通过p写值是未定义的行为 // 下面转换也是ok的,因为字符串本质是字符数组,首元素地址类型为const char* string s = static_cast<string>(pc); -
reinterpret_cast: 对象类型转换将从位上重新解释int *pi; char *pc = reinterpret_cast<char*>(pi); // 将一个int*类型转换为char*类型 // 但注意pc所指的真实对象是int而非char,转换虽然合法,但操作可能导致异常的运行时行为,能不用就不用
-
语句
语句: 以分号结束。一个表达式末尾加上分号就成了表达式语句。
常用语句
; // 最简单的语句,空语句
int i = 0; // 赋值语句
i + 5 // 表达式
i + 5; // 表达式语句
// if-else语句
if (i){
;
} else {
;
}
// while 语句
while(i){
break; // break 语句
}
// do while 语句
do {
;
} while(i){
break;
}
// for 语句
for (; i>0; --ix){
continue; // continue 语句
}
switch-case语句
case标签必须是整型常量表达式;- 如果某个
case标签匹配成功,将从该标签开始往后顺序执行所有的case分支,除非显式break,否则将直到switch结尾; - 不允许跨过变量的初始化语句,而直接跳转到变量作用域的另一个位置。
- 如果需要为某个case分支定义并初始化一个变量,应该把变量定义在块内,从而确保后面的所有case分支都在变量的作用域之外。
unsigned cnt = 0, other_cnt = 0;
// ...
// ch 匹配到a、e、i、o、u任意一个时,cnt均会+1
switch (ch) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
++cnt;
break;
default:
++other_cnt;
break;
}
switch (ch) {
case 'a':
bool match_a = true;
case 'e':
cout << acnt << endl; // not ok, 不允许跨过变量的初始化语句
case 'i':
{
bool match_i = true; // ok, 应该把变量定义在块内,避免影响后续case分支
}
case 'o':
case 'u':
++cnt;
break;
default:
++other_cnt;
break;
}
try-catch语句
try语句块内声明的变量在块外部无法访问,在catch子句中也无法访问.
#include <iostream>
#include <stdexcept>
#include <string>
int main() {
std::string item1, item2;
while (std::cin >> item1 >> item2) {
try {
if (item1 != item2) {
throw std::runtime_error("not the same input items!");
}
} catch (std::runtime_error err) {
std::cout << err.what() << "\nTry again? Enter y or n" << std::endl;
char c;
std::cin >> c;
if (!std::cin || c == 'n') {
break;
}
}
}
return 0;
}
参考书籍:《C++ Primer》第5版