专业编程基础技术教程

网站首页 > 基础教程 正文

c++ 表达式(1)

ccvgpt 2024-08-11 15:08:22 基础教程 8 ℃

https://zh.cppreference.com/w/cpp/language/expressions

c++ 表达式 (2)

c++ 表达式(1)

c++ 表达式 (3)

表达式是运算符??和它们的操作数??的序列,它指定一项计算。

表达式的求值可以产生一个结果(比如 2 + 2 的求值产生结果 4),也可能产生副作用(比如对 std::printf("%d", 4) 的求值在标准输出上打印字符 '4')。

每个 C++ 表达式均被描述为具有两种独立的性质:类型和值类别。

1. 概述

  • 值类别(左值 (lvalue)、右值 (rvalue)、泛左值 (glvalue)、纯右值 (prvalue)、亡值 (xvalue) (C++11 起))是根据表达式的值所进行的分类
  • 实参和子表达式的求值顺序指定获得中间结果所用的顺序

运算符

常见运算符

赋值

a = b

a %= b

a <<= b

a += b

a &= b

a >>= b

a -= b

a |=b


a *= b

a ^= b


a /= b

a <<= b


自增/自减

++a

a--

a++

--a

算术

+a

a +b

a%b

a<<b

-a

a-b

a|b

a>>b

~a

a/b

a&b



a*b

a^b


逻辑

!a

a&&b

a||b

比较

a == b

a < b

a > b

a != b

a <= b

a >= b


a <=> b


成员访问

a[b]

a->b

*a

a.b

&a

a->*b


a.*b

其它

函数调用

a(...)

逗号

a,b

条件

a ? b : c

特殊运算符

static_cast

转换一个类型为另一相关类型

dynamic_cast

在继承层级中转换

const_cast

添加或移除 cv 限定符

reinterpret_cast

转换类型到无关类型

new

创建有动态存储期的对象

delete

销毁先前由 new 表达式创建的对象,并释放其所拥有的内存区域

sizeof

查询类型的大小

sizeof...

查询形参包的大小(C++11 起)

typeid

查询类型的类型信息

noexcept

查询表达式是否能抛出异常(C++11 起)

alignof

查询类型的对齐要求(C++11 起)

  • 运算符优先级定义了运算符绑定到它的各个实参的顺序
  • 替代表示是一些运算符的其他代用书写方式
  • 运算符重载允许对用户定义的类指定各运算符的行为。

转换

  • 标准转换是从一个类型到另一类型的隐式转换
  • const_cast 转换
https://zh.cppreference.com/w/cpp/language/const_cast
在有不同 cv 限定的类型间转换。
const_cast< 目标类型 >( 表达式 )		
返回 目标类型 类型的值。

#include <iostream>
 
struct type
{
    int i;
 
    type(): i(3) {}
 
    void f(int v) const
    {
        // this->i = v;                 // 编译错误:this 是指向 const 的指针
        const_cast<type*>(this)->i = v; // 只要该对象不是 const 就 OK
    }
};
 
int main() 
{
    int i = 3;                 // 不声明 i 为 const
    const int& rci = i; 
    const_cast<int&>(rci) = 4; // OK:修改 i
    std::cout << "i = " << i << '\n';
 
    type t; // 如果这是 const type t,那么 t.f(4) 会是未定义行为
    t.f(4);
    std::cout << "type::i = " << t.i << '\n';
 
    const int j = 3; // 声明 j 为 const
    [[maybe_unused]]
    int* pj = const_cast<int*>(&j);
    // *pj = 4;      // 未定义行为
 
    [[maybe_unused]]
    void (type::* pmf)(int) const = &type::f; // 指向成员函数的指针
    // const_cast<void(type::*)(int)>(pmf);   // 编译错误:const_cast 不能用于成员函数指针
}

输出:
i = 4
type::i = 4
  • static_cast 转换
https://zh.cppreference.com/w/cpp/language/static_cast
使用隐式和用户定义转换的组合来进行类型之间的转换。
static_cast<目标类型??>(表达式??)		
返回 目标类型 类型的值。

#include <iostream>
#include <vector>
 
