C++ Primer学习练习——第三章 字符串、向量和数组


引言

介绍了两种最重要的标准库类型:stringvectorstring表示可变长的字符序列vector存放的是某种给定类型对象的可变长序列。本章还将介绍内置数组类型,和其他内置类型一样,数组的实现与硬件密切相关。因此相较于标准库类型string和vector,数组在灵活性上稍显不足。

1、using声明

  • 使用某个命名空间:例如 using std::cin表示使用命名空间std中的名字cin
  • 头文件中不应该包含using声明。这样使用了该头文件的源码也会使用这个声明,会带来风险。

练习3.1

使用恰当的using 声明重做 1.4.1节和2.6.2节的练习。

解:

1.4.1

#include <iostream>

using std::cin;
using std::cout;
using std::endl;

static void test03_01()
{
	int sum = 0;
	for (int val = 1; val <= 10; ++val) sum += val;

	cout << "Sum of 1 to 10 inclusive is " << sum << endl;
}

2.6.2 类似

2、string

  • 标准库类型string表示可变长的字符序列。
  • #include <string>,然后 using std::string;
  • string对象:注意,不同于字符串字面值。

定义和初始化string对象

初始化string对象的方式:

方式解释
string s1默认初始化,s1是个空字符串
string s2(s1)s2s1的副本
string s2 = s1等价于s2(s1)s2s1的副本
string s3("value")s3是字面值“value”的副本,除了字面值最后的那个空字符外
string s3 = "value"等价于s3("value")s3是字面值"value"的副本
string s4(n, 'c')s4初始化为由连续n个字符c组成的串
  • 拷贝初始化(copy initialization):使用等号=将一个已有的对象拷贝到正在创建的对象。
  • 直接初始化(direct initialization):通过括号给对象赋值。

练习3.2

编写一段程序从标准输入中一次读入一行,然后修改该程序使其一次读入一个词。

解:

一次读入一行:

#include <iostream>
#include <string>

using std::string;
using std::cin;
using std::cout;
using std::endl;
using std::getline;

static void test03_02_1()
{
	string s;
	while (getline(cin,s))  //一次读入一行
	{
		cout << s << endl;
	}
}

一次读入一个词

#include <iostream>
#include <string>

using std::string;
using std::cin;
using std::cout;
using std::endl;
using std::getline;

static void test03_02_2()
{
	string s;
	while (cin >> s)  //一次读入一个词
	{
		cout << s << endl;
	}
}

练习3.3

请说明string类的输入运算符和getline函数分别是如何处理空白字符的。

解:

  • 类似is >> s的读取:string对象会忽略开头的空白并从第一个真正的字符开始,直到遇见下一空白为止。
  • 类似getline(is, s)的读取:string对象会从输入流中读取字符,直到遇见换行符为止。

练习3.4

编写一段程序读取两个字符串,比较其是否相等并输出结果。如果不相等,输出比较大的那个字符串。改写上述程序,比较输入的两个字符串是否等长,如果不等长,输出长度较大的那个字符串。

解:

比较大的

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;

static void test03_04_1()
{
	string str1, str2;
	while (cin >> str1 >> str2)
	{
		if (str1 == str2)
			cout << "The two strings are equal." << endl;
		else
			cout << "The larger string is " << ((str1 > str2) ? str1 : str2);
	}
}

长度大的

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;

static void test03_04_2()
{
	string str1, str2;
	while (cin >> str1 >> str2)
	{
		if (str1.size() == str2.size())
			cout << "The two strings have the same length." << endl;
		else
			cout << "The longer string is " << ((str1.size() > str2.size()) ? str1 : str2) << endl;
	}
}

练习3.5

编写一段程序从标准输入中读入多个字符串并将他们连接起来,输出连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分割开来。

解:

未隔开的:

#include <iostream>
#include <string>

using std::string;
using std::cin;
using std::cout;
using std::endl;

static void test03_05_1()
{
	string result, s;
	while (cin >> s)
	{
		result += s;
	}
	cout << result << endl;
}

隔开的:

#include <iostream>
#include <string>

using std::string;
using std::cin;
using std::cout;
using std::endl;

static void test03_05_2()
{
	string result, s;
	while (cin >> s)
	{
		result += s + " ";
	}
	cout << result << endl;
}

