effective c++:引用传递与值传递,成员函数与非成员函数
以pass-by-reference-to-const 替换pass-by-value
考虑以下class继承体系
class Person {
public:
Person(); // parameters omitted for simplicity
virtual ~Person(); // see Item 7 for why this is virtual
...
private:
std::string name;
std::string address;
};
class Student: public Person {
public:
Student(); // parameters again omitted
virtual ~Student();
...
private:
std::string schoolName;
std::string schoolAddress;
};
bool validateStudent(Student s); // function taking a Student
// by value
Student plato; // Plato studied under Socrates
bool platoIsOK = validateStudent(plato); // call the function
本次以by value方式传递一个Student对象会导致一次Student 构造函数、一次Person 构造函数、四次string构造函数,共六次构造函数。当销毁时,同样学要六次的析构函数,可见其效率是如此的低,而使用pass-by-reference-to-const可以有效地回避原本需要的构造,析构带来的性能损耗。
bool validateStudent(const Student& s);
这种方式来传递参数效率高很多,因为没有新的对象被创建,其中的const的作用是保证传入参数在执行时不被修改,而使用by value传递参数要达到这种效果是通过对实参做一个副本,然后在副本上做修改,效率上的优劣是显而易见的。
通过by reference 传递还可以避免对象被切割的问题,当一个派生类对象以By value 传递时被视为了一个基类对象,派生类特有的特性被舍弃掉了
class Window {
public:
...
std::string name() const; // return name of window
virtual void display() const; // draw window and contents
};
class WindowWithScrollBars: public Window {
public:
...
virtual void display() const;
};
void printNameAndDisplay(Window w) // incorrect! parameter
{ // may be sliced!
std::cout << w.name();
w.display();
}
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);
以上代码的本意是在printNameAndDisplay中传递一个wwsb的对象,调用派生类版本的display函数,但由于是值传递,代码执行时实际上运行的是基类的display,解决方法时改为引用传递避免对象被切割:
void printNameAndDisplay(const Window& w) // fine, parameter won’t
{ // be sliced
std::cout << w.name();
w.display();
}
pass-by-reference确实在很多时候扮演提高效率,减少错误的角色,但它也不是万能的,以下就举个有理数乘机的例子来说明
class Rational {
public:
Rational(int numerator = , // see Item 24 for why this
int denominator = ); // ctor isn’t declared explicit
...
private:
int n, d; // numerator and denominator
friend
const Rational // see Item 3 for why the
operator*(const Rational& lhs, // return type is const
const Rational& rhs);
};
这个例子中并没有使用引用类型返回是有道理的,引用只是个名称,它代表这一个已经存在的对象,如果我们在上面代码中把返回类型Rational改为Rational&,就相当于我在两个有理数乘积之前已经存在了一个两数相乘的结果,我返回的只是这个结果变量的一个别名,这听起来很傻比,但如果有些情况下返回引用,它的意思就是这样。所以在选择使用引用传递和值传递时要仔细考虑我们到底需要表达的是什么意思。
成员变量应声明为private
如果成员变量都声明为public,会造成每个人都可以读写它,而实际应用中我们并不希望对用户开放所有权限,所以将变量声明为private可以控制每个变量的读写权限,这样也提高了类的封装性
class AccessLevels {
public:
...
int getReadOnly() const { return readOnly; }
void setReadWrite(int value) { readWrite = value; }
int getReadWrite() const { return readWrite; }
void setWriteOnly(int value) { writeOnly = value; }
private:
int noAccess; // no access to this int
int readOnly; // read-only access to this int
int readWrite; // read-write access to this int
int writeOnly; // write-only access to this int
};
使用非成员函数替代成员函数
假设有个class来表示浏览器,它其中需要实现清除缓存,历史记录,cookies.
class WebBrowser {
public:
...
void clearCache();
void clearHistory();
void removeCookies();
...
};
用户需要整个执行所有动作,因此浏览器也提供这样的功能
class WebBrowser {
public:
...
void clearEverything(); // calls clearCache, clearHistory,
// and removeCookies
...
};
以上是成员函数的实现方案,非成员函数则可以写成这样。
void clearBrowser(WebBrowser& wb)
{
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
直觉告诉我们成员函数的实现更好些,但可惜的是这个直觉是错误的,面向对象要求数据尽可能的被封装,即越少的人可以看到内部数据,封装性越好,所以显然在这里我们选择非成员函数。
像WebBrowser这样的类所提供的功能肯定有很多,我们让整个核心机能组成一个类,而对于另外一些功能就没有必要放在同一个文件中,没有理由cookies管理与书签管理产生编译相依的关系
,分离他们的做法就是将各个辅助功能各自声明于不同的头文件,像stl那样<vector>,<algorithm>,<memory>只是属于同一个命名空间,我们可以根据需要来增加头文件,没有必要把整个stl增加进来。
// header “webbrowser.h” — header for class WebBrowser itself
// as well as “core” WebBrowser-related functionality
namespace WebBrowserStuff {
class WebBrowser { ... };
... // “core” related functionality, e.g.
// non-member functions almost
// all clients need
}
// header “webbrowserbookmarks.h”
namespace WebBrowserStuff {
... // bookmark-related convenience
} // functions
// header “webbrowsercookies.h”
namespace WebBrowserStuff {
... // cookie-related convenience
} // functions
...
非成员函数除了提高封装性外,当遇到参数需要隐式的类型转换,也需要使用非成员函数
class Rational {
public:
Rational(int numerator = , // ctor is deliberately not explicit;
int denominator = ); // allows implicit int-to-Rational
// conversions
int numerator() const; // accessors for numerator and
int denominator() const; // denominator — see Item22 const Rational operator*(const Rational& rhs) const;
private:
...
};
以上是个关于有理数操作类,处理两个有理数乘积,看上去设计很合理,但遇到下面代码时就出现了问题。
Rational oneEighth(, );
Rational oneHalf(, );
Rational result = oneHalf * oneEighth; // fine
result = result * oneEighth; // fine
result = oneHalf * ; // fine
result = * oneHalf; // error!
在执行result = oneHalf * 2;这条语句时,onehalf是一个有opeator*函数的对象,"2"则是作为参数传递,编译器直接理解为
const Rational temp(); // create a temporary
// Rational object from 2
result = oneHalf * temp; // same as oneHalf.operator*(temp);
即把‘2’隐式转换成了Rational对象,而当执行result = 2 * oneHalf;时出现错误是"2"不能隐式转换引起的,隐式转换产生的条件是参数必须列于参数列表中,这条规则导致了第二条语句执行错误而第一条语句正确。找到原因之后,我们需要做的是把参与乘法的两个数都作为参数放到参数列表中。
class Rational {
... // contains no operator*
};
const Rational operator*(const Rational& lhs, // now a non-member
const Rational& rhs) // function
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
Rational oneFourth(, );
Rational result;
result = oneFourth * ; // fine
result = * oneFourth; // hooray, it works!
effective c++:引用传递与值传递,成员函数与非成员函数的更多相关文章
- 理解Java中的引用传递和值传递
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...
- (转载)PHP数组传递是值传递而非引用传递
(转载)http://www.fengfly.com/plus/view-212127-1.html 在调用函数时通过将PHP数组作为实参赋给形参,在函数中修改,并不会影响到数组本身. 说明此过程中的 ...
- Java Object 引用传递和值传递
Java Object 引用传递和值传递 @author ixenos Java没有引用传递: 除了在将参数传递给方法(或函数)的时候是"值传递",传递对象引用的副本,在任何用&q ...
- (转载)理解Java中的引用传递和值传递
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天 ...
- java到底是引用传递还是值传递?
今天我们来讲讲一个在学习中容易误解的问题,面试中也偶尔问到,java方法调用时到底是值传递还是引用传递? 首先,请大家来做一个判断题,下面的3个问题是否描述正确 1. java基本数据类型传递是值传递 ...
- Java中的引用传递和值传递
Java中的引用传递和值传递 关于Java的引用传递和值传递,在听了老师讲解后,还是没有弄清楚是怎么一回事,于是查了资料,所以在这里与大家分享,有不对的地方,欢迎大家留言. java中是没有指针的,j ...
- Java中没有引用传递只有值传递(在函数中)
◆传参的问题 引用类型(在函数调用中)的传参问题,是一个相当扯的问题.有些书上说是传值,有些书上说是传引用.搞得Java程序员都快成神经分裂了.所以,我们最后来谈一下“引用类型参数传递”的问题. 如下 ...
- Java千百问_05面向对象(011)_引用传递和值传递有什么差别
点击进入_很多其它_Java千百问 1.什么是值传递 值传递,是将内存空间中某个存储单元中存放的值,传送给还有一个存储单元.(java中的存储单元并不是物理内存的地址,但具有相关性) 比如: //定义 ...
- java引用传递和值传递
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...
随机推荐
- javascript 阻止冒泡
JS 阻止冒泡 function stopBubble(e) { //如果提供了事件对象,则这是一个非IE浏览器 if(e && e.stopPropagation) { //因此它 ...
- 设置Windows Azure Linux虚拟机中的root账户
使用Windows Azure 创建好Linux虚拟机之后,如果你使用默认的用户密码登陆root是不行的,如下图所示: 其原因是Windows Azure创建Linux虚拟机时并没有同时设置root密 ...
- svn服务器及客户端安装使用
一.服务器安装: 1.yum install subversion 2.输入rpm -ql subversion查看安装位置,如下图: 我们知道svn在bin目录下生成了几个二进制文件. 输入 ...
- springmvc 中常用的注解配置使用说明
很久没有用springmvc了,今天复习了一下,然后记录一下总结. @Controller 使用 @Controller 注释对将成为 MVC 中控制器的类进行注释并处理 HTTP 请求. @ ...
- Android构建boot.img(一):root目录与ramdisk.img的生成
以TCC88XX为例,当在Android顶层源码目录使用make编译完成后,会生成这样一个目录: out/target/product/tcc8800,该目录内部有我们需要的boot.img和syst ...
- HDU 4644 BWT(Burrows–Wheeler transform+KMP)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4644 题意:给出一个串,按照下面的步骤得到一个新串: (1)首先将其后面增加一个美元符号: (2)将每 ...
- [51NOD1181]质数中的质数(质数筛法)(欧拉筛)
题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1181 思路:欧拉筛出所有素数和一个数的判定,找到大于n的最小质 ...
- JS生成指定长度的随机数
/** * 生成指定长度的UUID * @param len * @param radix * @returns uuid * eg: createUUID(8, 2) "01001010& ...
- 单点登录系统构建之二——SSO原理及CAS架构
基本概念 SSO(Single Sign On)单点登录,是在多个应用系统中,用户只需要登录一次就能访问所有相互信任的应用系统.它包括将这次的主要登录映射到其他应用中用户同一个用户的登录机制. SSO ...
- 64位Ubuntu 13.04 安装Bochs 2.3.5
bochs 2.3.5源码编译 网上编译bochs的资料非常多,基本的问题都有解决方案,我重点讲不常见的问题. 基本安装步骤 tar vxzf bochs-2.3.5.tar.gz cd bochs- ...