Effective c++(笔记)----类与函数之实现

上篇博客中集中说明了在设计一个类的时候常遇到的问题,当然博客中还夹杂着我随时想到的一些知识,发现自己写博客没很多人写的好,可能是自己语言不会组织,要么就是写的东西大家不愿意看,反正是有这方面的专业问题或者博客中有什么明显的错误和问题,大家提出来,我也好改进哈!

回归正题,这篇博客就大概的把Effective c++中类与函数这节看到的知识点做个笔记。

设计好一个类后,自己就要去实现这个类(实现类中的成员函数、友元、非成员函数等)

可能大家会遇到以下问题

1.在类的成员函数中,尽量避免返回内部数据的handles

答:起初看到这句话感觉就不是很懂,什么叫内部数据的handles,其实说白了,就是尽量在成员函数中避免返回类的指针或者引用成员。

为什么这么说呢?

原因----当成员函数返回类的指针成员或者引用成员时,可能会破坏类的抽象性,或者破环const 成员函数的const性,特别是当涉及暂时对象(个人理解就是临时对象),可能会造成悬浮指针也就是指针指向了已经释放的内存空间-------野指针

感觉上面的话好抽象,于是就拿例子来详细说明了

class String{
public:
	String(const char *value);
	~String();
	operator char * () const;
private:
	char *data;
};
String::operator char * () const
{
	return data;
}

上面的const成员函数可是类型转换运算符,它没有返回类型,没有参数。

顺便插句:c++中没有返回类型的只有构造函数、析构函数和类型转换运算符函数

这个函数实现的功能是可以将String类型转换为char*类型的

如果定义了一个常量的String对象B,如下所示

const String B = "Hello World";
char *str = B; //调用了B.operator char * ()
strcpy(str , "Hi Mom");

当调用了String类中的类型转换符,此时指针str也指向了对象B中指针data成员指向的内存空间,这样由str直接就可以改变指针data成员指向的值,但是这就违背了对象B为const的性质,所以当函数中返回类的指针成员时,对于const的成员函数会破坏对象的const特性,那么该怎么处理呢?一种比较快比较安全的方法就是针对const对象和非const对象分别写一个成员函数。如下所示

class String{
public:
	String(const char *value);
	~String();
	operator const char * () const;
	operator char * ();
private:
	char *data;
};
String::operator const char * () const
{
	return data;
}
String::operator char * ()
{
	return data;
}

这样的话const对象调用对象的const成员函数且同时返回是const char*类型,这就使其不能通过str改变其指针指向的值(这里的const是指指针指向的值为const----上篇博客详细讨论过这个问题)

对于引用也是一样的 , 如下例所示,对const和非const的分别处理

class String{
public:
	String(const char *value);
	~String();
	operator const char * () const;
	operator char * ();
	//取元素运算符重载
	const char& operator[](int index) const;
	char& operator[](int index);
private:
	char *data;
};

const char& String::operator[](int index) const
{
	return data[index];
}
char& String::operator[](int index)
{
	return data[index];
}

当成员函数非得返回类的指针成员和引用成员时,例如上述返回char&,对于内置类型不能返回char,只能返回char&,因为对于返回值是内置型的函数,修改返回值是绝对不合法的!应该对non-const和const版本的分别处理,除此之外尽量不要返回内部数据的handles。

另外是当涉及到临时对象时会造成野指针的情况,如下例所示,对上面的类再添加一个非const的成员函数

String someFamousAuthor()
{
	if(...)
	{
		return "A";
	}else if(...)
	{
		return "B";
	}else{
		return "C";
	}
}

当同样有指针指向该函数时,如

const char *pc = someFamousAuthor();

函数最后返回String对象,当该函数执行完其对象会调用析构函数,这个对象的指针成员data所指向的内存会被释放,而此时指针pc仍然指向那块内存区域,这就是指针指向了已经释放的内存区域,也就是悬浮指针的情况,------野指针

2.在c++中定义变量时,尽可能的在能给予初值时才定义,这是为什么呢?

答:如果你写的代码较多,我相信你对这样的情况深有体会,常常会有自己的变量没有用到而受到编译器的警告,这还不算什么,最可恨的是,编译器没错误,但是执行每次的结果都不同,一直查bug,很长时间过去了,才知道原来是自己定义的变量没有初始化就开始使用了,这。。。。没办法再往下说了,一定要谨记定义变量时就要给予初值,养成这个好习惯,肯定不会出错。