3、string对象上的操作

string的操作:

操作解释
os << ss写到输出流os当中,返回os
is >> sis中读取字符串赋给s,字符串以空白分割,返回is
getline(is, s)is中读取一行赋给s,返回is
s.empty()s为空返回true,否则返回false
s.size()返回s中字符的个数
s[n]返回s中第n个字符的引用,位置n从0计起
s1+s2返回s1s2连接后的结果
s1=s2s2的副本代替s1中原来的字符
s1==s2如果s1s2中所含的字符完全一样,则它们相等;string对象的相等性判断对字母的大小写敏感
s1!=s2同上
<, <=, >, >=利用字符在字典中的顺序进行比较,且对字母的大小写敏感(对第一个不相同的位置进行比较)
  • string io:
    • 执行读操作>>:忽略掉开头的空白(包括空格、换行符和制表符),直到遇到下一处空白为止。
    • getline:读取一整行,包括空白符
  • s.size()返回的时string::size_type类型,记住是一个无符号类型的值,不要和int混用
  • s1+s2使用时,保证至少一侧是string类型。string s1 = "hello" + "world" // 错误,两侧均为字符串字面值
  • 字符串字面值和string是不同的类型。

处理string对象中的字符

  • ctype.h vs. cctype:C++修改了c的标准库,名称为去掉.h,前面加c

    如c++版本为cctype,c版本为ctype.h

    • 尽量使用c++版本的头文件,即cctype

cctype头文件中定义了一组标准函数:

函数解释
isalnum(c)c是字母或数字时为真
isalpha(c)c是字母时为真
iscntrl(c)c是控制字符时为真
isdigit(c)c是数字时为真
isgraph(c)c不是空格但可以打印时为真
islower(c)c是小写字母时为真
isprint(c)c是可打印字符时为真
ispunct(c)c是标点符号时为真
isspace(c)c是空白时为真(空格、横向制表符、纵向制表符、回车符、换行符、进纸符)
isupper(c)c是大写字母时为真
isxdigit(c)c是十六进制数字时为真
tolower(c)c是大写字母,输出对应的小写字母;否则原样输出c
toupper(c)c是小写字母,输出对应的大写字母;否则原样输出c
  • 遍历字符串:使用范围for(range for)语句: for (auto c: str),或者 for (auto &c: str)使用引用直接改变字符串中的字符。 (C++11)
  • str[x],[]输入参数为string::size_type类型,给出int整型也会自动转化为该类型

练习3.6

编写一段程序,使用范围for语句将字符串内所有字符用X代替。

解:

#include <iostream>
#include <string>
#include <cctype>

using std::string;
using std::cin;
using std::cout;
using std::endl;

static void test03_06()
{
	string s = "this is a string";

	for (auto &x : s)
	{
		x = 'X';
	}

	cout << s << endl;
}

练习3.7

就上一题完成的程序而言,如果将循环控制的变量设置为char将发生什么?先估计一下结果,然后实际编程进行验证。

解:

如果设置为char,那么原来的字符串不会发生改变。

练习3.8

分别用while循环和传统for循环重写第一题的程序,你觉得哪种形式更好呢?为什么?

解:

#include <iostream>
#include <string>
#include <cctype>

using std::string;
using std::cin;
using std::cout;
using std::endl;

static void test03_08()
{
	string s = "this is a string";

	decltype(s.size()) i = 0;
	while (i != s.size())
	{
		s[i] = 'X';
		++i;
	}
	cout << s << endl;
	for (i = 0; i != s.size(); ++i)
	{
		s[i] = 'Y';
	}
	cout << s << endl;
}

范围for语句更好,不直接操作索引,更简洁。

练习3.9

下面的程序有何作用?它合法吗?如果不合法?为什么?

string s;
cout << s[0] << endl;

解:

不合法。使用下标访问空字符串是非法的行为。

练习3.10

编写一段程序,读入一个包含标点符号的字符串,将标点符号去除后输出字符串剩余的部分。

解:

#include <iostream>
#include <string>
#include <cctype>

using std::string;
using std::cin;
using std::cout;
using std::endl;

static void test03_10()
{
	string s = "this, is. a :string!";
	string result;

	for (auto x : s)
	{
		if (!ispunct(x))
		{
			result += x;
		}
	}
	
	cout << result << endl;
}

