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

● std::cin 表示从标准输入中读取内容, 此处使用作用域操作符(::)的含义是: 编译器应从操作符左侧名字所示的作用域中寻找右侧的那个名字, std::cin 的意识就是 要使用命名空间std 中的名字 cin。

● 每个using 声明只引入命名空间中的一个成员。

● 注意 : 在头文件中不应该使用using 声明, 这是因为头文件的内容会拷贝到所有引用它的文件中去, 如果头文件里有某个 using 声明, 那么每个使用了该头文件的文件就都会有这个声明。 对于某些程序来说, 由于不经意间包含了一些名字, 反而可能产生名字冲突。


这里写图片描述


这里写图片描述


● 在以这样 cin>>s 的形式给string对象读取操作时, string 对象会自动忽略 开头的空白(即空格符、换行符、制表符等), 并从第一个真正的字符开始读起, 直到遇见下一处空白为止

● 注意 : string对象的此类操作也是返回运算符左侧的运算对象作为其结果。


使用 getline 读取一整行


● getline 函数的参数是一个输入流和一个 string 对象, 函数从给定的输入流中读入内容, 直到遇到换行符为止(注意 : 换行符也被读进来了),然后把所读的内容存入到那个string 对象中去(注意 : 此时不存换行符), getline 只要一遇到换行符就结束读取操作并返回结果, 哪怕输入的一开始就是换行符也是如此。

注意 : 如果输入真的一开始就是换行符, 那么所得到的结果就为 空 string

● empty函数根据string 对象是否为空返回一个对应的布尔值, 为空就为真,不空就为假。

● size() 函数返回string 对象的长度 (即string对象中字符的个数),


string::size_type 类型


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

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

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 对象

● 如果两个string 对象在某些对应的位置上不一致, 则 string 对象比较的结果, 其实就是string对象中第一对 相异字符比较的结果


字面值 和 String 对象相加


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

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

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

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

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

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


处理 string 对象中的字符


这里写图片描述

这里写图片描述


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


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

for(auto 标识符 :已存在的string变量)

**说明:该“ 标识符” 将被用于访问序列中的基础元素, 每次迭代,“ 标识符”部分的变量会被初始化为下一个元素的值 **

注意: 用atuo关键字让编译器来决定“标识符”的类型, 这里“标识符”的类型是 char。

● 可以使用范围for语句和 ispunct() 函数 来统计 string对象中标点符号的个数:

string s("Hello World!!!");

	decltype(s.size())punct_cnt = 0;

	for (auto c : s)
	{
		if (ispunct(c))
		{
			++punct_cnt;
		}
		
	}
	cout << "在" << s << "中有" << punct_cnt << "个标点符号!" << endl;

只处理一部分字符


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

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

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

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

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

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

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

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

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

这里写图片描述


标准库类型 vector


● 因为 vector “容纳着”其他对象, 所以它也常被称作 容器

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

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

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

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

Vector 是一个类模板。不是一种数据类型。 Vector是一种数据类型。

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


定义和初始化 vector 对象


1):vector vi; //创建一个int类型的vector容器,但这个容器是空的。默认构造函数vi为空

2):vector vi2(10); //创建一个int类型的vector容器,该容器暂有10个int元素,每个元素被赋初始值0。

3):vector vi3(5,99); //创建一个int类型的vector容器,该容器暂有5个int元素,每个元素被赋值99。

4):vector vi4(vi2); //创建一个int类型vector容器,该容器拷贝了vi2,既该容器具有10个值为0的元素。

(6)vector inventory(10, “hi!”); // 声明了一个大小为10的向量,全部元素都初始化为“hi!”

vector v4(v3.begin(), v3.end()); // v4是与v3相同的容器(完全复制)
(7)
Vector v1; //默认构造函数v1为空
Vector v2(v1);或者v2=v1 //v2是v1的一个副本
Vector v3(n,i);//v3包含n个值为i的元素
Vector v4(n); //v4含有n个值为0的元素

(8) vector b(a.begin(), a.begin()+3) ; //将a向量中从第0个到第2个(共3个)作为向量b的初始值
除此之外, 还可以直接使用数组来初始化向量:

    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 v2(v1.begin(), v1.end()); //v2是v1的一个副本, 若v1.size()>v2.size()则赋值后v2.size()被扩充为v1.size()。


向Vector对象中添加元素


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

● 如果要在vector对象中创建很多个数字,但可能这样数字也不相同, 用列表初始化、直接初始化、拷贝初始化、都是不可取得。

那么更好的处理方法是先创建一个空vector, 然后在运行时再利用vector的成员函数push_back 向其中添加元素, 添加到vector对象的尾部, 例如:

当把一个vector对象拷贝到另一个vector对象时, 注意 两个vector对象的类型必须相同。


