C++字符串、容器和数组

1. 命名空间的using声明

目前为止用到的库函数基本都存在于命名空间std中,例如:

std::cin;
std::cout;
std::endl;

域操作符的含义是:编译器应从操作符左侧名字所示的作用域中寻找右侧那个名字。使用using声明之后,程序中就无需专门的前缀也能使用所需的名字了。

using std::cin;
using std::cout;
using namespace std;

1.头文件不应包含using声明
位于头文件的代码一般来说不应该使用 using 声明,因为头文件的内容会拷贝到所有引用的文件中去,可能会引发始料未及的冲突。

2. 标准库类型string

标准库类型string表示可变长的字符序列,使用 string 类型必须包含string头文件。作为标准库的一部分,string 定义在命名空间std中。

2.1 初始化string对象

如何初始化类的对象是由类本身决定的,string 的初始化方法如下:

string s1;             // 默认初始化,空字符串
string s2 = s1;        // s2是s1的副本
string s3 = "hiya";    // s3是该字符串字面值的副本;
string s4(10,'c');     // s4的内容是cccccccccc;
string s5("hiya");     // s5是该字符串字面值的副本

注意: 如果使用等号初始化一个变量,实际执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。如果不使用等号,执行的是直接初始化。

2.2 string对象上的操作

一个类除了要规定初始化其对象的方式外,还会定义对象所能执行的操作。

操作含义
os << s将 s 写到输出流 os 中,返回 os
is >> s从 is 中读取字符串赋值给 s,字符串以空白分隔,返回 is
getline(is, s)从 is 中读取一行赋给 s,返回 is
s.empty( )s 为空返回 true,否则返回 false
s.size( )返回 s 中字符的个数
s[n]返回 s 中第 n 个字符的引用,n 从 0 开始
s1 + s2返回 s1 和 s2 连接后的结果
s1 = s2用 s2 的副本代替 s1 中原来的字符
s1 == s2如果 s1 和 s2 中所含的字符完全一样则相等,该判断对字母大小写敏感
s1 != s2如果 s1 和 s2 中所含的字符存在不同则不等,该判断对字母大小写敏感
<, <=, >, >=利用字符在字典中的顺序进行比较,对字母的大小写敏感

1.读写string对象

string s;
cin >> s;
cout << s << endl;

注意: 在执行读写操作时,string 对象会自动忽略开头的空白并从第一个真正的字符开始读起,直到遇见下一处空白为止。
2.读取未知数量的string对象

string word;
while(cin >> word)
        cout << word << endl;

3.使用getline读取一整行
有时我们希望最终得到的字符串中保留输入的空白符,这时应该使用getline函数。getline 函数的参数是一个输入流和一个 string 对象,函数从给定的输入流中读取内容,直到遇到换行符为止,然后把所读的内容存到那个 string 对象中。

string line;
while(getline(cin, line))
       cout << line << endl;

4.string的成员函数
empty函数根据 string 对象是否为空返回一个对应的布尔值,只要使用点操作符指明是哪个对象执行了就可以了。
size函数返回 string 对象的长度,返回值的类型是string::size_type

string str = "Hello, world!";
// 使用 empty 函数检查字符串是否为空
if (str.empty()) {
      cout << "String is empty." << endl;
} else {
      cout << "String is not empty." << endl;
}
string::size_type len = str.size();
cout << "Length of the string: " << len << endl;

5.比较string对象
string 类定义了几种用于比较字符串的运算符:
1.相等运算符和不等运算符分别检验两个 string 对象相等或不等,string 对象相等意味着它们的长度相等且所包含字符也全相等。
2.关系运算符 <、<=、>、>= 分别检验一个 string 对象是否小于、小于等于、大于、大于等于另外一个 string 对象。
3.如果两个 string 对象在某些对应的位置上不一样,则 string 对象比较的结果其实是 string 对象中第一对相异字符比较的结果。

std::string str1 = "apple";
std::string str2 = "banana";
if (str1 == str2) {
      std::cout << "str1 and str2 are equal" << std::endl;
} else if (str1 < str2) {
      std::cout << "str1 is less than str2" << std::endl;
} else {
      std::cout << "str1 is greater than str2" << std::endl;
}

6.两个string对象相加
对 string 对象使用加法的结果是一个新的 string 对象,它所包含的字符由两部分组成,前半部分是加号左侧 string 对象所含内容,后半部分是加号右侧 string 对象所含内容。