练习3.11

下面的范围for语句合法吗?如果合法,c的类型是什么?

const string s = "Keep out!";
for(auto &c : s){ /* ... */ }

解:

要根据for循环中的代码来看是否合法,c是string 对象中字符的引用,s是常量。因此如果for循环中的代码重新给c赋值就会非法,如果不改变c的值,那么合法。

4、vector

  • vector是一个容器,也是一个类模板;
  • #include <vector> 然后 using std::vector;
  • 容器:包含其他对象。
  • 类模板:本身不是类,但可以实例化instantiation出一个类。 vector是一个模板, vector<int>是一个类型。
  • 通过将类型放在类模板名称后面的尖括号中来指定类型,如vector<int> ivec

定义和初始化vector对象

初始化vector对象的方法

方法解释
vector<T> v1v1是一个空vector,它潜在的元素是T类型的,执行默认初始化
vector<T> v2(v1)v2中包含有v1所有元素的副本
vector<T> v2 = v1等价于v2(v1)v2中包含v1所有元素的副本
vector<T> v3(n, val)v3包含了n个重复的元素,每个元素的值都是val
vector<T> v4(n)v4包含了n个重复地执行了值初始化的对象
vector<T> v5{a, b, c...}v5包含了初始值个数的元素,每个元素被赋予相应的初始值
vector<T> v5={a, b, c...}等价于v5{a, b, c...}
  • 列表初始化: vector<string> v{"a", "an", "the"}; (C++11)

练习3.12

下列vector对象的定义有不正确的吗?如果有,请指出来。对于正确的,描述其执行结果;对于不正确的,说明其错误的原因。

vector<vector<int>> ivec;         // 在C++11当中合法
vector<string> svec = ivec;       // 不合法,类型不一样
vector<string> svec(10, "null");  // 合法

练习3.13

下列的vector对象各包含多少个元素?这些元素的值分别是多少?

vector<int> v1;         // size:0,  no values.
vector<int> v2(10);     // size:10, value:0
vector<int> v3(10, 42); // size:10, value:42
vector<int> v4{ 10 };     // size:1,  value:10
vector<int> v5{ 10, 42 }; // size:2,  value:10, 42
vector<string> v6{ 10 };  // size:10, value:""
vector<string> v7{ 10, "hi" };  // size:10, value:"hi"

向vector对象中添加元素

  • v.push_back(e) 在尾部增加元素。

练习3.14

编写一段程序,用cin读入一组整数并把它们存入一个vector对象。

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>

using std::cin;
using std::cout;
using std::endl;
using std::vector;

static void test03_14()
{
	vector<int> v;
	int i;
	while (cin >> i)
	{
		v.push_back(i);
	}
}

练习3.15

改写上题程序,不过这次读入的是字符串。

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;

static void test03_15()
{
	vector<string> v;
	string i;
	while (cin >> i)
	{
		v.push_back(i);
	}
}

其他vector操作

vector支持的操作:

操作解释
v.emtpy()如果v不含有任何元素,返回真;否则返回假
v.size()返回v中元素的个数
v.push_back(t)v的尾端添加一个值为t的元素
v[n]返回v中第n个位置上元素的引用
v1 = v2v2中的元素拷贝替换v1中的元素
v1 = {a,b,c...}用列表中元素的拷贝替换v1中的元素
v1 == v2v1v2相等当且仅当它们的元素数量相同且对应位置的元素值都相同
v1 != v2同上
<,<=,>, >=以字典顺序进行比较
  • 范围for语句内不应该改变其遍历序列的大小。
  • vector对象(以及string对象)的下标运算符,只能对确知已存在的元素执行下标操作,不能用于添加元素。

练习3.16

编写一段程序,把练习3.13中vector对象的容量和具体内容输出出来

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;

