1. String类型
1.1 定义和初始化
有以下几种初始化方法:
string s0; //初始化为空串
string s1(s0); //s1为s0的一个副本,direct initialization
string s11 = s0; //同上,copy initialization
string s2("Hello");//s2为字符串"Hello",不包含NULL结束符,direct initialization
string s22 = "Hello";//同上,copy initialization
string s3(5, 'H'); //初始化为5为'H',direct initialization
使用=的初始化方法是告诉编译器,复制初始化(copy initialize)对象。而不带等号的方法则是直接初始化(direct initialization)。
string s33 = (5, 'H'); //copy initialization
s33初始化等价于:
string tmp(5, 'H');
string s33(tmp);
比s3的初始化方式效率要低。
1.2 输入个数未知
string word;
while (cin >> word){
cout << word << endl;
}
每次读入一个word,就会测试流的状态,如果输入合法或未遇到文件结束符,则测试状态为真;否则为假,循环结束。
1.3 读入一整行
string line;
while (getline(cin , line){
cout << line << endl;
}
getline会读入一行字符直到遇到第一个换行(new line)。如果第一个字符为换行,则该字符串为空。
1.4 string::size_type类型
【C++11】
auto len = line.size(); //len的类型为string::size_type
1.5 字符串比较
比较规则:- 如果两个string的长度不同,且短string中的所以字符和对应的长string中的字符都相同,则短string小于长string。
- 如果有不同的字符,则结果即为比较第一个不相同的字符的结果。
1.6 字符串相加
string s0;
string s1 = "hello", s2 = "world";
string s3 = s1 + s2; // ok
string s4 = s1 + "world"; // ok
string s5 = "hello" + s2; // ok
string s6 = "hello" + "world"; // error
string s7 = s0 + "hello" + "world"; // ok
s7可以解释为下面两个式子:
string tmp = s0 + "hello";
string s7 = tmp + "world";
这与cin,cout的串联规则相似。
1.7 处理string中的字符
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是空白符时为真 |
isupper(c) | 当c是大写字母时为真 |
isxdigit(c) | 当c是十六进制数字时为真 |
tolower(c) | 如果c是大写字母,则转换为小写;否则不变 |
toupper(c) | 如果c是小写字母,则转换为大写;否则不变 |
控制字符:
- 0(null,NUL,\0),许多编程语言用来标示字符串结束。
- 7(bell,BEL,\a),会使接收它的设备发出类似警报的声音。
- 8(backspace,BS,\b),擦除最后一个字符。
- 9(horizontal tab,HT,\t),相当于键盘上的tab键。
- 10(line feed,LF,\n),在UNIX系统及其变体中,作为行结束符。
- 12(form feed,FF,\f),使打印机弹出纸,或使视频终端刷新屏幕。
- 13(carriage return,CR,\r),在Mac OS,OS-9,FLFX(及变体)系统中作为行结束符。
CR\LF组合应用于CP/M-80和包括DOS和Windows在内的衍生系统,以及像HTTP的应用层协议。 - 27(escape,ESC,\e[GCC only])。
- 127(delete,DEL,)。
- ...
【C++11】range for
如果想对string里的每个元素做一些操作,新标准引入的一种新的for语句:range for。该语句遍历一个特定的序列,且可以对序列中的每个元素进行操作。语法格式为:
for (declaration : expresion)
statement
expression表示要遍历的对象,declaration定义了一个变量用来访问并操作序列中的元素。每次迭代开始时,declaration就会被初始化为序列中的下一个元素。
下面看一个例子,将sting中的字符都转换为大写:
for (auto c : s0) {
toupper(c);
}
这种写法确实很酷,而且使用auto类型就不需要考虑序列中元素的具体类型了。(效率如何就有待验证了)
细心的读者应该会发现上面的程序并没有改变s0中的元素值,这是因为如果要在range for语句中改变序列中的元素值,循环变量必须是引用类型(像不像函数调用时的参数传递?)。上面的程序可以修改为:
for (auto &c : s0) {
c = toupper(c);
}
使用下标[ ]操作
对string使用下标操作类似于C语言中的字符数组的下标操作,唯一需要注意的是下标的范围为0到s.size() - 1。下标操作可以实现随机访问。
2. vector类型
2.1 定义和初始化
vector是具有相同类型的一组对象的集合。我们可以用下标访问vector中对应的元素。vector是一个容器(container),也是一个类模板(class template)。
模板本身不是函数或者类,模板可以认为是一组指令,指示编译器生成类或函数。编译器根据模板生成类或函数的过程就叫实例化(instantiation)。在实例化是我们一般需要提供一些信息,来生成我们需要的类或函数。
vector<int> vi; // hold int
vector<string> vstr; // hold string
vector<vector<int>> vvi; //hold vector<int>
vector可以保存大多数类型的对象,包括内置类型和类类型,当然也包括vector自身。
记住:vector不是类型,而是模板。
注:有些编译器在定义vector的vector时,需要在里面那对<>后面加空格,vector<vector<int>_>,即下划线的位置。
vector的初始化方法有多种,以vector<int>为例:
vector<int> v1; // v1: 空,即元素个数为0
vector<int> v2(v1); // v2: v1
vector<int> v3 = v1; // v3: v1
vector<int> v4(5, 1); // v4: 1, 1, 1, 1, 1
vector<int> v5(5); // v5: 0, 0, 0, 0, 0
vector<int> v6{1, 2, 3, 4, 5}; // v6: 1, 2, 3 ,4, 5
vector<int> v7 = {1, 2, 3, 4, 5}; // v7: 1, 2, 3, 4, 5
vector<T> vt(n, val)是将vector初始化为n个val,vector<T> vt(n),则是用n个默认值初始化vector。
v6, v7的初始化方法是列表初始化,类似于数组的初始化,是C++11的新标准。
我们看到,C++提供了多种初始化的方法。在多数情况下,这些初始化形式都是可互换使用的。但是,也有例外的情况。当使用复制初始化时(使用=),只能提供单个初始化对象(initializer);当在类内(in-class)初始化时,必须使用复制初始化或使用大括号。第三个限制是,使用元素列表初始化时,必须使用大括号{},而不能使用圆括号()。
关于列表初始化,编译器首先会判断列表中提供的元素是否可以用来列表初始化,如果不行,就尝试其他的初始化方法(编译器越来越智能?)。
vector<string> vs1{"hello"}; // list initialization
vector<string> vs2("hello"); // error
vector<string> vs3{10}; // vs3被初始化为10个空串
vector<string> vs4{10, "hello"}; // vs4被初始化为10个"hello"
一般在定义vector时如果不知道其大小,则将其定义为空,然后在运行时使用
push_back成员函数往vector中添加元素。vector添加元素的操作是高效的,甚至比提前定义vectoer的大小还要快,这个跟C语言中的习惯先定义数组的大小是相反的。
警告:range for作用的迭代序列,不能在for结构体中改变序列的大小,比如对vector使用push_back操作。
2.2 vector的其他操作
v.empty() | 如果为空返回true;否则返回false |
v.size() | 返回v中的元素个数,类型为size_type |
v.push_back(t) | 在v后面插入一个元素t |
v[n] | 返回v中位置n出的元素的引用 |
v1 = v2 | 将v1中的元素替换为v2中的元素的副本 |
v1 = {a, b, c...} | 将v1中的元素替换为以逗号分隔的列表中的元素 |
v1 == v2 | 如果v1和v2中的元素相等且对应元素相同,则v1和v1相等 |
v1 != v2 | 与v1 == v2相反 |
<, <=, >, >= | 返回用字典序比较的结果 |
注意:习惯使用数组的C语言程序员,往往会犯的一个错误是对vector的下标引用越界。vector是动态管理内存的容器(container),而C语言的数组则是在初始化的时候就分配好内存。错误的下标引用往往会造成缓冲区泄漏( buffer overflow),很多应用程序的安全问题就来自于此。
3 arrays
数组具有固定的长度,在运行时效率更高。但是灵活性较差。
3.1 数组的声明和定义
数组是复合类型,声明的格式为a[d]。其中a是数组名,d表示数组维度。d必须是大于0整型,且必须是常量表达式。
unsigned int uiSize = 10;
const unsigned int cuiSize = 10;
int iArray1[uiSize]; // error
int iArray2[cuiSize]; // ok
int iArray3[array_size()]; // ok if array_size() is constexpr, error otherwise
数组的初始化一般采用列表初始化,初始化列表的长度不能超过数组定义的长度,未初始化的元素会初始化为默认值。使用字符串字面值初始化字符数组时,会在字符串后面自动加上'\0'字符:
int a3[10]; // 数组元素初始化为0
int main() {
int a0[10]; // 数组元素为初始化。使用为初始化的变量是危险的行为,编译器一般会给出警告。
int a1[10] = {}; // 数组元素初始化为0
char s0[] = "ok"; // 数组长度为3,s[2] = '\0'
return 0;
}
更复杂的定义
可以定义指针的数组,也可以定义指向数组的指针或引用。不存在引用的数组:
int arr[10] = {};
int *ptrs[10]; // 包含10个指向int的指针的数组
int &refs[10]; // error
int (*pArr)[10] = &arr; // pArr 是指向包含10个int类型元素数组的指针
int (&arrRef)[10] = arr; // arrRef 是包含10个int类型元素数组的引用
int *(&rpArr)[10] = ptrs;// rpArr is a reference to an array of ten pointers(英文表述更简洁)
从右往左解读,定义就很容易理解了。比如:ptrs是一个包含10个元素的数组,数组元素的类型为int*。如果包含括号,则需要先解读括号内的部分,比如:pArr是一个指针,该指针指向一个包含10个元素的数组,该数组的元素类型为int。有括号就先解读括号里面的,然后从右往左解读。这样rpArr的含义就很容易理解了。
3.2 数组与指针
int arr[10] = {};
int *pArr = arr;
arr[4] = 10;
*(pArr+4) = 10; // 与arr[4] = 10有相同的效果
使用auto和decltype声明数组,会有不同的结果:
int arr[10] = {};
auto arr1 = arr; // arr1 : int *
decltype(arr) arr2 = {}; arr2 : int [10]
auto语句声明变量时,是根据初始化表达式的类型来推断变量的类型;而decltype则是根据其作用的变量的类型声明变量。于是,arr1是指向int类型的指针,arr2是包含10个int类型元素的数组。
int arr[10] = {};
int *b = &arr[0];
int *e = &arr[10];
注意:我们只能使用&arr[10]来初始化越界判定指针,其他操作均非法的。
【C++11】begin和end函数
新标准引入了函数begin和end专门用来初始化便利数组的开始指针和越界判定指针。
int arr[10] = {};
int *b = begin(arr);
int *e = end(arr);
对string和vector的下标操作的索引值必须是非负的,而数组的下标则没有这个限制。
int arr[10] = {};
int *p = arr[4];
int a = *(p-4); // a = arr[0];