施工提示

本博文尚在施工中,内容并未完成且可能变更。

数据类型进阶

本节继续 常用数据类型 一节的内容,介绍:

  • const 常量限定符

  • 类型别名:typedef 等用法

  • 类型操作:auto 与 decltype

以及:

  • 标准库支持的变量类型:例如字符串(std::string)。

  • 标准库支持的模板类型:例如向量(std::vector)。

关于指针与引用,请参考 引用与指针 一节的内容。

常量:const

关于引用与常量、指针与常量,分别参考 常量引用指针与 const 章节。

当我们想定义一个不能被改变的值时,使用 const 限定符在变量定义时进行修饰。

  • 常量在定义时 必须显式初始化

  • 常量一经创建,就不能再改变其值。

  • 常量可以用同基础类型的变量初始化;反之,变量也可以用同类型的常量初始化或赋值。

const double e_base = 2.71828;

long double pi_value = 3.14159265;
const long double pi = pi_value;

const int capacity;     // 错误:未初始化
e_base = 2.718;         // 错误:不能更改常量的值

常量一般只在当前文件有效

值得注意的是,const 常量只在当前文件内有效。要在多个文件之间共享某个常量,需要在定义与声明中均使用 extern 修饰:

// 在 lib.h 头文件中声明常量,令包含该头文件的文件共享该常量
extern const int maxflow;
// 在 lib.cpp 文件中定义常量
extern const int maxflow = 1800;

常量表达式:constexpr

常量表达式 constexpr C++ 11 是指值不变且在编译时就能得到结果的表达式,或者用这种表达式初始化的常量对象。

  • 常量表达式类型必须是字面值类型,比如浮点数、任意整型。指针也属于字面值类型,但是定义为常量表达式时,初始值必须为 nullptr 或 0,或者固定地址对象的地址。

constexpr double half_pi = 3.14159 / 2;

类型别名、decltype 与 auto

typedef

使用类型别名可以简化输入、让人迅速理解使用类型的目的。C++ 使用 typedef 或者 using = 定义类型别名:

typedef int ages;       // 将 ages 定义为 int 的别名
ages person_age = 21;

using grades = double;  // 将 grades 定义为 double 的别名
grades midterm = 95, final_exam = 90;

需要特别注意对于复杂类型(例如指针)的类型别名使用。请注意下例中 p1p2 的不同。

typedef int *intp;      // 定义了 intp 类型,即指向 int 的指针
const intp p1 = 0;      // 指向 int 的常量指针

const int *p2 = 0;      // 指向 const int 的指针

在上例中,p1 定义中的 const 修饰 intp 类型,表示 p1 是一个 intp 类型的常量,即指向 int 的常量指针。p2 定义中的 const 则只修饰 int,而 *p2 作为标识符。这表示 p2 是一个指针,这个指针指向 const int 类型。

decltype

decltype 推断类型(而不计算结果),并用推断类型定义对象:

  • 在对变量进行类型推断时,decltype 保留:(1)引用,以及;(2)顶层常量属性。

  • 在对表达式(非变量)进行类型推断时,decltype 返回表达式结果的类型

const int m = 12, &r = m;
decltype(n) n1 = 0;     // 推断为 const int
decltype(r) r1 = m;     // 推断为 const int& 引用

int n = 37, *p = &n;
decltype(*p) r2 = n;    // 解引用后,推断为 int& 引用

auto 类型

auto C++ 11 类型允许编译器推断类型,其在定义时必须初始化:

double x = 1.23;
int n = 1;
auto y = n + x;  // y 推断为 double 类型
  • 将引用用于 auto 对象的初始化,auto 将被推断为被引对象的类型,而不是引用类型。

  • auto 一般忽略顶层常量;如果需要顶层常量,显式地使用 const auto

    const double pi = 3.14;
    auto a = pi;           // a 推断为 double 类型
    const auto b = pi;     // b 推断为 const double 类型
    const auto &r = pi;    // 推断引用 r 为常量引用
    

字符串

在 C++ 中,有三种字符串相关的类型:

  • 字符串字面值,如 "Hello"

  • C 风格字符串(常量字符指针),形如 const char *cp = ...

  • 标准库字符串,如 std::string s;

C 风格字符串

std::string

C++ 标准库 string 定义了 std::string 类型。我们更多地使用该类型,而不是 C 风格的字符串类型。

初始化

std::string 可以用下例中的方法初始化,尤其注意:

  • s(n, c) 重复字符 c 数字 n 次

  • s(ss, i) 复制字符串 ss 下标 i 及之后的内容

  • s(ss, i, n) 复制字符串 ss 从下标 i 开始的 n 个字符内容

下面是一些字符串初始化的例子:

#include <string>
using std::string;

string s1;
string s2 = "foo bar";  // 不包括最后的空字符

// 以下都初始化为 "aaa"
string s3(3, 'a');
string s4 = string(3, 'a');
string s5 = s3;

常用操作

如果有 std::string 类型的变量 s,那么它支持的常用操作有:

  • 拼接:s + ss 将 s 与另一个字符串 ss 拼接

  • 长度:s.size() 返回字串长度,其返回类型为无符号类型 std::string::size_type;我们用 size_t 来作为迭代数组时的下标变量类型,这通常对于字符串也适用。

  • 判断是否为空:s.empty()

  • 子字符串:s.substr(i, n) 返回一个从下标 i 开始长为 n 的子字串

  • 取字符:用 s[i] 返回对下标 i 字符的引用。

    • 注意 i 必须小于字符长,即 \(0\leq i < s.size()\)

    • 由于是引用,允许通过 s[i] 对原字符串 s 进行更改。

    下面是一个按下标 i 逐个更改字符串中字符的例子(其中 cctype 的字符操作参考 cctype:字符操作):

     1#include <iostream>
     2#include <string>
     3#include <cctype>
     4
     5int main() {
     6    std::string str = "Hello World!";
     7
     8    for (size_t i = 0; i < str.size(); ++i) {
     9        str[i] = std::toupper(str[i]);
    10    }
    11    std::cout << "All uppercase: " << str << std::endl;
    12    
    13    // 也可以使用 range for 语法
    14    for (auto &c : str) { c = std::tolower(c); }
    15    std::cout << "All lowercase: " << str << std::endl;
    16    return 0;
    17}
    18/*
    19  输出:
    20  All uppercase: HELLO WORLD!
    21  All lowercase: hello world!
    22*/
    
  • 打印与读入

    • 打印到 ostream 对象 os:os << s

    • 从 istream 对象 is 读入:is >> s,注意读入时会跳过开头的空白符

    • 从 is 中读取一行到 s:getline(is, s),它会在读到换行符时停止,向 s 中存储不含换行符的该行内容,然后返回 is。下例将逐行读入的字符按原样(因为添加回了换行符)输出:

      string line;
      while (getline(cin, line)) {
          cout << line << '\n';
      }
      

容器操作

向量(std::vector)