一、基本概念
泛型编程:所谓泛型程序设计,就是编写不依赖于具体数据类型的程序。C++中,模板是泛型编程的主要工具。泛型程序设计的主要思想是将算法从特定的数据结构中抽象出来,使算法成为通用的、可以作用于各种不同的数据结构。以函数模板形式实现的通用算法与各种通用容器结合,提高了软件的复用性[2]。例如,所有标准库容器的迭代器都定义了==和!=,我们只要养成使用迭代器和!=的习惯,就不用太在意用的到底是哪种容器类型。
缓冲区溢出(buffer overflow):一种严重的程序故障,主要的原因是试图通过一个越界的索引访问容器内容,容器类型包括string、vector和数组等[1]。
容器(container):容器是容纳、包含一组元素的对象。容器类库中包含13种基本容器:向量(vector)、双端队列(deque)、列表(list)、单向链表(forward_list),数组(array),集合(set)、多重集合(multiset)、映射(map)、多重映射(multimap),以及后面四种容器的无序(unorder)形式。顺序容器(sequence container)将一组具有相同类型的元素以严格的线性形式组织起来,向量、双端队列、列表、单向链表和数组容器就属于这一种;关联容器(associative container)具有根据一组索引来快速提取元素的能力,集合和映射容器就属于这一种。而关联容器又可分为有序和无序,有序容器中键(key)按顺序存储,无序容器则使用哈希函数组织元素[2]。
迭代器(iterator):迭代器提供了顺序访问容器中每个元素的方法,如++、--、*、->等运算符。指针也具有相同的特性,因此指针本身就是一种迭代器,迭代器是泛化的指针[2]。所有标准库容器都可以使用迭代器,但是其中只有少数几种才同时支持下标运算符。
常量表达式(const expression):指值不会改变并且在编译过程就能得到计算结果的表达式。常量表达式有字面值、用常量表达式初始化的const对象等。
程序的完整流程:需求分析、程序设计、代码编写(遵循良好的编码规范,确保代码的可读性、可维护性和可扩展性)、编译和构建(使用相应的编译器将源代码转换为可执行文件或库文件)、调试和测试(通过单元测试、集成测试和系统测试,验证程序在各种情况下的正确性和稳定性)、部署和运行(将程序部署到目标环境中,并根据需要提供必要的配置和资源)、优化和维护以及使用文档交付。
范围for语句:该语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,语法形式为:
for(declaration:expression)
statement;
其中,expression部分是一个对象,用于表示一个序列。declaration部分负责定义一个变量,该变量在每次迭代后会被初始化为expression部分的下一个元素值。
编程规范:
现代的C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针[1]。
二、auto类型说明符和decltype类型指示符
auto类型说明符:
使用目的:编程时常常需要把表达式的值赋给变量,这就要求在声明变量的时候清楚地知道表达式的类型。auto类型说明符,可以让编译器替我们去分析表达式所属的类型。
注意事项:编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。如,auto一般会忽略掉顶层const,同时底层const则会保留下来。
decltype类型指示符:
使用目的:希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。decltype类型指示符,可以让编译器分析表达式(如变量、函数等)并得到它的类型,却不实际计算表达式的值。
注意事项:decltype的结果类型与表达式形式密切相关。如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,编译器就会把它当成是一个表达式,从而decltype就会得到引用类型。
常量符号:
constexpr符号,既是一种类型说明符,声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化;也是C++11标准下的一种特殊的constexpr函数,这种函数足够简单以使得编译时就可以计算其结果,这样就能用constexpr函数去初始化constexpr变量。constexpr函数必须遵循①函数的返回类型及所有形参的类型都得是字面值类型;②函数体中必须有且只有一条return语句。
注意事项:constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关(即声明类型为常量指针)。
const限定符,定义一种值不能被改变的变量。const对象一旦创建后其值就不能再改变,所以const对象必须初始化。
const术语:针对复杂的指针类型而言,顶层const(top-level const)表示指针本身是个常量,而用底层const(low-level const)表示指针所指的对象是一个常量。一般的话,顶层const可以表示任意的对象是常量,如算术类型、类、指针等;而底层const则与指针和引用等复合类型的基本类型部分有关。
二、编程实践
练习3.17(P94)
# include<vector>
# include<iostream>
# include<string>
using namespace std;
int main()
{
string word = "";
vector<string> WordVector;
cout << "请输入一组英文单词(单词间利用空格符间隔,EOF结尾):" << endl;
// 将所有原始单词存入词向量中
while (cin >> word)
{
WordVector.push_back(word);
}
cout << "单词向量构建完成!" << endl;
// 逐单词修改为大写形式并输出(每个单词占用一行)
// 基于范围for循环修改单词
for (auto b = WordVector.begin(); b != WordVector.end(); ++b)
{
for (auto &w : *b)
w = toupper(w);
cout << *b << endl;
}
return 0;
}
实验结果:
实验笔记[3][4]:
Ⅰ cin输入流对象自动忽略空格符、制表符(Tab);
Ⅱ cin读取的是从非空白字符开始,到与目标类型不匹配的第一个字符之间的全部内容。不匹配的内容将被保存在输入缓冲流中,等待下一次输入;
Ⅲ 文件读取末尾存在EOF标识符。但是,若需要用户在控制台输入信息并模拟文件结尾符,则需要在输入信息结尾完成一系列操作(Enter换行 -> Ctrl+Z -> Enter换行)。需要注意的是,该操作限于Windows系统。
练习3.20(P94)
# include<vector>
# include<iostream>
using namespace std;
int main()
{
int num = 0;
vector<int> numVector;
vector<int> biSum;
cout << "请输入一系列整型数字串(数字与数字之间利用空格间隔,输入末尾以EOF结尾)" << endl;
while (cin >> num)
numVector.push_back(num);
auto amount = numVector.size();
auto code = amount % 2;
auto b = numVector.begin(), e = numVector.end()-1;
// 双指针移动
if (amount == 0)
cout << "整数向量未存储元素!" << endl;
else
{
if (code == 1)
{
while (b != e)
{
biSum.push_back(*b + *e);
++b;
--e;
}
// 添加中间单值
biSum.push_back(*b);
}
else
{
while (b != e - 1)
{
biSum.push_back(*b + *e);
++b;
--e;
}
biSum.push_back(*b + *e);
}
}
// 打印计算结果
for (auto val : biSum)
cout << val << endl;
return 0;
}
实验结果:
实验笔记:
Ⅰ size成员函数返回vector对象中元素的个数,返回值的类型是由vector定义的size_type类型(此处实验为vector<int>::size_type);
练习3.43(P116)
# include<iostream>
using namespace std;
// 所有版本直接写出数据类型
// 版本一:范围for语句管理迭代过程
void Version1(int(&array)[3][4],int row)
{
for (int (&arr)[4] : array)
{
for (int a : arr)
cout << a << " ";
cout << endl;
}
}
// 版本二:下标运算符
void Version2(int array[][4],int row)
{
for (unsigned i = 0; i != row; ++i)
{
for (unsigned j = 0; j != 4; ++j)
cout << array[i][j] << " ";
cout << endl;
}
}
// 版本三:指针
void Version3(int (*array)[4], unsigned row)
{
for (decltype(array) arr = array; arr != array + row; ++arr)
{
for (int *a = *arr; a != *arr + 4; ++a)
cout << *a << " ";
cout << endl;
}
}
// 版本四:auto引入
// 将二维数组的引用作为参数传递
void Version4(int (&array)[3][4],int row)
{
for (auto &arr : array)
{
for (auto a : arr)
cout << a << " ";
cout << endl;
}
}
int main()
{
int ia[3][4] = { {0,1,2,3},{4,5,6,7},{8,9,10,11} };
cout << "Version1输出结果如下所示:" << endl;
Version1(ia,3);
cout << "Version2输出结果如下所示:" << endl;
Version2(ia,3);
cout << "Version3输出结果如下所示:" << endl;
Version3(ia,3);
cout << "Version4输出结果如下所示:" << endl;
Version4(ia,3);
return 0;
}
实验结果:
实验笔记[5][6]:
Ⅰ 要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型;
Ⅱ 定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。引用只能绑定在对象上,引用自身不是对象;
Ⅲ 把二维数组作为参数时,一般不指定数组第一维的大小,即使指定,也会被忽略。同时,传入的调用实参是指针,即指向固定个数容量数组的指针;
Ⅳ 如果使用数组名作函数的参数,则实参和形参都应该是数组名,且类型要相同。和普通变量作参数不同,使用数组名传递数据时,传递的是地址。
参考资料:
[1] C++ Primer中文版:第5版 /(美)李普曼(Lippman,S.B.),(美)拉乔伊(Lajoie,J.),(美)默(Moo,B.E.)著;王刚,杨巨峰译. —北京:电子工业出版社,2013.9.(第二章-变量和基本类型 /第三章-字符串、向量和数组)
[2] C++语言程序设计 / 郑莉,董渊编著.—5版.—北京:清华大学出版社,2020.11.(第十章-泛型程序设计与C++语言标准模板库)
Debug参考资料:
[3] C++ cin的使用,看这一篇就够了-CSDN博客
[4] 输入一串未知个数的数据直到EOF(-1)停止和键盘输入文件结尾符EOF_c++输入-1结束输入要怎么输-CSDN博客
[5] C++笔记 二维数组作为函数的参数详解 三种传参的方法总结 注意要点总结_二维数组作为参数传入函数-CSDN博客
[6] C++中如何将数组作为函数参数_c++ 数组作为函数参数-CSDN博客