*****代码在Debian g++ 5.40 / clang++ 3.8(C++11)下编写调试*****
本章主要是关于字符串、数组的内容,以及一些简单的容器知识。
1.using的声明
using关键字有2种用法:
using
std::cin;
//这种叫做using声明,注意分号
using namespace std; //这种叫做using指示,注意分号
两者的区别是一个using声明一次只引入一个std的成员,而using指示则将整个命名空间的所有成员都引入。
头文件不要包含using声明,否则会导致引用了头文件的文件中包含using声明,产生始料未及的冲突。
虽然直接打开/usr/include/c++/5/iostream头文件看到里面的namespace std只包含了几个对象名,但并不全,因为命名空间可以分离不连续,在多处定义不同的部分。
2.标准库类型string
使用string类型要包含string头文件,string是定义在命名空间std中的。
C++string类的对象初始化的几种方式,如下:
string s1;
//默认初始化
string s2 = s1;
string s3 =
"hiya"
;
string s4(
"vlaue"
);
string s5(10,
'c'
);
//s5 = cccccccccc 初始化为10个c
1. os< 将s写到输出流os当中,返回os |
|
2. is>>s | 从is中读取字符串赋给s,字符串以空白字符分割,返回is |
3. getline(is,s) | 从is中读取一行赋给s,返回is |
4. s.empty() | s为空返回true,否则返回false |
5. s.szie() | 返回s中字符的个数 |
6. s[n] | 返回s中第n个字符的引用,位置从0开始计数 |
7. s1+s2 | 返回s1和s2连接后的副本 |
8. s1=s2 | 用s2的副本代替s1中原来的字符 |
9. s1==s2 | 如果s1和s2中所含的字符完全一样,则他们相等,否则 |
10. s1!=s2 | 不相等,string对象的相等性判断对字母大小写敏感 |
11.<,<=,>,>= | 利用字符在字典中的顺序进行比较,且对字母的大小写敏感 |
string类型可以做加法运算,该加法运算的对象可以是两个string对象,也可以是一个string对象和一个字符串字面值,但是不能两个字符串字面值进行加法运算后赋值给string对象,因为字符串字面值直接并没有加法运算。
vector是一种容器,是同种类型对象的有序集合,集合中的元素可以通过下标索引来进行访问。
要使用vector必须包含相应的头文件,另外vector只是一个模板的名字,并非一个具体的类或函数,模板需要提供一些信息来实例化。(这里的vector有多种含义,比如容器类型,模板类)
如书本之前提到的:如何初始化类的对象是由类本身决定的。一个类可以定义很多种初始化对象的方式,只不过这些方式之间必须有所区别:或者是初始值的数量不同,或者是初始值的类型不同。这里的vector类似的具有多种初始化对象的方式:
vectorv1; //默认初始化 vector v2(v1); //拷贝初始化 vector v3 = v2; //拷贝初始化 vector v4(n, val); //制定初始容器元素个数,并将每个元素值指定为val vector v5(n); //指定初始容器元素个数 vector v6{a, b, c...}; //列表初始化 vector v6 = {a, b, c...};//列表初始化
定义了一个vector之后,可以使用push_back方法来向其中添加元素,不能直接通过下标索引来向vector添加元素
vector除了push_back操作之外,还提供其他的操作,比较重要的如下
v.empty() | v为空返回真,否则返回假 |
v.size() | 返回v的元素个数 |
v.push_back(t) | 向v末尾添加一个值为t的元素 |
v[n] | 访问第n个位置上的元素,位置从0开始计数 |
v1=v2 | 用v2副本替换v1 |
v1={a,b,c...} | 用列表中的元素替换给v1 |
v1==v2 | 比较v1和v2是否相等 |
v1!=v2 | 比较v1和v2是否不等 |
<,<=,>,>= | 比较运算 |
对于string对象或者vector对象可以使用下表索引来访问其中的元素,还有一种方法也能访问元素,该方法就是迭代器。
所有标准库的容器都有迭代器,string不属于容器,但也可以使用迭代器。
迭代器是对对象的间接访问,迭代器也有有效和无效之分。
另外迭代器可以进行移动。可以使用标准库容器或者string的begin和end方法来获取相应的迭代器。内置数组不具有begin和end方法,可以使用标准库的begin和end运算符来获取。begin和end运算符类似于sizeof运算符。
const vector和vector成员是const的,以及第6章的initializer_list
迭代器
内置数组在很多方面与vector不同,因此vector的方法不适用于内置数组。
1.数组不能直接复制
2.数组在定义时必须指明大小,且该大小再也无法改变(g++/clang++支持c99的扩展特性,允许编译时不指明数组大小,但该特性非c++标准,可以在编译时使用-pedantic选项来查看 )
3.数组定义时不允许使用auto关键字进行推断
4.字符数组允许使用字符串字面值来初始化
5.数组也没有begin,end,size,empty等方法
指针和数组之间有非常密切的联系。很多情况下,使用数组名字时,编译器会自动将其转换为其首元素地址的指针,因此一些情况下数组的操作是指针的操作。
当使用auto去推断数组作为变量的初始值的时候,该变量的类型是指针,而非数组,对于多维数组,则是第一维的数组指针,例如
int array[3][4] = {0}; auto p = array //p是array[4],而不是array[0][0]
这里的p是指向含有4个int元素的数组指针,而非指向一个int元素的指针
指针也是一种迭代器,因此迭代器所支持的操作,指针也全部支持,但指针还拥有更多的功能。如前所述,一些情况下数组的操作是指针的操作,比如有时使用数组的名字时其实用的是一个指向数组首元素的指针,因此对数组执行下标运算符其实是对指向数组元素的指针执行下标运算:
int ia[] = {1, 2, 3, 4, 5}; //ia转换成指向数组首元素指针 int i = ia[2]; //ia[2]得到(ia+2)所指的元素 int *p = ia; //p指向ia的首元素 i = *(p + 2); //等价于i=ia[2] int *p = &ia[2]; //p指向索引为2的元素 int j = p[1]; //p[1等价于*(p+1),就是ia[3]表示的那个元素 int k = p[-2]; //p[-2]是ia[0]表示的那元素
与标准库的string和vector类型不同,虽然这些都支持下标运算符,但内置的数组下标运算符支持负数索引,而标准库的下标索引则是无符号类型,无法处理负值。
C风格字符串:
1. C风格字符串并不是一种类型,是一种约定的写法,表示方法是将字符串放在字符数组中,且在数组中以'\0'作为终结符,一般是使用指针来操作这些字符串。
2. 字符串字面值是一串常量字符,字符串字面值常量用双引号括起来的零个或多个字符表示,为兼容C语言,C++中所有的字符串字面值都由编译器自动在末尾添加一个空字符。字符串是没有变量名字的,自身表示自身。
多维数组:
关于多维数组,在C++中其实并不存在,多维数组其实是数组的数组,理解多维数组是数组的数组有利于对多维数组使用auto和指针时的理解。
使用多维数组的名字时,也会将数组名转换为指向数组首元素的指针。因为多维数组实际上是数组的数组,因此多维数组名转换得到的指针是指向第一个内层数组的指针。
int ia[3][4]; int (*p)[4] = ia; p = &ia[2]; auto p = ia; //等价于int (*p)[4]=ia;
练习3.1:使用恰当的using声明重做1.4.1节(第13页)和2.6.2节(第76页)的练习。
1.
#includeusing std::cout; using std::endl; int main() { int sum = 0; for (int i = 50; i <= 100; i++) sum += i; cout << "Sum of 50 to 100 inclusive is " << sum << endl; return 0; }
2.
#includeusing std::cout; using std::endl; int main() { for (int i = 10; i >= 0; i--) cout << i << endl; return 0; }
3.
#includeusing std::cin; using std::cout; using std::endl; int main() { int a, b; cout << "Enter two numbers:" << endl; for ( cin >> a >> b; a <= b; a++) cout << a << " "; return 0; }
练习3.2:编写一段程序从标准输入中一次读入一整行,然后修改该程序使其一次读入一个词。
#includeusing namespace std; int main() { string line; getline(cin, line); string word; cin >> word; return 0; }
练习3.3:请说明string类的输入运算符和getline函数分别是如何处理空白字符的。
string类一但遇到空白字符就停止读入,而getline一旦遇到回车符则停止读入
练习3.4:编写一段程序读入两个字符串,比较其是否相等并输出结果。如果不相等,输出较大的那个字符串。改写上述程序,比较输入的两个字符串是否等长,如果不等长,输出长度较大的那个字符串。
#include#include <string> using namespace std; int main() { string st1, st2; cout << "input two strings:"; cin >> st1 >> st2; if (st1 == st2) cout << st1 << " = " << st2 << endl; else { if (st1 > st2) cout << st1 << endl; else cout << st2 << endl; } if (st1.size() > st2.size()) cout << st1 << endl; else cout << st2 << endl; return 0; }
练习3.5:编写一段程序从标准输入中读入多个字符串并将它们连接在一起,输出连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分隔开来。
#include#include <string> using namespace std; int main() { string st1, st2; while (cin >> st1) st2 += st1; cout << st2 << endl; while (cin >> st1) st2 = st2 + st1 + " "; cout << st2 << endl; return 0; }
练习3.6:编写一段程序,使用范围for语句将字符串内的所有字符用X代替。
#include#include <string> using namespace std; int main() { string str; cin >> str; if (!str.empty()) for (auto &i : str) i = 'X'; return 0; }
练习3.7:就上一题完成的程序而言,如果将循环控制变量的类型设为char将发生什么?先估计一下结果,然后实际编程进行验证。
不会发生什么,结果和auto是相同的
练习3.8:分别用while循环和传统的for循环重写第一题的程序,你觉得哪种形式更好呢?为什么?
#include#include <string> using namespace std; int main() { string str; cin >> str; string::size_type i = 0; if (!str.empty()) while (i < str.size()) { str[i] = 'X'; ++i; } //for loop if (!str.empty()) for (string::size_type i = 0; i < str.size(); ++i) { str[i] = 'X'; } return 0; }
三种循环相比,最好的形式是范围for循环,简便易用.
练习3.9:下面的程序有何作用?它合法吗?如果不合法,为什么?
string s; cout << s[0] << endl;
不合法,因为此时s并没有元素,s[0]是非法访问,其行为是未定义的.
练习3.10:编写一段程序,读入一个包含标点符号的字符串,将标点符号去除后输出字符串剩余的部分。
#include#include <string> using namespace std; int main() { string str, result; cin >> str; if (!str.empty()) for (auto &i : str) { if (!ispunct(i)) result += i; } cout << result << endl; return 0; }
练习3.11:下面的范围for语句合法吗?如果合法,c的类型是什么?
const string s = "Keep out!"; for(auto &c : s){ /* ... */ }
合法,此时的auto能推断出顶层const,因此c将是一个常量,c的类型是一个const char.
练习3.12:下列vector对象的定义有不正确的吗?如果有,请指出来。对于正确的,描述其执行结果;对于不正确的,说明其错误的原因。
(a) vector
(b) vector
(c) vector
练习3.13:下列的vector对象各包含多少个元素?这些元素的值分别是多少?
(a) vector
(b) vector
(c) vector
(d) vector
(e) vector
(f) vector
(g) vector
练习3.14:编写一段程序,用cin读入一组整数并把它们存入一个vector对象。
#include#include using namespace std; int main() { int i; vector<int> v; while (cin >> i) v.push_back(i); return 0; }
练习3.15:改写上题的程序,不过这次读入的是字符串。
#include#include using namespace std; int main() { string s; vector<string> v; while (cin >> s) v.push_back(s); return 0; }
练习3.16:编写一段程序,把练习3.13中vector对象的容量和具体内容输出出来。检验你之前的回答是否正确,如果不对,回过头重新学习3.3.1节(第97页)直到弄明白错在何处为止。
#include#include using namespace std; int main() { vector<int> v1; //没有包含元素 for (auto i : v1) cout << i << '\t'; cout << "\n============\n"; vector<int> v2(10); //包含10个元素,所有的元素值为0 for (auto i : v2) cout << i << '\t'; cout << "\n============\n"; vector<int> v3(10, 42); //包含10个元素,所有的元素值为42 for (auto i : v3) cout << i << '\t'; cout << "\n============\n"; vector<int> v4{10}; //包含1个元素,值为10 for (auto i : v4) cout << i << '\t'; cout << "\n============\n"; vector<int> v5{10, 42}; //包含2个元素,值为10和42 for (auto i : v5) cout << i << '\t'; cout << "\n============\n"; vector<string> v6{10}; //包含10个元素,所有的元素值为空字符串 for (auto i : v6) cout << i << '\t'; cout << "\n============\n"; vector<string> v7{10, "hi"}; //包含10个元素,所有的元素值为字符串“hi” for (auto i : v7) cout << i << '\t'; cout << "\n============\n"; return 0; }
练习3.17:从cin读入一组词并把它们存入一个vector对象,然后设法把所有词都改写为大写形式。输出改变后的结果,每个词占一行。
#include#include using namespace std; int main() { string s; vector<string> v; while (cin >> s) v.push_back(s); for (auto &i : v) { for (auto &ii : i) ii = toupper(ii); cout << i << '\n'; } return 0; }
练习3.18:下面的程序合法吗?如果不合法,你准备如何修改?
vector<int> ivec; ivec[0] = 42;
不合法 ,应修改为ivec.push_back(42);
练习3.19:如果想定义一个含有10个元素的vector对象,所有元素的值都是42,请列举出三种不同的实现方法。哪种方法更好呢?为什么?
#include#include using namespace std; int main() { vector<int> v1(10, 42); vector<int> v2{42, 42, 42, 42, 42, 42, 42, 42, 42, 42,}; vector<int> v3; vector<int> ivec3; for (int i = 0; i < 10; ++i) v3.push_back(42); return 0; }
显然,圆括号实现的方法更好,简洁易用。
练习3.20:读入一组整数并把它们存入一个vector对象,将每对相邻整数的和输出出来。改写你的程序,这次要求先输出第1个和最后1个元素的和,接着输出第2个和倒数第2个元素的和,以此类推。
#include#include using namespace std; int main() { vector<int> v; int n; while (cin >> n) v.push_back(n); for (int i = 0; i < v.size() - 1; ++i) //考虑到v[i+1],防止溢出,应该循环 v.size() - 1 次 { cout << v[i] + v[i + 1] << '\t'; ++i; } cout << '\n'; return 0; }
#include#include using namespace std; int main() { vector<int> v; int n; while (cin >> n) v.push_back(n); vector<int>::size_type loop = v.size(); for (int i = 0; i < v.size() / 2; ++i) { cout << v[i] + v[loop - 1] << '\t'; --loop; } cout << '\n'; return 0; }
练习3.21:请使用迭代器重做3.3.3节(第105页)的第一个练习。
#include#include using namespace std;
//以下两个同名函数为重载 void traverse(const vector<int> fv) { for (auto b = fv.begin(); b != fv.end(); ++b) { cout << *b << '\t'; } cout << '\n'; } void traverse(const vector<string> fv) { for (auto b = fv.begin(); b != fv.end(); ++b) { cout << *b << '\t'; } cout << '\n'; } int main() { 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"}; traverse(v1); traverse(v2); traverse(v3); traverse(v4); traverse(v5); traverse(v6); traverse(v7); return 0; }
练习3.22:修改之前那个输出text第一段的程序,首先把text的第一段全都改成大写形式,然后再输出它。
#include#include #include <string> using namespace std; int main() { string s; vector<string> v; while (cin >> s) v.push_back(s); for (auto b = v.begin(); b != v.end() && !b->empty(); ++b) { for (auto &i : *b) i = toupper(i); cout << *b << '\t'; } cout << '\n'; return 0; }
练习3.23:编写一段程序,创建一个含有10个整数的vector对象,然后使用迭代器将所有元素的值都变成原来的两倍。输出vector对象的内容,检验程序是否正确。
#include#include using namespace std; int main() { vector<int> v; for (int i = 0; i < 10; ++i) { int x; cin >> x; v.push_back(x); } for (auto &i : v) { i += i; cout << i << '\t'; } cout << '\n'; return 0; }
练习3.24:请使用迭代器重做3.3.3节(第94页)的最后一个练习。
#include#include using namespace std; int main() { vector<int> v; int n; while (cin >> n) v.push_back(n); for (auto b = v.begin(); b < v.end() - 1; ++b) { cout << *b + *(b + 1) << '\t'; ++b; } cout << '\n'; auto b = v.begin() , e = v.end() - 1; while (b < e) { cout << *b + *e << '\t'; ++b; --e; } return 0; }
练习3.25:3.3.3节(第94页)划分分数段的程序是使用下标运算符实现的,请利用迭代器改写该程序并实现完全相同的功能。
#include#include using namespace std; int main() { vector scores(11, 0); unsigned grade; while (cin >> grade) ++*(scores.begin() + grade / 10); return 0; }
练习3.26:在100页的二分搜索程序中,为什么用的是mid = beg + (end - beg) / 2,而非mid = (beg + end) /2;?
因为迭代器之间没有定义“ + ”的运算。
练习3.27:假设txt_size 是一个无参数的函数,它的返回值是int。请回答下列哪个定义是非法的,为什么?
unsigned buf_size = 1024;
(a) int ia[buf_size]; //非法,非常量表达式(g++/clang++扩展的C99特性可以编译通过)
(b) int ia[4 * 7 - 14]; //合法
(c) int ia[txt_size()]; //非法,非常量表达式
(d) char st[11] = "fundamental"; //非法,字符串末尾隐含'\0'字符,数组溢出
练习3.28:下列数组中元素的值是什么?
#includeusing namespace std; string sa[10]; //空字符元素 int ia[10]; //元素0 int main() { string sa2[10]; //空字符元素 int ia2[10]; //未定义 return 0; }
练习3.29:相比于vector 来说,数组有哪些缺点,请例举一些。
缺点有三:
1.vector大小可变,数组固定,无法增长,限制大
2.vector可以进行赋值,而数组无法复制
3.vector带有一些方法用于操作vector,比如push,size,empty,<,>等,而数组没有这些方便使用的方法
练习3.30:指出下面代码中的索引错误。
constexpr size_t array_size = 10; int ia[array_size]; for (size_t ix = 1; ix <= array_size; ++ix) //数组大小是10,但数组下标索引从0开始,最大为9,循环溢出 ia[ix] = ix;
练习3.31:编写一段程序,定义一个含有10个int的数组,令每个元素的值就是其下标值。
#includeusing namespace std; int main() { int arr[10]; for (int i = 0; i != 10; ++i) { arr[i] = i; } return 0; }
练习3.32:将上一题刚刚创建的数组拷贝给另外一个数组。利用vector重写程序,实现类似的功能。
#include#include using namespace std; int main() { int arr[10]; for (int i = 0; i != 10; ++i) { arr[i] = i; } int arr2[10]; for (auto i : arr) { arr2[i] = i; } //vector vector<int> v(10); for (int i = 0; i != 10; ++i) { v[i] = i; } vector<int> v2 = v; return 0; }
练习3.33:对于104页的程序来说,如果不初始化scores将会发生什么?
数组中所有元素的值将会默然初始化,其中的元素值是未定义的。
练习3.34:假定p1 和 p2 指向同一个数组中的元素,则下面程序的功能是什么?什么情况下该程序是非法的?
p1 += p2 - p1;
程序的功能是将p1指向p2所指的元素,当p1和p2不指向同一个数组中的元素时,该程序是非法的
练习3.35:编写一段程序,利用指针将数组中的元素置为0。
#includeusing namespace std; int main() { int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; for (int *p = array; p != &array[9]; p++) { cout << *p << '\t'; *p = 0; cout << *p << '\t'; cout << '\n'; } return 0; }
练习3.36:编写一段程序,比较两个数组是否相等。再写一段程序,比较两个vector对象是否相等。
#include#include using namespace std; int main() { int array1[] = {1, 2, 3, 4, 5, 6, 7, 8}; int array2[] = {8, 7, 6, 5, 4, 3, 2, 1}; if (sizeof(array2) == sizeof(array2)) //数组长度一致时才具体比较 { for (int i = 0; i != sizeof(array1) / sizeof(int); ++i) { if (array1[i] != array2[i]) //逐元素比较 { cout << "not equal!" << endl; break; } } } else { cout << "not equal!" << endl; } //vector vector<int> v1{1, 2, 3, 4, 5}; vector<int> v2{1, 2, 3, 4, 5, 6}; if (v1 != v2) cout << "not equal!" << endl; return 0; }
练习3.37:下面的程序是何含义,程序的输出结果是什么?
const char ca[] = { 'h', 'e', 'l', 'l', 'o' }; const char *cp = ca; while (*cp) { cout << *cp << endl; ++cp; }
只要cp所指向的元素不是'\0'(空字符),就一直循环遍历,该程序将会一直循环,直到在内存中遇到空字符
练习3.38:在本节中我们提到,将两个指针相加不但是非法的,而且也没有什么意义。请问为什么两个指针相加没有意义?
两个指针相加,即两个内存地址相加,是没有任何逻辑意义的。
练习3.39:编写一段程序,比较两个string对象。再编写一段程序,比较两个C风格字符串的内容。
#include#include #include <string> using namespace std; int main() { string s1{"hello"}; string s2{"world"}; if (s1 == s2) cout << "s1 = s2" << endl; else cout << "s1 != s2" << endl; //C-style character string const char *c1 = "string1"; const char *c2 = "string1"; if (strcmp(c1, c2)) { cout << "c1 != c2" << endl; } else { cout << "c1 = c2" << endl; } return 0; }
练习3.40:编写一段程序,定义两个字符数组并用字符串字面值初始化它们;接着再定义一个字符数组存放前面两个数组连接后的结果。使用strcpy和strcat把前两个数组的内容拷贝到第三个数组当中。
#include#include using namespace std; int main() { char c1[] = "hello"; char c2[] = "world"; char c3[20]; strcpy(c3, c1); strcat(c3, c2); cout << c3 << endl; return 0; }
练习3.41:编写一段程序,用整型数组初始化一个vector对象。
#include#include using namespace std; int main() { int arr[5] = {1, 2, 3, 4, 5}; vector<int> v(begin(arr), end(arr)); return 0; }
练习3.42:编写一段程序,将含有整数元素的vector对象拷贝给一个整型数组。
#include#include using namespace std; int main() { vector<int> v{1, 2, 3, 4, 5}; int array[5]; for (int i = 0; i < v.size(); ++i) { array[i] = v[i]; } return 0; }
练习3.43:编写3个不同版本的程序,令其均能输出ia的元素。版本1使用范围for语句管理迭代过程;版本2和版本3都使用普通的for语句,其中版本2要求用下标运算符,版本3要求用指针。此外,在所有3个版本的程序中都要直接写出数据类型,而不能使用类型别名、auto关键字或decltype关键字。
#includeusing namespace std; int main() { int ia[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; //version 1 for (int (&i)[4] : ia) { for (int j : i) cout << j << '\t'; } cout << endl; //version 2 for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) cout << ia[i][j] << '\t'; } cout << endl; //version 3 for (int *p = &ia[0][0]; p != &ia[2][4]; ++p) //ia[2][3]是最后一个元素,ia[2][4]是尾后元素 { cout << *p << '\t'; } cout << endl; return 0; }
练习3.44:改写上一个练习中的程序,使用类型别名来代替循环控制变量的类型。
#includeusing namespace std; int main() { int ia[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; //version 1 using ref4 = int (&)[4]; for (ref4 i : ia) { for (int j : i) cout << j << '\t'; } cout << endl; //version 2 for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) cout << ia[i][j] << '\t'; } cout << endl; //version 3 for (int *p = &ia[0][0]; p != &ia[2][4]; ++p) //ia[2][3]是最后一个元素,ia[2][4]是尾后元素 { cout << *p << '\t'; } cout << endl; return 0; }
练习3.45:再一次改写程序,这次使用auto关键字。
#includeusing namespace std; int main() { int ia[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; //version 1 for (auto &i : ia) { for (auto j : i) cout << j << '\t'; } cout << endl; //version 2 for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) cout << ia[i][j] << '\t'; } cout << endl; //version 3 for (auto p = ia; p != ia + 3; ++p) { for (auto q = *p; q != *p + 4; ++q) cout << *q << '\t'; } cout << endl; return 0; }