std::string str1 = "Hello, ";
std::string str2 = "World!";
std::string result = str1 + str2; 
std::cout << result << std::endl;

7.字面值和string对象相加
标准库允许把字符字面值和字符串字面值转化成 string 对象,所以在需要 string 对象的地方就可以使用这两种字面值来替代。

string str1 = "hello,";
string result = str1 + "nihao";
cout << result << std::endl;

注意: 当把 string 对象和字符串字面值或字符字面值混在一条语句中时,必须确保加号两侧的运算对象至少有一个是 string。

2.3 处理string对象中的字符

下面是一些 string 的内置成员函数:

函数含义函数含义
isalnum(c )当 c 是字母或者数字时为真isprint(c )当 c 是可打印字符时为真
isalpha(c )当 c 是字母时为真isspace(c )当 c 是空白时为真
iscntrl(c )当 c 是控制字符时为真isupper(c )当 c 是大写字母时为真
isdigit(c )当 c 是数字时为真isxdigit(c )当 c 是十六进制数字时为真
isgraph(c )当 c 不是空格但可打印时为真tolower(c )当 c 是大写字母时输出小写字母
islower(c )当 c 是小写字母时为真toupper(c )当 c 是小写字母时输出大写字母
ispunct(c )当 c 是标点符号时为真

1.处理每个字符?使用基于范围的for语句
如果想对 string 对象中的每个字符做点什么操作,最好的办法是使用范围for语句。这种语句遍历给定序列中的每个元素并对序列中的每个值执行操作。

string str("some string");
for(auto c : str)
     cout << c << endl;

2.使用范围for语句改变字符串中的字符
如果想要改变 string 对象中字符的值,必须把循环变量定义成引用类型。

string s("hello world!!!");
for(auto &c : s)
      c = toupper(c);
cout << s << endl;

3.处理一部分字符
有时需要访问的只是其中一个字符或者访问多个字符但遇到某个条件就要停下来,这时建议使用下标或者迭代器。

string str2 = "some strings";
if (!str2.empty())
	str2[0] = toupper(str2[0]);
cout << str2 << endl;     // Some strings
for (auto index = 0; index != str2.size() && !isspace(str2[index]); ++index)
	 str2[index] = toupper(str2[index]);
cout << str2 << endl;    // SOME strings

3. 标准库类型vector

标准库类型vector表示对象的集合,集合中所有对象的类型相同。集合中的每个对象都有一个与之对应的索引,索引用于访问集合中的对象。使用 vector 时需要头文件vector

3.1 定义和初始化vector对象

初始化 vector 对象常用方法如下所示:

vector<T> v1;           // 空集合,T表示数据类型,如int、double或自己定义的数据类型
vector<T> v2(v1);
vector<T> v2 = v1;
vector<T> v3(n, val);  // n个val
vector<T> V4(n);       // n个重复执行了默认初始化的对象
vector<T> v5{a, b, c....};
vector<T> v5 = {a, b, c....};

1.列表初始值还是元素数量

vector<int>v1(10);    // v1有10个元素,每个值都是0
vector<int>v2{10};    // v2有1个元素,值是10
vector<int>v3(10, 1); // v3有10个元素,每个值都是1
vector<int>v4{10, 1}; // v1有2个元素,值是10, 1

3.2 向vector对象中添加元素

可以用 vector 的成员函数push_back向容器中添加元素,push_back 负责把一个值压到 vector 的尾端

vector<int> v2;
for(int i = 0; i != 100; ++i){
    v2.push_back(i);
}

注意: 不能使用范围for循环向 vector 对象添加元素,范围 for 循环会使用容器的迭代器进行遍历,当往容器中添加元素时容器可能会进行内存重新分配或者元素的移动,这会导致之前获取的迭代器失效,因此应该使用传统的for语句来添加元素。

3.3 其它的vector操作

操作含义操作含义
v.empty( )如果 v 为空返回真,否则假v1 = {a, b}用列表中的元素拷贝替换v1中的元素
v.size( )返回 v 中元素的个数v1 == v2判断是否相等
v.push_back( )向 v 的尾端添加一个值v1 != v2判断是否不等
v [n]返回 v 中第 n 个位置的元素的引用,从0开始计数<, <=, >, >=以字典顺序进行比较
v1 = v2v2 拷贝给 v1

