(整理说明:本资料是我在网上无意间找到的,读起来感觉不错,但由于原文是每章一个网页的格式,读起来不是很习惯,而且也不方便保存,所以我花了2个多小时的时间将所有网页的内容综合整理了一下,但最后才发现,文章的顺序颠倒了,所以各位如果愿意阅读本文的话,请从后面向前读,每个红色“标题”代表一章,如有不便还请各位见谅,或到原文网站阅览)
标题:重载函数再论
重载函数是C++提出来的概念,但是在C中却未必没有。比如“1+3”和“1.0+3.0”,虽然都是加法,做的却不是同的操作:编译器要因操作数的不同而调用不同的加法操作。只是C语言中除了内部类型变量可以参与运算以外,没有“类”这么高深的概念。“结构体”也只是内存数据的组织方法,而不涉及对整个结构体的处理。所以,在C语言时代编译器明明做了类似于重载的事情,却可以像雷锋一样“做好事不留名”。
C++发展出了类,并且赋予了“类”很高的期望,类的对象也能像内置类型对象一样参与一切运算。那么,就拿加法运算来说,编译器如何知道对某类对象的加法该调用哪一个详细的操作代码?于是,即使不出现普通函数的重载,至少运算符是要重载的。
林锐博士在《高质量C++/C编程指南》中为重载函数的必要性提了另一个理由:类的构造函数名称必须与类名相同,而类却经常要定义多个不同的构造函数。那就只好重载了。
对于普通程序员来说,我们完全可以不用考虑得这么深。重载函数给我们至少还带来了另一个好处:不用记忆多个不同的函数名了,也不用为了给函数起名而绞尽脑汁了。不过本书还给出了一个建议:并不是任何时候都有必要重载函数的,有的时候不同的函数名可以直观地带来好多信息,滥用重载只是牺牲了名称中的信息。
标题::重载函数的概念
引用:出现在相同作用域中的两个(可以是两个以上——偷猫注)函数,如果具有相同的名字而形参表不同,则称为重载函数。
本节开头第一句话就给出了重载函数的定义:重载函数必须符合两个条件:一是出现在相同的作用域中、二是函数名字相同而形参表不同。
其中第一个条件一般人往往是不去想的,其实函数名相同而作用域不同的函数大大存在,比如在MFC中就有。它们是完全不相干的函数。
第二个条件还可以详说一下:函数名字相同当然不在话下,这是函数被称为“重载”的根源。之于形参表不同,可能表现在形参个数不同、可能表现在形参类型不同、还可能表现在形参顺序不同。
如果要扩展开来说,还可以举出许多不是重载函数的情况。
一、如果既在同一作用域下、名称也相同、形参表也相同,则后者被视为前者的重复声明。——函数可以重复声明,因为函数的声明并不产生目标代码,但是函数的定义不允许重复出现。
二、如果既在同一作用域下、名称也相同、形参表也相同,但是返回值不同,则后者被视为错误的声明。函数不可以只凭返回值来区分,因为调用函数的时候只凭名称和形参来选择函数,而不凭返回值。再究其原因,一是因为函数的返回值可以被丢弃;二来即使不丢弃,将返回值赋予另一个变量之前没必要检查我需要什么样的返回值,而能否赋值也与函数本身无关。
三、有些时候看起来形参表不同,实际上是完全相同的,书本第229页讲了四组这样的例子:
Record lookup(const Account &acct);
Record lookup(const Account &);//区别在于有没有给形参命名
typedef Phone Telno;
Record lookup(const Phone&);
Record lookup(const Telno&);//只是给类型取了个别名
Record lookup(const Phone&, const Name&);
Record lookup(const Phone&, const Name& = "");//区别在于给形参提供了默认值
Record lookup(Phone);
Record lookup(const Phone);//区别在于是否const
其中第三组可能会让人产生函数的形参个数不同的假像,其实可缺省的形参并没有减少形参的个数。第四组有点不容易搞清:因为有的时候可以凭是否const来重载,比如引用传递和指针传递。
标题::文件的组织
一个程序往往由多个源文件组成,这些代码究竟应该放在哪个源文件里、哪些代码可以放在同一个源文件里、哪些代码必需分开放。这是一个管理层面的问题。
说它是管理层面的问题,是因为这些代码的组织往往没有惟一的准则。但是它们还是有一定的规律的。
首先,软件的维护是一个复杂的系统工程。代码的组织应该有利于维护。应该尽量把直接相关的内容放在同一文件、不相关的内容放在不同的文件里。如果这些代码还有亲和疏,那就要分不同的文件夹来存放了。
其次,软件的代码是一个严格的组织体系。不同的内容之间可能是并列的,也可能有必要的先后关系。于是在“#include”的时候要注意顺序。
最后,也是最重要的一点,有些代码在同一工程中可以重用(或必须重用),有些代码在同一个工程中只能出现一次。可以重用的有类的声明、函数的声明、变量的声明等,不可以重用的是类的实体、函数的实体、变量的定义等。那么,把可以重用的内容放在h文件中,把不可以重用的放在cpp文件中是一个好办法。
拿类的声明和类的实体为例,如果把一个类的所有内容一古脑放在同一个文件中,将可能出现问题。因为在其它用到类实例的地方都必须让类的声明“可见”,所以我们往往在文件头部加个“#include”,结果类的实体也被编译多次,在连接时产生冲突。
在前文中曾提到过,内联函数是惟一允许(也是必须)在编译时让函数实体可见的的函数。所以内联函数可以放在h文件中。C++规则中有一句正好与此照应:在类的声明中直接写出的函数被认为是内联函数。
Visual C++给类的文件起默认名时,文件名往往与类名一致。如果类名由“C”开头,则文件会是除去开头的“C”字以外的其它文字。如类“CMyClass”,它的代码存放在以下两个文件中:“MyClass.h”和“MyClass.cpp”中。原因是VC++建议类名以C开头,至于为什么在文件名中不出现开头的“C”,可能是出于微软的习惯吧。
标题::类的构造函数
引用:构造函数是特殊的成员函数。
笔记:构造函数的确是一类“特殊”的成员函数。它的特殊性至少表现在以下几个方面:一是它的调用不用程序员操心,只要类对象被创建它就会被调用,而且它不允许被程序员显式地调用。二是它们是必需的,如果程序员偷懒,编译器将自动创建简单的构造函数。三是它们的名字不用程序员多考虑,直接与类名相同。四是它们没有返回值。
下面详说这几个特性:
一、它们在类对象被创建时自动调用,创建对象可能有以下方法:程序中用声明变量的语句直接声明创建,或者在程序中用new关键字动态创建。这两种方法都可以创建单个对象,也都可以创建对象数组。只要有一个对象被创建,构造函数就被调用一次。
如果程序员想显式地调用构造函数那是不行的。正因为如此,构造函数中还有一种特定的部分叫“初始化列表”,通过它程序员可以调用基类或成员的构造函数。必竟类的设计千差万别,如果某个类的基类或(和)成员有多个构造函数,那么,该类必须能够指定用哪一个构造函数,否则类的功能将大打折扣。调用构造函数不是程序员的事,程序员不应该管也管不了。初始化列表为解决这个问题而生,所以只有构造函数才有初始化列表,其它函数不能有。
上面说到的“大打折扣”究竟是怎样的折扣呢?如果不能指定基类和成员用哪一个构造函数,那就只好让编译器去挑了,构造出来的对象往往不符合要求,只好调用基类和成员的其它函数,比如赋值函数或其它进行参数设定的函数——当然,基类和成员必须包含这样的函数。这样就浪费了资源。
二、类必须包含构造函数——确切地说是必须包含无参数构造函数和拷贝构造函数——原因是因为它们的调用是自动的。如果这两个函数根本就没有,你让系统如何调用?所以,C++也不含糊,你要是懒得写,它就帮你写一个简单的。简单就意味着至少要丧失一些功能,如果类设计得比较复杂(比如包含指针操作)还可能引起灾难性事故。
三、函数名与类名一致。构造函数的名称是必须特殊的,即使这个特殊不表现在与类名相同,也必须找到另一个规则来实现。因为系统要自动调用这些函数,你就必须让系统知道哪些函数是构造函数。
第四个特性直接改变了C/C++语言的一条规则:C语言规定,如果函数没有明显指出返回类型,那么C语言认为返回值是int型。C语言之所以可以有这条规则,一是因为返回int的函数很多,二是因为即使没有返回值,也必须指明void。当时制定规则的人无法预料到,C++中居然会出现“连void都不是的返回值”的函数,void虽然表示不返回任何值,必竟与类构造函数的“没有返回值”是两码事。于是,C++新标准规定:在定义或声明函数时,没有显式指定返回类型中不合法的。当然类的构造函数除外。
构造函数的出现有它的可行院捅厝恍浴?尚行允怯捎贑++的类允许包含成员函数,既然类可以包含普通的成员函数,那么包含特殊的函数自然也不在话下。必然性是由于类的对象往往必须经过特定的初始化。C++到来之前,C语言中的数据类型只是内置类型。对于内置类型对象,如果忘了初始化,大不了这个对象失去作用,但是不会导致大的问题。比如一个int型值,无论内存如何随机,它的取值范围都不会超过int能表达的范围,对它进行运算也不会产生危险(溢出不能算危险,即使初始化过的数据也不能保证不溢出,而且溢出只是一种逻辑问题)。但是现在的类不这么简单了,忘了初始化往往将带来运行错误。于其每次都要考虑数据的初始化,还不如把这个初始化写成统一的函数,让系统自动调用来得既安全又方便。
标题::类的成员函数
类与C语言中的结构体最大的区别就是类可以带函数,而结构体只是一个内存组合。所以,要提类就不得不提成员函数。
类的成员函数与普通函数(全局函数)相比,最根本的区别是实现了类的封装性。封装性的第一个表现是访问权限:都是函数,但是你能访问哪个不能访问哪个却可以设定。第二个表现是直观,通过类成员(或指针)来调用函数,给人的直觉就是“这是类提供的功能”。你好像“Bird.Fly();”一样一目了然。
在理解this指针以前要想彻底理解成员函数是有困难的,我就曾以为在类的实例中保存了函数的副本。要不然,为什么同一个类的不同对象调用这个函数有不同的效果呢?原来,在函数所有的形参之外,还有一个不用你操心的参数this,它是一个指针,该指针的目标就是函数的调用者。这么一说就明白了。
函数形参表后加入const就成了“const成员函数”,这样的函数保护了调用者自身不被修改。如CString的GetLength()函数,你只能获取它的长度,不能修改它的内容或长度。加入const的作用倒不是怕调用者修改,而是防止编写函数的人不小心改动了对象。因为百密总有一疏,万一在某个不该修改数据的函数中改变了数据(比如将“==”写成“=”),或者万一调用了另一个非const的成员函数都将可能引起错误。在编写函数前就先加上const可以记编译器来帮你检查。
这个const加在形参表的后面显得有些怪怪的,造成“怪怪的”原因就是因为函数的形参表中没有this,也就没有能用const来修饰的东西了。林锐说“大概是因为其它地方都已经被占用了”并不是根本原因。
标题::内联函数
内联函数应该是为了改善C语言中的宏替换的不足而产生的吧。因为宏替换是预编译中直接展开的,展开过程中将产生意想不到的结果。典型的有“#define MAX(a, b) (a) > (b) ? (a) : (b)”。“result = MAX(i, j)+2;”将被展开为“result = (i) > (j) ? (i) : (j) + 2;”。虽然外面再加一对括号可以解决以上问题,但是“result = MAX(i++, j);”被展开后将导
分享到:
相关推荐
C++Primer中文第三版(C++从入门到精通)第一章的读书笔记,主要是C++程序、预处理器指示符、iostream库等的基础知识点读书笔记。
C++Primer摘记,还不错.是第四版的
c++ primer 读书笔记,来自于网络,不可多得,分享给大家,同时感谢原作者的辛勤努力
C++ Primer读书笔记.经典教材的读书笔记,结合自己的所得体会一下吧
对C++primer 每章内容读完后的经典总结 很有好处。
C++primer的笔记,可以看一下,一些容易犯错的陷阱
牛人的c++primer学习笔记,c++基础学习必须看的,努力中。
经典之作c++ primer,笔记有助于学习总结
C++primerplus笔记.pdf
《C++ Primer 5th》 这本书的笔记上。C++ 在我多年以来断断续续学了好几遍,之前用《C++ 编程思想》也学过,但那本书过于陈旧了,用《C++ Primer 4th》也学过一段时间,由于书本过于沉溺细节,导致最终没法坚持下来...
C++ Primer学习笔记 内容很详细
C++ primer plus 第五版的个人学习笔记,仅供大家学习参考。
被业界奉为百科全书式的C++著作C++primer的读书笔记,与他人交流住进学习!
自己写的C++primer笔记,比较简略。只有前面十章。
C++primer笔记C++primer笔记C++primer笔记C++primer笔记C++primer笔记
C++ primer学习笔记,看对家有用木........
适合新手。适合想学习c++但是基础又不是特别好的...本人计算机专业,找工作的时候自己看C++primer做的笔记,全程较为详细,每次复习前面一天的笔记,再看后面的书。效果较好,上传上来给打击做个参考。希望能帮到大家。
不错的笔记,里面有初学者笔记。C++ Primer学习笔记.doc
C++ primer plus学习笔记之三,分为一下几个部分: 函数参数:介绍了函数的生命规则以及定义 数组函数:数组作为变量时的使用方法 指针和const:灵活运用指针和const 函数和二维数组:二维数组作为变量时声明以及定义...
学习C++ Primer 3rd 时做的一些笔记,相信对初学者学习 C++ Primer 很有帮助 !