64位C++学习笔记

编程化境

  • 操作系统:windows 64位
  • 具体环境:windows64位 + msys2(UCRT64,gcc)+ clion + cmake
  • c++标准的配置: c++2026

常用附加库

  • boost
  • fmt

头文件:ayzw.top/init.h

持续更新

//
// Created by ayzw.top on 2026/6/10.
// 自定义cpp文件
//
#include <iostream> // 尖括号 < > —— 优先搜索系统路径
#include <limits>
#include <iomanip>
#include <cstdint>
#include <quadmath.h>
#include <fmt/core.h>
#include <fmt/ranges.h>
#include <fmt/os.h>
#include <format>

// using 是现代 C++ 的类型安全系统(诞生于 C++11),完全遵循 C++ 的作用域规则。
// 它可以被限制在某个函数内部、某个类(class)内部,或者某个命名空间(namespace)中
// 不要用 #define ,它处于预处理阶段,在代码编译之前,预处理器会像文本编辑器的“全部替换”功能一样,
// 粗暴地把宏名换成后面的文本。它完全不懂 C++ 语法
// 整数类型简写

using int8 = std::int8_t;
using int16 = std::int16_t;
using int32 = std::int32_t;
using int64 = std::int64_t;

using uint8 = std::uint8_t;
using uint16 = std::uint16_t;
using uint32 = std::uint32_t;
using uint64 = std::uint64_t;

using f128 = __float128;

using namespace std;
using namespace fmt;

整数类型

使用 <cstdint> 库,该库提供了跨平台、大小完全固定的整数类型。使用这些类型可以彻底解决 int 或 long 在不同系统(如 32位 vs 64位)上字节长度缩水的问题。

字节数 类型名称 (有符号) 有符号取值范围 类型名称 (无符号) 无符号取值范围
1 字节 int8_t -128 到 127 uint8_t 0 到 255
2 字节 int16_t -32,768 到 32,767 uint16_t 0 到 65,535
4 字节 int32_t 约 -21.4 亿 到 21.4 亿 uint32_t 0 到 约 42.9 亿
8 字节 int64_t ≈ ± 9.22 × 10¹⁸ (百亿亿) uint64_t 0 到 ≈ 1.84 × 10¹⁹

打印示例:

#include <cstdint>
#include <fmt/core.h>

int main() {
    // 1. 有符号整数 (Signed Integers)
    int8_t  a_8  = -12;
    int16_t a_16 = -1234;
    int32_t a_32 = -1234567;
    int64_t a_64 = -1234567890123LL;

    fmt::print("--- 有符号整数 ---\n");
    fmt::print("int8_t:  {}\n", a_8);
    fmt::print("int16_t: {}\n", a_16);
    fmt::print("int32_t: {}\n", a_32);
    fmt::print("int64_t: {}\n", a_64);

    // 2. 无符号整数 (Unsigned Integers)
    uint8_t  b_8  = 250;
    uint16_t b_16 = 65000;
    uint32_t b_32 = 4294967295U;
    uint64_t b_64 = 18446744073709551615ULL;

    fmt::print("\n--- 无符号整数 ---\n");
    // 注意:uint8_t 可能会被某些旧版或特殊配置当作 char 打印。
    // fmt 默认会将其当作数字打印。
    fmt::print("uint8_t:  {}\n", b_8); 
    fmt::print("uint16_t: {}\n", b_16);
    fmt::print("uint32_t: {}\n", b_32);
    fmt::print("uint64_t: {}\n", b_64);

    // 3. 高级格式化技巧
    fmt::print("\n--- 高级格式化 ---\n");
    fmt::print("十六进制显示 (小写): {:x}\n", b_32);
    fmt::print("十六进制显示 (大写且带前缀): {:#X}\n", b_64);
    fmt::print("二进制显示: {:b}\n", b_8);
    fmt::print("靠右对齐并补零 (宽度为10): {:010}\n", a_32);

    return 0;
}

输出:

--- 有符号整数 ---
int8_t:  -12
int16_t: -1234
int32_t: -1234567
int64_t: -1234567890123

--- 无符号整数 ---
uint8_t:  250
uint16_t: 65000
uint32_t: 4294967295
uint64_t: 18446744073709551615

