字符串、向量、数组

文章目录
  1. 1. 命名空间的using声明
  2. 2. 标准库string
    1. 2.1. 定义和初始化string对象
      1. 2.1.1. 直接初始化和拷贝初始化
    2. 2.2. string对象上的操作
      1. 2.2.1. 读取string对象
      2. 2.2.2. 使用getline读取一行
      3. 2.2.3. string的empty、size操作
    3. 2.3. 处理string对象中的字符
      1. 2.3.1. 范围for语句(range for)
  3. 3. 标准库vector
    1. 3.1. 定义、初始化vector对象
    2. 3.2. 向vector对象中添加元素
    3. 3.3. 其他vector操作
  4. 4. 迭代器
    1. 4.1. 使用迭代器
    2. 4.2. 迭代器运算
  5. 5. 数组
    1. 5.1. 定义、初始化内置数组
    2. 5.2. 访问数组元素
    3. 5.3. 指针和数组
    4. 5.4. C分格字符串
    5. 5.5. 与旧代码的接口
  6. 6. 多维数组

主要内容:

  1. 命令空间的using声明
  2. 标准库类型string
  3. 标准库类型vector
  4. 迭代器介绍
  5. 数组
  6. 多维数组

基本类型体现了大多数计算机硬件本身具备的能力。标准库定义了另外一组具有更高级性质的类型。他们尚未直接实现到计算机硬件中。

命名空间的using声明

std::cin中作用域运算符的含义:编译器从操作符左侧的名字所示的作用域中寻找右侧那个名字。

using声明的形式如下:

1
using namespace::name;

using 声明每次只能引入一个名字。using 声明必须以分号结束。

头文件不应该包括using声明。

标准库string

C++标准一方面对库类型所提供的操作做了详细的规定、另一方面也对库的实现者做了性能的要求。

定义和初始化string对象

如何初始化对象由类的本身决定的。

初始化sting对象的方式

初始化形式 说明
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’) 初始化为连续n个字符c组成的字符串

直接初始化和拷贝初始化

  1. 拷贝初始化(copy initialization):使用等号(=)初始化一个变量
  2. 直接初始化:不用等号

当初始值只有一个时,使用直接初始化和拷贝初始化都行,如果用到多个初始值,一般来说只能直接初始化。如果非要用拷贝初始化也可以,需要显示的创建一个临时对象用于拷贝。

string对象上的操作

一个类除了规定初始化对象的方式外,还要定义对象上所能执行的操作。

操作示例 说明
os<<s 将s写到输出流os当中,返回os
is>>s 从is中读取字符串赋给s,字符串以空白介绍,返回s
getline(is,s) 从is中读取一行赋给s,返回is
s.empty s为空返回true,否则返回false
s.size 返回s中字符的个数
s[n] 返回s中第n个字符的引用
s1+s2 返回s1、s2连接后的结果
s1=s2 用s2的副本代替s1中原来的字符
s1 == s2 如果s1和s2所含的字符完全一样,则他们相等
s1 != s2
<,<=,>,>= 利用字符在字典中的顺序进行比较

读取string对象

string对象会自动忽略开头的空白(空格,换行、制表符)并从第一个真正的字符开始算起,知道遇到下一处空白为止。

sting对象的此类输入输出操作返回运算符左侧的运算对象,所以,如果多个输入或者多个输出可以连在一起写。

使用getline读取一行

getline函数从给定的输入流中读入内容,直到遇到换行符为止,换行符的内容也被读入进来。然后将所读的内容从存入到string中。

触发getline函数返回的那个换行符实际上被丢弃了,得到的string对象中不包含该换行符。

string的empty、size操作

size返回的是string:size_type

string的相等意味着长度相同,且所包含的字符也完全相等。

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

处理string对象中的字符

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是空白时为真(空格、制表符、回车、换行、进制符)
issupper(c) 当c是大写字母时为真
isxdigit(c) 当c是十六进制数字时为真
tolower(c) 如果c是大写字母,输出对应的小写字母,否则原样输出
toupper(c) 如果c是小写字母,输出对应的大小字母,否则原因输出

c语言的头文件形如name.h,在C++中将这些文件命名为cname,也就是去掉了.h后缀,在name名字前添加了字母c,这里的c表示是一个属于c语言标准库的头文件。cnmae的头文件中定义的明智属于命名空间std。而定义在.h的名字则不在。

范围for语句(range for)

1
2
for (declaration : expression)
statement

其中expression部分是一个对象,表示序列。declaration是定义一个变量,用于访问序列中的基础元素。每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值。