列表初始化Vector 对象


● 在大多数情况下这些初始化方式可以相互等价地使用, 不过也并非一直如此, 有两种情况例外:

(1) 使用拷贝初始化(=)时 只能提供一个初始值

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

还有一个特殊的是 : 如果提供的是 初始元素值的列表, 则只能把初始值都放在花括号里进行初始化, 而不能放在圆括号里

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

值初始化!


● 通常情况下,可以只提供vector对象容纳的元素数量而略去初始值。 那么此时库会创建一个值初始化的元素初值, 并把它赋给容器中的所有元素。 这个初值由vector对象中元素的类型决定。

vector<int> ivec(10); vector<string> svec(10);

● ** 注意 : 对这种初始化的方式有两个特殊的限制:**

(1) 有些类要求必须明确地提供初始值, 如果vector对象中的元素的类型不支持默认初始化, 我们就必须提供初始的元素值。 对这种类型的对象来说, 只提供元素的数量而不设定初始值无法完成初始化工作。

(2) 如果只提供了元素的数量而没有设定初始值, 只能使用直接初始化。

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

列表初始值还是元素数量


● 初始化的真实含义依赖于传递初始值时用的是花括号还是圆括号, 例如:

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 }; // v4 有2个元素,值分别是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对象:

string word;
vector<string> text;  //  空 vector对象

while(cin>>word)
{
  text.push_back(word);  把 Word 添加到text后面
}

这里写图片描述


● 注意:要确保所写的循环正确无误, 特别是在循环有可能改变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对象相等当且仅当它们所包含的元素个数相同, 而且对应位置的元素值也相同。

关系运算符依照字典顺序进行比较: 如果两个vector 对象的容量不同, 但是在相同位置上的元素值都一样, 则元素较少的vector对象小于元素较多的vector对象; 若元素的值有区别, 则vector对象的大小关系由第一对相异的元素值的大小关系决定。

● vector 对象的下标也是从0开始计起, 下标的类型是相应的 size——type 类型。 只要vector对象不是一个常量, 就能向下标运算符返回的元素赋值。


不能用下标形式添加元素


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

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

● 注意 : 只能对确知已存在的元素执行下标操作,试图用下标的形式去访问一个不存在的元素将引发错误, 不过这种错误不会被编译器发现, 而是在运行时产生一个不可预知的值。

注意 : 确保下标合法的一种有效手段就是尽可能使用范围for语句


迭代器介绍


● 所有标准库容器都可以使用迭代器, 但是其中只有少数几种才同时支持下标运算符。 严格来说, string 对象不属于容器类型, 但是string 支持很多与容器类型类似的操作。 vector支持下标运算符, 这点和string一样; string支持迭代器, 这也和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 的成员,其中 begin 成员负责返回指向第一个元素的迭代器。 end 成员 返回容器中最后一个元素之后的迭代器, 也就说该迭代器指示的是容器中一个本不存在的“尾后元素”。 这样的迭代器没什么实际含义, 仅是个标记, 标识我们已经处理完了容器中的所有元素。

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

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

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

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


迭代器运算符


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

这里写图片描述

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


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


注意 : 迭代器使用 递增(++) 运算符来从一个元素移动到下一个元素。

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

这里写图片描述


迭代器类型


● 一般来说我们也不知道(其实无须知道) 迭代器的精确类型。 而实际上, 那些拥有迭代器的标准库类型使用 iterationconst_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 新标准引入了两个新函数 , 分别是 cbegincend

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 是带符号类型的。


定义和初始化内置数组


● 如果不清楚元素的确切个数, 请使用vector

● 数组是一种复合类型,数组的个数(维度)必须大于0, 数组中元素个数也属于数组类型的一部分, 编译的时候个数应该是已知的, 也就是说,个数必须是一个常量表达式。

注意:和内置类型的变量一样, 如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值。

注意 : 定义数组的时候必须指定数组的类型, 不允许使用auto关键字由初始值的列表推断数组类型。 另外和vector一样, 数组的元素应为对象, 因此不存在引用的数组。

char a1[]={'C','+','+'}; //列表初始化,没有空字符,维度是3
char a2[]={'C','+','+',‘\0’}; //列表初始化,含有显示的空字符,维度是4
char a3[]=“C++”;//自动添加表示字符串结束的空字符,  维度为4
const char a4[6]="Daniel"; // 错误, 没有空间可存放空字符, 维度为7

注意 : 当使用这种方式时, 一定要注意字符串字面值的结尾处还有一个空字符, 这个空字符也会像字符串的其他字符一样被拷贝到字符数组中去

注意 : 不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值。


理解复杂的数组声明


● 因为数组本身就是对象, 所以允许定义数组的指针及数组的引用,在这几种情况中, 定义存放指针的数组比较简单和直接, 但是定义数组的指针或数组的引用就稍微复杂一点了,


