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中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...
随机推荐
- PHP dirname() 返回路径中的目录部分basename() 函数返回路径中的文件名部分。
dirname (PHP 4, PHP 5) dirname — 返回路径中的目录部分说明string dirname ( string $path ) 给出一个包含有指向一个文件的全路径的字符串,本 ...
- 第一篇 ERP是什么?-从道的层面浅谈我的理解
世界上称为ERP软件的软件很多,国外的有SAP,ORACLE,国内的有金蝶,用友,浪潮.这些由不同的厂商开发制作的软件总有其软件适用的场合.这个场合就是企业,而且是市场经济中的企业.个人是不会购买ER ...
- 《OD学hive》第五周0723
https://cwiki.apache.org/confluence/display/Hive/LanguageManual 一.创建表 create table student(id int, n ...
- jQuery 清除div内容
$.ajax({ url: "SearchSN.aspx", data: "SN=" + $("#txtS ...
- bzoj4197
这题现场想的思路方向都是对的,但限于现场和实力因素没能A 很显然我们会想到质因数的选取 如果某个质数p被W选了,那G就不能选含有质因子p的数 因此我们不难想到状压质数的选取情况,令f[i][j]为w质 ...
- 在maven项目中使用mybatis-generator-maven-plugin生成mybatis代码
项目整体的目录结构如下: pom.xml如下: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=&q ...
- 查看mysql存储引擎
一般情况下,mysql会默认提供多种存储引擎,你可以通过下面的查看: 看你的mysql现在已提供什么存储引擎:mysql> show engines; 看你的mysql当前默认的存储引擎:mys ...
- EF5&MVC4 学习1、创建新的Contoso University Application,并创建Model Class 生成对应的database
参考:http://www.asp.net/mvc/tutorials/getting-started-with-ef-5-using-mvc-4/creating-an-entity-framewo ...
- init: sys_prop: permission denied uid:1003 name:service.bootanim.exit
/************************************************************************* * init: sys_prop: permiss ...
- Android Studio 学习 - AndroidManifest.xml文件学习
首先,今天发现了一个很牛逼的教程网站:慕课网(http://www.imooc.com/).有很多大牛发布的教学视频.值得收藏.学习. 今天主要参照陈启超老大的视频,学习了多个Activity之间的切 ...