文章目录

  • 书评
  • 阅读方法
  • 代码实战
    • 复现
      • Stash&Stack 1.0 ——简单的结构
      • 访问控制——嵌套友元与迭代器初步
      • Stash&Stack 2.0——添加访问控制
      • 句柄类——封装的封装
      • Stash&Stack 3.0 ——添加构造函数和析构函数
      • SuperVar——Union的重载
      • MyString 1.0——逐字节控制内存工具的应用以及开销的思考
      • StringStack&Quoter——const 综合应用
      • Comm——晦涩的volatile
      • Stash&Stack 4.0——内联函数减少小函数调用开销
        • 补充:内联理解
        • 补充:宏的骚操作
      • 命名空间
      • 引用与拷贝构造函数
        • 引用
        • 默认拷贝构造函数
        • 拷贝构造函数
        • 成员指针
      • 操作符重载
        • 基本应用
        • operator->/operator->* 与迭代器嵌入
        • operator= 与赋值拷贝
      • PStash——new和delete的动态内存管理
        • new/delete概览
        • delete和delete[]的本质
        • PStash
      • 继承与组合
        • 基本继承机制
        • 名字隐藏——背后还是继承
        • 非继承函数
        • StringStack——继承与组合应用
      • 多态
        • 晚捆绑与虚机制
        • 纯虚函数与抽象类
        • 对象切片
        • 虚函数和构造/析构函数
      • 模板
        • 基本概念
        • TPStash&AutoCounter——模板初体验&自动计数测试类
        • 指针指向空间的所有权控制与改进
        • Stach&Stack 5.0——模板+容器迭代器写法
          • Stack数组写法:
          • Stack链表写法:
          • PStash终极版本
        • 多态与函数模板,泛型实践
    • 自己写
      • MyVector
        • head.h
        • main.cpp
      • 分糖果问题——类对现实世界的模拟
      • 上课管理后台
        • 容器结构
        • 数据库存取
      • 时间管理工具类
        • MyTime.h
        • MyTime_test.cpp
      • 西南财经大学数学建模校赛
        • 试题
        • 项目文件
        • 收获

书评

这本书确实不适合新手上路,尤其是没学过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;//下一个的indexunsigned 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 arraystringStash.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;//绑定Holderp = 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;//下一个的indexunsigned 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 arraystringStash.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后缀声明floatSuperVar 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_Hclass 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]同时指向stringstack[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_Hint 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); //不行,不是volatilereturn 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 arraystringStash.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;
}

补充:宏的骚操作

  1. 字符串定义
  2. 字符串转换——用于字符串拼接
  3. 标志符粘贴
#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函数,符合假定,也就是按位拷贝。

但是这样也会带来一个问题,就是不适合处理复杂的类,比如带指针的。你光是复制了指针,不复制人家指向的对象,就不行,所以得自己编写拷贝构造函数来实现复杂的操作。

拷贝构造函数

  1. 目的。定义按值传递时的行为
  2. 写法。同构造函数,只不过参数只有一个,class &,有时候会是const class &,其实个人感觉const更好,因为拷贝构造一般不会修改原有对象的。
  3. 为什么是传引用呢?一方面比较有效率,另一方面,拷贝构造函数就是为了定义类按值传递时的行为,不用引用的话,你还没定义呢,你就按值传递了。
  4. 初始化列表。不是构造函数就是拷贝构造函数,说白了,构造和拷贝构造是同级的,只不过场景不同。
  5. 特殊应用:私有拷贝构造。这个可以阻止按值传递。

成员指针

本质:定义的时候表示一种相对于基地址的偏移,针对一个类,而不针对某个对象

所以没有精确指向某个对象的成员的指针,只有指向一个类中成员的指针,然后配合对象或者对象指针来使用。

#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类

传入参数:

  1. 成员函数不需要传自己,并且可以使用this进行对自己的引用和操作。
  2. this返回当前对象指针,*this返回当前对象(外面一般用引用承接)
  3. 一般都是const &的,因为基本都是仅仅用值

关于返回值:

返回值取决于我要返回什么,我要对我的返回值做什么。

  1. 不修改返回值,且返回值为原对象。const class & 理由:返回原有对象,引用高效,const防止修改。
  2. 返回值作为临时量。const class 理由:一般用于返回新对象。不能传引用,因为临时量生存期很短。
  3. 修改返回值。class & 理由:返回原有对象继续修改。

