第三章-C++字符串、向量和数组

本文深入探讨C++中的字符串(string)与动态数组(vector)的使用方法,包括初始化、操作及迭代器使用等内容,并对比了C++数组与C风格字符串的特点。

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

string类:可变长度的字符串。vector :可变长度的集合。

3.1 命名空间的 using 声明

在程序中显示声明将使用XXX命名空间里的某个值,那么下面的程序会自动识别到这个声明

#include <iostream>
int main()
{
       {
              using std::cout;//声明下面使用的cout都是命名空间std中定义的;
              cout << "hello world"; //cout 已经用using声明了,所以不用显示声明cout是谁的;
              cout << std::endl; //endl 没有用using声明,每一次用到都要在程序中用std::显示声明;
       }
		//============================
       {
              using namespace std;//用namespace std声明下面用到的都是std库里的函数
              cout << "good morning" << endl; //无须显示声明
       }
       getchar();
}

使用用户自定义的命名空间

#include <iostream>
namespace sxd {
       int i = 10;
}
int main()
{
          using namespace sxd;
          i = 100;
          std::cout << "i = " << i << std::endl;
          getchar();
}

头文件不应包含using声明,因为如果头文件中包含了using声明,那么引用它的文件也会拷贝这个using声明,则每个引用该头文件的文件都会有这个声明

3.2.1 定义和初始化string对象

string类对象默认初始化为空串;
string s(10,'c');//s内容为“cccccccccc”
用“=”初始化string类型都是拷贝初始化,比如 string s1 = "nihao";
如果不使用“=”,就是直接初始化:例如string s1("hello");

3.2.2 string对象上的操作

  • 读写操作 cin >> s; cout << s;//cin将对象读入s中,遇到空白停止
  • 使用getline()函数读取一整行字符串:getline(cin, s);//其中cin是输入流,s是字符串,getline函数遇到换行符结束,存取到s中没有换行符
  • string的empty()操作,s.empty(); 判断string类s是否为空,返回布尔类型。if(s.empty()) ……
  • string的size()操作,s.size();返回string类s的字符串长度。
  • string::size_type类型,string的size()函数返回的就是size_type类型,是无符号的,而且与机器无关,体现了标准库与机器无关的特性。
  • string类型支持+,+=,操作,是字符串相接,(连加时注意不能使两个字面值常量相加)。

3.2.3 处理string对象中的字符

  • 头文件C++与C的区别:C++包含所有C语言的库,只是形式有点改变C:#include <name.h> C++: #include 将后缀.h改为前缀c。
  • C++中一般使用cname的头文件,因为这样库中的名字总能在命名空间std中找到。
  • 范围for(declaration:expression) statement; 语句。定义declaration对象去迭代访问对象expression,执行statement语句。如下:
/*
*下面的程序使用范围for语句、decltype确定类型函数、以及auto类型
*实现寻找一个字符串中有多少个标点符号
*/
#include <iostream>
#include <string>
using namespace std;
int main()
{
       string s("hello world!!!");
       decltype(s.size()) punct_num = 0;
       for (auto c : s)
              if (ispunct(c))
                     punct_num++;
       cout << "s 中的标点符号有:" << punct_num << "个。" << endl;
       getchar();
       return 0;
}
  • 如果想要使用范围for语句改变对象中的值,则循环变量declaration必须是引用类型。如下:
#include <iostream>
#include <string>
using namespace std;
int main()
{
        /*
       *下面的程序使用范围for语句
       *修改字符串里面的值
       */
       {
              string s("Hello world!!!");
              decltype(s.size()) punct_num = 0;
              for (char &c : s)
                     c = toupper(c); //c是一个引用,所以改变的并不是c,而是c代表的值
              cout << s << endl;
              getchar();
              return 0;
       }
}
  • string类型支持下标运算符[],如string s(“hello”); s[0] = h;且可以通过访问下标赋值,下标不能越界,下标越界将引发未知结果。
  • 养成习惯:用下标访问string时,先判空。if(!s.empty()) ……;