1.关系运算符按照字典顺序进行比较
如果两个 vector 容器在相同位置的元素都一样,则元素较少的 vector 对象小于元素较多的 vector 对象,若元素的值有区别,则 vector 对象的大小关系由第一对相异的元素的大小关系来决定。

 std::vector<int> vec1 = {1, 2, 3};
 std::vector<int> vec2 = {1, 2, 4};
 // 比较大小
 if (vec1 < vec2) {
       std::cout << "vec1 小于 vec2" << std::endl;
 } else if (vec1 > vec2) {
       std::cout << "vec1 大于 vec2" << std::endl;
 } else {
       std::cout << "vec1 等于 vec2" << std::endl;
 }

2.计算vector内对象的索引
使用下标运算符能获取到指定的元素。和 string 一样,vector 对象的下标也是从 0 开始计起,其下标的类型是相应的size_type类型

std::vector<int> numbers = {10, 20, 30, 40, 50};
// 获取指定索引处的元素
std::cout << "索引为 2 的元素是: " << numbers[2] << std::endl;
// 使用 vector 的 size_type 进行迭代
for (std::vector<int>::size_type i = 0; i < numbers.size(); ++i) {
        std::cout << "索引为 " << i << " 的元素是: " << numbers[i] << std::endl;
}

3.不能用下标形式添加元素
vector 对象以及 string 对象的下标运算符可用于访问已存在的元素,而不能用于添加元素。这是因为当使用下标访问元素时,确切位置的内存可能不存在或者已经被其它数据占用。

4. 迭代器介绍

我们可以使用下标运算符来访问 string 对象的字符或 vector 对象的元素,还有一种更通用的机制也可以实现,这就是迭代器。迭代器的对象是容器中的元素或者 string 中的字符。

4.1 使用迭代器

和指针不一样的是获取迭代器不是使用取地址符,有迭代器的类型拥有返回迭代器的成员函数,这些类型都拥有名为 beginend 的成员。begin 成员负责返回指向容器中第一个元素的迭代器,end 成员负责返回指向容器尾元素的下一位置的迭代器。
1.迭代器运算符

运算符含义运算符含义
* iter返回迭代器 iter 所指元素的引用- - iter令 iter 指向容器中的上一个元素
iter. mem解引用 iter 并获取该元素名为 mem 的成员iter1 == iter2判断2个迭代器是否相等
++iter令 iter 指向容器中的下一个元素iter1 != iter2判断2个迭代器是否不等

2.迭代器的类型
实际上,迭代器的标准库类型使用iteratorconst_iterator来表示迭代器的类型。

vector<int>::iterator it;
string::iterator it2;
vector<int>::const_iterator it3;  // 只能读,不能写;
string::const_iterator it4;       // 只能读,不能写;

注意: begin( )和end( ) 返回的具体类型由对象是否是常量决定,如果对象是常量 begin 和 end 返回 const_iterator;如果对象不是常量返回类型是 iterator。C++新标准引入两个新函数,分别是 cbegin 和 cend,cbegin 和 cend 的返回值都是 const_iterator。
3.结合解引用和成员访问操作
解引用迭代器可获得迭代器所指的对象,如果该类型的对象恰好是类,就有希望进一步访问它的成员函数。

#include <iostream>
#include <vector>
class MyClass {
public:
    void display() {
        std::cout << "Inside MyClass display function!" << std::endl;
    }
};
int main()
{
   std::vector<MyClass> objects(3); // 创建包含3个 MyClass 对象的容器
   // 获取迭代器,并使用它来访问每个对象的成员函数
   for (auto it = objects.begin(); it != objects.end(); ++it) {
          it->display();   // 访问 MyClass 对象的 display 函数
   }
   return 0;
}

4.2 迭代器运算

可以令迭代器和一个整数值相加或相减,其返回值是向前或向后移动了若干个位置的迭代器。

// 计算得到v1中间元素最近的一个迭代器
auto mid = v1.begin() + v1.size()/2;

只要两个迭代器指向的是同一个容器中的元素或者尾元素的下一位置,就能将其相减,所得结果是两个迭代器之间的距离。所谓距离就是指右侧的迭代器向前移动多少位置就能追上左侧的迭代器,其类型是名为difference_type的有符号整型。

5. 数组

数组是一种类似于标准库类型 vector 的数据结构,数组也是存放类型相同的对象的容器。这些对象本身没有名字,需要通过其所在位置访问。与 vector 不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。对某些特殊的应用来说使用数组程序的运行性能会较好,但是相应的也损失了一些灵活性。

5.1 定义和初始化内置数组

数组是一种复合类型,语法结构如下:

unsigned cnt = 42;              // 不是常量表达式
constexpr unsigned sz = 42;     // 常量表达式
int arr[10];                    // 10个元素,数组的元素被默认初始化
int *parr[sz];                  // 数组维度必须为常量

注意: 定义数组的时候必须指定数组的类型,不允许使用 auto 关键字由初始值的列表推断类型。另外和 vector 一样,数组的元素应为某个数据类型,因此不存在引用的数组。
1.显式初始化数组元素
如果在声明时没有指明维度,编译器会根据初始值的数量计算并推断出来;如果指明了维度,则用提供的初始值初始化靠前的元素,剩下的元素被初始化为默认值。初始值的数量不应该超出维度的大小,否则会发生错误。

const unsigned sz = 3;
int ial[sz] = {0,1,2};   // 含有3个元素的数组
int a2[] = {0,1,2};
int a3[5] = {0,1,2};     // 等价于a3[5] = {0,1,2,0,0};

2.字符数组的特殊性
字符数组有一种额外的初始化方式,使用字符串字面值对数组初始化。注意字符串的结尾处还有一个空字符。

const char a4[7] = "Daniel";

3.不允许拷贝和赋值

int a[] = {0,1,2};
int a2[] = a;       // 错误
a2 = a;             // 错误

4.理解复杂的数组声明
和 vector 一样,数组能存放大多数类型的对象。定义存放指针的数组比较简单和直接,但是定义数组的指针或数组的引用就比较复杂。

int * ptrs[10];             // ptrs是含有10个整型指针的数组
int & refs[10] =  /*?*/;    // 错误,不存在引用的数组
int (*P)[10] = &arr;        // P指向一个含有10个整数的数组
int (&Ref)[10] = arr;       // Ref引用一个含有10个整数的数组

5.2 访问数组元素

与标准库类型 vector 和 string 一样,数组的元素也能使用范围for语句或下标运算符来访问。数组的索引从 0 开始。

 int arr[] = {1, 2, 3, 4, 5};
 // 使用下标运算符访问数组的特定元素
 for (int i = 0; i < 5; ++i) {
       cout << arr[i] << " "; // 输出数组的每个元素
 }

与 vector 和 string 一样,数组的下标是否在合理范围内由程序员自己检查。

5.3 指针和数组

在 C++ 语言中,指针和数组有非常紧密的联系。数组有一个特性,在很多用到数组名字的地方编译器会自动地将其替换成为一个指向数组首元素的指针。

string num[] = {"one","two"};
string *p = &num[0];  // p指向num的第一个元素
string *p2 = num;     // 等价于p2 = &num[0];

注意: 指向数组元素的指针拥有更多功能,指向数组元素的指针可以执行大多数迭代器运算。这些运算包括解引用、比较、递增、与整数相加等。

int ia[] = { 0,1,2,3,4,8 };
int last = *(ia + 4);  // 指向数组的第五个元素
int last2 = *ia + 5;   // 表示数组第一个元素的值加上5

1.标准库函数begin和end
C++11 新标准在数组中引用了两个名为 begin 和 end 的函数,两个函数与容器中的两个同名成员功能类似,不过数组毕竟不是类类型,因此这两个函数不是成员函数,正确的使用形式是将数组作为它们的参数,返回的是对应的指针类型。如下所示:

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

2.下标和指针

int ia[] = {0,1,2,3,4,6};
int *p = &ia[2];   // 指针p指向数组ia的第三个元素
int j = p[1];      // p[1]指的是数组中的第四个元素
int k = p[-2];     // p[-2]指的是数组中的第一个元素

5.4 C风格字符串

1.C标准库函数

函数含义
strlen(p )返回 p 的长度,空字符不计算在内
strcmp(p1, p2)比较 p1 和 p2 的相等性,相等返回 0,p1 > p2 返回正值,p1 < p2 返回负值
strcat(p1, p2)将 p2 附加到 p1 之后,返回 p1
strcpy(p1, p2)将 p2 拷贝给 p1,返回 p1

2.strcmp函数
要想比较两个 C 风格字符串需要调用strcmp函数,此时比较的就不再是指针了。如果两个字符串相等,strcmp 返回 0,如果前面的字符串较大返回正值,后面的字符串大返回负值。

char cha1[100] = "hello ";
const char cha2[] = "world";
if (strcmp(cha1, cha2) == 0)  // 字符串比较
	cout << "cha1 == cha2";
else if (strcmp(cha1, cha2) < 0)
	cout << "cha1 < cha2" << endl;
else
	cout << "cha1 > cha2";