static void test03_16()
{
	vector<int> v1;         // size:0,  no values.
	vector<int> v2(10);     // size:10, value:0
	vector<int> v3(10, 42); // size:10, value:42
	vector<int> v4{ 10 };     // size:1,  value:10
	vector<int> v5{ 10, 42 }; // size:2,  value:10, 42
	vector<string> v6{ 10 };  // size:10, value:""
	vector<string> v7{ 10, "hi" };  // size:10, value:"hi"

	cout << "v1 size :" << v1.size() << endl;
	cout << "v2 size :" << v2.size() << endl;
	cout << "v3 size :" << v3.size() << endl;
	cout << "v4 size :" << v4.size() << endl;
	cout << "v5 size :" << v5.size() << endl;
	cout << "v6 size :" << v6.size() << endl;
	cout << "v7 size :" << v7.size() << endl;

	cout << "v1 content: ";
	for (auto i : v1)
	{
		cout << i << " , ";
	}
	cout << endl;

	cout << "v2 content: ";
	for (auto i : v2)
	{
		cout << i << " , ";
	}
	cout << endl;

	cout << "v3 content: ";
	for (auto i : v3)
	{
		cout << i << " , ";
	}
	cout << endl;

	cout << "v4 content: ";
	for (auto i : v4)
	{
		cout << i << " , ";
	}
	cout << endl;

	cout << "v5 content: ";
	for (auto i : v5)
	{
		cout << i << " , ";
	}
	cout << endl;

	cout << "v6 content: ";
	for (auto i : v6)
	{
		cout << i << " , ";
	}
	cout << endl;

	cout << "v7 content: ";
	for (auto i : v7)
	{
		cout << i << " , ";
	}
	cout << endl;
}

练习3.17

从cin读入一组词并把它们存入一个vector对象,然后设法把所有词都改为大写形式。输出改变后的结果,每个词占一行。

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;

static void test03_17()
{
	vector<string> v;
	string s;

	while (cin >> s)
	{
		v.push_back(s);
	}

	for (auto &str : v)
	{
		for (auto &c : str)
		{
			c = toupper(c);
		}
	}

	for (auto i : v)
	{
		cout << i << endl;
	}
}

练习3.18

下面的程序合法吗?如果不合法,你准备如何修改?

vector<int> ivec;
ivec[0] = 42;

解:

不合法。应改为:

ivec.push_back(42);

练习3.19

如果想定义一个含有10个元素的vector对象,所有元素的值都是42,请例举三种不同的实现方法,哪种方式更好呢?

如下三种:

vector<int> ivec1(10, 42);
vector<int> ivec2{ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42 };
vector<int> ivec3;
for (int i = 0; i < 10; ++i)
	ivec3.push_back(42);

第一种方式最好。

练习3.20

读入一组整数并把他们存入一个vector对象,将每对相邻整数的和输出出来。改写你的程序,这次要求先输出第一个和最后一个元素的和,接着输出第二个和倒数第二个元素的和,以此类推。

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;

static void test03_20()
{
	vector<int> ivec;
	int i;
	while (cin >> i)
	{
		ivec.push_back(i);
	}

	for (int i = 0; i < ivec.size() - 1; ++i)
	{
		cout << ivec[i] + ivec[i + 1] << endl;
	}
	
	//---------------------------------
	cout << "---------------------------------" << endl;
	
	int m = 0;
	int n = ivec.size() - 1;
	while (m < n)
	{
		cout << ivec[m] + ivec[n] << endl;
		++m;
		--n;
	}
}

5、迭代器iterator

  • 所有标准库容器都可以使用迭代器。
  • 类似于指针类型,迭代器也提供了对对象的间接访问。

使用迭代器

  • vector<int>::iterator iter
  • auto b = v.begin();返回指向第一个元素的迭代器。
  • auto e = v.end();返回指向最后一个元素的下一个(哨兵,尾后,one past the end)的迭代器(off the end)。
  • 如果容器为空, begin()end()返回的是同一个迭代器,都是尾后迭代器。
  • 使用解引用符*访问迭代器指向的元素。
  • 养成使用迭代器和!=的习惯(泛型编程)。
  • 容器:可以包含其他对象;但所有的对象必须类型相同。
  • 迭代器(iterator):每种标准容器都有自己的迭代器。C++倾向于用迭代器而不是下标遍历元素。
  • const_iterator:只能读取容器内元素不能改变。
  • 箭头运算符: 解引用 + 成员访问,it->mem等价于 (*it).mem
  • 谨记:但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素

标准容器迭代器的运算符:

运算符解释
*iter返回迭代器iter所指向的元素的引用
iter->mem等价于(*iter).mem
++iteriter指示容器中的下一个元素
--iteriter指示容器中的上一个元素
iter1 == iter2判断两个迭代器是否相等

