身为C++的零基础初学者,短期内把《C++Primer》啃下来是一个比较笨但是有效的方法,一方面可以掌握比较规范的C++语法(避免被项目中乱七八糟的风格带跑偏),另一方面又可以全面地了解C++语法以及C++11新标准(后续要做的事情就剩下查漏补缺,不断完善自己的知识体系)。
个人感觉从零学习一门新知识比较好的方法是快速了解知识的全貌,然后构建自己的知识地图,后续不断地补充相应的细节。
由于《C++Primer》和大多数的教科书一样废话连篇,因此想要精炼一下每篇文章的内容再打印成pdf,方便温故知新。
遍历获取string元素:
string str("some string"); for (auto c : str) cout << c << endl;前面讲到当引用被用作初始值时,真正参与初始化的其实是引用对象的值,此时编译器以引用对象的类型作为auto类型。
因此如果我们需要获得字符的引用来改变string,需要把循环变量定义为引用类型:
string s("Hello World!!!"); // 转化为大写 for (auto &c : s) c = toupper(c); cout << s << endl;C++标准要求vector应该能在运行时高效快速地添加元素,因此在定义vector对象时设定其大小也就没有必要了,事实上这么做可能性能更差。只有一种例外情况:即所有元素的值都一样。一旦元素的值有所不同,更有效的方式是先定义一个空的vector对象,再在运行时向其添加具体值。
大部分的操作都和string类似:
v.empty() // 是否为空 v.size() v.push_back(t) // 尾部插入 v[n] v1 = v2 v1 = {a,b,c...} // 用列表中的元素拷贝替换v1中的元素 v1 == v2 // 当且仅当元素数量相同且相应位置的元素值都相同 v1 != v2 <, <=, >, >= // 以字典顺序比较注意:
如果想遍历并修改vector中的值,可以在循环条件写for (auto &i : v) vector<T>的下标类型为vector<T>::size_type vector和string对象的下标运算符可用于访问已存在的元素而不能用于添加元素,试图用下标访问一个不存在的元素将引发错误使用迭代器遍历string:
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it) *it = toupper(*it); // 将当前字符改成大写迭代器类型:
C++11中通过cbegin()和cend()函数可以得到const_iterator迭代器。
vector<int>::iterator it; // it能读写vector<int>元素 string::iterator it2; // it能读写string中字符 vector<int>::const_iterator it3; // it3只能读元素而不能写元素 string::const_iterator it4; // it4只能读字符而不能写字符与vector不同的是,数组的大小确定不变,不能随意向数组中添加元素。这种特性使得对于某种特殊的应用而言程序的运行时性能更好,但是也损失了一些灵活性。
和内置类型的变量一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组内含有未定义的值。
// 显式初始化数组元素 int a1[3] = {0, 1, 2}; int a2[] = {0, 1, 2}; // 维度为3 int a3[5] = {0, 1, 2}; // 等价于a3[] = {0, 1, 2, 0, 0}; // 字符数组的特殊性 char a1[] = {'C', '+', '+'}; // 列表初始化, 不带空字符 char a2[] = {'C', '+', '+', '\0'}; // 列表初始化, 含有空字符 char a3[] = "C++"; // 自动在末尾添加空字符 不允许用一个数组初始化另一个数组,也不允许用一个数组给另一个数组赋值复杂的数组声明 int *ptrs[10]; // 含有10个整型指针的数组 int (*Parray)[10] = &arr; // Parray指向一个含有10个整数的数组 int (&arrRef)[10] = arr; // arrRef引用一个含有10个整数的数组与vector和string一样,当需要遍历数组的所有元素时,最好的方法是使用范围for语句:
for (auto i : scores) cout << i << " "; cout << endl;在C++中,指针和数组由非常紧密的联系,使用数组的时候编译器一般会把它替换为一个指向数组首元素的指针。
C++11新标准引入了两个名为begin和end的函数:
int *pbeg = begin(arr), *pend = end(arr); // 寻找第一个负值元素 while (pbeg != pend && *pbeg >= 0) ++pebg;只要指针指向的是数组中的元素,都可以执行下标操作:
int *p = &ia[2]; // p指向索引为2的元素 int j = p[1]; // p[1]等价于*(p + 1), 即ia[3] int k = p[-2]; // p[-2]即ia[0]虽然C++支持C风格字符串,但最好还是不要使用。这是因为不仅使用起来不太方便,而且极易引发程序漏洞,是诸多安全问题的根本原因。
一些可操作C风格字符串的函数定义在cstring头文件中,cstring是C语言头文件string.h的C++版本:
strlen(p) // 返回p的长度, 空字符不算在内 strcmp(p1, p2) // 比较p1和p2的相等性 strcat(p1, p2) // 将p2附加到p1之后, 返回p1 strcpy(p1, p2) // 将p2拷贝到p1, 返回p1对于C++中的string,比较大小时是使用普通的关系运算符和相等性运算符;但是C风格字符串需要使用strcmp函数。
如果我们要实现两个C风格字符串拼接,正确的方法是使用strcat和strcpy函数,还需要使用一个用于存放结果字符串的数组,例如下面的代码虽然很常见,但是充满了安全风险,极易引发错误:
// 如果我们计算错了largeStr的大小将引发错误 strcpy(largeStr, cal); // 将ca1拷贝到largeStr strcat(largeStr, car2); // 将ca2连接到largeStr后面大多数情况下,使用标准库string要比C风格字符串更安全
现代的C++程序中充满了数组或者C风格字符串的代码,因此C++专门提供了一组功能:
严格来说,C++中没有多维数组,通常所说的多维数组其实都是数组的数组。
初始化:
int ia[3][4] = { // 三个元素, 每个元素都是大小为4的数组 {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} } // 显式地初始化每行的首元素 int ia[3][4] = {{ 0 }, { 4 }, { 8 }}; // 显式地初始化第一行, 其他元素执行值初始化 int ix[3][4] = {0, 3, 6, 9}遍历元素:
int ia[rowCnt][colCnt]; for (size_t i = 0; i != rowCnt; ++i) { for (size_t j = 0; i != colCnt; ++i) { ia[i][j] = i * colCnt + j; } } // 使用范围for语句 size_t cnt = 0; for (autp &row : ia) // 对于外层数组每一个元素 for (auto &col : row) { // 对于内层数组每一个元素 col = cnt; ++cnt; }