3.3 标准库类型vector(模板)

vector——对象的集合(容器),其中的所有的对象的类型都相同;
vector中每个对象都有对应的索引;
vector使用的头文件——#include <vector>  

3.3.1 定义和初始化vector对象

(1)默认初始化vector对象,vector对象中不含任何元素
vector v1;//v1为空
(2)列表初始化:vector除了提供这些初始化方式外,提供了一种新的初始化方式:列表初始化:
vector s = {“hello”, “world”, “nihao”};

  • 初始化的几种不同方式除以下三种外都可以互相等价:
    • 使用拷贝初始化时(即使用=时),只能提供一个初始值;
    • 类内初始化:只能使用拷贝初始化(“=”),或使用花括号形式初始化(“{}”);
    • 提供的是初始化元素列表时,只能将元素放在花括号里面初始化;
class ChuShiHuaClass
{
private:
   int a{10};
   int b = 10;
    int c(10); //错误,类内初始化只能用以上两种
public:
   void Geta();
   ChuShiHuaClass();
};

(3)创建指定数量的元素

vector<string> s(10,"hello");  //创建10和hello字符串。
vector<string> s1{10,"hello"}; //创建10和hello字符串,{}在此同();
        值初始化:可以只提供vector对象容纳的元素个数而省略元素初始值,这样的话,C++库会自动根据vector的元素类型值初始化里面的元素,如int型的初始化为0。
vector<int> v1(10);//自动创建10个值为0的元素。
vector<string> v2{10};//自动创建10个值为0的string元素。
        限制1:有些类不支持默认初始化(定义对象时必须提供初始值),则无法完成初始化工作。
        限制2:如果只提供了元素的个数,没有提供初始值,那么不能进行赋值初始化,只能进行直接初始化(构造初始化)
// vector<string> v1 = {10};  //错误,只提供了元素个数,没有提供初始值,不能进行赋值初始化
vector<string> v1{10};    //这样就可以了,因为是直接初始化的

(4)判断是列表初始化还是元素数量
因为,使用{}可以代替()进行构造初始化,但{}又单独可以列表初始化,所以{}里面第一个值既有可能是列表值,又有可能是元素的个数
分辨的方法:编译时先判断{}里面的值是否能直接列表初始化vector,如果不能,则再判断是否可以进行创建指定数量元素的初始化

vector<int> v1{10,20};//列表初始化
vector<string> v0{10,"hello"}; //指定数量的元素初始化
vector<int> v2{10};//1个元素,值为10;
vector<int> v3{10, 1};//2个元素,分别为10,1;
//vector<string> v4("hello"); //错误,不能使用字面值常量构建vector对象(构造初始化)

3.3.2 向vector 对象中添加元素——push_back()函数

push_back()函数负责将一个值当成vector对象的元素压(push)到vector对象的尾端。

vector<int> v1;
int i;
while(cin >> i) {
    v1.push_back(i);
}

C++里,vector对象的个数能够高效的增长
使用范围for()语句遍历vector对象的时候,不应该有添加、删除对象的操作:因为范围for()语句不应该改变其所遍历序列的大小。

3.3.3 其他vector操作

  • .empty()函数:判空函数,空则返回true,不空则返回false
  • .size()函数:返回元素的个数 返回值类型:vector::size_type(而不是vector::size_type)
  • [n] 下标运算符,返回第n个位置上的元素的引用
  • = 拷贝替换
  • ==判等,所有元素都相同且个数相同才相等

注意:

  1. 不能以下标形式添加对象,只能以。push_back()函数添加,下标运算符只能操作已存在的元素
  2. 习惯:在使用下标运算符之前必须判空
  3. 尽量使用范围for()语句可以确保下标合法
  4. 通过下标去访问不存在的元素将引发错误,这种错误不会被编译器发现,在运行时产生不可预知的值,所谓的缓冲区溢出(buffer overflow)就是指这样的错误

3.4迭代器

所有标准库容器都可以使用迭代器,但只有少数几种支持下标运算符[]

