C++中的容器(字符串、向量、数组)

总列表

命名空间using namespace

    注意不要在.h头文件中包含using声明

using namespace std;
之后你可以使用std命名空间下定义的一些个体,而不需要附加std::
另一种用法是C++11独有的,可以代替typedef使用,以下两条语句的效用相同
using int_array = int[4];
typedef int int_array[4];

int_array array_row;
//可以表示定义了一个数组类型,数组的长度就是4

string(C++标准库类型字符串)

    需要#include< string >才能将其作为对象使用cout和cin等

对string的初始化

string s1;//初始化为空串
string s2(s1);//拷贝初始化,s2是s1的副本,将s1的值完全拷贝给s2
string s3("Glaurung");//将字符串初始化为Glaurung
string s4 = "Smaug";//将字符串的值Smaug赋值给s4
string s6(3,'s');//s6被初始化为连续的3个字符‘s’
string s7 = string(3,'s');//和以上的语句有同样的效果,推荐使用上面那种方法

常用对string的操作

getline(is, s);//从i_stream中读取一行,赋值给s,注意这里空格字符也会被输入进去而直接使用cin则只会将空格之前的连续字符输入
s.empty();//判空,为空时返回true
s.size();//返回字符串中字符个数
.size()操作返回的是一个size_type类型,是一个无符号类型,了解就好
s1+s2//将s1和s2连接,最终返回连接后的结果
关于字符串的比较(是不是要重启一个课题)
因为C传统的char*类型字符串比较和string会有所不同
string s1("Ancalagon");
string s2 = s1 + "the Black";//正确
string s3 = "Ancalagon" + "the Black";//错误:不允许将两个字面值直接赋值给字符串
string s4 = s1 + (s2 + "Fire");//正确
string s5 = s1 + ("the" + "Black");//错误:运算符之间必须有一个确定为string类型

    总结一下,就是当给string类型赋值时,运算符之间必须有一个确定的类型(string类型),字符串之间的连接不允许全部使用字面值;或者说:当吧string对象和字符字面值以及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)两侧的运算对象至少有一个是string。
    注意上面我说的“字符串字面值”、“字符字面值”。后者很好理解,就是char类型比如’s’这种;但是对于前者,由于与C语言兼容,其实它并不是string类型,而是由C定义的char* a[] = “sss”;那种类型。之后我还会提到关于C字符串和C++string的一些区别和联系。

常用的对字符串中字符的操作

isalnum(c)判字母和数字(alphabet/number)
isalpha(c)判字母
iscntrl(c)判控制字符(control)
isdigit(c)判数字(digital)
isgraph(c)判非空格且可以打印(graph,即有显示)
islower(c)判小写字符
isupper(c)判大写字符
isprint(c)判是否为可打印(空格也行,即可打印、可视就为真)
ispunct(c)判标点符号
isspace(c)判空白(不仅仅是空格,还可以是一些制表符、回车、进纸符、换行符)
isxdigit(c)判大写字母
toupper(c)如果是小写字母则返回大写字母
tulower(c)如果是大写字母则返回小写字母

    以下是一个例子,转换大写字母

将字符串s中的第一个单词转变为大写,使用下标
for(decltype(s.size()) index = 0; index!=s.size()&&!isspace(s[index]);index++)
	s[index] = toupper(s[index]);

vector(C++标准库类型)

    vector是一种容器,用来装填同种类型的对象,在使用之前必须声明vector所存储对象的类型,类型通过类模板来定义。

vector<int> ivec;//ivec保存int类型的对象
vector<Sales_item> Sales_vec;//保存Sales_item类型的对象
vector<vector<string>> file;//该向量元素本身也是vector

    可见vector中的类型可以是C++中现有定义的类型,也可以是自定义的类,也可以本身就是一个vector。

