现在挑选U S B/Type-C双口网卡,具体买USB无线网卡哪个牌子信号最好?

随着各种数码设备的大量普及,特别是手机和平板的普及,我们周围的USB设备渐渐多了起来。每部手机、平板都需要一个适配器(充电头)才能充电,然而这些设备虽然都是采用了USB接口,但是这些设备的数据线并不完全相同。如果跟朋友一起出去,手机快没电时找不到适配器,那就只有再买个新的了,毕竟品牌众多,这些数据线在连接PC的一端都是相同的,但是在连接设备端的时候,通常出于体积的考虑而采用了各种不同的接口。即便现在看看手中设备的接口,你真的能叫出它们的名字吗?今天把这些可能错过的知识补回来。下面就跟小编一起了解一下常见的USB、micro USB还有Type-C之间的定义与区别!!!

  Type-c接口:常用于智能手机底部,用作充电、数据传输等。

  2、支持USB接口双面插入,解决了“USB永远插不准”的大难题。

  3、速度快:最高传输速率为每秒10Gb。(充电快啊~)

  USB:电脑连接设备的数据传输线,包括一个电脑接口、一个手机接口。

  特点:应用于电脑和移动设备等信息通讯产品,属于PC领域的接口技术。

  1、方便电脑对外传输数据。

  2、支持设备的即插即用和热拨插功能。

  Micro USB:各种不同的设备间的联接,进行数据交换、充电等。

  特点:(各种不同的设备间的联接,进行数据交换、充电等。)

  1、便携:接口更小,节省空间。

  2、具有高达10000次的插拔寿命和强度,盲插结构设计。

  3、支持目前USB的OTG功能:可同时提供充电和数据传输等。

  Type-A是我们最常见的一种USB接口类型,在电脑上常用。但它有一些显著问题:有方向要求。必须从某个特定的方向才能将接头(公口)插入接口(母口),但由于 USB 公口的两面外形非常接近,这个插入的过程经常出错。

(2)Micro:移动设备的USB标准

  当前大部分安卓手机中采用的是Micro USB 接口(即 USB Micro-B),这种接口至今仍被广泛地应用在各种移动便携式设备上。

stringvector是两种最重要的标准库类型。string支持可变长字符串,后者则表示可变长的集合。

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

头文件不应包含 using 声明

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

标准库类型string表示可变长的字符序列。

初始化string对象的方式
默认初始化,s1 是一个空串
s3 是字面值"value"的副本,除了字面值最后的那个空字符外
s4 初始化为由连续 n 个字符 c 组成的串

直接初始化和拷贝初始化

  • 拷贝初始化(copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去
s写到输出流os当中,返回os
is中读取字符串赋给s,字符串以空白分隔,返回is
is中读取一行赋给s,返回is
返回s中第n个字符的引用,位置n0计起
返回s1s2连接后的结果
s2的副本代替s1中原来的字符
如果s1s2中所含的字符完全一样,则它们相等;
string对象的相等性判断对字母的大小写敏感
利用字符在字典中的顺序进行比较,且对字母的大小写敏感

在执行读取操作时,string对象会自动忽略开头的空白(即空格符、换行符、制表符等)并从第一个真正的自负开始读起,直到遇见下一处空白为止。如果程序的输入是"Hello World!",则输出将是“Hello”。


 
 
使用情景:有时我们希望能在最终得到的字符串中保留输入时的空白符,这时应该用 getline 函数代替原来的 >> 运算符。
getline与换行符水火不容
getline函数的参数是一个输入流和一个string对象,
函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),
然后把所读的内容存入到那个string对象中去(注意不存换行符)。
Note:触发getline函数返回的那个换行符实际上被丢掉了,得到的string对象中并不包含该换行符。
getline只要一遇到换行符就结束读取操作并返回结果,哪怕输入的一开始就是换行符也是如此,此种情况下那么所得的结果是个空string。
我们也能用getline的结果作为判断条件,可以让程序一次输出一整行,而不再是每行输出一个词了:

因为line中不包含换行符,所以我们手动地加上换行操作符。使用 endl结束当前行并刷新显示缓冲区。


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

  • 标准库 string 的输入运算符自动忽略字符串开头的空白(包括空格符、换行符、制表符等),从第一个真正的字符开始读起,直到遇见下一处空白为止。
  • 如果希望在最终的字符串中保留输入时的空白符,应该使用 getline 函数代替原来的 >>运算符,getline从给定的输入流中读取数据,直到遇到换行符为止,此时换行符也被读取进来,但是并不存储在最后的字符串中。

