C++基础编程实践10例
写在前面
这篇Blog是基于哈工大(威海)刘东明老师所讲述课程及所给程序写就,用于C++大题考前复习。侧重于在实践中提高代码能力,需要注意的细节,以及许多库函数的使用。很少涉及语法知识,如有需要可以看另一篇Blog:还没上传。这是去往bing的url。
让我们开始吧(●’◡’●)
Demo1 带符号的大数运算:顺序程序,函数封装,头文件的调用
题目描述:
给定一个输入,如果是数字,计算其平方并输出,否则显示“input error!”。其中数字可能带符号,或很大(超过int类型能容纳的最大值)。其中大数运算的类函数文件在此给出。
题目解答:
#include <iostream>
#include <string>
#include <sstream>
#include "bignum.cpp"// 大数运算类
using namespace std;
bool IsDigit(string& str)
{
bool flag=true;
int i;
// 判断第一位是否是正负号
if(str[0]=='+'||str[0]=='-')
i=1;
else
i=0;
for(i=i;i<str.length();i++)
{
while(!isdigit(str[i])
{
flag=false;
break;
}
}
return flag;
}
//求大数的平方
void square(string s)
{
BigNum ss(1);
ss=ss.str2Num(s);
ss=ss*ss;
ss.print();
}
int main()
{
string s;
cout<<"input a number:";
cin>>s;
if(IsDigit(s))
{
square(s);
}
else
{
cout<<"input error!"<<endl;
}
system("pause");
return 0;
}
题目收获:
- 引入外部文件时,
<>
会在系统路径进行寻找,""
会在链接库和本地文件进行查找 sizeof()
、size()
和length()
:sizeof()
的功能是计算一个数据类型的大小,这个类型可以是数组、函数、指针、对象等,单位为字节,它的返回值是size_t类型,也就是unsigned int类型,是一个无符号整数。特别注意两点:1. 其会将空字符\0
计算在内。2.sizeof()
不是一个函数,它是一个运算符,所以它不需要包含任何头文件。size()
、length()
是c++中string
的类的方法,只有string类的对象才可以用该方法,而字符串数组不可用,size()
与length()
完全等同,遇到空字符不会被截断,可以返回字符串真实长度,它们结束的标志都是null-termination
也就是\n
,需要包含头文件#include<string>
。strlen()
,源于C语言,遇到空字符会截断,从而无法返回字符串真实长度,需要include<string.h>
- C++ 头文件里的:isalpha、islower、isupper、isalnum、isblank、isspace函数
str2Num
是BigNum
类中自己写的方法,如果需要自己实现,通常使用下面的代码:int str2num(string s) { stringstream ss; double i; ss<<s; ss>>i; return i; }
Demo2 抽奖程序:对象思想,动态分配,随机数的使用
题目描述:
给定总人数和要抽奖的人数,利用数组实现抽奖程序。
题目解答:
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
class select
{
private:
int n,m;
public:
select(int nn,int mm)
{
n=nn;m=mm;
}
void prize()
{
int i,temp;
int* a=new int[n];// 动态数组
// int a[n]; 不稳定,有些编译器不支持
for(i=0;i<n;++i)
{
a[i]=0;
}
for(i=0;i<m;++i)
{
while(true)
{
temp=rand()%n;
if(a[temp]==0)
{
cout<<"the people of num "<<temp+1 << "is selected!"<<endl;
a[temp]=1;
break;
}
}
}
delete [] a;//释放动态数组
}
};
int main()
{
srand(time(0));
select s(30,3);
s.prize();
return 0;
}
题目收获:
- C++中的动态内存分配
- 随机数如何初始化,资料很多,不再给出
Demo3 父类有重载的构造函数:重载,默认构造函数,继承,覆盖
#include <iostream>
using namespace std;
class A
{
protected:
int m;
public:
A()
{
m = 10;
cout << "A's default constructor is called!" << endl;
}
A(int mm)
{
m = mm;
}
void output()
{
cout << "m=" << m << endl;
}
};
class B : public A
{
protected:
int n;
public:
// 自动调用父类的默认构造函数
B(int nn)
{
n = nn;
}
// 调用指定的父类构造函数
B(int mm, int nn) : A(mm)
{
n = nn;
}
void output()
{
cout << "n=" << n << endl;
}
void test()
{
// 调用父类的函数
A::output();
// 默认被覆盖
output();
}
};
int main()
{
B b(2);
b.test();
return 0;
}
Demo4 构造一个类:友元函数,运算符重载
题目描述:
构造一个类,分别完成输入输出,双目(一个加号,一个赋值),单目运算符(一个前增,一个后增)的重载。
#include <iostream>
using namespace std;
class A
{
private:
int m;
public:
A(int mm){m=mm;}
// 使用友元函数使得类外可以访问类内private和protect!
friend istream &operator>>(istream &os, A &a);
friend ostream &operator<<(ostream &os, A a);
// 双目运算符一般有两个参数,但在类内重载时往往省略第一个参数,默认即为this指向的当前对象
A operator+(A a)
{
A temp(0);
temp.m = m + a.m;
return temp;
}
// 如果缺少显示的返回值类型时,编译器假定为int类型
// operator=(point p)
// {
// x = p.x;
// y = p.y;
// }
// 当然也可以写成下面的形式
A &operator=(A &a)
{
this->m = a.m;
a.m = 9;
/* 其实此处的返回值并没有意义,因为譬如调用a1=a2,此时双目运算符的两个操作数为this(a1), A &a(a2)。
而返回值是整个表达式的值,而不会赋值给this。只有当用于a3=a1=a2时返回才具有意义,即对于连续的操作很重要。
*/
return p;
}
// ++A,前自增运算符,返回加后引用
A &operator++()
{
++m;
return *this;
}
// A++,后自增运算符,返回加之前的值,此处的(int n)没有实际意义,只是用来区分。而返回一个会因函数退出而销毁的引用值被认为是不规范的。
A operator++(int n)
{
A old(*this);
++(*this);
return old;
}
};
// 类外是不能加friend关键字的,流对象禁止复制所以传引用,输入流必须使用应用以赋值到实参,输出流往往使用const A a
istream &operator>>(istream &os, A &a)
{
cin >> a.m;
return os;
}
ostream &operator<<(ostream &os, A a){
cout << a.m;
return os;
}
int main()
{
A a1(10), a2(20);
a2 = a1++;
cin >> a1;
cout << a2;
A a3(0);
a3 = a1 + a2;
cout << a2;
return 0;
}
Demo5 身高排序:宏定义,简单容器,迭代器,函数指针,计时器
题目描述:
随机生成一组(个数由宏定义决定)学生身高,使用vector和迭代器完成对学生身高的排序和遍历。使用库函数输出平均值,最大值及运行时间。
题目解答:
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <algorithm>
#include <numeric>
#define NUM 10
using namespace std;
class test
{
private:
vector<int> a;
vector<int>::iterator p;
public:
test()
{
srand(time(0));
for (int i = 0; i < NUM; ++i)
{
a.push_back(rand() % 55 + 150);
}
}
// 函数名即是函数地址。使用静态函数,从而下面的函数指针无须使用对象.方式即可访问
static bool cmp(int num1, int num2)
{
return num1 > num2;
}
void mysort()
{
sort(a.begin(), a.end(), cmp);
}
void browse()
{
for (p = a.begin(); p != a.end(); ++p)
{
cout << *p << endl;
}
}
void output()
{
cout << "mean" << accumulate(a.begin(), a.end(), 0) / (double)NUM << endl;
cout << "max:" << *max_element(a.begin(), a.end()) << endl;
}
};
int main()
{
clock_t t0 = clock();
test t;
t.mysort();
t.browse();
t.output();
cout << "running costs time:" << clock() - t0 << "ms" << endl;
}
Demo5.1 map使用:容器练习2
#include <iostream>
#include <cstdlib>
#include <map>
using namespace std;
struct S
{
int number;
int score;
};
class test
{
private:
map<int, S> a;
map<int, S>::iterator p;
int n, i;
public:
test(int nn)
{
n = nn;
srand(time(0));
S t;
for (i = 0; i < n; ++i)
{
t.number = rand();
t.score = rand();
// map底层是根据key值的hash-tree来存储的,所以key为int型时,无须重载小于号。key为结构体时,需要重载<但无须重载==,包括后面的find,利用的是(!a<b & !b<a)
a.insert(pair<int, S>(i, t));
}
}
void browse()
{
for (p = a.begin(); p != a.end(); ++p)
{
// map中迭代器的用法
cout << p->first << "-" << p->second.number << "-"
<< p->second.score << endl;
}
}
void find()
{
int temp;
cout << "input key:";
cin >> temp;
p = a.begin();
// map中find是根据key值进行查找的!
p = a.find(temp);
if (p != a.end())
{
cout << p->first << "-" << p->second.number << "-"
<< p->second.score << endl;
}
else
{
cout << "no record!" << endl;
}
}
};
int main()
{
test t(100);
t.browse();
t.find();
return 0;
}
Demo5.2 寻找元素:容器练习3
#include <iostream>
#include <cstdlib>
#include <list>
#include <ctime>
#include <functional>
#include <algorithm>
using namespace std;
struct S
{
int number;
int score;
bool operator==(S s)
{
return score == s.score;
}
};
class test
{
private:
int num;
S t;
list<S> a;
list<S>::iterator p;
public:
test(int num)
{
this->num = num;
for (int i = 0; i < num; ++i)
{
t.number = rand() % 100;
t.score = rand() % 100;
a.push_back(t);
}
}
void find1(int nn)
{
t.score = nn;
p = a.begin();
while (1)
{
p = find(p, a.end(), t);
if (p != a.end())
{
cout << p->number << "-" << p->score << endl;
++p;
}
else
break;
}
}
static bool find_fun(S s)
{
return s.score == s.number;
}
void find2(int nn)
{
t.score = nn;
p = a.begin();
while (p != a.end())
{
/*
template<class InputIterator, class Predicate>
InputIterator find_if ( InputIterator first, InputIterator last, Predicate pred )
{
for ( ; first!=last ; first++ )
if ( pred(*first) ) break;
return first;
}
*/
p = find_if(p, a.end(), find_fun);
}
}
static void display(S s)
{
cout << s.number << "-" << s.score << endl;
}
void foreach ()
{
for_each(a.begin(), a.end(), display);
}
};
int main()
{
srand(time(0));
test t(50);
t.find1(23);
cout << "==================================" << endl;
t.find2(23);
cout << "==================================" << endl;
t.foreach ();
cout << "==================================" << endl;
return 0;
}
题目收获:
- C++容器vector库函数
- C++容器list库函数
- C++容器map库函数
- C++迭代器详解
- sort()函数用法
- erase()函数用法,小心使用!!!
- accumulate()函数用法
- C++三种容器:list、vector和deque的区别
Demo6 秒表计时器:键盘交互,界面控制,伪多线程
basic.cpp:
//光标移动到指定坐标处
void gotoxy(int x, int y)
{
COORD c; //结构体,坐标值
c.X = x;
c.Y = y;
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); //句柄,对象的索引
SetConsoleCursorPosition(h, c);
}
//隐藏光标
void hide_cursor()
{
HANDLE h_GAME = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info;
GetConsoleCursorInfo(h_GAME, &cursor_info);
cursor_info.bVisible = false; //不显示光标
SetConsoleCursorInfo(h_GAME, &cursor_info);
}
//显示光标
void show_cursor()
{
HANDLE h_GAME = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info;
GetConsoleCursorInfo(h_GAME, &cursor_info);
cursor_info.bVisible = true; //显示光标
SetConsoleCursorInfo(h_GAME, &cursor_info);
}
//设置文本颜色
void color(int a)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), a);
}
main.cpp:
//秒表计时器
#include <windows.h>
#include <iostream>
#include <ctime>
#include <list>
// 格式化输出头文件
#include <iomanip>
#include "basic.cpp"
using namespace std;
class test
{
private:
int n, x, y;
clock_t t,t2;
public:
test()
{
n = 0;
t = clock();
t2 =clock();
x = 10;
y = 10;
hide_cursor();
}
void draw()
{
gotoxy(x, y);
// 格式化输出
cout << setw(3) << setfill('0') << n;
}
void erase()
{
gotoxy(x, y);
cout << " ";
}
void move1()
{
// 注意此处的数必须是1000,因为1000ms=1s对应题目中的秒表计时器
if (clock() - t > 1000)
{
draw();
++n;
t = clock();
}
}
void move2()
{
if(clock() - t2 > 50)
{
erase();
if (GetAsyncKeyState(VK_ESCAPE))
exit(0);
if (GetAsyncKeyState(VK_UP))
--y;
if(GetAsyncKeyState(VK_DOWN))
--x;
if (GetAsyncKeyState(VK_RIGHT))
++x;
draw();
t2 = clock();
}
}
void move()
{
while (true)
{
move1();
move2();
}
}
~test()
{
show_cursor();
}
};
int main()
{
test t;
t.move();
return 0;
}
Demo7 寻找子串:字符串
题目描述:
给定一个长字符串和一个短字符串,输出所有包含的子串的头、尾位置和找到的子串。
题目解答:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = "abcdbce";
string s1 = "bc";
string::size_type n1, n2 = 0;
while (s.find(s1, n2) != string::npos)
{
// 从0位置开始查找
n1 = s.find(s1, n2);
n2 = n1 + s1.size() - 1;
cout << "n1=" << n1 << endl;
cout << "n2=" << n2 << endl;
cout << "substr=" << s.substr(n1, n2 - n1 + 1) << endl;
}
return 0;
}
题目收获:
- 关于
string::size_type
find(string str, int pos)
:第一点,从pos处(含pos)开始寻找,所以如果寻找相同的单字符,记得加一以从下一位开始寻找。第二点,字符串的下标也是从0开始的。第三点,找不到会返回string::npos。substr(int pos, int length)
:从pos处(含pos)开始输出,第二个参数为个数,包括pos算作第一位,所以在使用脚标索引时,记得加一以计算出字符串真正长度。replace(int pos, int length, string str)
- C++中max_size()、size()、capacity()和reserve()函数
- reserve()和resize()的区别
- C++中string类下的begin,end,rbegin,rend的用法
- C++ string、const char*、 char* 、char[ ]相互转换
Demo8 英汉词典查询:文本文件读取,字符串
题目描述:
给定一个文件“英汉词典.txt”。编写方法实现一次性读入。再编写方法实现精准查询。其中,文件的读的打开方式为以传参+构造函数方法打开。
题目解答:
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
class test
{
private:
ifstream f1;
string s = "#";
public:
void read_from_file(string filename)
{
// C++11之前,只能接受c_str()类型
ifstream f(filename.c_str(), ios::in);
if (!f)
{
cout << "open file error!" << endl;
exit(0);
}
else
{
string ss = "";
while (!f.eof())
{
getline(f, ss);
s = s + ss + "#";
}
}
}
void dict()
{
string word;
cout << "input a word:";
cin >> word;
// int pos = 0;
int n1, n2, n3 = 0;
while (s.find(word, n3) != string::npos)
{
n1 = s.find(word, n3);
// 为实现全词匹配,需要找到该词前面最近的“#”
n1 = s.rfind("#", n1);
n2 = s.find(" ", n1 + 1);
string dicword = s.substr(n1 + 1, n2 - n1 - 1);
// 不断让s3推移,以防止模糊匹配未进if的情况,程序有出口的前提是每个分支都有出口!
n3 = s.find("#", n2);
// 如果想实现模糊查询,只需把该判断条件去掉即可,即只要部分包含该词,就取前后两个##之间字符串进行打印
if (dicword == word)
{
string exp = s.substr(n1 + 1, n3 - n1 - 1);
cout << exp << endl;
}
}
}
};
int main()
{
string filename;
cout << "input filename:";
cin >> filename;
test t;
t.read_from_file(filename);
t.dict();
}
Demo8.1 英汉词典查询:文本文件,容器
题目描述:
给定一个文件“英汉词典.txt”。编写方法实现按行读入并存储进容器。再编写方法实现精准查询。将最终查询结果写入“results.txt”。
题目解答:
#include <iostream>
#include <string>
#include <fstream>
#include <map>
#include <algorithm>
using namespace std;
class test
{
private:
ifstream f1;
ofstream f2;
string s, s1, word;
string::size_type n;
map<string, string> a;
map<string, string>::iterator p;
public:
test()
{
f1.open("./英汉词典.txt", ios::in);
f2.open("results.txt", ios::app);
if (!f1)
{
cout << "open file error!" << endl;
exit(0);
}
else
{
while (!f1.eof())
{
getline(f1, s);
n = s.find(" ", 0);
s1 = s.substr(0, n);
a.insert(pair<string, string>(s1, s));
}
}
}
void dict()
{
cout << "input a word:";
cin >> word;
p = a.find(word);
if (p != a.end())
{
cout << p->second << endl;
f2 << p->second << endl;
}
else
{
cout << "no word!" << endl;
f2 << "no word!" << endl;
}
}
~test()
{
f1.close();
f2.close();
}
};
int main()
{
test t;
t.dict();
return 0;
}
Demo8.2 便签合成:二进制文件
题目描述:
打开一个二进制文件,读出其中内容,以二进制方式写入另一个二进制文件。
题目解答:
#include <iostream>
#include <fstream>
using namespace std;
class test
{
private:
fstream f1,f2;
string s;
string::size_type n;
public:
test()
{
f1.open("test.txt",ios::in|ios::out|ios::binary);
f2.open("test1.txt",ios::out|ios::binary);
if(!f1||!f2) cout<<"打开文件错误!"<<endl;
}
~test()
{
if(f1&&f2) {f1.close();f2.close();}
}
void turn()
{
f1.seekg(0,ios::end);
n=f1.tellg();
s.resize(n);
f1.seekg(0,ios::beg);
f1.read((char*)s.c_str(),n);
f2.seekp(0,ios::beg);
f2.write((char*)s.c_str(),n);
cout<<"处理结束!"<<endl;
}
};
int main()
{
test t;
t.turn();
return 0;
}
题目收获:
- 文件打开类型
- ios::app和ios::ate的区别
- 二进制文件常用操作:
seekg/p()
:其中偏移量为负,向前;偏移量为正,向后。ios::beg为0,ios::end为eof,也就是最后一个字符的后一位(C++也遵从左闭右开的法则)。关于偏移量的问题点这里。read(n, sizeof(n))
:先向后移动sizeof(n)
字节,如果读到了eof,不论sizeof(n)
大小,则会返回上一次成功读取的内容。
- 二进制文件和文本文件的差别:无论是什么文件,本质上都是存储的二进制。
- 文本文件是以编码后的格式,如一个文件以ASCII码存储,则如int类型15将会被以1的ASCII码+5的ASCII码存储,共2个字节。
- 二进制文件,则会按照数据类型存储4个字节,内容为0…001111,但人打开文件时会发现其不可读,因为文件展示的都是其按照编码集编码过后的字符。
Demo9 :模板,虚函数,异常
题目描述:
题目解答:
题目收获:
Demo10 直接上代码啦:零散知识点
- 键盘事件:
c=getch();
system("pause");
if(kbhit()) break;
- 分配空间:
int a = new int;
delete a;
int b = new int[5];
delete []b; // 会依次释放每一个元素
- 指针与数组:
int* a,b[5],i;
a=new int[5];
cout<<"a="<<a<<endl;
cout<<"&a="<<&a<<endl;
cout<<"&a[0]="<<&a[0]<<endl;
cout<<"&a[1]="<<&a[1]<<endl;
cout<<"=============="<<endl;
cout<<"b="<<b<<endl;
cout<<"&b="<<&b<<endl;
cout<<"&b[0]="<<&b[0]<<endl;
cout<<"&b[1]="<<&b[1]<<endl;
/*
a=0x1031688
&a = 0x60fe88
&a[0] = 0x1031688
&a[1] = 0x103168c
== == == == == == ==
b = 0x60fe74
&b = 0x60fe74
&b[0] = 0x60fe74
&b[1] = 0x60fe78
*/
- 引用:
int a=0;
int &p =a;
a= 99; // 同时改变
p =88; // 同时改变
int &p3 = p; // 引用可以被引用,因为此刻代表的是变量啦
cout << p3;
const int &p1 = a; // 一个变量允许多引用。
p1=77; // ERROR! 常引用,和int const一个效果,不许修改。
int &p1=b; // ERROR! 必须有对象。必须从一而终。
- 字符串:
string.erase(pos, n);