字体颜色说明:
黑色字体,从书本中截取或提炼的主要说明性内容
黑色粗体,本章的关键词或是强调提醒的词语
绿色字体,多为废话,更多的是我”苦中作乐“的体现吧,不至于让我记录笔记的过程过于乏味。
黄色字体,包含了较多个人理解,补充了书本此处未涉及的说明性内容。
目录
3.1 命名空间的using声明
在前两章为使用命名空间中的成员(如cin,cout),每次使用都通过作用域操作符将命名空间显式的标注出来。例如:std::cin
上面的方法确实有些烦琐,这里将开始使用更简单的途径来使用命名空间里的成员。本章将学习其中最安全的方法,使用using声明。在遥远的第18章才会讲解另一种方法,从叙述中就可体会到,另一种方法安全性更低
using声明格式如下:
using namespace::name; //例如:using std::cin;
一旦声明了上述语句,就可以直接访问命名空间中的名字。通俗的说我们把这个名字给暴露出来了。
每个名字都需要独立的using声明
按照规定,每个using声明引入命名空间中的一个成员。如果你习惯于使用 using namespace std;这个”大杀器“,想必你依旧会觉得这个方法比较繁琐,但便利的使用往往要承担更高的风险,后面会简单提及使用using的弊端。
头文件不应包含using声明
位于头文件的代码一般来说不应该使用using声明。因为头文件有被拷贝到其它文件的可能,如果某个头文件使用了uisng声明,那么每个包含这个头文件的的文件都会有这个声明。对某些程序而言,由于不经意间包含了一些名字,会产生始料未及的名字冲突。
3.2 标准库类型string
标注库类型string表示可变长的字符序列,欲使用string需要先包含string头文件
#include<string>
欲”直呼其名“需using声明
using std::string
3.2.1 定义和初始化string对象
string s1 | 默认初始化,s1是一个空串 |
string s2(s1) | s2是s1的副本 |
string s2 = s1 | s2是s1的副本 |
string s3("value") | s3是字符串常量”value“的副本 |
string s3 = "value" | s3是字符串常量”value“的副本 |
string s4(n,’c‘) | 把s4初始化n个连续的字符c组成的字符串 |
直接初始化和拷贝初始化
使用等号初始化一个变量,执行的是拷贝初始化,编译器把等号左侧的初始值拷贝到新创建的对象中去。如果不使用等号,则执行的是直接初始化。
string s1 = "hello"; //拷贝初始化
string s2 ("hello"); //直接初始化
这两个声明语句,产生的效果是相同的(都调用了拷贝构造函数),差异体现在行为的意图上,前者是在用一个对象去初始化另一个对象,后者在通过构造函数显式的参数来初始化。
3.2.2 string对象上的操作
该部分属于具体操作的使用讲解,记录于笔记当中过于烦琐,出于此,该部分只会记录一些零散的知识点(不那么司空见惯的知识点)
读写string对象
string对象执行读取操作的规则:string对象会自动忽略开头的空白(即空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到遇到下一处空白为止。
使用getline读取一整行
如果希望字符串中能读取到输入时的空格符,那么应该运用getline函数,而非<<运算符。
getline函数接收两个参数,一个是输入流对象,一个是string对象。函数从给定输入流中读入内容,直到遇到换行符为止(注意这个换行符也被读进来了),然后把所读内容存入string对象当中(注意不存换行符)。getline函数,只要遇到换行符就结束,如果一开始就是换行符,那么得到的结果是空string。
下面从程序可以一行一行的读取内容,直到内容读完为止
int main()
{
string line;
while (getline(cin, line))
cout << line << endl;
return 0;
}
string::size_type类型
size_type类型配套于string类,在具体使用时,通过作用域操作符来表明size_type是在类string中定义的。
string的size成员函数返回的就是一个string::size_type类型的值。
string::size_type的具体细节不必过多了解,但要明确它是一个无符号类型的值。当然该类型的存在必然有其价值,不然为何不让size函数直接返回unsigned int类型呢?
区别在于,string::size_type类型可以在不同机器上表现出不同的大小,从而使得程序有更好的可移植性。
比较string对象
一组值的比较必然会比单个值的比较要麻烦许多,这里有必要记录一下。
string对象对于相等性运算符(==和!=)的检验是苛刻的。必须长度相等,每一个字符都相同且对应,并且区分大小写(string对象的比较对大小写是敏感的)。
关系运算符 <,<=,>,>= 比较规则如下:
1.两个string对象,元素从左往右,如果在某个位置对应的字符不一样,以这两个字符为依据,按照字典排序确定两个string对象大小。
2.如果比较到较短的那个string对象元素末尾为止,两个string对象所有字符都相同且对应,那么较短string对象小于较长string对象。
3.2.3 处理string对象中的字符
遇到了些我未曾了解过的内容,看来这里要多花点时间了
处理string对象中的字符串,在cctype头文件中定义了一组标准库函数处理这部分工作,下表列出了主要的函数名及其含义。(不得不说,一边看下来,啥也记不住)
isalnum(c) | 当c是字母或数字时为真 |
isalpha(c) | 当c是字母时为真 |
iscntrl(c) | 当c是控制字符时为真 |
isdigit(c) | 当c是数字时为真 |
isgraph(c) | 当c不是空格但可打印时为真 |
islower(c) | 当c是小写字母时为真 |
isprint(c) | 当c是可打印字符时为真(即c是空格或c具有可视形式) |
ispunct(c) | 当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种) |
isspace(c) | 当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种) |
isupper(c) | 当c是大写字母时为真 |
isxdigit(c) | 当c是十六进制数字时为真 |
tolower(c) | 如果c是大写字母,输出对应小写字母;否则原样输出c |
toupper(c) | 如果c是小写字母,输出对应大写字母;否则原样输出c |
处理每个字符?使用基于范围的for语句
如果相对string对象中的每个字符做点什么操作,目前最好的办法是使用C++11新标准提供的一种语句:范围for语句。这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,其语法形式是:
for (declaration : expression)
statement
其中,expression部分是一个序列性的对象。declaration部分负责定义一个变量,将变量被用于访问序列中的基础元素。每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值。(其实就是在遍历序列中的每一个元素)
让我们来尝试运用一下范围for语句吧,并且简单结合一下前面所学的内容。实现一个可以统计string对象中有多少个标点符号的程序:
#include<iostream>
#include<string> //为了使用string
#include<cctype> //为了使用ispunct函数,如果该字符为标点,返回结果为true
using std::string; //将位于命名空间std中的string这一名称暴露出来
using std::cout; //将位于命名空间std中的 cout 这一名称暴露出来
using std::endl; //将位于命名空间std中的 endl 这一名称暴露出来
int main()
{
string str("Hello World!!!"); //声明定义一个string对象,用一段字符串字面值直接初始化
decltype(str.size())num = 0; //声明定义了无符整型的变量num,并将它初始化为0,用来表示标点的数量
for (auto c : str) //遍历string对象str,用推导出来的字符型变量c来依次储存字符串中的元素(注意c的生命周期只有一个循环,一个循环结束后,旧的c被释放掉了,又重新定义了一个新的c)
if (ispunct(c)) //判断该字符是否为标点
num++; //如果真的是标点,那么让num加一
cout << "该字符串当中 标点 的数量为:" << num << endl; //输出一个结果
system("pause"); //功能就是“按任意键继续”,以免程序结束的太突然
return 0;
}
程序运行后的结果就是:该字符串当中 标点 的数量为:3
使用范围for语句改变字符串中的字符
如果现在想改变string对象中字符的值该怎么做呢?比如,将string对象里的字符元素全转为大写,这里的关键点在于引用的使用,程序的实现部分代码如下:
string str("Hello World!!!"); //创建一个将被转化的string对象
for (auto& c : str) //遍历string类型的str对象中的每一个元素
c = toupper(c); //如果c所表示的字符是小写的,那么改为大写的
cout << str << endl; //输出结果
程序输出结果为:HELLO WORLD!!!
如果c不是引用而只是一个变量的话,你会发现字符串str将毫无改变。
为什么呢?其中的原理和 经典的值传递与地址传递的问题 的道理互通。想要较好的理解其中的原理,图文并茂的简单描述下内存模型才是最优解,当然对于这一章而言有些超纲了,所以书本在这里也并未做出解释。(说了这么多,然而我也并不打算在这里做出解释,狗头)
只处理一部分字符?
这里引出的是下标运算符 [ ] 。对于这里依旧只简单的记录一些细节知识点。
下标的值称作“下标”或“索引”。如果某个索引是带符号的类型的值将自动转换成由 string::size_type表达的无符号类型。
逻辑与运算符(&&),全真为真,否则为假。C++语言规定只有当左侧运算对象为真时才会检查右侧运算对象的情况。
3.3 标准库类型vector
来认识一下强大的vector容器吧!
标准库类型vector表示对象的集合,其中所有对象的类型都相同。因为vector“容纳着”其它对象,所以它也常被称作容器。
欲使用vector,需包含头其文件
#include<vector>
同样,如果你不希望每次使用vector时都显式的说明作用域,那么就应该加上using声明
using std::vector;
C++语言既有模板类,又有模板函数,其中vector就是一个类模板。模板本身不是类或函数,相反可以将模板看作为编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为实例化,当使用模板时,需要指出编译器应该把类或函数实例化成何种类型。以类模板为例,你需要区分开“类模板”与“模板类”。类模板,重点在 模板 二字,模板就是模板,不是类,可以把它理解为生成类的模子,生成类的加工厂;模板类,重点在 类 一字,说明它的本质是一个类,不过这个类是由(类)模板实例化出来的而已。“函数模板”与“模板函数”也是同样的道理。
vector能容纳绝大多数类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的vector。如果书本中“引用不是对象”这一点,还不足以解决你对此的疑惑,那么我会尝试这么说,vector容器需要一块连续的内存空间去储存每个元素,但若vector的元素是引用,那么储存空间的连续性会被打乱,因为对引用取址得到的是其所绑定对象的地址。对于这些解释,在学习数组后会有更加深刻的体会。
在C++11中 vector<vector<int>> 的写法可以无误的使用了,不再需要将末尾的两个尖括号用空格隔开。
3.3.1 定义和初始化vector对象
定义vector对象的常用方法
vector<T> v1; | v1是一个空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对象的讲解内容,这里仅记录些细节性的问题。
当 直接初始化 和 列表初始化 混在一起,确实有些迷惑。理解下列vector对象的定义,可以帮助你理解:
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" }; //列表初始化,v5具有1个元素,为hi
vector<string> v6("hi"); //这是一个语法不同的错误
vector<string> v7{ 10 }; //相当于直接初始化,v7具有10个元素,每个都是空串
vector<string> v8{ 10,"hi" }; //相当于直接初始化,v8具有10个元素,每个都是hi
哦,v7和v8对象的定义简直是在刁难我们的编译器、刁难阅读这段代码的人,这样毫无益处可言。强烈不建议这种写法,既然意图是直接初始化,为何不采用小括号呢?
3.3.3 其它vector操作
和string的操作讲解一样,对于vector的操作,我也将大段的略过该部分。
3.4 迭代器介绍
迭代器是一种泛型指针,它的本质依旧是一种类,在类内,通过重载运算符,实现了诸多类似于指针的操作,使得这个类的对象用起来像一个指针。
标准迭代器的运算符如下:
*iter | 返回迭代器iter所指元素的引用 |
iter->mem | 解引用iter并获取该元素的名为mem的成员,等价于 (*iter).mem |
++iter | 令iter指示容器中的下一个元素 |
--iter | 令iter指示容器中的上一个元素 |
iter1 == iter2 | 判断两个迭代器是否相等,如果两个迭代器都指向同一个元素,或者它们是同一个容器的尾后迭代器,则相等。 |
iter1 != iter2 | 判断两个迭代器是否不等 |
3.4.1 使用迭代器
如果容器为空,则begin和end返回的都是同一个迭代器,都是尾后迭代器。
begin和end运算符
begin和end返回的具体类型由对象是否是常量决定,如果对象是常量,begin和end返回cosnt_iterator;如果不是常量,返回iterator。
但有些时候这种默认行为并非我们想要。为了便于专门得到cosnt_iterator类型的返回值,不论vector对象(或string对象)本身是否是常量,返回值都是const_iterator类型的返回值,C++11引入了两个新函数,分别是cbegin()和cend()
某些对vector对象的操作会使迭代器失效
说明白一些,能够使得vector对象扩展容量(容量和大小是两个不同的概念,容量 >= 大小)的操作,就有可能使得迭代器失效。其根本原因在于vector容器的动态扩展。
3.4.2 迭代器运算
迭代器的递增运算符令迭代器每次移动一个元素,所有的标准库容器都有支持递增运算的迭代器。(但类似于 it + 2 的操作,只有随机访问迭代器可以进行,这也是确定是否属于随机访问迭代器的一个技巧)类似的,也能用==和!=对任意标准库类型的两个有效迭代器进行比较。
随机访问迭代器支持更多的运算,如下:
iter + n | 迭代器加上一个整数值任然得一个迭代器,迭代器指示的新位置与原来相比向前移动了若干个元素。结果迭代器或指示容器内一个元素,或者指向容器末尾的下一个位置 |
iter - n | 类比上面 |
iter1 += n | 迭代器加法的复合赋值语句,将iter1加n的结果赋给iter1 |
iter1 -= n | 类别上面 |
iter1 - iter2 | 两个迭代器相减的结果是它们之间的距离。 |
>、>=、<、<= | 迭代器的关系运算符,如果某迭代器指向的容器位置在另一个迭代器所指的位置之前,则说前者小于后者(后面的大)。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一个位置。 |
迭代器的算术运算
支持随机访问的迭代器,可以令迭代器和一个整数的值相加减,其返回值是向前(或先后)移动了若干个位置的迭代器。
两个随机访问迭代器指向的是同一个容器中的元素或者尾元素的下一个位置(即尾后元素),就能将其相减,所得结果是两个迭代器的距离。所谓距离指的是右侧迭代器向前移动多少个位置就能追上左侧的迭代器,其类型是名为difference_type的带符号整型数。
使用迭代器运算
这里讲解了二分算法,还是比较重要的内容,书本通过文字描述的形式讲解的已很详细。若还是不好理解,推荐网上寻找图文讲解教程。
3.5 数组
数组是一种复合类型。数组的声明形如 a[d],其中a是数组的名字,d是数组的维度。维度说明了数组中元素的个数,因此必须大于0。数组中的元素个数也属于数组类型的一部分,编译的时候维度应该是已知的。也就是说,维度是一个常量表达式。
3.5.1 定义和初始化内置数组
默认情况下,数组的元素被默认初始化。
定义数组的时候必须指定数组的类型,不允许使用auto关键字由初始值的列表推断类型。数组的元素同样不可以是引用类型。
理解复杂数组声明
因为数组本身是一个对象,所以也允许定义数组的指针以及数组的引用。几种绕人的东西也便出现了,现在让我们尝试理清它们的性质。
int *ptrs[10]; //ptrs是含有10个整型指针的数组,即指针数组
int &refs[10]; //错误,不存在引用的数组
int (*Parrary)[10] = &arr; //名为 Parrary的指针 指向了具有10个整型数据的数组,即数组指针
int (*arrRef)[10] = arr; //arrRef引用一个含有10个整数的数组,即数组引用
这里再强调一个概念,分清指向数组元素的指针,和指向数组的指针(即数组指针)
int arr[3] = {};
int *p = arr; //指向数组元素的指针,实际上指向的是arr[0]的地址,它的跨度是数组中一个元素的大小
int (*parr)[3] = arr; //指向整个数组的指针,它的跨度是一整个arr数组的大小
3.5.2 访问数组元素
使用数组下标的时候,通常讲其定义为size_t类型。size_t是一种机器相关的无符号类型,它被设计的足够大以便能表示内存中任意对象的大小。在cstddef头文件中定义了size_t类型。
3.5.3 指针和数组
这会是一个让无数C++学习者头痛的部分,指针和数组的联系确实十分微妙。
在C++语言中,指针和数组有非常紧密的关系。使用数组的时候编译器一般会把它转化为指针。
数组有一个特性:在很多用到数组名的地方,编译器都会自动的讲其替换为一个指向数组首元素的指针:
string *p2 = arr; //等同于 p2 = 7arr[0]
讲数组名转化为了指针,这一点在auto推断类型时可以有所体现
int arr[] = {0,1,2,3,4};
auto p = arr; //p是一个整型指针,指向arr的第一个元素
指针也是迭代器
指向数组的指向拥有更多的功能(其实普通的指针,乃至空指针也是如此,都有这些功能,只不过一般对它们而言这些更多的功能没什么意义),vector和string的访问迭代器支持的运算,数组的指针全部支持。
标准库函数begin和end
为了让指针的使用更加简单、安全,C++11新标准引入了两个名为begin和end的函数。
int iarr[] = {0,1,2,3,4};
int *beg = begin(iarr); //指向iarr首元素的指针
int *end = end(iarr); //指向iarr尾后元素的指针
这两个函数的定义在iterator头文件中。
下标和指针
虽然标准库类型string和vector也能执行下标运算,但是数组与它们相比还是有所不同。标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求(也就是说下标可以为负数,造成的结果就是向左移动n个单位)。
3.5.4 C风格字符串
尽管C++支持C风格的字符串,但在C++程序中最好还是不要使用它们。C风格字符串使用不方便,且易引发程序漏洞,是诸多安全问题的根本原因。
字符串字面值是一种通用结构的实例,这种结构即是C++由C继承而来的C风格字符串。C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定熟成的写法。
C标准库String函数
下标列举了C语言标准库提供的一组函数,这些函数可以用于操作C风格字符串,它们定义在cstring头文件中。
strlen(p) | 返回p的长度,空字符不计算在内 |
strcmp(p1,p2) | 比较p1与p2的相等性。如果p1 == p2,返回0;如果p1 > p2 返回一个正值;如果p1 < p2 返回一个负值 |
strcat(p1,p2) | 将p2附加刀p1之后,返回p1 |
strcpy(p1,p2) | 将p2拷贝p1,返回p1 |
传入此类函数的指针必须以空字符作为结束的数组:
char carr[] = {'c' ,'+','+'}; //不以空字符结束
cout<<strlen(carr)<<endl; //严重错误,ca没有以空字符结束
上述行为在VS编译器中会得到一个警告,程序是可以运行起来的,但得到的结果是未知的,strlen函数将沿着carr在内存中不断的向前寻找,直到遇到空字符。
目标字符串的大小由调用者指定
好家伙,本部分的代码案例在较新版本的vs编译器中直接无法通过(可以手动关闭这个警告,然后正常运行),因为strcpy以及strcat等函数,出于其不安全性以及有更好的替代的原因,已被弃用。它们确实已经过时,是不推荐的用法。
3.5.5 与旧代码的接口
难逃时代的拘泥,现代的C++程序不得不与那些充满了数组和C风格字符串的代码衔接,为了使这一工作简单,C++专门提供了一组功能。
也正是出于这些原因,C++中的字符串特别绕人,就一个string对象的初始化都能整出一堆花样来,属实增加学习成本。
混用string对象和C风格字符串
如前面所说,允许使用字符串字面值来初始化string对象:
string s("hello world");
更一般的情况的是,任何出现字符串字面值的地方都可以用空字符结束的字符数组来代替
✦允许使用空字符结束的字符数组来初始化string对象或为string对象赋值。(其实用没有空字符结束的字符数组也可以运行起来,这时会出现警告,输出string对象的结果也是未知的)
✦在string对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算符都是);在string对象的复合赋值运算当中,允许使用以空字符结束的字符数组作为右侧的运算对象。
上述性质反过来就不成立了,如果某个程序需要处理一个C风格字符串,无法直接使用string对象来代替它。为了完成该功能,string专门提供一个名为c_str的成员函数:
char *str = s; //错误,不能使用string对像来初始化char*
const char *str = s.c_str(); //正确
c_str函数的返回值是一个C风格的字符串。也就是说,函数的函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组,而这个数组所存的数据恰好与那个string对象的一样。
使用数组初始化vector对象
允许使用数组来初始化vector对象。要实现这一目的,只需要指明要拷贝的区间的首尾地址就可以了。例如:
int iarr[] = {0,1,2,3,4,5,6}
vector<int> iv(begin(iarr), end(iarr));
现代C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用C风格的基于数组的字符串。
3.6 多维数组
严格来说,C++语言中没有多维数组,通常所说的多维数组其实就是数组的数组。前面说过,数组本身也是一个对像,数组的元素当然也可以是数组。好一个套娃行为!
//大小为3的数组,其3个元素都是含有4个整型元素的数组
int ia[3] [4];
//大小为10的数组,其10个元素都是大小为20的数组,这20个数组又各包含30个整型的元素
int arr[10] [20] [30];
对于二维数组而言,常把第一个维度称为行,第二个维度称为列。
使用范围for语句处理多维数组
使用范围for可以大大的简化遍历二维数组的写法:
constexpr size_t rowCnt = 3, colCnt = 4;
int arr_arr[rowCnt][colCnt] = {}; //元素全部默认初始化为0
for (size_t i = 0; i < rowCnt; i++)
{
for (size_t j = 0; j < colCnt; j++)
cout << arr_arr[i][j] << " ";
cout << endl;
}
for (auto &arr : arr_arr) //这里的引用颇为关键,如果不加引用符的话,arr会被推导为int*类型,它指向作为二维数组元素的一维数组的首地址。
//不加引用符,arr会失去数组的属性,仅仅只是一个指向int类型数据的指针,auto i = arr 便会报错,编译器不认为arr是一个序列。
{
for (auto i : arr)
cout << i << " ";
cout << endl;
}
上述的遍历行为中,并未有要修改数组内元素的意图,但在范围for循环遍历二维数组时,外层循环还是定义为了引用。这是为了避免数组被自动转成指针。
假设外层循环的arr也没使用引用类型,编译器初始化arr时会将这些数组形式的元素转换成指向该数组内首元素的指针。这样得到的arr类型就是int*。
要使用范围for语句处理多维数组,除了最内层的循环外,其它所有循环的控制变量都应该是引用类型。
指针和多维数组
当程序使用多维数组的名字时,也会自动的将其转化为指向数组首元素的指针。
多维数组实际上是数组的数组,所以多维数组名转化得到的指针实际上是指向(内层)数组的指针。
随着C++11新标准的提出,通过使用auto或者decltype就能尽可能地避免在数组前面加上一个指针类型了。
//创建了一个3行4列的二维数组
int ia[3][4];
//ia是多维数组的数组名,编译器会讲其转会为指向它首元素的指针,
//该数组的元素为维度为4的数组,所以转化的指针为指向维度为4的数组的指针,即 (*int)[4]
//因此,auto推导出的p的类型也为(*int)[4],指向了ia的第一个元素
for (auto p = ia; p != ia + 3; p++)
{
//p是指向维度为4的数组的指针,对其解引用,得到的就是p所指的对象,即一个维度为4的数组
//这个维度为4的数组又自动转化为了一个指向int类型数组的指针,因此q推导为int*
for (auto q = *p; q != *p + 4; q++)
//对q解引用,得到它所指的对象,一个int类型的数据
cout << *q << " ";
//没输出一行(一个内层数组)还一行
cout << endl;
}
本章完结!!!
后续的进度要加快了,争取3天一章节,稳步推进。
参考书籍 《C++ Primer (第5版)》