c++基础:4,5-表达式和语句

179 阅读6分钟

表达式

  • 表达式:表达式由一个或多个运算对象组成,对表达式求值可以获得一个结果,字面值和变量是最简单的表达式。

    核心是可以求值,字面值和变量都可以获得一个值

  • 左值和右值:当一个对象被用作右值时,用的是对象的值;当对象被用作左值的时,用的是对象的内存地址;

    在等号右边取的是对象的值,在等号左边取的是对象的地址(联想"对象是具有某种数据类型的内存空间")

  • 递增++和递减--运算符: 前置版本将对象本身作为左值返回,后置版本将对象原始值的副本作为左值返回

    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:转换对象具有明确定义的类型,不含底层const

      int i = 100;
      double d = static_cast<double>(i); // i 显式转换为double
      
      void *p = &d;
      double *pd = static_cast<double*>(p); // 从void*中取回类型
      
    • const_cast: 将常量对象转换为非常量,且只能转换底层const

      const 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版