string::size_type是一个无符号类型的值,而且能足够存放下任何 string 对象的大小。

注意:在表达式中混用了带符号数和无符号数将可能产生意想不到的结果。
如果一条表达式中已经有了size()函数就不要再使用int了,这样可以避免混用intunsigned可能带来的问题。


    1. 如果两个 string 对象的长度不同,而且较短 string 对象的每个字符都与较长 string 对象对应位置上的字符相同,就说较短 string 对象小于较长 string 对象。
    1. 如果两个 string 对象在某些对应的位置上不一致,则 string 对象比较的结果其实是 string 对象中第一对相异字符比较的结果。

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

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

当 c 是字母或数字时为真
当 c 是控制字符时为真
当 c 不是空格但可打印时为真(不熟悉)
当 c 是小写字母时为真
当 c 是可打印字符时为真(即 c 是空格或者 c 具有可视形式)
当 c 是标点符号时为真(即 c 不是控制字符、数字、字母、可打印空白中的一种)
当 c 是空白时为真(即 c 是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种
当 c 是大写字母时为真
当 c 是十六进制数字时为真
如果 c 是大写字母,输出对应的小写字母;否则原样输出 c
如果 c 是小写字母,输出对应的大写字母;否则原样输出 c

建议:使用 C++ 版本的 C 标准库头文件。比如cctype头文件和ctype.h头文件的内容是一样的,只不过从命名规范上来讲更符合 C++ 语言的要求。

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

范围for(range for)语句,这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作:
expression 表示一个序列,declaration 定义一个变量,该变量将被用于访问序列中的基础元素。
每次迭代,declaration 部分的变量会被初始化为 expression 部分的下一个元素。

举例:使用范围 for 语句把 string 对象中的字符每行一个输出出来:

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

如果想要改变 string 对象中字符的值,必须把循环变量定义成引用类型。

练习3.8:分别用 while 循环和传统的 for 循环重写 范围for循环 的程序。


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


解答:语法上来说是合法的,s 是一个常量字符串,则c的推断类型是常量引用,即c所绑定的对象值不能改变。
由于c是绑定到常量的引用,其值不能改变。否则编译器会报错。

访问 string 对象中的单个字符有两种方式:

    1. 使用迭代器 (将在3.4节P95 和第9章中介绍)

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


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

for循环使用变量index作为s的下标,index的类型是由decltype关键字决定的。


提示:注意检查下标的合法性:下标必须大于等于 0 而小于字符串的 size() 的值。一种简便易行的方法是,总是设下标的类型为 string::size_type,因此此类型是无符号数,可以确保下标不会小于 0。此时,代码只需保证下标小于 size() 的值就可以了。


下标nstring::size_type类型,也就是无符号类型,所以n可以确保大于或等于 0。

标准库类型vector表示对象的几何,其中所有对象的类型都相同。因为vector“容纳着”其他对象,所以它也被称作容器(container)。

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

对于类模板来说,我们通过提供一些额外信息来指定模板到底实例化成什么样的类,需要提供哪些信息由模板决定。提供信息的方式:在模板名字后面跟一对尖括号,在括号内放上信息。

初始化vector对象的方法
v1 是一个空 vector,它潜在的元素是 T 类型的,执行默认初始化
v2 中包含有 v1 所有元素的副本
等价于 v2(v1),v2 中包含有 v1 所有元素的副本
v3 包含了 n 个重复的元素,每个元素的值都是 val
v4 包含了 n 个重复地执行了值初始化的对象
v5 包含了初始值个数的元素,每个元素被赋予相应的初始值

注意两个 vector 对象的类型必须相同:

练习3.12:下列 vector 对象的定义有不正确的吗?

列表初始化:用花括号括起来 0 个或多个厨师元素值被赋给 vector 对象:

回顾:C++定义了初始化的几种不同形式:P39
其中用花括号来初始化变量的这种初始化形式被称为列表初始化(list initialization)。

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

还可以用vector对象容纳的元素数量和所有元素的统一初始值来初始化 vector 对象:

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

列表初始值还是元素数量?

花括号 vs 圆括号:

上面其实只有 v5 是列表初始化。要想列表初始化对象,花括号里的值必须与元素类型相同。

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


关键概念:vector对象能高效增长
当所有(all)元素的值都一样时,在定义 vector 对象的时候有必要设定其大小;
一旦元素的值有所不同,更有效的办法是先定一个空的 vector 对象,再在运行时向其中添加具体值。
这跟 C 或 Java在创建对象时顺便制定其容量的做法恰恰相反。

vector对象添加元素蕴含的编程假定

注意:如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环。
WARNING: 范围for语句体内不应改变其所遍历序列的大小。

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

如果v不含有任何元素,返回真,否则返回假
v的尾端添加一个值为t的元素
返回v中第n个位置上元素的引用
v2中元素的拷贝替换v1中的元素
用列表中元素的拷贝替换v1中的元素
v1v2相等当且晋档它们的元素数量相同且对应位置的元素值都相同
顾名思义,以字典顺序进行比较

使用范围for语句处理 vector 对象中的所有元素:

第一个循环把控制变量 i 定义成引用类型,这样就能通过 i 给 v 的元素赋值,其中 i 的类型由 auto 关键字指定。
要使用size_type,需首先指定它是由哪种类型定义的。vector对象的类型总是包含着元素的类型:

计算vector内对象的索引

下标的类型也是相应的size_type类型。

举个例子,假设有一组成绩的集合,其中成绩的取值是从 0 到 100。以 10 分为一个分数段,要求统计各个分数段各有多少个成绩。
显然,从 0 到 100 总共有 101 种可能的成绩取值,这些成绩分布在 11 个分数段上:每 10 个分数构成一个分数段,这样的分数段有 10 个,额外还有一个分数段表示满分 100 分。
这样第一个分数段将统计成绩在 0 到 9 之间的数量;
第二个分数段将统计成绩在 10 到 19 之间的数量,以此类推。
最后一个分数段统计满分 100 分的数量。
按照上面的描述,如果输入的成绩如下:

成绩在30分以下的没有、

执行计数值累加的那条语句很好地体现了 C++ 程序代码的简洁性。

另一种方法:用迭代器(iterator)改写该程序并实现完全相同的功能。(练习 3.25)

不能用下标形式添加元素


ivec是一个空vector,根本不包含任何元素,当然也就不能通过下标去访问任何元素。正确的方法是使用push_back

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

通过下标访问不存在的元素会产生很严重的后果,所谓的缓冲区溢出(buffer overflow)指的就是这类错误。

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

之前学习了使用下标运算符访问 string 对象的字符或 vector 对象的元素;
使用**迭代器(iterator)**也可以实现同样的目的。
所有标准库容器都可以使用迭代器,但是其中只有少数几种才同时支持下标运算符。

有迭代器的类型同时拥有返回迭代器的成员:

  • begin,负责返回指向第一个元素(或第一个字符)的迭代器;
  • 如果容器为空,则beginend返回的是同一个迭代器,都是尾后迭代器。

标准容器迭代器的运算符
返回迭代器iter所指元素的引用
解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem
iter指示容器中的下一个元素
iter指示容器中的上一个元素
判断两个迭代器是否相等(不相等),如果两个迭代器指示的是同一个元素或者它们是同一个容器的尾后迭代器,则相等;反之,不相等。

如果两个迭代器指向的元素相同或者都是同一个容器的尾后迭代器,则它们相等;否则就说这两个迭代器不相等。

通过解引用迭代器来获取它所指示的元素:


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

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


循环部分首先用s.begin的返回值来初始化it,意味着it指示的是s中的第一个字符(如果有的话)。
条件部分检查是否已到达s的尾部,如果尚未到达,则将it解引用的结果传入isspace函数检查是否遇到了空白。

关键概念:泛型编程:C++程序员习惯性地使用!=而非<,是因为只有 string 和 vector 等一些标准库类型有下标运算符,大多数都没有定义<运算符,但是所有标准库容器的迭代器都定义了==!=。所以要养成使用迭代器和!=的习惯。

就像不知道stringvectorsize_type成员到底是什么类型一样,一般来说我们也不知道或无须知道迭代器的精确类型。

实际上,那些拥有迭代器的标准库类型使用iteratorconst_iterator来表示迭代器的类型。

结合解引用和成员访问操作

解引用迭代器可获得迭代器所指的对象,如果该对象的类型恰好是类,就有可能希望进一步访问它的成员。

对于一个由字符串组成的vector对象来说,要想检查其元素是否为空,令it是该vector对象的迭代器,只需检查it所指字符串是否为空就可以了:(*it).empty()

注意:(*it).empty() 圆括号必不可少。该表达式的含义是先对it解引用,然后解引用的结果再执行点运算符。如果不加圆括号,点运算符将由it来执行,而非it解引用的结果

上面第二个表达式的含义是从名为it的对象中寻找其empty成员,显然it是一个迭代器,它没有哪个成员叫empty的,所以第二个表达式将发生错误。

为了简化,箭头运算符(->)解引用成员访问两个操作结合在一起,也就是说,it->mem(*it).mem表达的意思相同。

例:假设用一个名为 text 的字符串向量存放文本文件中的数据,其中的元素或者是一句话或者是一个用于表示段落分隔的空字符串。如果要输出 text 中第一段的内容,可以利用迭代器写一个循环令其遍历 text,直到遇到空字符串的元素为止:


值得注意的是,因为循环从头到尾只是读取 text 的元素而未向其中写值,所以使用了cbegincend来控制整个迭代过程。

练习 3.22:修改之前那个输出 text 第一段的程序,首先把 text 的第一段全都改成大写形式,然后再输出它。

练习3.23:编写一段程序,创建一个含有 10 个整数的 vector 对象,然后使用迭代器将所有元素的值都变成原来的两倍。

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

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

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

只要两个迭代器指向的是同一个容器中的元素或者尾元素的下一位置,就能将其相减,所得结果是两个迭代器的距离。所谓举例指的是右侧的迭代器向前移动多少位置就能追上左侧的迭代器,其类型是名为 difference_type 的带符号整型数。string 和 vector 都定义了 difference_type,因为这个举例可正可负,所以

使用迭代器运算的一个经典算法是二分搜索。


循环部分先检查搜索范围是否为空,如果 mid 和 end 的当前值相等,说明已经找遍了所有元素。此时条件不满足,循环终止。当搜索范围不为空,可知 mid 指向了某个元素,检查该元素是否就是我们所要搜索的,如果是,也终止循环。

循环过程终止时,mid或者等于end或者指向要找的元素。如果mid等于end,说明 text 中没有我们要找的元素。P101

C++并没有定义两个迭代器的加法运算,实际上直接把两个迭代器加起来是没有意义的。

与之相反,C++定义了迭代器的检法运算,两个跌大气相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动多少个元素后可以得到左侧的迭代器,参与运算的两个跌大气必须指向同一个容器中的元素或尾后元素。

另外,C++还定义了迭代器与整数的加减法运算,用以控制迭代器在容器中左右移动。

在本题中,因为迭代器的加法不存在,所以mid = (beg + end) / 2;不合法。
mid = beg + (end - beg) / 2;的含义是,先计算end - beg的值得到容器中的元素个数,然后控制迭代器从开始处向右移动二分之一容器的长度,从而定位到容器正中间的元素。

练习 3.38:将两个指针相加不但是非法的,而且也什么意义。请问为什么两个指针相加没什么意义?110:

【出题思路】与标准库vector类似,C++也为指针定义了一系列算术运算,包括递增、递减、指针求差、指针与整数求和等,但是并没有定义两个指针的求和运算。
要想理解这一规定,必须首先明白指针的含义。

指针也是一个对象,与指针相关的属性有 3 个,分别是指针本身的值(value)、指针所指的对象(content)、以及指针本身在内存中的存储位置(address)。它们的含义分别是:

  • 指针本身的值是一个内存地址值,表示指针所指对象在内存中的存储地址;
  • 指针所指的对象可以通过解引用指针访问;
  • 因为指针也是一个对象,所以指针也存储在内存的某个位置,它有自己的地址,这也是为什么有“指针的指针”的原因。

通过上述分析我们知道,指针的值是它所指对象的内存地址,如果我们把两个指针加在一起,就是试图把内存中两个对象的存储地址加在一起,这显然是没有任何意义的。
与之相反,指针的减法是有意义的。如果两个指针指向同一个数组中的不同元素,则它们相间的结果表征了它们所指的元素在数组中的距离。

练习3.24:请使用迭代器重做3.3.3节(第94页)的最后一个练习(P101)。


练习 3.25:3.3.3节(第93页)划分分数段的程序是使用下标运算符实现的,请利用迭代器改写该程序并实现完全相同的功能。



与 vector 相似的地方是,数组也是存放类型相同的对象的容器;
与 vector 不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。
Tip: 所以,如果不清楚元素的确切个数,请使用 vector。

练习 3.29:相比于vector来说,数组有哪些缺点,请列举一些。

解答:数组与vector的相似之处是hi都能存放类型相同的对象,且这些对象本身没有名字,需要通过其所在位置访问。

数组与vector的最大不同是,数组的大小固定不变,不能随意向数组种增加额外的元素,虽然在某些情景下运行时性能较好,但是与vector相比损失了灵活性。

  • 数组的维度在定义时已经确定,如果我们想更改数组的长度,只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组中去。
  • 我们也无法像vector那样使用size函数直接获取数组的维度。
    • 如果是字符数组,可以调用strlen函数得到字符串的长度;

3.5.1 定义和初始化内置数组

数组的声明如 a[d],其中a是数组的名字,d是数组的维度。维度说明了数组中元素的个数,因此必须大于0
数组种元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的。也就是说,维度必须是一个常量表达式。

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

练习3.27:假设 txt_size 是一个无参数的函数,它的返回值是 int。

可以对数组的元素进行列表初始化,此时允许忽略数组的维度。
如果在声明时没有指明维度,编译器会根据初始值的数量计算并推测出来;
相反,如果指明了维度,那么初始值的总数量不应该超出指定的大小。

字符数组有一个额外的初始化形式,我们可以用字符串字面值对此类数组初始化。
当使用这种方式时,一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其他字符一样被拷贝到字符数组中去:

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

(数组本身是对象,所以允许定义数组的指针和数组的引用)

类型修饰符默认情况下从右向左依次绑定。
对于ptrs来说,从右向左理解其含义:首先知道我们定义的是一个大小为10 的数组,它的名字是ptrs,然后知道数组种存放的是指向int的指针。

当遇到指针指向数组或引用指向数组,由内往外阅读。
对于Parray来说,因为数组多维度是紧跟着被声明的名字的,所以这里应由内向外阅读。
首先是圆括号括起来的部分,*Parray意味着Parray是个指针,
接下来观察右边,可知道Parray是个指向大小为10的数组指针,
最后观察左边,知道数组种的元素是int
所以含义为:Parray是一个指针,它指向一个int数组,数组种包含10个元素。
同理,(&arrRef)表示arrRef是一个引用,它引用的对象是一个大小为10的数组,数组种元素的类型是int

按照由内而外的顺序阅读,首先知道arry是一个引用,
然后观察右边知道,arry引用的对象是一个大小为10的数组,
最后观察左边知道,数组的元素类型是指向int的指针。
所以含义为;arry是一个含有10int型指针的数组的引用。

数组的元素也能使用范围for语句或下标付来访问。
在使用数组下标的时候,通常将其定义为**size_t**类型。size_t是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小。
cstddef头文件中定义了size_t类型,这个文件是C标准库stddef.h头文件的C++语言版本。



  • scores 的声明有不同,这里 scores 是一个含有 11 个无符号元素的数组。
    • 该程序对 scores 执行了列表初始化,为所有元素赋初值为 0,这样在后续统计时会从 0 开始计算各个分数段的人数,是正确的做法。
    • 如果不初始化 scores,则该数组会含有未定义的数值,这是因为 scores 是定义在函数内部的整形数组,不会执行默认初始化。
  • 所用的下标运算符是由 C++ 语言直接定义的,这个运算符能用在数组类型的运算对象上,而上面的vector程序中所用的下标运算符是库模板 vector 定义的,只能用于 vector 类型的运算对象。

与 vector 和 string 一样,当需要遍历数组的所有元素时,最好的办法也是使用范围 for 语句。

练习 3.32:将上一题刚刚创建的数组拷贝给另外一个数组。


WARNING: 大多数常见的安全问题都源于缓冲区溢出错误。当数组或其他类似数据结构的下标越界并试图访问非法内存区域时,就会产生此类错误。

通过数组名字或者数组中首元素的地址都能得到指向首元素的指针:

  • 通常情况下,使用取地址符来获取指向某个对象的指针,取地址符可以用于任何对象。对数组使用取地址符就能得到指向该元素的指针
  • 数组还有一个特性:编译器都会自动地将其替换为一个指向数组首元素的指针
  • 当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组:

尽管ia是由 10 个整数构成的数组,但当使用ia作为初始值时,编译器实际执行的初始化过程类似于下面的形式:

  • 必须指出的是,当使用decltype关键字时,上述转换不会发生,decltype(ia)返回的类型是由 10 个整数构成的数组:

vector 和 string 的迭代器支持的运算,数组的指针全都支持。


先获取到指向数组第一个元素的指针和指向数组尾元素的下一位置的指针。

arr有 10 个元素,尾元素所在位置的索引是9,接下来那个不存在的元素唯一的用处就是提供其地址用于初始化e
就像尾后迭代器一样,尾后指针也不指向具体的元素。因此不能对尾后指针执行解引用或递增的操作。


尽管能计算得到尾后指针,但这种用法极易出错。
为了让指针的使用更简单、更安全,C++ 新标准引入了两个名为beginend的函数。
begin函数返回指向ia首元素的指针,end函数返回指向ia尾元素下一位置的指针,这两个函数定义在iterator头文件中。
这两个函数与容器中的两个同名成员功能类似,不过数组毕竟不是类类型,因此这两个函数不是成员函数。
正确的使用形式是将数组作为它们的参数

使用beginend可以很容易地写出一个循环并处理数组中的元素。例如,假设arr是一个整形数组,下面的程序负责找到arr中的第一个负数:


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

新指针指向的元素与原来的指针相比前进了(后退了)该整数值个位置:

注意:当给arr加上sz时,编译器自动地将arr转换成指向数组arr中首元素的指针。
执行加法后,指针从首元素开始向前移动了sz(这里是 5)个位置,指向新位置的元素。
也就是说,它指向了数组arr尾元素的下一位置。


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

只要两个指针指向同一个数组的元素,或者指向该数组的尾元素的下一位置,就能利用关系运算符对其进行比较。
如果两个指针分别指向不相关的对象,则不能比较它们:

注:上述指针运算同样适用于空指针所指对象并非数组的指针
如果p是空指针,允许给p加上或减去一个值为0的整型常量表达式。两个空指针也允许彼此相减,结果当然是0

解引用和指针运算的交互

指针加上一个整数所得的结果还是一个指针。
假设结果指针指向了一个元素,则允许解引用该结果指针:

在很多情况下使用数组的名字其实用的是一个指向数组首元素的指针。
当对数组下标运算符时,编译器会自动执行上述转换操作。

ia[0] 是一个使用了数组名字的表达式,对数组执行下标运算其实是对指向数组元素的指针执行下标运算。

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

注意:数组与标准库类型 string 和 vector 是有所不同的:

标准库类型 string 和 vector 限定使用的下标必须是无符号类型,
而内置的下标运算无此要求。上面的最后一个例子很好地说明了这一点。内置的下标运算符可以处理负值。

WARNING: 内置的下标运算符所用的索引值不是无符号数,这一点与 vector 和 string 不一样。

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

尽管 C++ 支持C风格字符串(C-style character string),但在 C++ 程序中最好还是不要使用它们。这是因为 C 风格字符串不仅使用起来不太方便,而且极易引发程序漏洞,是诸多安全问题的根本原因。

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

C 语言标准库提供的一组函数,这些函数可用于操作 C风格字符串,它们定义在 cstring 头文件中,cstring是 C语言头文件 string.h 的C++版本。

返回 p 的长度,空字符不计算在内

传入此类函数的指针必须指向以空字符作为结束的数组:

ca 虽然也是一个字符数组,但它不是以空字符作为结束的,因此上述程序产生未定义的结果。
strlen 函数将有可能沿着 ca 在内存中的位置不断向前寻找,直到遇到空字符才停下来。

练习3.37:以列表初始化方式赋值的C风格字符串与以字符串字面值赋值的有所区别:


比较两个 C风格字符串的方法和之前学习过的比较标准库 string 对象的方法大相径庭。

  • (1) 比较标准库 string 对象的时候,用的是普通的关系运算符和相等性运算符:
  • (2) 比较两个 C 风格字符串时,实际比较的将是指针而非字符串本身:

  • (3) 要想比较两个 C风格字符串 需要调用 strcmp 函数,此时比较的久不再是指针了。
    如果两个字符串相等,strcmp返回0
    如果前面的字符串较大,返回正值;如果后面的字符串较大,返回负值。

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


目标字符串的大小由调用者指定

连接或拷贝 C风格字符串也与标准库string对象的同类操作差别很大。
要想把刚刚定义的那个 string对象s1s2连接起来,可以直接写成下面的形式:


同样的操作如果放到 ca1ca2 这两个数组身上就会产生错误。表达式 ca1 + ca2 试图将两个指针相加,显然这样的操作没什么意义,也肯定是非法的。


C风格字符串的操作函数定义在cstring头文件中。

  • strcpy函数负责把字符串的内容拷贝给另一个字符串
  • strcat函数则负责把字符串的内容拼接到另一个字符串之后
  • strlen函数用于计算字符串的长度

需要注意的是,利用指腹从字面值常量初始化C风格字符串时,默认在数组最后添加一个空字符,因此strlen的计算结果比字面值显示的字符数量多1

为了细致起见,计算两个字符串拼接后的长字符串长度时,应该在两个字符串各自长度求和后减去1,即减去1个多余空字符所占的额外空间。

Tip: 对大多数应用来说,使用标准库string要比使用 C风格字符串更安全、更高效。

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

3.5.5 与旧代码的接口

一般情况下,任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代:

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

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

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

使用数组初始化 vector 对象

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

用于创建ivec的两个指针实际上指明了用来初始化的值在数组int_arr中的位置,
其中第二个指针应指向待拷贝区域尾元素的下一位置。
此例中,使用标准库beginend来分别计算int_arr的首指针和尾后指针。

用于初始化vector对选哪个的值也可能仅是数组的一部分:


建议:尽量使用标准库类型而非数组:

现代 C++ 程序应当尽量使用vector和迭代器,避免使用内置数组和指针;
应该尽量使用string,避免使用C风格的基于数组的字符串。

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

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

多维数组是数组的数组,一个维度表示数组本身大小,另外一个维度表示其元素(也是数组)大小:
对于二维数组来说,常把第一个维度称作行,第二个维度称作列。

多维数组的每一行分别用花括号括了起来:


如果仅仅想初始化每一行的第一个元素,如下:

如果再省略掉内层的花括号,结果就大不一样:



程序中经常会用到两层嵌套的for循环来处理多维数组的元素:

使用范围 for 语句处理多维数组

用范围for循环重写上面的代码:

因为要改变元素的值,所以得把控制变量rowcol声明成引用类型。
其实还有一个深层次的原因,就是如果不是引用类型,编译器初始化时会自动将这些数组形式的元素转换成指向该数组内首元素的指针。

这个循环中并没有任何写操作,课时我们还是将外层循环的控制变量声明成了引用类型,这是为了避免数组被自动转成指针。假设不是引用类型,则循环如下述形式:

程序无法通过贬义,因为row不是引用类型,所以编译器初始化row时会自动将这些数组形式的元素(和其他类型的数组一样)转换成指向该数组内首元素的指针。这样得到的row的类型就是int*,显然内层的循环就不合法了,编译器将试图在一个int*内遍历。

Note: 要使用范围for语句处理多维数组,除了嘴内层的循环外,其他所有循环的控制变量都应该是引用类型。

因为多维数组实际上是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针:

首先明确(*p)意味着p是一个指针。
接着观察右边发现,指针p所指的是一个维度为4的数组;
再观察左边,数组中的元素是整数。
因此,p是指向含有4个整数的数组的指针。

通过使用auto或者declytpe就能尽可能地避免在数组前面加上一个指针类型:


外层的for循环首先声明一个指针p并令其指向ia的第一个内层数组,然后依次迭代直到ia的全部3行都处理完为止。
其中递增运算++p负责将指针p移动到ia的下一行。

内层的for循环负责输出内层数组所包含的值。它首先令指针q指向p当前所在行的第一个元素。
*p是一个含有4个整数的数组,数组名被自动地转换成指向该数组收元素的指针。
内层for循环不断迭代直到我们处理完了当前内层数组的所有元素为止。为了获取内层for循环的终止条件,再一次解引用p得到指向内层数组首元素的指针,给它加上4就得到了终止条件。

使用标准库函数beginend也能实现同样的功能,而且看起来更简洁一些:


这一版本的程序中,循环终止条件由end函数负责判断。
虽然我们也能推断出p的类型是指向含有4个整数的数组的指针,q的类型是指向整数的指针,但是使用auto关键字我们就不必再烦心这些类型到底是什么了。

练习3.43:编写 3 个不同版本的程序,令其均能输出 ia 的元素。

类型别名简化多维数组的指针

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

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



在查询中的每个表会输出一行,如果有两个表通过 join 连接查询,那么会输出两行

会在 explain 的基础上额外提供一些查询优化的信息。紧随其后通过 show warnings 命令可 以得到优化后的查询语句,从而看出优化器优化了什么。额外还有 filtered 列,是一个百分比的值,

演示filtered列的计算公式:

相比 explain 多了个 partitions 字段,如果查询是基于分区表的话,会显示查询将访问的分区。11

/!../ 是一种特殊的注释,其他的数据库产品当然不会执行。mysql特殊处理,会选择性的执行。可以认为是:预编译中的条件编译。特别注意 50100,它表示5.01.00 版本或者更高的版本,才执行。

select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序。
id的情况有三种,分别是:

  • id相同表示加载表的顺序是从上到下。
  • id不同id值越大,优先级越高,越先被执行。
  • id有相同,也有不同,同时存在。id相同的可以认为是一组,从上往下顺序执行;在所有的组中,id的值越大,优先级越高,越先执行。id为NULL最后执行

select_type 表示对应行是简单还是复杂的查询。

简单查询。查询不包含子查询和union

复杂查询中最外层的 select

包含在 select 中的子查询(不在 from 子句中)

包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表(derived的英文含义)

mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表

mysql能对查询的某部分进行优化并将其转化成一个常量(可以看show warnings 的结果)。用于 primary key 或 unique key 的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。system是 const的特例,表里只有一条元组匹配时为system

primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。这可能是在 const 之外最好的联接类型了,简单的 select 查询不会出现这种 type。

相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会 找到多个符合条件的行。
a)简单 select 查询,name是普通索引(非唯一索引)

扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接 对二级索引的叶子节点遍历和扫描,速度还是比较慢的,**这种查询一般为使用覆盖索引,二级索引一般比较小,所占磁盘空间更小,**所以这种通常比ALL快一些。

即全表扫描,扫描你的聚簇索引的所有叶子节点。通常情况下这需要增加索引来进行优化了。

这一列显示查询可能使用哪些索引来查找。 explain 时可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,mysql认为索引 对此查询帮助不大,选择了全表查询。 如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查 where 子句看是否可以创造一个适当的索引来提 高查询性能,然后用 explain

这一列显示mysql实际采用哪个索引来优化对该表的访问。 如果没有使用索引,则该列是 NULL。如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用 force index、ignore index。

这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。 举例来说,film_actor的联合索引 idx_film_actor_id 由 film_id 和 actor_id 两个int列组成,并且每个int是4字节。通 过结果中的key_len=4可推断出查询使用了第一个列:film_id列来执行索引查找。

字符串,char(n)和varchar(n),5.0.3以后版本中,**n均代表字符数,而不是字节数,**如果是utf-8,一个数字 或字母占1个字节,一个汉字占3个字节

  • char(n):如果存汉字长度就是 3n 字节
  • varchar(n):如果存汉字则长度是 3n + 2 字节,加的2字节用来存储字符串长度,因为 varchar是变长字符串

