字符串、vector和数组

该博客聚焦于C++中字符串、vector和数组的学习。介绍了命名空间的using声明,详细讲解了标准库类型String的初始化、操作及字符处理方法。还阐述了vector容器的初始化、元素添加与操作,迭代器的使用与运算,以及数组的定义、初始化、遍历和指针应用,包括多维数组的相关知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习目标:

字符串、vector和数组


学习内容:

一、命名空间的using声明

  • 形式:using std::name 编译器会从左侧的命名空间中去寻找右侧那个名字

    #include <iostream>
    using std::cin;
    using std::cout;
    using std::endl;
    int main(){
        int x;
        cin >> x;
        cout << x << endl;
        return 0;
    }
    
  • 但是头文件不宜有using,因为头文件可能会被拷贝到其他文件中,那么每个拷贝这个头文件的文件都会包含这个声明。

  • 更常用的方式:using namespace name;

    #include <iostream>
    using namespace std;
    int main(){
        int x;
        cin >> x;
        cout << x << endl;
        return 0;
    }
    

二、标准库类型String
string类型表示可变长的字符串序列。string类型使用都要引用头文件:

#include <string>

1. string初始化

  • 初始化方式
    i. string s1; 默认初始化,s1是空串 直接初始化
    ii. string s2(s1); 直接初始化 <> string s2 = s1; 拷贝初始化
    iii. string s3(“hello”) 直接初始化 <
    > string s3 = “hello”; 拷贝初始化
    iv. string s4(n, ‘h’); 把字符’h’复制n份 直接初始化 string s4(n, ‘h’)
    <=>string s5 = string(n, ‘h’)<=> string temp = string(n, ‘h’);string s6 = temp;

  • 用等号=初始化:拷贝初始化;不用等号,直接初始化;

2. string对象操作
2.1 读写对象操作

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s;
    cin >> s;
    cout << s << endl;
    return 0;
}
  • 执行读取操作时,会自动忽略开头的空白(空格、换行符、制表符等),即输入" sda “,输出还是"sda”;

  • 输入输出返回的是左侧的运算对象,所以可以连续输入输出;

    #include <iostream>
    #include <string>
    using namespace std;
    int main(){
        string s1, s2;
        cin >> s1 >> s2;
        cout << s1 << " " << s2 << endl;
        return 0;
    }
    
  • 读取未知数量的string对象: while(cin >> s) 以非空格符开始,空格符结束

    #include <iostream>
    #include <string>
    using namespace std;
    int main(){
        string s;
        // 判断输入流是否有效,如果有效执行while内语句
        // 如果输入流无效,输入结束符,结束while循环
        // 以非空格开始  空格符结束  如输入'  abc def'
        // 实际输出两个值'abc'和'def'
        while (cin >> s){
            cout << s << endl;
        }
        return 0;
    }
    

2.2 使用getline读取一整行getline(cin,s):以换行符结束,会读取这一行所有空格符(空格符也被当中正常的字符)。所以就算输入是空行,也同样会正常读取,输出空行。

#include <iostream>
#include <string>
using namespace std;
int main(){
    string line;
    // 以换行符结束  输入'   abc   sds   '
    // 输出15 和 '   abc   sds   '
    while(getline(cin, line)){
        cout << line.length() << line << endl;
    }
    return 0;
}

2.3 判断string对象是否为空:s.empty(),空返回True,否则返回True

#include <iostream>
#include <string>
using namespace std;
int main(){
    string line;
    while(getline(cin, line)){
        if(!line.empty()){
            cout << line << endl;
        }
    }
    return 0;
}

2.4 返回string对象长度,即有多少个字符:s.size()、s.length(); 注意size和length返回的都是string::size_type类型。所以一般不用一个int型的变量存放size函数返回值。

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s = " hello world ";
    cout << s.size() << endl;    // 13
    cout << s.length() << endl;  // 13
    
    int len = s.size();   // 最好不要这样写
    auto len = s.size();  // 利用c++11新特性auto这样写比较好
}

2.5 比较string对象

  • ==或者 != :两个对象,只有等长,对所有字符完全一样(包括大小写),==才成立;

  • <、>、<=、>=:按照字符顺序从第一个字符开始比较(大小写敏感);

    #include <iostream>
    #include <string>
    using namespace std;
    int main(){
        // s3 > s2 > s1
        string s1 = "hello";
        string s2 = "hello world";
        string s3 = "hiya";
    }
    