注意const成员函数:

  1. 重载中大量使用const 返回值,所以其成员函数,能const尽量const,不然操作不了这个返回的临时量。

是否全局:

  1. 遵循直观原则,一元就成员,二元就全局。
  2. 特殊的,比如= () [] -> ->*,这种具有赋值和选择含义的,因为总是伴随对象,所以只能作为成员。

索引符:

  1. 必须是成员函数且只能接受一个索引参数
  2. 很常用。

#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;}//全局函数,需要传入全部参数,给个friendfriend const Integer& //传入a引用,返回a,所以全用const引用效率最高,能用const&最好operator + (const Integer& a);friend const Integer //二元写成全局更直观operator+(const Integer& left, const Integer& right);//成员函数,自己就不用传入了,不用friendconst 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->* 与迭代器嵌入

这个也只能是成员函数,这种形式,就意味着有类似于指针的行为,被叫做灵巧指针,常用于迭代器。

有两个规定:

  1. 必须返回一个对象,其也可以进行间接引用,这样保证了链式调用。
  2. 或者返回最终指向的类型,这就标志着到达终点。
#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 {//一般是嵌入,也可以选择全局+friendpublic: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= 与赋值拷贝

=号的行为有两种

  1. 初始化一个对象:将取右边的参数(只能有一个),去调用对应的C函数或者CC函数。
  2. 赋值一个已经初始化的对象:调用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;}

常用的三种用法

  1. 复杂带指针对象的赋值。这个要求把指针指向的东西进行递归复制与赋值
  2. 引用计数:优化operator=和CC函数的行为。赋值和拷贝构造时不新建空间,而是指向同一个空间,当计数为0,就销毁(有java那味儿了)。要修改对象时,如果有多个引用,那么就执行写拷贝,复制一份新的再写。
  3. 通过CC函数和operator=实现类之间的自动类型转换。

PStash——new和delete的动态内存管理

new/delete概览

相当于加强版的malloc类/free类函数,将这些步骤封装起来。而且,可以重载,但是重载往往是用来优化加速的,一般使用默认的即可。

new和delete让动态内存管理更加方便,从此数组是路人,二级指针闯天下。

new:

  1. 开辟内存,一般是调用malloc
  2. 调用构造函数
  3. 检查内存分配

delete:

  1. 调用析构函数。如果析构函数里有别的delete,可以实现递归调用,清楚所有相关对象
  2. 释放内存,一般是调用free
  3. 建议在delete p后加上p=NULL补刀,防止二次delete同一片区域
  4. 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_Hclass 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继承的理解没那么深,对比理解可能还不如直接去理解。

  1. 继承是将基类当做子成员。
  2. 继承不会真的覆盖。基类的base::i和子类的i都存在,不是覆盖,只不过是形式上的覆盖,实际上你可以通过base::i来调用基类i。函数继承同成员继承,都存在。
  3. 继承的本质。就是从子类向基类找声明的过程,如果子类有i,那么就不需要用基类的i,如果子类有print(),就不需要使用基类的print(),这就是继承所谓的“覆盖”效应。实际上是没有覆盖的。
  4. 基类中的访问修饰。相当于子类对基类权限的门。public代表任意访问,protected只允许子类访问,private不允许包括子类在内的访问。
  5. 继承时的访问修饰。相当于快捷设定基类在子类中的访问修饰。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;
}

名字隐藏——背后还是继承

基类中重载的函数会被隐藏。隐藏条件:

  1. 子类重写一个同名同型函数。
  2. 子类增加一个同名不同型函数。

本质上,就是继承中层次的选择,和我们前面的继承原则一模一样,这就是基于继承机制衍生出来的一种现象罢了。

  1. 如果子类中没有该名字,那么就跳到上一层去寻找
  2. 如果子类中有该名字,就停留在这一层。
  3. 层与层之间被隔开,强制进入上一层需要使用
    基类::标志符 的方法。
#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以后,他自己以及所有派生类的同名函数,都会支持多态,也就是即使进行向上类型转换也可以执行我们前面说的继承机制。