1
2
3
4
5
for ( auto c : str)
{
cout<< c << endl;
}
///对字符串c的每个字符c做某种操作

如果想改变string对象中的字符的值,必须把循环变量定义成引用类型。这个变量实际上绑定到序列中的每个元素。

下标运算符:通过位置,返回该位置上字符的引用。 使用超出下标范围将引发不可预知的结果,使用下标访问空string也引发不可预知结果。

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

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

标准库vector

标准库类型vector表示对象的集合,其中所有对象的类型都相同,集合中每个对象都有一个与之对应的索引,索引可以访问对象。因为vector“容纳着”其他对象,所以,所以他被称为容器(container)。

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

提供信息的方式:即在类模板名字后面跟一对尖括号,括号内放类型信息。

vector是模板,而不是类。

定义、初始化vector对象

|初始化示例|说明|
|vector v1| v1是一个空vector,他潜在的元素是T类型,执行默认初始化|
|vector v2(v1)|v2中包含v1所有元素副本|
|vector v2 = v1|等价于v2(v1)|
|vector v3(n,val)|v3包含那个重复的元素。每个元素的值是val|
|vector v4(n)|v4 包含了n个重复的执行了值初始化的元素|
|vector v5{a,b,c,…}|v5包含了初始值个数的元素,每个元素被赋予相应的初始值|
|vector v5 = {a,b,c,…}|等价于v5{a,b,c,…}|

列表初始化vector对象。

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

如果是内置类型,如int,元素初始值自动设置为0,如果是类类型,比如string,元素由类默认初始化。

如果只提供元素的数量而没有设定初始值,只能使用直接初始化。不让就是将数量拷贝给string对象了。

初始化过程中,尽可能地把花括号内的值当做是元素初始值列表处理,但是如果花括号形式所提供的值不能进行列表初始化,就要考虑用这样的值构造vector对象。

1
2
3
4
vector<string> v5{"hi"} //列表初始化,v5有一个元素
vector<string> v6("hi"); //错误不能构建
vector<string> v7{10}; //v7有10个默认初始化的元素
vector<string> v8{10,"hi"}; //v8有10个值为“hi” 的元素

在确认无法执行列表初始化后,编译器会尝试用默认值初始化vector对象。

向vector对象中添加元素

vector对象能高效的增长,如果初始化的时候指定大小,可能效率会低。

范围for循环体内不应改变其所遍历序列的大小。

其他vector操作

vector操作 说明
v.empty() 如果v不含有任何元素,返回真,否则,返回假
v.size() 返回v中元素个数
v.push_back(t) 向v的尾部添加一个值为t的元素
v[n] 返回v中第n个位置上元素的引用
v1 = v2 用v2中元素的拷贝替换v1中的元素
v1 = {a,b,c…} 用列表中元素的拷贝替换v1中的元素
v1== v2 元素数目相同、对应位置上的元素值相同
v1 != v2
< ,<=, > ,>= 以字典的顺序进行比较

vector对象的类型总是包含元素的类型。只有当元素的类型可以比较时,vector对象才能比较。vector可以使用下标读写元素,不能通过下标添加元素。
只能对已经存在的元素

缓冲区溢出(buffer overflow) 就是指的下标越界这类错误。

迭代器

类似指针类型,迭代器提供了对对象的间接访问。就迭代器而言,其对象是容器中元素或者string对象中的字符。

和指针不同,获取迭代器不是使用取地址符,有迭代器的类型,同时拥有返回迭代器的成员。比如begin、end成员。

  1. begin成员负责返回指向第一个元素的迭代器。
  2. end成员负责返回指向容器 尾元素的下一个位置的迭代器。
  3. end成员返回的迭代器通常称为尾后迭代器。
  4. 如果容器为空,则begin、end返回的是同一个迭代器。都是尾后迭代器。

使用迭代器

迭代器的运算符如下:

运算符 说明
*iter 返回迭代器所指元素的引用
iter->mem 解引用iter并获取该元素的名为mem的成员,等价于(*iter)->mem
++iter 令iter指示容器的下一个元素
–iter 令iter指示容器的上一个元素
iter1 == iter2 判断两个元素是否相等,如果两个迭代器指示的是同一个元素,或者他们是同一个容器的尾后迭代器,则他们相等,否则不行等
iter1 != iter2

