C++ 编程基础与高级特性解析
1. 参考资料概述
在 C++ 编程的学习和实践中,有许多有价值的参考资料,以下为部分列举:
|序号|作者|标题|出版信息|
| ---- | ---- | ---- | ---- |
|1|D. Abrahams, A. Gurtovoy|C++ Template Metaprogramming: Concepts, Tools and Techniques from Boost and Beyond|Addison - Wesley, Boston, 2004|
|2|ANSI|ANSI X3.159 - 1989 American National Standard for Information Systems – Programming Language C|American National Standard for Information, 1989|
|3|ARDUINO®|ARDUINO®|2015. http://www.arduino.cc|
|4|M. Barr|Programming Embedded Systems with C and GNU Development Tools, Second Edition|O’Reilly, Sebastopol, 2006|
|5|P. Becker|The C++ Standard Library Extensions: A Tutorial and Reference|Addison - Wesley, Boston, 2006|
2. C++ 基础特性
2.1 C++ 类型转换运算符
C++ 有四种模板类型转换运算符,下面介绍其中两种:
-
static_cast 运算符
:用于较为常规的类型转换,例如从 float 类型转换为 int 类型。示例代码如下:
// appendix0a_01 - 001_static_cast.cpp
float f = 3.1415926535'8979323846'264338328F;
// The value of n is 3.
int n = static_cast<int>(f);
- reinterpret_cast 运算符 :可以进行看似不相关类型之间的转换,如将地址的整数值转换为地址指针。不过该运算符具有非可移植性,使用时需要谨慎。以下是一个设置微控制器端口寄存器位的示例:
// appendix0a_01 - 002_reinterpret_cast.cpp
// The address of portb is 0x25.
constexpr std::uint8_t portb = UINT8_C(0x25);
// Cast std::uint8_t to std::uint8_t*.
volatile std::uint8_t* pb = reinterpret_cast<volatile std::uint8_t*>(portb);
// Set portb.5.
*pb |= UINT8_C(0x20);
此外,C++ 还提供了 dynamic_cast 和 const_cast 运算符。dynamic_cast 常用于在类层次结构中转换指针和引用,会进行代价较高但可靠的运行时检查;const_cast 可以更改对象的常量或易变属性。
2.2 统一初始化语法
C++11 引入了统一初始化语法,它可以用于任何对象的初始化,能与传统的括号构造函数初始化和赋值运算符初始化一起使用。统一初始化语法使用花括号来包含初始值。以下是不同类型的初始化示例:
-
内置类型初始化
:
// appendix0a_02 - 001_uniform_initialization.cpp
int n { 123 };
float f { 3.1415926535'8979323846F };
- 聚合类型初始化 :
// appendix0a_02 - 002_uniform_initialization.cpp
struct my_struct
{
int my_n;
float my_f;
my_struct(const int n = 0, const float& f = 0.0F) : my_n(n), my_f(f) { }
};
my_struct instance
{
123,
// Initial value of n.
3.1415926535'8979323846F // Initial value of f.
};
- 编译器类型推导 :
// appendix0a_02 - 003_uniform_initialization.cpp
struct my_struct
{
// ...
};
my_struct function()
{
// The compiler correctly deduces the return type.
return
{
456,
0.5772156649'0153286061F
};
}
- 类类型初始化 :
// appendix0a_02 - 004_uniform_initialization.cpp
struct point
{
point(const int x = 0, const int y = 0) : my_x{x}, my_y{y} { }
int my_x;
int my_y;
};
point pt
{
123,
456
};
- STL 容器初始化 :
// appendix0a_02 - 005_uniform_initialization.cpp
std::array<int, 3U> a
{
{ 1, 2, 3 }
};
std::vector<char> v
{
{ 'a', 'b', 'c' }
};
2.3 函数重载
函数重载允许创建多个同名但输入和输出参数类型不同的函数。例如:
// appendix0a_03 - 001_function_overloading.cpp
// The area of a rectangle.
float area(const float& length, const float& width)
{
return length * width;
}
// The area of a circle.
float area(const float& radius)
{
constexpr float pi = 3.1415926535'8979323846F;
return (pi * radius) * radius;
}
全局函数、局部函数和类成员函数都可以进行重载,但要注意不要将类成员重载与动态多态和运行时虚函数机制混淆。
2.4 编译时断言
static_assert 用于在编译时检查常量表达式。其语法如下:
// appendix0a_04 - 001_static_assert.cpp
static_assert(expression, message);
其中,expression 是要检查的条件,message 是可能有用的诊断文本。如果 expression 为真,编译继续进行;如果为假,则会产生编译器错误并显示消息文本。例如:
// appendix0a_04 - 002_static_assert.cpp
constexpr unsigned int version = 3U;
// Print error message if version is less than 2.
static_assert(version >= 2U, "Version is too low!");
在 C++17 中,static_assert 的错误文本可以省略,会提供默认文本。示例如下:
// appendix0a_04 - 003_static_assert.cpp
constexpr unsigned int version = 3U;
// Print default error message if version is not 3.
static_assert(version == 3U);
2.5 数值限制
C++ 标准库在 头文件中提供了 std::numeric_limits 模板,用于查询内置类型的数值限制。以下是该模板类的概要:
// appendix0a_05 - 001_numeric_limits.cpp
namespace std
{
template<class T>
class numeric_limits
{
public:
static constexpr bool is_specialized = false;
static constexpr T min() { return T(); }
static constexpr T max() { return T(); }
static constexpr T lowest() { return T(); }
static constexpr int digits = 0;
static constexpr int digits10 = 0;
static constexpr int max_digits10 = 0;
static constexpr bool is_signed = false;
static constexpr bool is_integer = false;
static constexpr bool is_exact = false;
static constexpr int radix = 0;
static constexpr T epsilon() { return T(); }
static constexpr T round_error() { return T(); }
static constexpr int min_exponent = 0;
static constexpr int min_exponent10 = 0;
static constexpr int max_exponent = 0;
static constexpr int max_exponent10 = 0;
static constexpr bool has_infinity = false;
static constexpr bool has_quiet_NaN = false;
static constexpr bool has_signaling_NaN = false;
static constexpr float_denorm_style has_denorm = denorm_absent;
static constexpr bool has_denorm_loss = false;
static constexpr T infinity() { return T(); }
static constexpr T quiet_NaN() { return T(); }
static constexpr T signaling_NaN() { return T(); }
static constexpr T denorm_min() { return T(); }
static constexpr bool is_iec559 = false;
static constexpr bool is_bounded = false;
static constexpr bool is_modulo = false;
static constexpr bool traps = false;
static constexpr bool tinyness_before = false;
static constexpr float_round_style round_style = round_toward_zero;
};
}
对于 32 位 int 类型的特化示例如下:
// appendix0a_05 - 001_numeric_limits.cpp
namespace std
{
template<>
class numeric_limits<int>
{
public:
static constexpr bool is_specialized = true;
static constexpr int min() { return 0; }
static constexpr int max() { return +2147483647; }
static constexpr int lowest() { return -2147483648; }
static constexpr int digits = 32;
static constexpr int digits10 = 9;
static constexpr int max_digits10 = 9;
static constexpr bool is_signed = false;
static constexpr bool is_integer = true;
static constexpr bool is_exact = true;
static constexpr int radix = 2;
static constexpr int epsilon() { return 0; }
static constexpr int round_error() { return 0; }
static constexpr int min_exponent = 0;
static constexpr int min_exponent10 = 0;
static constexpr int max_exponent = 0;
static constexpr int max_exponent10 = 0;
static constexpr bool has_infinity = false;
static constexpr bool has_quiet_NaN = false;
static constexpr bool has_signaling_NaN = false;
static constexpr float_denorm_style has_denorm = denorm_absent;
static constexpr bool has_denorm_loss = false;
static constexpr int infinity () { return 0; }
static constexpr int quiet_NaN() { return 0; }
static constexpr int signaling_NaN() { return 0; }
static constexpr int denorm_min() { return 0; }
static constexpr bool is_iec559 = false;
static constexpr bool is_bounded = false;
static constexpr bool is_modulo = false;
static constexpr bool traps = false;
static constexpr bool tinyness_before = false;
static constexpr float_round_style round_style = round_toward_zero;
};
}
使用示例:
// appendix0a_05 - 001_numeric_limits.cpp
constexpr int n_max = std::numeric_limits<int>::max();
constexpr int n_min = std::numeric_limits<int>::max();
constexpr int n_low = std::numeric_limits<int>::lowest();
数值限制还可以方便地用于其他模板中,例如:
// appendix0a_05 - 002_numeric_limits.cpp
template<typename unsigned_type>
struct hi_bit
{
// The bit - position of the high bit.
static constexpr int bpos = std::numeric_limits<unsigned_type>::digits - 1;
// The value of the type with the high - bit set.
static constexpr unsigned_type value = static_cast<unsigned_type>(1) << bpos;
};
可以根据不同的无符号类型生成编译时常量值:
// appendix0a_05 - 002_numeric_limits.cpp
constexpr std::uint8_t hi08 = hi_bit<std::uint8_t>::value;
// (1 << 7)
constexpr std::uint16_t hi16 = hi_bit<std::uint16_t>::value; // (1 << 15)
constexpr std::uint32_t hi32 = hi_bit<std::uint32_t>::value; // (1 << 31)
constexpr std::uint64_t hi64 = hi_bit<std::uint64_t>::value; // (1 << 63)
也可以编写 std::numeric_limits 的特化来提供用户自定义类型的数值限制信息。
2.6 STL 容器
C++ 标准库的 STL 包含多种容器类型,用于在单个对象中存储多个元素。以下是常见的 STL 容器及其特点:
|容器类型|特点|
| ---- | ---- |
|std::array|固定长度的顺序数组,在内存中连续对齐|
|std::vector|类似于 std::array,但长度可以动态改变,设计用于快速随机访问|
|std::deque|双端队列,适合在前后端进行快速插入和删除操作|
|std::list|可双向遍历的序列,但不支持随机访问,支持在序列中任意位置快速插入和删除|
|std::forward_list|类似于 std::list,但只能向前遍历|
|std::basic_string, std::string, std::wstring|用于存储基于字符的序列(即字符串),虽不完全符合顺序 STL 容器的正式要求,但常被视为顺序容器|
此外,STL 还包括关联容器(如 std::set、std::multiset、std::map 和 std::multimap)和标准适配器(如 std::stack、std::queue 和 std::priority_queue)。
以下是使用 std::vector 容器的示例:
// appendix0a_06 - 001_stl_containers.cpp
#include <vector>
void do_something()
{
// Create v with three elements having value 0.
std::vector<int> v(3U, 0);
// Set the values in v to (1,2,3).
v[0U] = 1;
v[1U] = 2;
v[2U] = 3;
// The initial size is 3.
std::vector<int>::size_type s = v.size();
v.push_back(4);
// The size is now 4.
s = v.size();
int x0 = v.at(0U); // Value of x0 is 1.
int x3 = v.back(); // Value of x3 is 4.
// Copy constructor from another vector.
std::vector<int> v2(v);
// Constructor from other input iterators.
std::vector<int> v3(v.begin(), v.end());
// Support for operator=().
std::vector<int> v4 = v;
}
容器可以进行复制构造、从另一个迭代器序列创建和复制赋值。容器的其他成员函数包括访问函数和序列操作(如插入、赋值等)。模板容器使用成员类型定义来定义常见的成员类型,如 std::vector 的 size_type。
2.7 STL 迭代器
迭代器用于遍历顺序容器的元素并访问其值,可以读写标准 STL 容器的元素。每个标准 STL 容器都通过提供专用的迭代器类型和标准化的迭代器函数(如 begin() 和 end())来方便地操作其元素。
以下是使用 std::vector 迭代器的示例:
// appendix0a_07 - 001_stl_iterators.cpp
#include <vector>
void do_something()
{
// Set v to (1,2,3).
std::vector<int> v( { 1, 2, 3 } );
// Declare an iterator for std::vector<int>.
std::vector<int>::iterator it;
// Add 5 to each element in v.
for(it = v.begin(); it != v.end(); ++it)
{
*it += 5;
}
// Now v is (6,7,8).
}
迭代器对
[First, Last)
表示一个序列的范围,其中 First 指向序列的第一个元素,Last 指向最后一个元素的下一个位置。STL 的标准算法遵循此约定。例如,使用 std::copy() 函数复制元素:
// appendix0a_07 - 002_stl_iterators.cpp
#include <algorithm>
#include <array>
#include <vector>
void do_something()
{
// Initialized src with (101, 101, 101).
const std::vector<int> src(3U, 101);
// Default - initialized dst.
std::array<int, 3U> dst = {{ }};
// Copy from vector src to array dst.
// dst now also contains (101, 101, 101).
std::copy(src.begin(), src.end(), dst.begin());
}
所有迭代器都支持递增(++)操作,部分支持递减(–)操作,一般前置递增和递减形式比后置形式更高效。STL 迭代器使用解引用运算符(*)或成员选择运算符(->)进行元素访问。C++ 有多种迭代器类别,如前向迭代器、双向迭代器和随机访问迭代器。同时,常量迭代器只能进行只读访问,非常量迭代器可以读写容器元素。
// appendix0a_07 - 003_stl_iterators.cpp
#include <array>
void do_something()
{
using container_type = std::array<int, 3U>;
using const_iterator_type = typename container_type::const_iterator;
using iterator_type = typename container_type::iterator;
container_type cnt = {{ 1, 2, 3 }};
iterator_type nonconst_iterator = cnt.begin();
}
3. 总结与应用建议
3.1 特性总结
| 特性 | 描述 | 示例代码 |
|---|---|---|
| 类型转换运算符 | 包含 static_cast、reinterpret_cast、dynamic_cast 和 const_cast 四种。static_cast 用于常规类型转换,reinterpret_cast 可进行看似不相关类型转换,dynamic_cast 用于类层次结构中指针和引用转换,const_cast 可更改对象常量或易变属性 |
cpp<br>float f = 3.1415926535'8979323846'264338328F;<br>int n = static_cast<int>(f);<br>constexpr std::uint8_t portb = UINT8_C(0x25);<br>volatile std::uint8_t* pb = reinterpret_cast<volatile std::uint8_t*>(portb);<br>
|
| 统一初始化语法 | C++11 引入,使用花括号包含初始值,可用于内置类型、聚合类型、类类型和 STL 容器初始化,还能让编译器推导类型 |
cpp<br>int n { 123 };<br>struct my_struct { ... };<br>my_struct instance { 123, 3.1415926535'8979323846F };<br>std::array<int, 3U> a { { 1, 2, 3 } };<br>
|
| 函数重载 | 允许创建多个同名但输入和输出参数类型不同的函数,全局、局部和类成员函数均可重载 |
cpp<br>float area(const float& length, const float& width) { return length * width; }<br>float area(const float& radius) { constexpr float pi = 3.1415926535'8979323846F; return (pi * radius) * radius; }<br>
|
| 编译时断言 | static_assert 用于在编译时检查常量表达式,若表达式为假会产生编译器错误并显示消息文本,C++17 中错误文本可省略 |
cpp<br>constexpr unsigned int version = 3U;<br>static_assert(version >= 2U, "Version is too low!");<br>static_assert(version == 3U);<br>
|
| 数值限制 | C++ 标准库在 头文件中提供 std::numeric_limits 模板,用于查询内置类型的数值限制,也可编写特化提供用户自定义类型的数值限制信息 |
cpp<br>constexpr int n_max = std::numeric_limits<int>::max();<br>template<typename unsigned_type><br>struct hi_bit { ... };<br>
|
| STL 容器 | 包含顺序容器(如 std::array、std::vector、std::deque、std::list、std::forward_list、std::string 等)、关联容器和标准适配器,用于在单个对象中存储多个元素,各有特点 |
cpp<br>std::vector<int> v(3U, 0);<br>v[0U] = 1; v[1U] = 2; v[2U] = 3; v.push_back(4);<br>
|
| STL 迭代器 |
用于遍历顺序容器的元素并访问其值,每个标准 STL 容器提供专用迭代器类型和标准化迭代器函数,迭代器对
[First, Last)
表示序列范围,有多种迭代器类别,常量迭代器只读,非常量迭代器可读写
|
cpp<br>std::vector<int> v( { 1, 2, 3 } );<br>std::vector<int>::iterator it;<br>for(it = v.begin(); it != v.end(); ++it) { *it += 5; }<br>
|
3.2 应用建议
- 类型转换运算符 :在进行常规类型转换时优先使用 static_cast,对于底层操作(如直接访问寄存器)可谨慎使用 reinterpret_cast。避免过度使用 dynamic_cast,因为其运行时检查会带来一定性能开销。
- 统一初始化语法 :尽量使用统一初始化语法,它能提高代码的一致性和可读性,尤其在初始化 STL 容器时非常方便。
- 函数重载 :合理使用函数重载可以提高代码的可读性和可维护性,但要注意避免重载过于复杂导致代码难以理解。
- 编译时断言 :在代码中使用编译时断言可以提前发现一些潜在的错误,特别是在处理平台特定要求或常量表达式检查时非常有用。
- 数值限制 :在编写涉及数值计算的代码时,使用 std::numeric_limits 可以避免数值溢出等问题,同时可以方便地进行模板编程。
- STL 容器 :根据具体的编程需求选择合适的 STL 容器。如果需要快速随机访问,可选择 std::array 或 std::vector;如果需要频繁在前后端插入和删除元素,可选择 std::deque;如果需要在序列中任意位置快速插入和删除,可选择 std::list。
- STL 迭代器 :熟练掌握迭代器的使用,利用迭代器可以方便地遍历和操作容器元素,同时要注意常量迭代器和非常量迭代器的区别。
3.3 操作流程总结
以下是使用 STL 容器和迭代器的一般操作流程:
graph LR
A[创建容器] --> B[初始化容器元素]
B --> C[使用迭代器遍历容器]
C --> D[对容器元素进行操作]
D --> E[根据需要修改容器大小或内容]
E --> F[使用容器的其他成员函数进行操作]
通过以上对 C++ 编程基础与高级特性的解析,希望能帮助大家更好地理解和运用这些特性,编写出更高效、更健壮的 C++ 代码。
超级会员免费看


被折叠的 条评论
为什么被折叠?