3.4.1迭使用迭代器

  • 如何获取迭代器:有迭代器的类型能够返回迭代器成员,这些类型都能返回 .begin() 和 .end() 成员
  • end成员返回尾后迭代器——尾元素的下一位置的迭代器
  • 尾后迭代器本不存在,没有实际含义,表示已经处理完成了所有元素
  • 当容器为空的时候,.begin 和.end成员都指向尾后迭代器
    迭代器是一个指针
    迭代器运算符
    *iter 解引用,返回iter所指向的元素的引用
    iter->mem 解引用,iter所指向的成员,等价于(*iter).mem(一定要加括号,否则先运行 . 操作)
    ++iter iter指向下一个元素
    –iter iter指向上一个元素
    == / != 判等或不等,只有两个iter指向同一个元素的地址时候才等于,否则不等(所有的迭代器都支持判等,但不一定支持 > < >= <= 等不等式运算符,所以尽量用判等 左判断条件)
    使用迭代器和使用下标运算符一样之前要先判空
if(v.begin() != v.end())

.end()成员指向尾后迭代器,不能解引用,也不能递增,所以迭代器指向下一元素时先判断不为尾后迭代器

iter != v.end();

关键概念:泛型编程——是一种编程风格,比如这一段代码到哪里都能用,通用性很强——这一段代码不依赖于具体的数据类型,什么数据类型都会定义这种操作

  • 具体来说:养成习惯:尽量使用 != 或 == 在循环中进行判断iter迭代器,而不要使用 <= 、>= 等判断,因为绝大多数类都会定义 判等操作,很少有类会定义比较操作
    所以:一段循环代码,vector容器 不同类型(如int、string 之类) 都会支持 == !=运算,但是换种类型可能就不支持 <= 、>= 不等式操作了。
  • 运算符优先级
    在这里插入图片描述

迭代器类型
像容器这样有迭代器的类都定义了迭代器自己的类型:iterator 和 const_iterator类型(常迭代器,迭代器本身可以跳转,但是不能改变其指向的元素)

    vector<int> v1;
    vector<int>::iterator it1;//不常用
    vector<int>::const_iterator it2; //不常用
    auto it3 = v1.begin(); //一般常用auto让编译器自己去定义iterator类型

auto自动定义迭代器类型:

vector<int> v1;
    auto it3 = v1.begin(); //那么it3的类型是:vector<int>::iterator
    const vector<int> v2;
    auto it4 = v2.begin(); //那么it4的类型是:vector<int>::const_iterator
  • 使用 .cbegin() 和 .cend() 成员函数(cbegin()意思是:const_begin(),同理cend())
    我们想用iter迭代器访问vector容器元素但不想改变元素的值(vector本身不是const的),那么就可以使用.cbegin() 和 .cend() 来赋值定义给auto对象,让迭代器编程const_iterator类型的;
auto it5 = v1.cbegin();
vector<int>::const_iterator it6 = v1.begin();  //it5 和 it6的作用相同

改变容器的大小(添加或删除元素)都会让迭代器失效,这同范围for()一样,不能用于改变容器的容量

3.4.2 迭代器运算

iter + n;
iter - n;
iter += n;
iter -= n;
iter1 - iter2;
<、 > 、<=、 >= 比较迭代器之间的前后关系。
注意:没有 iter1 + iter2 不存在这种方法 ; 迭代器中也没有定义这种方法

//++++++++iterator 二分查找++++++++++++++
    {
        vector<int> v{0,1,2,4,5,8,9,12,13,16,19};
        auto beg = v.begin(), end = v.end();
        auto mid = beg + (end -beg) / 2;
        int i;
        cout << "input the number which needs find"<< endl;
        cin >> i;  
        while(mid != end && *mid != i)
        {
            if(i < *mid)
                end = mid;
            if(i > *mid)
                beg = mid + 1;
            mid =beg + (end -beg) / 2;
        }
        if(mid ==  end)
            cout << "no such a number " << endl;
        else
            cout << "the num in v's position is " << mid - v.cbegin() << endl;
    }

