结构化编程
ADT
- 抽象数据类型:
array
、struct
、union
等。 - 结合性
- 优先级
- 类型转换:
coersion
(隐式转换) 和casting
(强制转换),溢出处理; - 运算顺序:副作用和精度
声明 cast
static.cast <TYPE> (DATA)
:是最常用的类型转换之一,在编译时刻做表示形式级别的转换,适用于大多数显式类型转换。相对于直接类型转换,这种方式可以提供更强的类型检查。一般用于基础的类型转换(如int
和float
等。const.cast <TYPE> (DATA)
:const_cast
是用于移除或添加const
或volatile
修饰符的唯一合法方法。它可以将const
对象的常量性去掉,使其可以被修改,或者反之。例如:c++const int i = 42; int* p = const_cast<int*>(&i); // 移除 const,允许修改 i *p = 24; // 可能会产生未定义行为
注意: 如果对原本是常量的对象进行修改,结果是未定义行为,一般的编译器会在直接引用常量时填入原来的值,而在对指针解引用时返回新值。因此需谨慎使用
const_cast
。dynamic.cast <TYPE> (DATA)
:主要用于类层次结构中,尤其是在有虚函数的多态情况下。它会在运行时进行类型检查,确保转换是安全的。它可以向上转换(将派生类对象转换为基类类型)和向下转换(将基类对象转换为派生类类型)。如果转换失败,指针类型会返回nullptr
,而引用类型会抛出std::bad_cast
异常。考虑下面的一个简单的基类和派生类:
c++class F { public: void foo() { cout << "Father" << endl; } }; class S1 : public F { public: int x = 1; void foo() { cout << "Son1" << endl; } }; class S2 : public F { public: int x = 2; void foo() { cout << "Son2" << endl; } };
运行一下几组代码,可以看出
dynamic_cast
具有更强的类型检查:c++F* obj = new S1; obj->foo(); //print 'Father' static_cast<S1*>(obj)->foo(); //print 'Son1' F* obj1 = new F; static_cast<S1*>(obj1)->foo(); //Unsafe cout << static_cast<S1 *>(obj1)->x << endl; //Undefined behavior F* obj2 = new S1; dynamic_cast<S1*>(obj2)->foo(); //Compile Error... F* obj3 = new F; dynamic_cast<S1*>(obj3)->foo(); //Exception
reinterpret.cast <TYPE> (DATA)
:它保持位的二进制序列不变,只是以新的类型解释变量。这是一种非常危险的转换,它允许几乎任意类型之间的强制转换,甚至可以将指针转换为整数,或者将整数转换为指针。它不会执行任何类型检查或安全保证,适用于底层操作和低级别编程。
表达式
auto
关键字:可以使用auto
关键字来避免冗余的类型定义。decltype
关键字:用法是decltype (实体或表达式)
,推导出一个与括号中实体相同的类型,并将该类型作用于后面的对象。例如c++int i = 33; decltype(i) j = i * 2; //Type of j is int
使用
range-for
使语法简洁:这是C++
中新增的语法糖。如果只是遍历容器中的元素,就可以使用range-for
语法来简化,如下c++vector<int> v = {1, 2, 3, 4, 5}; for (int i : v) { cout << i << endl; }
Union 组合体
union
是一种特殊的数据结构,它允许在相同的内存位置存储不同的数据类型。union
的大小是其成员中最大的成员的大小。其核心思想就是共享内存。如果某一组合体最近被写入为某一个成员变量的类型,那么就只能按这一类型读取,否则会出现未定义行为。例如可以定义以下数据结构 S
:
union S
{
std::int32_t n; // 占用 4 字节
std::uint16_t s[2]; // 占用 4 字节
std::uint8_t c; // 占用 1 字节
}; // 整个联合体占用 4 字节
下面我们来看一个例子:
#include <cstdint>
#include <iostream>
int main()
{
S s = {0x12345678}; // 初始化首个成员,s.n 现在是活跃成员
// 于此点,从 s.s 或 s.c 读取是未定义行为
std::cout << std::hex << "s.n = " << s.n << '\n';
s.s[0] = 0x0011; // s.s 现在是活跃成员
// 在此点,从 n 或 c 读取是 UB 但大多数编译器都对其有定义
std::cout << "s.c 现在是 " << +s.c << '\n' // 11 或 00,取决于平台
<< "s.n 现在是 " << s.n << '\n'; // 12340011 或 00115678
}
这一程序的输出是:
s.n = 12345678
s.c 现在是 11
s.n 现在是 12340011
我们可以看出,利用 Union
可以实现简单的多态。
结构体
struct
是一种用户自定义的数据类型,它可以包含不同类型的数据成员。和 class
一样,struct
也是类关键词。但 struct
的默认访问权限是 public
,而 class
的默认访问权限是 private
。struct
可以包含成员函数,但是不能包含构造函数和析构函数。此外,struct
可以继承自其他类,也可以作为基类被继承。
结构体内存布局
在结构体对象内,其成员的地址(及位域分配单元的地址)按照成员定义的顺序递增。可以把指向结构体的指针转型为指向其首成员(或者若首成员为位域,则指向其分配单元)的指针。类似地,能转型指向结构体首成员的指针为指向整个结构体的指针。在任意两个成员间和最后的成员后可能存在无名的填充字节,但首成员前不会有。结构体的大小至少与其成员的大小之和一样大。