练习3.21

请使用迭代器重做3.3.3节的第一个练习。

解:

#include <vector>
#include <iterator>
#include <string>
#include <iostream>

using std::vector;
using std::string;
using std::cout;
using std::endl;

static void check_and_print(const vector<int>& vec)
{
	cout << "size: " << vec.size() << "  content: [";
	for (auto it = vec.begin(); it != vec.end(); ++it)
		cout << *it << (it != vec.end() - 1 ? "," : "");
	cout << "]\n" << endl;
}

static void check_and_print(const vector<string>& vec)
{

	cout << "size: " << vec.size() << "  content: [";
	for (auto it = vec.begin(); it != vec.end(); ++it)
		cout << *it << (it != vec.end() - 1 ? "," : "");
	cout << "]\n" << endl;
}

static void test03_21()
{
	vector<int> v1;
	vector<int> v2(10);
	vector<int> v3(10, 42);
	vector<int> v4{ 10 };
	vector<int> v5{ 10, 42 };
	vector<string> v6{ 10 };
	vector<string> v7{ 10, "hi" };

	check_and_print(v1);
	check_and_print(v2);
	check_and_print(v3);
	check_and_print(v4);
	check_and_print(v5);
	check_and_print(v6);
	check_and_print(v7);
}

练习3.23

编写一段程序,创建一个含有10个整数的vector对象,然后使用迭代器将所有元素的值都变成原来的两倍。输出vector对象的内容,检验程序是否正确。

解:

#include <iostream>
#include <vector>

using namespace std;

static void test03_23()
{
	vector<int> v(10, 1);
    for (auto it=v.begin(); it!=v.end(); it++){
        *it *= 2;
    }
    for (auto one : v){
        cout << one <<endl;
    }
}

迭代器运算

vectorstring迭代器支持的运算:

运算符解释
iter + n迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置和原来相比向前移动了若干个元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。
iter - n迭代器减去一个整数仍得到一个迭代器,迭代器指示的新位置比原来向后移动了若干个元素。结果迭代器或者指向容器内的一个元素,或者指示容器尾元素的下一位置。
iter1 += n迭代器加法的复合赋值语句,将iter1加n的结果赋给iter1
iter1 -= n迭代器减法的复合赋值语句,将iter2减n的结果赋给iter1
iter1 - iter2两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动差值个元素后得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置。
>>=<<=迭代器的关系运算符,如果某迭代器指向的容器位置在另一个迭代器所指位置之前,则说明前者小于后者。参与运算的两个迭代器必须指向的是同一个容器中的元素或尾元素的下一位置。
  • difference_type:保证足够大以存储任何两个迭代器对象间的距离,可正可负。

练习3.24

请使用迭代器重做3.3.3节的最后一个练习。

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;

static void test03_24()
{
	vector<int> ivec;
	int i;
	while (cin >> i)
	{
		ivec.push_back(i);
	}

	for (auto it = ivec.begin(); it != ivec.end() - 1; ++it)
	{
		cout << *it + *(it + 1) << endl;
	}

	//---------------------------------
	cout << "---------------------------------" << endl;

	auto it1 = ivec.begin();
	auto it2 = ivec.end() - 1;
	while (it1 < it2)
	{
		cout << *it1 + *it2 << endl;
		++it1;
		--it2;
	}
}

练习3.25

3.3.3节划分分数段的程序是使用下标运算符实现的,请利用迭代器改写该程序实现完全相同的功能。

解:

#include <vector>
#include <iostream>

using std::vector; 
using std::cout; 
using std::cin; 
using std::endl;

static void test03_25()
{
	vector<unsigned> scores(11, 0);
	unsigned grade;
	while (cin >> grade)
	{
		if (grade <= 100)
			++*(scores.begin() + grade / 10);
	}

	for (auto s : scores)
		cout << s << " ";
	cout << endl;
}

练习3.26

在100页的二分搜索程序中,为什么用的是 mid = beg + (end - beg) / 2, 而非 mid = (beg + end) / 2 ; ?

解:

因为两个迭代器相互之间支持的运算只有 - ,而没有 +
但是迭代器和迭代器差值(整数值)之间支持 +

