1. 编程题目概况
c++是一门庞大而复杂的语言, 像个巨人. 里面有太多让人不容易理解的抽象概念, 掌屋更是很难. c++ primer plus和c primer plus的作者都是普拉达教授, 个人觉得比较适合初学者, 设计的编程题也很有针对性.
第10章主要讲了对象和类, 编程题的最后一道题, 原文如下:
8.可以将简单列表描述成下面这样:
a. 可存储0或多个某种类型的列表
b. 可创建空列表
c. 可在列表中添加数据项
d. 可确定列表是否为空
e. 可确定列表是否为满
f. 可访问列表中的每一个数据项, 并对它执行某种操作.
可以看到这个列表确实很简单, 例如, 它不允许插入或删除数据项.
可以选择使用数组或链表来实现该列表, 但公有接口不应依赖于所做的选择.也就是说, 公有接口不应有数组索引, 节点指针等. 应使用通用的概念来表达创建列表, 在列表中添加数据项等操作.
这里第 f 项要求用函数指针实现对所有列表成员应用某种操作, 函数指针 理解稍有难度.
我用了链表来实现这个列表, 除了数组, 链表 也是可以的. 另外, 还实现了 "删除" 和 "插入" 操作.
当然我的算法可能会有错误, 欢迎指正交流, 写得可能也不太好, 这里只是提供一种方式供初学者参考, 代码中都有详细的注释供参考.
1.1 一共有三个文件, 先是头文件 list_linkedlist.h:
#ifndef LIST_LINKEDLIST_H
#define LIST_LINKEDLIST_H
typedef int Item;
class List
{
private:
enum {MAX = 10}; /*列表上限*/
struct node /*列表成员节点结构*/
{
Item member; /*列表数据*/
struct node *next; /*指向下一个列表成员*/
};
struct node *head; /*存放列表第一个成员指针*/
public:
List(); /*默认构造函数*/
List(const unsigned int n);/*带参构造函数*/
~List(); /*析构函数,清除列表*/
bool isempty() const; /*查看列表是否为空*/
bool isfull() const; /*查看列表是否已满*/
bool add(const Item &it); /*添加列表成员*/
bool del(const Item &it); /*删除列表成员*/
bool insert(const Item &it, const int index = -1); /*插入节点*/
void visit(void (*pf)(Item &it)); /*遍历所有成员并传给函数指针指向的函数*/
};
#endif
1.2. 然后是类实现文件 list_linkedlist.cpp
#include <iostream>
#include "list_linkedlist.h"
using std::cout;
using std::cin;
using std::endl;
/*默认构造函数*/
List::List()
{
/*默认构造函数不创建节点,成员数为0*/
head = nullptr;
}
/*带参构造函数*/
List::List(const unsigned int n)
{
unsigned int i;
node *temp, *lastnd;
head = temp = lastnd = nullptr;/*初始化指针*/
Item tp;
/*循环输入列表成员*/
for (i = 0; i < n && i < MAX; i++)
{
cout << "Please enter #" << (i + 1)
<< "'s members of the list: ";
cin >> tp;
if (!cin) /*若输入错误字符*/
{
cin.clear(); /*输入错误要重置cin输入状态标记位*/
while (cin.get() != '\n') /*清除多余输入*/
continue;
cout << "Bad input, program terminated.\n";
break; /*中断输入*/
}
while (cin.get() != '\n') /*输入正确后也要清除多余输入*/
continue;
/*如果是第一次创建则创建head(头)节点*/
if (this->isempty()) /*若当前列表为空*/
{
temp = new node; /*创建第一个节点*/
temp->member = tp;
temp->next = nullptr;
head = temp;
}
else/*否则在末节点后创建新节点*/
{ /*找到末节点后将新节点添加到最后位置成为新*/
/*末节点,并把原末节点next指向新末节点*/
lastnd = head;
while (lastnd->next != nullptr) /*找到末节点*/
lastnd = lastnd->next;
temp = new node;
temp->member = tp; /*列表成员存入新节点*/
temp->next = nullptr; /*成为新末节点*/
lastnd->next = temp; /*原末节点next指向新末节点*/
}
}
}
/*析构函数*/
List::~List()
{
node *delnode = nullptr;
if (!(this->isempty()))
{
/*head控制后面一个的列表成员位置*/
/*delnode标识当前要被删除的列表成员位置*/
delnode = head;
while (head->next != nullptr)
{
head = head->next; /*指向下一个节点*/
delete delnode; /*释放当前节点*/
delnode = head; /*更新当前节点*/
}
delete delnode; /*释放最后一个节点*/
delnode = nullptr;
head = nullptr;
}
}
/*判断列表是否为空函数*/
bool List::isempty() const
{
return head == nullptr;
}
/*判断列表是否已满函数*/
bool List::isfull() const
{
int count = 0;
/*如果是空列表(未满)*/
if (this->isempty())
return false;
else
{ /*否则统计列表成员数(节点数)*/
/*与最大值MAX比较*/
node *temp = head;
count++;
while (temp->next != nullptr)
{
count++;
temp = temp->next;
}
return count == MAX;
}
}
/*添加列表成员*/
bool List::add(const Item &it)
{
/*如果是列表已满则不执行添加操作*/
if (this->isfull())
{
cout << "\nThe list is full, no more items can be added.\n";
return false;
}
else if (this->isempty())
{ /*如果列表为空则创建新节点,将列表成员值*/
/*存入head节点,然后head节点next置空为末节点*/
node *temp = new node;
temp->member = it;
temp->next = nullptr;
head = temp;
}
else
{ /*如果列表不为空则先找到末节点再创建新节点*/
/*并将新节点加入链表同时设置为新末节点*/
node *lastnd = head;
while (lastnd->next != nullptr)
lastnd = lastnd->next;
node *temp = new node;
temp->member = it;
temp->next = nullptr;
lastnd->next = temp;
}
cout << "\nThe member of the list added successfully.\n";
return true;
}
/*删除列表成员*/
bool List::del(const Item &it)
{
bool delflag = false;
if (head == nullptr)
{ /*如果是空列表则不执行删除*/
cout << "The list is empty, deletion failed.\n";
return false;
}
else
{ /*否则列表不为空*/
node *delnode = nullptr;
node *prenode = nullptr;
/*如果要删除的是列表成员是第一个成员*/
if (head->member == it)
{ /*分两种情况,第一种如果只有一个列表成员*/
/*则删除head节点并置空*/
if (head->next == nullptr)
{
delete head;
head = nullptr;
cout << "\nThe only list member was deleted successfully.\n";
}
else if (head->next != nullptr)
{ /*第二种如果后面还有列表成员*/
/*删除head节点,head指向第二个节点*/
delnode = head;
head = head->next;
delete delnode;
delnode = nullptr;
cout << "\nThe first member of list was deleted successfully.\n";
}
}
else /*否则要删除的是非第一个列表成员*/
{
delnode = head;
prenode = head;
while (delnode->next != nullptr)
{ /*如果找到要删除的列表成员*/
if (delnode->member == it)
{
/*则将要删除节点的上一个节点指向要删除*/
/*节点的下一个节点(跳过要被删除的节点)*/
prenode->next = delnode->next;
delete delnode; /*删除找到的节点*/
delnode = nullptr;
cout << "\nThe member of the list was deleted successfully.\n";
delflag = true;
break;
}
/*prenode保存要删除节点的上一个节点位置*/
prenode = delnode;
delnode = delnode->next;
}
/*循环退出时,有两种情况:第一种,没成功执行删除操作(delfalg = false),则delnode
已指向最后一个列表成员,那么与最后一个列表成员比对,若比对成功则要删除最后一
个列表成员则先删除末节点,再设置倒数第二个列表成员为新末节点;否则提示没有
找到要删除的列表成员。第二种,已经执行过删除操作(delflag = true),则不再删除
最后一个列表成员操作*/
if (delflag == false && delnode->member == it)
{
delete delnode;
delnode = nullptr;
prenode->next = nullptr;
cout << "\nThe last member of the list was deleted successfully.\n";
}
else if (delflag == 0)
{
cout << "\nCouldn't find '" << it
<< "' in the list, deletion failed.\n";
return false;
}
}
}
return true;
}
/*插入节点函数*/
/*这里index设定了默认值-1,表示如果不指定index则在*/
/*末尾插入*/
bool List::insert(const Item &it, const int index)
{
bool istflag = false;
int location = 0;
node *temp = nullptr; /*存放要插入的新列表成员*/
node *prnode = nullptr; /*保存要插入节点的上一个节*/
node *istnode = nullptr; /*点位置要插入列表成员的位置*/
/*如果列表已满则不执行插入*/
if (this->isfull())
{
cout << "The list is full, no more members can be inserted.\n";
return false;
}
else if (this->isempty())
{
/*如果为空列表则插入第一个列表成员*/
add(it);
}
else
{
/*否则列表有空位且有列表数据,如果要插入
的位置在head节点(索引值为0)则创建新成员
并将要插入的成员节点设置为新head节点*/
if (index == 0)
{
temp = new node;
temp->member = it;
temp->next = head; /*新节点next指向head节点*/
head = temp; /*(新节点加入链表)新节点成为新的head节点*/
cout << "\nThe members of the list was inserted successfully.\n";
}
else if (index < 0 || index >= MAX)
{
/*索引值为负数或大于等于MAX则*/
/*直接在最后插入列表成员*/
cout << "\nPosition index error "
<< "the list member will be inserted at the end.\n";
add(it);
}
else /*这个else代码块中要插入的节点位置索引值都在2~MAX-1*/
{ /*如不在头节点插入则查找列表成员位置,找到后创建新
节点并将新节点的next指向要插入位置的节点然后将要
插入节点的位置的前一个节点的next指向新创建的节点*/
prnode = head;
istnode = head;
while (istnode->next != nullptr)
{
prnode = istnode; /*保存当前节点位置到prnode*/
istnode = istnode->next; /*移动当前节点到下一节点*/
location++; /*移位置索引增加*/
if (index == location) /*如果是用户指定位置*/
{
temp = new node; /*创建新节点*/
temp->member = it; /*存放用新列表成员*/
/*新节点next指向要插入位置的节点*/
temp->next = prnode->next;
/*要插入节点位置的上一节点的next指向新节点(实现插入操作)*/
prnode->next = temp;
cout << "\nThe member of the list was inserted successfully.\n";
istflag = true;
break;
}
}
/*如果没在正常index插入,则在末尾插入*/
if (istflag == false)
{
cout << "\nPosition index error "
<< "the list member will be inserted at the end.\n";
add(it);
}
}
}
return true;
}
/*遍历函数*/
/*函数指针是一个C/C++中很重要的概念,它和其它数据类型*/
/*指针一样,只保存一个地址,只不过这个保存的这个地址是*/
/*函数的地址,而函数的地址就是函数名,函数名就是个地址*/
/*常量,和数组名一样就是个地址常量,将函数名传给函数指*/
/*针,那么在这个使用函数指针的地方可以用这个指针来调用*/
/*这个函数,只要符合这个函数指针的类型,就可以把相同类*/
/*型的函数传给这个函数指针*/
/*这里是给pf函数指针指向的函数传递所有的列表成员*/
void List::visit(void (*pf)(Item &))
{
if (this->isempty())
cout << "The list is empty, can't visit.\n";
else
{
node *temp = head;
while (temp->next != nullptr)
{
/*pf调用函数,给被调函数传递所有列表成员*/
(*pf)(temp->member);
temp = temp->next;
}
(*pf)(temp->member);
}
}
1.3. 最后是主程序文件 uselist_linkedlist.cpp
#include <iostream>
#include "list_linkedlist.h"
/*打印成员*/
void print(Item &it);
/*成员乘法运算*/
void multiply(Item &it);
/*提示信息*/
void promptmsg(const List &ls);
using std::cout;
using std::endl;
int main()
{
/*测试自定义列表*/
int i;
/*测试数据*/
int num[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 101};
/*声明一个列表*/
List listb;
/*检查成员函数isempty是否正常*/
cout << "list.isempty() = " << listb.isempty() << endl;
/*根据列表状态打印提示信息*/
promptmsg(listb);
/*用visit函数遍历所有列表成员*/
/*并作为visit参数中的函数指针指*/
/*向的函数的参数*/
listb.visit(print); /*对所有成员执行打印*/
/*添加列表成员测试*/
for (i = 0; i < 8; i++)
listb.add(num[i]);
/*再次打印所有成员*/
listb.visit(print);
/*添加成员*/
listb.add(222);
/*指定位置插入成员*/
listb.insert(103, 3);
/*指定位置插入成员*/
listb.insert(333, 8);
/*打印所有成员*/
listb.visit(print);
/*检查提示*/
promptmsg(listb);
/*对所有成员执行乘法*/
listb.visit(multiply);
/*打印所有成员*/
listb.visit(print);
cout << "\n\nDone.\n";
return 0;
}
/*打印成员*/
void print(Item &it)
{
cout << it << " ";
}
/*成员乘法运算*/
void multiply(Item &it)
{
it *= 2;
}
/*提示信息*/
void promptmsg(const List &ls)
{
const char *msg[3] = {
"\nThe list is empty, members can be added.\n",
"\nThe list is full.\n",
"\nThe list has some members, but more can be added.\n"
};
if(ls.isempty())
cout << msg[0] << endl;
else if (ls.isfull())
cout << msg[1] << endl;
else
cout << msg[2] << endl;
}
运行结果:
2. 结语
c++它不光有c中的指针, 它还有类, 还有C++的标准模板库(Standard Template Library, STL)中的各种容器, 关于类还有多重继承, 有很多复杂且容易出错的地方. 要想精通并不容易.java的设计就是摒弃了c++中的一些复杂特性而诞生的. 删除指针, 取消多重继承(但可实现多个接口), 设计垃圾回收器以免忘记释放内存等等.
喜欢这篇文章就点赞收藏, 欢迎留言交流, 您的点赞和收藏就是我写文章的动力. 谢谢.