What does the compiler behind our programming?
Overloading in C++
- Override all of overloaded functions defined in base class.
- The following rules from Bjarne Stroustrup:
- Begin:
- Allowing overloading based on const was part of a general tightening up of the rules for const and a trend towards enforcing those rules. Note: only for reference or pointer.
- Experience showed that hierarchies established by public class derivations should be taken into account in function matching so that the conversion to the “most derived” class is chosen if there is a choice.
- A void* argument is chosen only if no other pointer argument match.
- void * is established as root of the tree of class conversion.
- (Order-dependent) The original C++ overloading mechanism resolved ambiguities by relying on the order of declaration. Declarations were tried in order and the first match win, only non-narrowing (through promoting or widening) conversion were accepted in a match.
- (Order-dependent) 1. Find best match. 2. if no best match, if only one match using conversion, OK; if more than one match using conversion , Ambiguous, compile-time error.
- C++ distinguishes 5 kinds of “matches”: 1 Match using no or only unavoidable conversions (for example, array name to pointer, function name to pointer to function, and T to const T); 2. Match using integral promotions; 3. Match using standard conversion( int to double, derived * to base *); 4. Match using user-defined conversions (both constructors and conversion operator); 5. Match using the ellipsis… in a function declaration.
- The idea is always to choose the best match, that is, the one highest on the list above. If there are two best matches, the call is ambiguous and thus a compile-time error.
- For class involving more than one argument, a function is chosen provided(if, on the condition of) it has a better match than every other function for at least one argument and at least as good a match as every other function for every argument.
- The Null Pointer: Note that the null pointer need not be represented by the same bit pattern as the integer 0.
- (void *) 0 is not a good choice for the null pointer in C++.
- char *p = (void *) o; /* legal C, illegal C++
- Conversion from normal pointer to void* is allowed candidate function when resolving overloading function.
- End
- In general, we shall have two member function to get value of given index.
- Empty& operator[ ] ( int ); //for ordinary object
- const Empty& operator[ ] (int) const; //for const object
- const 成员函数可以被相同参数表的非const 成员函数重载。
- 如果一个重载操作符是类成员,那么只有当跟它一起被使用的左操作数是该类的对象时,它才会被调用。如果该操作符的左操作数必须是其他的类型,那么重载操作符必须是名字空间成员。
- Sometimes, we only need only the one friendly non-member overloading operator, the first operand provide a constructor to convert some other type to its type. But define other overloading operator with special operand also is allowed, just up to concern the overhead of conversion through constructor.
- bool operator==( const String &, const String & ); string has a copy constructor: String ( const char*)
- bool operator==( const char *, const String & ); bool operator==( const String &, const char* );
- The operator for built-in type can not be changed.
- The priority of operator can not be changed if it is overloaded.
- 赋值,取地址,以及逗句操作符,对于类类型的操作数有预定义的意义,对于类操作数这些操作符也可以被重载,为使其他操作符在被应用到类类型的操作数上时也有意义,类的设计者必须显式地定义它。
- 把全局等于操作符声明为String 类的友元friend, 通过把函数或操作符声明为友元,一个类可以授予这个函数或操作符访问其非公有成员的权利。
- 如果一个函数操纵两个不同类类型的对象,而且该函数需要访问这两个类的非公有成员,则这个函数可以被声明为这两个类的友元,或者作为一个类的成员函数并声明为另一个类的友元。
- 指向const 的指针不能被赋给一个指向非const 的指针。
- 指向non-const 的指针能被赋给一个指向const 的指针。
- 赋值操作符的返同类型是String 类的一个引用, 为什么我们要将这个赋值操作符为声明返回一个引用呢, 因为对于内置类型赋值操作符可以被串联在一起. Consecutive assignment.
- Subscript operator, assignment operator, address operator, conversion operator,
- 重载的operator()必须被声明为成员函数, 它的参数表可以有任意数目的参数.
- CString::operator LPCTSTR() const;
- 我们也可以为类类型的对象重载成员访问操作符箭头, 它必须被定义为一个类的成员函数. 它的作用是赋予一个类类型与指针类似的行为. 它通常被用于一个代表智能指针smart pointer 的类类型, 也就是说, 一个类的行为很像内置的指针类型但是支持某些额外的功能.
- 解引用操作符* 和成员操作符箭头->
- Screen& operator*() { return *ptr; }
- Screen* operator->() { return ptr; }
- 重载的成员访问操作符箭头的返回类型必须是一个类类型的指针,或者是定义该成员访问操作符箭头的类的一个对象,如果返回类型是一个类类型的指针,则内置成员访问操作符箭头的语义被应用在返回值上。如果返回值是另外一个类的对象或引用,则递归应用该过程直到返回的是指针类型或语句错误。
- ( ++object ) = other; //return a reference after ++; (object++) = other, // just return a temporary object
- Screen& operator++(); // 前置操作符 shall return reference, but value many times
- Screen& operator- - ();
- Screen operator++(int); // 后置操作符 shall return value(before ++), but reference many times.
- Screen operator- - (int);
- 非虚函数是静态绑定的static binding / type-declared binding (in compiling time)(即使基类指针指向的是派生类对象),虚函数是动态绑定的。Dynamic binding(in run time).
- C++提供了一种机制, 通过它,每个类都可以定义一组可被应用在该类型对象上的转换 through operator type ().
- 转换函数conversion function 是一种特殊类型的类成员函数, 它定义了一个由用户定义的转换以便把一个类对象转换成某种其他的类型, 在类体中通过指定关键字operator, 并在其后加上转换的目标类型后我们就可以声明转换函数. operator type ().
- operator LPCTSTR() const;
- 转换函数必须是成员函数, 它的声明不能指定返回类型和参数表.
- 显式的强制类型转换会导致调用转换函数, 如果被转换值的类型是一个类类型, 它有个转换函数并且该转换函数的类型是强制转换所指定的类型, 则调用这个类的转换函数.
- 在一个类的构造函数中凡是只带一个参数的构造函数, 都定义了一组隐式转换把构造函数的参数类型转换为该类的类型. 只发生在值的转换上,not on reference or pointer.
- 我们可能决定构造函数Number(const SmallInt&), 必须只能被用来以SmallInt 型的值初始化Number 型的对象, 而在其他情况下编译器不会使用这个构造函数进行隐式类型转换.
- 为防止使用该构造函数进行隐式类型转换, 我们可以把它声明为显式的explicit.
- 精确匹配要好于标准转换.
- Conversion through copy constructor and operator type() will give rise to ambiguity.
- 重载解析和成员函数: 1 选择后选函数( concern const function only invoked on const object), 2 选择可行函数(including static function, or functions that can call through implicitly type conversion), 3 选择最佳匹配函数.
- 最佳可行函数, 与参数完全匹配.
- 为同一个类类型, 既提供转换函数copy constructor来实现到内置类型之间的隐式转换, 又提供重载的操作符可能会导致在重载操作符和内置操作符之间的二义性.
- 这就是为什么我们要在设计类的接口时, 以及为特殊的类类型声明重载操作符, 构造函数和转换函数时必须小心谨慎的原因, 用户定义的转换被编译器隐式地应用可能使得当一个操作符与类类型的操作数一起被使用时, 内置操作符变成可行函数, 因此, 我们应该明智地使用转换函数和非显式的构造函数, 即没有被声明为explicit 的构造函数.
- 最佳函数有多个则表示存在二义性,要么完全匹配,要么都是通过转换得来的最佳函数.
- 如同在名字空间域中声明的函数一样, 在类中声明的成员函数可以有相同的名字, 只要参数表惟一, 或者在参数的数目上不同, 或者在参数的类型上不同, 如果以同一名字声明的两个成员数只有返回类型不同, 则第二个声明被视为错误的声明被标记为编译时刻错误.
- 重载函数集中的所有函数都在同一个域中被声明, 因此, 成员函数不会重载在名字空间域中声明的函数. 又因为每个类都维护了自己的域, 所以两个不同类的成员的函数也不会相互重载.
- 重载成员函数集可以包含静态和非静态成员函数.
- 为了匹配两个可行成员函数的参数而被应用在实参上的转换都是标准转换, 所以该函数调用是二义的, 因为, 对于函数调用中指定的实参两个成员函数一样好,如果通过隐式转换有多个函数可调用,则这个调用是ambiguous.
- 在选择最可行成员函数时, 在每个实参上的类型转换被划分等级, 最佳可行成员函数是这样的:可行成员函数其应用在实参上的转换不比调用任何其他可行函数所需的转换更差, 而且在某些实参上的转换要比调用其他可行函数在相同实参上所需的转换更好.
- 静态成员函数和非静态成员函数都可以被包含在可行函数集中, even through class:: staticFunction().
- 即使成员函数mf()通过类名和域解析操作符myClass()::mf() 来调用, 或者mf()没有通过对象或指向对象的指针和类成员访问操作符点. 或箭头-> 来调用, 非静态成员函数mf(char)仍然被包含在该调用的可行函数集中和静态成员函数mf(int)一样.
- 如果被选中的最佳可行函数是非静态成员函数, 并且该调用不能真正发生, 则会因为该调用没有指定对象像本例这种情况而使该调用被编译器标记为错误.
- 当选择可行函数集时必须考虑成员函数的另一个方面那就是非静态成员函数的const或volatile 属性.
- 当通过点或箭头成员访问操作符调用成员函数时, 在选择可行函数集中的函数时需要考虑在调用成员函数时所使用的对象或指针的类型.
- 对于const 对象只有const 非静态成员函数才可以被调用, 因为不能调用非const 非静态成员函数mf(double), 所以它被排除在可行函数集之外, 该调用的惟一可行函数是const 成员函数mf(int) const 它被选为该调用的最佳可行函数.
- 所以静态成员函数从不会因为被调对象或指针的限定修饰符const 或volatile 而被排除在可行函数集之外, 静态成员函数被认为匹配它所在类类型的任何对象或指针.
- 如果写类D时重新定义了从类B继承而来的非虚函数mf,D的对象就可能表现出精神分裂症般的异常行为。也就是说,D的对象在mf被调用时,行为有可能象B,也有可能象D,决定因素和对象本身没有一点关系,而是取决于指向它的指针所声明的类型。引用也会和指针一样表现出这样的异常行为。Up to / due to / rest with / lie to / depend on the static type of pointer or reference.
- Override。如果派生类重新声明了基类的非或者虚函数,基类的所有重载函数(overloaded functions)奖被覆盖(overriding)。
- 如果两个函数的参数表中参数的个数或类型不同则认为这两个函数是重载的 overload, have nothing to do with return type. Leave nothing to concern.
- Overload involve: return type(ignore), type of parameter(care), number of parameter(care), const of parameter(care only for reference or pointer), const for member function ( care because of have the this pointer as the first parameter), default value (ignore), typedef ( care after replaced), throw(throw statement must be same), inheritance in parameter type.
- 如果两个函数的返回类型和参数表精确匹配则第二个声明被视为第一个的重复声明. 参数表的比较过程与参数名无关.
- 如果两个函数的参数表相同但是返回类型不同则第一个声明被视为第一个的错误重复声明会被标记为编译错误. 函数的返回类型不足以区分两个重载函数. Because the return type of function is disregarded at some time.
- 如果在两个函数的参数表中只有缺省实参不同则第二个声明被视为第一个的重复声明.
- typedef 名为现有的数据类型提供了一个替换名它并没有创建一个新类型因此如果两个函数参数表的区别只在于一个使用了typedef 而另一个使用了与typedef 相应的类型则该参数表不被视为不同的. Typedef is replaced with the real type at compile time.
- 当一个参数类型是const 或volatile 时在识别函数声明是否相同时并不考虑const 和volatile 修饰符,针对传值对象.only for value-passed.
- 参数是const ,这只跟函数的定义有关系它意味着函数体内的表达式不能改变参数的值.但是对于按值传递的参数这对函数的用户是完全透明的用户不会看到函数对按值传递的实参的改变按值传递的实参以及参数的其他传递方式在。
- 当实参被按值传递时将参数声明为const 不会改变可以被传递给该函数的实参种类任何int 型的实参都可以被用来调用函数f(const int) 因为两个函数接受相同的实参集所以刚才给出的两个声明并没有声明一个重载函数。
- 但是如果把const 或volatile 应用在指针或引用参数指向的类型上则在判断函数声明是否相同时就要考虑const 和volatile 修饰符。
- const 对象或者引用只能调用那些定义为const 的成员函数。如果一个变量被声明为mutable, 则无论是在const or non-const 函数中,它都是可被修改的。
- 重载函数集合中的全部函数都应在同一个域中声明,例如一个声明为局部的函数将隐藏而不是重载一个全局域中声明的函数。
- Local function will hide the global function, not overload. Overloaded functions shall be in same space.
- 我们也可以在一个名字空间内声明一组重载函数每个名字空间也都维持着自己的一个域作为不同名字空间成员的函数不能相互重载。
- 用户不能在using 声明中为一个函数指定参数表。只能使用名字,要么全部使用,要么一个也不用。Using BaseClass: :DateMemberName or using BaseClass::FunctionMemberName. Import the functions in another namespace will become overloaded functions with other function in this space.
- using 声明总是为重载函数集合的所有函数声明别名(alias) without arguments。
- 名字空间的作者希望调用函数libs_R_us::pring(int), 由于某种原因库的作者给出了几个不同的函数若允许用户有选择地把一组重载函数中的一个函数而不是全部函数加入到一个域中那么这将导致令人吃惊的程序行为。
- 如果using 声明向一个域中引入了一个函数而该域中已经存在一个同名的函数又会怎样呢记住using 声明只是一个声明由using 声明引入的函数就好像在该声明出现的地方被声明一样因此由using 声明引入的函数重载了在该声明所出现的域中同名函数的其他声明。Overloaded functions together.
- using 指示符使名字空间成员就像在名字空间之外被声明的一样,通过去掉名字空间的边界using 指示符把所有声明加入到当前名字空间被定义的域中。如果在当前域中声明的函数与某个名字空间成员函数名字相同则该名字空间成员函数被加入到重载函数集合中。Collection of overloading function.
Overloading under inheritance:
- Inheritance of class will impact on resolving overloading function.
- Note, three ways to resolve overloading: 1. Select the candidate function. 2. Select feasible function. 3. Select the best match
- 候选函数的选择会受到继承机制的影响, 是因为与基类相关联的函数, 即基类的成员函数或者是在基类被定义的名字空间内声明的函数, 都将在选择候选函数时被考虑.
- 可行函数的选择会受到继承机制的影响, 是因为对于从实参到可行函数参数的类型转换, 将考虑一个更大的用户定义转换集.
- 最佳可行函数的选择也受到继承机制的影响, 因为继承机制影响了可以把实参转换成函数参数类型的转换序列的等级
- 在继承机制下, 如果一个实参是类类型, 类类型的引用, 或者类类型的指针, 并且该类有基类可能会有许多基类, 则在定义基类的名字空间内声明的、并且与被调函数同名的函数也被加入到候选函数集中例。
- 在继承机制下,如果实参的类型是一个带有基类的类,那么与被调函数同名的在基类定义中声明的友元函数也将被加入到候选函数集中。
- 所以如果普通函数调用的实参是类类型的对象,类类型的指针或类类型的引用,则候选函数是以下集合的并集
- 1 在调用点上可见的函数。
- 2 在定义该类类型的名字空间或定义该类的基类的名字空间中声明的函数。
- 3 该类或其基类的友元函数。
- 如果使用多个using 指示符情况也是这样具有相同的名字但是来自不同名字空间的成员函数都将被加到同一重载函数集合中。
- 对于用点或箭头成员访问操作符调用的成员函数,继承机制也会影响到它的候选函数集的建立。
- 在派生类中的成员函数声明并没有重载基类中声明的同名成员函数, 相反,派生类中的成员函数隐藏了基类中同名成员函数的声明,即使函数参数表并不相同。
- 为了纠正这种情况,使基类的成员函数重载派生类的成员函数,派生类的设计者可以用using 声明把基类成员函数引入到派生类的域中。
- 在多继承下建立候选成员函数集时,成员函数的声明必须在同一个基类中被找到,否则,该调用就是错误的。
- 可行函数是指这样的函数,从函数调用的每个实参到相应的可行函数参数之间都存在类型转换。
- 用户定义的转换,或者是一个转换函数,或者是一个单参数的非显式构造函数,在继承机制下在函数重载解析过程的第二步期间将会考虑一个更大的用户定义转换的集合。
- 转换函数像其他的类成员函数一样会被继承。
- 如果参数是派生类类型,基类存在一个构造函数转换,这个构造函数是不能被继承的来达到调用此函数的。必须明确该派生类有个构造转换函数。
- 为了选择最佳可行函数,用来将实参转换成相应函数参数类型的类型转换被划分等级。
- 把派生类类型的实参转换成任何一个基类类型的参数。
- 把派生类类型的指针转换成任何一个基类类型的指针。
- 用派生类类型的左值初始化基类类型的一个引用。
- 从基类到派生类的转换(reference or pointer) 是不可行的。
- 标准转换序列(派生类基类,promotion)好于用户定义的转换序列( conversion operator, constructor conversion).
- 对于从派生类类型到不同基类类型的不同标准转换进行等级划分时, 对于从派生类类型到基类类型移动较少距离, 较近的转换被认为好于移动较多距离较远的转换.
- The conversion to the “most derived” class is chosen if there is a choice.
- 类似的规则也适用于指针, 对从派生类类型指针到不同基类类型的指针的不同标准转换进行等级划分时, 从派生类类型到基类类型移动较少的转换被认为是较好的转换, 类似的规则也可以扩展到void*的处理上, 到基类类型指针的标准转换好于到void*的转换.
- 如果从派生类类型到两个基类类型的移动距离相等, 则从派生类到两个不同的基类类型的标准转换等级相同, 由于两个转换一样好, 所以,就不能为以下的调用选择最佳可行函数,该调用是错误的.
- 为了能够解析这个调用, 程序员必须给出显式强制类型转换.
- 用一个基类类型的对象初始化一个派生类对象,或初始化一个派生类类型的引用,或者从一个基类类型的指针到派生类类型的指针的转换,都不会被当作一个隐式转换来应用,调用是错误的。Down cast
- 基类到派生类的引用或者指针转换是不可行的,但是如果存在一个函数的参数可以用基类的conversion operator 来转换,则这个函数是可行的。
- extern "C"链接指示符(without strategy of name mangling) 只能指定重载函数集中的一个函数。可以声明一个指向重载函数集合里的某一个函数的指针。因为ff()是一个重载函数,所以只看初始化表达式&ff 编译器并不知道该选择哪个函数为选择初始化该指针的函数,编译器要查找重载函数集合里与指针指向的函数类型只有相同的返回类型和参数表的函数。
- 重载允许同一个函数名以不同参数表出现多次,这是程序源代码层次上的词法便利,但是大多数编译系统的底层组件要求每个函数名必须惟一,这是因为大多数链接编辑器都是按照函数名来解析外部引用的。为处理这个问题每个函数名及其相关参数表都被作为一个惟一的内部名编码。encoded 编译系统的底层组件只能看到编码后的名字,名字转换的细节并不重要。在不同的编译器实现中它们可能不同一般的做法是把参数的个数和类型都进行编码然后再将其附在函数名后面。
- Encode, name mangling, decode
- 特殊的编码可确保同名函数的两个声明(它们有不同的参数表,处于不同的文件中)不会被链接编辑器当作同一个函数的声明,因为这种编码帮助链接阶段区分程序中的重载函数,所以我们把它称作类型安全链接type-safe linkage。这种特殊编码不适用于用链接指示符extern "C"声明的函数这就是为什么在重载函数集合中只有一个函数可以被声明为extern "C"的原因,具有不同的参数表的两个extern "C"的函数会被链接编辑器视为同一函数。
- 如果D重新定义了mf,设计中就会产生矛盾。如果D真的需要实现和B不同的mf,而且每个B的对象 ---- 无论怎么特殊 ---- 也真的要使用B实现的mf,那么,每个D将不 "是一个" B。这种情况下,D不能从B公有继承。相反,如果D真的必须从B公有继承,而且D真的需要和B不同的mf的实现,那么,mf就没有为B反映出特殊性上的不变性。这种情况下,mf应该是虚函数。最后,如果每个D真的 "是一个" B,并且如果mf真的为B建立了特殊性上的不变性,那么,D实际上就不需要重新定义mf,也就决不能这样做。
- 任何条件下都要禁止重新定义继承而来的非虚函数。Redefining the non-virtual function will hide all overloading functions in base class.
- 决不要重新定义redefine继承而来的缺省参数值default parameter。Never have a mind to redefine the parameter which has a default value in the base class.
- 缺省参数只能作为函数的一部分而存在;另外,只有两种函数可以继承:虚函数和非虚函数。因此,重定义缺省参数值的唯一方法是重定义一个继承而来的函数。然而,重定义继承而来的非虚函数是一种错误,所以,我们完全可以把讨论的范围缩小为 "继承一个有缺省参数值的虚函数" 的情况。Non-virtual function is static binding or early binding and only be bound to the type of object of definition class.
- 虚函数是动态绑定而缺省参数值是静态绑定的, only related to the type declared。
- ps, pc,和pr都被声明为Shape指针类型,所以它们都以此作为自己的静态类型 static type。注意,这和它们真的所指向的对象的类型绝对没有关系 ----它们的静态类型总是Shape*, 将调用派生类的虚函数,但是缺省参数仍然是静态类型Shape的虚函数中定义的缺省参数。The non-virtual functions in class shape are always called even if they are changed in derived class by other programmer, as well as the default parameter of function in Shape is used.
- 对象的动态类型是由它当前所指的对象的类型决定due to/up to / depend on/lie on /reset with/ the real type of object的。即,对象的动态类型表示它将执行何种行为。the type of being pointed to by pointer declared with base class type.
- Only virtual function is dynamic binding. All of the rest is static binding, only related to the declared type.
- 虚函数是动态绑定的dynamic binding,意思是说,虚函数通过哪个对象被调用,具体被调用的函数就由那个对象的动态类型决定。只有虚函数是动态邦定的。虚函数是动态绑定的,但缺省参数是静态绑定的。Dynamic binding or late binding or run-time binding
- 和运行效率performance有关。如果缺省参数值被动态绑定,编译器就必须想办法为虚函数在运行时确定合适的appropriate/proper/right缺省值,这将比现在采用的在编译阶段确定缺省值的机制mechanism更慢更复杂。做出这种选择是想求得速度上的提高和实现上的简便,所以大家现在才能感受得到程序运行的高效。