6、数组

  • 相当于vector的低级版,长度固定

定义和初始化内置数组

  • 初始化:char input_buffer[buffer_size];,长度必须是const表达式,或者不写,让编译器自己推断。
  • 数组不允许直接赋值给另一个数组。

练习3.27

假设txt_size是一个无参函数,它的返回值是int。请回答下列哪个定义是非法的,为什么?

unsigned buf_size = 1024;
(a) int ia[buf_size];
(b) int ia[4 * 7 - 14];
(c) int ia[txt_size()];
(d) char st[11] = "fundamental";

解:

  • (a) 非法。维度必须是一个常量表达式。
  • (b) 合法。
  • ( c) 非法。txt_size() 的值必须要到运行时才能得到。
  • (d) 非法。数组的大小应该是12。

练习3.28

下列数组中元素的值是什么?

string sa[10];
int ia[10];
int main() {
	string sa2[10];
	int ia2[10];
}

解:

数组的元素会被默认初始化。
sa的元素值全部为空字符串,ia 的元素值全部为0。
sa2的元素值全部为空字符串,ia2的元素值全部未定义。

练习3.29

相比于vector 来说,数组有哪些缺点,请例举一些。

解:

  • 数组的大小是确定的。
  • 不能随意增加元素。
  • 不允许拷贝和赋值

访问数组元素

  • 数组下标的类型:size_t
  • 字符数组的特殊性:结尾处有一个空字符,如 char a[] = "hello";
  • 用数组初始化 vectorint a[] = {1,2,3,4,5}; vector<int> v(begin(a), end(a));

练习3.30

指出下面代码中的索引错误。

constexpr size_t array_size = 10;
int ia[array_size];
for (size_t ix = 1; ix <= array_size; ++ix)
	ia[ix] = ix;

解:

ix增长到 10 的时候,ia[ix]的下标越界。

练习3.31

编写一段程序,定义一个含有10个int的数组,令每个元素的值就是其下标值。

#include <iostream>
using std::cout; 
using std::endl;

static void test03_31()
{
    int arr[10];
    for (auto i = 0; i < 10; ++i) arr[i] = i;
    for (auto i : arr) cout << i << " ";
    cout << endl;

}

练习3.32

将上一题刚刚创建的数组拷贝给另一数组。利用vector重写程序,实现类似的功能。

#include <iostream>
#include <vector>
using std::cout; 
using std::endl; 
using std::vector;

static void test03_32()
{
    // array
    int arr[10];
    for (int i = 0; i < 10; ++i) arr[i] = i;
    int arr2[10];
    for (int i = 0; i < 10; ++i) arr2[i] = arr[i];

    // vector
    vector<int> v(10);
    for (int i = 0; i != 10; ++i) v[i] = arr[i];
    vector<int> v2(v);
    for (auto i : v2) cout << i << " ";
    cout << endl;
}

练习3.33

对于104页的程序来说,如果不初始化scores将会发生什么?

解:

数组中所有元素的值将会未定义。

数组和指针

  • 使用数组时,编译器一般会把它转换成指针。
  • 标准库类型限定使用的下标必须是无符号类型,而内置的下标可以处理负值。
  • 指针访问数组:在表达式中使用数组名时,名字会自动转换成指向数组的第一个元素的指针。

练习3.34

假定p1p2 都指向同一个数组中的元素,则下面程序的功能是什么?什么情况下该程序是非法的?

p1 += p2 - p1;

解:

p1 移动到 p2 的位置。任何情况下都合法。

练习3.35

编写一段程序,利用指针将数组中的元素置为0。

解:

#include <iostream>
using std::cout; using std::endl;

int main()
{
    const int size = 10;
    int arr[size];
    for (auto ptr = arr; ptr != arr + size; ++ptr) *ptr = 0;

    for (auto i : arr) cout << i << " ";
    cout << endl;

    return 0;
}

练习3.36

编写一段程序,比较两个数组是否相等。再写一段程序,比较两个vector对象是否相等。

解:

#include <iostream>
#include <vector>
#include <iterator>

using std::begin; 
using std::end; 
using std::cout; 
using std::endl; 
using std::vector;