指针和迭代器的相同点:

  1. 可以解引用来获取它所指元素
  2. 也有有效和无效之分,有效:迭代器指向某个元素,或者指向容器中尾元素的下一个位置,其他所有情况都无效
  3. 提供了对对象的间接访问
  4. 都可以移动到下一个元素、上一个元素
  5. 视图解引用非法迭代器或者尾后迭代器都是未定义的, 解引用非法指针也是非法的

指针和迭代器的不同点:

  1. 获取方式不同,指针:取地址; 迭代器:通过容器的成员。

迭代器使用实例

1
2
3
4
5
6
string s("some string");
if(s.begin() != s.end())
{
auto it = s.begin();
*it = touper(*it);
}

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

1
2
3
4
for(auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
{
*it = toupper(*ite);
}

只有string、vector等一些标准库类型有下标运算符,而并非全部如此,与之类似,所有标准库的迭代器都定义了==、!=,但是他们中的大多数没有定义< 运算符,因此, 我们要养成使用迭代器和!=运算符的习惯。

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

const_iterator和常量指针差不多。能读取,不能修改。

如果vector、string对象是一个常量,只能使用const_iterator,如果vector、string不是常量,既能使用iterator,也能使用const_iterator

begin和end运算符: begin和end返回的具体类型由对象是否常量觉得,如果对象是常量,begin和end返回const_iterator,如果不是,返回iterator。

1
2
3
4
vector<int> v1;
const vector<int > v2;
auto iter1 = v1.begin(); //iter1的类型是vector<int>::iterator
auto iter2 = v2.begin(); //iter2 的类型是vector<int>::const_iterator

cbegin()、cend() 返回const_iterator类型迭代器。

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

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

迭代器运算

迭代器运算 说明
iter + n 迭代器加上整数值,扔是一个迭代器类型。迭代器指示的新位置与原来的位置相比,向前移动了n个元素。结果迭代器或者指向容器的下一个元素,或者指向容器尾元素的下一个位置
iter - n
iter += n
iter1= n
iter 1 - iter2
<, <=, >,>=

两个迭代器的间距的类型是diffrence 类型。带符号的。

数组

定义、初始化内置数组

数组中元素的个数也属于数组类型的一部分,编译的时候纬度应该已知。

1
int *parr[10]  ; //含有10个整形指针的数组。

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

定义数组的时候必须指定数组的类型。不允许使用auto关键字由初始值的列表来推断类型。

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

字符数组的特殊性: 一定要注意,字符串字面值的结尾还有一个空字符,这个空字符也会像字符串的其他字符一样被拷贝到字符数组中。

1
char a[] = "C++" ; // 容量4

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

1
2
3
int a[] = {1,2,3};
int a2[] = a; //错误不能用一个数组初始化另一个数组
a2 =a; //错误不能把一个数组赋值给另一个数组

理解复杂数组声明

1
2
3
4
int *ptrs[10]; //ptrs是含有10个整数指针的数组
int &refs[10]; //错误,不存在引用的数组
int (*parray)[10]; //parray指向一个含有10个整数的数组
int (&arrRef)[10];//parray引用一个含有10个整数的数组

对数组而言,由内向外阅读比较合适。parray是一个指针,指向一个含有10个元素的数组,数组的元素类型是整形。

访问数组元素

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

指针和数组

数组的特性:在很多用到数组名字的地方,编译器都会自动的将其替换为一个指向数组首元素的指针。
当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组。

decltype不会执行数组到指针的转化。

指针也是迭代器

标注库函数begin、end。由于数组不是类类型,因此这两个函数不是成员函数。begin函数返回指向数组首元素的指针,end函数返回指向数组尾元素下一个位置的指针。

尾后迭代器不能执行解引用和递增操作。

两个指针递减的结果类型是一种名为ptrdiff_t的标准库类型。定义在cstddef头文件中。

很多情况下,使用数组的名字,其实用的是一个指向数组首元素的指针。

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

C分格字符串

c分格字符串的函数 说明
strlen() 返回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

当使用数组的时候其实真正用的是指向数组首元素的指针。所以比较字符串实际上比较的是指针。

与旧代码的接口

  1. 允许使用 以空字符结束的字符数组来初始哈动态string对象或者为string对象赋值
  2. 在string对象的加法运算中,允许使用空字符结束的字符数组作为其中一个运算对象。

string对象的c_str函数返回一个C风格的字符串。

可以使用数组初始化vector对象。

多维数组

严格来说,C++语言没有多维数组,通常所说的多维数组其实是数组的数组。

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

1
2
using int_ary = int[4];  //int_ary 是含有4个整型元素的数组
typedef int int_ary[4];