C++面向对象笔记与练手代码 |《C++编程思想》(《Thinking In 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;//下一个的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;
}
补充:宏的骚操作
  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;
	}

	//全局函数,需要传入全部参数,给个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->* 与迭代器嵌入

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

有两个规定:

  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 {//一般是嵌入,也可以选择全局+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= 与赋值拷贝

=号的行为有两种

  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_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继承的理解没那么深,对比理解可能还不如直接去理解。

  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 申请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——模板+容器迭代器写法

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

容器:

  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];
	}

	//定义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 [图片]

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

  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();
	}
	//传给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;
}

数据库存取

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

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

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

  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++的文件读写。
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亦梦亦醒乐逍遥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值