2.6 string对象赋值:和普通变量一样

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s1 = "hello", s2;  // 初始化
    s2 = s1;  // 对象赋值
}

2.7 两个string对象相加,相当于将两个string对象串联起来,生成一个新的string对象。

#include <iostream>
#include <string>
using namespace std;
int main(){
    string s1 = "hello", s2 = " world", s3;
    s3 = s1 + s2;  
    s1 += s2;  
    cout << s1 << endl;  // s1 = 'hello world'
    cout << s3 << endl;  // s3 = 'hello world'
}

2.8 string对象和字面值常量相加

  • string字面值常量和string是不同的类型,但是标准库可以将string字面值常量转换为string对象,所以可以将string对象和string字面值常量相加;

  • 遵循原则:+两边至少要有一个string对象

    #include <iostream>
    #include <string>
    using namespace std;
    int main(){
        string s1 = "hello", s2 = " world";
        string s3 = s1 + s2;
        string s4 = s1 + ",  ";
        string s5 = "hello" + "world";  // 错误  两个字面值常量不能直接相加
        // 正确 和连续输入输出一样  s1 + ", "返回的是string对象
        // 等价于string temp = s1 + ",  "; string s6 = temp + "world"
        string s6 = s1 + ",  " + "world";
        string s7 = "hello, " + "world" + s2;  // 错误  两个字面值常量不能直接相加
    }
    