当然,我们也想知道里面的原因,当定义变量没有使用编译器常常会有警告,此时,你就是定义了变量分配了内存,却没使用,你这不是糟蹋内存么?!同时你这样干也影响了你程序的效率,变量的定义除了分配了对应的内存,还要调用默认构造函数,当程序结束时调用析构函数,这都浪费时间影响程序的效率。

结论-----尽量延缓变量的定义,当你真正需要用它并且在定义的时候能够给予初值时再去定义它。

3.在函数内千万不要返回局部对象的引用和返回堆空间中的对象(也就是new产生的对象)

答:这个应该很容易理解,局部对象的作用域在局部,当函数执行完后,局部对象会随之析构,如果此时你返回了局部对象的引用,引用就是别名,看到引用就要看它绑定的对象,此时局部对象已经析构,那么此时引用没有绑定的对象。上篇博客中好像也说了,对于函数返回值是对象object时,尽量不要返回对象的引用形式,因为那样效率还没有直接返回对象的高!对于内置型返回引用的效率可能还比较高。

当然,如果返回堆空间中的对象,也就是以new获得的指针所指的对象,返回这个对象时,很容易造成内存泄漏,调用这个函数时,往往就应该想也是必须想的是,调用该函数的对象应该负责删除new的内存,如果考虑不周,很容易造成内存泄漏,不要去尝试造成内存泄漏的任何情况。

4.什么时候应该在函数的前面加上inline关键字?

答:inline关键字是声明该函数为内联函数,当调用内联函数时,内联函数的代码在调用处展开,通常是函数代码量比较少的函数,但是好像现在还是不知道怎么使用,以及使用的标准。

inline函数背后的含义是对此函数的每一个调用动作都以函数代码取代之。虽然免除了调用函数的成本,但是这很明显会增加目标代码的大小,当内联函数的代码量较大时,很容易造成程序代码膨胀现象,更糟的可能产生换页行为,使程序的大部分时间都浪费在了换页上面。所以对于代码较多的函数是不合适进行内联化的。

如果你在函数前面加上了inline关键字,这并不表示,编译器一定会将此函数内联化,有可能没有内联成功也就是out of inline ,那么此时对于这个未内联成功的函数,当调用时编译器会按照普通函数调用之。

在旧规则下,对未内联成功的函数而言,当调用它时每个编译单元都会产生这个函数的静态副本,如果这个函数中有自己定义的静态局部变量,同时也会产生这个副本,也就是说在旧规则下,对于未内联成功的函数会当成static函数,甚至是当定义了指向该函数的函数指针时,也就是对这个函数取地址,此时对于每个取地址的编译单元也还是会产生这个函数的静态副本,相反,在新规则下,不论牵扯到的编译单元有多少个,只有一个未内联成功的函数副本产生出来。

对于构造函数和析构函数往往不是内联函数的最佳选择,也许你认为构造函数中什么代码也没有,正好合适内联化,但是你只看到了表面没有看到内在的运行机制,对于构造函数,它会初始化类中所有数据成员 ,如果初始化成员时的构造函数也是内联函数的话,那个该构造函数会包含好几个内联函数,再且,当这个类是派生类的时候,它不仅需要构造自己的数据成员,首先它会构造自己直接基类的数据成员,这些都是需要执行的,所以对于构造函数和析构函数而言是不适合作为内联函数的。

对于函数,在开始的时候尽量不要定义为内联,当自己确定找到了那些占重要效率地位的函数,且代码量确实很少,立即将其内联化会有助于提高自己的程序效率。

5.怎么将文件之间的编译依赖关系降至最低来提高程序重编译的效率呢?

答:首先说为什么这么干,因为如果文件之间的关系太紧密,如这个类中包含了好几个类的.h文件,那么就会导致我们改变任何一个.h文件的任何一个小的地方,就会导致整个程序重新编译,那么在编译过程就会浪费很长的时间。

其实这些都是可以避免的,有两种方法可以达到这种效果降低文件之间的依赖关系。

首先,尽量以class声明取代class定义,成为Handle class的做法

另外,使用抽象类的做法

说实话,这两种方法有些没看懂,可能是自己的基础不好,还需要再研究一下,等什么时候透彻了,将对这部分的理解和例子添加进来哈!

Effective c++(笔记)----类与函数之实现

时间: 2024-10-09 17:55:07

Effective c++(笔记)----类与函数之实现的相关文章

Effective c++(笔记) 之 类与函数的设计声明中常遇到的问题