struct B
{
    int m = 42;
    const char* hello() const
    {
        return "Hello world,这里是 B!\n";
    }
};
 
struct D : B
{
    const char* hello() const
    {
        return "Hello world,这里是 D!\n";
    }
};
 
enum class E { ONE = 1, TWO, THREE };
enum EU { ONE = 1, TWO, THREE };
 
int main()
{
    // 1: 静态向下转换
    D d;
    B& br = d; // 通过隐式转换向上转换
    std::cout << "1) " << br.hello();
    D& another_d = static_cast<D&>(br); // 向下转换
    std::cout << "1) " << another_d.hello();
 
    // 2: 左值到亡值
    std::vector<int> v0{1,2,3};
    std::vector<int> v2 = static_cast<std::vector<int>&&>(v0);
    std::cout << "2) 移动后,v0.size() = " << v0.size() << '\n';
 
    // 3: 初始化转换
    int n = static_cast<int>(3.14); 
    std::cout << "3) n = " << n << '\n';
    std::vector<int> v = static_cast<std::vector<int>>(10);
    std::cout << "3) v.size() = " << v.size() << '\n';
 
    // 4: 弃值表达式
    static_cast<void>(v2.size());
 
    // 5. 隐式转换的逆转换
    void* nv = &n;
    int* ni = static_cast<int*>(nv);
    std::cout << "5) *ni = " << *ni << '\n';
 
    // 6. 数组到指针后随向上转换
    D a[10];
    [[maybe_unused]]
    B* dp = static_cast<B*>(a);
 
    // 7. 有作用域枚举到 int 或 float
    E e = E::TWO;
    int two = static_cast<int>(e);
    std::cout << "7) " << two << '\n';
 
    // 8. int 到枚举,枚举到另一枚举
    E e2 = static_cast<E>(two);
    [[maybe_unused]]
    EU eu = static_cast<EU>(e2);
 
    // 9. 指向成员指针向上转换
    int D::*pm = &D::m;
    std::cout << "9) " << br.*static_cast<int B::*>(pm) << '\n';
 
    // 10. void* 到任何类型
    void* voidp = &e;
    [[maybe_unused]]
    std::vector<int>* p = static_cast<std::vector<int>*>(voidp);
}

输出:
1) Hello world,这里是 B!
1) Hello world,这里是 D!
2) 移动后,v0.size() = 0
3) n = 3
3) v.size() = 10
5) *ni = 3
7) 2
9) 42
  • dynamic_cast 转换
https://zh.cppreference.com/w/cpp/language/dynamic_cast
沿继承层级向上、向下及侧向,安全地转换到其他类的指针和引用。
dynamic_cast< 目标类型 >( 表达式 )		
目标类型 - 指向完整类类型的指针,到完整类类型的引用,或指向(可有 cv 限定的)void 的指针
表达式	- 如果目标类型??是引用,那么是完整类类型的左值 (C++11 前)泛左值 (C++11 起)表达式,如果目标类型??是指针,那么是指向完整类类型的指针纯右值
如果转换成功,那么 dynamic_cast 就会返回目标类型??类型的值。如果转换失败且目标类型??是指针类型,那么它会返回该类型的空指针。如果转换失败且目标类型??是引用类型,那么它会抛出与类型 std::bad_cast 的处理块匹配的异常。
    
    
#include <iostream>
struct V
{
    virtual void f() {} // 必须为多态,以使用带运行时检查的 dynamic_cast
};
 
struct A : virtual V {};
 
struct B : virtual V
{
    B(V* v, A* a)
    {
        // 构造中转换(见后述 D 的构造函数中的调用)
        dynamic_cast<B*>(v); // 良好定义:v 有类型 V*,V 是 B 的基类,产生 B*
        dynamic_cast<B*>(a); // 未定义行为:a 有类型 A*,A 不是 B 的基类
    }
};
 
struct D : A, B
{
    D() : B(static_cast<A*>(this), this) {}
};
 
struct Base
{
    virtual ~Base() {}
};
 
struct Derived: Base
{
    virtual void name() {}
};
 