3. 处理string对象中的字符

  • 为了方面我们访问string对象中的字符,cctype头文件中定义了一组标准库函数来处理,而cctype头文件中定义的名字从属于命名空间std中,所以只要引用std命名空间,即可直接访问下面这一组库函数。
    在这里插入图片描述

  • 访问string对象中的字符:范围for语句 形式:for(declaration : expression) statement; 其中expression表示对象,declaration定义一个变量,这个变量是expression对象(序列)中的基本元素.每次for迭代expression对象都会被初始化为expression中的下一个元素。而statement可以定义一些对基本元素的操作。

    #include <iostream>
    #include <string>
    using namespace std;
    int main(){
        string str = "hello world";
        for(auto s : str){
            cout << s << endl;
        }
    }
    
  • 统计string对象中的字符:不改变string对象 例子:统计string对象中的小写字母个数

    #include <iostream>
    #include <string>
    using namespace std;
    int main(){
        string str = "Hello World";
        // string::size_type类型
        decltype(str.size()) count = 0;
        for(auto s : str){
            if(islower(s))
                ++count;
        }
        cout << count << endl;  // 8
    }
    
  • 改变string对象中的字符:在declaration中引用string对象中的每个元素 例子:把string对象中每个小写字母该成大写字母

    #include <iostream>
    #include <string>
    using namespace std;
    int main(){
        string str = "Hello World";
        for(auto &s : str){
            // 这里要把输出的大写形式重新赋值给s
            // s其实就是str中元素的别名,所以对别名操作,也是对str中元素进行操作
            s = toupper(s);
        }
        cout << str << endl;
    
  • 使用下标访问string对象中的元素 下标[],输入一个索引(string::size_type类型),指示元素位置,返回该位置上字符的引用。 索引最好是使用string::size_type类型,范围在0~size-1之间,但是如果是int带符号类型,系统也会自动转换为string::size_type无符号类型;
    i. 只使用下标:访问string对象中的某个元素 例子:把string对象中第一个元素变为大写

    #include <iostream>
    #include <string>
    using namespace std;
    int main(){
        string str = "hello world";
        if(!str.empty()){
            str[0] = toupper(str[0]);  // 下标返回的是引用,所以可以直接重新赋值
        }
        cout << str << endl;  // Hello world
    }
    

    ii. 使用下标+for迭代:访问string对象中的部分元素 例子:把string对象中的第一个单词该成大写形式

    #include <iostream>
    #include <string>
    using namespace std;
    int main(){
        string str = "hello world";
        
        for(decltype(str.size()) index = 0; index < str.size() && !isspace(str[index]); ++index)
            str[index] = toupper(str[index]);
        cout << str << endl;  // HELLO world
    }
    

三、标准库类型vector容器
vector表示对象的集合,而且所有对象的类型都必须一样。 vector是一个类模板,模板本身不是类或函数。 可以将模板看作编译器生成类或函数的一份说明,编译器根据模板创建类或函数的过程可以称为实例化。 vector可以使用大多数类型(甚至可以是vector类型的对象)的对象作为其元素,但是引用不行,引用不是对象。 vector的使用是这样的:

#include <iostream>
#include <string>
#include <vector>   // 引入vector头文件
using namespace std;
int main(){
    // 必须指明vector到底要实例化一个什么类
    // 这里指明实例化一个vector 这个vector存放的所有对象必须是int类型
    vector<int> ivec = {1, 2, 3};
}

1. vector初始化

  • 常见的vector初始化方法
    i. 默认初始化: vector<类型> v1;
    ii. 直接初始化:vector<类型> v2(v1);
    iii. 拷贝初始化: vector<类型> v3 = v1;
    iv. 列表初始化: vector<类型> v4 = {a,b,c};
    v. 指定初始化个数和值: vector<类型> v5(n, val); 包含n个元素,每个元素都是val;
    vi. 值初始化: vector<类型> v6(n); 包含n个元素 每个元素都是默认初始化;

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    int main(){
        vector<int> v1;               // 默认初始化 {}  生成一个空vector对象  0个元素
        vector<int> v2(v1);           // 直接初始化 {}
        vector<int> v3 = v1;          // 拷贝初始化 {}
        vector<int> v4 = {1, 2, 3};   // 列表初始化 {1,2,3}
        vector<int> v5(4, 1);         // 指定初始化个数和值  {1,1,1,1}
        vector<int> v6(5);            // 值初始化   {0,0,0,0,0}  
    }
    
  • 注意这几种初始化方式在大多数情况可以相互等价,但是在以下3种情况下不行:

    i. 使用拷贝初始化时,只能提供一个初始值;
    ii. 如果是类内初始化,只能使用拷贝初始化或者列表初始化{};
    iii. 如果要初始化一个列表,只能用花括号,不能用圆括号;

  • 如何确定()和{}的含义,到底执行列表初始化还是构造初始化
    i. 如果是使用(),肯定是构造初始化,即通过()内的参数,来构造vector对象;如果()不能执行构造初始化,会直接报错,因为它不可以执行列表初始化;

    vector<int> v1(10);  // 构造初始化  10个元素 每个元素都是默认初始化0
    vector<int> v2(10, 1);  // 构造初始化 10个元素 每个元素都是1
    vector<int> v3("hello"); // 报错 无法执行构造初始化,不能执行列表初始化 报错
    

    ii. 如果是使用{},第一下肯定看它能否执行列表初始化,如果不可以,它可以执行构造初始化;

    vector<int> v1{10};             // 列表初始化  一个元素 10
    vector<int> v2{10, 2};          // 列表初始化 两个元素 10 2
    vector<string> v3{"hello"};     // 列表初始化 一个元素 "hello"
    vector<string> v4{10};          // 无法执行列表初始化 -> 构造初始化 10个元素 都是""
    vector<string> v5{10, "hello"}; // 无法执行列表初始化 -> 构造初始化 10个元素 都是"hello"
    

2. 对vector对象中加入元素:push_back
使用vector的成员函数push_vector,可以让vector高效动态增长(添加元素) 例子:

#include <iostream>
#include <vector>
using namespace std;
int main(){
    vector<int> v1;
    for(int i = 0; i < 100; i++){
        v1.push_back(i);
    }
    return 0;
}

3. 其他vector操作

  • v.empty():如果v不含任何元素,返回True,否则返回False;

  • v.size(): 返回容器中元素的个数, v.size()返回的是vector<类型>::size_type类型;

  • v.push_back(t): 在容器末尾添加一个元素t;

  • 同样可以用范围for语句访问容器中的所有元素和修改(&)容器中的所有元素;

    #include <iostream>
    #include <vector>
    using namespace std;
    int main(){
        vector<int> v = {1, 2, 3, 4};
        for(auto i : v){    // 访问
            cout << i << endl;  
        }
        for(auto &j : v){   // 修改
            j *= j;
        }    // v = {1, 4, 9, 16}
        return 0;
    }
    
  • v[i]:访问或修改容器v中idx=i的元素,返回的是这个元素的引用;
    i. 下标访问容器中元素时,下标必须合法,即处于0->size-1之间,超出这个范围会报错;
    ii. 所以为了保证下标合法,应该尽量用for语句,for(int i = 0; i < v.size(); i++),肯定不会出错;
    iii. 下标是无法添加元素的,只能访问元素,添加只能用push_back函数;

  • 比较:==、!=,只有当两个容器中原始个数相同且每个元素值也完全相同时,等式成立,返回True,否则返回False;

  • 比较:<、>、<=、>=,将每个元素从左到右按字典顺序进行比较;

  • 赋值:v1 = v2; v1 = {a,b,c,d},将v2或者{a,b,c,d}拷贝替换v1中的元素;

四、迭代器
1. 使用迭代器

  • string对象不属于容器,但是它们有很多相似的操作
    i. 都支持下标访问;
    ii. 都支持迭代器访问;

  • 遍历string和vector的方式
    i. for(auto temp : v)访问
    ii. 下标访问:for(int i = 0; i < v.size; ++i)
    iii. 迭代器访问:for(auto it = v.begin(); it != v.end(); ++it)

  • 如何使用迭代器遍历一个vector v.begin()函数返回第一个元素的迭代器;v.end()函数返回尾(后)迭代器。 当且仅当vector为空时,begin和end返回的都是尾迭代器。所以通常用v.begin() == v.end()来判断vector是否为空。

    #include <iostream>
    #include <vector>
    using namespace std;
    int main(){
        vector<int> v = {1, 2, 3, 4};
        for(auto it = v.begin(); it != v.end(); ++it){
            *it = 100;  // *it返回的是当前迭代器所指的元素的引用,所以可以直接修改当前元素值
            cout << *it << endl;  //返回当前迭代器所指的元素的引用
        }
        return 0;
    }
    
  • 迭代器常见的运算符
    i. 星号it:返回迭代器it所指对象的引用 和指针类似,迭代器也可以使用解引用符号*返回当前迭代器所指示对象的引用,所以可以直接用( *it)修改当前元素的内容; 执行解引用的迭代器必须合法,即通常要先判断vector是否为空,即当前迭代器是否执行某个具体的元素;
    ii. it->item:解引用it,并且返回这个元素名为item的成员,等价于(*it).item,因为解引用返回的是迭代器所指的对象引用,如果这个对象有成员,那么就可以直接访问到;

    // 假如it是一个vector<string>中的对象
    (*it).empty(); // 解引用后返回迭代器所指的对象引用,再用点运算符执行string的内置函数.empty()判断这个对象是否为空
    it->empty();   // 两句话等价  (*it).empty()  <==>  it->empty()
    *it.empty();   // 错  这样会执行it.empty()  只要*先解引用才能返回迭代器所指的对象引用
    

    iii. ++it:返回下一个迭代器
    iv. --it: 返回上一个迭代器
    v. it1 == it2:两个迭代器指向同一个元素返回True,否则返回False
    vi. it1 != it2:两个迭代器指向不同元素返回Ture,否则返回False

  • 练习1:用迭代器把string对象中第一个字符改为大写

    #include <iostream>
    #include <vector>
    #include <string>
    using namespace std;
    int main(){
        string s = "hello world";
        if(s.begin() != s.end()){
            auto it = s.begin();
            *it = toupper(*it);  // "Hello world"
        } 
        return 0;
    }
    
  • 练习2:用迭代器把string对象中第一个单词字符全部改为大写

    #include <iostream>
    #include <vector>
    #include <string>
    using namespace std;
    int main(){
        string s = "hello world";
        for(auto it = s.begin(); it != s.end() && !isspace(*it); ++it){
            *it = toupper(*it);  // "HELLO world"
        }
        return 0;
    }
    

    注意:这里for循环里面为什么要使用!=,而不用<
    为了泛型编程,因为标准库中所有的容器都定义了==和!=,但是大多数没定义<

  • 迭代器类型:iteration 和 const_iteration
    i. iteration:可读可写,如果vector或者string对象不是一个常量
    ii. const_iteration:只读,如果vector或者string对象是一个常量
    iii. 返回的begin和end迭代器类型由vector或者string对象类型决定

    vector<int> v;
    const vector<int> cv;
    auto it1 = v.begin();  // vector<int>::iteration类型
    auto it2 = cv.end();   // vector<int>::const_iteration类型
    

    iv. c++11引入两个新函数:cbegin和cend,无论vector或者string是否是常量,都会返回const_iteration

    vector<int> v;
    const vector<int> v;
    auto it1 = v.cbegin();  // vector<int>::const_iteration类型
    auto it2 = v.cend();    // vector<int>::const_iteration类型
    

2. 迭代器运算

  • 两个迭代器运算的前提条件:两个迭代器必须处于同一个vector/string中

  • 迭代器和一个整数相加/相减:返回的依然是一个迭代器,新迭代器相比原迭代器向右/向左移动了若干个位置。注意不能超过vector或者string对象的左右范围,如果超过了程序会出问题。

  • 两个迭代器没有相加操作;
    i. iter + n
    ii. iter - n
    iii. iter += n 迭代器自身向前移动n个位置
    iv. iter -= n 迭代器自身向后移动n个位置

    vector<string> vec = {"he", "ll", "o", "wo", "rl", "d"};
    auto it1 = vec.cbegin();   // he  
    auto it2 = it1 + 2;       // o  
    auto it3 = it2 - 1;      // ll
    auto it4 = vec.cbegin() + vec.size() / 2;  // wo 找到vector的中间迭代器
    auto it5 = it1 - 2;     // 超出vector右边界  程序出问题
    auto it6 = it1 + 6;     // 超出vector左边界 程序出问题
    
  • 判断两个迭代器的位置关系 >、>=、<、<=、== 以 (it1>it2) 为例,如果it1在it2位置右边,返回True=1,否则返回False=0,特殊的当it1和it2是同一个迭代器,it1==it2

    vector<string> vec = {"he", "ll", "o", "wo", "rl", "d"};
    auto it1 = vec.cbegin();   // he
    auto it2 = it1 + 2;       // o
    cout << (it2 > it1) << endl;  // 1
    

    访问vector前一半迭代器:

    vector<string> vec = {"he", "ll", "o", "wo", "rl", "d"};
        auto it = vec.cbegin();
        auto mid = it + vec.size() / 2;
        while(it < mid){
            // 访问前一半迭代器  he ll o
            cout << *it << endl;
            ++it;
        }
    
  • 判断两个迭代器之前相差几个迭代器:it1-it2,返回类型difference_type,是一个带符号的整数。当it1在it2右边,返回值大于0,当it2在it1左边,返回值小于0。

    vector<string> vec = {"he", "ll", "o", "wo", "rl", "d"};
    auto it = vec.cbegin();
    auto mid = it + vec.size() / 2;
    cout << mid - it << endl;  // 3
    cout << it - mid << endl;  // -3
    

五、 数组
数组也是存放相同对象一个容器。 与vector不同的是:数组的大小确定不变,不能任意向数组中添加元素。
1. 定义和初始化数组

  • 形式:类型名 数组名[数组维度]

  • 类型名必须明确,不能用auto,也不允许是引用类型(引用不是对象)

    auto arr3[10];      // 必须明确数组类型
    int &arr2[10];    // 错 引用不是对象
    
  • 默认初始化 数组维度必须明确,必须是常量表达式

    unsigned a = 42;  // 非常量表达式
    const unsigned b = 42;  // 常量表达式
    int arr[10];     // 定义一个含有10个整数的数组  默认初始化全为0
    string sarr1[a];  // 错  不是常量表达式b
    string sarr[b];  // 定义一个含有b个string的数组  默认初始化全为""
    int *parr[3];  // 含义3个整型指针的数组(数组中每个元素都是整型指针) 
    int c[4*7-14];  // 合法
    
  • 列表初始化
    i. 允许忽略维度,如果维度没有明确,编译器会根据列表初始值的个数推测出来;
    ii. 如果明确了维度,列表初始值的个数不能超过维度,超过会报错;如果维度比列表初始值个数更多,剩下的部分执行默认初始值

    int a[] = {1, 2, 3};   // 3个对象 {1,2,3}
    int b[5] = {1, 2, 3};  // 5个对象 {1,2,3,0,0}
    char e[] = {'a', 'b', 'c'};  // 3个对象 {'a', 'b', 'c'}
    string c[4] = {"hello", "world"};  // 4个对象 {"hello", "world", "", ""}
    int d[2] = {1, 2, 3};  // 报错
    
  • 字符串字面值初始化:字符数组独有的初始化方式,字符串字面值尾部还有一个空字符,所以以字符串字面值对字符数组初始化时,后面还有一个空字符;

    char f[] = "hello";  // 6个对象 {'h','e','l','l','o',''}
    char g[5] = "world";  // 报错 没有空间放空字符
    
  • 不允许将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值;

    int a[3] = {1, 2, 3};
    int b[3];
    int c[3] = a;  // 错  不能拷贝初始化
    b = a;         // 错  不能赋值
    
  • 数组、指针和引用(从数组名字开始从内向外阅读)
    i. int *p[10]; 定义了一个含义10个对象的数组,每个对象都是一个int型的指针;
    ii. int &p[10] = …; 非法 因为数组存储是对象,引用不是对象;
    iii. int (*p)[10] = &arr; 定义了一个指针,这个指针指向一个int数组,这个数组包含10个元素;
    iv. int (&p)[10] = arr; 定义了一个引用,这个引用指向一个int数组,这个数组包含10个元素;

2. 遍历数组:访问和修改数组元素

  • 范围for

    int a[3] = {1, 2, 3};
    for(auto temp : a){  // 遍历
        cout << temp << endl;// 访问数组
    }
    for(auto &temp : a){
        temp = 4;  // 修改数组
    }
    
  • 下标:下标访问要注意下标溢出问题

    int a[3] = {1, 2, 3};
    for(int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){  // 遍历
        a[i] = 4;  // 修改数组元素
        cout << a[i] << endl;  // 访问数组
    }
    
  • 使用指向数组元素的指针,可以做到类似迭代器的功能

    string str[] = {"hello", "world", "hk"};
    string *e = &str[3];
    for(string *s = str; s != e; ++s){
        cout << *s << endl;
    }
    

    但是这种做法很不安全,所以通常用标准库函数begin和end模拟迭代器进行遍历

    string str[] = {"hello", "world", "hk"};
    string *b = begin(str);  // 指向str的首元素
    string *e = end(str);    // 指向str的尾元素的下一个元素
    while(b != e){
        cout << *b << endl;
        ++b;
    }
    

3. 指针和数组

  • 在c++中,指针和数组有着非常紧密的关系,使用数组的时候,编译器一般会把数组转换为指针。

  • 对数组执行下标运算可以得到数组中指定位置的对象,再对这个对象使用取地址符就可以得到指向该元素的指针。

    string str[] = {"hello", "world", "hk"};
    string *p1 = &str[1];  // string类型的指针p1,指向数组中第1个对象string  "world"
    
  • 数组还有一个特殊的性质:使用数组的对象时,通常都是指向该数组的首元素的指针。

    string str[] = {"hello", "world", "hk"};
    string *p2 = str;  // string类型的指针p2,指向数组的首元素str[0]
    auto p3 = str;     // p3是string类型的指针
    cout << *p3 << endl;  // "hello"
    
  • 指向数组元素的指针也可以当做迭代器使用,对数组进行遍历;

  • 指向数组元素的指针拥有和迭代器一样的相关运算:解引用、递增、递减、与整数相加、两个指针相减等;

    int a[] = {0, 2, 4, 6, 8};
    int *p1 = &a[0];
    int *p2 = &a[2];
    cout << *p1 << endl;         // 解引用
    cout << *(++p1) << endl;     // 自增  相当于指针向后移动一位 再*解引用
    cout << (p1 < p2) << endl;   // 比较  < > <= >=  ==      
    cout << *(p1 + 1) << endl;   // 与整数相加  相当于指针向后移动1位  再*解引用
    cout << (*p1) + 1 << endl;   // 注意两者区别  此时是先解引用  再把解引用的结果加1
    cout << p1 - p2 << endl;     // 两个指针相减  必须是同一个数组的指针  两个指针相差多少个元素
    
  • 下标和指针
    i. a[2]等价于*(p+2);
    ii. 只要指针指向的是数组中的元素,那么就可以对这个指针执行下标运算

    int a[] = {0, 2, 4, 6, 8};
    int *p1 = &a[3];  // p1执行idx=3的元素6
    int p2 = p1[1];   // p1[1]等价于*(p+1)=8
    int p3 = p1[-2];  // p1[-2]等价于*(p-2)=2
    
  • 可以发现,string和vector的下标只能是无符号类型,但是数组的下标可以是负数;

六、多维数组

  • 多维数组定义 int a[3][4]; 表示定义了一个3行4列的多维数组

  • 多维数组初始化
    i. 大括号嵌套形式 + 一个大括号形式

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

    ii. 和一维数组一样,没有初始化的元素会执行默认初始化

    int a[3][4] = {0};  // {{0,0,0,0}, {0,0,0,0},{0,0,0,0}}
    int b[3][4] = {{0,1,2}, {4,5,6},{8,9,10}};   // {{0,1,2,0}, {4,5,6,0},{8,9,10,0}}
    int c[3][4] = {0,1,2,3,4,5,6,7,8};           // {{0,1,2,3}, {4,5,6,7},{8,0,0,0}}
    int d[3][4] = {{1}, {2}, {3}};  // {{1,0,0,0},{2,0,0,0},{3,0,0,0}}
    int e[3][4] = {1,2,3};        // {{1,2,3,0},{0,0,0,0},{0,0,0,0}}
    
  • 多维数组的遍历/修改
    下标方式
    i. 当表达式下标运算符数量和多维数组的维度一样多时,该表达式返回的是多维数组对应位置的元素;但是当表达式中的运算符个数少于多维数组的维度,那么表达式返回的对应索引位置处的内层数组;

    int a[2][3];
    a[0] // 表示第一行 内置4个int型元素
    a[0][0] // 表示第一行第一列这个元素
    

    ii. 遍历和修改两层数组

    constexpr size_t row = 3, col = 4;
        int a[row][col];    // {{0,1,2,3},{4,5,6,7},{8,9,10,11}}
        for(size_t i = 0; i != row; ++i){
            for(size_t j = 0; j != col; ++j){
                a[i][j] = i * col + j;          // 修改数组
                cout << a[i][j] << endl;        // 遍历数组
            }
        }
    

    范围for语句方式
    i. 遍历两层数组 使用范围for语句遍历多维数组时,除了最内层的循环以外,其他所有层循环都必须使用引用形式。 主要是为了避免数组被自动转换成指针。因为如果第一层实际上每一个row都是一个大小为4的数组,而如果不用&的话,编译器会自动将这个数组转换为指向这个数组内首元素的指针,那么得到的row就是一个int*类型了,导致最终第二层循环异常。

    int a[3][4] = {0};
        int cnt = 0;
        for(auto &row : a){
            for(auto col : row){
                cout << col << endl;
            }
        }
    

    ii. 修改两层数组 因为要改变数组元素,所以多维数组中所有层都必须采样引用的形式&遍历行和列。

    int a[3][4];  // {{0,1,2,3},{4,5,6,7},{8,9,10,11}}
        int cnt = 0;
        for(auto &row : a){
            for(auto &col : row){
                col = cnt;
                ++cnt;
            }
        }
    
  • 指针和多维数组
    i. 当程序使用多维数组的名字时,会自动将其转换为指向第一个内层数组的指针。

    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    // int *p = a;     // 报错 使用多维数组时  会自动将其转换为指向第一个内层数组的指针 即int *[4]类型
    int (*p)[4] = a;   // 对 p指针指向第一行  *p=[1,2,3,4]
    p = &a[2];         // a[2] = (a+2)  p指针指向第三行  *p = [9,10,11,12]
    

    注意上面 int *p[4]代表一个整型指针的数组,而int ( *p)[4]表示指向含义4个整数的数组。
    ii. 使用指针也可以进行遍历

    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    // 指针p指向a的第一行  ++p将指针指向第二行  直到4行遍历完为止
    // p是一个指针  指向一个int[4]的数组
    // q是一个指针  指向一个int整数
    for(auto p = a; p != a + 3; ++p){
        for(auto q = *p; q != *p + 4; ++ q){
            cout << *q << endl;
        }
    }
    

    iii. 标准库函数begin()和end()遍历多维数组更安全

    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    for(auto p = begin(a); p != end(a); ++p){
        for(auto q = begin(*p); q != end(*p); ++p){
            cout << *q << endl;
        }
    }
    

    iv. 但是有时候为了让代码看出去可读性更强,会使用类型别名来声明

    using int_array = int[4];
    // 类型别名 直接说清楚p是一个指针 指向一个int[4]数组  代码看上去更清晰
    for(int_array *p = begin(a); p != end(a); ++p){
        for(int *q = begin(*p); q != end(*p); ++q){
            cout << *q << endl;
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值