第3章 字符串、向量和数组

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34536551/article/details/82913355

string::size_type 类型


● size() 函数返回的是一个 string::size_type 类型的值, 在具体使用的时候, 通过作用域操作符来表明 size_type 是在类 string 中定义的。

● string::size_type 类型 是一个无符号类型的值, 而且能足够存放任何string 对象的大小, 所有用于存放string类 的 size 函数返回值的变量, 都应该是 string::size_type 类型 的。

●  允许编译器通过 auto 或者 decltype 来推断变量的类型:

auto len = line.size() // len的类型是string::size_type 类型

● 由于 size函数返回的是一个无符号整型数, 注意 : 如果在表达式中混用了带符号数和无符号数将可能产生意想不到的结果,

假设n是一个具有负值的int, 则表达式 s. zize() < n的判断结果几乎肯定是 true, 这是因为负值n 会自动地转换成一个比较大的无符号值。

建议 : 如果一条表达式中已经有了 size () 函数就不要再使用 int了, 这样可以避免混用 int 和 unsigned 可能带来的问题。


字面值 和 String 对象相加


● 因为标准库允许把字符字面值的字符串字面值转换成 string 对象, 所以在需要string 对象的地方就可以使用这两种字面值来替代。

● 注意 : 当把 string 对象和字符字面值及字符串字面值混在一条语句中使用时, 必须确保每个加法运算符 (+) 的两侧的运算对象至少有一个是 string。

string s3 = s1 + "," + s2 + '\n'; // 正确

string s6 = (s1 + ",")+"world";

其中表达式 s1+","的结果是一个string对象, 它同时作为第二个加法运算符的左侧运算对象。 

 string s7 = (“hello”+“,”)+s2 ;// 错误:不能把字面值直接相加

说明:上面代码错误,不能把字面值直接相加。

● 注意:c++语言中的字符串字面值并不是标准库类型 string 的对象, 切记, 字符串字面值与string是不同的类型 。


处理每个字符? 使用基于范围的for语句


● 如果想对string对象中的每个字符做点儿什么操作, 目前最好的方法是使用C++11 新标准提供的一种语句: 范围for 语句, 这种语句访问给定序列中的每个元素, 并对序列中的每个值执行某种操作, 其语法形式是:

for( 范围变量的声明 : 表达式 )
{
  // statement
}

范围变量声明 含有一个类型名称和一个标识符(例如: int item )、该变量将被用于访问序列中的基础元素。 表达式 部分:表示需要迭代遍历的 array 对象,用于表示一个序列。  每次迭代,“范围变量声明” 部分的变量都会被初始化为 array中、下一个元素的值。

●   注意: 范围变量声明中的类型必须与 array 对象的元索类型相一致 。

●  注意:基于范围的 for 语句可以和大多数 C++ 标准库中预制的数据结构 ( 通常称为容器)一起使用,包括类 array 和 vector。
 


适用范围for语句改变字符串中的字符


●   如果想要改变 string 对象中字符的值, 必须把循环变量定义成引用类型。当使用引用作为循环控制变量时,这个变量实际上被依次绑定到了序列的每个元素上。  使用这个引用,我们就能改变它绑定的字符。


只处理一部分字符?


● 要想访问string对象中的单个字符有两种方式: 一种是 使用下标, 另一种是使用迭代器

● 下标运算符 [ ] 接收的输入参数是 string::sizee_type 类型的值,这个参数表示要访问的字符的位置; 返回值是该位置上字符的引用。

● 注意 : s[s.size()-1] 是最后一个字符。

● 注意 : string 对象的下标值必须大于等于0而小于 s.size(), 使用超过此范围的下标将引发不可预知的结果, 以此推断, 使用下标访问空string 也会引发不可预知的结果。

● 注意:下标的值称作“索引”或者 “ 下标”, 任何表达式只要它的值是一个整型值就能作为索引, 不过, 如果某个索引是带符号类型的值将被自动转换成由 string::size_type 表达的无符号类型的值。

● 注意: 不管什么时候只要对string对象使用了下标, 都要确认在那个位置上确实有值。

if(!s.empty())  // 确保确实有字符需要输出
 cout<<s[0]<<endl;

● 只要字符串不是常量, 就能为下标运算符返回的字符赋新值。

 string s("some string"); 
if(!s.empty())
{
  s[0] = toupper[0];  // 为s 的第一个字符赋一个新值
}

 


使用下标迭代


string s("some string"); 

