《C++编程惯用法——高级程序员常用方法和技巧》——2.3 公用数据

简介:

本节书摘来自异步社区出版社《C++编程惯用法——高级程序员常用方法和技巧》一书中的第2章,第2.3节,作者: 【美】Robert B. Murray ,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.3 公用数据

假设有如下一个复数类:

class Complex {
public:
   double real;
   double imag;
   Complex (double r, double i) : real (r), imag(i) {}
   //其他的被忽略
};

这个类可能可以工作,但它的接口却有着重大的缺陷。问题出在那两个公用的数据成员:real和imag上面。这个接口保证Complex对象中的实部和虚部会以浮点数的形式存储在对象中。由于用户代码可以直接存取到公用的数据成员:

Complex c(3.0,4.0);
double d = c.real;
c.imag = 0.0;

所以一旦我们需要改变信息的存储格式或存储位置时,这样的改动会变得相当困难。

假设在后面的某个版本中,我们希望将Complex的实现(而不是接口)中的信息存储方式由笛卡儿坐标格式改为极坐标格式。(也许我们有着的某些算法在处理使用极坐标格式表示的数字时效果最好。)如果笛卡儿坐标是公用数据,我们就将陷入泥潭:我们将不得不改变所有的用户代码或者是在每个Complex对象中同时维持极坐标和笛卡儿坐标格式。

如果我们在一开始就避免使用公用数据,我们就不会碰到这样的问题了:

class Complex {
private:
   double real_d;
   double imag_d;
public:
   Complex(double r,double i):real_d(r),imag_d(i){}
   double real() const { return real_d; }
   duoble imag() const { return imag_d; }
   void real(double r) { real_d = r; }
   void imag(double i) { imag_d = i; }
 
   //此处忽略细节
};

一旦接口中指定可以通过Complex对象得到浮点型的实部和虚部之后,如何在对象中存储这些浮点数就变成了类的私有实现细节。用户代码要想取得这些数据,就必须调用类的成员函数,而不是直接去访问数据成员。

Complex c(3.0, 4.0);
double d = c.real();
c.imag (0.0);

现在对于用户来说,唯一的不同就是他们为了获取值将不得不多输入两个字符(即括号)。由于Complex::real是一个内嵌函数,所以这两种形式产生的代码应该是一致的——将取值操作封装在一个内嵌函数中不会带来任何效率上的损失。同样,我们也可以将修改值的操作封装到内嵌函数中去。

现在,我们的类中再也没有公用数据了,为了将信息以其他格式存储而修改类的实现也变得容易起来:

//文件Complex.h
class Complex {
private:
   double r;
   double theta;
public:
   Complex(double re, double im); //不再是内嵌函数了
   double real() const { return r*sin(theta); }
   double imag() const { return r*cos(theta); }
   void real(double); //不再是内嵌函数了
   void imag(double); //不再是内嵌函数了

   //此处忽略细节
};

//文件Complex.c
Complex::Complex(double re, double im)
   : r(sqrt( re*re + im*im )),
    theta (atan2 (im,re)){
}

  //"real(double)"和"imag(double)"留给读者作为练习

此时,用户代码必须重新编译,程序的性能也会有一定的影响,但程序的接口没有改变。原来可以正常工作的程序仍然可以正常工作。

我们也可以将实现改为把信息存储到其他的位置上去:

class Complex_rep {
private:
   friend class Complex;
   double real_d;
   double imag_d;
   Complex_rep(double r, double i): real_d(r),imag(i){}
};

class Complex {
private:
   Complex_rep*rep;
public:
   Complex(double r, double i)
   : rep(new Complex_rep(r,i)){}
   ~Complex() { delete rep;}
   //此处忽略细节
};

我们将在第3章中解释为什么这么做。

2.3.1 表示不变量(Representation invariant)

公用数据还会使得我们的类难以用来保证表示不变量。表示不变量是一个谓词,对于某个完全被构建好的对象,它都将得到真值。

例如,假设我们有一个用来表示有理数的类Rational,它里面有两个整型数(一个用于分子,一个用于分母):

class Rational {
private:
   int num_d;
   int denom_d,
public:
   Rational(int n, int d);
   int num()const { return num_d;}
   int denom()const { return denom_d;}
   voie num(int n) {num_d = n;}
   void denom(int);//改变分母
};

如果我们的类具有这么一个表示不变量(分母denom_d不可能为0),这将会使得我们的算法变得更简单,并且更高效。我们在每个可能改变分母的函数中都对新的分母进行检测,如果它是0的话,我们就向外报告一个错误[2]:

static void
check_zero(int d){
   if (d == 0) {
     cerr << "Zero denominator in Rational\n";
     abort();
   }
}

Rational::Rational(int n, int d)
: num_d(n),
  denom_d(d) {
   check_zero(d);
)

void
Rational::denom(int d){
   check_zero(d);
   denom_d = d;
}

有了这两个函数我们就可以保证不变量的成立,因此在Rational的其他成员函数中,我们并不需要担心出现分母为0的情况。如果我们将分母声明为一个公用数据成员,我们就无法来保证不变量的成立,用户代码也就可以在任意时刻将分母赋值为0。有关Rational的所有操作也都将不得不对这种可能性进行检测。

综上所述,我们得到如下的结论:避免公用数据成员的出现。公用成员将使得我们很难去更改类的实现(如改变信息的存储格式或者存储位置);它也无法保证表示不变量的长期有效性。

相关文章
|
8月前
|
存储 监控 算法
基于 C++ 哈希表算法实现局域网监控电脑屏幕的数据加速机制研究
企业网络安全与办公管理需求日益复杂的学术语境下,局域网监控电脑屏幕作为保障信息安全、规范员工操作的重要手段,已然成为网络安全领域的关键研究对象。其作用类似网络空间中的 “电子眼”,实时捕获每台电脑屏幕上的操作动态。然而,面对海量监控数据,实现高效数据存储与快速检索,已成为提升监控系统性能的核心挑战。本文聚焦于 C++ 语言中的哈希表算法,深入探究其如何成为局域网监控电脑屏幕数据处理的 “加速引擎”,并通过详尽的代码示例,展现其强大功能与应用价值。
189 2
|
9月前
|
存储 C++
UE5 C++:自定义Http节点获取Header数据
综上,通过为UE5创建一个自定义HTTP请求类并覆盖GetResult方法,就能成功地从HTTP响应的Header数据中提取信息。在项目中使用自定义类,不仅可以方便地访问响应头数据,也可随时使用这些信息。希望这种方法可以为你的开发过程带来便利和效益。
337 35
|
11月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
11月前
|
算法 Serverless 数据处理
从集思录可转债数据探秘:Python与C++实现的移动平均算法应用
本文探讨了如何利用移动平均算法分析集思录提供的可转债数据,帮助投资者把握价格趋势。通过Python和C++两种编程语言实现简单移动平均(SMA),展示了数据处理的具体方法。Python代码借助`pandas`库轻松计算5日SMA,而C++代码则通过高效的数据处理展示了SMA的计算过程。集思录平台提供了详尽且及时的可转债数据,助力投资者结合算法与社区讨论,做出更明智的投资决策。掌握这些工具和技术,有助于在复杂多变的金融市场中挖掘更多价值。
404 12
|
11月前
|
存储 机器学习/深度学习 编译器
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
【C++终极篇】C++11:编程新纪元的神秘力量揭秘
|
11月前
|
存储 监控 算法
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
在数字化办公时代,公司监控上网软件成为企业管理网络资源和保障信息安全的关键工具。本文深入剖析C++中的链表数据结构及其在该软件中的应用。链表通过节点存储网络访问记录,具备高效插入、删除操作及节省内存的优势,助力企业实时追踪员工上网行为,提升运营效率并降低安全风险。示例代码展示了如何用C++实现链表记录上网行为,并模拟发送至服务器。链表为公司监控上网软件提供了灵活高效的数据管理方式,但实际开发还需考虑安全性、隐私保护等多方面因素。
219 0
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
|
12月前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
272 5
|
11月前
|
存储 算法 C++
深入浅出 C++ STL:解锁高效编程的秘密武器
C++ 标准模板库(STL)是现代 C++ 的核心部分之一,为开发者提供了丰富的预定义数据结构和算法,极大地提升了编程效率和代码的可读性。理解和掌握 STL 对于 C++ 开发者来说至关重要。以下是对 STL 的详细介绍,涵盖其基础知识、发展历史、核心组件、重要性和学习方法。
|
11月前
|
存储 安全 算法
深入理解C++模板编程:从基础到进阶
在C++编程中,模板是实现泛型编程的关键工具。模板使得代码能够适用于不同的数据类型,极大地提升了代码复用性、灵活性和可维护性。本文将深入探讨模板编程的基础知识,包括函数模板和类模板的定义、使用、以及它们的实例化和匹配规则。
|
消息中间件 存储 安全