int main()
{
    D d; // 最终派生对象
    A& a = d; // 向上转换,可以用 dynamic_cast,但不是必须的
 
    [[maybe_unused]]
    D& new_d = dynamic_cast<D&>(a); // 向下转换
    [[maybe_unused]]
    B& new_b = dynamic_cast<B&>(a); // 侧向转换
 
    Base* b1 = new Base;
    if (Derived* d = dynamic_cast<Derived*>(b1); d != nullptr)
    {
        std::cout << "成功从 b1 向下转换到 d\n";
        d->name(); // 可以安全调用
    }
 
    Base* b2 = new Derived;
    if (Derived* d = dynamic_cast<Derived*>(b2); d != nullptr)
    {
        std::cout << "成功从 b2 向下转换到 d\n";
        d->name(); // 可以安全调用
    }
 
    delete b1;
    delete b2;
}

输出:
成功从 b2 向下转换到 d
  • reinterpret_cast 转换
https://zh.cppreference.com/w/cpp/language/reinterpret_cast
通过重新解释底层位模式在类型间转换。
reinterpret_cast< 目标类型 >( 表达式 )		
返回目标类型??类型的值。

#include <cassert>
#include <cstdint>
#include <iostream>
 
int f() { return 42; }
 
int main()
{
    int i = 7;
 
    // 指针到整数并转回
    std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // 不能误用 static_cast
    std::cout << "&i 的值是 " << std::showbase << std::hex << v1 << '\n';
    int* p1 = reinterpret_cast<int*>(v1);
    assert(p1 == &i);
 
    // 到另一函数指针并转回
    void(*fp1)() = reinterpret_cast<void(*)()>(f);
    // fp1(); 未定义行为
    int(*fp2)() = reinterpret_cast<int(*)()>(fp1);
    std::cout << std::dec << fp2() << '\n'; // 安全
 
    // 通过指针的类型别名化
    char* p2 = reinterpret_cast<char*>(&i);
    std::cout << (p2[0] == '\x7' ? "本系统是小端的\n"
                                 : "本系统是大端的\n");
 
    // 通过引用的类型别名化
    reinterpret_cast<unsigned int&>(i) = 42;
    std::cout << i << '\n';
 
    [[maybe_unused]] const int &const_iref = i;
    // int &iref = reinterpret_cast<int&>(const_iref); // 编译错误——不能去除 const
    // 必须用 const_cast 代替:int &iref = const_cast<int&>(const_iref);
}

输出:
&i 的值是 0x7fff352c3580
42
本系统是小端的
42
  • 显式类型转换,可使用 C 风格写法和函数风格写法
https://zh.cppreference.com/w/cpp/language/explicit_cast
用显式和隐式转换的组合进行类型之间的转换。
( 目标类型 ) 表达式	(1)	
目标类型 ( 表达式列表??(可选) )	(2)	
目标类型 { 表达式列表??(可选) }	(3)	(C++11 起)
模板名 ( 表达式列表??(可选) )	(4)	(C++17 起)
模板名 { 表达式列表??(可选) }	(5)	(C++17 起)
auto ( 表达式 )	(6)	(C++23 起)
auto { 表达式 }	(7)	(C++23 起)
返回 目标类型 类型的值。    
    
#include <cassert>
#include <iostream>
 
double f = 3.14;
unsigned int n1 = (unsigned int)f; // C 风格转型
unsigned int n2 = unsigned(f);     // 函数风格转型
 
class C1;
class C2;
C2* foo(C1* p)
{
    return (C2*)p; // 转换不完整类型到不完整类型
}
 
void cpp23_decay_copy_demo()
{
    auto inc_print = [](int& x, const int& y)
    {
        ++x;
        std::cout << "x:" << x << ", y:" << y << '\n';
    };
 
    int p{1};
    inc_print(p, p); // 打印 x:2 y:2,因为此处的形参 y 是 p 的别名
    int q{1};
    inc_print(q, auto{q}); // 打印 x:2 y:1,auto{q} (C++23) 转型为纯右值,
                           // 因此形参 y 是 q 的副本(而非 q 的别名)
}
 
// 在这个例子中,C 风格转型被转译成 static_cast
// 尽管它的作用也可以和 reinterpret_cast 一致
struct A {};
struct I1 : A {};
struct I2 : A {};
struct D : I1, I2 {};
 