int *ptrs[10]; // ptrs 是含有10个整型指针的数组

int &refs[10] =/* ? */; //错误:不存在引用的数组

int(*Parray)[10] = &arr; //Parray指向一个含有10个整数的数组

int(&arrRef)[10] = arr; // arrRef 引用一个含有10个整数的数组

● 注意:要想理解数组声明的含义, 最好的办法是从数组的名字开始按照由内向外的顺序阅读


访问数组元素


● 数组的元素也能使用范围for语句或下标运算符来访问

● 在使用数组下标的时候, 通常将其定义为size_t 类型,size_t 是一种机器相关的无符号类型, 它被设计得足够大以便能表示内存中任意对象的大小, 在 cstdef 头文件中定义了 size_t类型。

● 当需要遍历数组的所有元素时, 最好的办法也是使用范围for语句,列如:

unsigned scores[11] = {};
	unsigned grade;
	cout << "请输入11个成绩:\n";
	while (cin >> grade)
	{
		if (grade <= 100)
		{
			++scores[grade / 10];
		}
	}
	cout << "输出结果:\n";
	for (auto i : scores) //对于scores中每个计数值
	{
		cout << i << endl;  // 输出当前的计数值
	}
	cout << endl;
	```

因为维度(个数)是数组类型的一部分, 所以系统知道数组scores中有多少个元素,使用范围 for语句可以减轻人为控制遍历过程的负担。

●    与vector 和 string 一样, 数组的下标是否在合理范围之内由程序员负责检查, 所谓合理就是说下标应该大于0等于0而且小于数组的大小。


----------
**指针和数组**


----------


●   通常情况下, 使用取地址符来获取指向某个对象的指针,**取地址符可以用于任何对象, 对数组元素使用取地址符就能得到指向该元素的指针**

●  **注意 : 数组有一个特性:  在很多用到数组名字的地方, 编译器都会自动地将其替换为一个指向数组首元素的指针。**

●  在一些情况下数组的操作实际上是指针的操作,这一结论有很多隐含的意识。  其中一层意识是 当使用数组名作为auto 变量的初始值时, 推断得到的类型是指针  而非数组。例如:


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

auto ia2(ia); // ia2a 是一个整型指针,指向ia 的第一个元素

ia2 = 42;  // 错误: ia2 是一个指针,不能用int值给指针赋值


----------

**●  注意  :   当使用 `decltype`  关键字时,上述的转换不会发生,  即数组名不会被当成指针。**

●  **注意 : 不能用整型指针给数组赋值**


----------
**指针也是迭代器**


----------

●    使用指针也能遍历数组中的元素,  当然, 这样做的前提是先得获取到**指向数组第一个元素的指针和 指向数组尾元素的下一位置的指针。  这个指针唯一用处就是用来初始化,  它不指向任何具体的元素, 不能对尾后指针执行解引用或者递增操作。**


----------
**标准库函数 begin 和 end **


----------


●   C++11 新标准引入了两个名为 `begin`和`end` 的函数,  这两个函数与容器中的两个同名成员功能类型, 不过数组不是类类型, 因此这两个函数不是成员函数,  **正确的使用形式是将数组作为它们的参数**。

●  这两个函数都定义在 iterator 头文件中。

● 一个指针如果指向了某种内置类型数组的尾元素的“下一个位置”,则其具备与`vector`的`end` 函数返回的迭代器类似的功能, **特别注意:  尾后指针不能执行解引用和递增操作。**


----------
**指针运算**


----------

●  指向数组元素的指针(数组指针)可以执行的运算有:  解引用、递增递减、 和整数相加减、两个指针相减。

●  从一个指针加上(减去)某整数值, **结果仍是指针**。

●  给指针加上一个整数,  得到的新指针仍需指向同一数组的其他元素, **或者指向同一数组的尾元素的下一个位置。**

**●  注意 :  两个指针相减,  参与运算的两个指针必须指向同一个数组中的元素,两个指针的运算结果值是两个指针指向的地址位置之间的数据个数。  由此看出, 两指针相减实质上也是地址计算, 它执行的运算不是两指针持有的地址值相减。  注意 :  两个指针相减的结果值不是地址量, 而是一个整数。**  

**●   两个指针相减的结果 的类型是一种名为 `ptrdiff_t` 的标准库类型, 和 `size_t` 一样, `ptrdiff_t`  也是一种定义在`cstddef` 头文件中的机器相关的类型。  因为差值可能为负值,  所以`ptrdiff_t` 是一种带符号类型。**

**●  只要两个指针指向同一个数组的元素, 或者指向该数组的尾元素的下一位置, 就能利用关系运算符对其进行比较。 **

**两指针之间的关系运算表示它们指向的地址位置之间的关系,  两指针相等的概念是两指针指向同一个位置。** 

**如果两个指针分别指向不相关的对象,是没有意义的, 则不能比较它们。 指针与 非0整数之间的关系运算也是没有意义的。  但是指针可以和零之间进行等于或不等于的关系运算(根据指针与零的比较, 可以判断指针是否为空指针。)**

**●  如果p是空指针, 允许给p加上或减去一个值为0 的整型常量表达式, 两个空指针也允许彼此相减, 结果当然是0**


----------
**下标和指针**


----------


**●  对数组执行下标运算其实是对指向数组元素的指针执行下标运算,  只要指针指向的是数组中的元素(或者数组中尾元素的下一位置),都可以执行下标运算。**


int ia[] = { 0,2,4,6,8 };
int *p = &ia[2];   // p  指向索引为2的元素
cout << *p << endl;

int j = p[1];   // p[1] 等价于*(p+1),就是 ia[3] 表示的那个元素
cout << j << endl;

int k = p[-2];  //p[-2]  是  ia[0]表示的那个元素。
cout << k << endl;


----------



●   虽然标准库类型 string 和 vector 也能执行下标运算, 但是数组与它们相比还是有所不同。  

**标准库类型限定使用的下标必须是无符号类型,  而内置的下标运算无此要求, 它是有符号类型,内置的下标运算符可以处理负值, 当然, 结果地址必须指向原来的指针所指同一数组中的元素(或是同一数组尾元素的下一位置)。**



----------
**C  风格字符串**


----------
![这里写图片描述](https://img-blog.csdn.net/20170724170845292?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzQ1MzY1NTE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)


●  C  风格字符串不是一种类型,  而是为了表达和使用字符创而形成的一种约定俗成的写法。  按此习惯书写的字符串 放在 字符数组并以 **空字符结束**,  以空字符结束的意思是在字符串最后一个字符后面紧跟着一个空字符(‘\0’),  一般利用指针来操作这些字符串。

![这里写图片描述](https://img-blog.csdn.net/20170724171451957?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzQ1MzY1NTE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

●  **注意 : 传入此类函数的指针必须指向以空字符作为结束的数组。否则将运到错误。**


----------
**比较字符串**


----------

●  在比较标准库string对象的时候, 用的是普通的关系运算符和相等性运算符,**如果把这些运算符用在两个C 风格字符串上,  实际比较的将是指针而非字符串本身。**

const char  ca1[] = "A string example";
 const char ca2[] = "A different string";
if ( s1 < s2 )   ///未定义的:  试图比较两个无关的地址

**记住:  当使用数组的时候其实真正用的是指向数组首元素的指针。**

**上面的if条件实际上比较的是两个  `const char* `的值,  这两个指针指向的并非同一个对象, 所以得到未定义的结果。**


----------


**与旧代码的接口**


----------

●    任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代:

(1)  允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值。

(2)  在 string 对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是);  在 string 对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象。

上述的性质反过来就不成立了, 如果程序的某处需要一个C 风格字符串,  无法直接用 string对象来代替它。  列如,  不能用 string对象直接初始化指向字符的指针。  为了完成这个功能,  string 专门提供了一个名为 c_str 的成员函数。

string s("Hello World");
char  *str=s ; //错误,  不能用 string对象初始化 char *  
const char *str=s.c_str();  // 正确

**说明:  c_str 函数返回值是一个 C 风格的字符串。  也就说, 函数的返回结果是一个指针,  该指针是指向一个以空字符结束的字符数组, 而这个数组所存的数据恰好与那个 string 对象中的数据是一样的,  结果指针的类型是 const char*,  从而确保我们不会改变字符数组的内容**

**注意 :  无法保证 c_str  函数返回的数组一直有效,  事实上, 如果后续的操作改变了 s的 值就可能让之前返回的数组失去效用。**

**建议 :  如果 执行完 c_str () 函数后程序想一直都能使用其返回的数组, 最好将该数组重新拷贝一份。**


----------
**使用数组初始化vector对象**


----------

●   不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用 vector 对象初始化数组。  相反的,  允许使用数组来初始化 vector 对象。  要实现这一目的,**只需指明要拷贝区域的首元素地址和尾后地址就可以了。**

int arr[]={0,1,2,3,4,5};

vector<int> ivec ((begin(arr), end(arr));  // ivec 有6个元素, 分别是 arr中对应元素的副本


●    用于初始化 vector对象的值也可能仅是数组的一部分

vector<int> subVec(arr+1,arr+4);

这条初始化语句用3个元素创建了对象 subVec,  分数是 arr[1] arr[2] arr[3].


![这里写图片描述](https://img-blog.csdn.net/20170725163142960?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzQ1MzY1NTE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值