C++现代编程技术–写给工程师的C++教程

第1章 C++基础

在线参考网站 cppreference: https://zh.cppreference.com/%E9%A6%96%E9%A1%B5

不考虑存储空间问题,整数用 long long, 浮点数用 long double

整数:推荐用int,long long

win64,msys2 + clang64环境:

#include <iostream>
#include <limits>

int main() {
    // 打印字节数
    std::cout << "int 长度: " << sizeof(int) << " 字节\n";
    std::cout << "long 长度: " << sizeof(long) << " 字节\n";
    std::cout << "long long 长度: " << sizeof(long long) << " 字节\n\n";

    // 打印有符号范围
    std::cout << "--- 有符号范围 (Signed) ---\n";
    std::cout << "int 范围: [" << std::numeric_limits<int>::min() << " 到 " << std::numeric_limits<int>::max() << "]\n";
    std::cout << "long 范围: [" << std::numeric_limits<long>::min() << " 到 " << std::numeric_limits<long>::max() << "]\n";
    std::cout << "long long 范围: [" << std::numeric_limits<long long>::min() << " 到 " << std::numeric_limits<long long>::max() << "]\n\n";

    // 打印无符号范围
    std::cout << "--- 无符号范围 (Unsigned) ---\n";
    std::cout << "unsigned int 最大值: " << std::numeric_limits<unsigned int>::max() << "\n";
    std::cout << "unsigned long 最大值: " << std::numeric_limits<unsigned long>::max() << "\n";
    std::cout << "unsigned long long 最大值: " << std::numeric_limits<unsigned long long>::max() << "\n";

    return 0;
}

输出:

int 长度: 4 字节
long 长度: 4 字节
long long 长度: 8 字节

--- 有符号范围 (Signed) ---
int 范围: [-2147483648 到 2147483647]
long 范围: [-2147483648 到 2147483647]
long long 范围: [-9223372036854775808 到 9223372036854775807]

--- 无符号范围 (Unsigned) ---
unsigned int 最大值: 4294967295
unsigned long 最大值: 4294967295
unsigned long long 最大值: 18446744073709551615

定宽整数类型(c++11及以上)

参考: https://zh.cppreference.com/cpp/types/integer

整数类型分为有符号数(正整数、零、负整数)和无符号数(非负整数)。

64位整数的字面值加 LL 或 ULL。

有符号类型 无符号类型 位数 字节数 有符号范围 无符号范围
int8_t uint8_t 8 1 -128 ~ 127 0 ~ 255
int16_t uint16_t 16 2 -32,768 ~ 32,767 0 ~ 65,535
int32_t uint32_t 32 4 -2,147,483,648 ~ 2,147,483,647 0 ~ 4,294,967,295
int64_t uint64_t 64 8 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 0 ~ 18,446,744,073,709,551,615

printf打印格式:

类型 printf 格式 说明
int8_t / int16_t / int32_t %d int8_t/int16_t 需先强转为 int
int64_t %lld 或 %ld(Linux 64位) long long 类型
uint8_t / uint16_t / uint32_t %u uint8_t/uint16_t 需先强转为 unsigned int
uint64_t %llu 或 %lu(Linux 64位) unsigned long long 类型

#include
#include

int main() {
int8_t i8 = 127;
int16_t i16 = 32767;
int32_t i32 = 2147483647;
int64_t i64 = 9223372036854775807LL;

uint8_t ui8 = 255;
uint16_t ui16 = 65535;
uint32_t ui32 = 4294967295;
uint64_t ui64 = 18446744073709551615ULL;

printf("int8_t: %d\n", i8);
printf("int16_t: %d\n", i16);
printf("int32_t: %d\n", i32);
printf("int64_t: %lld\n", i64);

printf("uint8_t: %u\n", ui8);
printf("uint16_t: %u\n", ui16);
printf("uint32_t: %u\n", ui32);
printf("uint64_t: %llu\n", ui64);

return 0;

}
使用定宽整数类型,需要引入 <cstdint> 并且编译器实现了相应的类型,检测是否支持:

