默认的构造拷贝函数是浅拷贝
A a1;
A a2(a1); //a1 与a2 之间还存在资源的共享 这里不是值语义
一个类实现了深拷贝之后才算是值语义
string a("abc");
string b(a); //这里是深拷贝
对象语义对象生命不容易控制,(容易产生资源泄漏,内存泄漏问题)
基于对象编程 值语义
值语义对象通常以类对象的方式来使用。
面向对象编程 对象语义
对象语义对象通常以指针或者引用方式来使用。
常用的智能指针:
auto_ptr 所有权独占,不能共享,但是可以转移。
shared_ptr 所有权共享,内部维护了一个引用计数,+1 或-1, 变为0 的时候,资源释放。
weak_ptr 弱指针,它通常与shared_ptr 配合使用,解决循环引用的问题。
scoped_ptr 与auto_ptr 类似, 所有权独占,不能共享,但是不可以转移。
这些智能指针释放对象。
Node n1;
Node n2(n1);
浅拷贝
当n1 销毁的时候,会释放它的子代。
当n2 销毁的时候,会释放它的子代。
一个对象可能被销毁两次,出现运行时错误。
怎么区分某个对象是值语义对象,还是对象语义对象。
值语义的析构是确定的。
计算机模型中,那写类是值语义对象,那些是对象语义对象?
当前的Node 并没有实现拷贝构造函数。默认情况应该是浅拷贝的,不是值语义。
但是实际上使用是以对象语义来使用的。(用到一些指针)
从语法形式来说,是值语义的,可以被拷贝。但是这时,并没有脱离关系,
Node n1;
Node n2(n1);
这里内部实现的是浅拷贝,并没有将子代的资源拷贝过来,仅仅是指针的指向关系。这样当n1 对象销毁的时候,会释放他的子代,当n2 对象销毁的时候,也会释放他的子代,导致一个对象被销毁两次,出现运行时错误。
解决方案 深拷贝 或者 是 Node 类定义成值语义,(这里深拷贝是没有意义的,就好像是一个人,张三 拷贝 成张三2,没有任何意义,不可能存在两个一样的人)
将node 转换成对象语义:
两种方法:1,对象拷贝禁止。那么这个对象就是对象语义了。2,拷贝之后,依然共享底层资源,对任何一个改变都将改变另一个。
一个对象禁止拷贝,只需要将拷贝构造函数设置为私有的。并且不给出实现,= 赋值运算符设置为私有的,不给出实现
#ifndef _NODE_H_
#define _NODE_H_
class Noncopyable{
protected:
Noncopyable(){};
~Noncopyable(){};
private:
Noncopyable(const Noncopyable&);
const Noncopyable& operator=(const Noncopyable&);
};
//把Node 类变为对象语义 private 继承因为,因为Noncopyable 没有任何接口,是实现继承
class Node:private Noncopyable
{
public:
virtual double Calc() const=0;//纯虚函数
virtual ~Node(){};
};
class NumberNode:public Node
{
public:
double Calc() const;
NumberNode(double number):number_(number){};
private:
const double number_;//一经过初始化就不会改变
};
class BinaryNode:public Node
{
public:
BinaryNode(Node* left,Node* right):left_(left),right_(right)
{
}
~BinaryNode();
//这里没有实现Calc 方法意味着还是抽象类 不懂如何计算
protected:
Node* const left_;//一经初始化也不会改变 指针不能改变
Node* const right_;
};
class UnaryNode:public Node
{
public:
UnaryNode(Node* child):child_(child)
{
}
~UnaryNode();
protected:
Node* const child_;
};
class AddNode:public BinaryNode
{
AddNode(Node* left,Node* right):BinaryNode(left,right)
{
}
double Calc() const;
};
class SubNode:public BinaryNode
{
SubNode(Node* left,Node* right):BinaryNode(left,right)
{
}
double Calc() const;
};
class MultiplyNode:public BinaryNode
{
MultiplyNode(Node* left,Node* right):BinaryNode(left,right)
{
}
double Calc() const;
};
class DivideNode:public BinaryNode
{
DivideNode(Node* left,Node* right):BinaryNode(left,right)
{
}
double Calc() const;
};
class UMInusNode:public UnaryNode
{
UMInusNode(Node* child):UnaryNode(child)
{
}
double Calc() const;
};
#endif
表达式解析:
表达式解析至少两种方案:
1,逆波兰表示法 栈的方式
2,递归下降法, 编译原理 语法树 表达式树
Scanner 只负责扫描,并且登记当前状态
Parser 根据扫描结果,进行解析,递归下降解析,直到生成一棵树。
程序的大体框架:
main.cpp
#include <iostream>
#include <string>
#include "Scanner.h"
#include "Parser.h"
using namespace std;
int main(void)
{
do
{
cout<<"> ";
string buf;
getline(cin,buf);//输入一行表达式,放到buf 中。
cout<<buf<<endl;//输出
Scanner scanner(buf);//扫描表达式
Parser parser(scanner);
parser.Parse();//解析
parser.Calculate(); //解析完毕,计算
}while(1);
return 0;
}
Scanner.h
#ifndef _SCANNER_H_
#define _SCANNER_H_
#include <string>
using namespace std;
class Scanner
{
public:
Scanner(const string& buf);
private:
const string buf_;
};
#endif
#include "Scanner.h"
Scanner::Scanner(const string& buf):buf_(buf)
{
}
Parser.h
#ifndef _PARSER_H_
#define _PARSER_H_
class Scanner;
class Parser
{
//这里是引用的方式来使用Scanner 或者指针的方式,没有必要这么快就包含头文件,可以用前项声明。
//这样的话,头文件会比较小,cpp 中多次包含了,就会使得可执行文件增大
public:
Parser(Scanner& scanner);
void Parse();
double Calculate() const;
private:
Scanner& scanner_;
};
#endif
parser.cpp
#include "Parser.h"
#include "Scanner.h"
//引用在初始化列表中进行初始化
Parser::Parser(Scanner& scanner):scanner_(scanner)
{
}
void Parser::Parse()
{
}
//加const 和不加const 是可以构成重载的。
double Parser::Calculate() const
{
return 0.0;
}