// 依次处理s中的字符直至我们处理完全部字符或者遇到一个空白
for(decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index)
{
  s[index] = toupper(s[index]); //将当前字符改写成大写形式
}


输出结果:  SOME string

● 注意: 逻辑与运算符(&&) , C++语言规定 只有当左侧运算对象为真时才会检查右侧对象的情况。


使用下标执行随机访问


●  其实,也能通过计算得到某个下标值,然后直接获取对应位置的字符,并不是每次都得从前往后依次访问。


标准库类型 vector


● vector 是一个类模板, 模板本身不是类或函数, 相反可以将模板看作为编译器生成类或函数编写的一份说明。 编译器根据模板创建类或函数的过程称为实例化, 当使用模板时, 需要指出编译器应把类或函数实例化成何种类型。

vector<int> ivec; // ivec 保存int类型的对象 
vector<vector<string>>file; // 该向量的元素是 vector 对象

● “ <>” 表示的是vector内所存对象的类型。

● 注意 : vector 是模板而非类型, 由 vector 生成的类型必须包含 vector 中元素的类型, 例如: vector<int> .

● Vector 是一个类模板。不是一种数据类型。 Vector<int> 是一种数据类型。

● 注意:vector 能容纳绝大多数类型的对象作为其元素,但是因为引用不是对象, 所以不存在包含引用的vector。 除此之外, 其他大多数(非引用)内置类型和类类型都可以构成vector对象, 甚至组成vector的元素也可以是 vector。


定义和初始化 Vector 对象


● vector v4(v3.begin(), v3.end()); // v4是与v3相同的容器(完全复制)

 vector b(a.begin(), a.begin()+3) ; //将a向量中从第0个到第2个(共3个)作为向量b的初始值

vector v2(v1.begin(), v1.end()); //v2是v1的一个副本, 若v1.size()>v2.size()则赋值后v2.size()被扩充为v1.size()。


除此之外, 还可以直接使用数组来初始化向量:

int n[] = {1, 2, 3, 4, 5} ;
vector<int> a(n, n+5) ; //将数组n的前5个元素作为向量a的初值,末尾指针都是指结束元素的下一个元素,
vector<int> a(&n[1], &n[4]) ; //将n[1] - n[4]范围内的元素作为向量a的初值

● 注意: 可以把一个vector对象的值用来初始化另一个新的vector,但是两个vector 对象的类型必须相同。


列表初始化vector对象


●  有两种例外的情况: 第一, 使用拷贝初始化时( = ) 只能提供一个初始值、或者是字符串类型的、放在花括号里进行列表初始化。

第二, 如果提供的是一个类内初始值,则只能使用拷贝初始化或使用花括号的形式初始化。 

第三种特殊的要求是: 如果提供的是初始元素值的列表,则只能把初始值都放在花括号里进行列表初始化, 而不能放在圆括号里初始化:

vector <string> v1{"a","an","the"}; //正确 列表初始化
vector<string> v2("a","an","the"); //错误

值初始化


vector<int> ivec(10) ; //10个元素,每个都初始化为0
vector<string> svec(10); //都是空串

注意: 对这种初始化的方式有两个特殊的限制:第一, 有些类要求必须明确地提供初始值, 如果vector对象中的元素的类型不支持默认初始化,我们就必须提供初始的元素值。  对这种类型的对象来说, 只提供元素的数量而不设定初始值无法完成工作.

第二,如果只提供了元素的数量而没有设定初始值,只能使用直接初始化:

vector<int> vi = 10; //错误。必须使用直接初始化的形式指定vector的大小 

 


列表初始值还是元素数量


●   在某些情况下,初始化的真实含义依赖于传递初始值时用的是花括号还是圆括号。 例如:

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

vector<int> v3(10,1); //有10个元素,每个都是1
vector<int> v4{10,1}; // 有两个元素,分别10、1

●  如果用的是圆括号,可以说提供的值是用来构造vector对象的。 如果用的是花括号,可以表述为想列表初始化该vector对象。初始化过程会尽可能地把花括号内的值当成是元素初始值的列表来处理。 只有在无法执行列表初始化时才会考虑其他初始化方式。

● 如果初始化时使用了花括号的形式但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造对象了。

vector<string> v5{ "hi" }; // 一个元素hi,列表初始化
vector<string> v6("hi" ); // 错误: 不能使用字符串字面值构造vector对象

vector<string>v7{ 10 };  // 错误,v7 有10个默认初始化的元素
vector<string> v8{10,"hi"}; //错误, v8有10个值为 “hi” 的元素 

