C++字符串、向量和数组

字符串、向量和数组


1. using声明

  • using声明,注意与using别名声明相区分

    1
    2
    using namespace::name; //声明之后可以直接使用命名空间namespace中的name
    using std::cin;
  • 每个名字都要独立的进行using声明

  • 头文件中不应包含using声明。以防不经意间的包含产生意想不到的错误

2. 标准库类型string

  • string库表示可变长字符序列,使用时首先包含string头文件。此库定义在std命名空间中。

  • 初始化的方式

  • 直接初始化和拷贝初始化

    • 拷贝初始化:使用等号初始化

      1
      string a = "aa";
    • 直接初始化:不使用等号初始化

      1
      2
      string a("aa");
      string b(10,'a');
  • 相关操作

    • 读写操作。读取时,开头的空白会被忽略,从第一个真正的字符开始,到下一处空白结束

      1
      2
      3
      string s;
      cin >> s;
      cout << s << endl;

      返回左侧的运算对象。特别是读取时,如果读取到非法字符(结束符等),则返回流无效,可以作为读取结束的判断标志实现读取未知数量的串。

    • 读取一整行。遇到换行符就终止,换行符被读到流中,但是不会存入string对象。读取后返回流,同样可作为判断条件。

      1
      2
      string s;
      getline(cin,s); //从cin中读取数据给到s
    • 判断长度操作

      1
      2
      3
      4
      5
      string s;
      s.empty(); //判断是否为空,返回布尔值
      s.size(); //返回字符个数,即长度。
      //size()返回值是string::size_type类型,是无符号整数,注意和有符号数混用时的情况
      //如s.size() < n判断条件当n为负值时结果大概率始终为true
    • 比较操作。逐字符进行比较,且大小写敏感,依照字典顺序。

      • 一个是另一个的前缀,则短的小
      • 其余则是二者第一个相异字符比较的结果
    • 赋值和相加操作

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      string s1,s2;
      s1 = s1; //可直接赋值
      string s3 = s1 + s2; //相加结果是串的拼接
      string s3 += s1;

      //string与字面值相加。字符和串字面值都可以转换为string对象
      s3 = s1 + "," + s2 + '\n'; //必须保证每个+两侧至少有一个是string对象
      s4 = "a" + 'b'; //错误
      //注意
      s5 = s1 + "," + 'a'; //正确
      s6 = "," + 'a' + s1; //错误
  • 处理字符,使用cctype头文件提供的标准库进行处理,同样是在std空间中

  • 遍历string对象

    • 使用范围for语句

      1
      2
      3
      4
      5
      for(auto c : s)		//c的类型为char,每次执行会将s中的下一个字符拷贝给c
      cout << c << endl;
      //如果想要修改字符,则要使用引用
      for(auto &c : s)
      c = toupper(c);
    • 使用下标运算符[],接收参数类型为string::size_type,从0开始,返回该位置上字符的引用。同样注意带符号类型的转换问题。并且使用时确保对象非空,否则会产生无法预知的结果。

      1
      2
      if(index!=s.size() && !isspace(s[index]))	//注意检测条件的巧妙之处。
      //只有索引合法时才会真正的去访问

3. 数组

数组用于存放类型相同的对象,且数组大小确定不变。数组和引用、指针类似,是基于其他类型定义的类型,属于复合类型。数组本身也是对象,可以有指向数组的指针和引用。

3.1 定义和初始化数组
数组定义
1
2
3
4
5
6
7
type a[n];
/*
* type 为数组内对象的类型。定义时必须指定,不能是auto;必须是对象,不存在引用的数组
* a 为数组名
* n 为数组每对象个数,属于数组类型的一部分,必须是常量表达式
*/
int a[10];
默认初始化

默认情况下进行默认初始化,满足默认初始化的规则

显式初始化

