using 声明(using declaration)
使用using声明可以省略专门的前缀(形如命名空间::)
using std::cin;
using std::cout:
using std::endl;
注:头文件中不应包含using声明,避免不经意间包含的名字产生始料未及的冲突。
标准库类型string
标准库类型string:表示可变长的字符串序列
#include <string>
using std::string
使用string类型必须首先包含string头文件,string定义在命名空间std中。
string s1;
string s2 = s1;
string s2(s1);
string s3("value");
string s3 = "value";
string s4(n, 'c');
string s4 = string(n,'c'); //多字符的拷贝初试化 不建议使用
string支持的大多说操作:
getline函数
函数从给定的输入流中读入内容,直到遇到换行符为止(换行符也被读入),将读入内容(不包括换行符)放入string对象中去
string::size_type类型
size函数返回的是一个string::size_type类型的值,解释如下:
string类及其他大多数标准库类型都定义了几种配套的类型。这些配套的类型体现了标准库类型与机器无关的特性,类型size_type就是其中一种。在具体使用时,通过作用域操作符表示size_type是在类string中定义的。
auto len = line.size();
C++11支持,运行编译器通过auto或者decltype来推断变量类型。
字面值和string对象相加
string s1 = "hello" , s2("world");
string s3 = s1 + "'" + s2 + '\n';
把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)的两侧的运算对象至少有一个string
string s = s1 + “.” + "world"; //正确
string s = “.” + "world" + s1; //错误
因为标准库运行把字符字面值和字符串字面值转换成string对象,所以在需要string对象的地方可以用这两种字面值来代替。
注:C++中字符串字面值并不是标准库类型string的对象。切记,字符串字面值和string是不同的类型
处理string对象中的字符
范围for(range for)语句(C++11):
这种语句遍历给定序列中的每个元素并对序列中的每个值进行操作。
string str("some string");
for(auto c: str)
cout<< c << endl;
for(declaration : expression)
statement
declaration:负责定义一个变量,该变量将被用于访问序列中的基础元素。每次迭代,都更新为下一个值
expression:一个对象,用于表达一个序列
附:在cctype头文件中定义了一组标准库函数处理某个字符特性的工作。
C++标准库中除了定义C++语言特有的功能外,也兼容了C语言的标准库。C语言的头文件形如name.h,C++中将其命名为cname。标准库中的名字总能在命名空间std中找到。
使用下标进行for循环迭代:
for(decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index)
{
s[index] = toupper(s[index]);
}
使用下标运算符([]) 注意:1.接受的输入参数时string::size_type类型的值。
2.下标必须大于等于0而小于s.size()
const string hexdigit("0123456789ABCDEF");
decltype(hexdigit.size()) n;
string result;
while (cin >> n)
{
if (n < 16)
{
result += hexdigit[n];
}
}
cout << result << endl;
标准库类型vector
标准库类型vector表示对象的集合,其中所有对象的类型都相同。
要想使用vector,必须包含适当的头文件。
#include <vector>
using std::vector
vector是一个类模板,编译器根据模板创建类的过程称为实例化(instantiation)。
通过提供一些额外信息(在模板名字后面跟一对尖括号,在括号内放入信息)来制定模板到底实例化成什么样的类。
vector<int> ivec; //int类型对象
vector<Sales_item> Sales_vec; //Sales_item类型对象
vector<vector<string>> file; //向量元素为vector对象
①(非引用)内置类型 ②类类型 ③vector 都可以称为构成vector的元素
定义与初始化vector
最常见的方式先定义一个空vector,然后当运行时获取到元素的之后再逐一添加。
vector<string> v1{"a","an","the"}; //列表初始化
注:在某些情况,初始化的真实含义依赖于传递初始值时所用的是花括号还是圆括号。
圆括号(()):提供的值是用来构造(construct)vector对象的
或括号({}):列表初始化(list initialize)该vector对象
另一方面,如果初始化时使用了花括号的形式但是提供的值又不能用来列表初始化,如下:
vector<string> v1{10}; //有10个默认初始化的元素
vector<string> v2{10, "hi"}; //有10个“hi”的元素
编译器会尝试用默认值初始化方式【类表初始化,花括号里的值必须与元素类型相同】
向vector对象中添加元素
vector<int> v2;
for (int i = 0; i!= 100; ++i)
v2.push_back(i);
首先创建一个空vector,再运用vector的成员函数push_back向其中添加元素;
push_back负责将一个值当成vector对象的尾元素“压到(push)”vector对象的“尾端(back)”
注:1.如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环。
2.vector对象(以及string对象)的下标运算符可用于访问已经存在元素,而不能用于添加元素。
3.确保下标合法的一种有效手段就是尽可能使用范围for语句。
其他的vector操作
size函数返回vector对象中的元素,返回值的类型事由vector定义的size_type类型
vector<int>::size_type //正确
vector::size_type //错误
要使用size_type需首先指定它是由哪种类型定义的。vector对象的类型总是包含着元素的类型
迭代器
除了vector外,标准库还定义了其他几种容器。所有标准容器都可以使用迭代器,但只有少量几种(vector等)包含下标运算。
//由编译器决定b和e的类型
//b表示v的第一个元素,e表示v尾元素的下一位置
auto b = v.begin(), e = v.end();
一般来说,我们不清楚(不在意)迭代器的类型到底是什么【如同不知道string和vector的size_type成员一样】。
实际上,拥有迭代器的标准库类型使用iterator和const iterator来表示迭代器的类型。
vector<int> :: iterator it;
vector<int> :: const_iterator it2;
begin和end返回的都是iterator类型
cbegin和cend返回的是const_iterator类型,不可以修改迭代器指向的内容
和指针类似,也能通过解引用迭代器来获取它所指示的元素。
begin:指向容器中的首元素
end:指向容器中尾元素的下一个位置
迭代器使用递增(++)运算符来从一个元素移动到下一个元素。
术语:
迭代器这个名词有三个不同的含义:可能是迭代器概念本身,也可能是指容器定义的迭代器类型,还有可能是某个迭代器对象。
重点是理解存在一组概念上相关的类型,认为某个类型是迭代器当且仅当它支持一套操作,这套操作使得我们能访问一套容器的元素或者从某一元素移动到另外一个元素。
每个容器类定义了一个名为iterator的类型,该类型支持迭代器概念所规定的一套操作。
解引用迭代器可以获得迭代器所指的对象,当对象的类型为类类型时,可以运用箭头运算符(->)将解引用和类成员访问结合起来。
string、vector的迭代器提供了更多额外的运算符:
iter + n
iter - n
iter += n
iter -= n
iter1 - iter2
> 、 >= 、 < 、<=
二分搜索(使用迭代器经典算法)
数组
数组是一种类似于标准库类型vector的数据结构,不同的是:数组的大小固定不变。
数组是一种符合类型,数组中元素个数也属于数组类型的一部分,维度必须是一个常量表达式【①值不变 ②在编译过程中能得到计算结果】
unsigned cnt = 42; //不是常量表达式
constrexpr unsigned sz = 42; //常量表达式
int arr[10];
int *parr[sz];
注:不能讲数组的内容拷贝给其他数组作为其初试值,也不能用数组为其他数组赋值
int a[] = {0,1,2};
int a2[] = a; //错误
a2 = a; //错误
理解复杂的数据声明
int *ptrs[10];
首先定义的是一个大小为10的数组,名字为ptrs。然后知道数组中存放的是指向int的指针。
int &refs[10] //错误,不存在引用的数组
int (*Parray)[10] = &arr;
首先是()内意味着:Parray是一个指针,观察右侧,Parray是一个指向大小为10的数组的指针,最后观察数组元素为int
int (&arrRef)[10] = arr;
引用一个含有10个整数的数组;
int *(&arry)[10] = ptrs; //含有10个int型指针的数组的引用
arry是一个引用,然后观察右边:引用对象是一个大小为10的数组。最后观察左边,数组的元素是指向int型的指针;
【要想理解数组声明的含义,最好的办法是从数组的名字开始按照由内向外的顺序阅读】
访问数组元素
在使用数组下标时,通常将其定义为size_t类型【一种机器相关的无符号类型】。
与vector和string一样,当需要遍历所有数组元素是,最好的办法是适用范围for语句
for(auto i : scores)
cout << i << "";
指针与数组
在很多用到数组名字的地方,编译器都会自动的将其替换为一个指向数组首地址的指针
string nums[] = {"one","two","three"};
string *p2 = nums;
string *p2 = &nums[0]; //等价
更进一步:
int ia[] = {0,1,2,3,4,5,6,7};
auto ia2(ia); //ia2是一个整型指针,指向第一个元素
auto ia2(&ia[0]); //等价
但需要指出:使用decltype关键字时,上述转换不发生
指针也是迭代器
vector和string的迭代器支持的运算,数组指针全部支持。
int arr[9] = {0,1,2,3,4,5,6,7,8};
int *p = arr;
++p; //p指向arr[1]
int *e = &arr[9]; //指向arr尾指针的下一位置的指针
for(int *b = arr; b!=e; ++b)
cout << *b <<endl;
为了简化,C++11推出:begin和end函数 【在iterator头文件中】
和迭代器一样,两个指针相减的结果是他们之间的距离。参与运算的两个指针必须指向同一个数组当中的元素;类型是【ptrdiff_t的标准库类型】
int *p = &ia[2];
int j = p[1]; //ia[3]
int k = p[-2]; //ia[0]
只要是指针指向的是数组中的元素(或者数组中尾元素的下一位置),都可以有如上下标运算
【标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求】
C风格字符串
不是一种类型 不是一种类型 不是一种类型
一种约定俗称的写法:将字符串放在字符数组中并以空字符结束('\0');
char ca[] = {'C','+','+','\0'};
C++支持此风格,但最好还是不要使用,更多使用标准库string 更加高效、安全。
与旧代码的接口
1.string对象和C风格字符串转换
C风格字符串——>string对象:
string s("Hello World");
之前介绍,允许使用字符串字面值【char[]】来初始化string对象。
更为一遍的情况是:
允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值;
string对象——>C风格字符串:
string专门提供了c_str的成员函数【不能用string对象直接初始化指向字符的指针】
string s(10, 'a');
const char *str = s.c_str();
2.使用数组初试化vector对象
不允许不允许不允许使用一个数组为另一个内置类型的数组赋初值
不允许不允许不允许使用vector初始化数组
可以 用数组初试化vector对象
只需要指明拷贝的首元素地址和尾后地址就可以了
【尽量使用标准库而非数组】现代的C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用C风格的基于数组的字符串。
多维数组
严格来说,C++没有多维数组。其实质是数组的数组
int arr[10][20][30] = {0};
int ia[3][4] = {
{0,1,2,3},
{0,1,2,3},
{0,1,2,3}
};
int ia[3][4] = {0,1,2,3,0,1,2,3,0,1,2,3}; //等价
int (&row)[4] = ia[1]; //把row绑定到ia的第二个4元素数组上
使用范围for语句处理多维数组
【注意】除了最内层循环外,其他所有循环的控制变量都应该是引用类型
for(auto &row : ia)
for(auto col : row)
cout << col << endl;
for(auto row : ia) //没有引用&
for(auto col : row) //编译报错
cout << col << endl;
原因:在第二段代码中,row不是引用类型,编译器初始化row时,会自动将数组形式的元素转化成指向该数组内首地址的指针,这样row的类型就是int*。显然内层就不合法了;
指针和多维数组
int ai[3][4]; //大小为3的数组,每个元素含有4个整数的数组
int (*p)[4] = ai; //p指向含有4个整数的数组
int *p[4]; //整型指针的数组
p = &ia[2]; //p指向ia的尾元素
C++11提出,通过使用auto或者decltype就能尽可能的避免在数组前面加上指针类型;
for(auto p = ia; p != ia + 3; ++p )
for (auto q = *p; q != *p + 3; ++q)
cout << *q ;
使用库函数begin和end可以进行优化;
for(auto p = begin(ai); p != end(ia); ++p )
for (auto q = begin(*p); q != end(*p); ++q)
cout << *q ;
或者运用类型别名优化:
using int_array = int[4];
typedef int int_array[4]; //等价
for(int_array p = begin(ai); p != end(ia); ++p )
for (int q = begin(*p); q != end(*p); ++q)
cout << *q ;
术语表:
缓冲区溢出(buffer overflow):一种严重的程序故障,主要的原因是通过一个越界的索引访问容器内容,容器类型包括string,vector,数组等
类模板(class template):用于创建具体类类型的模板。需要提供类型的辅助信息才能使用。如,定义个vector对象需要制定元素的类型:vector<int>包含int类型的元素。
编译器拓展(compiler extension):某个特定的编译器为C++语言额外增加的特性。基于此写的程序不易移植。
拷贝初始化(copy initialization):使用赋值号(=)的初始化形式。新创建的对象是初始值的一个副本。
直接初始化(direct initialization):不使用赋值号(=)的初始化方式。
difference_type:由string和vector定义的一种带符号整数类型,表示两个迭代器之间的距离。
prtdiff_t:cstddef头文件中定义,表示数组中任意两个指针之间的距离。
size_t:cstddef头文件中定义,表示任意数组的大小。