zoukankan      html  css  js  c++  java
  • c++学习(三):表达式和语句

    四、 表达式
    1. 算术运算符
    高优先级的操作符要比低优先级的结合得更紧密。这些算术操作符都是左结合。

    操作符

    功能

    用法

    +

    unary plus(一元正号)

     

    -

    unary minus(一元负号)

     

    *

    multiplication(乘法)

     

    /

    division(除法)

     

    %

    remainder(求余)

     

    +

    addition(加法)

     

    -

    subtraction(减法)

     

     
    2. 位操作符
    左结合,移位操作符具有中等优先级:其优先级比算术操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。

    操作符

    功能

     

    ~

    位求反

     

    << 

    左移

    在右边插入 0 以补充空位

    >> 

    右移

    如果其操作数是无符号数,则从左边开始插入 0;如果操作数是有符号数,则插入符号位的副本或者 0 值

    &

    位与

    如果两个操作数对应的位都为 1,则操作结果中该位为 1

    ^

    位异或

    在每个位的位置,如果两个操作数对应的位只有一个为 1,则操作结果中该位为 1,否则为 0。

    |

    位或

     

     

        1. 与其他二元操作符不同,赋值操作具有右结合特性。多个赋值操作中,各对象必须具有相同的数据类型,或者具有可转换为同一类型的数据类型。
      2. 使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数。
      3. 只有在必要时才使用后置操作符:因为前置操作需要做的工作更少,只需加1后返回加1后的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。
      4. 假设有一个指向类类型对象的指针(或迭代器),下面的表达式相互等价:
        (*p).foo; // dereference p to get an object and fetch its member namedfoo
        p->foo; // equivalent way to fetch the foo from the object to whichp points

    3. sizeof
    sizeof操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t,长度的单位是字节。
    该操作符有以下三种语法形式:
      sizeof (type name);
      sizeof (expr);
      sizeof expr;
    将 sizeof 用于 expr 时,并没有计算表达式 expr 的值。特别是在 sizeof*p 中,指针 p 可以持有一个无效地址,因为不需要对p做解引用操作。
    使用 sizeof 的结果部分地依赖所涉及的类型:
      • 对 char 类型或值为 char 类型的表达式做 sizeof 操作保证得 1。
      • 对引用类型做 sizeof 操作将返回存放此引用类型对象所需的内在空间大小。
      • 对指针做 sizeof 操作将返回存放指针所需的内在大小;注意,如果要获取该指针所指向对象的大小,则必须对指针进行引用。
      • 对数组做 sizeof 操作等效于将对其元素类型做 sizeof 操作的结果乘上数组元素的个数。

      5. 逗号表达式是一组由逗号分隔的表达式,这些表达式从左向右计算。逗号表达式的结果是其最右边表达式的值。如果最右边的操作数是左值,则逗号表达式的值也是左值。
    4. 操作符的优先级

    操作符

    结合性

    功能

    用法

    ::

    L

    全局作用域

    :: name

    ::

    L

    类作用域

    class::name

    ::

    L

    命名空间作用域

    namespace::name

    .

    L

    成员选择

    • object.member

    ->

    L

    成员选择

    pointer.member

    []

    L

    下标

    variable[expr]

    ()

    L

    函数调用

    name(expr_list)

    ()

    L

    类型构造

    type(expr_list)

    ++

    R

    后自增

     

    --

    R

    后自减

     

    typeid

    R

    类型ID

    typeid (type/expr)

    explicit cast

    R

    显示类型转换

    cast_name<type>(expr)

    sizeof

    R

     

    sizeof expr/(type)

    ++

    R

    前自增

     

    --

    R

    前自减

     

    ~

    R

    位求反

     

    R

    逻辑非

     

    -

    R

    一元负号

     

    +

    R

    一元正号

     

    *

    R

    解引用

     

    &

    R

    取地址

     

    ()

    R

    类型转换

    (type) expr

    new

    R

    创建对象

     

    delete

    R

    释放对象

     

    delete[]

    R

    释放数组

     

    ->*

    L

    指向成员操作的指针

    ptr ->* ptr_to_member

    .*

    L

    指向成员操作的指针

    • obj .*ptr_to_member

    *

    L

     

     

    /

    L

     

     

    %

    L

     

     

    +

    L

    加法

     

    -

    L

    减法

     

    << 

    L

    位左移

     

    >> 

    L

    位右移

     

    L

     

     

    <=

    L

     

     

    L

     

     

    >=

    L

     

     

    ==

    L

     

     

    !=

    L

     

     

    &

    L

    位与

     

    ^

    L

    位异或

     

    |

    L

    位或

     

    &&

    L

     

     

    ||

    L

     

     

    ?:

    R

     

     

    =

    R

     

     

    *=,/=,%=,+=,-=,<<=,>>=,

    &=,|=,^=

    R

    复合赋值操作

     

    throw

    R

     

     

    ,

    L

     

     


    5. 求值顺序
    && 和 || 操作符计算其操作数的次序:当且仅当其右操作数确实影响了整个表达式的值时,才计算这两个操作符的右操作数。C++中,规定了操作数计算顺序的操作符还有条件(?:)和逗号操作符。除此之外,其他操作符并未指定其操作数的求值顺序。
    6. new 与 delete
    定义变量时,必须指定其数据类型和名字。而动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,new表达式返回指向新创建对象的指针,我们通过该指针来访问此对象。

    int i; // named, uninitialized int variable
    int *pi = new int; // pi points to dynamically allocated,unnamed, uninitialized int

    如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同。对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。

    int *pi = new int(1024);     // object to which pi points is 1024
    string *ps = new string(10, '9'); // *ps is "9999999999"
    string *ps = new string; // initialized to empty string
    int *pi = new int; // pi points to an uninitialized int
    string *ps = new string(); // initialized to empty string
    int *pi = new int(); // pi points to an int value-initialized to 0

    C++ 提供了 delete 表达式释放指针所指向的地址空间。
    delete pi;该命令释放pi指向的int型对象所占用的内存空间。
    如果指针指向不是用new分配的内存地址,则在该指针上使用delete是不合法的。C++ 保证:删除 0 值的指针是安全的。删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。一旦删除了指针所指向的对象,立即将指针置为0,这样就非常清楚地表明指针不再指向任何对象。

    下面三种常见的程序错误都与动态内存分配相关:
      1. 删除(delete)指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区。删除动态分配内存失败称为“内存泄漏(memory leak)”。内存泄漏很难发现,一般需等应用程序运行了一段时间后,耗尽了所有内存空间时,内存泄漏才会显露出来。
      2. 读写已删除的对象。如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出这类错误。
      3. 对同一个内存空间使用两次 delete 表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做 delete 运算,将该对象的内存空间返还给自由存储区,然后接着 delete 第二个指针,此时则自由存储区可能会被破坏。
    7. 类型转换
      1. 隐式类型转换
      在混合类型的表达式中
      用作条件的表达式被转换为 bool 类型
      用一表达式初始化某个变量,或将一表达式赋值给某个变量
      整型提升:对于所有比 int 小的整型,包括 char、signed char、unsigned char、short 和 unsigned short,如果该类型的所有可能的值都能包容在int内,它们就会被提升为int型,否则,它们将被提升为unsigned int。如果将bool值提升为int,则false转换为0,而true则转换为 1。
      对于包含 signed 和 unsigned int 型的表达式,表达式中的 signed 型数值会被转换为 unsigned 型。

      指针转换:在使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针。不将数组转换为指针的例外情况有:数组用作取地址(&)操作符的操作数或 sizeof 操作符的操作数时,或用数组对数组的引用进行初始化时,不会将数组转换为指针。C++ 还提供了另外两种指针转换:指向任意数据类型的指针都可转换为void* 类型;整型数值常量 0 可转换为任意指针类型。

      2. 显示转换:static_cast、dynamic_cast、const_cast 和 reinterpret_cast
      强制类型转换符号的一般形式如下:cast-name<type>(expression);
        dynamic_cast:支持运行时识别指针或引用所指向的对象。
        const_cast :将转换掉表达式的 const 性质。
        static_cast:编译器隐式执行的任何类型转换都可以由 static_cast 显式完成。
        reinterpret_cast:通常为操作数的位模式提供较低层次的重新解释。

    五、 语句
    1. 常用语句注意点
    复合语句:通常被称为块,是用一对花括号括起来的语句序列。块标识了一个作用域,在块中引入的名字只能在该块内部或嵌套在块中的子块里访问。通常,一个名字只从其定义处到该块的结尾这段范围内可见。

    switch语句中default后必须要有一个语句,case后必须是整型常量表达式。对于 switch 结构,只能在它的最后一个 case 标号或 default 标号后面定义变量。

    While语句:在循环条件中定义的变量在每次循环里都要经历创建和撤销的过程。

    可以在 for 语句的 init-statement 中定义多个对象;但是不管怎么样,该处只能出现一个语句,因此所有的对象必须具有相同的一般类型。

    与 while 语句不同。do-while 语句总是以分号结束。

    int a[]={1,2,3,4,5};
    int *source=a;
    size_t sz=sizeof(a)/sizeof(*a);
    int *dest=new int[sz];
    while(source!=a+sz){
      *dest++=*source++;
    }
    int *p=dest-1;
    while(p!=dest-sz-1){
      std::cout<<*p<<std::endl;
      --p;
    }

    goto 语句不能跨越变量的定义语句向前跳转。

    2. 异常处理
    throw 语句使用一个表达式,该表达式是 runtime_error 类型的对象。runtime_error类型是标准库异常类中的一种,在 stdexcept 头文件中定义。
    每一个标准库异常类都定义了名为 what 的成员函数。这个函数不需要参数,返回const char* 类型值,它返回的指针指向一个 C 风格字符串。
    如果不存在处理该异常的 catch 子句,程序的运行就要跳转到名为terminate 的标准库函数,该函数在 exception 头文件中定义。这个标准库函数的行为依赖于系统,通常情况下,它的执行将导致程序非正常退出。
    标准库异常类定义在四个头文件中:
      1. exception 头文件定义了最常见的异常类,它的类名是 exception。这个类只通知异常的产生,但不会提供更多的信息。
      2. stdexcept 头文件定义了几种常见的异常类
    exception 最常见的问题,只定义了默认构造函数
    runtime_error 运行时错误:仅在运行时才能检测到问题
    range_error 运行时错误:生成的结果超出了有意义的值域范围
    overflow_error 运行时错误:计算上溢
    underflow_error 运行时错误:计算下溢
    logic_error 逻辑错误:可在运行前检测到问题
    domain_error 逻辑错误:参数的结果值不存在
    invalid_argument 逻辑错误:不合适的参数
    length_error 逻辑错误:试图生成一个超出该类型最大长度的对象
    out_of_range 逻辑错误:使用一个超出有效范围的值
    3. new 头文件定义了 bad_alloc 异常类型,提供因无法分配内在而由 new抛出的异常,只定义了默认构造函数
    4. type_info 头文件定义了 bad_cast 异常类型,只定义了默认构造函数
    #define NDEBUG
    预处理器还定义了其余四种在调试时非常有用的常量:
    __FILE__ 文件名
    __LINE__ 当前行号
    __TIME__ 文件被编译的时间
    __DATE__ 文件被编译的日期
    另一个常见的调试技术是使用 NDEBUG 预处理变量以及 assert 预处理宏。assert 宏是在 cassert 头文件中定义的,所有使用 assert 的文件都必须包含这个头文件。预处理宏有点像函数调用。assert 宏需要一个表达式作为它的条件:
    assert(expr)
    只要 NDEBUG 未定义,assert 宏就求解条件表达式 expr,如果结果为false,assert 输出信息并且终止程序的执行。如果该表达式有一个非零值,则 assert 不做任何操作。

  • 相关阅读:
    网络问题排查
    SpringBoot 自定义注解清除缓存
    MYSQL外键的使用以及优缺点
    Java List
    黑客帝国代码雨
    前端接收字节数据流,显示图片
    何为熔断降级
    spring的线程
    window.open 与 iframe
    js 全部替换
  • 原文地址:https://www.cnblogs.com/yshb/p/2725578.html
Copyright © 2011-2022 走看看