虚机制:

  1. VPTR是一个指向VTABLE的指针,VTABLE里存放类中虚函数的地址(注意,有地址代表虚函数不可以内联),VPTR和VTABLE就相当于记录了类型信息。
  2. 具体执行时,编译器自动插入获取VPTR以及在VTABLE中查询函数地址的代码,无需用户操心。
  3. 优化。如果编译器直到一个对象确切的类型,即不使用多态(没有进行向上类型转换)时调用虚函数,会执行早捆绑。

继承时的虚机制:

  1. 新建VTABLE,首先将VTABLE继承过去
  2. 然后选择性覆盖(只要重名都覆盖),和添加(不重名)

纯虚函数与抽象类

  1. 当基类的某个虚函数仅仅是想成为一个接口,约定,那么就声明纯虚,这样就不需要去实现了,仅仅定义即可。一旦声明了纯虚,这个类也就变成了抽象基类。
  2. 纯虚声明也限制了对这个函数的调用,甚至是这个基类中其他函数的调用,原因见1。要想解决这个问题,可以编写纯虚定义,为任何的派生类都制造同样的默认代码。(我觉得可以覆盖,但是我没有做实验)
  3. 纯虚强制一系列派生类去实现,否则就一直延续下去。
  4. 纯虚函数不允许传值,只能进指针和引用
  5. 当类中全是纯虚函数,这个类的意义仅为约定接口,叫做纯抽象类,在Java中有接口类与之对应。
virtual void pure_virtual()=0;

对象切片

  1. 函数参数类型的本质是限定内存大小。比如int就4字节,某一个class都有固定的大小。
  2. 如果传入多态指针,因为指针的大小都一样,所以无所谓
  3. 但是按值传递会强行将子类切片成为基类,包括他的VTABLE等都被切割,成为一个新的基类对象。

因此,尽量按引用和指针传递,防止按值传递的对象切片问题。

虚函数和构造/析构函数

编译器将初始化VPTR相关代码插入构造函数开头,会隐形的增加函数长度。

虚函数的本质是让基类形式的调用,实现对派生类的调用。

构造函数非虚:

  1. 构造函数一定是先基类再派生类
  2. 如果使用虚函数,会引发基类调用派生类的情况,然而派生类还没初始化呢
  3. 说白了就是构造函数方向和虚函数方向相反

析构函数 总应该 虚:

  1. 析构函数的方向和虚函数方向相同,因此可以直接找到末端派生类开始执行析构。
  2. 否则会出现仅仅析构基类,但是不析构派生部分的问题。

模板

基本概念

模板真的好用。写起来也很简单:

  1. 在每一个用到模板的部分前加上模板声明,比如类前面,以及非内联函数前面。
  2. 常量可以写在模板声明里
  3. 非内联函数的类名访问作用域写class<T>:: 因为实际上生成的具体的类就是class<T>
  4. 模板可以将非内联函数都放在头文件中,似乎违背了头文件不放地址相关代码的原则,但是实际上编译器不对模板分配内存,而且会自动优化重定义问题。所以模板大可放心地全部放在头文件。
  5. 可以使用自己的一个测试类(比如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 申请storagedelete[] 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_Hclass 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——模板+容器迭代器写法

其实刚开始听到容器和迭代器,对于新手是懵逼的,为什么要搞那么复杂?说白了就是为了使用的安全和方便,而牺牲一点简单度和速度。

容器:

  1. 比如Stash里装了一个数组,Stash就是容器,很直观。
  2. 容器的作用就是将实际储存和用户隔开一层。
  3. 容器可以提供operator[]之类的访问,但是这样的访问还是比较单一。

迭代器:

  1. 迭代器常常是灵巧指针,目标是模仿指针行为,不论指向哪种容器,接口都是一样的,这样我们就可以编写更加一般的代码,搭配函数模板对不同的容器进行操纵,而且不同的容器还可以多态装元素,这也和现在的弱类型有关系。
  2. 简单的灵巧指针仅仅含有一个容器引用和当前位置,占用空间很小。同时具有丰富的对容器访问的方法,以及更安全的检查。
  3. 迭代器通常内嵌
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];}//定义iteratorclass 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);}
};#endifint 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>::iteratorsb = 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_Htemplate<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; //必须inlineT* 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);//ccnew_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_Htemplate<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; //必须inlineT* 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);//ccnew_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://构造类 //beginiterator(MyVector& vector_r) :vector(vector_r), index(0){//begin}//enditerator(MyVector& vector_r,bool) :vector(vector_r), index(vector.next){//end}//copy constructoriterator(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测试inflatefor (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 [图片]