#include <iostream>
#include <limits>
#include <cstdint> //cstdint 是 C++11 引入的

// ==========================================
// 辅助打印逻辑 
// ========================================== 

// 通用打印函数模板
// 注意:对于 int8_t/uint8_t,必须强转为 int/unsigned int,否则会打印字符 
template <typename T>
void print_type_range(const char* type_name) {
	std::cout << "【" << type_name << "】" << std::endl;

	// 使用一元加号 (+) 触发整型提升,避免 int8_t 被当作字符打印 
	// 此时 +val 会自动转换为 int 或 unsigned int
	std::cout << "  最小值: " << +std::numeric_limits<T>::min() << std::endl;
	std::cout << "  最大值: " << +std::numeric_limits<T>::max() << std::endl;
	std::cout << "  字节数: " << sizeof(T) << " (固定宽度保证)" << std::endl;
	std::cout << "-----------------------------------" << std::endl;
}

// ==========================================
// 主程序 
// ========================================== 

int main() {
	std::cout << "=== 编译器固定宽度整数类型检测 ===" << std::endl;
	std::cout << "\n>>> 开始检测具体类型支持情况...\n" << std::endl;

	// 检测 int8_t
#ifdef INT8_MAX // 标准规定:类型定义存在当且仅当对应的宏定义存在
	print_type_range<int8_t>("int8_t (有符号8位)");
#else
	std::cout << "[不支持] int8_t" << std::endl;
#endif 

	// 检测 uint8_t 
#ifdef UINT8_MAX
	print_type_range<uint8_t>("uint8_t (无符号8位)");
#endif 

	// 检测 int16_t 
#ifdef INT16_MAX
	print_type_range<int16_t>("int16_t (有符号16位)");
#endif 

	// 检测 uint16_t 
#ifdef UINT16_MAX
	print_type_range<uint16_t>("uint16_t (无符号16位)");
#endif

	// 检测 int32_t
#ifdef INT32_MAX 
	print_type_range<int32_t>("int32_t (有符号32位)");
#endif

	// 检测 uint32_t
#ifdef UINT32_MAX 
	print_type_range<uint32_t>("uint32_t (无符号32位)");
#endif

	// 检测 int64_t
#ifdef INT64_MAX 
	print_type_range<int64_t>("int64_t (有符号64位)");
#endif

	// 检测 uint64_t
#ifdef UINT64_MAX 
	print_type_range<uint64_t>("uint64_t (无符号64位)");
#endif

	return 0;
}

输出:

=== 编译器固定宽度整数类型检测 ===

>>> 开始检测具体类型支持情况...

【int8_t (有符号8位)】
  最小值: -128
  最大值: 127
  字节数: 1 (固定宽度保证)
-----------------------------------
【uint8_t (无符号8位)】
  最小值: 0
  最大值: 255
  字节数: 1 (固定宽度保证)
-----------------------------------
【int16_t (有符号16位)】
  最小值: -32768
  最大值: 32767
  字节数: 2 (固定宽度保证)
-----------------------------------
【uint16_t (无符号16位)】
  最小值: 0
  最大值: 65535
  字节数: 2 (固定宽度保证)
-----------------------------------
【int32_t (有符号32位)】
  最小值: -2147483648
  最大值: 2147483647
  字节数: 4 (固定宽度保证)
-----------------------------------
【uint32_t (无符号32位)】
  最小值: 0
  最大值: 4294967295
  字节数: 4 (固定宽度保证)
-----------------------------------
【int64_t (有符号64位)】
  最小值: -9223372036854775808
  最大值: 9223372036854775807
  字节数: 8 (固定宽度保证)
-----------------------------------
【uint64_t (无符号64位)】
  最小值: 0
  最大值: 18446744073709551615
  字节数: 8 (固定宽度保证)
-----------------------------------

浮点数:建议用double或long double

参考:https://zh.cppreference.com/cpp/types/floating-point

windows系统下clang、MSVC等long double都是64位,对128位目前无完整支持。建议日常用double。

示例1:

#include <iostream>
#include <limits>
#include <iomanip>