cout << endl;

3.strcat函数和strcpy函数
连接或拷贝 C 风格字符串也与标准库 string 对象的同类操作差别很大,正确的方法是使用strcat函数和strcpy函数

char cha1[100] = "hello ";
const char cha2[] = "world";
strcat_s(cha1, cha2);      // 连接
cout << cha1 << endl;
strcpy_s(cha1, cha2);      // 拷贝
cout << cha1 << endl;

5.5 与旧代码的接口

很多 C++ 程序在标准库出现之前就已经写成了,因此它们没用到 string 和 vector 类型,但现代 C++ 程序不得不与那些充满了数组和 C 风格字符串代码衔接,为了使这工作简单易行,C++ 专门提供了一组功能。
1.混用string对象和C风格字符串

std::string str = "Hello, ";
const char* cStr = "world!";
std::string result = str + cStr; // 使用加法运算符连接 string 和 C 风格字符串
std::cout << "Result: " << result << std::endl;

2.c_str函数
string 提供了一个名为c_str的成员函数,函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组。指针的类型是const char*。

string myString = "Hello, world!";
// 使用 c_str() 返回一个指向以空字符结尾的 C 风格字符串的指针
const char* cStyleString = myString.c_str();
cout << "Content of C-style string: " << cStyleString << endl;

3.使用数组初始化vector对象
不允许使用 vector 对象初始化数组,但可以使用数组来初始化 vector 对象。

int arr[] = {1, 2, 3, 4, 5}; // 声明并初始化数组
// 使用数组初始化 vector 对象
// arr首元素,arr + sizeof(arr) / sizeof(arr[0])尾元素的下一位置
vector<int> vec(arr, arr + sizeof(arr) / sizeof(arr[0]));
// 打印 vector 中的元素
for (int num : vec) {
      cout << num << " ";
}
cout << endl;

6. 多维数组

严格来说,C++ 语言中没有多维数组,通常所说的多维数组其实是数组的数组。

6.1 多维数组的初始化与引用

1.多维数组的初始化
允许使用花括号括起来的一组值初始化多维数组,这点和普通数组一样。

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

类似于一维数组,在初始化多维数组时并非所有元素的值都必须包含在初始化列表中,如果只想初始化每一行的第一个元素,如下:

int ia[3][4] = {{0},{4},{8}}; // 初始化每行第一个元素
int ix[3][4] = {0,1,2,3};     // 显示初始化第一行,其它元素执行值初始化

2.多维数组的下标引用
可以使用下标运算符来访问多维数组的元素,此时数组的每个维度对应一个下标运算符,如果表达式含有的下标运算符数量和数组的维度一样多,该表达式的结果将是给定类型的元素。如果表达式含有的下标运算符数量比数组维度小,则表达式的结果将是给定索引处的一个内层数组。

int arr[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
cout << "Element at index [1][2]: " << arr[1][2] << endl; // 输出元素值
for (auto& row : arr) {
   for (auto value : row) {
          cout << value << " ";
   }
   cout << endl;
}

6.2 多维数组与指针

1.指针和多维数组
当程序使用多维数组的名字时也会自动将其转换成指向数组首元素的指针。因为多维数组实际上是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针。随着C++新标准的提出,通过使用 auto 和 decltype 就能避免在数组前面加上一个指针类型了。

#include <iostream>
void printArray(int (*arr)[3], int rows) {
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < 3; ++j) {
            std::cout << arr[i][j] << " ";
        }
        std::cout << std::endl;
    }
}
int main() {
    int arr[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };
// 传递多维数组给函数,arr会自动转换为指向第一个子数组的指针
printArray(arr, 2);
return 0;
}
// 参数 int (*arr)[3] 表示一个指向包含3个整数的数组的指针。
// 通过传递 arr 到 printArray 函数,arr 会自动转换为指向第一个子数组的指针,允许函数对整个多维数组进行遍历。

2.类型别名简化多维数组指针
读、写和理解一个指向多维数组的指针是一个让人烦不胜烦的工作,使用类型别名能让这项工作变得简单一点,如下所示:

// 类型别名简化多维数组指针
int ia[4][4] = {
		{1,2,3,4},
		{2,3,4,5},
		{3,4,5,6},
		{4,5,6,7},
};
using array = int[4];
for (array* p = ia; p != ia + 4; p++){
	for (int* q = *p; q != *p + 4; ++q){
		   cout << *q <<" ";
    }
	cout << endl;
}
  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Litle_Pudding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值