有的问题没有数学规律和公式,所以就直接暴力模拟就好。我们采用逐层设计的方法:

  1. 玩游戏之前先分糖果。
  2. 一轮一轮玩游戏,直到全部相等。
  3. 一轮游戏中,先进行传递,再补发糖果,最后判断是否相等。
  4. 具体设计请参见函数

其实用面向过程也可以写的很短,但是思路并不清晰,而且比较费脑子,用类比较简单粗暴。非常直观。

#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();}//传给0children[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直接修改,需要通过CourseStudent 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;
}

数据库存取

新手上路无非就是两个数据库:

  1. sqlite。轻量简单。
  2. MySQL。广为人知,且也不算大。

MySQL我自己还没学,再加上Java就用的sqlite,所以就直接用sqlite。

但是,c++配置sqlite的难度远超Java,官方文档的指导写的也不是太好,所以我找了一篇文章。

vs2019配置C++的Sqlite

这篇文章不仅仅能让你学会c++sqlite配置,里面还包含了很多系统相关知识。

至于具体的系统怎么写,我就没继续写了,没时间了,无非就是写几种工具,我在写Java的时候就写过了:

  1. 建立连接与断开连接
  2. 将类insert/update到表里
  3. 把从表里select出来的句柄,转化为类
  4. 再加上多线程监控程序状态,来进行自动保存。
  5. 其他扩展功能。

时间管理工具类

TODO [图片]

时间类其实也挺简单。层次可以选择一个时间包含两个类,一为年月日的粗时间类,二为时分秒的细时间类。我选择直接统一。

功能无非就是:

  1. 新建时间类
  2. 类的一些基本修改,检查功能
  3. 求时间差
  4. 自动同步本地功能。这个要写底层的话,得找到系统内部的时间,偷懒一点就把ctime库的函数套进来。
  5. 基于基础功能的复杂功能。

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:33friend 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)时间最长的线路为从哪个城市至哪个城市?并描述该线路及给出线路旅行时间。


朋友事情比较多,他自己搞不完了,并且打算通宵,所以我虽然也有个互联网+压着,但是还是拯救一下朋友的肝吧。这题其实挺简单的,校赛看来都不难,尤其对于我来说,这就是一道算法题罢了。具体步骤:

  1. 首先定下思路,因为要最优解,所以暴力搜索,bfs或者dfs(最后选定了dfs,因为要保存所有路径,bfs比较麻烦)。
  2. 语言选择。c语言没跑了,暴力搜索python我没试过,但是怎么也得一两分钟一次,如果数据量再大点我就不敢想了。而且数据也不难,所以用python将excel转化为csv格式(c语言难以处理excel格式),并稍微调整一下表头以及一点不太好操作的数据,变成舒服的格式。
  3. 之后就是写程序了,c语言编程采取c++ STL技术+面向对象设计,写的比较舒服,几百行代码也是很快就敲出来了,一边敲一边测试,保证代码无误后开始跑结果,结果也非常舒服。

项目文件

  1. python用来转化格式
  2. c++用来跑结果

链接
提取码:1pil

收获

  1. 主要是复习了算法,说来也有趣,当年学的时候挺折磨的,自己多练几次以后,也能自己写出来了,说明计算机编程本身没有难度,什么专业都可以学,也都得学,难度在于背后的知识体系。
  2. 其次就是复习了面向对象,不过没有用虚函数多态继承,也就是简单用了封装,运算符重载,以及c++的文件读写。