1.当我们开始去敲代码的时候,想过这个问题么?怎么去设计一个类? 或者对于程序员来说,写代码真的就如同搬砖一样,每天都干的事情,但是我们是否曾想过,在c++的代码中怎么样去设计一个类?我觉得这个问题可比我们"搬砖"重要的多,大家说不是么? 这个答案在本博客中会细细道来,当我们设计一个类时,其实会出现很多问题,例如:我们是否应该在类中编写copy constructor 和assignment运算符(这个上篇博客中已说明),另外,我们是让编写的函数成为类的成员函数还是友元还是非成员函数,

[Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+1}" 反射获取函数源代码的功能很强大,使用函数对象的toString方法有严重的局限性.toString方法的局限性ECMAScript标准对函数对象的toString方法的返回结果(即该字符串)并没有任何要求.这意味着不同的js引擎将产生不同的字符串,甚至产生的字符串与该函数并不相关. 如果函数

初探swift语言的学习笔记(类对象,函数)

swift扩展了很多功能和属性,有些也比较奇P.只有慢慢学习,通过经验慢慢总结了. 下面将初步学习一下类的写法. 码工,最大爱好就是看码,而不是文字,太枯燥. // // computer.swift // swiftDemo // // Created by apple on 14-6-8. // Copyright (c) 2014年 fengsh. All rights reserved. /* 写本例子的目的在于快速学习swift类的写法,包括知识点: 1.属性设置 2.构造.释构 3.

Effective C++笔记:构造/析构/赋值运算

条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函数时,编译器会给我们构造默认的. 当成员变量里有const对象或引用类型时,编译器会不能合成默认的拷贝赋值函数:当一个基类把它的拷贝赋值函数定义为private时,它的派生类也不无生成默认的拷贝赋值函数,因为它无法完成基类成份的赋值. 条款06:若不想使用编译器自动生成的函数,就该明确拒绝 将拷贝构

Effective C++笔记06:继承与面向对象设计

关于OOP 博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处! 1,继承可以是单一继承或多重继承,每一个继承连接可以是public.protected或private,也可以是virtual或non-virtual. 2,成员函数的各个选项:virtual或non-virtual或pure-virtual. 3,成员函数和其他语言特性的交互影响:缺省参数值与virtual函数有什么交互影响?继承如何影响C++的名称查找规则?设计选项有如些?如果class的行为

C++ 空类默认产生的类成员函数

C++的空类有哪些成员函数:. 缺省构造函数.. 缺省拷贝构造函数.. 缺省析构函数.. 缺省赋值运算符.. 缺省取址运算符.. 缺省取址运算符 const.     注意:有些书上只是简单的介绍了前四个函数(宝典4th p112).没有提及后面这两个函数.但后面这两个函数也是空类的默认函数(Ref:<effective c++>).另外需要注意的是,只有当实际使用这些函数的时候,编译器才会去定义它们. //C++ 空类默认产生的类成员函数: //缺省构造函数,拷贝构造函数,析构函数,赋值运算

Effective c++(笔记)之继承关系与面向对象设计

1.公有继承(public inheritance) 意味着"是一种"(isa)的关系 解析:一定要深刻理解这句话的含义,不要认为这大家都知道,本来我也这样认为,当我看完这章后,就不这样认为了. 公有继承可以这样理解,如果令class D以public 的形式继承了class B ,那么可以这样认为,每一个类型为D的对象同时也可以认为是类型为B的对象,但反过来是不成立的,对象D是更特殊化更具体的的概念,而B是更一般化的概念,每一件事情只要能够施行于基类对象身上,就一定可以应用于派生类对

Effective C++笔记05:实现

条款26:尽可能延后变量定义式的出现时间 博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处! 有些对象,你可能过早的定义它,而在代码执行的过程中发生了导常,造成了开始定义的对象并没有被使用,而付出了构造成本来析构成本. 所以我们应该在定义对象时,尽可能的延后,甚至直到非得使用该变量前一刻为止,应该尝试延后这份定义直到能够给它初值实参为止. 这样做的好处是:不仅可以避免构造(析构)非必要对象,还可以避免无意义的default构造行为. 遇到循环怎么办?此时往往我

java effective 读书笔记

java effective 读书笔记 [1]创建和销毁对象 1 静态工厂方法 就是“封装了底层 暴露出一个访问接口 ” 门面模式 2 多参数时 用构建器,就是用个内部类 再让内部类提供构造好的对象 3 枚举 singleton 不知道怎么操作,觉得意义不大 单例模式 4 私有化构造器不能实例化,也不可被子类继承 5 能用原生类的就尽量不用对象 [2]对于所有对象都通用的方法 reflexivity 自反性 symmetry 对称性 [3]类成员 降低可访问性 尽量把公有域 变成私有域,并提供