应该优先选用标准库提供的类型,之后再考虑内置的低层的替代品数组或指针。
-
命名空间的using声明不应该出现在头文件里。
using std::cin; using namespace std; //懒猪写法
string
-
初始化:
#include <string> using std::string; string s1; //s1是空字符串 string s2 = s1; string s3 = "Hiya"; //s2和s3是拷贝初始化 string s4(10, 'a'); //s2是"aaaaaaaaaa"
-
is>>s
从is中读取s的值,以空白格为分隔符。用
getline
获取一整行,直到遇到\n
(换行符不被读进去)。while (cin >> s) {...} //直到遇到文件结束符 while (getline(cin, ss)) {...} //每次读入一整行,直到到达文件末尾
-
empty()
函数返回一个布尔值,size()
函数返回一个string::size_type
的值(无符号类型)。size_type
型和int
型不要混用。凡是遇到取字符串中某下标的值,下标要设置为size_type
型,且必须检查其小于string.size()
(防止数组越界)。 -
标准库允许把字符字面值和字符串字面值转换为
string
对象:string s1 = "hello"; string s2 = s1 + "worl" +'d'; //注意!!!+的左右两侧至少要有一个是string
-
cctype
头文件用于判断字符特性:- isalnum© (字母或数字)
- isalpha© (字母)
- isdigit© (数字)
- issapce© (空白)
- islower©/isupper© (小/大写字母)
- ispunct© (标点)
- tolower©/toupper© (不是字母则原样输出)
-
范围for语句:
string s("Hello World!!!"); decltype(s.size()) punctCnt = 0; //为size_type型 for (auto c : s) //范围for语句 if (ispunct(c)) punctCnt ++; cout << punctCnt << "punctuation characters in" << s << endl;
如果想要改变
string
对象中字符的值,必须把循环变量定义为引用类型:auto &c : s
。 -
(参见第三点)下标运算符
[]
返回字符的引用。在访问指定字符前,必须检查s是否为空。整形的下标也会自动转换为size_type
型。以下是一个十进制数转十六进制数的神奇方法:const string hexdigits = "0123456789ABCDEF"; string result; string::size_type n; //保证大于0 while (cin >> n) if (n < hexdigits.size()) //保证小于size result += hexdigits[n]; //这样就不用写switch语句
vector
-
vector
是一个类模板,被称为向量或容器。vectro<int> i1(10); //十个元素,每个都是0 vector<string> s1(10, "hi"); //十个元素,每个都是hi vector<string> s2{10, "hi"}; //同上 vector<string> s3{"hi", "hi"}; //两个hi元素
-
向
vector
对象中添加数据用push_back()
函数(注:如果循环体内包含向vector对象添加元素的语句,则不能用范围for语句)。vector
的操作函数和string
差不多(没有+=)。 -
vector
对象的类型总是包括着元素的类型:vector::size_type //错误! vector<int>::size_type //正确!
-
vector
对象和string
对象的下标运算符可用于访问已存在的元素,但不能用于添加元素(确保下标合法的一种有效手段:尽可能使用范围for语句)。
迭代器
-
除了
vector
容器之外,标准库还定义了许多容器,它们都能使用迭代器(string
也能使用),但只有其中很少几种支持下标运算符。 -
迭代器的使用:
//由编译器决定b和e的类型 //end成员返回指向容器“尾元素下一位置”的迭代器(不能解引用) auto b = v.begin(), e = v.end();
-
标准容器迭代器的运算符:*iter、->iter、++iter、–iter、iter1==iter2、iter1!=iter2。运算方式和指针类似:
string s("some string"); if (s.begin() != s.end()) { //判断非空 auto it = s.begin(); *it = toupper(*it); }
-
由于标准库的很多容器的迭代器都没定义
<
运算符,所以我们只要养成使用迭代器和!=
的习惯,而不用太在意是哪种容器。如:for (auto it=s.begin(); it!=s.end() && !isspace(*it); ++it) *it = toupper(*it);
-
拥有迭代器的标准库类型使用
iterator
和const_iterator
(能读取但不能修改它所指的元素值)来表示迭代器的类型。如果string
对象或vector
对象是常量,只能使用后者;反之两者都可以。//如果只需要读操作而不需要写操作,最好使用常量类型 //it的类型是vector<int>::const_iterator //不论vector对象是否为常量,返回值都是const_iterator auto it1 = v.cbegin(); auto it2 = v.end();
-
和范围for一样,但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
-
vector
和string
还支持的运算有:iter+n、iter-n、iter1+=n、iter1-=n、iter1-iter2(返回带符号的difference_type
型) 、>、<、>=、<=。 -
使用迭代器完成二分搜索:
//text为有序序列 auto beg = text.begin(), end = text.end(); auto mid = beg + (end - beg)/2; while (mid!=end && *mid!=sought) { if (sought < *mid) end = mid; else beg = mid + 1; mid = beg + (end - beg)/2; }
数组
-
如果不清楚元素的确切个数(维度),请使用
vector
! -
初始化数组时,维度必须是一个常量表达式。
unsigned cnt = 10; //cnt不是常量表达式 const unsigned num = 42; int a[10] = {}; //初始化10个元素为0 int *parr[num]; //含有42个整形指针的数组(如果定义在函数内部,默认初始化会令数组含有未定义的值) int (*par)[num]; //par指向一个含有42个整数的数组 int *(&array)[num]; //array是数组的引用,该数组包含42个指针
-
和
vector
一样,数组的元素应为对象,因此不存在引用的数组。 -
如果用字符串字面值初始化字符数组,则最后一个元素为’\0’。注意
char a[6] = "Daniel";
的语句是错误的(数组大小至少是7)。 -
数组间不允许拷贝和赋值!
-
使用数组下标时,通常将其定义为
size_t
型(带符号),该类型定义在头文件cstddef
中。两数组元素指针相减(这两个指针须指向同一数组内的元素)的结果为ptrdiff_t
类型(带符号),同样定义在cstddef
头文件中。 -
数组名很多时候会被自动转换为指向首元素地址的指针。
int ia[] = {0,1,2,3,4,5,6}; auto ia2 = ia; //ia2是一个整形指针 //上一句等价于 auto ia2(&ia[0]); decltype(ia) ia3 = {0,1,2}; //例外!!! ia3[2] = 0; //decltype返回数组类型
-
begin
和end
函数被定义在iterator
头文件中:int ia[] = {0,1,2}; int *pbeg = begin(ia); //指向第一个元素 int *pend = end(ia); //指向尾元素的下一位置(尾后指针不能解引用或递增)
-
只要指针指向的是数组中的元素(或尾元素的下一位置),都可以执行下标运算:
int a[] = {0,1,2,3,4}; int p = *(a+3); //p为3 int *i = &a[2]; //i指向2 int j = i[1] //i[1]等价于*(i+1),即3 int k = i[-1] //i[-1]等价于*(i-1),即1
C风格字符串(不推荐使用)
-
定义在头文件
ccstring
中的函数包括:strlen()
、strcmp
、strcat
、strcpy
。传入此类函数的指针必须指向以空字符作为结束的数组。 -
任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代:
- 允许使用以空字符结束的字符数组初始化
string
对象或为string
对象赋值。 - 在
string
对象的加法运算中,允许使用以空字符结束的字符数组作为其中一个运算对象。
上述性质反过来不成立。
string s("Hello world!"); const char *p = s.c_str();
- 允许使用以空字符结束的字符数组初始化
-
类似地,允许使用数组初始化
vector
对象,但不允许用vector
对象初始化数组。int intArr[] = {0,1,2,3}; vector<int> ivec(begin(intArr), end(intArr));
多维数组
-
通常所说的多维数组其实是数组的数组。
int ia[3][4] = { {0,1,2,3}, {4,5,6,7}, {8,9,10,11} }; int ib[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; //同上 int ic[3][4] = {{0},{4},{8}}; //未列出的元素默认初始化 int id[3][4] = {0,3,6,9}; //初始化第一行的4个元素
-
若使用范围for处理多维数组,除了最内层的循环外,其他所有循环的控制变量都必须是引用类型(避免数组被自动转换为指针)。
for (const auto &row : ia) for (auto col : row) cout << col << endl;
-
由多维数组名转换得来的指针实际上是指向第一个内层数组的指针:
int ia[3][4]; int (*p)[4] = ia; //p指向ia首元素(一个有4个元素的数组) p = &ia[2]; //p指向ia尾元素(同样为一个数组)
-
使用
auto
、decltype
、标准库函数begin
和end
,看起来更简洁:for (auto p=begin(ia); p!=end(ia); ++p) { for (auto q=begin(*p); q!=end(*p); ++q) cout << q << endl; } //注意解引用!(p是指向数组的指针)
-
类型别名化简化多维数组的指针:
using array = int[4]; //typedef int array[4]; for (array *p=ia; p!=ia+3; ++p) for (int *q=*p; q!=*p+4; ++q) cout << *q << endl;