int main()
{
    D* d = nullptr;
//  A* a = (A*)d;                   // 编译时错误
    A* a = reinterpret_cast<A*>(d); // 可以编译
    assert(a == nullptr);
 
    cpp23_decay_copy_demo();
}

输出:
x:2 y:2
x:2 y:1    
  • 用户定义转换使得可以指定源自用户定义类的转换
https://zh.cppreference.com/w/cpp/language/cast_operator
启用从类类型到另一类型的隐式转换或显式转换。
转换函数的声明与非静态成员函数或成员函数模板类似,但没有形参和显式返回类型,并拥有下列形式的名字:
operator 转换类型标识	(1)	
explicit operator 转换类型标识	(2)	(C++11 起)
explicit ( 表达式 ) operator 转换类型标识	(3)	(C++20 起)
1) 声明用户定义的转换函数,它参与所有隐式和显式转换。
2) 声明用户定义的转换函数,它只会参与直接初始化和显式转换。
3) 声明用户定义的转换函数,它是有条件显式的。
转换类型标识是一个类型标识,但它的声明符中不能出现函数与数组运算符 [] 
或 ()(因此转换到例如数组的指针的类型就需要使用类型别名、typedef 或标识模板:见下文)。
无论是否使用 typedef,转换类型标识都不能代表数组或函数类型。

//1    
struct X
{
    // 隐式转换
    operator int() const { return 7; }
 
    // 显式转换
    explicit operator int*() const { return nullptr; }
 
    // 错误:转换类型标识中不能出现数组运算符
//  operator int(*)[3]() const { return nullptr; }
 
    using arr_t = int[3];
    operator arr_t*() const { return nullptr; } // 可以通过 typedef 转换
//  operator arr_t () const; // 错误:任何情况下都不能转换到数组
};
 
int main()
{
    X x;
 
    int n = static_cast<int>(x);   // OK:设 n 为 7
    int m = x;                     // OK:设 m 为 7
 
    int* p = static_cast<int*>(x); // OK:设 p 为空
//  int* q = x; // 错误:没有隐式转换
 
    int (*pa)[3] = x;  // OK
}

//2
struct To
{
    To() = default;
    To(const struct From&) {} // 转换构造函数
};
 
struct From
{
    operator To() const {return To();} // 转换函数
};
 
int main()
{
    From f;
    To t1(f);  // 直接初始化:调用构造函数
    // 注意:如果转换构造函数不可用,那么就会选择隐式的复制构造函数,并调用转换函数以准备它的实参
 
//  To t2 = f; // 复制初始化:有歧义
    // 注意:如果转换函数来自非 const 类型,例如 From::operator To();,
    // 那么它在这种情况下会替代构造函数被选中
 
    To t3 = static_cast<To>(f); // 直接初始化:调用构造函数
//  const To& r = f;            // 引用初始化:有歧义
}


//3
struct D; 
struct B
{
    virtual operator D() = 0;
};
 
struct D : B
{
    operator D() override { return D(); }
};
 
int main()
{
    D obj;
    D obj2 = obj; // 不会调用 D::operator D()
    B& br = obj;
    D obj3 = br;  // 通过虚派发调用 D::operator D() 
}

//4
struct B {};
 
struct X : B
{
    operator B&() { return *this; };
};
 
int main()
{
    X x;
    B& b1 = x;                  // 不会调用 X::operatorB&()
    B& b2 = static_cast<B&>(x); // 不会调用 X::operatorB&
    B& b3 = x.operator B&();    // 调用 X::operatorB&
}

//5
& x.operator int * a; // 错误:解析成 & (x.operator int*) a
                      //      而不是 & (x.operator int) * a
 
operator int [[noreturn]] (); // 错误:noreturn 属性应用到了类型

//6
struct X
{
    operator int(); // OK
    operator auto() -> short; // 错误:尾随返回类型不是此类语法的一部分
    operator auto() const { return 10; } // OK:推导的返回类型
    operator decltype(auto)() const { return 10l; } // OK:推导的返回类型
};

Tags:

最近发表
标签列表