--- 高级格式化 ---
十六进制显示 (小写): ffffffff
十六进制显示 (大写且带前缀): 0XFFFFFFFFFFFFFFFF
二进制显示: 11111010
靠右对齐并补零 (宽度为10): -001234567

整数字面量,建议大写后缀U,LL,ULL:

#include "ayzw.top/init.h"

int main() {
    // 1. 显式指定 8位 和 16位 整型变量(GCC 原生最正统写法)
    int8 val_i8 = 120;                       // 8位 有符号整型
    uint8 val_u8 = 250;                       // 8位 无符号整型
    int16 val_i16 = 32000;                     // 16位 有符号整型
    uint16 val_u16 = 64000;                     // 16位 无符号整型

    // 2. GCC 原生最欢迎的大写纯后缀(32位与64位)
    int32 val_i32 = 2147483647;                    // 默认就是标准的 32位 signed int
    uint32 val_u32 = 4294967295U;                   // 大写 U 表示 32位 unsigned int
    int64 val_i64 = 9'223'372'036'854'775'807LL;   // 大写 LL 强行确保 64位 signed
    uint64 val_u64 = 18'446'744'073'709'551'615ULL; // 大写 ULL 强行确保 64位 unsigned

    print("=== 8位与16位整型验证 ===\n");
    // 注意:8位整型底层是 char,必须强转 int 才能用 fmt 正确打印出数字
    print("val_i8  物理大小: {} 字节, 数值: {}\n", sizeof(val_i8), static_cast<int>(val_i8));
    print("val_u8  物理大小: {} 字节, 数值: {}\n", sizeof(val_u8), static_cast<int>(val_u8));
    print("val_i16 物理大小: {} 字节, 数值: {}\n", sizeof(val_i16), val_i16);
    print("val_u16 物理大小: {} 字节, 数值: {}\n\n", sizeof(val_u16), val_u16);

    print("=== 32位与64位大写后缀验证 ===\n");
    print("val_i32 物理大小: {} 字节, 数值: {}\n", sizeof(val_i32), val_i32);
    print("val_u32 物理大小: {} 字节, 数值: {}\n", sizeof(val_u32), val_u32);
    print("val_i64 物理大小: {} 字节, 数值: {}\n", sizeof(val_i64), val_i64);
    print("val_u64 物理大小: {} 字节, 数值: {}\n", sizeof(val_u64), val_u64);

    return 0;
}

输出:

=== 8位与16位整型验证 ===
val_i8  物理大小: 1 字节, 数值: 120
val_u8  物理大小: 1 字节, 数值: 250
val_i16 物理大小: 2 字节, 数值: 32000
val_u16 物理大小: 2 字节, 数值: 64000

=== 32位与64位大写后缀验证 ===
val_i32 物理大小: 4 字节, 数值: 2147483647
val_u32 物理大小: 4 字节, 数值: 4294967295
val_i64 物理大小: 8 字节, 数值: 9223372036854775807
val_u64 物理大小: 8 字节, 数值: 18446744073709551615

浮点数

在 64 位操作系统下,float 和 double 的取值范围是绝对的定值:

  • float(单精度浮点数):永远占用 4 个字节(32 位)
  • double(双精度浮点数):永远占用 8 个字节(64 位)
类型 占用字节 有效数字精度
float 4 字节 约 6 ~ 7 位
double 8 字节 约 15 ~ 17 位
f128 16 字节 约 33 ~ 36 位
  • float只能保证6位有效数字
  • double只能保证15位有效数字
  • __float128 (对应 IEEE 754 标准中的 binary128 四精度格式)拥有 113 位二进制尾数。转换为十进制后,它保证具有 33 位完全准确的有效数字(digits10),最大可完整显示 36 位有效数字
#include "ayzw.top/init.h"

#include <iostream>
#include <iomanip>    // 用于控制 float 和 double 的精度
#include <quadmath.h> // GCC 原生 128 位浮点数头文件

int main() {

    // 1. 字面量声明(注意:__float128 必须加 q 后缀)
    float       f_val = 1.123456789012345678901234567890123456789f;
    double      d_val = 1.123456789012345678901234567890123456789;
    __float128  q_val = 1.123456789012345678901234567890123456789q;

    // 2. 打印 float 和 double(使用最常用的 std::setprecision)
    std::cout << "--- 强行打印 40 位小数对比 ---" << std::endl;
    std::cout << "float:      " << std::fixed << std::setprecision(40) << f_val << std::endl;
    std::cout << "double:     " << std::fixed << std::setprecision(40) << d_val << std::endl;

    // 3. 打印 __float128(最原始、最直接的单行缓冲区法)
    char buf[128];
    // %.36Qf 表示:打印 36 位小数,Q 是 __float128 的专属占位符
    quadmath_snprintf(buf, sizeof(buf), "%.36Qf", q_val);

    std::cout << "__float128: " << buf << std::endl;

    return 0;
}