3.5 数组

3.5.1 定义和初始化内置数组

数组是一种复合类型:a[d], 数组的名称a 和数组的维度d(d>0) 在编译时应该都是已知的,所以维度d是一个常量表达式

string s;
//以下三种都是常量表达式,都可以作数组维度
constexpr int i = 32;
const int j = 32;
const int m = sizeof(s.size());
int b[i];
int a[j];
int c[m];
cout << "m = " << m << endl;
cout << "c[m]'s size = " << sizeof(c) << endl;
  • 在函数体内定义数组和定义内置类型一样,会默认初始化为未知的值。
  • 不允许使用auto定义数组!,定义数组必须指定数组类型
  • 数组里装的是对象,不能装引用。
  • 指定数量初始化未填充完成,后面自动补0
            int a[10] = { 0,1,2,3 };
            for (auto i : a) 
                cout << i << " ";//程序输出结果为 0 1 2 3 0 0 0 0 0 0

字符数组特殊,特殊处在于可以用字符串赋值:

char a[6] = "hell0";//hello占6个字,后面有一个'\0'

数组不允许整体直接拷贝或复制

char a[] = “hello”;
char b[] = a;//错误
b = a;//错误
  • 复杂的数组声明(类型饰符是从右向左依次绑定的)
  • 数组能存放元素(是个容器,能装元素),所以能装指针,引用不是对象,所以不能存放引用
//数组能存放对象(是个容器,能装元素),所以能装指针
//数组ptr里面存放了10个指针,不能是野指针,所以习惯性初始化!!
int *ptr[10] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL,  NULL, NULL, NULL };
int a[10] = { 00,01,02,03,04,05,0xD,0xC,0xA,0xB };
for (int i = 0; i != 10; i++)
{
      ptr[i] = &a[i];
      cout << "*ptr[" << i << "] = " << *ptr[i] << endl;
}
//int &refs[10]; //错误,引用不是对象,不能存放
  • 数组本身又是对象,可以被指针指向或者被引用。
//数组本身又是对象,可以被指针指向,或者被引用
int arr[10] = { '0','1','2', '3', '4', '5', '6', '7', '8','9'  };
int(*ptr)[10] = &arr; //定义了一个指针,该指针指向一个数组,这个数组必须是拥有10个元素的int数组
int(&refs)[10] = arr; //定义了一个引用,该引用指向一个数组,这个数组必须是拥有10个元素的int数组

//类型修饰符满足从右向左依次绑定的规律
//int *ptr[10] ,首先绑定的是ptr[10],说明ptr是一个数组,然后再将数组ptr[10]绑定*即 *(ptr[10]),是包含指针的数组
//int(*ptr)[10],首先绑定的是(*ptr),说明ptr是指针,然后(*ptr)  与[10]绑定,[10]作为修饰,说明这一个指针指向的                       //数组必须只含有[10]个元素。

类型修饰符满足从由内向外(括号内外)、从右向左依次绑定的规律

3.5.2 访问数组元素

使用或定义数组下标时,可以使用size_t类型:(是一个与机器无关的无符号整型,且被设计的足够大),需要包含头文件cstddef(C++)或stddef.h©
注意下标的值不要溢出,判空,判满,范围for( )

3.5.3 指针和数组

C++中,用数组的时候编译器一般会将他转换成指针,很多用到数组名字的地方,编译器会自动将数组名转换成指向首地址的指针:
数组名就是一个指针(这个指针指向数组首元素地址)

       int a[] = { 1,2,3,4 };
       cout << *a << endl;
       getchar();

使用数组名作为auto类型的初始值时,定义的变量将是一个指针

int a[] = { 1,2,3,4 };
auto b(a); // 也就是 auto b(&a[0]);
b = &a[3];
cout << "*b = " << *b << endl;

使用decltype(数组名)的时,返回的是数组,

int a[] = { 1,2,3,4 };
decltype(a)b;
b[0] = 0;
b[1] = 1;
b[2] = 2;
b[3] = 3;
//相当于 int b[4];