vector<T> v1;//空vector,执行默认初始化,类型是T
vector<T> v2(v1);//将v1的拷贝给v2
vector<T> v2 = v1;//同上
vector<T> v3(n, val);//n个重复元素val
vector<T> v4(n);//n个重复元素,注意这个元素的值由类型决定,每一个类型有其特定的默认值
vector<T> v5{a, b, c...};//v5中有列表中的这些元素
vector<T> v5 = {a, b, c...};//同上

    列表初始值还是元素数量?! 存在这样一种问题:列表初始化时,列表中的值无法用来列表初始化(类型不对应),如下给出了解释

vector<string> vc{10, "hi"};
显然10这个字面值无法用来初始化一个string对象,这时编译器会构造一个有10个值都为"hi"的vector
对比如下两个“列表”初始化
vector<int> vi{10, 1};//vi中的两个对象为 10 和 1
vector<string> vc_e{10};//vc_e中包含string默认类型的10个对象

vector基本操作

    将vector看成一个队列,push_back()函数将在队列最后方为一个vector加入新的对象成员。

vector<int> v2;
for(int i = 0; i!=100; i++)
	v2.push_back(i);
其中v2包含了0`99100int类型对象

    以下还有一些其他操作

v.empty();//判空
v.size();//返回vector中元素数量
v[n];//可以使用索引来访问vector中的元素(从0开始),但是不能使用索引来对其添加元素

    注意size()函数的返回类型:实际上是size_type类型,但是定义size_type类型时必须加上类型模板如下

vector<int>::size_type  a;//正确
vector::size_type  b;//错误:没有说明模板类型

迭代器

    很多“容器”都支持迭代器,比如string和vector都可支持。迭代器提供了对容器元素的间接访问,有点类似于指针。

auto b = v.begin(), e = v.end();
如果v是一个vector, 则b指向它的第一个元素,而e是指向其最后一个元素的下一个位置,如果容器是空的,begin和end将返回同一个值。

    以上使用auto类型表示我们还不确定其返回值的类型,实际上其返回的类型是对应对象的iterator类型,如string::iterator,int::iterator等等,其实表示的就是地址,要访问其中的元素需要添加解引用符" * ".。

string s("any string");
if(s.begin()!=s.end())//判非空字符串
{
	auto it = s.begin();
	*it = toupper(*it);//第一个字符访问并转大写
}

迭代器基本操作

iter = s.begin();
iter++;//访问下一个元素位置(最末尾是end位置)当然也可以+=x,向后移动x个位置
iter--;//访问前一个元素位置(最开始是begin位置)
iter1==iter2;//仅当两个迭代器本身是指向同一个对象,或同一个容器的尾后位置时返回true
iter1-iter2;//当二者是同一个容器中的迭代器时成立,得到的是两个元素的位置差(差几个元素)
//以上操作得到的类型是difference_type(差分类型)
关于解引用
(*it).empty();//成立,先解引用之后判空
*it.empty();//错误,先判空后解引用,但是it作为指向的一个位置,不存在empty成员函数
it->empty();//这种就是成立的,C++定义了箭头运算符来方便操作,与指针的使用十分类似

数组

    与之前的一些容器有所不同,数组的大小是确定不变的,不可以向数组中增加元素,由于之前有所涉及,这部分将进行简略。

const int sz = 10;
int szz = 10;
int any_array[sz];//正确:大小必须是常量值
int next_array[cnt];//错误,大小不是常量
string strs[get_size()];//当get_size()是一个constexpr时是正确的

数组初始化以及字符数组(较为特殊)

const unsigned sz = 3;
int ia[sz] = {2,3,4};
int a2[] = {1,2,3};
int a3[5] = {2,3,6};//我可以只初始化其前三个元素,后面的仍然是默认值
string a4[3] = {"Dark", "Lord"};//其第三个元素是空串
char a1[] = {'C', '+', '+'};//不兼容C
char a2[] = {'C', '+', '+', '\0'};//兼容C
char a3[] = "sss";//兼容C标准字符串,sss后面其实还有隐含的一个'\0'
char a4[3] = "sss";//其实这个字符串这样定义的话其长度是4,故容量不够,引发错误
int a[] = {0,1,2};
int a1[] = a;//错误:不允许用一个数组这样初始化另一个数组
a1 = a;//错误:数组赋值请使用循环,将其中的元素一个个的赋值
数组默认初始化时,全局变量和局部变量还会有一些区别,需要注意
string sa[10];//标准库类型(复合类型)string
int ia[10];//C++内置类型int
int main(void)
{
	string sa2[10];
	//对于复合类型数组,string类本身接受无参数的初始化方式,所以无论定义在哪,都被默认初始化为空串。
	int ia2[10];
	//内置类型int,根据C++语言规定,全局变量ia1将被初始化为0,定义在函数内部的ia2将不被初始化。
	//任何对其拷贝、输出未初始化的变量,将得到未定义的奇异值。(实际我在VS2015上根本就无法编译通过)
}
unsigned sss[10];
unsigned sss[10] = {};
上面的语句不执行初始化,里面的值不可知;下面的语句执行默认初始化,值均为0

数组复杂声明(指向数组的指针,指针数组)

int *ptrs[10];//存放了10个int型指针的数组
int &refs[10] = /* ? any things */ //错误,没这种写法,不存在引用的数组
int (*p_array)[10] = &arr;//p_array指向一个含有10个int型变量的数组
int (&arr_Ref)[10] = arr;//arr_Ref引用一个含有10个int型变量的数组
以下语句建议从内向外剥开来看
int *(&array)[10] = ptrs;//首先array是数组引用,该数组里面是int型指针,容量是10
int ia[10];
auto ia2(ia);//其实在这里不是把ia的值都拷贝给了ia2,此时应该是将ia首元素的地址传递给了ia2
ia2 = 44;//所以这种直接赋值是不正确的,正确写法是*ia2 = 44
int ia[] = {0,1,2,3,4,5,6,7,8,9};
int *first = begin(ia);//指向首地址 
int *last = end(ia);//指向尾后地址
注意指向尾后地址的这个无法解引用,因为这个地址不在数组内,状态也未知

auto n = last - first;//可以得到数组长度
其中n被定义的内置基本类型是 ptrdiff_t,它是有符号的类型
if(last>first)//关于指针的比较
当指针是对于同一个容器来说的话,比较才有意义,后面的指针大于前面的
const size_t sz = 5;//数组索引被定义的内置类型是 size_t,它是无符号的
int arr[sz] = {1,2,3,4,5};
int *ip = arr;//指向数组首元素地址
*(ip+2) = 10;//将数组第三个元素改变值为3

其实索引也可以是负值 比较少见,使用如下:

int ia[] = [2,4,6,8];
int *p = &ia[2];//绑定到元素 6 对应的指针
int j = p[1];//这个指向的实际是元素 8 即p对应元素的下一个元素
int k = p[-2];//这个指向的实际是元素 2 即p指向元素的前两个元素

C风格字符串操作(实际是char数组)

strlen(p) //返回p的长度,空字符串不计算在内
strcpy(p1, p2) //将p2拷贝给p1,返回p1
strcmp(p1, p2) //比较字符串相等性,相等返回 0;p1>p2返回一个正数;p1<p2返回负值
strcat(p1, p2) //p2附加到p1后,返回p1
char ca[] = {'C''+''+'};
cout << strlen(ca) << endl;
//发生错误,没有使用'\0'来结束字符串,无法正常返回其长度,使用如下定义方式可以避免这种问题
char ca[] = "C++";

旧代码接口(容器的相互初始化)

    我们允许使用一个C风格的字符串(char 数组)来初始化一个string类型的对象。

string s("Hello, World");
char *str = s;//你无法使用一个字符串来初始化一个char*类型
const char *str = s.c_str();//可以实现这个初始化,c_str()将返回一个C风格的字符串,注意返回值是const char*类型的,从而保证我们不会改变字符数组的内容

    我们允许使用数组初始化vector对象。

int i_array[] = {0,1,2,3,4,5};
vector<int> ivec(begin(i_array), end(i_array));
vector<int> ivec(i_array+1, i_array+4);//数组第二个元素到第四个元素
//注意初始化到后一个参数的前面一个元素,二部是其指向的元素,比如第二条语句end指向的就是尾后元素

多维数组

使用数组初始化vector对象
int ix[3][4] = {{0},{4},{8}};//三行四列,只初始化每行的第一个元素
int ia[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};//加不加括号其实是一样的,还是三个为一组
int px[3][4] = {1,2,3,4};//只初始化第一行

int (&row)[4] = ia[1];//将row绑定到ia的第二行的4个元素
范围for语句处理多维数组中的元素
for(auto &row : ia)
	for(auto &col : row)
		//col = 
注意这里外层循环必须是引用,这是为了避免数组被自动转换成指针类型,那样外层的row就将变成数组元素类型,将不再支持内层的循环。
使用类型别名简化多维数组的指针
using int_array = int[4];
typedef int int_array[4];//以上两条语句的效用相同
for(int_array *p = ia; p != ia+3; p++)
	for(int *q = *p; q!=*p+4; q++ )
		cout<<...;
注意:其中int_array类型就代表了一个容量为4int型数组,定义的指针变量即指向一个容量为4int型数组的指针。
试着理解多维数组的理解方式
int ia[3][4];//ia是一个多维数组,它有3个元素,每个元素是含有四个整数的数组
int (*p)[4] = ia;//p指向的是ia的首元素,首元素是一个含有4个整数的数组
p = &ia[2];//p指向ia的尾元素。
(*p)[2] = 9;//相当于将9赋值给ia[2][2]

待解决的思考

1、范围for语句中为什么for(auto &a : sss),其中a必须是引用才可以修改其中的值(访问不需要引用,但是修改就必须使用引用)
2、while (cin >> index),形如这种形式的输入,应该怎样使条件退出呢(即输入什么)
3、为什么包含的头文件有两种不同的形式?有一些是带.h的一些则不带,并且有一些头文件开头字符都是c
4、vector本身对象就是vector,形如vector<vector< string >>,这种定义是不是可以理解为“多维数组”?!
5、关于经常作为示例演示的get_size()到底是个怎样的函数,常常和constexpr一起出现
6、如果使用strcpy(),strcat()这种函数,如何确定C类型字符串的大小是否满足要求,比如链接两个字符串,如何保证第一次字符串在定义的时候就足够长,还是说使用时必须检测其容量是否足够?!
7、与旧代码接口部分第一个代码段,书上对应部分说“我们无法保证c_str()汉化返回的数组一直有效,事实上,如果后续的操作改变了s的值就可能让之前返回的数组失去效用”,这段话我暂时没有理解。
8、与旧代码接口第二个代码段,什么叫“显示地”、“隐式的”初始化?
9、有string类型的vector,那么有没有string类型的数组呢?引申一下:这几种容器可不可以存在相互包含使用的关系,哪两种可以,哪两种配合不行。
10、使用迭代器进行二分搜索,为什么找中点的时候用的是mid = begin + (begin+end)/2;而不是mid = (beign+end)/2; 有什么区别?
11、数组初始化以及字符数组(较为特殊) 中最后两个代码段,两个默认初始化方式好像不太一样。上面的代码段没有加“{}”也进行了数组默认初始化,但是后面的那个代码段中却说只有加“{}”后才会执行默认初始化。
12、多维数组第一个代码段,第三行:如果我在大括号里只写5个数,那么他是如何初始化的,是不是初始化第一行的所有元素,之后初始化第二行的第一个元素?

note

1、对于标准库定义的类型,使用逻辑运算符时(比如在for循环中)最好使用!=或==运算符,而避免使用<=,>=,>,<等运算符,因为大多数情况下库中仅对前面两种运算符进行了重定义。
2、尽量使用标准库定义的string类型,而不要使用C传统的字符串类型,前者更加安全和高效。

想法

    之后我想的是将这几个部分联动起来的部分单独列写一篇博文。比如数组和字符串、使用数组初始化vector等等。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值