面向对象知识 |《C++编程思想》(《Thinking In Cpp》)阅读感受相关推荐

  1. python递归 及 面向对象初识及编程思想

    递归 及 面向对象初识及编程思想 一.递归 1.定义: 在函数内部,可以调用其他函数.如果一个函数在内部调用自身本身,这个函数就是递归函数. (1)递归就是在过程或函数里调用自身: (2)在使用递归策 ...

  2. python中的递归思想_〖Python〗-- 递归、面向对象初识及编程思想

    [递归.面向对象初识及编程思想] 一.递归 1.定义: 在函数内部,可以调用其他函数.如果一个函数在内部调用自身本身,这个函数就是递归函数. (1)递归就是在过程或函数里调用自身: (2)在使用递归策 ...

  3. 学习Java第一天:1、Java是什么?2、面向对象的编程思想的特点 3、Java的开发工具 4、安装JDK和配置环境变量 5、Java程序的运行过程 6、Java语言的特点

    目录 1.Java是什么? 2.面向对象的编程思想的特点 3.Java的开发工具 4.安装JDK和配置环境变量 5.Java程序的运行过程 6.Java语言的特点 1.Java是什么? java是一门 ...

  4. 面向过程与面向对象——编程思想的演变

    编程语言从无到有,据维基百科统计,至今为止有600多种.如此众多语言,需要逐个学习吗?实际上编程语言都是相通的,编程语言只需要精通一门即可,其他都能融会贯通.比如Java与C++就差不多,Java和C ...

  5. python 清空所有对象_Python编程思想(7):列表的增删改操作

    李宁老师已经在「极客起源」 微信公众号推出<Python编程思想>电子书,囊括了Python的核心技术,以及Python的主要函数库的使用方法.读者可以在「极客起源」 公众号中输入 160 ...

  6. 好书推荐---单片机编程魔法师之高级裸编程思想

    <单片机编程魔法师之高级裸编程思想>以单片机裸环境为基础,为编程者定义了一个微操作系统(MOS)的编程环境,并面向应用中不断提高的需求对编程策略进行了深度剖析与研究,从而分离出数据驱动.并 ...

  7. 面向对象编程思想及入门知识

    这几天在调程序,所以想写写自己对"面向对象编程"的一些理解,希望对打算入门计算机编程的同志们有所帮助.之前,好几个师弟问过我,C++与C有什么区别,学习面向对象语言需要掌握哪些基础 ...

  8. 200819C阶段一C++面向对象的编程思想

    目录 一.学习的知识点 作业 面向对象的编程思想 类与对象 类 二.上课没有听懂或者没有理解的地方 三.当天学习的收获 四.作业的思路.不会的地方 五.其他需要反馈的问题 六.心得体会 一.学习的知识 ...

  9. Android知识架构 · Java的编程思想

    1.面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面.抽象只关注对象有哪些属性和行为,并不关注这些 ...

  10. 通俗理解面向对象和面向过程+编程思想

    上午单位的C++大佬给我们分享了一些编程思想,开始介绍了面向对象和面向过程 面向过程是什么,就是对事件的过程进行编程,我们已知事件的发展过程,我们根据过程发展的节点去编程.整个编程是过程驱动,开始,然 ...

最新文章

  1. python2.7和3.5相互兼容吗_以与Python 2.7和Python 3.5兼容的方式使用abc.ABCMeta
  2. 2019.4.26学习笔记(路由器router)
  3. DS18B20 理解与操作源码
  4. ==和equals()的区别
  5. 用python语言编斐波那契数列_用python函数写斐波那契数列
  6. 关于用Delphi开发的一些基本的套路
  7. QT的QDrag类的使用
  8. AIoT原生技术带来更好的应用开发
  9. DEV C++如何不需要通过建项目可以调试程序
  10. Dapper的基本使用
  11. SpringCloud Sentinel 使用restTemplate的两种配置介绍
  12. 使用游标正确提取SQL Server数据并将其放置在Reporting Services矩阵中
  13. NYOJ113 - 字符串替换
  14. Mentor软件盗版
  15. Linux修改SSH端口号
  16. 互联网晚报 | 06月14日 星期二 | 罗永浩称苹果有些产品明显退步;​旷视首席科学家孙剑博士去世;吉利拟收购魅族...
  17. mac电脑视频去水印
  18. RPG游戏制作-04-接入脚本前的准备
  19. HarmonyOS系统架构
  20. hmcl离线登陆_hmcl启动器下载

热门文章

  1. 华为android最新版本下载地址,华为Android手机驱动
  2. 模拟、数字基带/频带通信系统:编码、信源/信道编码、调制、码间串扰
  3. Phonegap 之 iOS银联在线支付(js调用ios端银联支付控件)
  4. python中的系统模块_python中与系统发育相关的模块
  5. 2021国赛数学建模赛题与分析
  6. 平面设计图文混排要怎么做
  7. 机器学习之BP算法推导
  8. 粒子滤波matlab示例,[转载]粒子滤波Matlab示例
  9. 使用Java的JNI调用C
  10. CheckboxPreference 改造