注意: 要想列表初始化vector对象,花括号里的值必须与元素类型相同。显然不能用 int 初始化 string 对象, 所以 v7 和 v8 提供的值不能作为元素的初始值。  确认无法执行列表初始化后,编译器会尝试用默认值初始化vector 对象。


向vector对象中添加元素


● 对vector对象来说, 直接初始化的方式适用于三种情况: 初始值已知且数量较少、初始值是另一个vector对象的副本、所有元素的初始值都一样。

vector<int> v2;  //空vector对象
for(int i =0;i!=100;++i)
{
  v2.push_back(i); // 依次把整数值放到 v2 尾端
  // 循环结束后, 有100个元素, 值从 0到99
  }

●  注意: 在定义vector 对象的时候没有必要设定其大小,更好的方法是先定义一个空的vector对象,在运行时向其添加元素。

● 注意 : 如果循环体内部包含向vector对象添加元素的语句, 则不能使用范围for循环。

● 注意: 范围for语句 体内不应该改变其所遍历序列的大小。


其他vector 操作


● 访问 vector 对象中元素的方法和访问string对象中字符的方法差不多, 也是通过元素在vector对象中的位置,列如可以使用范围for语句处理vector对象中的所有元素:

vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
	for (auto &i : v)  //对于v中的每个元素(注意: i是个引用)
	{
		i *= i;  //求元素值得平方
	}
	for (auto g : v)  //对于 v中的每个元素
	{
		cout << g << " ";  //输出该元素
	}

● 要使用 size_type, 需首先指定它是由哪种类型定义的。 vector对象的类型总是包含着元素的类型:

vector <int>::size_type //  正确
vector::size_type //  错误

● 注意: vector 对象的下标也是从0开始计起, 下标的类型是相应的 size_type 类型。 只要vector对象不是一个常量, 就能向下标运算符返回的元素赋值。 也能通过计算得到 vector 内 对象的索引,然后直接获取索引位置上的元素。


不能用下标添加元素


●  注意:  vector对象(以及string 对象)的下标运算符可用于访问已存在的元素,而不能添加元素。

vector<int> ivec; 
for(decltype(ivec.size()) ix = 0; ix != 10; ++ix)

{
  ivec[ix] = ix; //错误:ivec 不包含任何元素
}

此时ivec 不包含任何元素,也不能用下标访问任何元素。 正确的方法是使用 push_back 给 vector 添加元素。


迭代器介绍


● 迭代器的声明语法为:

容器类型 <包含的对象类型> :: iteration 标识符 ;

● 那么何为迭代器 : 迭代器是标识容器中某个特定元素的值。 那么给定一个迭代器, 可以访问元素的值; 给定正确类型的迭代器, 就可以修改其值。 迭代器还可以通过常见的算术运算符在元素之间移动。

迭代器有有效和无效之分,这一点和指针差不多。 有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一个位置; 其他所有情况都属于无效。

● 可以将迭代器想象成贴在容器中某个特定元素上的便签。 迭代器虽然不是元素本身,但它是引用元素的一种方式。


使用迭代器


vector <string>:: const_iterator iter;

是一个包含string对象的vector创建了名为iteration 的常量迭代器。 除了不能用来修改其引用的元素外, 常量迭代器与常规迭代器几乎一样。 由常量迭代器引用的元素必须保持不变。 可以将常量迭代器想象成提供了只读访问权限。 然而, 迭代器自身还是可以改变的, 即如果需要, 可以让iter 在 向量之中移动。 然而, 无法通过 iter 修改任何元素的值。

● vector的成员函数 end() 返回的迭代器指向向量中最后一个元素之后——而不是最后一个元素。 因此,无法从end() 返回的迭代器获得元素值。 这样的迭代器没什么实际的含义,仅是个标记而已。 表示我们已经处理完了容器中的所有元素。

 vector<string> inventory; 
inventory.insert(inventory.begin(), "crossbow");

说明 : 此种形式的 insert ()成员函数将新元素插入至vector中给定迭代器引用的元素之前, 此种形式的insert() 需要两个实参 : 第一个为 一个迭代器, 第二个为需要插入的元素。, 在本例子中, 程序将 “crossbow” 插入至inventory中第一个元素之前, 因此,所有其他元素将下移一位。 此种形式的insert()成员函数返回一个迭代器, 它引用新插入的元素。

● 注意 : 对vector调用insert()成员函数会使所有引用了插入点之后的元素的迭代器失效, 因为所有插入点之后的元素都下移了一位。

vector<string> inventory; 
inventory.erase((inventory.begin() + 2));