输出:

--- 强行打印 40 位小数对比 ---
float:      1.1234568357467651367187500000000000000000
double:     1.1234567890123456912476740399142727255821
__float128: 1.123456789012345678901234567890123475

一般情况下,double的精度足够了

不用boost库的float128位小数,速度相对较慢,不过boost通用性更强–支持clang编译器

浮点数字面量后缀:建议大写F、Q

#include "ayzw.top/init.h"

int main() {
    print("=== 64位系统 浮点数字面量后缀极限体检 ===\n\n");

    // 1. 使用标准大写后缀声明字面量(故意写一个 40 位的超长小数)
    auto f_val = 1.123456789012345678901234567890123456789F;   // 大写 F 强制 float
    auto d_val = 1.123456789012345678901234567890123456789;    // 无后缀 默认 double
    auto q_val = 1.123456789012345678901234567890123456789Q;   // 大写 Q 强制 __float128

    // 2. 强行要求打印 45 位小数,肉眼观察内存截断点
    print("[float (F后缀)]\n");
    cout << fixed << setprecision(45) << f_val << "\n";
    print("-> 结论: 仅前 7 位正确,后面全部崩塌。\n\n");

    print("[double (无后缀)]\n");
    cout << fixed << setprecision(45) << d_val << "\n";
    print("-> 结论: 仅前 15~17 位正确,后面开始失真。\n\n");

    print("[__float128 (Q后缀)]\n");
    char buf[128];
    quadmath_snprintf(buf, sizeof(buf), "%.45Qf", q_val); // 使用专属的 Q 占位符
    print("{}\n", buf);
    print("-> 结论: 完美保持精准到第 34 位小数!之后才出现二进制舍入误差。\n");

    return 0;
}

输出:

=== 64位系统 浮点数字面量后缀极限体检 ===

[float (F后缀)]
1.123456835746765136718750000000000000000000000
-> 结论: 仅前 7 位正确,后面全部崩塌。

[double (无后缀)]
1.123456789012345691247674039914272725582122803
-> 结论: 仅前 15~17 位正确,后面开始失真。

[__float128 (Q后缀)]
1.123456789012345678901234567890123474525951569
-> 结论: 完美保持精准到第 34 位小数!之后才出现二进制舍入误差。

求模 %

  • 结果如果不为零,的符号和被除数一致,所以可能为负数
  • 判断奇数、偶数比较好的方式是:求模结果是否为零,而不是为1
int main() {
    print("=== C++ 求模运算符号测试 ===\n\n");

    print(" 10 %  3 = {}\n",  10 %  3);
    print(" 10 % -3 = {}\n",  10 % -3);
    print("-10 %  3 = {}\n", -10 %  3);
    print("-10 % -3 = {}\n", -10 % -3);

    return 0;
}

结果

=== C++ 求模运算符号测试 ===

 10 %  3 = 1
 10 % -3 = 1
-10 %  3 = -1
-10 % -3 = -1

强制类型转换

函数风格:

typeName (value)

强制类型转换运算符:

static_cat(value)

#include "ayzw.top/init.h"