// pb point to begin of the array, pe point to end of the array.
static bool compare(int* const pb1, int* const pe1, int* const pb2, int* const pe2)
{
    if ((pe1 - pb1) != (pe2 - pb2)) // have different size.
        return false;
    else
    {
        for (int* i = pb1, *j = pb2; (i != pe1) && (j != pe2); ++i, ++j)
            if (*i != *j) return false;
    }

    return true;
}

static void test03_36()
{
    int arr1[3] = { 0, 1, 2 };
    int arr2[3] = { 0, 2, 4 };

    if (compare(begin(arr1), end(arr1), begin(arr2), end(arr2)))
        cout << "The two arrays are equal." << endl;
    else
        cout << "The two arrays are not equal." << endl;

    cout << "==========" << endl;

    vector<int> vec1 = { 0, 1, 2 };
    vector<int> vec2 = { 0, 1, 2 };

    if (vec1 == vec2)
        cout << "The two vectors are equal." << endl;
    else
        cout << "The two vectors are not equal." << endl;
}

7、C风格字符串

  • 从C继承来的字符串。
  • 用空字符结束(\0)。
  • 对大多数应用来说,使用标准库 string比使用C风格字符串更安全、更高效。
  • 获取 string 中的 cstringconst char *str = s.c_str();

C标准库String函数,定义在<cstring> 中:

函数介绍
strlen(p)返回p的长度,空字符不计算在内
strcmp(p1, p2)比较p1p2的相等性。如果p1==p2,返回0;如果p1>p2,返回一个正值;如果p1<p2,返回一个负值。
strcat(p1, p2)p2附加到p1之后,返回p1
strcpy(p1, p2)p2拷贝给p1,返回p1

尽量使用vector和迭代器,少用数组

练习3.37

下面的程序是何含义,程序的输出结果是什么?

const char ca[] = { 'h', 'e', 'l', 'l', 'o' };
const char *cp = ca;
while (*cp) {
    cout << *cp << endl;
    ++cp;
}

解:

会将ca 字符数组中的元素打印出来。但是因为没有空字符的存在,程序不会退出循环。

练习3.38

在本节中我们提到,将两个指针相加不但是非法的,而且也没有什么意义。请问为什么两个指针相加没有意义?

解:

将两个指针相减可以表示两个指针(在同一数组中)相距的距离,将指针加上一个整数也可以表示移动这个指针到某一位置。但是两个指针相加并没有逻辑上的意义,因此两个指针不能相加。

练习3.39

编写一段程序,比较两个 string 对象。再编写一段程序,比较两个C风格字符串的内容。

解:

#include <iostream>
#include <string>
#include <cstring>
using std::cout; using std::endl; using std::string;

static void test03_39()
{
    // use string.
    string s1("Mooophy"), s2("Pezy");
    if (s1 == s2)
        cout << "same string." << endl;
    else if (s1 > s2)
        cout << "Mooophy > Pezy" << endl;
    else
        cout << "Mooophy < Pezy" << endl;

    cout << "=========" << endl;

    // use C-Style character strings.
    const char* cs1 = "Wangyue";
    const char* cs2 = "Pezy";
    auto result = strcmp(cs1, cs2);
    if (result == 0)
        cout << "same string." << endl;
    else if (result < 0)
        cout << "Wangyue < Pezy" << endl;
    else
        cout << "Wangyue > Pezy" << endl;
}

练习3.40

编写一段程序,定义两个字符数组并用字符串字面值初始化它们;接着再定义一个字符数组存放前面两个数组连接后的结果。使用strcpystrcat把前两个数组的内容拷贝到第三个数组当中。

解:

#include <iostream>
#include <cstring>

const char cstr1[]="Hello";
const char cstr2[]="world!";

static void test03_40()
{
    constexpr size_t new_size = strlen(cstr1) + strlen(" ") + strlen(cstr2) +1;
    char cstr3[new_size];
    
    strcpy(cstr3, cstr1);
    strcat(cstr3, " ");
    strcat(cstr3, cstr2);
    
    std::cout << cstr3 << std::endl;
}

练习3.41

编写一段程序,用整型数组初始化一个vector对象。

#include <iostream>
#include <vector>
using std::vector; using std::cout; using std::endl; using std::begin; using std::end;

static void test03_41()
{
    int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    vector<int> v(begin(arr), end(arr));

    for (auto i : v) cout << i << " ";
    cout << endl;
}