说明 : 此种形式的erase ()成员函数 可以从vector中移除一个元素。 该形式的 erase () 接受一个实参 : 引用需要移除元素的迭代器, 在本例中, 传递的实参((inventory.begin() + 2)) 等于引用inventory中第三个元素的迭代器。

移除元素之后,所有随后的元素都上移一位。 该形式的 erase ()成员函数返回一个迭代器, 它引用移除的元素之后的那个元素。

● 注意 : 对vector调用erase()成员函数会使所有引用了插入点之后的元素的迭代器失效, 因为所有插入点之后的元素都上移了一位。

● 注意 : 使用 push_back()可能使引用vector中所有的迭代器失效,因为所引用的元素改变了位置。


 ● 注意: 特殊情况下如果容器为空, 则 begin 和 end 返回的是同一个迭代器, 那么返回的都是 尾后迭代器。

 一般来说,我们并不清楚(不在意) 迭代器准确的类型到底是什么, 可以使用auto 关键字来判断迭代器的返回类型, 如:

auto b=v.begin(),e=v.end();

这两个变量的类型也就是begin 和 end 的返回值类型。

 


迭代器运算符


● 使用 == 和 != 来比较两个合法的迭代器是否相等, 如果两个迭代器指向的元素相同或者都是同一个容器中的尾后迭代器,则它们相等, 否则就说这两个迭代器不相等。

● 注意 :执行解引用的迭代器必须合法并确实指示着某个元素。 试图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为。


将迭代器从一个元素移动到另外一个元素


● 注意 : 因为 end 返回的迭代器并不实际指示某个元素, 所以不能对其进行递增或解引用的操作。


迭代器类型


●   一般来说我们也不知道(其实无须知道) 迭代器的精确类型。 而实际上, 那些拥有迭代器的标准库类型使用 iteration 和 const_iterator, 来表示迭代器的类型。

vector<int>::iterator it; // it 能读写vector<int> 的元素
string::iterator it2; //it2 能读写string对象中的字符

vector<int>::const_iterator it3; // it3 只能读元素,不能写元素
string::const_iterator it4; //it4 只能读元素,不能写元素

● 注意 : 如果vector 对象或 string 对象是一个常量 , 只能使用 const_iterator如果vector 对象或 string 对象不是常量, 那么既能使用 iterator 也能使用 const_iterator 。


begin 和  end 运算符


● 注意 : begin 和 end 返回的具体类型 由对象 是否是 常量决定 ,如果对象是常量, begin 和 end 返回 const_iterator; 如果对象不是常量, 返回 iterator。

vector<int> v;
const vector<int> cv;

auto it1 = v.begin(); // it1的类型是 vector<int>::iterator
auto it2 = cv.begin(); //it2 的类型是 vector<int>::const_iterator

● 注意 : 如果对象 只需读操作而无须写操作的话最好使用常量类型,(比如const_iterator)。 为了便于专门 得到 const_iterator 类型的返回值, C++11 新标准引入了两个新函数 , 分别是 cbegin和 cend

auto it3=v.cbegin(); // it3 的类型 是 vector<int>::const_iterator

类似于 begin 和 end , 这两个函数分别 返回 指示容器中 第一个元素 或 最后元素的下一个位置的迭代器。 有所不同的是, 不论 vector 对象 (或string 对象)本身是否是常量, 返回值都是 const_iterator 。


某些对vector对象的操作会使迭代器失效


● 注意: 不能在范围for循环中向vector对象添加元素, 任何一种可能改变vector对象容量的操作, 比如 push_back 都会使该 vector 对象的迭代器失效。

● 注意 : 但凡是使用了迭代器的循环体, 都不要向迭代器所属的容器添加元素。


迭代器运算


● 所有的标准库容器都有支持递增运算符的迭代器。  也能用 “ == ” 和 “  != ” 对任意标准库类型的两个有效迭代器进行比较。

● 可以令迭代器和一个整数值相加或相减,其返回值是向前或向后移动了若干个位置的迭代器。 执行这样的操作时, 结果迭代器或者指示原vector对象(或string)内的一个元素, 或者指示原vector对象(或string) 尾元素的下一个位置。

● 还能使用关系运算符来进行两个迭代器的比较, 参与比较的两个迭代器必须合法而且指向的是同一个容器的元素(或者尾元素的下一个位置)。

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

string 和 vector 都定义了 difference_type, 因为这个距离可正可负, 所以difference_type 是带符号类型的。

猜你喜欢

转载自blog.csdn.net/qq_34536551/article/details/82913355
今日推荐