int main() {
    print("=== C++ 强制类型转换全面验证 ===\n\n");

    // ====================================================
    // 1. 验证“向零截断”行为(用 3.678 证明不是四舍五入)
    // ====================================================
    double val_double = 3.678;

    // 无论是函数风格还是 static_cast,3.678 转换后都会变成 3,而不是 4
    int32_t int_func = int32_t(val_double);
    int32_t int_static = static_cast<int32_t>(val_double);

    print("[1. 浮点数转整数:截断行为验证]\n");
    print("原始 double 数值:       {}\n", val_double);
    print("函数风格转换结果:       {}\n", int_func);
    print("static_cast 转换结果:   {}\n\n", int_static);


    // ====================================================
    // 2. 新增:双精度 double 转换为 单精度 float
    // ====================================================
    // 声明一个拥有 16 位长尾数的典型 double 变量
    double precise_double = 3.123456789012345;

    // 转换为只有 32 位位宽的 float(有效数字只有约 7 位)
    float float_func = float(precise_double);
    float float_static = static_cast<float>(precise_double);

    print("[2. double 转 float:精度缩水验证]\n");
    print("原始精确 double:        {:.15f}\n", precise_double);
    // 强行要求打印 15 位小数,观察 float 内存截断
    print("函数风格转 float:       {:.15f}\n", float_func);
    print("static_cast 转 float:   {:.15f}\n", float_static);
    print("-> 结论: 转换后从第 8 位数字开始就发生了精度扭曲丢失。\n\n");


    // ====================================================
    // 3. 真实重现:字符 'A' 的打印困境
    // ====================================================
    char char_val = 'A'; // 底层对应的 ASCII 码就是整数 65

    print("[3. 真实应用:解决字符与数字的打印困境]\n");
    print("直接打印 char 变量:      {}\n", char_val);
    print("使用 static_cast 强转后: {}\n\n", static_cast<int>(char_val));


    // ====================================================
    // 4. 补充:C++ 标准流 cout 中的困境重现
    // ====================================================
    uint8_t byte_val = 65;
    cout << "[4. 标准 cout 流中的相同表现]\n";
    cout << "直接用 cout 打印 uint8_t:   " << byte_val << " (屏幕打印出的是字符 A!)\n";
    cout << "用 static_cast 强转后打印: " << static_cast<int>(byte_val) << " (顺利打印出数字 65)\n";

    return 0;
}

输出:

=== C++ 强制类型转换全面验证 ===

[1. 浮点数转整数:截断行为验证]
原始 double 数值:       3.678
函数风格转换结果:       3
static_cast 转换结果:   3

[2. double 转 float:精度缩水验证]
原始精确 double:        3.123456789012345
函数风格转 float:       3.123456716537476
static_cast 转 float:   3.123456716537476
-> 结论: 转换后从第 8 位数字开始就发生了精度扭曲丢失。

[3. 真实应用:解决字符与数字的打印困境]
直接打印 char 变量:      A
使用 static_cast 强转后: 65

[4. 标准 cout 流中的相同表现]
直接用 cout 打印 uint8_t:   A (屏幕打印出的是字符 A!)
用 static_cast 强转后打印: 65 (顺利打印出数字 65)

数组

数组定义:

typeName arrayName[arraySize];

arraySize不能为变量。

字符串

  • c语言风格的字符串是以空字符\0(ASCII码为0)结尾的字符串数组。C 语言风格的字符串在内存中本质上就是一个 char 数组。它最核心的灵魂在于 必须以 \0(空字符)作为结束标记。如果不含空字符\0,就不是字符串,只是一个字符数组。
  • 双引号z
char str1[5]={'a','b','c','d','e'};
char str2[5]={'a','b','c','d','\0'};

#include "ayzw.top/init.h"

int main() {
    string  s1="hello",s2=" world";
    string s3=s1+s2;
    cout<<s3<<endl;
    cout<<s1.size()<<" "<<s2.size()<<" "<<s3.size()<<endl;
    getline(cin,s3);
    cout<<s3<<endl;
    return 0;
}

输出:

hello world
5 6 11
你好
你好

结构

结构比数组更灵活,可以存储多种类型的数据。结构也是C++OOP编程的基石。

#include "ayzw.top/init.h"

struct person {
    string name;
    float heightCM;
    float weightKG;
};

int main() {

    person myFriend1{
            "小明",
            180,
            80
    };

    person myFriend2 = myFriend1;
    myFriend2.name = "大刚";
    myFriend2.heightCM = 190;
    myFriend2.weightKG = 90;

    cout << "我有一位好朋友名字叫" << myFriend1.name << ",他身高" << myFriend1.heightCM << "cm,体重"
         << myFriend1.weightKG
         << "kg." << endl;


    cout << "我有一位好朋友名字叫" << myFriend2.name << ",他身高" << myFriend2.heightCM << "cm,体重"
         << myFriend2.weightKG
         << "kg." << endl;
    return 0;
}

输出:

我有一位好朋友名字叫小明,他身高180cm,体重80kg.
我有一位好朋友名字叫大刚,他身高190cm,体重90kg.

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