第3章 处理数据 基本数据类型 整数类型 有符号整数 类型 大小(字节) 范围 short 2 -32768 到 32767 int 4 -2147483648 到 2147483647 long 4 或 8 取决于平台 long long 8 -9223372036854775808 到 9223372036854775807
无符号整数 类型 大小(字节) 范围 unsigned short 2 0 到 65535 unsigned int 4 0 到 4294967295 unsigned long 4 或 8 取决于平台 unsigned long long 8 0 到 18446744073709551615
浮点类型 类型 大小(字节) 精度 范围 float 4 约6-7位有效数字 ±3.4e38 double 8 约15-17位有效数字 ±1.7e308 long double 8 或 16 取决于平台 取决于平台
字符类型 类型 大小(字节) 范围 char 1 -128 到 127 或 0 到 255(取决于实现) signed char 1 -128 到 127 unsigned char 1 0 到 255 wchar_t 2 或 4 取决于实现 char16_t 2 0 到 65535(C++11+) char32_t 4 0 到 4294967295(C++11+) char8_t 1 0 到 255(C++20+,用于UTF-8编码)
布尔类型 类型 大小(字节) 值 bool 1 true 或 false
空类型 C++23数据类型增强 std::expected(C++23+) std::expected是C++23引入的类型,表示可能失败的操作结果,包含一个值或一个错误:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <expected> #include <string> std::expected<int , std::string> divide (int a, int b) { if (b == 0 ) { return std::unexpected ("Division by zero" ); } return a / b; } int main () { auto result1 = divide (10 , 2 ); if (result1) { std::cout << "Result: " << *result1 << std::endl; } else { std::cout << "Error: " << result1. error () << std::endl; } auto result2 = divide (10 , 0 ); if (result2) { std::cout << "Result: " << *result2 << std::endl; } else { std::cout << "Error: " << result2. error () << std::endl; } return 0 ; }
std::mdspan(C++23+) std::mdspan是C++23引入的多维数组视图,提供对连续内存的多维访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <mdspan> #include <vector> int main () { std::vector<int > data = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 }; std::mdspan<int , std::extents<size_t , 2 , 6 >> md (data.data ()); md (0 , 0 ) = 100 ; md (1 , 5 ) = 200 ; for (size_t i = 0 ; i < md.extent (0 ); ++i) { for (size_t j = 0 ; j < md.extent (1 ); ++j) { std::cout << md (i, j) << " " ; } std::cout << std::endl; } return 0 ; }
std::to_underlying(C++23+) std::to_underlying是C++23引入的函数,用于将枚举转换为其底层类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <utility> enum class Color { Red, Green, Blue }; enum class Status : int { Ok = 0 , Error = 1 , Warning = 2 }; int main () { Color c = Color::Green; int color_value = std::to_underlying (c); std::cout << "Color value: " << color_value << std::endl; Status s = Status::Error; int status_value = std::to_underlying (s); std::cout << "Status value: " << status_value << std::endl; return 0 ; }
常量 字面常量 整数常量 1 2 3 4 5 6 7 123 0123 0x123 123U 123L 123LL 123UL
浮点常量 1 2 3 4 5 123.456 123.456f 123.456L 1.23456e2 1.23456E-2
字符常量 1 2 3 4 5 6 7 8 9 10 'a' '\n' '\\' '\'' '"' '\0' L'a' u8'a' u'a' U'a'
字符串常量 1 2 3 4 5 6 "Hello" "Line 1\nLine 2" R"(raw string)" u8"UTF-8" u"UTF-16" U"UTF-32"
布尔常量 const 常量 1 2 3 const int MAX_AGE = 120 ;const double PI = 3.1415926535 ;const std::string NAME = "C++" ;
constexpr 常量 C++11引入的编译时常量:
1 2 3 4 5 constexpr int MAX_SIZE = 100 ;constexpr double CALCULATE_PI () { return 3.1415926535 ; } constexpr double PI = CALCULATE_PI ();
consteval 常量(C++20+) C++20引入的强制编译时计算常量,确保函数在编译期执行:
1 2 3 4 5 6 7 8 9 10 consteval int factorial (int n) { return n <= 1 ? 1 : n * factorial (n - 1 ); } constexpr int result = factorial (5 );
constinit 常量(C++20+) C++20引入的常量初始化,确保变量在编译期初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 constinit int global_value = 42 ;void function () { static constinit int static_value = 100 ; } constinit int counter = 0 ;void increment () { counter++; }
枚举常量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 enum Color { RED, GREEN, BLUE }; enum Direction { UP = 1 , DOWN, LEFT, RIGHT }; enum class TrafficLight { RED, YELLOW, GREEN }; Color c = RED; TrafficLight tl = TrafficLight::RED;
变量 变量声明 1 2 3 4 5 int age; double salary = 5000.0 ; bool isStudent = true ; char grade = 'A' ; std::string name = "John" ;
变量初始化 拷贝初始化 1 2 3 int a = 10 ;double b = 3.14 ;std::string s = "Hello" ;
直接初始化 1 2 3 int a (10 ) ;double b (3.14 ) ;std::string s ("Hello" ) ;
列表初始化(C++11+) 1 2 3 4 int a{10 };double b{3.14 };std::string s{"Hello" }; int c{};
变量作用域 全局变量 在函数外部声明的变量,作用域为整个文件:
1 2 3 4 5 int globalVar = 100 ; void function () { std::cout << globalVar << std::endl; }
局部变量 在函数内部声明的变量,作用域为函数内部:
1 2 3 4 void function () { int localVar = 50 ; std::cout << localVar << std::endl; }
块作用域变量 在代码块内部声明的变量,作用域为代码块内部:
1 2 3 4 5 6 7 8 void function () { int x = 10 ; { int y = 20 ; std::cout << x << " " << y << std::endl; } }
函数参数 函数参数的作用域为函数内部:
1 2 3 void function (int param) { std::cout << param << std::endl; }
存储类别 auto 存储类别 局部变量的默认存储类别,在函数调用时创建,函数返回时销毁:
1 2 3 void function () { int x; }
static 存储类别 静态变量,在程序开始时创建,程序结束时销毁:
1 2 3 4 5 void function () { static int count = 0 ; count++; std::cout << count << std::endl; }
extern 存储类别 声明外部变量,用于在一个文件中使用另一个文件中定义的全局变量:
1 2 3 4 5 6 7 8 9 extern int globalVar; void function () { std::cout << globalVar << std::endl; } int globalVar = 100 ;
register 存储类别 提示编译器将变量存储在寄存器中(现代编译器通常会忽略此提示):
1 2 3 void function () { register int counter; }
运算符 算术运算符 运算符 描述 示例 结果 + 加法 5 + 3 8 - 减法 5 - 3 2 * 乘法 5 * 3 15 / 除法 5 / 3 1(整数除法) / 除法 5.0 / 3.0 1.666…(浮点除法) % 取模(余数) 5 % 3 2 ++ 自增(前缀) ++x x+1,返回x+1 ++ 自增(后缀) x++ x,返回x – 自减(前缀) –x x-1,返回x-1 – 自减(后缀) x– x,返回x
关系运算符 运算符 描述 示例 结果 == 等于 5 == 3 false != 不等于 5 != 3 true > 大于 5 > 3 true < 小于 5 < 3 false >= 大于等于 5 >= 3 true <= 小于等于 5 <= 3 false <=> 三路比较运算符(C++20+) 5 <=> 3 正数(表示大于)
三路比较运算符(C++20+) C++20引入的三路比较运算符(<=>),也称为宇宙飞船运算符,用于简化比较操作的实现。它返回一个比较类别类型的值,表示两个操作数之间的关系:
正数 :左操作数大于右操作数零 :左操作数等于右操作数负数 :左操作数小于右操作数使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <compare> int a = 5 , b = 3 ;auto result1 = a <=> b; std::string s1 = "apple" , s2 = "banana" ; auto result2 = s1 <=> s2; struct Point { int x, y; auto operator <=>(const Point&) const = default ; }; Point p1{1 , 2 }, p2{3 , 4 }; bool less = p1 < p2; bool equal = p1 == p2;
三路比较运算符的主要优点是:
简化代码 :只需要定义一个运算符,就可以自动生成所有其他比较运算符类型安全 :返回类型携带了比较的语义信息效率 :避免了重复的比较操作逻辑运算符 运算符 描述 示例 结果 && 逻辑与 true && false false || 逻辑或 true || false true ! 逻辑非 !true false
赋值运算符 运算符 描述 示例 等价于 = 简单赋值 x = 5 x = 5 += 加后赋值 x += 5 x = x + 5 -= 减后赋值 x -= 5 x = x - 5 *= 乘后赋值 x *= 5 x = x * 5 /= 除后赋值 x /= 5 x = x / 5 %= 取模后赋值 x %= 5 x = x % 5 <<= 左移后赋值 x <<= 2 x = x << 2 >>= 右移后赋值 x >>= 2 x = x >> 2 &= 按位与后赋值 x &= 5 x = x & 5 ^= 按位异或后赋值 x ^= 5 x = x ^ 5 |= 按位或后赋值 x |= 5 x = x | 5
位运算符 运算符 描述 示例 结果 & 按位与 5 & 3 (101 & 011) 1 (001) | 按位或 5 | 3 (101 | 011) 7 (111) ^ 按位异或 5 ^ 3 (101 ^ 011) 6 (110) ~ 按位取反 ~5 (~~101) -6 (在补码表示中) << 左移 5 << 2 (101 << 2) 20 (10100) >> 右移 5 >> 2 (101 >> 2) 1 (1)
其他运算符 运算符 描述 示例 结果 sizeof 计算大小 sizeof(int) 4 & 取地址 &x x的地址 * 解引用 *p p指向的值 ? : 条件运算符 x > y ? x : y x和y中的较大值 . 成员访问 obj.member obj的member成员 -> 指针成员访问 ptr->member ptr指向对象的member成员 [] 下标访问 arr[5] arr的第6个元素 () 函数调用 func() 调用func函数 new 动态内存分配 new int 分配int大小的内存 delete 动态内存释放 delete p 释放p指向的内存
类型转换 类型转换是将一种类型的值转换为另一种类型的过程。C++提供了多种类型转换方式,包括隐式转换和显式转换。
隐式类型转换 隐式类型转换是编译器自动进行的类型转换,不需要程序员显式指定。
隐式转换的规则 算术转换 :
当不同算术类型的操作数进行运算时,编译器会将较小的类型转换为较大的类型 转换顺序:bool → char/unsigned char/signed char → short/unsigned short → int/unsigned int → long/unsigned long → long long/unsigned long long → float → double → long double 整型提升 :
小于int的整型类型(如char、short)在运算时会被提升为int 无符号类型的提升规则可能导致意外结果 其他隐式转换 :
bool转换:bool与整型之间的转换(true→1, false→0)指针转换:任何指针都可以转换为void*,nullptr可以转换为任何指针类型 引用转换:派生类引用可以转换为基类引用 常量转换:非常量可以转换为常量 隐式转换的示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 int i = 100 ;double d = i; double pi = 3.14 ;int intPi = pi; char c1 = 'A' ; char c2 = 'B' ; int sum = c1 + c2; bool b = true ;i = b; i = 0 ; b = i; i = 100 ; b = i; int * p = &i;void * voidPtr = p; class Base {};class Derived : public Base {};Derived derived; Base& baseRef = derived;
隐式转换的安全性 拓宽转换 :从小类型转换为大类型,通常是安全的(如int→double)窄化转换 :从大类型转换为小类型,可能丢失数据(如double→int、int→char)符号转换 :有符号类型与无符号类型之间的转换,可能导致意外结果指针转换 :不同类型指针之间的隐式转换可能导致类型错误显式类型转换 显式类型转换是程序员通过特定语法显式指定的类型转换,也称为强制类型转换。
C风格的类型转换 1 2 3 4 5 double pi = 3.14 ;int intPi = (int )pi; void * ptr = πdouble * dPtr = (double *)ptr;
C风格类型转换的问题:
语法模糊,难以区分转换的目的 缺乏类型安全检查 难以在代码中查找 C++风格的类型转换 C++提供了四种类型转换运算符,每种用于特定的转换场景:
1. static_cast 用于相关类型之间的转换,编译时进行检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 double pi = 3.14 ;int intPi = static_cast <int >(pi);class Base {};class Derived : public Base {};Base* basePtr = new Derived; Derived* derivedPtr = static_cast <Derived*>(basePtr); Derived derived; Base& baseRef = derived; Derived& derivedRef = static_cast <Derived&>(baseRef); int i = 100 ;void * voidPtr = &i;int * intPtr = static_cast <int *>(voidPtr);
2. dynamic_cast 用于多态类型之间的转换,运行时进行检查,确保转换的安全性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Base { virtual void f () {} }; class Derived : public Base { void f () override {} };Base* basePtr1 = new Derived; Derived* derivedPtr1 = dynamic_cast <Derived*>(basePtr1); Base* basePtr2 = new Base; Derived* derivedPtr2 = dynamic_cast <Derived*>(basePtr2); try { Base base; Base& baseRef = base; Derived& derivedRef = dynamic_cast <Derived&>(baseRef); } catch (const std::bad_cast& e) { std::cout << "Bad cast: " << e.what () << std::endl; }
3. const_cast 用于添加或删除const、volatile限定符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const int i = 100 ;const int * constPtr = &i;int * nonConstPtr = const_cast <int *>(constPtr);int j = 200 ;int * ptr = &j;const int * constPtr2 = const_cast <const int *>(ptr);volatile int k = 300 ;volatile int * volPtr = &k;int * nonVolPtr = const_cast <int *>(volPtr);
4. reinterpret_cast 用于不相关类型之间的转换,最危险的类型转换:
1 2 3 4 5 6 7 8 9 10 11 12 int i = 100 ;int * intPtr = &i;double * doublePtr = reinterpret_cast <double *>(intPtr); uintptr_t ptrValue = reinterpret_cast <uintptr_t >(intPtr);int * newIntPtr = reinterpret_cast <int *>(ptrValue);typedef void (*FuncPtr) () ;int * funcPtr = reinterpret_cast <FuncPtr>(0x12345678 );
类型转换的最佳实践 优先使用C++风格的类型转换 :
更安全:dynamic_cast提供运行时检查 更清晰:明确转换的目的 更易维护:易于在代码中查找和分析 避免不必要的类型转换 :
设计时考虑类型匹配 使用模板和泛型减少类型转换 优先使用类型安全的容器和算法 注意转换的安全性 :
避免窄化转换,使用static_cast时要确保转换是安全的 对于多态类型,优先使用dynamic_cast确保安全 避免使用reinterpret_cast,除非绝对必要 使用const_cast时要确保不会修改const对象 使用显式转换明确意图 :
即使是安全的隐式转换,在关键位置使用显式转换可以提高代码可读性 对于可能有歧义的转换,使用显式转换明确意图 类型转换与类型安全 类型安全的重要性 :
避免运行时错误和未定义行为 提高代码的可读性和可维护性 减少调试时间和错误修复成本 类型安全的实践 :
使用强类型枚举(enum class)替代传统枚举 避免使用void*和原始指针 使用智能指针管理动态内存 优先使用标准库容器和算法 利用C++的类型系统进行编译时检查 现代C++中的类型转换 :
使用std::variant和std::any进行类型安全的多态 使用概念(Concepts)约束模板参数类型 使用std::optional表示可能不存在的值 使用std::expected表示可能失败的操作结果 类型推导的高级用法 auto与引用和const 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int i = 100 ;auto a1 = i; auto & a2 = i; const auto & a3 = i; auto * a4 = &i; const auto * a5 = &i; auto a6 = i + 1.0 ;
decltype与表达式值类别 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int i = 100 ;decltype (i) j = 200 ; decltype ((i)) k = i; decltype (i + 1 ) l = 300 ; std::string s = "Hello" ; decltype (s.size ()) m = 5 ;
类型推导与模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 template <typename T>void func (T param) { } template <typename T, typename U>auto add (T t, U u) { return t + u; } template <typename T, typename U>auto multiply (T t, U u) -> decltype (t * u) { return t * u; } template <typename T>class MyClass {public : MyClass (T value) : value (value) {} T getValue () const { return value; } private : T value; }; template <typename T>MyClass (T) -> MyClass<T> ;auto mc = MyClass (42 );
类型别名 typedef 1 2 3 4 5 6 7 typedef unsigned long ulong;typedef std::vector<int > IntVector;typedef void (*FunctionPtr) (int ) ;ulong x = 100 ; IntVector vec; FunctionPtr func;
using 别名声明(C++11+) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 using ulong = unsigned long ;using IntVector = std::vector<int >;using FunctionPtr = void (*)(int );ulong x = 100 ; IntVector vec; FunctionPtr func; template <typename T>using Vec = std::vector<T>;Vec<int > intVec; Vec<double > doubleVec;
类型信息 typeid 运算符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <typeinfo> int i = 100 ;double d = 3.14 ;std::cout << typeid (i).name () << std::endl; std::cout << typeid (d).name () << std::endl; class Base { virtual void f () {} };class Derived : public Base {};Base* basePtr = new Derived; std::cout << typeid (*basePtr).name () << std::endl;
类型特性(C++11+) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <type_traits> std::cout << std::is_integral<int >::value << std::endl; std::cout << std::is_floating_point<int >::value << std::endl; std::cout << std::is_convertible<int , double >::value << std::endl; using SignedInt = std::make_signed<unsigned int >::type;using UnsignedInt = std::make_unsigned<int >::type;using AddPointer = std::add_pointer<int >::type;using RemovePointer = std::remove_pointer<int *>::type;
数值极限 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <limits> std::cout << "int min: " << std::numeric_limits<int >::min () << std::endl; std::cout << "int max: " << std::numeric_limits<int >::max () << std::endl; std::cout << "double min: " << std::numeric_limits<double >::min () << std::endl; std::cout << "double max: " << std::numeric_limits<double >::max () << std::endl; std::cout << "double epsilon: " << std::numeric_limits<double >::epsilon () << std::endl; std::cout << "int is signed: " << std::numeric_limits<int >::is_signed << std::endl; std::cout << "int digits: " << std::numeric_limits<int >::digits << std::endl;
小结 本章介绍了C++中的数据类型、常量、变量、运算符和类型转换等核心概念。通过本章的学习,你应该能够:
掌握C++的基本数据类型,包括整型、浮点型、字符型、布尔型和空类型 理解各种常量的定义和使用方法,包括字面常量、const常量、constexpr常量和枚举常量 掌握变量的声明、初始化和作用域规则 熟悉C++的各种运算符及其优先级和结合性 理解类型转换的原理和方法,包括隐式类型转换和显式类型转换 掌握C++11引入的类型推导和类型别名特性 了解类型信息和数值极限的获取方法 数据是程序的基础,掌握好C++的数据处理能力对于编写高效、可靠的程序至关重要。在后续章节中,我们将学习如何使用这些数据类型和运算符来构建更复杂的程序结构,如控制语句、函数、数组、指针、类等。
C++提供了丰富的数据类型和运算符,这使得它既可以处理底层的系统编程任务,也可以处理高层的应用开发任务。通过合理选择和使用数据类型,你可以编写出更高效、更安全、更易维护的代码。