目录
🌼面向过程的编程风格 -- 第2章
🍈2.2
要求
Pentagonal 数列的求值公式 P(n) = n(3n - 1) / 2,借此产生1,5,12,22,35等元素值。试定义一个函数,利用上述公式,将产生的元素放到用户传入的 vector 中,元素个数由用户指定。请检查元素个数的有效性(太大可能引发 overflow 问题)。接下来编写第二个函数,能将给定的 vector 的所有元素一一打印出来。此函数的第二参数接受一个字符串,表示存放在 vector 内的数列的类型。最后再写一个 main,测试上述两个函数。
代码
#include<iostream>
#include<string>
#include<vector>
using namespace std;
bool calc_elements(vector<int> &vec, int pos); // 计算并存储
void display_elems(vector<int> &vec,
const string &title, ostream &os=cout); // 显示元素序列
int main()
{
vector<int> pent; // 存储元素序列
const string title("Pentagonal Numeric Series"); // 定义一个字符串
if (calc_elements(pent, 0)) // 计算并存储位置0的元素
display_elems(pent, title); // 打印元素序列
if (calc_elements(pent, 8))
display_elems(pent, title);
if (calc_elements(pent, 14))
display_elems(pent, title);
if (calc_elements(pent, 138))
display_elems(pent, title);
}
bool calc_elements(vector<int> &vec, int pos)
{
// 检查元素个数有效性, 防止 overflow
if (pos <= 0 || pos > 64) {
cerr << "sorry. Invalid position: "<<pos<<endl;
return false;
}
for (int ix = vec.size() + 1; ix <= pos; ++ix)
vec.push_back( (ix*(3*ix-1)) / 2 ); // 插入元素
return true;
}
void display_elems(vector<int> &vec,
const string &title, ostream &os)
{
os << '\n' << title << "\n\t";
for (int ix = 0; ix < vec.size(); ++ix)
os << vec[ix]<<' ';
os << endl;
}
输出
0和138都是非法输入
sorry. Invalid position: 0
Pentagonal Numeric Series
1 5 12 22 35 51 70 92
Pentagonal Numeric Series
1 5 12 22 35 51 70 92 117 145 176 210 247 287
sorry. Invalid position: 138
Process returned 0 (0x0) execution time : 0.029 s
Press any key to continue.
🍈2.4
要求
写一个函数,以静态局部(local static)的vector储存 Pentagonal 数列元素。
此函数返回一个 const 指针,指向该 vector。
如果 vector 的大小小于指定的元素个数,就扩充 vector 的大小。
接下来再实现第二个函数,接受一个位置值,返回该位置上的元素。
最后,编写 main() 函数测试这些函数。
解释
(1)
// 第18行 return &_elems;在函数
pentagonal_series中,返回_elems的地址(即指针)是为了避免将整个_elems向量复制一次。通过返回指针,可以避免内存的额外开销,同时保证返回的是对同一个_elems向量的引用。因为
_elems是一个静态向量(static vector),它在函数第一次调用时被初始化,并且在后续的调用中保持着其状态。如果不使用指针返回_elems,而是直接返回_elems本身,那么每次函数调用时都会复制一份_elems向量,这样会产生额外的开销和内存消耗。因此,通过返回指向
_elems向量的指针,可以有效地避免不必要的复制操作和内存开销。
代码
#include<vector>
#include<iostream> // 输入输出流
using namespace std; // 标准命名空间
// 检查输入是否有效
inline bool check_validity(int pos)
{
return (pos <= 0 || pos > 64) ? false : true;
}
// 生成和返回数列
const vector<int>* pentagonal_series(int pos)
{
static vector<int> _elems; // 数列的静态向量
if (check_validity(pos) && (pos > _elems.size())) // 该位置未计算
for (int ix = _elems.size() + 1; ix <= pos; ++ix)
_elems.push_back( ix * (3*ix-1) / 2);
return &_elems; // 返回数列的指针
}
// 获取指定位置的值
bool pentagonal_elem(int pos, int &elem)
{
if (!check_validity(pos)) { // 无效输入
cout<<"Sorry. Invalid position: "<<pos<<endl;
elem = 0; // 元素置0
return false;
}
const vector<int> *pent = pentagonal_series(pos); // 获取数列的指针
elem = (*pent)[pos - 1]; // 指定位置元素值
return true;
}
int main()
{
int elem;
if (pentagonal_elem(8, elem)) // 计算并输出位置 8 的值
cout<<"element 8 is "<<elem<<'\n';
if (pentagonal_elem(88, elem))
cout<<"element 88 is "<<elem<<'\n';
if (pentagonal_elem(12, elem))
cout<<"element 12 is "<<elem<<'\n';
if (pentagonal_elem(64, elem))
cout<<"element 64 is "<<elem<<'\n';
}
输出
element 8 is 92
Sorry. Invalid position: 88
element 12 is 210
element 64 is 6112
🍈2.5
2.6会用模板再实现一次
要求
实现一个重载的 max() 函数,接受以下参数:
(a) 两个整数
(b) 两个浮点数
(c) 两个字符串
- - - -
(d) 一个整数vector
(e) 一个浮点数vector
(f) 一个字符串vector
- - - -
(g) 一个整数数组 和 一个表示数组大小的整数值
(h) 一个浮点数数组 和 数组大小的整数值
(i) 一个字符串数组 和 数组大小的值
解释
(1)
inline string max(const string& t1, const string& t2)a. 常量引用
const string&。这是因为字符串类型string的对象通常比较大,如果不使用引用传递的话,每次传递字符串时都会进行一次拷贝操作,这样会增加额外的开销和内存消耗.b. 使用常量引用作为函数参数,可以避免进行不必要的拷贝操作。常量引用允许我们在函数中以只读方式访问传递进来的字符串对象,而无需复制整个对象
(2)
这些函数被声明为
inline,以允许编译器将其插入到程序中的调用点,从而提高性能
代码
#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
using namespace std;
inline int max(int t1, int t2)
{ return t1 > t2 ? t1 : t2; }
inline float max(float t1, float t2)
{ return t1 > t2 ? t1 : t2; }
// 常量引用
inline string max(const string& t1, const string& t2)
{ return t1 > t2 ? t1 : t2; }
// -------------------------------------
// 常量引用...
inline int max(const vector<int> &vec)
{ return *max_element(vec.begin(), vec.end()); }
inline float max(const vector<float> &vec)
{ return *max_element(vec.begin(), vec.end()); }
inline string max(const vector<string> &vec)
{ return *max_element(vec.begin(), vec.end()); }
// -------------------------------------
inline int max(const int *parray, int Size)
{ return *max_element(parray, parray + Size); }
inline float max(const float *parray, int Size)
{ return *max_element(parray, parray + Size); }
inline string max(const string *parray, int Size)
{ return *max_element(parray, parray + Size); }
int main()
{
string sarray[] = {"we", "were", "her", "pride", "of", "ten"};
vector<string> svec(sarray, sarray + 6);
int iarray[] = {12,70,2,169,1,5,29};
vector<int> ivec(iarray, iarray + 7);
float farray[] = {2.5, 24.8, 18.7, 4.1, 23.9};
vector<float> fvec(farray, farray + 5);
int imax = max( max(ivec), max(iarray, 7) );
float fmax = max( max(fvec), max( farray, 5) );
string smax = max ( max(svec), max(sarray, 6) );
cout << "imax should be 169 -- found: "<< imax << '\n'
<< "fmax should be 24.8 -- found: "<< fmax << '\n'
<< "smax should be were -- found: "<< smax << '\n';
return 0;
}
输出
imax should be 169 -- found: 169
fmax should be 24.8 -- found: 24.8
smax should be were -- found: were
🍈2.6
要求
以 template 重新完成练习 2.5,并对 main() 函数适度修改。
用一个 template max() 函数取代 9 个 non-template max() 函数。
main() 不需要任何修改。
解释
(1)如果函数名还是 max,会报错
(2)error: call of overloaded 'max(int, int)' is ambiguous
(3)二义性错误的原因是使用了自定义的
max函数与<algorithm>标准库中的std::max函数冲突(4)这里将上个代码中的 max 改为 Max 即可
代码
#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
using namespace std;
template <typename Type>
inline Type Max(Type t1, Type t2)
{ return t1 > t2 ? t1 : t2; };
template <typename elemType>
inline elemType Max( const vector<elemType> &vec)
{ return *max_element( vec.begin(), vec.end() ); }
template <typename arrayType>
inline arrayType Max( const arrayType *parray, int Size)
{ return *max_element( parray, parray + Size ); }
int main()
{
string sarray[] = {"we", "were", "her", "pride", "of", "ten"};
vector<string> svec(sarray, sarray + 6);
int iarray[] = {12,70,2,169,1,5,29};
vector<int> ivec(iarray, iarray + 7);
float farray[] = {2.5, 24.8, 18.7, 4.1, 23.9};
vector<float> fvec(farray, farray + 5);
int imax = Max( Max(ivec), Max(iarray, 7) );
float fmax = Max( Max(fvec), Max( farray, 5) );
string smax = Max ( Max(svec), Max(sarray, 6) );
cout << "imax should be 169 -- found: "<< imax << '\n'
<< "fmax should be 24.8 -- found: "<< fmax << '\n'
<< "smax should be were -- found: "<< smax << '\n';
return 0;
}
输出
imax should be 169 -- found: 169
fmax should be 24.8 -- found: 24.8
smax should be were -- found: were
🌼泛型编程风格 -- 第3章
🍍3.1
要求
写一个读取文本文件的程序,将文件中的每个 key 存入map
再定义一个由 “排除字眼” 组成的 set,其中包含 a, an, or , the, but 之类的 Key
将某个 key 放入 map 前,先确定该 key 不在 “排除字集” 中
一旦文本读取完毕,显示一份 key 清单,并显示每个 key 出现次数
还可加以扩展,显示 key 前,允许用户查询某个 Key 是否出现在文本文件中
解释
(1)const set<string>& 为啥后面加个&
答:
在C++中,函数可以使用引用参数(Reference Parameter)。在函数声明中,在参数类型后面加上&符号就表示该参数是一个引用类型的参数。
const set<string>& exclude_set这里的
exclude_set是一个常量引用,它指向一个set<string>对象。使用引用参数的好处是避免了函数调用时进行大量的拷贝操作,提高了程序的性能和效率。通过使用引用参数,函数可以直接访问和修改传递给它的实际对象,而无需创建对象的副本。另外,在这个示例代码中,将
exclude_set声明为常量引用的原因是函数initialize_exclusion_set只需要读取exclude_set,而不会修改它的内容。通过将其声明为常量引用,可以确保函数内部不会意外地修改exclude_set
(2)
ifstream ifile("C:\\Users\\1\\Desktop\\C++\\column.txt"); ofstream ofile("C:\\Users\\1\\Desktop\\C++\\column.map");答:
定义了两个文件流ifstream和ofstream,其中ifile打开了名为"C:\My Documents\column.txt"的文本文件,而ofile则打开了名为"C:\My Documents\column.map"的二进制文件。可以通过这些文件流来读取或写入文件中的内容
(3)
cerr << "Unable to open file -- bailing out!\n";答:
a. cerr是C++标准库中的一个输出流对象,它代表了标准错误流(standard error stream)。与cout用于标准输出不同,cerr主要用于将错误消息输出到终端或其他错误日志文件中。b.
<<是C++中的流插入运算符,用于将字符串或其他数据插入到输出流中c.
cerr,可以将错误信息输出到标准错误流中,而不是标准输出流cout中。这样做有助于区分程序的正常输出和错误信息,方便调试和错误处理
(4)
map<string, int>::const_iterator it;答:
声明了一个名为
it的常量迭代器(iterator),用于遍历map<string, int>容器中的元素迭代器:
迭代器将容器和算法联系起来。
在 C++ 标准库中,很多算法函数(如排序、查找、遍历等)都接受迭代器作为参数。这些算法函数不依赖于具体的容器类型,而是通过迭代器来遍历和处理容器中的元素,从而实现对容器的各种操作。
column.txt
MooCat is a long-haired white kitten with large
black patches Like a cow looks only he is a kitty
poor kitty Alice says cradling MooCat in her arms
pretending he is not struggling to break free
代码
#include<map> // 包含map库,用于使用映射容器
#include<set> // 包含set库,用于使用集合容器
#include<string> // 包含string库,用于使用字符串
#include<iostream> // 包含iostream库,用于输入输出操作
#include<fstream> // 包含fstream库,用于文件操作
using namespace std;
void initialize_exclusion_set(set<string>&); // 初始化排除词集合
void process_file(map<string, int>&, const set<string>&, ifstream&); // 处理文件,统计单词出现次数
void user_query(const map<string, int>&); // 用户查询,查找指定单词的出现次数
void display_word_count(const map<string, int>&, ofstream&); // 显示单词及其出现次数
int main()
{
ifstream ifile("C:\\Users\\1\\Desktop\\C++\\column.txt"); // 打开输入文件流(读取文件)
ofstream ofile("C:\\Users\\1\\Desktop\\C++\\column.map"); // 打开输出文件流(写入文件)
if (!ifile || !ofile) {
cerr << "Unable to open file -- bailing out!\n"; // 若打开文件失败输出错误信息并退出程序
return -1;
}
set<string> exclude_set; // 定义排除词集合
initialize_exclusion_set(exclude_set); // 初始化排除词集合
map<string, int> word_count; // 定义单词计数映射容器
process_file(word_count, exclude_set, ifile); // 处理文件,统计单词出现次数
user_query(word_count); // 用户查询,查找指定单词的出现次数
display_word_count(word_count, ofile); // 显示单词及其出现次数
return 0;
}
void initialize_exclusion_set(set<string>& exs) {
static string _excluded_words[25] = {
"the", "and", "but", "that", "then", "are", "been",
"can", "a", "could", "did", "for", "of",
"had", "have", "him", "his", "her", "its", "is",
"were", "which", "when", "with", "would"
};
exs.insert(_excluded_words, _excluded_words + 25); // 将排除词插入排除词集合中
}
void process_file(map<string, int>& word_count,
const set<string>& exclude_set, ifstream& ifile)
{
string word;
while (ifile >> word) {
if (exclude_set.count(word))
continue; // 若单词在排除词集合中,则跳过该单词继续下一次循环
word_count[word]++; // 统计单词出现次数
}
}
void user_query(const map<string, int>& word_map)
{
string search_word;
cout << "Please enter a word to search: q to quit ";
cin >> search_word;
while (search_word.size() && search_word != "q") {
map<string, int>::const_iterator it;
if ((it = word_map.find(search_word)) != word_map.end())
cout << "Found! " << it->first
<< " occurs " << it->second
<< " times.\n"; // 输出找到的单词及其出现次数
else cout << search_word
<< " was not found in text.\n"; // 输出未找到的单词信息
cout << "\nAnother search? (q to quit) ";
cin >> search_word;
}
}
void display_word_count(const map<string, int>& word_map, ofstream& os)
{
map<string, int>::const_iterator
iter = word_map.begin(),
end_it = word_map.end();
while (iter != end_it) {
os << iter->first << " ( "
<< iter->second << " ) " << endl; // 输出单词及其出现次数到输出文件流
++iter;
}
os << endl;
}
输出
Please enter a word to search: q to quit Alice
Found! Alice occurs 1 times.
Another search? (q to quit) MooCat
Found! MooCat occurs 2 times.
Another search? (q to quit) a
a was not found in text.
Another search? (q to quit) q
🍍3.4
前言
(1) 关于back_inserter():back_inserter的使用-优快云博客
(2)关于 partition()
std::partition - cppreference.com
(3)关于 copy():cplusplus.com/reference/algorithm/copy/
(4)关于 eos
eos是一个标识迭代器,它表示输入数据的结束位置。在这段代码中,istream_iterator<int> eos;声明了一个名为eos的istream_iterator对象,用于指示输入数据的结束。通过使用
copy(in, eos, back_inserter(input));将输入流in中的数据复制到整数型向量input中。copy函数会从in开始读取数据,直到遇到eos,即输入数据的结束位置。因此,
eos在这里起到了标记输入数据结束位置的作用,它是一个特殊的迭代器,不指向实际的数据,只是表示输入结束的标志
要求
编写一个程序,利用 istream_iterator 从标准输入设备读取一连串整数。
利用 ostream_iterator 将其中的奇数写至某个文件,每个数值皆以空格分隔。
再利用 ostream_iterator 将偶数写到另一个文件,每个数值单独放在一行中。
首先定义2个 istream_iterator,用来从标准输入设备读入一串整数。其中一个绑定(bind)至 cin,另一个表示文件结束 (end-of-file)
istream_iterator<int> in( cin ), eos;
接下来,定义一个 vector,用来存储读入的元素:
vector< int > input;
以下使用泛型算法 copy() 进行读取操作:
#include <iterator>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
vector< int > input;
istream_iterator<int> in( cin ), eos;
copy( in, eos, back_inserter( input ));
// ...
}
在此,vector 是必要的,因为 copy() 会采用 assignment 运算符来复制每个元素。由于 input vector 是空的,第一个元素赋值操作就会导致溢出(overfolw)错误。使用 back_iterator() 可以避免 assignment 运算符,改以 push_back() 函数来插入函数。
下面以泛型算法 partition() 来区分奇偶数。当然还得配合 function object even_elem() 的使用,后者在传入值为偶数时会返回 true
class even_elem {
public:
bool operator() ( int elem )
{ return elem % 2 ? false : true; }
};
vector<int>::iterator division =
partition( input.begin(), input.end(), even_elem() );
还需要 2 个 ostream_iterator:一个用于偶数文件,一个用于奇数文件。
首先,以 ofstream class 打开两个输出文件:
#include<fstream>
ofstream even_file( "C:\\Users\\1\\Desktop\\C++\\even_file" ),
odd_file( "C:\\Users\\1\\Desktop\\C++\\odd_file" );
if ( !even_file || !odd_file) {
cerr << "arghh! unable to open the output files. bailing out!";
return -1;
}
再将这 2 个 ostream_iterator 绑定至相应的 ofstream 对象上。第 2 个参数代表每个元素输出时的分隔符。
ostream_iterator<int> even_iter( even_file, "\n" ),
odd_iter( odd_file, " ");
最后,再以泛型算法 copy(),将已被分开的奇偶元素分别输出至不同文件:
copy( input.begin(), division, even_iter );
copy( division, input.end(), odd_iter );
完整代码
#include <iterator> // 包含迭代器相关的头文件
#include <vector> // 包含向量容器的头文件
#include <iostream> // 包含输入输出流的头文件
#include <algorithm> // 包含标准算法的头文件
#include <fstream> // 包含文件流的头文件
using namespace std; // 使用标准命名空间
class even_elem { // 定义一个名为even_elem的类
public:
bool operator()(int elem) // 重载()运算符,用于判断是否为偶数
{
return elem % 2 ? false : true; // 奇数返回 false
}
};
int main()
{
vector<int> input; // 创建一个整型向量input,用于存储输入的数据
istream_iterator<int> in(cin), eos; // 创建一个istream_iterator对象in,用于从cin读取数据
copy(in, eos, back_inserter(input)); // 将输入的数据复制到input向量中
// 使用partition函数将input向量中的元素按照even_elem对象进行分割,并返回分割点的迭代器
vector<int>::iterator division = partition(input.begin(), input.end(), even_elem());
ofstream even_file("C:\\Users\\1\\Desktop\\C++\\even_file"), // 打开一个名为even_file的输出文件流
odd_file("C:\\Users\\1\\Desktop\\C++\\odd_file"); // 打开一个名为odd_file的输出文件流
if (!even_file || !odd_file) { // 检查是否成功打开了输出文件流,如果失败则输出错误信息并返回-1
cerr << "arghh! unable to open the output files. bailing out!";
return -1;
}
ostream_iterator<int> even_iter(even_file, "\n"), // 创建一个ostream_iterator对象even_iter,用于向even_file写入数据,并以换行符分隔
odd_iter(odd_file, " "); // 创建一个ostream_iterator对象odd_iter,用于向odd_file写入数据,并以空格分隔
copy(input.begin(), division, even_iter); // 将input向量中的偶数部分复制到even_iter指定的输出流中
copy(division, input.end(), odd_iter); // 将input向量中的奇数部分复制到odd_iter指定的输出流中
}
输入输出
然后到创建好的文件查看就行
2 4 5 3 9 5 2 6 8 1 8 4 5 7 3 520 1314^Z
该博客围绕C++编程展开,介绍了面向过程和泛型两种编程风格。面向过程部分涉及Pentagonal数列求值、函数重载等;泛型编程部分包含文本文件读取、元素分类输出等。通过多个示例代码展示了不同编程风格的实现及应用。
2933

被折叠的 条评论
为什么被折叠?