int main() {
    // 1. 测试 size (占用字节数)
    std::cout << "Size of double: " << sizeof(double) << " bytes\n";

    // 2. 测试有效位数 (std::numeric_limits 可以输出底层标准保证的最小有效数字)
    std::cout << "Double digits10: " << std::numeric_limits<double>::digits10 << " digits\n";
    std::cout << "Double max_digits10: " << std::numeric_limits<double>::max_digits10 << " digits\n";

    // 3. 精度测试:观察超过有效位数后的截断现象
    double d1 = 12345.67890123456789; // 共19位数字
    double d2 = 1.0 / 3.0;             // 经典的无限循环小数
    float f1 = 2345.67890123456789; // 共19位数字
    float f2 = 1.0 / 3.0;
    // 使用 setprecision 控制输出的有效位数
    std::cout << "\nTesting Precision (Max digits output):\n";
    std::cout << std::setprecision(18); // 输出18位以便观察极限
    std::cout << "d1 (Original 19 digits) = " << std::endl << d1 << "\n";
    std::cout << "d2 (1.0 / 3.0)           = " << std::endl << d2 << "\n";
    std::cout << "f1 (Original 19 digits)           = " << std::endl << f1 << "\n";
    std::cout << "f2 (1.0 / 3.0)           = " << std::endl << f2 << "\n";
    return 0;
}

输出:

Size of double: 8 bytes
Double digits10: 15 digits
Double max_digits10: 17 digits

Testing Precision (Max digits output):
d1 (Original 19 digits) =
12345.6789012345671
d2 (1.0 / 3.0)           =
0.333333333333333315
f1 (Original 19 digits)           =
2345.678955078125
f2 (1.0 / 3.0)           =
0.333333343267440796

示例2(windows6, msys2 clang64环境):

#include <iostream>
#include <iomanip> // 必须包含此头文件来控制小数位数
#include <limits> // 必须包含该头文件

int main() {
    // 1. 定义并计算
    double d_val = 1.0 / 3.0;
    long double ld_val = 1.0L / 3.0L; // 使用 L 后缀确保以 long double 精度进行计算

    // 2. 打印字节长度(占用内存位数 = 字节数 * 8)
    std::cout << "double 长度: " << sizeof(d_val) << " 字节, 位数: " << sizeof(d_val) * 8 << " bits\n";
    std::cout << "long double 长度: " << sizeof(ld_val) << " 字节, 位数: " << sizeof(ld_val) * 8 << " bits\n\n";

    // 3. 设置输出格式:固定小数显示(fixed),并指定 40 位精度(setprecision)
    std::cout << std::fixed << std::setprecision(40);

    // 4. 打印 40 位小数精度的 1/3
    std::cout << "double 类型的 1/3 (40位小数):\n" << d_val << "\n\n";
    std::cout << "long double 类型的 1/3 (40位小数):\n" << ld_val << "\n";

    // digits10 返回的是可以绝对保证无误差表示的十进制位数(即 15 和 18)
    std::cout << "double 绝对保证的有效位数: "
              << std::numeric_limits<double>::digits10 << " 位\n";

    std::cout << "long double 绝对保证的有效位数: "
              << std::numeric_limits<long double>::digits10 << " 位\n\n";

    // max_digits10 返回的是能唯一标识该浮点数所需的十进制位数(即 17 和 21)
    std::cout << "double 区分不同数值所需的最大有效位数: "
              << std::numeric_limits<double>::max_digits10 << " 位\n";

    std::cout << "long double 区分不同数值所需的最大有效位数: "
              << std::numeric_limits<long double>::max_digits10 << " 位\n";

    // std::numeric_limits<T>::digits 返回的是二进制的有效位数(即尾数+隐含位)
    std::cout << "double 的实际二进制有效存储位数: "
              << std::numeric_limits<double>::digits << " bits\n";

    std::cout << "long double 的实际二进制有效存储位数: "
              << std::numeric_limits<long double>::digits << " bits\n";

    return 0;
}

输出:

double 长度: 8 字节, 位数: 64 bits
long double 长度: 16 字节, 位数: 128 bits

double 类型的 1/3 (40位小数):
0.3333333333333333148296162562473909929395

long double 类型的 1/3 (40位小数):
0.3333333333333333333423683514373792036167
double 绝对保证的有效位数: 15 位
long double 绝对保证的有效位数: 18 位

double 区分不同数值所需的最大有效位数: 17 位
long double 区分不同数值所需的最大有效位数: 21 位
double 的实际二进制有效存储位数: 53 bits
long double 的实际二进制有效存储位数: 64 bits
  • 在 Windows/Linux 的 x86/x64 架构上,long double 通常硬件层面是 80 位的(由 x87 FPU 寄存器决定),但在 64 位系统内存中为了对齐,通常会分配 128 位(16字节)的空间,其中有 48 位是空闲不用的。

128位浮点数方案之一:使用boost库(安装boost/multiprecision)

#include <iostream>
#include <boost/multiprecision/cpp_bin_float.hpp>

using namespace boost::multiprecision;

int main() {
    // Defines a 128-bit quadruple precision float (equivalent to binary128)
    cpp_bin_float_quad a = 2.0;
    cpp_bin_float_quad b = 3.0;
    cpp_bin_float_quad c = a / b;

    // Set high formatting precision for output
    std::cout << std::setprecision(std::numeric_limits<cpp_bin_float_quad>::digits10) << c << std::endl;
    return 0;
}

字符

ASCII为 0-127:

#include "cstdio"

int main() {
    // 循环打印 ASCII 表中 0 到 127 的所有字符
    for (int i = 0; i <= 127; ++i) {
        printf("ASCII %d:", i);
        // 前32个字符是不可打印的控制字符(如换行、退格等)
        if (i < 32) {
            printf("[控制字符]\n");
        } else {
            printf("%c\n", i);
        }
    }
    return 0;
}

输出:

ASCII 0:[控制字符]
ASCII 1:[控制字符]
ASCII 2:[控制字符]
ASCII 3:[控制字符]
ASCII 4:[控制字符]
ASCII 5:[控制字符]
ASCII 6:[控制字符]
ASCII 7:[控制字符]
ASCII 8:[控制字符]
ASCII 9:[控制字符]
ASCII 10:[控制字符]
ASCII 11:[控制字符]
ASCII 12:[控制字符]
ASCII 13:[控制字符]
ASCII 14:[控制字符]
ASCII 15:[控制字符]
ASCII 16:[控制字符]
ASCII 17:[控制字符]
ASCII 18:[控制字符]
ASCII 19:[控制字符]
ASCII 20:[控制字符]
ASCII 21:[控制字符]
ASCII 22:[控制字符]
ASCII 23:[控制字符]
ASCII 24:[控制字符]
ASCII 25:[控制字符]
ASCII 26:[控制字符]
ASCII 27:[控制字符]
ASCII 28:[控制字符]
ASCII 29:[控制字符]
ASCII 30:[控制字符]
ASCII 31:[控制字符]
ASCII 32:
ASCII 33:!
ASCII 34:"
ASCII 35:#
ASCII 36:$
ASCII 37:%
ASCII 38:&
ASCII 39:'
ASCII 40:(
ASCII 41:)
ASCII 42:*
ASCII 43:+
ASCII 44:,
ASCII 45:-
ASCII 46:.
ASCII 47:/
ASCII 48:0
ASCII 49:1
ASCII 50:2
ASCII 51:3
ASCII 52:4
ASCII 53:5
ASCII 54:6
ASCII 55:7
ASCII 56:8
ASCII 57:9
ASCII 58::
ASCII 59:;
ASCII 60:<
ASCII 61:=
ASCII 62:>
ASCII 63:?
ASCII 64:@
ASCII 65:A
ASCII 66:B
ASCII 67:C
ASCII 68:D
ASCII 69:E
ASCII 70:F
ASCII 71:G
ASCII 72:H
ASCII 73:I
ASCII 74:J
ASCII 75:K
ASCII 76:L
ASCII 77:M
ASCII 78:N
ASCII 79:O
ASCII 80:P
ASCII 81:Q
ASCII 82:R
ASCII 83:S
ASCII 84:T
ASCII 85:U
ASCII 86:V
ASCII 87:W
ASCII 88:X
ASCII 89:Y
ASCII 90:Z
ASCII 91:[
ASCII 92:\
ASCII 93:]
ASCII 94:^
ASCII 95:_
ASCII 96:`
ASCII 97:a
ASCII 98:b
ASCII 99:c
ASCII 100:d
ASCII 101:e
ASCII 102:f
ASCII 103:g
ASCII 104:h
ASCII 105:i
ASCII 106:j
ASCII 107:k
ASCII 108:l
ASCII 109:m
ASCII 110:n
ASCII 111:o
ASCII 112:p
ASCII 113:q
ASCII 114:r
ASCII 115:s
ASCII 116:t
ASCII 117:u
ASCII 118:v
ASCII 119:w
ASCII 120:x
ASCII 121:y
ASCII 122:z
ASCII 123:{
ASCII 124:|
ASCII 125:}
ASCII 126:~
ASCII 127:

字符串

可以用字符数组表示字符串,以二进制0结尾(c语言风格),更简单的方式是使用字符串类,需要引用 <string>,可以使用动态内存并自我管理。

#include "string"
#include "iostream"

int main() {
    std::string s = "hello world!";
    std::cout<<s<<std::endl;
    s+="你好,世界!";
    std::cout<<s<<std::endl;
    return 0;
}
//hello world!
//hello world!你好,世界!

字面量

整数字面量后缀:

后缀 代表类型 示例 说明
无后缀 int (优先) 42 默认类型。若超出 int 范围,编译器会自动提升为 long 或 long long。
U / u unsigned int 42U 强制将数字视为无符号整数(不能为负数)。
L / l long 42L 强制将数字视为 long 类型。
UL / ul unsigned long 42UL 强制将数字视为无符号 long 类型(U 和 L 顺序可颠倒,如 LU)。
LL / ll long long 42LL 强制将数字视为 64 位 long long 类型。
ULL / ull unsigned long long 42ULL 强制将数字视为无符号 64 位 long long(U 和 LL 顺序可颠倒)。

浮点数字面量后缀:

后缀 代表类型 占用字节 (x86_64) 示例 说明
无后缀 double 8 字节 3.14
1e-3
默认类型。保证 15-17 位十进制有效数字。
F / f float 4 字节 3.14F
1.0f
单精度浮点数。保证 6-7 位十进制有效数字。
L / l long double 12 或 16 字节 3.14L
1.0L
扩展/四精度浮点数。保证 18-21 位十进制有效数字。

long double 字面量建议加大写后缀L,如果不加 L 后缀,编译器会先以 double 的低精度来解析这个字面量,然后再隐式转换赋值给 long double。这会导致在字面量解析阶段,精度就已经丢失了:

#include <iostream>
#include <iomanip> // 必须包含此头文件来控制小数位数

int main() {
    // 1. 定义并计算
    long double ld_1 = 0.1234567890123456789;
    long double ld_2 = 0.1234567890123456789L;

    std::cout << std::fixed << std::setprecision(20);

    // 4. 打印 40 位小数精度的 1/3
    std::cout << "ld_1:" << ld_1 << std::endl;
    std::cout << "ld_2:" << ld_2 << std::endl;

    return 0;
}
//ld_1:0.12345678901234567737
//ld_2:0.12345678901234567890

0b或0B、0、0x或0X开头的整数,分别对应2进制、8进制、16禁止字面量。

布尔操作符

核心逻辑操作符:

操作符 语法名称 作用说明 示例 (a=true, b=false)
! 逻辑非 (NOT) 取反。将 true 变 false,false 变 true。 !a 结果为 false
&& 逻辑与 (AND) 两者皆为 true 时,结果才为 true。 a && b 结果为 false
|| 逻辑或 (OR) 只要有一个为 true,结果就为 true。 a || b 结果为 true

关系/比较操作符:

操作符 作用 示例 (x = 5, y = 10) 结果
== 等于 x == y FALSE
!= 不等于 x != y TRUE
< 小于 x < y TRUE
> 大于 x > y FALSE
<= 小于或等于 x <= 5 TRUE
>= 大于或等于 x >= 10 TRUE
<=> 三路比较 (C++20) 专门用于排序和全比较(返回结构体体,可直接隐式转为布尔判断)

C++ 独有的“文本字面量”替代关键字:如果不喜欢符号 !, &&, ||,或者某些旧键盘打不出这些符号,C++ 标准原生支持直接使用英文单词作为布尔操作符(不需要包含任何头文件):

符号操作符 文本替代关键字 示例写法
&& and if (condition1 and condition2)
|| or if (condition1 or condition2)
! not if (not is_valid)
!= not_eq if (x not_eq y)

位操作符(Bitwise Operators)

直接对数据的二进制位(bit)进行操作。它们常用于底层驱动开发、嵌入式系统、游戏物理引擎、加密算法以及程序性能优化。与逻辑操作符(&&, ||)关注整体真假不同,位操作符会对数据的每一个二进制位分别进行逻辑运算

假定以下示例中:A = 60(二进制 0011 1100),B = 13(二进制 0000 1101)。

操作符 语法名称 运算规则 示例结果(十进制) 示例结果(二进制)
& 按位与 (AND) 两位都是 1 结果才是 1,否则为 0。 A & B → 12 0000 1100
| 按位或 (OR) 两位只要有一个是 1 结果就是 1。 A | B → 61 0011 1101
^ 按位异或 (XOR) 两位不同结果为 1,相同结果为 0。 A ^ B → 49 0011 0001
~ 按位取反 (NOT) 单目运算符:1 变 0,0 变 1。 ~A → -61 1100 0011(注:补码)
<< 左移 所有位向左移动指定位数,右边补 0。 A << 2 → 240 1111 0000
>> 右移 所有位向右移动指定位数,左边补符号位或 0。 A >> 2 → 15 0000 1111

陷阱:进行位运算的数据,强烈建议全部定义为无符号类型(unsigned int / uint8_t 等)

赋值操作

a *= b+c 等效于 a=a*(b+c)

#include <iostream>

int main() {
    // 打印字节数
    int a = 2, b = 3, c = 4;
    a *= b + c;
    std::cout << a << std::endl;

    return 0;
}
// 14

访问操作符

语法层级 操作符 语法名称 核心作用与左值要求 底层本质 / 对应反操作
层级一:对象级 . 成员选择 访问实体对象/引用的内部成员 直接计算内存偏移
层级一:对象级 -> 指针的成员选择 访问指针指向对象的内部成员 等价于 (*ptr).member
层级一:对象级 [] 下标访问 访问数组/指针连续内存中的指定元素 等价于 *(ptr + index)
层级二:内存级 & 取地址 获取任意变量在内存中的起始物理地址 反操作为 *
层级二:内存级 * 间接寻址 / 解引用 访问指针地址中存储的实际数据值 反操作为 &
层级三:元编程级 .* 实体成员指针访问 通过对象实体与“类成员偏移指针”取值 依赖实例 + 相对偏移
层级三:元编程级 ->* 指针成员指针访问 通过对象指针与“类成员偏移指针”取值

层级一:对象与容器级访问(开发高频)

这一层级操作符直接作用于高级语法对象,用于检索对象内部封装的属性或方法。

  1. 成员选择(.)与 指针的成员选择(->)

核心区别:取决于操作符左侧数据的类型。

  • 左侧是实体、临时对象或引用:必须用 .
  • 左侧是原生指针或智能指针:必须用 ->

底层逻辑:ptr->member 纯粹是 (*ptr).member 的语法糖。智能指针(如 std::unique_ptr)则是通过重载 operator-> 实现了相同的语法表现。

  1. 下标访问([])

原生数组/指针:arr[i] 的底层指令是 *(arr + i)。由于加法满足交换律,在原生指针下,甚至写成 i[arr] 在语法上也是合法的(但不推荐)。

标准库容器:对于 std::vector 或 std::map,[] 是一个成员函数重载,底层会执行相应的边界索引查找或红黑树搜索。

层级二:内存与物理地址访问(指针核心)

这一层级打破了面向对象的封装,直接在计算机的物理内存地址层面进行转换。

  1. 取地址(&)

物理本质:将一个变量在栈区或堆区分配的第一个字节的十六进制地址提取出来,赋予其指针类型。

  1. 间接寻址(*)

物理本质:告诉 CPU 前往该指针内部存储的十六进制地址,并根据指针的类型(如 int* 读取 4 字节,double* 读取 8 字节)去强行解析该段内存中的二进制数据。

层级三:指向成员的指针(C++ 独有深水区)

这是 C++ 区别于 C 语言的高级特性。这里的指针存的不是内存绝对地址,而是该成员相对于类起始地址的相对偏移量(Offset)。因此,它必须和具体的实例绑定才能完成寻址。

  1. 对象实体绑定(.*)
  • 语法结构:对象实体 .* 成员偏移指针
  • 应用场景:当你拿到了一个具体的对象,想动态地决定访问它的哪一个属性时使用。
  1. 对象指针绑定(->*)
  • 语法结构:对象指针 ->* 成员偏移指针
  • 底层逻辑:等价于 (对象指针).成员偏移指针。
#include <iostream>
#include <string>

struct DynamicObject {
    std::string label;
    int data_node;
    void Action() { std::cout << label << " 执行了核心动作。\n"; }
};

int main() {
    // ==========================================
    // 【层级二演示】内存地址转换 (& 和 *)
    DynamicObject obj{"单元A", 2026};
    DynamicObject* p_obj = &obj;           // [&] 取地址
    std::cout << (*p_obj).label << "\n";  // [*] 间接寻址/解引用

    // ==========================================
    // 【层级一演示】基础成员选择 (. 、 -> 和 [])
    std::cout << obj.label << "\n";       // [.] 成员选择
    std::cout << p_obj->label << "\n";     // [->] 指针的成员选择

    DynamicObject cluster[2] = { {"节点1", 10}, {"节点2", 20} };
    std::cout << cluster[1].label << "\n\n"; // [[]] 下标访问

    // ==========================================
    // 【层级三演示】现代 C++ 成员指针 (.* 和 ->*)
    // 1. 定义指向类内部属性的指针(存储的是 data_node 的内存偏移量)
    int DynamicObject::*p_offset_data = &DynamicObject::data_node;

    // 2. 定义指向类内部无参函数的指针
    void (DynamicObject::*p_offset_func)() = &DynamicObject::Action;

    // 使用 .* 配合对象实体访问
    std::cout << ".* 访问属性: " << obj.*p_offset_data << "\n";
    (obj.*p_offset_func)(); // :warning:注意:必须加括号,因为 . * 优先级低于 ()

    // 使用 ->* 配合对象指针访问
    std::cout << "->* 访问属性: " << p_obj->*p_offset_data << "\n";
    (p_obj->*p_offset_func)();

    return 0;
}
//单元A
//单元A
//单元A
//节点2
//
//.* 访问属性: 2026
//单元A 执行了核心动作。
//->* 访问属性: 2026
//单元A 执行了核心动作。

陷阱:

1.运算符优先级大坑(带括号法则):在通过成员函数指针调用函数时,写成 obj.*p_offset_func() 会导致编译报错。因为系统会先结合右边的 ()。必须强制写为 (obj.*p_offset_func)()

2.多级指针的成员选择:如果有二级指针 DynamicObject** pp_obj = &p_obj;,不能直接使用 pp_obj->label。必须先解开一级:(*pp_obj)->label

3.安全防御:任何时候执行 -> 或 ->* 前,如果左侧是原生指针,必须先进行 if (p_obj != nullptr) 的判空防御,否则在运行期会触发致命的系统段错误(Segmentation Fault)。

这本书的组织也太BT了

正文完
 0
评论(没有评论)