即为列表初始化,int a[n] = {1,2,3,...},部分情况会忽略列表维度,有如下几种情况

  • 列表定义没有指出列表的维度,则编译器根据初始值的数量计算列表维度

  • 列表定义时指出了列表的维度,则初始值总数量不能大于指定的维度

    • 初始值数量等于维度,按顺序初始化
    • 初始值数量小于维度,使用初始值初始化靠前的元素,剩余元素执行默认初始化
    1
    2
    3
    4
    int a[3] = {1,2,3};	// int a[3] = {1,2,3}
    int a[] = {1,2,3}; // int a[3] = {1,2,3}
    int a[4] = {1,2,3}; // int a[4] = {1,2,3,0}或者int a[4] = {1,2,3,不确定值}
    int a[2] = {1,2,3}; // 错误
字符数组的特殊初始化方式

字符数组除了上述初始化方式之外,还可以使用字符串字面值进行初始化,并且字符串字面值结尾的空字符也会被初始化到字符数组之中

1
2
char a[] = "C++";	// char a[4] = {'C','+','+','\0'}
char a[3] = "C++"; // 错误,大小不匹配
不允许直接拷贝和赋值
1
2
3
4
5
int a[] = {0,1,2};
int c[3];

int b[] = a; // 错误
c = a; // 错误
复杂的数组定义
1
2
3
4
5
6
7
8
int	*a[10];			// 保存指针对象的数组
int (*a)[10] = &b; // 指向数组的指针,且指向的数组含有10个整型,注意不同于指向数组首元素的指针
int &a[10]; // 错误,不存在引用的数组,因为引用不是对象
int (&a)[10] = b; // 指向数组的引用,且指向的数组含有10个整型

int *(&a)[10] = b; // 指向数组的引用,且指向的数组含有10个整型指针
int *(*a)[10] = b; // 指向数组的指针,且指向的数组含有10个整型指针
int **(*a)[10] = b; // 类似,*可以无限的叠加,表示所指向的数组中保存的类型
3.2 访问数组元素

数组元素可以使用范围for语句或者下标运算符来访问。使用数组下标时,通常将其定义为size_t类型,size_t是一种机器相关的无符号类型,定义于cstddef头文件中

1
2
3
// 范围for语句,a为数组
for(auto i : a)
cout << i << endl;
1
2
3
// 下标运算符
size_t n = 1;
cout << a[n] << endl;
3.3 指针和数组
数组名字和指针

数组具有一个特点,在用到数组名字的地方,编译器有时会自动将其替换为一个指向数组首元素的指针

1
2
3
string test = {"one","two","three"};
string *p = &num[0]; // 可以使用通用方式&来获取特定元素的指针
string *p = num; // 也可以直接使用数组名获取指针

使用数组作为一个auto变量的初始值时,推断得出的类型是指针而不是数组。引用类型除外

1
2
3
4
5
6
7
int a[] = {1,2,3,4,5,6,7,8,9,0};
auto b = a; // 类似于auto b = &a[0]
cout << b << endl;
// 0x61fdf0,b是一个指针

//将auto设置为引用类型则不会自动进行转换,此时b表示 int [10]
auto &b = a;

使用decltype关键字时不会进行上述转换

1
2
int a[] = {1,2,3,4};
decltype(a) b = {2,3,4,5}; // decltype(a)返回的类型为 4个整数构成的数组
内置数组的迭代器操作

指向数组元素的指针具有容器类型迭代器的所有功能,并且支持所有迭代器操作和运算。因为迭代器本身就是在容器类型中对指针操作的模仿。

直接获取首元素指针和尾后指针

1
2
3
4
5
6
7
int a[] = {1,2,3,4,5,6,7,8,9,0};
// 首元素指针
int *begin = a;
int *begin = &a[0];
// 尾后指针
int *end = &a[10];
int *last = a+10;

类似容器迭代器,内置数组具有两个函数beginend,将数组作为其参数即可得到首元素和尾后元素指针

1
2
3
int a[] = {1,2,3,4,5,6};
int *beg = begin(a);
int *last = end(a);

标准库中的beginend只能用于数组类型,接收数组的引用,不能用于指针类型,包括指向数组的指针