如果字段允许为 NULL,需要1字节记录是否为 NULL
索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索 引。

这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:film.id)

这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。

这一列展示的是额外信息。常见的重要值如下:

覆盖索引定义:mysql执行计划explain结果里的key有使用索引,如果select后面查询的字段都可以从这个索引的树中 获取,这种情况一般可以说是用到了覆盖索引,extra里一般都有using index;覆盖索引一般针对的是辅助索引,整个 查询结果只通过辅助索引就能拿到结果,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其它字段值

使用 where 语句来处理结果,并且查询的列未被索引覆盖

查询的列不完全被索引覆盖,where条件中是一个前导列的范围;

mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索 引来优化。

将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一 般也是要考虑使用索引来优化的。
1. actor.name未创建索引,会浏览actor整个表,保存排序关键字name和对应的id,然后排序name并检索行记录

使用某些聚合函数(比如 max、min)来访问存在索引的某个字段时

如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。

2.3 不在索引列做计算

不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描

给hire_time增加一个普通索引:

转化为日期范围查询,有可能会走索引:

2.4 存储引擎不能使用索引中范围条件右边的列

2.5 尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少 select * 语句

< 小于、 > 大于、 <=、>= 这些,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引

mysql8的情况下会走索引:

2.8 like以通配符开头(‘$abc…’)mysql索引失效会变成全表扫描操作

问题:解决like’%字符串%'索引不被使用的方法?
a)使用覆盖索引,查询字段必须是建立覆盖索引字段

b)如果不能使用覆盖索引则可能需要借助搜索引擎

2.9 字符串不加单引号索引失效

2.10 少用or或in,用它查询时,mysql不一定使用索引,mysql内部优化器会根据检索比例、表大小等多个因素整体评 估是否使用索引

2.11 范围查询优化

没走索引原因:mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引。比如这个例子,可能是 由于单次数据量查询过大导致优化器最终选择不走索引
优化方法:可以将大的范围拆分成多个小范围

2.12 索引使用总结

我要回帖

更多关于 美德 V.I.R.T.U.E.S 的文章

 

随机推荐