练习3.42

编写一段程序,将含有整数元素的 vector 对象拷贝给一个整型数组。

解:

#include <iostream>
#include <vector>
using std::vector; using std::cout; using std::endl; using std::begin; using std::end;

static void test03_42()
{
    vector<int> v{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int arr[10];
    for (int i = 0; i != v.size(); ++i) arr[i] = v[i];

    for (auto i : arr) cout << i << " ";
    cout << endl;

}

8、多维数组

  • 多维数组的初始化int ia[3][4] = {{0,1,2,3}, ...}
  • 使用范围for语句时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。

指针vs引用

  • 引用总是指向某个对象,定义引用时没有初始化是错的。
  • 给引用赋值,修改的是该引用所关联的对象的值,而不是让引用和另一个对象相关联。

指向指针的指针

  • 定义: int **ppi = &pi;
  • 解引用:**ppi

动态数组

  • 使用 newdelete表达和c中mallocfree类似的功能,即在堆(自由存储区)中分配存储空间。
  • 定义: int *pia = new int[10]; 10可以被一个变量替代。
  • 释放: delete [] pia;,注意不要忘记[]

练习3.43

编写3个不同版本的程序,令其均能输出ia的元素。
版本1使用范围for语句管理迭代过程;版本2和版本3都使用普通for语句,其中版本2要求使用下标运算符,版本3要求使用指针。
此外,在所有3个版本的程序中都要直接写出数据类型,而不能使用类型别名、auto关键字和decltype关键字。

解:

#include <iostream>
using std::cout; using std::endl;

static void test03_43()
{
    int arr[3][4] = 
    { 
        { 0, 1, 2, 3 },
        { 4, 5, 6, 7 },
        { 8, 9, 10, 11 }
    };

    // range for
    for (const int(&row)[4] : arr)
        for (int col : row) cout << col << " ";
    cout << endl;

    // for loop
    for (size_t i = 0; i != 3; ++i)
        for (size_t j = 0; j != 4; ++j) cout << arr[i][j] << " ";
    cout << endl;

    // using pointers.
    for (int(*row)[4] = arr; row != arr + 3; ++row)
        for (int *col = *row; col != *row + 4; ++col) cout << *col << " ";
    cout << endl;

}

练习3.44

改写上一个练习中的程序,使用类型别名来代替循环控制变量的类型。

解:

#include <iostream>
using std::cout; using std::endl;

static void test03_44()
{
    int ia[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };

    // a range for to manage the iteration
    // use type alias
    using int_array = int[4];
    for (int_array& p : ia)
        for (int q : p)
            cout << q << " ";
    cout << endl;

    // ordinary for loop using subscripts
    for (size_t i = 0; i != 3; ++i)
        for (size_t j = 0; j != 4; ++j)
            cout << ia[i][j] << " ";
    cout << endl;

    // using pointers.
    // use type alias
    for (int_array* p = ia; p != ia + 3; ++p)
        for (int *q = *p; q != *p + 4; ++q)
            cout << *q << " ";
    cout << endl;
}

练习3.45

再一次改写程序,这次使用 auto 关键字。

解:

#include <iostream>
using std::cout; using std::endl;

static void test03_45()
{
    int ia[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };

    // a range for to manage the iteration
    for (auto& p : ia)
        for (int q : p)
            cout << q << " ";
    cout << endl;

    // ordinary for loop using subscripts
    for (size_t i = 0; i != 3; ++i)
        for (size_t j = 0; j != 4; ++j)
            cout << ia[i][j] << " ";
    cout << endl;

    // using pointers.
    for (auto p = ia; p != ia + 3; ++p)
        for (int *q = *p; q != *p + 4; ++q)
            cout << *q << " ";
    cout << endl;
}

小结

  • string和vector是两种最重要的标准库类型。string对象是一个可变长的字符序列,vector对象是一组同类型对象的容器。
  • 迭代器允许对容器中的对象进行间接访问,对于string对象和vector对象来说,可以通过迭代器访问元素或者在元素间移动。
  • 数组和指向数组元素的指针在一个较低的层次上实现了与标准库类型string和vector类似的功能。一般来说,应该优先选用标准库提供的类型,之后再考虑C++语言内置的低层的替代品数组或指针。

  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值