C++字符串与向量与数组
# using关键字
在C++中using关键字有两种作用,除了能用来声明别名外。using还可以将指定命名空间的成员引入到当前命名空间,这样在使用其他命名空间的类、函数、变量时,就不需要再使用"命名空间名::"前缀了。
它的使用格式为using namespace::name;
,一般会定义在所有#include的下面。
在std:c++17之前的标准,每个名称都要使用单独的using进行引入。 在std:c++17及其之后的标准,多个名称可以在同一条using中引入。
例如:
#include <iostream>
// std:c++17及其之后的标准可以使用using std::cout, std::endl;
using std::cout;
using std::endl;
int main(){
cout << "Hello, World!" << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
但需要注意的时过度使用using引入命名空间可能会导致命名冲突和代码不易读,因此要谨慎使用。 还要注意,头文件中不应该使用using声明,如果头文件中有某个using声明,那么包含该头文件的所有文件都会有这个声明,可能会造成始料未及的名称冲突。
我们还可以使用using引入整个名称空间中的名称,用法如下:
// 格式
using namespace 名称空间名;
// 例子
using namespace std;
2
3
4
5
# 标准库string类型
# 字符串介绍
标准库类型string表示可变长的字符序列,使用string类型必须先包含string头文件,string类型定义在命名空间std中。
另外iostream库也会隐式地包含string头文件,但并不建议依赖于这类隐式引用,如果没有显式地引用可能会引发一些问题,例如某些函数无法使用。 例如:
#include <string>
using std::string;
2
# 字符串定义
如何初始化类的对象是由类本身决定的。一个类可以定义多种初始化对象的方式。以下是string类型的初始化方法:
// 默认初始化,默认值为空字符串
string s1;
// 直接初始化
string s2("Hello");
// 拷贝初始化
string s2 = "Hello";
// 重复值初始化,创建一个由10个c字符组成的字符串
string s3(10, 'c');
2
3
4
5
6
7
8
# 字符串操作
# 介绍
一个类除了会规定初始化其对象的方式外,还会定义对象所能执行的操作。类不仅能定义函数方法,还能定义<<、+、-等各种运算符在该类对象上的含义。
操作 | 作用 |
---|---|
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中指定下标的引用 |
s1 + s2 | 拼接字符串s1和s2,并返回拼接结果 |
s1 == s2 | 用s2的副本代替s1中原来的字符 |
s1 != s2 | 判断s1和s2的字符串是否完全一致,对大小写敏感 |
<、<=、>、>= | 利用字符在编码字典中的顺序进行比较,对大小写敏感 |
# 读写字符串对象
正如上面字符串操作所示,字符串类型也能使用重定向符号进行输入输出。再执行输入操作时,字符串对象会自动忽略开头的空白符,然后从第一个真正的字符开始读取,直到遇到下一个空白符为止。
空白符即空格、换行、制表符。也就是使用重定向输入时遇到空格就会停止。 如果想要读取一整行,则可以使用getline函数,getline函数函数会从输入流中读取字符直到遇到换行符为止,另外它在存储字符串时会丢弃换行符。 例如:
// 案例一
string s;
// 如果输入hello world则s会为hello。
cin >> s;
cout << s << endl;
// 案例二
string s1, s2;
// 如果输入hello world则s1会为hello,s2会为world。
cin >> s1 >> s2;
cout << s1 + " " + s2 << endl;
// 案例三
string line;
// 使用getline函数可以读取一整行。
getline(cin, line);
cout << line << endl;
// 案例四
string line;
// 因为getline函数也返回cin对象,所以可以这样。按行读取输入的内容,直到文件末尾,也就是碰到EOF文件结束符开头的输入流为止。
while (getline(cin, line)) {
cout << line << endl;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
文件结束符用于标识文件末尾,不同操作系统的文件结束符也不同,Windows环境的文件结束符是Ctrl+z,Linux环境的文件结束符是Ctrl+d。
# 判断字符串是否为空
我们可以使用empty方法来判断string对象是否为空,如果为空则会返回True,否则返回False。 例如:
// 默认为空字符串
string line;
// 循环按行读入字符串
while(getline(cin, line)){
// !运算符取反,如果表示line字符串不为空则执行输出
if(!line.empty()){
cout << line << endl;
}
}
2
3
4
5
6
7
8
9
# 获取字符串长度
我们可以使用size方法来获取string对象的字符个数。 例如:
string line;
getline(cin, line);
// 如果字符串个数大于等于5则输出
if(line.size() >= 5){
cout << line << endl;
}
2
3
4
5
6
string类型的size方法会返回一个string::size_type类型的无符号整数,因此我们在判断时不应该混用有符号数进行判断,因为在自动类型转换时,负值必然会转换成一个比较大的无符号值。
# 字符串拼接
我们可以使用"+"加号将string对象进行相加,其内容是左侧的字符串与右侧的字符串进行拼接。另外复合赋值运算符"+="也可以用于字符串对象。
string对象也可以和字符字面值或字符串字面值相加,这两种字面值在和string对象运算时会自动转化为string对象。 注意:字符串字面值和string对象是不同的类型。进行拼接时必须确保+加号运算符左右两侧至少有一个是string对象,不能两个字符或字符串字面值进行相加,此时就需要考虑从左往右的运算顺序,s+"aa"+"bb"是允许的(因为s+"aa"会得到string对象),而"aa"+"bb"+s则是不允许的。 例如:
string s1 = "hello";
string s2 = "world";
// s1先和" "拼接,然后其结果再和s2拼接,然后赋值给s3,结果为"hello world"
string s3 = s1 + " " + s2;
string s4 = "goodnight ";
// 等价于s4 = s4 + s2,结果为"goodnight world"
s4 += s2;
2
3
4
5
6
7
# 字符处理函数
在需要对单个字符进行判断或处理时,我们可以使用cctype头文件中的标准库函数,该头文件中定义一组针对字符的判断和处理函数。 C语言的ctype.h库在C++语言中就叫cctype库。C++除了定义C++语言特有的功能外,还兼容了C语言的标准库。要使用这些库只需要将.h后缀去掉,然后开头加上c即可。此类被兼容的C标准库中定义的名称都从属于命名空间std。 cctype库函数如下所示:
函数 | 作用 |
---|---|
isalnum(c) | 如果c是字母或数字则返回True |
isalpha(c) | 如果c是字母则返回True |
iscntrl(c) | 如果c是控制字符则返回True |
isdigit(c) | 如果c是数字则返回True |
isxdigit(c) | 如果c是十六进制数字则返回True |
isgraph(c) | 如果c是非空格的可打印字符则返回True |
isprint(c) | 如果c是可打印字符则返回True |
ispunct(c) | 如果c是标点符号则返回True |
isspace(c) | 如果c是空白则返回True (即空格、横向制表符、纵向制表符、回车符、换行符、进纸符等) |
islower(c) | 如果c是小写字母则返回True |
issupper(c) | 如果c是大写字母则返回True |
tolower(c) | 如果c是大写字母则输出对应的小写字母,否则原样输出 |
toupper(c) | 如果c是小写字母则输出对应的大写字母,否则原样输出 |
# 迭代处理每个字符
想要迭代处理String对象的每个字符,我们可以使用基于范围的for循环。该方式的for循环可以对给定序列的每个元素进行迭代处理,而一个String对象可以表示为一个字符序列,因此它可以作为基于范围的for循环的迭代对象。 基于范围的for循环还需要指定一个变量用于存储序列中的元素值,第一次循环会被赋值为第一个元素的值,第二次循环会被赋值为第二个元素的值,以此类推,直到序列中的元素被全部遍历完。 默认情况下,循环变量是一个仅用来存储元素值副本的变量,对其进行的修改不会影响序列本身。如果我们想要修改序列本身的元素值,则需要将循环变量定义为引用类型,表示迭代时将循环变量与元素本身进行绑定。
与传统的for循环相比,基于范围的for循环能自动处理迭代,避免了迭代器或下标可能导致的错误,使代码更加易于编写和理解。 语法格式:
for (类型 变量名 : 序列表达式) {
/* 循环体 */
}
2
3
例如:
#include <iostream>
#include <string>
#include <cctype>
using std::cout;
using std::endl;
using std::string;
// 将字符串转为全大写
//第一种方式,通过另一个变量进行存储
void func1(){
string s = "hello world!";
string res;
for (auto i : s) {
res += toupper(i);
}
// 输出转换后的结果:HELLO WORLD!
cout << res << endl;
return;
}
//第二种方式,通过将循环遍历定义为引用来修改原本元素值
void func2(){
string s = "hello world!";
for (auto &i : s) {
i = toupper(i);
}
// 输出转换后的结果:HELLO WORLD!
cout << s << endl;
return;
}
// 第三种方式,使用传统for循环方式+下标进行迭代修改
void func3(){
string s = "hello world!";
for (decltype(s.size()) i = 0; i < s.size(); i++) {
s[i] = toupper(s[i]);
}
// 输出转换后的结果:HELLO WORLD!
cout << s << endl;
return;
}
int main(){
func1();
func2();
func3();
return 0;
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 下标访问字符
String对象是字符序列,因此我们可以通过下标(索引)来访问字符串中的指定字符。 下标运算符是"[整数表达式]",其中括号内的整数值代表下标,下标表示要访问的字符位置,下标从0开始。 例如要访问第一个字符元素就是s[0],要访问第二个字符元素就是s[1],以此类推。如果要访问最后一个元素,可以使用s[s.size() - 1]。
注意:访问的下标不能超过序列长度,否则会引发报错。
# 标准库vector类型
# vector介绍
vector类型又称容器类型,是用于存储单一类型的对象的单向动态数组。容器类型可以存储除了引用类型外的所有类型的对象,包括容器类型本身。容器类型的元素也是线性顺序排列的,因此可以通过下标来快速访问指定成员。 使用vector类型必须先包含vector头文件,vector类型也定义在命名空间std中。 例如:
#include <vector>
using std::vector;
2
# 类模板介绍
C++中有类模板和函数模板,使用模板时可以实例化时任意类型的类或函数。 模板本身不是类或者函数,可以把模板看作是为编译器生成类或函数编写的一份说明,编译器根据模板创建类或者函数的过程称为实例化。 类模板的主要作用就是支持泛型编程。它允许用户创建一个通用的类定义,类模板在实例化时会根据代码所传入的类型生成对应类型的类。 使用类模板可以避免"多个类传入对象类型不同但代码相同"的代码重复的情况。
例如我们想要定义存储double类型对象或string类型对象的类,此时除了保存的对象类型不同外,这两种类的代码是相同的。 这个时候如果不使用类模板,就需要手动声明两个类。而如果使用类模板,就只需要编写一个泛型的类模板,然后在代码中传入要存储的类型给这个类模板,编译器就能创建出能够存储指定类型的类了。
# vector对象的定义
vector本身是类模板而非类型,vector
其中"<>"用于传入类模板实例化的参数,T可以是除引用外的任意类型,表示容器要存储对象的类型。 例如:
// 定义一个存储int类型对象的容器
vector<int> ivec;
// 定义一个存储Book自定义类型对象的容器
vector<Book> book_vec;
// 定义一个存储int指针类型对象的容器
vector<int *> vint;
// 定义一个存储vector<tring>类型对象的容器
vector<vector<string>> file;
2
3
4
5
6
7
8
# vector对象的初始化
vector对象的初始化方法也是由vector模板控制。一下是一些常用初始化方法:
// 默认初始化,创建一个存储T类型的空容器。
vector<T> v1;
// 拷贝初始化,会拷贝v1容器中的所有元素来创建容器,必须类型一致。
vector<T> v2(v1);
vector<T> v3 = v1;
// 重复初始值初始化,n是整数,表示创建n个T类型初始值的元素。
vector<T> v4(n);
// 重复值初始化,n是整数,表示创建n个值为val的元素。
vector<T> v5(n, val);
// 列表初始化,等号可省略,用列表中的指定元素进行初始化。
vector<T> v6 {val1, val2, ...};
vector<T> v7 = {val1, val2, ...};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
例如:
// 大部分情况用的都是默认初始化,后续再插入元素。
vector<float> f_vec;
// 列表初始化
vector<string> str_vec{ "hello", " ", "world!"};
// 重复值初始化
vector<int> int_vec(10, -1);
2
3
4
5
6
# vector对象操作
vector类型中也定义了一系列方法来操作容器,以下是一些常用方法:
方法名 | 作用 |
---|---|
v.push_back(val) | 在v末尾添加元素val |
v.pop_back() | 移除v末尾的第一个元素 |
v.clear() | 清空v中所有元素 |
v.empty() | 如果v不含有任何元素则返回真,否则假 |
v.size() | 返回v中元素的个数 |
v[n] | 返回v中第n个位置上元素的引用 |
v1 = v2 | 用v2中元素的拷贝替换v1中的元素 |
v1 = {a,b,c,...} | 用列表中元素的拷贝替换v1中的元素 |
v1 == v2 | 如果v1和v2的元素值与位置都一致则为真,否则假 |
v1 != v2 | 如果v1和v2的元素值与位置不一致则为真,否则假 |
<、<=、>、>= | 以字典顺序进行比较 |
# 增删元素
vector容器似于一个单向的动态数组,可以动态插入和移除元素,但因为是单向,所以只能像堆栈一样后进先出。
注意:在对动态数组使用基于范围的for循环时,不能改变所遍历的数组的大小。 例如:
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::ends;
using std::vector;
int main() {
// 声明一个空容器
vector<int> ivec;
for (int i = 0; i < 10; i++) {
// 每次循环追加i到容器末尾
ivec.push_back(i);
}
// 将最后一个元素赋值给pop_last
int pop_last = ivec[ivec.size() - 1];
// 移除最后一个元素并赋值给pop_res
ivec.pop_back();
// 结果为:9
cout << "弹出的最后一个元素为:" << pop_last << endl;
// 结果为:0 1 2 3 4 5 6 7 8
cout << "当前容器元素为:" << ends;
for (auto i : ivec) {
cout << i << " " << ends;
}
return 0;
}
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
# 迭代器
# 迭代器介绍
迭代器是一个对象,它可以遍历(或"迭代")容器中的元素,如数组、向量、列表等。迭代器的工作方式类似于指针,提供对容器中元素的访问,允许读取或修改元素。
迭代器一般从容器第一个元素开始迭代,直到容器末尾,要想让迭代器指向下一个元素可以使用递增(++)操作。特定类型的迭代器还可以使用递减(--)操作来指向上一个元素。
# 迭代器的定义
要想定义迭代器,首先需要迭代对象是一个可迭代的类型,也就是拥有begin()和end()方法的类型,这两个方法会返回迭代器对象。例如string、vector等就是可迭代的类型。
begin()方法返回容器第一个元素。
end()方法返回容器末尾元素的下一个位置,因此它也被称为尾后迭代器。它仅是个标记,可以用判断迭代器是否已经迭代到尾部。
如果容器为空,没有任何元素,则begin()会等于end()。
例如:
vector<int> { 1, 3, 5, 7 };
// 不需要关系迭代器是什么类型,所以使用auto即可
auto iter_b = v.begin(), iter_e = v.end();
2
3
# 迭代器类型
不同类型的begin()
和end()
方法所返回的迭代器类型也可能不同。
在对非常量执行begin()和end()操作时一般会返回iterator类型。
而如果迭代对象是常量,则返回的迭代器就会是const_iterator类型,表示只读迭代器。如果想对非常量对象也使用只读迭代器,则可以使用cbegin()和cend()方法。
一般迭代器是从第一个元素向末尾元素前进的,所以向前移动的意思就是向容器末尾移动。
# 迭代器分类
另外根据迭代对象类型不同,其支持的操作也可能不同,基于使用方式和能执行的操作可以将其分为以下几类:
输入迭代器:单次单向只读,它仅能单向遍历(递增)一次,且只能读取容器中元素。也就是说,一旦通过输入迭代器读取了一个元素,就不能再次读取该元素。
常见的使用输入迭代器的类型有
vector
、deque
、array
等。
输出迭代器:单次单向只写,它仅能单向遍历(递增)一次,且只能将数据写入容器中元素。
常见的使用输出迭代器的类型有
vector
、deque
、array
等。
前向迭代器:多次单向读写,它仅能单向(递增)遍历,但可以多次遍历容器,且可以对容器中元素进行读写。
常见的使用随机访问迭代器的类型有有
vector
、list
、deque
、array
等。
双向迭代器:多次双向读写,它可以双向遍历(递增、递减),也可以多次遍历容器,且可以对容器中元素进行读写。
常见的使用随机访问迭代器的类型有
map
、set
、multimap
、multiset
等。
随机访问迭代器:功能最强大的迭代方式,它支持所有的迭代器操作,包括读写操作、递增、递减、比较和算术运算。使得我们可以像操作数组一样操作容器中的元素。
常见的使用随机访问迭代器的类型有
vector
、deque
、array
和string
等。
# 迭代器操作
操作符 | 作用 |
---|---|
*iter | 返回迭代器iter当前所指元素的引用 |
iter->mem | 解引用iter并获取该元素中名为mem的成员,等价于(*iter).mem |
++iter | 令iter指向容器的下一个元素 |
--iter | 令iter指向容器的上一个元素 |
iter1 == iter2、iter1 != iter2 | 判断两个迭代器是否相等或不相等。如果两个迭代器指向同一个元素,或者他们是同一个容器的尾后迭代器,则为真,否则假。 |
下标运算符只有部分标准库容器类型才有,而==和!=运算符是所有标准库容器类型都有,所以我们在遍历容器时,一般会使用迭代器加==和!=来进行遍历。
# 迭代器失效情况
如果我们对一个可动态增长的对象执行改变容量大小的操作,其已经生成的迭代器都会失效。
所以但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
例如:
vector<int> v { 1, 2, 3 };
auto vb = v.begin(), ve = v.end();
// 执行该操作后vb和ve迭代器都会失效,因为改变了容器的容量大小
v.pop_back();
2
3
4
# 迭代器移动元素和访问
要想移动迭代器所指向的元素只需要对迭代器进行递增递减操作即可。要想访问元素只需要使用对迭代器进行解引用即可。
例如:
vector<string> v{ "Hello", " ", "World!" };
// 使用传统for循环进行移动,iter不等于end()表示容器没有遍历完,每次循环结束都iter++指向下一个元素
for (auto iter = v.begin(); iter != v.end(); iter++) {
// 解引用迭代器当前元素得到其值,并输出
cout << *iter << endl;
// 解引用迭代器当前元素得到其值,并调用其clear()方法
iter->clear();
}
// v最终结果输出为空
for (auto i : v)
cout << i << ends;
2
3
4
5
6
7
8
9
10
11
12
编译器并不会检查迭代器是否指向的是容器中的元素,所以需要自己确保移动后的迭代器仍然指向容器内的元素或尾元素后一位。
# 迭代器运算
string和vector提供更多的额外运算符,可以使迭代器每次移动多个元素和支持更多的关系运算。
运算 | 作用 |
---|---|
iter + n | 返回iter向前移动n个元素后的迭代器对象,不修改iter本身的指向 |
iter - n | 返回iter向后移动n个元素后的迭代器对象,不修改iter本身的指向 |
iter += n | 迭代器加法的复合赋值语句,同等于iter = iter + n |
iter -= n | 迭代器减法的复合赋值语句,同等于iter = iter - n |
iter1 - iter2 | 两个迭代器相减的结果是它们之间的距离,两个迭代器必须指向的是同一个容器中的元素或尾元素后一位,其返回值是difference_type类型的有符号整数 |
>、>=、<、<= | 迭代器越靠前越大(越靠近序列末尾越大),越靠后则越小,两个迭代器必须指向的是同一个容器中的元素或尾元素后一位 |
# 迭代器运算实现二分法
二分法是对有序数组每次都进行二分查询,以提高查询效率,在C++中迭代器也可以实现二分法。 二分法首先需要序列是顺序排序的,然后取其中间值,如果中间值不等于查询值且比查询值小,则说明查询在后面,否则说明查询值在前面,循环往之,如果中间值不在尾后和首前范围内则跳出循环,说明查询值不存在。
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::ends;
using std::vector;
int search(vector<int> v, int sought) {
/*
返回值-1表示未找到,否则返回索引位置
*/
// 迭代器,首前和尾后
auto beg = v.begin() - 1, end = v.end();
// 中间值
auto mid = v.begin() + v.size() / 2;
// 如果中间值不等于搜索值,mid大于首前或尾后则循环
while (*mid != sought && mid < end && mid > beg) {
// 如果中间值不等于查询值且比查询值小,则说明查询在后面,则从中间值开始查找
if (*mid < sought) {
// 开头设为mid
beg = mid;
// 取中间值到尾后的中间值
mid += (end - mid) / 2;
}
else {
// 结尾设为mid
end = mid;
// 取中间值到首前的中间值
mid -= (mid - beg) / 2;
}
}
if (*mid == sought) {
return mid - v.begin();
}
else {
return -1;
}
}
int main() {
vector<int> v{ 1, 5, 9, 10, 23, 41, 122, 440 ,990, 1231, 4240 ,6531, 6699, 7777 ,9999 };
// 测试是否都能找到
for (auto i : v) {
int res = search(v, i);
if (res > -1) {
cout << i << " GET! 索引位置: " << res << endl;
}
else {
cout << i << " NOT FOUND." << endl;
}
}
return 0;
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 指针也可以作为迭代器
指针也可以进行递增递减操作,每次移动都会移动一个指针所指类型的大小,也就是到下一个元素的距离。 例如:
int v[3] = {13, 24, 35};
int *e = &v[3];
for (int *b = v; b != e; b++) {
cout << *b << endl;
}
2
3
4
5
# 数组
# 介绍
数组类似于vector类型,也用于存储单一类型的对象,但与vector类型不同的是,数组是固定长度的,不能随意向数组中增加元素。
在C++中应当尽量使用vector类型和迭代器,而非数组和指针,因为越底层越容易出错。
# 定义数组
数组是一种复合类型,使用数组类型不需要导入任何库或名称。它的定义格式为type name[length]
,类型就是数组元素的类型(不能使用auto),名称就是数组变量的名称,长度就是数组的长度(整数值)。
例如:
// 定义一个长度为10的、用于存储int类型值的数组。默认初始化时,元素值为默认值。
int arr[10];
// 定义一个长度为42的、用于存储int类型指针的数组。数组长度可以从常量表达式传入,但注意不能从普通变量传入。
constexpr unsigned int sz = 42;
int *parr[sz];
2
3
4
5
6
# 初始化数组
数组也可以进行列表初始化,此时可以忽略数组的维度(长度等),编译器会根据初始值的数量来计算。反之如果指定了维度,那么初始值的数量则不能超过指定的长度。如果数组长度大于传入的元素数量,那么会初始化靠前的元素,剩下的元素会初始化为默认值。 例如:
// 编译器会根据列表初始化的元素个数推断出长度,因此该数组长度为3。
int arr1[] = { 1, 2, 3 };
// 列表初始化,未初始化的部分会使用默认值,所以同等于{1,2,3,0,0}
int arr2[5] = { 1, 2, 3 };
// 字符类型的数组可以使用字符串进行初始化,编译器推断出的长度为字符数量+1,因为最后还需要有一个元素存储\0字符。
char arr3[] = "Hello World!";
// 定义一个存储int型指针的数组。
int *parr1[10];
// 定义一个名称为pname的指针,指向一个含有10个整数的数组。
int (*pname)[10] = &arr;
// 定义一个名称为rname的引用,引用一个含有10个整数的数组。
int (&rname)[10] = arr;
// 定义一个二维数组,多维数组以此类推,另外多维数组只能指定长度不能由编译器推算。
int marr[3][2] = {{1,2},{3,4},{5,6}};
// 会报错,C++不允许直接用数组为其他数组赋值或初始化,即便长度相同也不行。
char err1[3] = { 1, 2, 3 };
char err2[3] = err1;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 访问数组元素
访问数组元素可以通过下标表达式来访问,下标都是从0开始。如果需要使用常量表达式来代替下标,则可以将其定义为size_t类型,该类型定义在stddef.h库中。 另外数组名称本身是指向数组第一个元素的内存地址的,所以如果想要指针指向数组第一个元素,可以直接使用数组名(不用使用&取地址符)。 例如:
int arr[] = {1, 20, 33, 46, 59};
// 指向数组第一个元素
int *ptr = arr;
// 输出1
cout << *ptr << endl;
cout << arr[0] << endl;
// 输出20,加1会向后偏移一个int大小的地址,也就是索引1的位置
cout << *(ptr + 1) << endl;
cout << arr[1] << endl;
2
3
4
5
6
7
8
9
# begin与end函数
C++的标准库还提供两个函数供我们获取数组的首元素指针和尾后元素指针。它们定义在xutility库的std命名空间中。 使用例子:
int v[3] = {13, 24, 35};
int* e = end(v);
for (int *b = begin(v); b != e; b++) {
cout << *b << endl;
}
2
3
4
5