文章目录
- 书评
- 阅读方法
- 代码实战
书评
这本书确实不适合新手上路,尤其是没学过c语言的,所以知乎上大多是喷的。
但是这类人大多没有看过编者按,实际上作者已经明确写出,假定学过c语言,有比较好的基础。
我感觉,要想学懂这本书,至少一本c primer plus是少不了的,因为这本书里面大量讲了编译,链接期间发生的行为,以及堆栈,调用等等系统相关知识,涉及到很多需要反复理解的名词,并且翻译还偶尔有不通顺,如果没点基础,直接劝退呗。
就算你看过c primer plus,一遍也无法参透这本书的导引部分。
所以个人感觉,要学c++还是看c++ primer好,这本适合在c++ primer的基础上,进一步学习面向对象以及系统知识,还有更高深的编程思想。
阅读方法
我看过c primer plus,也会STL的基本用法,但是第一次看还是有很多地方看不懂,我记忆比较深的就是一些用到寄存器相关知识的,直接给我看破防了,但是还是硬着头皮啃下去了。
所以推荐二遍刷,第一遍用一周时间,全神贯注看,不会就查,有疑惑就敲代码实验,但是不必要看的特别懂,明白作者的意图就不错了。第一遍可以不敲代码,重要的是速通,打个基础,因为这书太高屋建瓴了,二刷才能更好地体会作者的思想。书里有源代码,给代码的有个好处,就是可以靠看来实现速刷,然后二刷来提高熟练度。
第一次刷完了,再代码二刷,全书的代码,最好把习题也连带上,都敲了,如果有别的实战项目也可以自己搞自己的,比如我就是要做这学期的面向对象上机作业,其中一个就是手写一个vector,我打算写个豪华版的,所以这个足够当做练习了。
第二遍要注意抓重点,有些东西就没必要了,或者选择性,比如make的分段编译之类的,如果以后不打算搞开发,学这个也没用,了解一下有这么个东西就行了。二刷的过程,是一次重新理解的过程,可以从更高的层次,以更接近作者思路的层次去理解书本,可以理解一些以前理解不了的东西,同时又是一次重新回忆的过程。
代码实战
复现
这个模块主要是复现书上的,加一点自己的理解,注释。
复现不会选择所有例子,只会选择综合性例子。
Stash&Stack 1.0 ——简单的结构
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STASH_H
#define STASH_H
const int increment = 100;
struct Stash
{
int size;//元素大小
int quantity;//元素个数上限
int next;//下一个的index
unsigned char* storage;//按Byte储存空间
void initialize(int size);
void cleanup(void);
int add(const void* elemtent);
void* fetch(int index);
int count();
void inflate(int increase);
};
void Stash::initialize(int sz)//通过size定型
{
size = sz;
quantity = 0;
storage = 0;
next = 0;
}
int Stash::add(const void* element)
{
if (next >= quantity)//兼顾从0到1以及从少到多
inflate(increment);
int startByte = next * size;
unsigned char* e = (unsigned char*)element;//转换类型以逐Byte赋值
for (int i = 0; i < size; i++)
{
storage[startByte + i] = e[i];
}
next++;
return (next - 1);
}
void* Stash::fetch(int index)
{
if (index < 0 || index >= next)
{
return NULL;
}
return &(storage[index * size]);
}
int Stash::count()
{
return next;
}
void Stash::inflate(int increase)
{
if (increase <= 0)//确保increase大于0
{
return;
}
int newQuantity = quantity + increase;
int newBytes = newQuantity * size;
int oldBytes = quantity * size;
unsigned char* new_s = new unsigned char[newBytes];//申请新空间
if (!new_s)//确保成功
return;
for (int i = 0; i < oldBytes; i++)//逐Byte复制
{
new_s[i] = storage[i];
}
delete[] storage;//删除原来的
storage = new_s;
quantity = newQuantity;
}
void Stash::cleanup()
{
if (storage != NULL)
{
cout << "clean up" << endl;
delete[] storage;
}
}
#endif //STASH_H//
int main(void)
{
//整数测试,装填int
Stash intStash;
intStash.initialize(sizeof(int));
for (int i = 0; i < 20; i++)
{
intStash.add(&i);
}
for (int j = 0; j < intStash.count(); j++)
{
cout << "intStash.fetch("
<< j << ") = "
<< *(int*)intStash.fetch(j) << endl;
}
//string测试,装填string对象
Stash stringStash;
const int bufsize = 80;//默认string最长长度
stringStash.initialize(sizeof(char) * bufsize);
ifstream in("input.txt");
string line;
while (getline(in, line))
{
//string::c_str,将string对象转化为c-char array
stringStash.add(line.c_str());//这里装填固定的长度,超出去的是垃圾,提前被\0截断
}
int k = 0;
char* cp;
while ((cp = (char*)stringStash.fetch(k++)) != NULL)
{
cout << "stringStash.fetch(" << k << ") = " << cp << endl;
}
intStash.cleanup(); //防止内存泄露
stringStash.cleanup();
return 0;
}
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STACK_H //防止重复声明结构,函数没有这个问题
#define STACK_H
struct Stack {
//嵌套结构,顺便定义个成员
//注意这个链表不同于常规链表,里面的data是指针,这是为了不同类型
struct Link {
void* data;
Link* next;
void initialize(void* dat, Link* nxt);
}*head;
void initialize();
void push(void* data);
void* peek();
void* pop();
void cleanup();
};
void Stack::Link::initialize(void* dat, Link* nxt)//节点初始化
{
data = dat;
next = nxt;
}
void Stack::initialize()//初始化头部为NULL
{
head = NULL;
}
void Stack::push(void* dat) //将新节点插到头部,注意是直接把元素插进去了,没有复制
{
Link* node = new Link;
node->initialize(dat, head);
head = node;
}
void* Stack::peek() //获取顶部元素
{
if (head == NULL) //判断空
return NULL;
return head->data;
}
void* Stack::pop()
{
if (head == NULL)//判断空
return NULL;
void* result = head->data; //暂时储存
Link* old_head = head;
head = head->next; //移位
delete old_head; //处理暂存
return result;
}
void Stack::cleanup()
{
cout << "clean up" << endl;
}
#endif //STACK_H//
int main(void)
{
ifstream in("input.txt");
Stack lineStack;
lineStack.initialize();
string line;
while (getline(in, line))
{
lineStack.push(new string(line));//拷贝构造一份push进去
//lineStack.push(&line);
/*
//这样会有问题,可以自己画个指针图来求解
//cout << &line << endl; //line的地址不变
//三个data都指向了line变量的内存位置,而line最后变为NULL
//后面的delete NULL就会报错
*/
}
string* lp;
while ((lp = (string*)lineStack.pop()) != NULL)
{
cout << *lp << endl;
delete lp;//lp得到的是data的指针,用完元素记得清理
}
lineStack.cleanup();
return 0;
}
访问控制——嵌套友元与迭代器初步
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
const int sz = 20;
//嵌套友元与迭代器初步
struct Holder {
private:
int a[sz];
public:
void initialize()
{
memset(a, 0, sz * sizeof(int));
}
struct Pointer; //不完全声明
friend Pointer;
//仅嵌套结构,不进行组合,实际上是一个迭代器,之所以嵌套是为了表达所属关系
struct Pointer {
private:
Holder* h;
int* p;
public:
//函数全部内联,因为太短了
void initialize(Holder* rv)
{
h = rv;//绑定Holder
p = h->a; //获取Holder容器的访问权限
}
//以下为指针移动与存取操作,通过Pointer来间接实现
void next()
{
if (p < &(h->a[sz - 1]))//通过地址判断越界
{
p++;
}
}
void previous()
{
if (p > &(h->a[0]))
{
p--;
}
}
void top()
{
p = &(h->a[0]);
}
void end()
{
p = &(h->a[sz - 1]);
}
int read()
{
return *p;
}
void set(int i)
{
*p = i;
}
};
};
int main(void)
{
Holder h;
Holder::Pointer hp, hp2;
int i;
h.initialize();
hp.initialize(&h);
hp2.initialize(&h);
for (i = 0; i < sz; i++)
{
hp.set(i);
hp.next();
}
hp.top();//双指针头尾反向遍历
hp2.end();
for (i = 0; i < sz; i++)
{
cout << "hp = " << hp.read()
<< ", hp2 = " << hp2.read() << endl;
hp.next();
hp2.previous();
}
return 0;
}
Stash&Stack 2.0——添加访问控制
//就是简单的加一下access specifier即可,实现不需要改变,成员函数随便访问private
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STASH_H
#define STASH_H
const int increment = 100;
struct Stash
{
public:
void initialize(int size); //定型
void cleanup(void);//清空
int add(const void* elemtent); //添加
void* fetch(int index); //读取
int count();//查询数量
private:
int size;//元素大小
int quantity;//元素个数上限
int next;//下一个的index
unsigned char* storage;//按Byte储存空间
void inflate(int increase);//扩容方法
};
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STACK_H //防止重复声明结构,函数没有这个问题
#define STACK_H
struct Stack {
//嵌套结构,顺便定义个成员
//注意这个链表不同于常规链表,里面的data是指针,这是为了不同类型
public:
void initialize();
void push(void* data);
void* peek();
void* pop();
void cleanup();
private:
struct Link {
void* data;
Link* next;
void initialize(void* dat, Link* nxt);
}*head;
};
句柄类——封装的封装
/*
句柄类用法,用一个只有public方法的类将一个具有private方法的类包装起来
使用不完全定义的嵌套类,并将其指针包含进去,只暴露指针
好处1:隐藏
好处2:修改结构成员和private都不会影响头文件,不使用句柄类就得同时重新编译头文件和目标程序,现在只需要编译头文件即可,目标程序因为头文件不变不需要重新编译.
*/
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef HANDLE_H
#define HANDLE_H
class Handle {
public:
void initialize();
void cleanup();
int read();
void change(int);
private:
struct Cheshire; //不完全定义
Cheshire* smile; //用户只能看到这个,至于指向的地方是什么样的(源代码)无法把握
};
//以下为自己放在别处的实现
struct Handle::Cheshire { //这里可以加各种private,这里仅仅简单做个类
int i;
};
void Handle::initialize()
{
smile = new Cheshire;
smile->i = 0;
}
void Handle::cleanup()
{
delete smile;
}
int Handle::read()
{
return smile->i;
}
void Handle::change(int x)
{
smile->i = x;
}
#endif //HANDLE_H//
int main(void)
{
Handle u;
u.initialize();
cout << u.read() << endl;
u.change(1);
cout << u.read() << endl;
u.cleanup();
return 0;
}
Stash&Stack 3.0 ——添加构造函数和析构函数
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STASH_H
#define STASH_H
const int increment = 20;
class Stash {
public:
Stash(int size);
~Stash();
int add(const void* element);
void* fetch(int index);
int count();
private:
int size;
int quantity;
int next;
unsigned char* storage;
void inflate(int increase);
};
Stash::Stash(int sz) :size(sz), quantity(0), storage(NULL), next(0)
{
cout << "stash constructor" << endl;
}
Stash::~Stash() {
cout << "stash destructor" << endl;
}
int Stash::add(const void* element)
{
if (next >= quantity)//兼顾从0到1以及从少到多
inflate(increment);
int startByte = next * size;
unsigned char* e = (unsigned char*)element;//转换类型以逐Byte赋值
for (int i = 0; i < size; i++)
{
storage[startByte + i] = e[i];
}
next++;
return (next - 1);
}
void* Stash::fetch(int index)
{
if (index < 0 || index >= next)
{
return NULL;
}
return &(storage[index * size]);
}
int Stash::count()
{
return next;
}
void Stash::inflate(int increase)
{
if (increase <= 0)//确保increase大于0
{
return;
}
int newQuantity = quantity + increase;
int newBytes = newQuantity * size;
int oldBytes = quantity * size;
unsigned char* new_s = new unsigned char[newBytes];//申请新空间
if (!new_s)//确保成功
return;
for (int i = 0; i < oldBytes; i++)//逐Byte复制
{
new_s[i] = storage[i];
}
delete[] storage;//删除原来的
storage = new_s;
quantity = newQuantity;
}
#endif //STASH_H//
int main(void)
{
//整数测试,装填int
Stash intStash(sizeof(int));
for (int i = 0; i < 20; i++)
{
intStash.add(&i);
}
for (int j = 0; j < intStash.count(); j++)
{
cout << "intStash.fetch("
<< j << ") = "
<< *(int*)intStash.fetch(j) << endl;
}
//string测试,装填string对象
const int bufsize = 80;//默认string最长长度
Stash stringStash(bufsize*sizeof(char));
ifstream in("input.txt");
string line;
while (getline(in, line))
{
//string::c_str,将string对象转化为c-char array
stringStash.add(line.c_str());//这里装填固定的长度,超出去的是垃圾,提前被\0截断
}
int k = 0;
char* cp;
while ((cp = (char*)stringStash.fetch(k++)) != NULL)
{
cout << "stringStash.fetch(" << k << ") = " << cp << endl;
}
//析构执行点
return 0;
}
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STACK_H
#define STACK_H
class Stack {
public:
Stack();
~Stack();
void push(void* dat);
void* peek();
void* pop();
private:
struct Link {
void* data;
Link* next;
Link(void* dat, Link* nxt); //所有类都应该有四大函数
~Link();
}*head;
};
Stack::Link::Link(void* dat, Link* nxt) :data(dat), next(nxt)
{
cout << "Stack::Link "<<data<<" constructor" << endl;
}
Stack::Link::~Link()
{
cout << "Stack::Link "<<data<<" destructor" << endl;
}
Stack::Stack() :head(NULL)
{
cout << "Stack constructor" << endl;
}
Stack::~Stack()
{
cout << "Stack destructor " << (head == NULL?"success":"fail")<<endl;
}
void Stack::push(void* dat) //将新节点插到头部,注意是直接把元素插进去了,没有复制
{
Link* node = new Link(dat, head);
head = node;
}
void* Stack::peek() //获取顶部元素
{
if (head == NULL) //判断空
return NULL;
return head->data;
}
void* Stack::pop()
{
if (head == NULL)//判断空
return NULL;
void* result = head->data; //暂时储存
Link* old_head = head;
head = head->next; //移位
delete old_head; //处理暂存
return result;
}
#endif //STACK_H//
int main(void)
{
ifstream in("input.txt");
Stack lineStack;//构造函数没有参数就不用带括号了
string line;
while (getline(in, line))
{
lineStack.push(new string(line));//拷贝构造一份push进去
//lineStack.push(&line);
/*
//这样会有问题,可以自己画个指针图来求解
//cout << &line << endl; //line的地址不变
//三个data都指向了line变量的内存位置,而line最后变为NULL
//后面的delete NULL就会报错
*/
}
string* lp;
while ((lp = (string*)lineStack.pop()) != NULL)
{
cout << *lp << endl;
delete lp;//lp得到的是data的指针,用完元素记得清理,之所以不将清理内存放在
//析构函数中,是因为传出来的不是拷贝,而是指针,这个程序的根本性问题就在于
//全都是用的指针,即使是在一些应该有拷贝的地方,这就极易出问题
}
return 0;
}
SuperVar——Union的重载
其实没啥用,因为已经失去了Union最本真的用途了,只是演示Union也可以像类一样有四大函数,也可以重载构造函数.
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
class SuperVar {
public:
SuperVar(char ch):c(ch),vartype(character)
{
cout << "SuperVar constructer char" << endl;
}
SuperVar(int ii) :i(ii),vartype(integer)
{
cout << "SuperVar constructer int" << endl;
}
SuperVar(float ff) :f(ff),vartype(floating_point)
{
cout << "SuperVar constructer float" << endl;
}
void print()
{
switch (vartype)
{
case character:
cout << "char: " << c << endl;
break;
case integer:
cout << "int: " << i << endl;
break;
case floating_point:
cout << "float: " << f << endl;
break;
default:
cout << "error" << endl;
}
}
private:
enum { //匿名枚举
character,
integer,
floating_point
}vartype;
union {//匿名联合,可以直接操控变量
char c;
int i;
float f;
};
};
int main(void)
{
SuperVar A('c'), B(1), C(1.2f); //这里必须通过f后缀声明float
SuperVar D(float(1.2)); //或者显式转化
A.print();
B.print();
C.print();
D.print();
return 0;
}
MyString 1.0——逐字节控制内存工具的应用以及开销的思考
其实之前的stash和stack都可以用这个做,char的另一个优势在于可以用strcpy,strcat等各种函数,以及memset,memcpy等等,估计这些也是用底层内存写的。
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef MEM_H
#define MEM_H //一个辅助内存块管理工具类
typedef unsigned char byte; //基本内存单位
class Mem {
public:
//不使用重载而是默认参数,因为这两个没有开销区别
/*Mem() :mem(0), size(0)
{
cout << "Mem constructor" << endl;
}*/
Mem(int sz=0) :mem(0), size(0) //分配sz的空间
{
cout << "Mem constructor " << sz << " Bytes" << endl;
ensureMinSize(sz);
}
~Mem()
{
cout << "Mem destructor" << endl;
delete[] mem; //可以delete NULL
}
int getSize() //总空间
{
return size;
}
byte* pointer() //返回空间指针,有需要可以转化为各种格式的指针对应类型
{
return mem;
}
byte* pointer(int minSize) //返回前确保空间
{
ensureMinSize(minSize);
return mem;
}
private:
byte* mem;
int size; //剩余空间
void ensureMinSize(int minSize);
};
void Mem::ensureMinSize(int minSize)
{
if (size < minSize) //空间不足
{ //新空间申请与清理,只清理后面的空间是因为前面的会被覆盖,没必要
byte* newmem = new byte[minSize];
memset(newmem + size, 0, minSize - size);
//转移处理老空间
memcpy(newmem, mem, size);
delete[] mem;
//更新
mem = newmem;
size = minSize;
}
}
#endif //MEM_H//
#ifndef MYSTRING_H
#define MYSTRING_H
class MyString {
public:
MyString() :buf(NULL)
{
cout << "MyString constructor" << endl;
}
MyString(const char* str)
{
cout << "MyString constructor" << endl;
buf = new Mem(strlen(str) + 1); //多一个\0的空间
strcpy((char*)buf->pointer(), str); //将空间视作char*
}
~MyString()
{
cout << "MyString destructor" << endl;
delete buf;//调用Mem析构函数,析构调用是递归的
}
void concat(const char* str)
{
//定义空串行为
if (!buf)
{
buf = new Mem;
}
//确保空间具有 原来串+新串长度+1的空间
strcat((char*)buf->pointer(buf->getSize() + strlen(str) + 1), str);
}
void print(ostream& os=cout) //将串输出到目标位置,默认为cout
{
if (!buf)
{
return;
}
os << buf->pointer() << endl;
}
private:
Mem* buf; //指向一个Mem类,Mem类里面有指针指向空间
};
#endif //MYSTRING_H//
int main(void)
{
MyString s("My test string");
s.print();
s.concat(" some additional stuff");
s.print();
MyString s2;
s2.concat("Using defalult constructor");
s2.print();
return 0;
}
StringStack&Quoter——const 综合应用
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STRINGSTACK_H
#define STRINGSTACK_H
class StringStack {
public:
StringStack() :index(0)
{
memset(stack, 0, size * sizeof(string*));
}
void push(const string* s)
//确保不会修改,所以用const指针,但是仅仅是我们这个指针const
//其他地方指向同样string的可能修改这个string
{
if (index < size)
{
stack[index++] = s;
}
}
const string* pop() //不允许返回值修改,所以返回const对象
{
if (index > 0)
{
const string* rv = stack[--index];//现在rv和stack[index]同时指向string
stack[index] = NULL;//释放一个指针
return rv;
}
return NULL;
}
private:
static const int size = 100;//static将类中const放到编译期间,全局const也在编译期间
const string* stack[size]; //size个const指针
int index;//指向下一位
};
#endif //STRINGSTACK_H
int main(void)
{
string iceCream[] =
{
"1,","2,","3,","4,"
};
//自动计算长度
const int iCsz = sizeof(iceCream) / sizeof(*iceCream);
StringStack ss;
for (int i = 0; i < iCsz; i++)
{
ss.push(&iceCream[i]);//非const赋const
}
const string* cp;
while ((cp = ss.pop()) != NULL)
{
cout << *cp << endl;
}
//以下证明const指针仅仅保护通过当前指针的路径不被修改,别的路还可以修改
iceCream[2] = "changed by no-const pointer";
for (int i = 0; i < iCsz; i++)
{
ss.push(&iceCream[i]);//非const赋const,从iceCream可以修改,但是不可以从ss修改
}
while ((cp = ss.pop()) != NULL)
{
cout << *cp << endl;
}
return 0;
}
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
#include<ctime> //时间相关
using namespace std;
class Quoter {
public:
Quoter();
int lastQuote() const; //const成员函数,当对象被声明为const就不能调用非const方法
const char* quote();
private:
int lastquote;
};
Quoter::Quoter():lastquote(-1)
{
srand(time(0));
}
int Quoter::lastQuote() const //声明和定义都要加const,统一仍然适用
{
return lastquote;
}
const char* Quoter::quote()//返回一个const*,但是会改变成员(lastquote)
{
static const char* quotes[] = { //static const,编译期间量
"quote-1",
"quote-2",
"quote-3",
"quote-4",
"quote-5"
};
const int qsize = sizeof quotes / sizeof * quotes;
int qnum = rand() % qsize; //随机获取一个范围内下标
while (lastquote >= 0 && qnum == lastquote)//不取和上一次相同的或者-1
{
qnum = rand() % qsize;
}
return quotes[lastquote = qnum];
}
int main()
{
Quoter q;
const Quoter cq;
cq.lastQuote();
// cq.quote() 压根不会出现在备选列表里
for (int i = 0; i < 20; i++) //q就可以调用,因为q不是const对象
{
cout << q.quote() << endl;
}
return 0;
}
Comm——晦涩的volatile
这个初学不会用,volatile的意思是不要让编译器进行想当然优化,也就是我们做好了出现意外情况的思想准备。比如我已经做好了被多线程修改的准备,就不要让编译器拒绝多线程。
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
#include<ctime> //时间相关
using namespace std;
class Comm {
public:
Comm();
void isr() volatile;
char read(int index) const;
private:
enum{bufsize=100};//等效操作
//static const int bufsize = 100;
const volatile unsigned char byte;
volatile unsigned char flag;
unsigned char buf[bufsize];
int index;
};
Comm::Comm() :index(0), byte(0), flag(0)
{
cout << "Comm constructor" << endl;
}
void Comm::isr() volatile
{
flag = 0;
buf[index++] = byte;
if (index >= bufsize)
{
index = 0;
}
}
char Comm::read(int index) const
{
if (index < 0 || index >= bufsize)
{
return 0;
}
return buf[index];
}
int main(void)
{
volatile Comm Port;
Port.isr();
//Port.read(0); //不行,不是volatile
return 0;
}
Stash&Stack 4.0——内联函数减少小函数调用开销
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STASH_H
#define STASH_H
const int increment = 20;
class Stash {
public:
Stash(int sz) :size(sz), quantity(0), storage(NULL), next(0)
{
cout << "stash constructor" << endl;
}
~Stash() {
cout << "stash destructor" << endl;
}
int add(const void* element); //非内联
void* fetch(int index)
{
if (index < 0 || index >= next)
{
return NULL;
}
return &(storage[index * size]);
}
int count()
{
return next;
}
private:
int size;
int quantity;
int next;
unsigned char* storage;
void inflate(int increase);//非内联
};
int Stash::add(const void* element)
{
if (next >= quantity)//兼顾从0到1以及从少到多
inflate(increment);
int startByte = next * size;
unsigned char* e = (unsigned char*)element;//转换类型以逐Byte赋值
for (int i = 0; i < size; i++)
{
storage[startByte + i] = e[i];
}
next++;
return (next - 1);
}
void Stash::inflate(int increase)
{
if (increase <= 0)//确保increase大于0
{
return;
}
int newQuantity = quantity + increase;
int newBytes = newQuantity * size;
int oldBytes = quantity * size;
unsigned char* new_s = new unsigned char[newBytes];//申请新空间
if (!new_s)//确保成功
return;
for (int i = 0; i < oldBytes; i++)//逐Byte复制
{
new_s[i] = storage[i];
}
delete[] storage;//删除原来的
storage = new_s;
quantity = newQuantity;
}
#endif //STASH_H//
int main(void)
{
//整数测试,装填int
Stash intStash(sizeof(int));
for (int i = 0; i < 20; i++)
{
intStash.add(&i);
}
for (int j = 0; j < intStash.count(); j++)
{
cout << "intStash.fetch("
<< j << ") = "
<< *(int*)intStash.fetch(j) << endl;
}
//string测试,装填string对象
const int bufsize = 80;//默认string最长长度
Stash stringStash(bufsize * sizeof(char));
ifstream in("input.txt");
string line;
while (getline(in, line))
{
//string::c_str,将string对象转化为c-char array
stringStash.add(line.c_str());//这里装填固定的长度,超出去的是垃圾,提前被\0截断
}
int k = 0;
char* cp;
while ((cp = (char*)stringStash.fetch(k++)) != NULL)
{
cout << "stringStash.fetch(" << k << ") = " << cp << endl;
}
//析构执行点
return 0;
}
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STACK_H
#define STACK_H
class Stack {
public:
Stack() :head(NULL)
{
cout << "Stack constructor" << endl;
}
~Stack()
{
cout << "Stack destructor " << (head == NULL ? "success" : "fail") << endl;
}
void push(void* dat) //将新节点插到头部,注意是直接把元素插进去了,没有复制
{
Link* node = new Link(dat, head);
head = node;
}
void* peek() //获取顶部元素
{
if (head == NULL) //判断空
return NULL;
return head->data;
}
void* pop() //这个也不算大,就内联了
{
if (head == NULL)//判断空
return NULL;
void* result = head->data; //暂时储存
Link* old_head = head;
head = head->next; //移位
delete old_head; //处理暂存
return result;
}
private:
struct Link {
void* data;
Link* next;
Link(void* dat, Link* nxt) :data(dat), next(nxt)//所有类都应该有四大函数
{
cout << "Stack::Link " << data << " constructor" << endl;
}
~Link()
{
cout << "Stack::Link " << data << " destructor" << endl;
}
}*head;
};
#endif //STACK_H//
int main(void)
{
ifstream in("input.txt");
Stack lineStack;//构造函数没有参数就不用带括号了
string line;
while (getline(in, line))
{
lineStack.push(new string(line));//拷贝构造一份push进去
//lineStack.push(&line);
/*
//这样会有问题,可以自己画个指针图来求解
//cout << &line << endl; //line的地址不变
//三个data都指向了line变量的内存位置,而line最后变为NULL
//后面的delete NULL就会报错
*/
}
string* lp;
while ((lp = (string*)lineStack.pop()) != NULL)
{
cout << *lp << endl;
delete lp;//lp得到的是data的指针,用完元素记得清理,之所以不将清理内存放在
//析构函数中,是因为传出来的不是拷贝,而是指针,这个程序的根本性问题就在于
//全都是用的指针,即使是在一些应该有拷贝的地方,这就极易出问题
}
return 0;
}
补充:内联理解
普通函数有自己的内存,地址,需要进行调用,而调用这一步具有比较大的开销,如果能直接将函数调用替换为源代码就好了,这实际上就是类似于宏的用法。
对于宏,宏其实是保存在符号表里的,占用空间,内联函数不仅有宏一样的特性,储存在符号表,然后替换,可以有效减少调用开销,而且还具有编译器的类型检查功能,这是宏没有的。代价就是占用符号表空间,如果内联函数太大,空间占用带来的替换开销或许就会超过调用的开销。
所以有一个原则,小函数内联,大函数和带循环的(展开后代码膨胀)调用。形式上没什么区别,本质上区别在于宏与非宏。如果要在性能上下功夫,内联是可以考虑的一个点,实际上比较复杂。
需要注意的是,inline只是一个建议,类似于register,编译器会自己进行判断是否采纳。
class X {
public:
void common_func(); //普通调用函数
void inline_out_class();
void func_inline_in_class()//内部直接写默认内联
{
cout << "inline" << endl;
}
};
void X::common_func()
{
cout << "common function" << endl;
}
inline void X::inline_out_class()//外部内联,需要inline
{
cout << "inline" << endl;
}
补充:宏的骚操作
- 字符串定义
- 字符串转换——用于字符串拼接
- 标志符粘贴
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
//定义函数语句最后的;不用加,因为用的时候会加
#define STRING "nihao" //字符串定义
#define DEBUG(x) cout << #x " = " << x << endl //字符串拼接,#将标志符直接变成字符串
#define TRACE(s) cerr<<#s<<endl; s
#define LABEL(i) string label_##i=#i //标志粘贴,##将i直接替换到标志符,提供代码级的重用
int main(void)
{
cout << "字符串定义: "<<STRING << endl;
cout << "字符串拼接:";
string str="nihaoya";
DEBUG(str);
cout << "TRACE: ";
TRACE(str);
LABEL(1);
LABEL(2);
cout <<"label_1: " << label_1 << " label_2: " << label_2 << endl;
return 0;
}
命名空间
这个别想了,初学没用处的,这是做大项目才会用到的,最多学一下using指令。
我们重点需要说的就是using不要在头文件里用,切记。
因为#include命令只是把头文件进行了原样替换,相当于把源代码复制粘贴进来,如果你头文件里有个
using namespace std;
那你一旦包含这个头文件,当前文件就会被开了std命名空间,这是致命的问题。
换句话说,你平时自己写代码可以开命名空间,因为小,但是你要是写头文件,写库文件,一定要亲手换成std::,否则问题会很大,至少运行不了是大概率的事情。
引用与拷贝构造函数
引用
引用的基本概念比较简单,就是用法需要注意一点。
const是需要注意的,如果不需要改,或者营造出一种传值的感觉,那就用const引用,否则就是要改了,要改的话用引用有可能引起混淆,因为外面的人不知道你是按值传递还是传引用。
所以按值传递就无脑const引用即可
要改的话就得想想用引用还是指针
默认拷贝构造函数
常理来说,拷贝构造应该是直接复制一份,如下实验说明了这个假设:
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
//测试默认拷贝构造函数
class X {
public:
string* p;
};
int main(void)
{
X x;
string str = "nihao";
x.p = &str;
X y = x; //默认cc函数
cout << "xp: " << x.p << endl;
cout << "yp: " << y.p << endl;
return 0;
}
可以看到,两个指针的内容一样,这就是默认的CC函数,符合假定,也就是按位拷贝。
但是这样也会带来一个问题,就是不适合处理复杂的类,比如带指针的。你光是复制了指针,不复制人家指向的对象,就不行,所以得自己编写拷贝构造函数来实现复杂的操作。
拷贝构造函数
- 目的。定义按值传递时的行为
- 写法。同构造函数,只不过参数只有一个,class &,有时候会是const class &,其实个人感觉const更好,因为拷贝构造一般不会修改原有对象的。
- 为什么是传引用呢?一方面比较有效率,另一方面,拷贝构造函数就是为了定义类按值传递时的行为,不用引用的话,你还没定义呢,你就按值传递了。
- 初始化列表。不是构造函数就是拷贝构造函数,说白了,构造和拷贝构造是同级的,只不过场景不同。
- 特殊应用:私有拷贝构造。这个可以阻止按值传递。
成员指针
本质:定义的时候表示一种相对于基地址的偏移,针对一个类,而不针对某个对象
所以没有精确指向某个对象的成员的指针,只有指向一个类中成员的指针,然后配合对象或者对象指针来使用。
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
class Data {
public :
int a, b, c;
void print() const
{
cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
}
};
int main(void)
{
Data d;
Data* dp = &d;
//重点解析这个定义式
/*
不过是在 *指针名 前面加个作用域,表示针对这个类
前面的类型表明只针对这种类型的成员
右边规定了指针指向类中哪个成员,写法仅仅符合指针写法,不具有取地址实际意义
*/
int Data::* memPointerInt = &Data::a;
d.*memPointerInt = 1; //点号用法
memPointerInt = &Data::b;//更换指向成员
dp->*memPointerInt = 2;
memPointerInt = &Data::c;
d.*memPointerInt = 3;
//函数成员指针类似,也是加个作用域,右边仅针对一类
void (Data:: * funcPointer)() const = &Data::print;
(d.*funcPointer)(); //这里要注意前面整体相当于函数名,要括起来
return 0;
}
操作符重载
基本应用
核心应用场景:
将内建数据类型的操作符操作移植到自定义类里。比如int 和 Integer类
传入参数:
- 成员函数不需要传自己,并且可以使用this进行对自己的引用和操作。
- this返回当前对象指针,*this返回当前对象(外面一般用引用承接)
- 一般都是const &的,因为基本都是仅仅用值
关于返回值:
返回值取决于我要返回什么,我要对我的返回值做什么。
- 不修改返回值,且返回值为原对象。const class & 理由:返回原有对象,引用高效,const防止修改。
- 返回值作为临时量。const class 理由:一般用于返回新对象。不能传引用,因为临时量生存期很短。
- 修改返回值。class & 理由:返回原有对象继续修改。
注意const成员函数:
- 重载中大量使用const 返回值,所以其成员函数,能const尽量const,不然操作不了这个返回的临时量。
是否全局:
- 遵循直观原则,一元就成员,二元就全局。
- 特殊的,比如= () [] -> ->*,这种具有赋值和选择含义的,因为总是伴随对象,所以只能作为成员。
索引符:
- 必须是成员函数且只能接受一个索引参数
- 很常用。
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef INTEGER_H
#define INTEGER_H //整数,操作符重载包装类
class Integer {
public:
Integer(long ll = 0) :i(ll)
{
cout << "Integer c" << endl;
}
long getValue() const //const函数主要是针对临时量的,否则(++a).getValue就无效
{
return i;
}
//全局函数,需要传入全部参数,给个friend
friend const Integer& //传入a引用,返回a,所以全用const引用效率最高,能用const&最好
operator + (const Integer& a);
friend const Integer //二元写成全局更直观
operator+(const Integer& left, const Integer& right);
//成员函数,自己就不用传入了,不用friend
const Integer //返回的是一个临时量,所以没办法用引用,注意是没办法才按值传递的。
operator - ()
{
return Integer(-i);//这里隐含返回值优化,可以直接将临时量创建在外面的承接空间中
//从而省去内部量的一次构造和析构
}
const Integer&
operator ++()
{
i++;
return *this; //返回当前对象引用的方法
}
const Integer //同理,因为返回临时量,所以只能按值传递
operator ++(int)
{
Integer before(*this);//CC函数
i++;
return before;//返回临时量
}
private:
long i;
Integer* This()
{
return this;
}
};
const Integer&
operator + (const Integer& a)
{
return a;//传入引用,返回引用,不变
}
const Integer
operator + (const Integer& left, const Integer& right)
{
return Integer(left.i + right.i);
}
#endif //INTEGER_H//
int main(void)
{
Integer a(1);
cout <<"a "<< a.getValue() << endl;
cout <<"+a "<< + a.getValue() << endl;
cout << "-a " << -a.getValue() << endl;
cout << "++a " << (++a).getValue() << endl;
cout << "a++ " << (a++).getValue() << endl;
cout << "a++ " << a.getValue() << endl;
return 0;
}
operator->/operator->* 与迭代器嵌入
这个也只能是成员函数,这种形式,就意味着有类似于指针的行为,被叫做灵巧指针,常用于迭代器。
有两个规定:
- 必须返回一个对象,其也可以进行间接引用,这样保证了链式调用。
- 或者返回最终指向的类型,这就标志着到达终点。
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
//
class Obj {
public:
void f() const
{
cout << i++ << endl;
}
void g() const
{
cout << j++ << endl;
}
private:
static int i, j; //所有Obj公用这两个
};
int Obj::i = 47;
int Obj::j = 11;
//容器
class ObjContainer {
public:
void add(Obj* obj)//添加成员
{
a.push_back(obj);
}
class SmartPointer; //迭代器,用于访问,相当于将一些公共接口又封装了一次
friend SmartPointer;//打开内部类向外操作路径
class SmartPointer {//一般是嵌入,也可以选择全局+friend
public:
SmartPointer(ObjContainer& objc) :oc(objc), index(0)
{
cout << "SmartPointer c" << endl;
}
//指针偏移,保证偏移不越界,空指针则通过返回值判断
bool operator++()
{
if (++index >= oc.a.size()) //源代码有误,应该在这里先自增
//再检查越界,否则后面还可能产生越界
return false;
if (oc.a[index] == NULL) //空指针
return false;
return true; //不越界且有东西
}
bool operator++(int)
{
return operator++();//结果相同
}
bool operator--()
{
if (--index < 0)
return false;
if (oc.a[index] == NULL)
return false;
return true;
}
bool operator--(int)
{
return operator--();
}
//间接引用,返回一个成员
Obj* operator->() const
{
if (oc.a[index] == NULL) //唯一的可能意外,空指针
{
cout << "there is a Null Pointer in ObjContainer" << endl;;
}
return oc.a[index];//额,实际上可以合并到这一步,空指针也没问题
}
private:
ObjContainer& oc; //用于绑定容器
int index; //容器索引信息
};
SmartPointer beginSP()//做个小包装,隐藏掉this调用
{
return SmartPointer(*this);
}
private:
vector<Obj*> a; //容器仅容纳指针向量
};
int main(void)
{
const int sz = 10;
Obj o[sz];
ObjContainer oc;
for (int i = 0; i < sz; i++)
{
oc.add(&o[i]);
}
ObjContainer::SmartPointer sp=oc.beginSP();//生成迭代器
//下面这样也可以,只不过非常麻烦
//ObjContainer::SmartPointer sp = ObjContainer::SmartPointer::SmartPointer(oc);
do
{
sp->f(); //->先左后右,左结合调用operator->,产生Obj*,右就是像一个正常指针
sp->g();
}while (sp++);
return 0;
}
->*目前感觉没啥用,所以就暂时不写了。
不重载. 号,因为影响太大了,妨碍了正常使用。
operator= 与赋值拷贝
=号的行为有两种
- 初始化一个对象:将取右边的参数(只能有一个),去调用对应的C函数或者CC函数。
- 赋值一个已经初始化的对象:调用operator=,默认的是位拷贝,可以自定义。
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
class Fi {
public:
Fi() {}
};
class Fee {
public:
Fee(int) {
cout << "int" << endl;
}
Fee(int, float) {
cout << "float" << endl;
}
Fee(const Fi&) {
cout << "cc" << endl;
}
};
int main(void)
{
Fee fee1 = 1; //初始化调用C/CC函数
Fi fi;
Fee fee2 = fi;
int a = 1;
float b = 2;
// Fee fee3 = a, b; //可以看到,=初始化只能有一个参数
return 0;
}
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
class Value {
public:
Value(int aa, int bb, float cc) :a(aa), b(bb), c(cc)
{
cout << "c" << endl;
}
Value & //赋值是二元符,其实返回值没那么重要,但是这里返回一个引用,因为后面可能要改
operator=(const Value& rv)
{
cout << "operator=" << endl;
a = rv.a;
b = rv.b;
c = rv.c;
return *this;
}
friend ostream& //输出流的本质就是从左向右结合,传递,每次结合一个仍然是ostream类
operator<<(ostream& os, const Value& rv)
{
return os << "a = " << rv.a << " b = " << rv.b << " c = " << rv.c << endl;
}
private:
int a, b;
float c;
};
int main(void)
{
Value a(0, 1, 2), b(1, 2, 3);
cout << "a " << a << endl;
cout << "b " << b << endl;
a = b;
cout << "a " << a << endl;
return 0;
}
常用的三种用法
- 复杂带指针对象的赋值。这个要求把指针指向的东西进行递归复制与赋值
- 引用计数:优化operator=和CC函数的行为。赋值和拷贝构造时不新建空间,而是指向同一个空间,当计数为0,就销毁(有java那味儿了)。要修改对象时,如果有多个引用,那么就执行写拷贝,复制一份新的再写。
- 通过CC函数和operator=实现类之间的自动类型转换。
PStash——new和delete的动态内存管理
new/delete概览
相当于加强版的malloc类/free类函数,将这些步骤封装起来。而且,可以重载,但是重载往往是用来优化加速的,一般使用默认的即可。
new和delete让动态内存管理更加方便,从此数组是路人,二级指针闯天下。
new:
- 开辟内存,一般是调用malloc
- 调用构造函数
- 检查内存分配
delete:
- 调用析构函数。如果析构函数里有别的delete,可以实现递归调用,清楚所有相关对象
- 释放内存,一般是调用free
- 建议在delete p后加上p=NULL补刀,防止二次delete同一片区域
- delete void*不建议这么做,因为不会调用析构函数,极易引起内存泄漏
delete和delete[]的本质
delete和delete[]都是针对指针的,目的都是释放指针指向的空间。
delete很简单。当p指向一个new 创建的类,delete直接调用类的析构函数。
delete[]是易混点
p指向对象数组,delete[]就是逐个调用指向数组的每个类的析构函数。
这就容易造成一种 错觉 ,delete相当于对每个成员分别进行一次delete。
比如如下代码,初学者很容易就混淆,认为代码会delete每一个storage[i]
MyClass* storage[]={new MyClass(),new MyClass()}
delete[] storage;
但是实际上,delete仅仅是针对指针的,而且即使是delete[]也只是针对一个数组整体,而不是对每一个成员delete。
况且,对于对象数组来说,假如按照对每一个成员delete来做,delete也不能对对象使用,只能对指针使用。
总之,要想删除二级指针指向的空间,必须用for loop,靠delete[]只能是把指针数组给放掉,指向的内容还好好的。
PStash
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
//注意,我们这里还是使用了void*,需要写好析构函数
#ifndef PSTASH_H
#define PSTASH_H
class PStash {
public:
PStash() :quantity(0), storage(0), next(0)
{
cout << "c" << endl;
}
~PStash(); //因为有循环,不内联
int add(void* element)
{
const int inflateSize = 10;
if (next >= quantity)//检查扩容
{
inflate(inflateSize);
}
storage[next++] = element;//指向传入的元素
return (next - 1);//返回下标
}
void* operator[](int index) const
{
if (index < 0 || index >= next)
{
return NULL;
}
return storage[index];
}
void* remove(int index)
{
void* v = operator[](index);
if (v != NULL) //确定有东西,在storage中置零指针
{
storage[index] = NULL;//注意!这里没有清理对象,而是将清理任务扔给了客户
}
return v;
}
int count() const
{
return next;
}
private:
int quantity;//上限
int next;//下一个索引,也是当前的数量值
void** storage;//storage实际上是指针数组,只不过不限制大小了
void inflate(int increase)
{
const int psz = sizeof(void*);
void** new_s = new void* [quantity + increase];
memset(new_s, 0, (quantity + increase) * psz);
memcpy(new_s, storage, quantity * psz);
quantity += increase;
delete[] storage;
storage = new_s;
}
};
PStash::~PStash()
{
for (int i = 0; i < next; i++)
{
if (storage[i] != NULL)
{
cout << "PStash not cleaned up" << endl;
}
}
delete[] storage;//对storage指针数组中每一个指针调用delete
}
#endif //PSTASH_H//
int main()
{
PStash intStash;
for (int i = 0; i < 5; i++)
{
intStash.add(new int(i));//内建类型也可以使用new,快速生成空间和指针
}
for (int i = 0; i < intStash.count(); i++)
{
//void*要强制转换类型还是很麻烦,而且不能对客户要求那么高,后面模板可以解决
cout << "intStash[" << i << "] = " << *(int*)intStash[i] << endl;
}
for (int i = 0; i < intStash.count(); i++)
{
delete (int*)intStash.remove(i);//remove去掉指针,delete将指针指向的内存释放
//int*其实不用加,加了保险
}
ifstream in("input.txt");
PStash stringStash;
string line;
while (getline(in, line))
{
stringStash.add(new string(line));
}
for (int i = 0; i < stringStash.count(); i++)
{
cout << "stringStash[" << i << "] = " << *(string*)stringStash[i] << endl;
}
for (int i = 0; i < stringStash.count(); i++)
{
delete (string*)stringStash.remove(i);
}
return 0;
}
继承与组合
继承的目的是将接口全部继承,同时大量特殊化,同时保证接口不变,最终目的是多态。
组合的目的是改变接口,大量加入成员,只引入部分接口,最终目的是变成一个更强的类。
基本继承机制
其实C++中继承和Java里面的继承差距还挺大,如果对Java继承的理解没那么深,对比理解可能还不如直接去理解。
- 继承是将基类当做子成员。
- 继承不会真的覆盖。基类的base::i和子类的i都存在,不是覆盖,只不过是形式上的覆盖,实际上你可以通过base::i来调用基类i。函数继承同成员继承,都存在。
- 继承的本质。就是从子类向基类找声明的过程,如果子类有i,那么就不需要用基类的i,如果子类有print(),就不需要使用基类的print(),这就是继承所谓的“覆盖”效应。实际上是没有覆盖的。
- 基类中的访问修饰。相当于子类对基类权限的门。public代表任意访问,protected只允许子类访问,private不允许包括子类在内的访问。
- 继承时的访问修饰。相当于快捷设定基类在子类中的访问修饰。public不改变,protected将基类的public变成protected,private变为private。
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
class base {
public:
int i;
void print()
{
cout << "base::i : " << i << endl;
}
};
class child :public base {
public:
void print()
{
cout << "child::i : " << i << endl;
}
};
int main(void)
{
child a;
a.i = 3; //如果是private(默认)base,这个就看不到
a.print(); //“覆盖”
return 0;
}
名字隐藏——背后还是继承
基类中重载的函数会被隐藏。隐藏条件:
- 子类重写一个同名同型函数。
- 子类增加一个同名不同型函数。
本质上,就是继承中层次的选择,和我们前面的继承原则一模一样,这就是基于继承机制衍生出来的一种现象罢了。
- 如果子类中没有该名字,那么就跳到上一层去寻找
- 如果子类中有该名字,就停留在这一层。
- 层与层之间被隔开,强制进入上一层需要使用
基类::标志符 的方法。
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
class base {
public:
int i;
void print()
{
cout << "base::i : " << i << endl;
}
};
class child :public base {
public:
void print(int i)
{
cout << "child::i : " << i << endl;
}
};
int main(void)
{
child a;
a.i = 3; //如果是private(默认)base,这个就看不到
a.print(1);//不论是重写“覆盖”,还是增加,都会让作用域停留在当前层
a.base::print(); //使用作用域强制访问上一层
return 0;
}
非继承函数
和当前层紧密相关的没办法继承。有些基类的函数没办法给子类用。
其实就是四大函数(C,CC,operator=,析构),都不能被继承。结果就是,要么自动创建,要么自己写。
需要注意的是,如果基类没有默认构造(也就是已经自己写了别的类型的却没有写void参数的),就只能自己写子类构造函数了,别指望编译器有多么高级,他只能递归调用默认函数。
总之就是在你自己写一个四大函数之后,你的子类就得写这个函数了。好消息是,如果你将子类的函数写成void参数型(就是默认型),那子类的子类就又不用写这个函数了。
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
class base {
public:
int i;
base(int ii):i(ii)
{
cout << "base written c" << endl;
}
void print()
{
cout << "base::i : " << i << endl;
}
};
class child :public base {
public:
static const int compiler_cant_do = 0;
child() :base(compiler_cant_do) //编译器没法为你写这个函数,比如这个const量应该是几?
{
cout << "child written c" << endl;
}
void print(int)
{
cout << "child::i " << i << endl;
}
};
class grandSon :public child {
public:
//默认构造
void print()
{
cout << "grandSon::i " << i << endl;
}
};
int main(void)
{
child a;
grandSon b;
a.i = 3; //如果是private(默认)base,这个就看不到
a.print(100);//不论是重写“覆盖”,还是增加,都会让作用域停留在当前层
a.base::print(); //使用作用域强制访问上一层
b.print();
return 0;
}
StringStack——继承与组合应用
继承版本
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STACK_H
#define STACK_H
class Stack {
public:
Stack() :head(NULL)
{
cout << "Stack constructor" << endl;
}
~Stack()
{
cout << "Stack destructor " << (head == NULL ? "success" : "fail") << endl;
}
void push(void* dat) //将新节点插到头部,注意是直接把元素插进去了,没有复制
{
Link* node = new Link(dat, head);
head = node;
}
void* peek () const //获取顶部元素
{
if (head == NULL) //判断空
return NULL;
return head->data;
}
void* pop() //这个也不算大,就内联了
{
if (head == NULL)//判断空
return NULL;
void* result = head->data; //暂时储存
Link* old_head = head;
head = head->next; //移位
delete old_head; //处理暂存
return result;
}
private:
struct Link {
void* data;
Link* next;
Link(void* dat, Link* nxt) :data(dat), next(nxt)//所有类都应该有四大函数
{
cout << "Stack::Link " << data << " constructor" << endl;
}
~Link()
{
cout << "Stack::Link " << data << " destructor" << endl;
}
}*head;
};
#endif //STACK_H//
//通过组合来改变接口,实现特殊化
#ifndef STRINGSTACK_H
#define STRINGSTACK_H
class StringStack :Stack{
public:
void push(string* str)
{
Stack::push(str);//string*->void* 自动转换
}
string* peek() const
{
return (string*)Stack::peek();
}
string* pop()
{
return (string*)Stack::pop();
}
};
#endif //STRINGSTACK_H//
int main(void)
{
ifstream in("input.txt");
string line;
StringStack lines;
while (getline(in, line))
{
lines.push(new string(line));
}
string* sp;
while ((sp = lines.pop()) != NULL)
{
cout << *sp << endl;
}
return 0;
}
组合写法
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<cassert> //调试
using namespace std;
#ifndef STACK_H
#define STACK_H
class Stack {
public:
Stack() :head(NULL)
{
cout << "Stack constructor" << endl;
}
~Stack()
{
cout << "Stack destructor " << (head == NULL ? "success" : "fail") << endl;
}
void push(void* dat) //将新节点插到头部,注意是直接把元素插进去了,没有复制
{
Link* node = new Link(dat, head);
head = node;
}
void* peek () const //获取顶部元素
{
if (head == NULL) //判断空
return NULL;
return head->data;
}
void* pop() //这个也不算大,就内联了
{
if (head == NULL)//判断空
return NULL;
void* result = head->data; //暂时储存
Link* old_head = head;
head = head->next; //移位
delete old_head; //处理暂存
return result;
}
private:
struct Link {
void* data;
Link* next;
Link(void* dat, Link* nxt) :data(dat), next(nxt)//所有类都应该有四大函数
{
cout << "Stack::Link " << data << " constructor" << endl;
}
~Link()
{
cout << "Stack::Link " << data << " destructor" << endl;
}
}*head;
};
#endif //STACK_H//
//通过组合来改变接口,实现特殊化
#ifndef STRINGSTACK_H
#define STRINGSTACK_H
class StringStack {
public:
void push(string* str)
{
stack.push(str);//string*->void* 自动转换
}
string* peek() const
{
return (string*)stack.peek();
}
string* pop()
{
return (string*)stack.pop();
}
private:
Stack stack;
};
#endif //STRINGSTACK_H//
int main(void)
{
ifstream in("input.txt");
string line;
StringStack lines;
while (getline(in, line))
{
lines.push(new string(line));
}
string* sp;
while ((sp = lines.pop()) != NULL)
{
cout << *sp << endl;
}
return 0;
}
多态
多态是面向对象的核心,也是初学最觉得用不上的东西。
实际上,对于只和基类接口通信的函数,无论我们如何继承,生成何种子类,都可以保持这个函数的接口不变,这就让程序具有良好的扩展性。
晚捆绑与虚机制
为了解决向上类型转换带来的类型丢失问题,我们需要额外添加信息,虚函数承担这个任务。
先不管虚机制如何实现,仅仅是对于virtual关键字,对函数加了virtual以后,他自己以及所有派生类的同名函数,都会支持多态,也就是即使进行向上类型转换也可以执行我们前面说的继承机制。
虚机制:
- VPTR是一个指向VTABLE的指针,VTABLE里存放类中虚函数的地址(注意,有地址代表虚函数不可以内联),VPTR和VTABLE就相当于记录了类型信息。
- 具体执行时,编译器自动插入获取VPTR以及在VTABLE中查询函数地址的代码,无需用户操心。
- 优化。如果编译器直到一个对象确切的类型,即不使用多态(没有进行向上类型转换)时调用虚函数,会执行早捆绑。
继承时的虚机制:
- 新建VTABLE,首先将VTABLE继承过去
- 然后选择性覆盖(只要重名都覆盖),和添加(不重名)
纯虚函数与抽象类
- 当基类的某个虚函数仅仅是想成为一个接口,约定,那么就声明纯虚,这样就不需要去实现了,仅仅定义即可。一旦声明了纯虚,这个类也就变成了抽象基类。
- 纯虚声明也限制了对这个函数的调用,甚至是这个基类中其他函数的调用,原因见1。要想解决这个问题,可以编写纯虚定义,为任何的派生类都制造同样的默认代码。(我觉得可以覆盖,但是我没有做实验)
- 纯虚强制一系列派生类去实现,否则就一直延续下去。
- 纯虚函数不允许传值,只能进指针和引用
- 当类中全是纯虚函数,这个类的意义仅为约定接口,叫做纯抽象类,在Java中有接口类与之对应。
virtual void pure_virtual()=0;
对象切片
- 函数参数类型的本质是限定内存大小。比如int就4字节,某一个class都有固定的大小。
- 如果传入多态指针,因为指针的大小都一样,所以无所谓
- 但是按值传递会强行将子类切片成为基类,包括他的VTABLE等都被切割,成为一个新的基类对象。
因此,尽量按引用和指针传递,防止按值传递的对象切片问题。
虚函数和构造/析构函数
编译器将初始化VPTR相关代码插入构造函数开头,会隐形的增加函数长度。
虚函数的本质是让基类形式的调用,实现对派生类的调用。
构造函数非虚:
- 构造函数一定是先基类再派生类
- 如果使用虚函数,会引发基类调用派生类的情况,然而派生类还没初始化呢
- 说白了就是构造函数方向和虚函数方向相反
析构函数 总应该 虚:
- 析构函数的方向和虚函数方向相同,因此可以直接找到末端派生类开始执行析构。
- 否则会出现仅仅析构基类,但是不析构派生部分的问题。
模板
基本概念
模板真的好用。写起来也很简单:
- 在每一个用到模板的部分前加上模板声明,比如类前面,以及非内联函数前面。
- 常量可以写在模板声明里
- 非内联函数的类名访问作用域写class<T>:: 因为实际上生成的具体的类就是class<T>
- 模板可以将非内联函数都放在头文件中,似乎违背了头文件不放地址相关代码的原则,但是实际上编译器不对模板分配内存,而且会自动优化重定义问题。所以模板大可放心地全部放在头文件。
- 可以使用自己的一个测试类(比如AutoCounter)作为测试,通过了再换成模板。
至于函数模板后面再说。
TPStash&AutoCounter——模板初体验&自动计数测试类
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<set> //集合
using namespace std;
#ifndef TPSTASH_H
#define TPSTASH_H
template <class T,int incr=10>
class PStash {
public:
PStash() :quantity(0), next(0), storage(NULL)
{
cout << "PStash c" << endl;
}
~PStash();
int add(T* element);
T* operator [] (int index) const;//取出一个指针
T* remove(int index);
int count() const
{
return next;
}
private:
int quantity;
int next;
T** storage;//storage空间储存指针
void inflate(int increase = incr);//声明了默认参,定义就不用了
};
template<class T,int incr> //仅仅在构造类时需要声明incr默认值
int PStash<T, incr>::add(T* element)
{
if (next >= quantity)//检查容量
{
inflate(incr);
}
storage[next++] = element;//仅仅指向,不新建
/*
! 能不能有个复制行为啊
*/
return next - 1;
}
template <class T, int incr>
PStash<T, incr>::~PStash()
{
//释放指向的对象
for (int i = 0; i < next; i++)
{
delete storage[i];
storage[i] = NULL;
}
//释放指针数组,详见inflate(),其中用new 申请storage
delete[] storage;
}
template<class T,int incr>
T* PStash<T, incr>::operator[] (int index) const
{
if (index < 0 || index >= next)
{
return NULL;
}
return storage[index];//返回NULL代表storage[index]是空指针
}
template<class T,int incr>
T* PStash<T, incr>::remove(int index)
{
T* p = operator[](index);
storage[index] = NULL;
return p; //处置权还在外面
}
template<class T,int incr>
void PStash<T, incr>::inflate(int increase)
{
const int psize = sizeof(T*);
//新建指针数组
T** new_s = new T * [quantity + increase];
memset(new_s + quantity, 0, increase * psize);
memcpy(new_s, storage, quantity * psize);
quantity += increase;
//delete原来指针数组
delete[] storage;
storage = new_s;
}
#endif //TPSTASH_H//
//自动计数的类,可用于测试模板
//用到了静态成员,虽然是成员,但是静态成员实际上是要高于所有实例的,
//是和这个类绑定在一起的
#ifndef AUTOCOUNTER_H
#define AUTOCOUNTER_H
class AutoCounter {
public:
static AutoCounter* create() //只暴露一个构造接口,static必须加
{
return new AutoCounter();
}
~AutoCounter()
{
cout << "~AutoCounter " << id << endl;
verifier.remove(this);
}
friend ostream&
operator<<(ostream& os, const AutoCounter& ac)
{
return os << ac.id;
}
friend ostream&
operator<<(ostream& os, const AutoCounter* acp)
{
return os << acp->id;
}
private:
int id; //唯一标识符
//静态成员:总数 和 计数集合,真正删除对象才会erase verifier里的东西
static int count;
class CleanupCheck { //总检验类,
public:
void add(AutoCounter* p)
{
cout << "insert id : " << p->id << endl;
trace.insert(p);
}
void remove(AutoCounter* p)
{
if (trace.erase(p) == 1)
{
cout << "erase successful"<<endl;
}
else
{
cout << "erase fail" << endl;
}
}
~CleanupCheck()
{
cout << "~CleanupCheck" << endl;
if (trace.size() == 0)
{
cout << "clean" << endl;
}
else
{
cout << "not clean" << endl;
}
}
private:
set<AutoCounter*> trace;
};
static CleanupCheck verifier;
//私有化构造
AutoCounter() :id(count++)
{
verifier.add(this);
cout << "AutoCounter() id : " << id << endl;
}
AutoCounter(const AutoCounter&);
void operator=(const AutoCounter&);
};
//初始化
int AutoCounter::count = 0;
AutoCounter::CleanupCheck AutoCounter::verifier;//调用默认构造
#endif //AUTOCOUNTER_H//
int main(void)
{
PStash<AutoCounter> acStash;
cout << "create and insert 10" << endl;
for (int i = 0; i < 10; i++)
{
acStash.add(AutoCounter::create());
}
cout << "remove 5" << endl;
for (int i = 0; i < 5; i++)
{
delete acStash.remove(i);
}
cout << "remove 2 without delete" << endl;
cout << "remove: " << acStash.remove(5) << endl;
cout << "remove: " << acStash.remove(6) << endl;
cout << "the destructor cleans up the rest:" << endl;
return 0;
}
指针指向空间的所有权控制与改进
之前我们就碰到一种情况,Stack不存放数据,而是存放指向数据的指针,那么如果有别的指针也指向数据,而且还恰巧把数据给delete了,那Stack里的指针如果对这个指针进行引用,就会出错。
这其实涉及到数据所有权的问题
这个问题可以通过内置一个bool变量来简单确定对指针指向空间的所有权。
TODO pdf 1092页
优化:
其实这么做有点复杂,一种简单做法就是让Stack存放数据,这个通过模板可以很容易解决。回顾我们曾经用指针的理由:为了配合类型转换实现自定义储存类型,现在我们有模板,完全可以不用指针。
唯一的缺点就是,需要扩容时的拷贝比较浪费时间。
Stach&Stack 5.0——模板+容器迭代器写法
其实刚开始听到容器和迭代器,对于新手是懵逼的,为什么要搞那么复杂?说白了就是为了使用的安全和方便,而牺牲一点简单度和速度。
容器:
- 比如Stash里装了一个数组,Stash就是容器,很直观。
- 容器的作用就是将实际储存和用户隔开一层。
- 容器可以提供operator[]之类的访问,但是这样的访问还是比较单一。
迭代器:
- 迭代器常常是灵巧指针,目标是模仿指针行为,不论指向哪种容器,接口都是一样的,这样我们就可以编写更加一般的代码,搭配函数模板对不同的容器进行操纵,而且不同的容器还可以多态装元素,这也和现在的弱类型有关系。
- 简单的灵巧指针仅仅含有一个容器引用和当前位置,占用空间很小。同时具有丰富的对容器访问的方法,以及更安全的检查。
- 迭代器通常内嵌
Stack数组写法:
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<set> //集合
#include"AutoCounter.h" //自动计数类
using namespace std;
//数组栈
#ifndef ITERSTACKTEMPLATE_H
#define ITERSTACKTEMPLATE_H
template<class T,int stack_size=100>
class StackTemplate {
T stack[stack_size];
int top;
public:
StackTemplate() :top(0)
{
cout << "StackTemplate()" << endl;
}
void push(const T& element)
{
if (top >= stack_size)
{
cout << "stack overflow" << endl;
return;
}
stack[top++] = element;
cout << "push " << stack[top - 1] << endl;
}
T pop()
{
if (top <= 0)
{
return NULL; //TODO 可以给对象返回NULL吗?
}
return stack[--top];
}
//定义iterator
class iterator;
friend class iterator;
class iterator {
StackTemplate& storage;
int index;
public:
//起止点iterator,私有构造
iterator(StackTemplate& s) :storage(s), index(0)//内嵌也还是需要绑定,只不过可以再封装
{
cout << "iterator() begin index: "<<index << endl;
}
iterator(StackTemplate& s, bool) :storage(s), index(storage.top)
{
cout << "iterator() end index: "<<index << endl;
}
//* ++ += == != << 运算符
T operator*() const //按值返回
{
return storage.stack[index]; //指针移动负责检查,返回和加入就直接
}
T operator++() //单目默认左结合
{
//安全检查
return storage.stack[++index];
}
T operator++(int)
{
//安全检查
return storage.stack[index++];
}
iterator& operator+=(int step)
{
//安全检查
index += step;
return *this;
}
bool operator==(const iterator& rv) const
{
return index == rv.index;
}
bool operator!=(const iterator& rv) const
{
return index != rv.index;
}
friend std::ostream&
operator<<(ostream& os, const iterator& it)
{
return os << *it; //感觉这里有问题,T 都可以直接<<吗?
}
};
//从容器生成迭代器兼顾绑定和包装简化
iterator begin()
{
return iterator(*this);
}
iterator end()
{
return iterator(*this, true);
}
};
#endif
int main(void)
{
StackTemplate<int> stack;
for (int i = 0; i < 10; i++)
{
stack.push(i);
}
StackTemplate<int>::iterator it = stack.begin();
while (it != stack.end())
{
it++;
}
ifstream in("input.txt");
string line;
StackTemplate<string> strings;
while (getline(in, line))
{
strings.push(line);
}
StackTemplate<string>::iterator
sb = strings.begin(), se = strings.end();
while (sb != se)
{
cout << (sb++) << endl;
}
return 0;
}
Stack链表写法:
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<set> //集合
using namespace std;
#ifndef TSTACK2_H
#define TSTACK2_H
template<class T> class Stack {
struct Link {
T* data;
Link* next;
Link(T* dat, Link* nxt) :data(dat), next(nxt)
{
cout << "Link()" << endl;
}
}*head;
public:
Stack() :head(NULL)
{
cout << "Stack()" << endl;
}
~Stack();
void push(T* dat)
{
head = new Link(dat, head); //直接指向,不新建data
}
T* peek() const
{
return head ? head->data : NULL;
}
T* pop();
//迭代器 构造,++ * -> == !=
class iterator;
friend class iterator;
class iterator {
Stack::Link* p;
public:
//迭代器构造
iterator(const Stack<T>& tl) :p(tl.head)
{
cout << "iterator()" << endl;
}
iterator(const iterator& tl) :p(tl.p)
{
cout << "iterator(&)" << endl;
}
iterator() :p(NULL)
{
cout << "iterator(NULL)" << endl;
}
//移动
bool operator++()
{
if (p->next)
{
p = p->next;
}
else
{
p = NULL;
}
return bool(p);
}
bool operator++(int)
{
return operator++();
}
//取值
T* current() const
{
if (!p)
{
return NULL;
}
return p->data;
}
T* operator*() const //这个可以简单取出一个指针
{
return current();
}
T* operator->() const //同样取指针,但一旦用这个就要调用T的函数了
{
//安全检查
return current();
}
//类型转换
operator bool() const //类型转换不需要指定返回类型
{
return bool(p);
}
//比较判别,这里用哑元
bool operator==(const iterator& rv) const
{
if (p == NULL || rv.p == NULL)//空指针没得比,就直接返回p的情况
{
return p != NULL; //非空为真
}
else
{
return p->data == rv.p->data; //直接比两个T* 指向的内容(地址)
}
}
bool operator!=(const iterator& rv) const
{
if (p == NULL || rv.p == NULL)//空指针没得比,就直接返回p的情况
{
return p != NULL; //非空为真
}
else
{
return p->data != rv.p->data; //直接比两个T* 指向的内容(地址)
}
}
};
iterator begin() const
{
return iterator(*this);
}
iterator end() const
{
return iterator();//指向NULL就意味着end
}
};
template<class T> Stack<T>::~Stack()
{
while (head)
delete pop();
}
template<class T> T* Stack<T>::pop()
{
if (head == NULL)
{
return NULL;
}
T* result = head->data;
Link* old_head = head;
head = head->next;
delete old_head;
return result;
}
#endif //TSTACK2_H//
int main(void)
{
ifstream in("input.txt");
Stack<string> lines;
string line;
while (getline(in, line))
{
lines.push(new string(line));
}
int i = 0;
Stack<string>::iterator it = lines.begin();
Stack<string>::iterator* it_2 = NULL;//指向迭代器的指针
Stack<string>::iterator end = lines.end();
while (it != end)
{
cout << it->c_str() << endl;
it++;
if (++i == 10) //it——2储存10个,也拥有对数据的所有权
{
it_2 = new Stack<string>::iterator(it);
}
}
cout << "it_2" << endl;
cout << (*it_2)->c_str() << endl;
delete it_2;
return 0;
}
PStash终极版本
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<set> //集合
using namespace std;
#ifndef TPSTASH2_H
#define TPSTASH2_H
template<class T, int incr = 20> class PStash {
int quantity;//总量
int next;//下一个下标&当前数量
T** storage;//指针数组
void inflate(int increase = incr);
public:
PStash() :quantity(0), next(0), storage(NULL)
{
cout << "PStash()" << endl;
}
~PStash();
int add(T* element);
T* operator[](int index) const; //必须inline
T* remove(int index);
int count() const
{
return next;
}
//迭代器
class iterator;
friend class iterator;
class iterator {
PStash& ps;
int index;
public:
//构造
iterator(PStash& ps_r) :ps(ps_r), index(0)
{
cout << "iterator() begin" << endl;
}
iterator(PStash& ps_r, bool) :ps(ps_r), index(ps.next)
{
cout << "iterator() end" << endl;
}
iterator(const iterator& it) :ps(it.ps), index(it.index)
{
cout << "iterator(&)" << endl;
}
iterator& operator=(const iterator& it)
{
ps = it.ps;
index = it.index;
return *this;
}
//移动
iterator& operator++()
{
//安全检查
if (index < ps.next) //TODO 等于要不要
{
index++;
}
return *this;
};
iterator& operator++(int)
{
return operator++();
}
iterator& operator--()
{
if (index > 0)
{
index--;
}
return *this;
}
iterator& operator--(int)
{
return operator--();
}
iterator& operator+=(int step)
{
if (index + step >= ps.next)
{
index = ps.next;
return *this;
}
else
{
index += step;
return *this;
}
}
iterator& operator-=(int step)
{
if (index - step <0)
{
index = 0;
return *this;
}
else
{
index -= step;
return *this;
}
}
iterator operator+(int step) const//生成临时iterator
{
iterator new_it(*this);//cc
new_it += step;
return new_it;
}
//取元素
T* current() const
{
return ps.storage[index];
}
T* operator*() const
{
return current();
}
T* operator->() const
{
T* p = current();
if (p == NULL)
{
//这里加入防止空指针引用的检查,目前还没有
return NULL;
}
else
{
return p;
}
}
//删除元素,因为用二级指针,处置权在外
T* remove()
{
return ps.remove(index);
}
//比较
bool operator==(const iterator& rv) const
{
return index == rv.index;
}
bool operator!=(const iterator& rv) const
{
return !operator==(rv);
}
//输出(自己写的,可能有错)
friend ostream& operator<<(ostream& os, const iterator& rv)
{
os << *(*rv); //第一次取个指针出来,再*将对象T解析,然后T也应该有operator<<
return os;
}
};
iterator begin()
{
return iterator(*this); //*this是容器
}
iterator end()
{
return iterator(*this, true);
}
};
template<class T, int incr>
PStash<T, incr>::~PStash()
{
cout << "~PStash()" << endl;
//先delete目标空间
for (int i = 0; i < next; i++)
{
delete storage[i]; //如果已经被删除(remove)了,delete NULL不会有问题
storage[i] = 0; //补刀
}
//delete指针数组
delete[] storage;
}
template<class T, int incr>
int PStash<T, incr>::add(T* element)
{
if (next >= quantity) //扩容
{
inflate();
}
storage[next++] = element;
return next - 1;
}
template<class T, int incr>
inline T* PStash<T, incr>::operator[] (int index) const
{
if (index >= next || index < 0)
{
return NULL;
}
return storage[index]; //可能返回NULL,自行判断
}
template<class T,int incr>
T* PStash<T, incr>::remove(int index)
{
T* element = operator[](index);
storage[index] = 0;//补刀,否则会引起二次delete(试试看)
return element;
}
template<class T,int incr>
void PStash<T, incr>::inflate(int increase)
{
const int psz = sizeof(T*);
T** new_s = new T * [quantity + increase];
memset(new_s, 0, (quantity + increase) * psz);
memcpy(new_s, storage, quantity * psz);
delete[] storage;
quantity += increase;
storage = new_s;
}
#endif //TPSTASH2_H//
class Int {
int i;
public:
//构造
Int(int ii = 0) :i(ii)
{
cout << "Int()" << endl;
}
~Int()
{
cout << "~Int() " << i << endl;
}
//自动类型转换
operator int() const
{
return i;
}
//operator<<
friend ostream&
operator<<(ostream& os, const Int& x)
{
return os << "Int: " << x.i;
}
friend ostream&
operator<<(ostream& os, const Int* x)
{
return os << "Int: " << x->i;
}
};
int main(void)
{
//规定作用域,强制提前析构
PStash<Int> ints;
for (int i = 0; i < 30; i++)
{
ints.add(new Int(i));
}
cout << endl;
PStash<Int>::iterator it = ints.begin(); //使用初始化operator其实是调用拷贝构造
it += 5;
PStash<Int>::iterator it2 = it + 10;
while (it != it2)//从5遍历到15
{
delete it.remove();
it++;
}
cout << endl;
PStash<Int>::iterator end = ints.end();
for (it = ints.begin(); it != end; it++)
{
if (*it)
{
cout << *it << endl; //这里其实是输出一个Int* ,但是已经做好约定了
}
else
{
cout << "NULL storage "<< endl;
}
}
cout << endl;
return 0;
}
多态与函数模板,泛型实践
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include<set> //集合
using namespace std;
#ifndef TSTACK2_H
#define TSTACK2_H
template<class T> class Stack {
struct Link {
T* data;
Link* next;
Link(T* dat, Link* nxt) :data(dat), next(nxt)
{
cout << "Link()" << endl;
}
}*head;
public:
Stack() :head(NULL)
{
cout << "Stack()" << endl;
}
~Stack();
void push(T* dat)
{
head = new Link(dat, head); //直接指向,不新建data
}
T* peek() const
{
return head ? head->data : NULL;
}
T* pop();
//迭代器 构造,++ * -> == !=
class iterator;
friend class iterator;
class iterator {
Stack::Link* p;
public:
//迭代器构造
iterator(const Stack<T>& tl) :p(tl.head)
{
cout << "iterator()" << endl;
}
iterator(const iterator& tl) :p(tl.p)
{
cout << "iterator(&)" << endl;
}
iterator() :p(NULL)
{
cout << "iterator(NULL)" << endl;
}
//移动
bool operator++()
{
if (p->next)
{
p = p->next;
}
else
{
p = NULL;
}
return bool(p);
}
bool operator++(int)
{
return operator++();
}
//取值
T* current() const
{
if (!p)
{
return NULL;
}
return p->data;
}
T* operator*() const //这个可以简单取出一个指针
{
return current();
}
T* operator->() const //同样取指针,但一旦用这个就要调用T的函数了
{
//安全检查
return current();
}
//类型转换
operator bool() const //类型转换不需要指定返回类型
{
return bool(p);
}
//比较判别,这里用哑元
bool operator==(const iterator& rv) const
{
if (p == NULL || rv.p == NULL)//空指针没得比,就直接返回p的情况
{
return p != NULL; //非空为真
}
else
{
return p->data == rv.p->data; //直接比两个T* 指向的内容(地址)
}
}
bool operator!=(const iterator& rv) const
{
if (p == NULL || rv.p == NULL)//空指针没得比,就直接返回p的情况
{
return p != NULL; //非空为真
}
else
{
return p->data != rv.p->data; //直接比两个T* 指向的内容(地址)
}
}
};
iterator begin() const
{
return iterator(*this);
}
iterator end() const
{
return iterator();//指向NULL就意味着end
}
};
template<class T> Stack<T>::~Stack()
{
while (head)
delete pop();
}
template<class T> T* Stack<T>::pop()
{
if (head == NULL)
{
return NULL;
}
T* result = head->data;
Link* old_head = head;
head = head->next;
delete old_head;
return result;
}
#endif //TSTACK2_H//
#ifndef TPSTASH2_H
#define TPSTASH2_H
template<class T, int incr = 20> class PStash {
int quantity;//总量
int next;//下一个下标&当前数量
T** storage;//指针数组
void inflate(int increase = incr);
public:
PStash() :quantity(0), next(0), storage(NULL)
{
cout << "PStash()" << endl;
}
~PStash();
int add(T* element);
T* operator[](int index) const; //必须inline
T* remove(int index);
int count() const
{
return next;
}
//迭代器
class iterator;
friend class iterator;
class iterator {
PStash& ps;
int index;
public:
//构造
iterator(PStash& ps_r) :ps(ps_r), index(0)
{
cout << "iterator() begin" << endl;
}
iterator(PStash& ps_r, bool) :ps(ps_r), index(ps.next)
{
cout << "iterator() end" << endl;
}
iterator(const iterator& it) :ps(it.ps), index(it.index)
{
cout << "iterator(&)" << endl;
}
iterator& operator=(const iterator& it)
{
ps = it.ps;
index = it.index;
return *this;
}
//移动
iterator& operator++()
{
//安全检查
if (index < ps.next) //TODO 等于要不要
{
index++;
}
return *this;
};
iterator& operator++(int)
{
return operator++();
}
iterator& operator--()
{
if (index > 0)
{
index--;
}
return *this;
}
iterator& operator--(int)
{
return operator--();
}
iterator& operator+=(int step)
{
if (index + step >= ps.next)
{
index = ps.next;
return *this;
}
else
{
index += step;
return *this;
}
}
iterator& operator-=(int step)
{
if (index - step < 0)
{
index = 0;
return *this;
}
else
{
index -= step;
return *this;
}
}
iterator operator+(int step) const//生成临时iterator
{
iterator new_it(*this);//cc
new_it += step;
return new_it;
}
//取元素
T* current() const
{
return ps.storage[index];
}
T* operator*() const
{
return current();
}
T* operator->() const
{
T* p = current();
if (p == NULL)
{
//这里加入防止空指针引用的检查,目前还没有
return NULL;
}
else
{
return p;
}
}
//删除元素,因为用二级指针,处置权在外
T* remove()
{
return ps.remove(index);
}
//比较
bool operator==(const iterator& rv) const
{
return index == rv.index;
}
bool operator!=(const iterator& rv) const
{
return !operator==(rv);
}
//输出(自己写的,可能有错)
friend ostream& operator<<(ostream& os, const iterator& rv)
{
os << *(*rv); //第一次取个指针出来,再*将对象T解析,然后T也应该有operator<<
return os;
}
};
iterator begin()
{
return iterator(*this); //*this是容器
}
iterator end()
{
return iterator(*this, true);
}
};
template<class T, int incr>
PStash<T, incr>::~PStash()
{
cout << "~PStash()" << endl;
//先delete目标空间
for (int i = 0; i < next; i++)
{
delete storage[i]; //如果已经被删除(remove)了,delete NULL不会有问题
storage[i] = 0; //补刀
}
//delete指针数组
delete[] storage;
}
template<class T, int incr>
int PStash<T, incr>::add(T* element)
{
if (next >= quantity) //扩容
{
inflate();
}
storage[next++] = element;
return next - 1;
}
template<class T, int incr>
inline T* PStash<T, incr>::operator[] (int index) const
{
if (index >= next || index < 0)
{
return NULL;
}
return storage[index]; //可能返回NULL,自行判断
}
template<class T, int incr>
T* PStash<T, incr>::remove(int index)
{
T* element = operator[](index);
storage[index] = 0;//补刀,否则会引起二次delete(试试看)
return element;
}
template<class T, int incr>
void PStash<T, incr>::inflate(int increase)
{
const int psz = sizeof(T*);
T** new_s = new T * [quantity + increase];
memset(new_s, 0, (quantity + increase) * psz);
memcpy(new_s, storage, quantity * psz);
delete[] storage;
quantity += increase;
storage = new_s;
}
#endif //TPSTASH2_H//
#ifndef SHAPES_H
#define SHAPES_H
//定义了一个基类和两个派生,测试多态
class Shape { //基类
public:
virtual void draw() = 0;//纯虚函数
virtual void erase() = 0;
virtual ~Shape() //析构总应该虚
{
cout << "~Shape()" << endl;
}
};
class Circle :public Shape {
public:
Circle()
{
cout << "Circle()" << endl;
}
~Circle()
{
cout << "Circle::~Circle()" << endl;
}
void draw()
{
cout << "Circle:: draw()" << endl;
}
void erase()
{
cout << "Circle:: erase()" << endl;
}
};
class Square :public Shape {
public:
Square()
{
cout << "Square()" << endl;
}
~Square()
{
cout << "Square::~Square()" << endl;
}
void draw()
{
cout << "Square::draw()" << endl;
}
void erase()
{
cout << "Square::erase()" << endl;
}
};
#endif //SHAPES_H//
//定义三个容器,都有和标准vector相同的迭代器,vector是按值储,所以用Shape*保持iterator统一
class ContainerA :public PStash<Shape>{
public:
~ContainerA() //重写析构
{
cout << "~ContainerA()" << endl;
}
};
class ContainerB :public Stack<Shape>{
public:
~ContainerB()
{
cout << "~ContainerB()" << endl;
}
};
class ContainerC :public vector<Shape*>{
public:
~ContainerC()
{
cout << "~ContainerC()" << endl;
}
};
//函数模板,将class作为参数,有一点弱类型的味道,我深度怀疑弱类型的底层实现
template<class Iter>
void drawAll(Iter start, Iter end)
{
while (start != end)
{
(*start)->draw();//这里的调用无法进行严格的检查
start++;//好在迭代器设计模式就推荐写这些函数,所以通常不会出问题
}
}
int main(void)
{
//四种容器,都使用多态,在加入指针的时候自动向上类型转换,使用多态机制。
ContainerA a;
a.add(new Circle);
a.add(new Square);
ContainerB b;
b.push(new Circle);
b.push(new Square);
ContainerC c;
c.push_back(new Circle);
c.push_back(new Square);
Shape* shape_array[] = { new Circle,new Square };
cout <<endl<< "drawAll ContainerA:" << endl;
drawAll(a.begin(), a.end());
cout << endl << "drawAll ContainerB:" << endl;
drawAll(b.begin(), b.end());
cout << endl << "drawAll ContainerC:" << endl;
drawAll(c.begin(), c.end());
cout << endl << "drawAll array:" << endl;
int array_size = sizeof(shape_array) / sizeof(shape_array[0]);
drawAll(shape_array, shape_array + array_size);
//请注意,迭代器本质上模仿指针行为,所以可以把指针丢进去这个函数模板
cout << endl << "main()结尾自动析构,先派生再基类" << endl;
return 0;
}
自己写
到此,前半本就学完了,其他边角料知识,完全可以靠看来稳固的活动,现在的基本功已经比较扎实了,进一步自己敲东西,让主干知识更加强化!同时理解更高深的思想,参透之前没有看懂的部分,就靠自己亲手敲了。
MyVector
顾名思义,这是个vector的仿制品。
这个案例综合了前面的几乎所有知识,命名空间,类,封装,迭代器,操作符重载,模板,多态,虚函数。
head.h
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
//using namespace std;//当一个类用吧,不开命名空间了
#ifndef MYVECTOR_H
#define MYVECTOR_H
// 运用模板,采用按值储存方式,扩容使用100为单位的内存块,适用于中小数据量
// 如果变动较多,可以采用链表储存,本类主要目标用于查找
// 具有增删查改功能
// 内嵌迭代器
// 重载多种运算符
// 没有办法处理越界的异常情况,空对象和空引用一直难以解决,只能exit
template<class T, int increment = 100>
class MyVector {
T* storage;
int quantity;
int next;
//扩容,非内联
void inflate(int increase = increment);
public:
//四大函数
MyVector() :quantity(0), next(0), storage(NULL)
{
//none
}
MyVector(int initsize) :quantity(initsize), next(0)
{
if (initsize <= 0)//检查错误初始化
{
quantity = increment;
}
storage = new T[quantity];
}
~MyVector()
{
delete[] storage;
}
MyVector(const MyVector& myvector) //拷贝复制,储存的元素全部复制
{
quantity = myvector.quantity;
next = myvector.next;
storage = new T[quantity];
for (int i = 0; i < next; i++)
{
storage[i] = myvector.storage[i];
}
}
MyVector& operator=(const MyVector& myvector)
{
return MyVector(myvector);//直接调用CC函数,其实就是默认的operator=
}
//增删查改
int push_back(const T& element)
{
if (next >= quantity) //检查扩容
{
inflate();
}
storage[next++] = element;
return next - 1;
}
bool empty() const
{
return next <= 0 ? true : false;
}
int size() const
{
return next;
}
T pop_back()//删除尾部的
{
if (next <= 0)
{
std::cout << "empty" << std::endl;
exit(0);
}
return storage[--next];
}
T get_elem(int index) const//按值返回
{
if (index < 0 || index >= next)
{
std::cout << "over scale" << std::endl;
exit(0);
}
return storage[index];
}
T back() const
{
return storage[next - 1];
}
T front() const
{
return storage[0];
}
//改 通过operator[]实现
T& operator[](int index) const //返回引用
{
if (index < 0 || index >= next)
{
std::cout << "over scale" << std::endl;
exit(0);
}
return storage[index];
}
//操作符重载
friend std::ostream& //全局
operator<<(std::ostream& os, const MyVector& myvector)//寄希望于T有<<重载吧!
{
for (int i = 0; i < myvector.next; i++)
{
os << myvector.storage[i] << std::endl;
}
return os;
}
//迭代器
class iterator;
friend class iterator;
class iterator {
MyVector& vector;
int index;
public:
//构造类
//begin
iterator(MyVector& vector_r) :vector(vector_r), index(0)
{
//begin
}
//end
iterator(MyVector& vector_r,bool) :vector(vector_r), index(vector.next)
{
//end
}
//copy constructor
iterator(const iterator& it) :vector(it.vector), index(it.index)
{
//copy constructor
}
//operator=
iterator& operator=(const iterator& it)
{
return iterator(it);
}
//移动
//简单
iterator& operator++()
{
if (index < vector.next)
{
index++;
}
return *this;
}
iterator& operator++(int)
{
return operator++();
}
iterator& operator--()
{
if (index > 0)
{
index--;
}
return *this;
}
iterator& operator--(int)
{
return operator--();
}
//跳跃
iterator& operator+=(int step)
{
if (index + step >= vector.next)
{
index = vector.next;
return *this;
}
else
{
index += step;
return *this;
}
}
iterator& operator-=(int step)
{
if (index - step < 0)
{
index = 0;
return *this;
}
else
{
index -= step;
return *this;
}
}
//生成临时量
iterator operator+(int step) const
{
iterator new_it(*this);
new_it += step;
return new_it;
}
iterator operator-(int step) const
{
iterator new_it(*this);
new_it -= step;
return new_it;
}
//取元素
T operator* ()const
{
return vector[index];
}
T* operator->() const
{
return &vector[index];
}
//比较
bool operator==(const iterator& rv) const
{
return index == rv.index;
}
bool operator!=(const iterator& rv) const
{
return !operator==(rv);
}
//输出
friend std::ostream& operator<<(std::ostream& os, const iterator& rv)
{
os << *rv;
return os;
}
};
iterator begin() //迭代器生成
{
return iterator(*this);
}
iterator end()
{
return iterator(*this, true);
}
};
template<class T, int increment>
void MyVector<T, increment>::inflate(int increase)
{
const int tsize = sizeof(T);
T* new_s = new T[quantity + increase];
memset(new_s, 0, (quantity + increase) * tsize);
memcpy(new_s, storage, quantity * tsize);
delete[] storage;
quantity += increase;
storage = new_s;
}
#endif //MYVECTOR_H//
//多态测试类
#ifndef SHAPES_H
#define SHAPES_H
//定义了一个基类和两个派生,测试多态
class Shape { //基类
public:
virtual void draw() = 0;//纯虚函数
virtual void erase() = 0;
friend std::ostream& operator<<(std::ostream& os, Shape& shape);
friend std::ostream& operator<<(std::ostream& os, Shape* shape);
virtual ~Shape() //析构总应该虚
{
std::cout << "~Shape()" << std::endl;
}
};
//定义两种输出方式
std::ostream&
operator<<(std::ostream& os, Shape& shape)
{
shape.draw();
return os;
}
std::ostream&
operator<<(std::ostream& os, Shape* shape)
{
shape->draw();
return os;
}
class Circle :public Shape {
public:
Circle()
{
std::cout << "Circle()" << std::endl;
}
~Circle()
{
std::cout << "Circle::~Circle()" << std::endl;
}
void draw()
{
std::cout << "Circle:: draw()" << std::endl;
}
void erase()
{
std::cout << "Circle:: erase()" << std::endl;
}
};
class Square :public Shape {
public:
Square()
{
std::cout << "Square()" << std::endl;
}
~Square()
{
std::cout << "Square::~Square()" << std::endl;
}
void draw()
{
std::cout << "Square::draw()" << std::endl;
}
void erase()
{
std::cout << "Square::erase()" << std::endl;
}
};
#endif //SHAPES_H//
main.cpp
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<fstream> //文件读写
#include"head.h"
using namespace std;
//int简单测试
void test_int()
{
MyVector<int> vector;
const int line = 10;
for (int i = 0; i < line; i++)
{
vector.push_back(i);
}
cout << "front(): " << vector.front() << ' ' << "back(): " << vector.back() << endl;
cout << "get_elem(2): " << vector.get_elem(2) << endl;
vector[2] = 200;
cout << "after operator[] get_elem(2): " << vector.get_elem(2) << endl;
MyVector<int> vector_2 = vector;//operator=
cout << "size(): " << vector.size() << endl;
while (!vector.empty())
{
vector.pop_back();
}
cout << "after pop() size(): " << vector.size() << endl;
//用vector_2测试inflate
for (int i = line; i < 110; i++)
{
vector_2.push_back(i);
}
cout << "vector_2 size(): " << vector_2.size() << endl << vector_2 << endl;
}
//多态测试
void test_multiple()
{
MyVector<Shape*> shapes;
shapes.push_back(new Circle);
shapes.push_back(new Square);
cout << shapes;
//指针vector只能自己清理,但是指针弹出来反而可以直接delete了
while (!shapes.empty())
{
delete shapes.pop_back();
}
}
//迭代器测试
void test_iterator()
{
MyVector<Shape*> shapes;
cout << "push 3 Circle and 3 Square: " << endl;
for (int i = 0; i < 3; i++)
{
shapes.push_back(new Circle);
}
for (int i = 0; i < 3; i++)
{
shapes.push_back(new Square);
}
cout << "iterate all:" << endl;
MyVector<Shape*>::iterator it = shapes.begin();
MyVector<Shape*>::iterator end = shapes.end();
while (it != end)
{
cout << it << endl;
it++;
}
it -= 4;
cout << "-=4: " << it << endl;
it += 2;
cout << "+=2: " << it << endl;
cout << "it - 2: " << it - 2 << "after it - 2 : " << it << endl;
//指针vector只能自己清理,但是指针弹出来反而可以直接delete了
while (!shapes.empty())
{
delete shapes.pop_back();
}
}
int main(void)
{
cout << "test int: " << endl << endl;;
test_int();
cout << "test multiple: " << endl << endl;
test_multiple();
cout << "test_iterator: " << endl << endl;
test_iterator();
return 0;
}
分糖果问题——类对现实世界的模拟
TODO [图片]
有的问题没有数学规律和公式,所以就直接暴力模拟就好。我们采用逐层设计的方法:
- 玩游戏之前先分糖果。
- 一轮一轮玩游戏,直到全部相等。
- 一轮游戏中,先进行传递,再补发糖果,最后判断是否相等。
- 具体设计请参见函数
其实用面向过程也可以写的很短,但是思路并不清晰,而且比较费脑子,用类比较简单粗暴。非常直观。
#include<cstdio>
#include<iostream>
#include<ctime>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<queue> //队列
using namespace std;
class RoundTable {
static const int num = 10;
const int init_limit;//最多20个糖
int children[num];
void init();//初始化
void list();//报数
void pass();//传递糖果
void give();//添加糖果
bool equal();//判断糖果相等
public:
RoundTable(int lim = 20) :init_limit(lim + 1) //构造,初始化,+1是为了上限
{
cout << "Game Start!" << endl << endl;
init();
}
int a_round();//走一圈,成功了就返回当前糖果,失败就返回-1
};
void RoundTable::init()
{
//设置随机种子,不然每次都一样
srand((unsigned int)time(NULL));
for (int i = 0; i < num; i++)
{
children[i] = rand() % init_limit / 2 * 2;//先限制范围,然后初二乘二
}
cout << "after init:" << endl;
list();
cout << endl;
}
void RoundTable::list()
{
for (int i = 0; i < num; i++)
{
cout << "child " << i << " : " << children[i] << " candies" << endl;
}
}
void RoundTable::pass()
{
queue<int> Q;
//先把0放进去
children[0] /= 2;
Q.push(children[0]);
//1-传到最后
for (int i = 1; i < num; i++)
{
children[i] /= 2;
Q.push(children[i]);
children[i] += Q.front();
Q.pop();
}
//传给0
children[0] += Q.front();
Q.pop();
cout << "after pass :" << endl;
list();
cout << endl;
}
void RoundTable::give()
{
for (int i = 0; i < num; i++)
{
if (children[i] % 2 != 0) //奇数
{
children[i]++;
}
}
cout << "after give():" << endl;
list();
cout << endl;
}
bool RoundTable::equal()
{
for (int i = 1; i < num; i++)
{
if (children[i - 1] != children[i])
{
return false;
}
}
if (children[num - 1] != children[0])
{
return false;
}
return true;
}
int RoundTable::a_round()
{
pass();
give();
if (equal())
{
cout << "equal!" << endl;
list();
cout << endl;
return children[0];
}
else
{
return -1;
}
}
int main(void)
{
int limit = 20; //限定最大数量
RoundTable table(limit);
int res;
while ((res = table.a_round()) == -1)
continue;
cout << "all children's candies are: " << res << endl;
system("pause");
return 0;
}
上课管理后台
TODO [图片]
之前写过一个Java的股票管理系统,还用到了简单的爬虫,但是我犯了一个致命的问题:数据库的频繁存取造成死锁。
关键是我对数据库还没啥理解,所以处理不了死锁。后面想到,其实在程序运行当中,可以不存入数据库,而是先保存在ArrayList类里,然后隔一段时间自动保存,再通过程序界面退出的检查来进行自动保存。这样就不会受到死锁影响。
那么对于这个c++的后台,我的第一反应不就是数据库嘛,然后第二反应是不能一上来就搞数据库,得先写容器结构。
容器结构
课程包含学生,系统包含课程,然后其信息与一个个实体绑定。这就是我的层次结构,容器也是这样设计的。
这里涉及到一个权限问题,我将查询权交给当前引用拥有者,但是修改权在当前类的容器手上。学生就是学生本身,只能看,不能改。课程对应老师的权利,可以改学生,但是不能改课。系统,对应学校的权利。
更多功能可以自己加
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<ctime>
using namespace std;
//上课管理:主要服务对象是老师
//有老师,课程,学生,课表,学期,成绩等内容,要用到数据库,sqlite
//数据库可能会涉及到关联,可以采取多表,老师表,学生表,课程表,时间
//具体功能:
// Table:所有课程(编号,老师,名字) (学期,开始时间,结束时间)
// 管理员或者老师可以在表中添加课程
// 老师通过select检索查看课程列表
// Table:课程编号(学号,学生名,成绩)
// 对具体课程可以查看学生名单,成绩,点名。
// 可以通过课程编号(表名)反向检索课程信息
// 学生可以查询成绩,同样是利用课程名Table
//使用string和vector类
class Student {
int id;
string name;
float score;
friend class Course;
//不设修改,仅仅被Course(容器)修改
public:
Student(int idd, const string& namee, float scoree = 0) :id(idd), name(namee), score(scoree)
{
cout << "Student(): " << name << endl;
}
//输出
friend ostream&
operator<<(ostream& os, const Student& student)
{
os << "学号:" << student.id << " 姓名:"
<< student.name << " 分数:" << student.score;
return os;
}
//查询
int getId() const
{
return id;
}
string getName() const
{
return name;
}
float getScore() const
{
return score;
}
};
class Course {
int id;
string name;
string teacher;
vector<Student> students;
//修改,仅能被System修改
friend class System;
public:
Course(int idd, const string& namee, const string& teacherr) :
id(idd), teacher(teacherr), name(namee)
{
cout << "Course(): " << name << endl;
}
//输出
friend ostream&
operator<<(ostream& os, const Course& course)
{
os << "编号:" << course.id << " 课程名:" << course.name
<< " 授课教师:" << course.teacher;
return os;
}
//查询学生
void list()
{
cout << "当前课程:" << name << endl;
if (students.size() == 0)
{
cout << "no class" << endl;
}
else
{
for (int i = 0; i < students.size(); i++)
{
cout << students[i] << endl;
}
}
}
//课堂功能
void add(const Student& student) //添加学生
{
students.push_back(student);
}
void check(int num)//点名num个
{
bool* uped = new bool[students.size()];
memset(uped, 0, sizeof(uped));
for (int i = 0; i < num; i++)
{
srand((unsigned int)(time(NULL)));
int index = rand() % students.size();//在学生范围里点
if (uped[index])//防止重复点
{
num--;
continue;
}
uped[index] = true;
cout << students[index].name<<" ,please stand up~" << endl;
}
delete[] uped;//清除
}
//选取学生,这个可以直接给学生,然后可以直接修改
Student& select_id(int id)
{
for (int i = 0; i < students.size(); i++)
{
if (students[i].id == id)
{
return students[i];//这里也是引用
}
}
}
Student& select_name(const string& name)
{
for (int i = 0; i < students.size(); i++)
{
if (students[i].name == name)
{
return students[i];//这里也是引用
}
}
}
void updateStudent(Student& old_s, const Student& new_s) //通过引用更新
{
old_s.id = new_s.id;
old_s.name = new_s.name;
old_s.score = new_s.score;
}
void updateStudent(int id, const Student& new_s) //修改学生的唯一途径,但是没办法进行
{
Student& old_s = select_id(id);
updateStudent(old_s, new_s);
}
void updateStudent(string name, const Student& new_s)
{
Student& old_s = select_name(name);
updateStudent(old_s, new_s);
}
};
class System {
vector<Course> courses;
public:
//查询课程
void list()
{
if (courses.size() == 0)
{
cout << "no class" << endl;
}
else
{
for (int i = 0; i < courses.size(); i++)
{
cout << courses[i] << endl;
}
}
}
//加课
void add(const Course& course)
{
courses.push_back(course);
}
//修改课程
Course& select_id(int id)
{
for (int i = 0; i < courses.size(); i++)
{
if (courses[i].id == id)
{
return courses[i];//这里也是引用
}
}
}
Course& select_name(string name)
{
for (int i = 0; i < courses.size(); i++)
{
if (courses[i].name == name)
{
return courses[i];//这里也是引用
}
}
}
void updateCourse(Course& old_c, const Course& new_c)
{
old_c.id = new_c.id;
old_c.name = new_c.name;
old_c.teacher = new_c.teacher;
}
void updateCourse(int id, const Course& new_s)
{
Course& old_s = select_id(id);
updateCourse(old_s, new_s);
}
void updateCourse(string name, const Course& new_s)
{
Course& old_s = select_name(name);
updateCourse(old_s, new_s);
}
};
int main(void)
{
System sys;
//加一门课
Course oop(1, "面向对象", "cyy");
sys.add(oop);
sys.list();
cout << endl;
//改课程
//无法通过oop直接修改
Course new_oop(1, "面向对象", "刘来旸");
sys.updateCourse(1, new_oop);
sys.list();
cout << endl;
//加两个学生
Student cyy(1120200944, "陈耀宇", 100);
Student zhangSan(1120200945, "张三");//默认分数
Course& oop_ref = sys.select_name("面向对象");
oop.add(cyy);
oop.add(zhangSan);
oop.list();
cout << endl;
//改学生
//无法通过student直接修改,需要通过Course
Student zhangSan_new(1120200945, "张三", 100);
oop.updateStudent(1120200945, zhangSan_new);
oop.list();
cout << endl;
//查学生,可以给学生一个Student引用
Student& query = oop.select_id(1120200944);
//无法通过这个接口修改信息,但是可以查询和输出
cout << query << endl;
cout << query.getId() << ' ' << query.getName() << ' ' << query.getScore() << endl;
system("pause");
return 0;
}
数据库存取
新手上路无非就是两个数据库:
- sqlite。轻量简单。
- MySQL。广为人知,且也不算大。
MySQL我自己还没学,再加上Java就用的sqlite,所以就直接用sqlite。
但是,c++配置sqlite的难度远超Java,官方文档的指导写的也不是太好,所以我找了一篇文章。
这篇文章不仅仅能让你学会c++sqlite配置,里面还包含了很多系统相关知识。
至于具体的系统怎么写,我就没继续写了,没时间了,无非就是写几种工具,我在写Java的时候就写过了:
- 建立连接与断开连接
- 将类insert/update到表里
- 把从表里select出来的句柄,转化为类
- 再加上多线程监控程序状态,来进行自动保存。
- 其他扩展功能。
时间管理工具类
TODO [图片]
时间类其实也挺简单。层次可以选择一个时间包含两个类,一为年月日的粗时间类,二为时分秒的细时间类。我选择直接统一。
功能无非就是:
- 新建时间类
- 类的一些基本修改,检查功能
- 求时间差
- 自动同步本地功能。这个要写底层的话,得找到系统内部的时间,偷懒一点就把ctime库的函数套进来。
- 基于基础功能的复杂功能。
MyTime.h
#include<cstdio>
#include<iostream>
#include<ctime>
#ifndef MYTIME_H
#define MYTIME_H
//MyTime
/*
1. 储存年月日-时分秒,初始化时确保闰年。
2. 获取当前日期并自动填充(系统ctime)
3. 修改当前日期,单独修改某一项,导出日和,秒和
4. 计算日期差operator-生成新的MyTime
*/
class MyTime {
int year, month, day;
int hour, minute, second;
const int* data;//根据是否闰年指向两个数组中一个
static const int normal_data[]; //定义编译期间常量
static const int moon_data[];
void check_time()//检查日期越界
{
if (month < 1 || month>12)
{
month = 1;
}
if (day<1 || day>data[month - 1])
{
day = 1;
}
if (hour < 0 || hour>24)
{
hour = 0;
}
if (minute < 0 || minute>60)
{
minute = 0;
}
if (second < 0 || second>60)
{
second = 0;
}
}
public:
//初始化+自动检查正确性
MyTime(int y = 0, int mon = 0, int d = 0, int h = 0, int min = 0, int s = 0) :
year(y), month(mon), day(d), hour(h), minute(min), second(s)
{
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) //判断闰年
{
data = moon_data;
}
else
{
data = normal_data;
}
//修正时间,越界则归零
check_time();
}
//读取
int getYear()
{
return year;
}
int getMonth()
{
return month;
}
int getDay()
{
return day;
}
int getHour()
{
return hour;
}
int getMinute()
{
return minute;
}
int getSecond()
{
return second;
}
int computeDays() //不算年
{
int days = 0;
for (int i = 0; i < month-1 ; i++) //月
{
days += data[i];
}
days += day; //加上天
return days;
}
int computeSeconds()
{
int seconds = 0;
seconds += hour * 3600;
seconds += minute * 60;
seconds += second;
return seconds;
}
//修改,加入检查机制以及闰年转换
void setYear(int ny)
{
year = ny;
check_time();
}
void setMonth(int nm)
{
month = nm;
check_time();
}
void setDay(int nd)
{
day = nd;
check_time();
}
void setHour(int nh)
{
hour = nh;
check_time();
}
void setMinute(int nm)
{
minute = nm;
check_time();
}
void setSecond(int ns)
{
second = ns;
check_time();
}
//同步系统
//操作符重载:输出标准日期格式 2022-04-23T07:08:33
friend std::ostream&
operator <<(std::ostream& os, MyTime time)
{
os << time.year << '-';
if (time.month < 10)
{
os << '0';
}
os << time.month << '-';
if (time.day < 10)
{
os << '0';
}
os << time.day << 'T';
if (time.hour < 10)
{
os << '0';
}
os << time.hour << ':';
if (time.minute < 10)
{
os << '0';
}
os << time.minute << ':';
if (time.second < 10)
{
os << '0';
}
os << time.second;
return os;
}
friend std::ostream& //输出地址
operator <<(std::ostream& os, MyTime* time)
{
return os << *time;//重用的艺术
}
friend MyTime
operator-(const MyTime& lv, const MyTime& rv)
{
return MyTime(
lv.year - rv.year, lv.month - rv.month, lv.day - rv.day,
lv.hour - rv.hour, lv.minute - rv.minute, lv.second - rv.second
);
}
MyTime //指针相减
operator-(const MyTime* rv)
{
return MyTime(
year - rv->year, month - rv->month, day - rv->day,
hour - rv->hour, minute - rv->minute, second - rv->second
);
}
int get_month_2() //查看2月时间
{
return data[1];
}
};
const int MyTime::normal_data[] = { 31, 28, 31 ,30, 31, 30, 31, 31, 30, 31, 30, 31 };
const int MyTime::moon_data[] = { 31, 29, 31 ,30, 31, 30, 31, 31, 30, 31, 30, 31 };
#endif //MYTIME_H//
MyTime_test.cpp
#include<cstdio>
#include<iostream>
#include<cstdlib> // 系统库,System调用相当于cmd
#include<string> //c++级别字符串
#include<vector> //c++级别数组
#include<queue> //队列
#include<fstream> //文件读写
#include<iomanip> //输出控制
#include"MyTime.h"
using namespace std;
int main(void)
{
MyTime* a = new MyTime(2022, 1, 1, 0, 0, 0);
MyTime* b = new MyTime(2022, 2, 2, 1, 1, 1);
cout << a << endl;
std::cout << "2月天数: " << a->get_month_2() << std::endl;
cout << "days: " << a->computeDays() << " seconds: " << a->computeSeconds() << endl;
cout << "作差计算" << endl;
MyTime c = *b - *a;
cout << c << endl;
cout << "days: "<<c.computeDays() << " seconds: " << c.computeSeconds() << endl;
system("pause");
return 0;
}
西南财经大学数学建模校赛
试题
2022年西南财经大学数学建模竞赛赛题
航空管理问题
航空公司的运营管理非常复杂,航空管理问题是运筹学应用的典范之一。
航空管理问题的描述包含许多概念,包括:
时间:航空公司的运营是跨时空的。本题时间的表达由时分组成,例如:落地“0055”代表“夜间24点55分”。所有时间均指定北京时间。
机场:航班的起飞和到达,以机场为节点。机场的标识一般按照国际民航组织IATA标准的三字码。比如“PEK”为北京首都机场,对应城市为北京。
航线:飞机飞行的路线称为空中交通线,简称航线。
航班:指飞机的一次起飞和降落,是航线的一次执行。航班号可能按天或星期重复,当航班应用于机组排班时也叫航段,每执行一次航班使用一架客机,本题不区分客机类型。1个航班号有两次飞行,代表此航班为经停航班。往返的连续航班号可简写,比如“AB0001/2”代表航班AB0001从广州至北京、航班AB0002从北京至广州。
乘机旅行线路:直达或转机的整个路线,简称“线路”。例如,从北海至广州的乘机旅行线路,可以采用北海至南昌的航班,在南昌转机南昌至北京的航班,在北京换乘北京至广州的航班,描述该线路为:北海-南昌-北京-广州,该线路的旅行时间为10小时15分。
注:如果概念定义和过程描述与业界有出入,皆以本赛题为准。凡是本赛题没有提及,可不在考虑之列。
附件文件包含北方航空公司的2022年春季的4组航空数据,分别对应4个问题。若乘坐北方航空公司飞机,请你们根据这些数据分别对乘机线路进行分析:
2、无线路重复的日航班计划
本题采用“问题2航班数据”。需转机的旅客必须提前到达换乘机场候机,本问中,换乘机场候机需提前1小时,从一城市到另一城市乘机线路指采用最短时间的线路。请确定:
(1)6小时之内可到达的线路有多少条?
(2)转机次数最多的线路中转机几次?有多少条?
(3)时间最长的线路为从哪个城市至哪个城市?并描述该线路及给出线路旅行时间。
朋友事情比较多,他自己搞不完了,并且打算通宵,所以我虽然也有个互联网+压着,但是还是拯救一下朋友的肝吧。这题其实挺简单的,校赛看来都不难,尤其对于我来说,这就是一道算法题罢了。具体步骤:
- 首先定下思路,因为要最优解,所以暴力搜索,bfs或者dfs(最后选定了dfs,因为要保存所有路径,bfs比较麻烦)。
- 语言选择。c语言没跑了,暴力搜索python我没试过,但是怎么也得一两分钟一次,如果数据量再大点我就不敢想了。而且数据也不难,所以用python将excel转化为csv格式(c语言难以处理excel格式),并稍微调整一下表头以及一点不太好操作的数据,变成舒服的格式。
- 之后就是写程序了,c语言编程采取c++ STL技术+面向对象设计,写的比较舒服,几百行代码也是很快就敲出来了,一边敲一边测试,保证代码无误后开始跑结果,结果也非常舒服。
项目文件
- python用来转化格式
- c++用来跑结果
链接
提取码:1pil
收获
- 主要是复习了算法,说来也有趣,当年学的时候挺折磨的,自己多练几次以后,也能自己写出来了,说明计算机编程本身没有难度,什么专业都可以学,也都得学,难度在于背后的知识体系。
- 其次就是复习了面向对象,不过没有用虚函数多态继承,也就是简单用了封装,运算符重载,以及c++的文件读写。