1
2
3
4
5
6
7
int a[] = {1,2,3,4,5,6};
int *b = a;
int (*p)[6] = &a;
int *beg = begin(b); // 错误
int *last = end(b); // 错误
int *beg = begin(p); // 错误
int *beg = begin(*p); // 正确

指向数组元素的指针支持所有迭代器运算,特别的对于-运算,得到的结果为ptrdoff_t类型,是一种机器相关的带符号类型,定义于cstddef头文件中。

3.4 多维数组

C++语言中没有多维数组,此处所说多维数组实质上为数组的数组

多维数组的定义
1
2
int a[3][4];		//大小为3的数组,每个数组是大小为4的数组
int b[3][4][5]; //大小为3的数组,b的元素本身又是大小为4的数组,这些数组的元素又是大小为5的数组
多维数组的初始化
1
2
3
4
5
6
7
8
9
10
11
// 1. 默认初始化,根据默认初始化的规则进行初始化
int a[3][4]; //默认初始化为0

// 2. 显式初始化,即列表初始化。两种视图,可以将其看做多维数组,也可看做特殊的一维数组
int a[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}}; //多维数组初始化
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; //一维数组初始化,等价于上一句

int a[3][4] = {{0},{4},{5}}; //多维数组,初始化每一行的第一个元素,其余元素默认初始化
int a[3][4] = {0,4,5}; //一维数组,按顺序初始化,其余元素默认初始化

int a[3][4] = {0,{4},{5}}; //错误,两种视图不能混用
多维数组的下标引用

多维数组的每个维度对应一个下标运算符。如果下标运算符数量等同于维度,则访问结果为数组元素;如果下标运算符数量小于维度,则访问结果为内层数组

1
2
3
4
int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};

int a[0][0]; // 1,具体元素
int a[0]; // 0x61fdf0,一个含有4个整型元素的数组,不是指针
多维数组的遍历
  • 传统for语句

    1
    2
    3
    4
    5
    6
    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    for(int i = 0;i<3;++i){
    for(int j = 0;j<4;++j){
    cout << a[i][j];
    }
    }
  • 范围for语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    for(auto &row : a){
    for(auto &col : row){
    cout << col;
    }
    }

    // 注意此处的row必须为引用类型,以防止数组自动转换为指针
    for(auto row : a){ //此处的auto为int*指针类型,下一个范围for语句会报错
    ....
    }
多维数组与指针

多维数组的名字同样有时会被转换为指向第一个子数组的指针。多维数组内部子数组或子多维数组同样符合数组和多维数组与指针的转换关系

1
2
3
4
5
6
7
int a[3][4];
int (*p)[4] = a; //p指向a的第一个子数组
auto p = a; //p指向a的第一个子数组,auto为指针类型,int(*)[4]
auto q = *p; //可对p进行解引用,得到相应的数组对象。auto为包含4个整型元素的数组

auto &p = a; //p指向a的第一个子数组,auto为包含4个整型元素的数组
decltype(a)b; //auto为包含4个整型元素的数组

多维数组的指针操作可以视为不同层次的内置数组的指针操作,满足迭代器操作和运算

1
2
3
4
5
6
int a[3][4];
for(auto p = a ; p != a + 3 ; ++p){
for(auto q = *p ; q != *p + 4 ; ++q){
cout << *q;
}
}

多维数组同样具有beingend函数,二者返回类型为指针

1
2
3
4
5
6
int a[3][4];
for(auto p = begin(a) ; p != end(a) ; ++p){
for(auto q = begin(*p) ; q != end(*p) ; ++q){
cout << *q;
}
}
多维数组指针的类型别名

可以使用类型别名简化指向多维数组的指针

1
2
3
4
5
//新旧两种声明类型别名的方式等价
using int_array = int[4]; //将in_array声明为包含4个整型元素的数组对象的别名
typedef int int_array[4];

int_array a = {1,2,3,4}; //等价于 int a[4] = {1,2,3,4};