数组的指针拥有迭代器的所有功能,其中,迭代器拥有尾后迭代器,数组指针也可以指向数组的尾后地址:(尾后指针不能执行解引用和递增操作)

int a[5] = { 0,1,2,3,4 };
int *p = &a[5];

标准库函数的begin和end:

  1. 上述的尾后指针是通过计算得到的,很容易出错,一般使用标准库函数的begin()和end()函数(为了模仿容器里的这两个函数)
  2. 需要头文件#include
  //标准库函数begin()和end()
  //需要用到 #include <iterator>
  {
         int a[5] = { 0,1,2,3,4 };
         int *pbeg = begin(a);
         int *pend = end(a);
         for (auto p = pbeg; pbeg != pend && p != pend; p++)
                cout << *p << " ";
         cout << endl;
  }
  • 指向同一个数组元素的两个指针可以相减,得到 ptrdiff_t 类型的有符号类型
  • 两个指针指向同一个数组的元素(或尾后)就可以进行比较,(两个指针若指向不相关的对象就不能比较)
  • (知识点没什么用):空指针和两个指向对象不是数组的指针也可以相减和比较:空指针只能和空指针比较,空指针只能+0 或 -0;指向对象的指针必须指向同一个对象或该对象的下一个位置。
  • 指向数组的指针也可以执行下标运算!!,指针的下标还可以为负数!与标准库的类(string、vector)不同。
       //指向数组的指针也可以执行下标运算
       {
              int a[5] = { 0,1,2,3,4 };
              int *p = a + 2;
              cout << "p[1] = " << p[1] << endl;
              cout << "p[-1] = " << p[-1] << endl;
       }

3.5.4 C风格字符串

尽量不要用C风格的字符串

3.5.5 与旧码的接口

  • 如果C风格的字符数组以‘\0’结束:可以用其对C++ 的 string类型 初始化或者赋值
char a[] = "hello world";
string s(a);
cout << s << endl;
char b[] = "你好";
s = b;
cout << s << endl;
  • 如果C风格的字符数组以‘\0’结束:可以用与C++中的字符串做加法运算
    ( + 是加法运算符, += 是复合赋值运算符)
//衔接上面的代码
s += a;
cout << s << endl;
  • C++ string 返回字符数组 c_str()函数,返回string对象的字符数组形式,但返回的是const char * 类型的,必须要用const char * 类型指向。
const char *p = s.c_str();
cout << "p = " << p << endl;
  • 将c_str()函数返回的常量字符数组拷贝到普通字符数组
//将string类型的s.c_str() 拷贝到C风格字符数组
       {
#define STRLEN 128
              string s = "hello world";
              const char *p = s.c_str();
              int str[STRLEN] = {};
              int i = 0;
              for ( i = 0; *p != '\0'; p++, i++)
                     str[i] = *p;
              str[i] = '\0';
              cout << "str = ";
              for (int i = 0;str[i] != '\0' ;i++)
                     cout << (char)str[i];
              cout << endl;
       }
  • C风格字符串初始化vector对象,可以用前后两个指针初始化vector对象。
 //用 字符数组初始化vector对象
{
       int a[] = { 0,1,2,3,4,5,6,7,8,9 };
       vector<int> v{ begin(a),end(a) };//指针不必是头指针或尾后指针,可以是指向字符数组的任意位置指针
       for (auto p : v)
              cout << p;
}
  • 将整型vector对象赋值到整型数组中
//将整型vector对象赋值到整型数组中
{
#define MAX 128
      vector<int> v{ 0,1,2,3,4,5,6 };
      int a[MAX] = {};
      int i = 0;
      for (auto p : v)
             a[i] = p;
}

3.6 多维数组

多维数组就是数组的数组,数组里面的每一个值仍然是数组。

       //多维数组的初始化
       {
              int a[2][3] = {
                     {0,1,2},
                     {3,4,5}
              };
              int b[2][3] = { 0,1,2,3,4,5 };
              //这两种方式是一样的,b的方法容易混淆
       }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值