您的位置:首页 > 编程语言 > C语言/C++

C++ Primer Plus第六版-第八章学习笔记

2016-01-04 10:07 483 查看
本章学习C++不同于C的函数新特性,这是进入C++领域的重要一章,其内容包括:内联函数、按引用传递变量、默认的参数值、函数重载(多态)、模板函数等;

1、内联函数;

C++中要使用函数必须提供定义和原型,并调用该函数。函数的定义是实现函数功能的代码;函数原型描述了函数的接口就是C里面的函数声明:传递给函数的值的数目和种类以及函数的返回类型。函数调用使得程序将参数传递给函数,并执行函数的代码;

为什么要使用内联函数?

内联函数是C++为提高程序的运行速度所做的一项改进,但是内联函数应该有选择的去应用;

内联函数与一般函数的区别:主要区别不在于编写方式,而在于编译器如何将函数组合到程序中,他们之间的区别必须深入到程序内部:

编译过程的最终产品是可执行文件——由一组机器语言指令组成,运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令,有时如有循环或分支语句时,将跳过一些指令,向前或者向后跳到特定地址。常规函数调用也是使程序跳到另一个地址(函数的地址),并在函数结束时返回。具体一般过程如下:当程序执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(此为保留的内存块),跳到标记函数起点的内存单元,执行函数代码(也许还需要将返回值放入到寄存器中),然后跳回到地址被保存的指令处;

内联函数提供了另一种选择:内联函数的编译代码与程序代码“内联”起来。也就是说,编译器将使用相应的代码替换函数调用,而对于内联代码指令,程序无需跳到另一个位置处执行代码再跳回来,因此内联函数运行速度比常规函数稍快,但是需要占用更多内存;所以应该有选择的使用内联函数。

内联函数的使用:

在函数声明前加上关键字inline;

在函数定义前加上关键字inline;

或者省略声明即省略原型,将整个定义放在本应该提供原型的地方

注意:内联函数不能递归

举例:内联函数也是按值传递的

#include<iostream>

inline double square(double x){return x*x;}

int main(){

using namespace std;

double a,b;

double c = 13.0;

a = square(5.0);

b = square(4.5+7.5);

cout << “a =”<<a<<”,b =”<<b<<endl;

cout<<”c squared =”<< square(c++)<<”\n”;

return 0;

}

内联函数与宏的区别:

C语言中使用预处理器语句#define来提供宏——内联代码的原始实现

但是inline工具实现的内联函数是通过值传递的,而宏是通过文本替换来实现;

如#define SQUARE(x) x*x

a = SQUARE(5.0); a = 5.0*5.0

b = SQUARE(4.5+7.5); b = 4.5+7.5*4.5+7.5(没有达到预期的功能)

c = SQUARE(c++); c = c++*c++; (c会增加两次)

2、按引用传递变量;

C++新增了一种复合类型——引用变量,在java中的变量就简化为两大类:基本数据类型变量和引用类型变量,java中数组、字符串都是引用类型的,这在C++中有些许不同;

引用就是已经定义的变量的别名,只是另外一个名称而已,引用变量的主要用途是作为函数的形参,通过将引用变量作为参数,函数将使用原始数据,而不是其副本;这样除了指针外,引用也为函数处理大型结构提供了一种途径,另外引用对于类来说是必不可少的;在java中没有指针的概念,java是一种完全面向对象的语言,所有的成员变量都用类来封装,所以在java中引用也必不可少;

1、 创建引用变量:

C++中用&来声明引用变量,且必须在声明引用变量时进行初始化,在函数形参中,引用变量的初始化就是调用函数时传入的实参,也就是函数的形参变成了被调函数传入的实参的别名;

int lsb;

int& sb = lsb;这里的&不是取地址,而是一个类型标识符的一部分,就如指针中的int*一样;

引用与指针的区别:引用用&定义,指针用*定义,另外引用声明必须初始化,而指针不需要;另外函数形参为引用或者指针时,函数内部使用形参过程中引用变量无需带&,但是指针变量需使用解除引用运算符*;引用很类似于const指针,引用声明初始化为某一个变量的引用,就固定为那个变量的别名,不能再切换为别的变量的引用,而指针的指向变量可以切换;函数形参按引用传递或者按指针传递都允许被调用的函数能够访问调用函数中的变量;

2、 临时变量、引用参数和const

参与引用参数不匹配,C++将产生临时变量,目前,仅当参数为const引用时,C++才允许这样做;

用参数是const引用时,编译器将在什么情况下生产临时变量:1、实参的类型正确,但不是左值,如多项的表达式;2、实参的类型不正确,但可以转换为正确的类型;

以上两种情况为什么要是const引用时才能产生临时变量呢?解释如下:如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现,而解决的方法就是禁止创建临时变量;而如果该函数的目的只是使用传递的实参值,而不是修改它们,这样临时变量不会造成任何不利的影响,反而使得函数在可处理的参数种类方面更通用,因此,如果声明将引用指定为const,C++在必要时可以产生临时变量。

所以应尽可能使用const,理由总结如下:

使用const可以避免无意中修改数据的编译错误;

使用const使函数能够处理const和非const实参,否则将只能接受非const数据;

使用const引用使函数能够正确生成并使用临时变量;

另外C++还提供了另外一种类型的引用,右值引用,使用&&声明

3、 Const引用,就某些函数来说,也是按值传递的,但与复制原始数据的拷贝相比,使用引用可以节省时间和内存;

4、 函数的返回值为返回引用时:返回引用的函数实际上是被引用的变量的别名,返回引用的好处:一般的返回,在使用时是先将返回值复制到一个临时变量,然后再使用这个临时变量的值,但是使用返回引用,就不需要临时变量,直接使用,效率更高;函数返回引用时应该注意,应避免返回函数终止时不再存在的内存单元引用,解决方法:返回一个作为参数传递给函数的引用,另外就是使用new来分配新的存储空间;

3、默认的参数值;

默认参数指的是当函数调用中省略了实参时自动使用的一个值;

默认参数设置的方法是在原型及函数声明中将默认值赋给原型中的形参;

带参数列表的函数,设置默认参数时,必须从右向左添加默认值;

默认参数让您能够使用不同数目的参数调用同一个函数;

4、函数重载(多态)

函数重载:可以定义多个同名的函数,但是它们有不同的参数列表,及有不同的特征标;

当函数参数列表有引用参数时:不同的引用类型;

何时使用函数重载:仅当函数基本上执行相同的任务,但使用不同形式的数据时;

5、 模板函数

什么是模板函数?

函数模板是通用的函数描述,也就是说,它使用泛型来定义函数,其中泛型可用具体的类型替换,通过将具体类型作为参数传递给模板,可以编译生成该类型的函数;

例如:

Template <typename AnyType>

Void swap(AnyType &a, AnyType &b)

{

AnyType temp;

Temp = a;

a = b;

b = temp;

}

要建立一个模板:关键字template 和typename是必须的,另外也可以用cass代替typename;另外就是必须使用尖括号;如果需要多个将同一算法用于不同类型的函数,可以使用模板;

使用模板,当把具体的类型传递给模板,编译器将检查使用的参数类型,并生产相应的函数,这个函数的代码程序员看不到,这个函数称之为模板函数的实例化,且是隐式实例化;

一般将模板放在头文件中,并在需要使用模板的文件中包含头文件;

重载的模板:模板函数也可以重载,和常规函数一样,被重载的模板的函数特征标必须不同;另外并非所有的模板参数都必须是模板参数类型;

模板的局限性:编写的模板函数很可能无法处理某些类型,另外,有时候通用化是有意义的,但是C++语法不允许这样做。例如,将两个包含位置坐标的结构相加是有意义的,虽然没有为结构定义运算符+;解决方案:一是重载运算符+,以便能够将其用于特定的结构或类;另外就是,为特定的类型提供具体化的模板定义;

显示具体化:

对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本;

显示具体化的原型和定义应以template<>打头,并通过名称来指出类型;

具体化优于常规模板,而非模板函数优于具体化和常规模板;(如果有多个原型)

显式具体化、隐式实例化、显式实例化统称为具体化;

显式具体化和显式实例化的区别:

显式具体化用template <>, 而显式实例化用template不需要加尖括号;

另外显式具体化不产生函数定义代码,而显式实例化生成函数定义代码;

显式实例化可以在程序中使用,而显式具体化不行 ,只能在原型处;

同一个文件中不可以同时使用同一种类型的显式实例化和显式具体化;

关键字 decltype

template<class T1, class T2>

void sb(T1 x, T2 y)

{

…..

Type? Xpy = x+y;

…..

} type 应该为什么类型呢?

可以使用decltype(x+y)如下所示:

template<class T1, class T2>

void sb(T1 x, T2 y)

{

…..

decltype(x+y)Type Xpy = x+y;

…..

}

另一种函数声明方法:后置返回类型

如:

auto h(int x, int y) ->double 将函数返回值类型移到了参数声明后面, ->double 被称为后置返回类型

auto h(T1 x, T2 y) -> decltype(x+y)

{

…..

return x+y;

……

}

C++模板
  模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
  模板是一种对类型进行参数化的工具;
  通常有两种形式:函数模板和类模板;
  函数模板针对仅参数类型不同的函数;
  类模板针对仅数据成员和成员函数类型不同的类。
使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。下面分别介绍。
注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。

1、函数模板的格式:
template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表)
{
      函数体
   }
  其中template和class是关键字,class可以用typename 关键字代替,在这里typename 和class没区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如swap的模板函数形式为
template <class T> void swap(T& a, T& b){},
当调用这样的模板函数时类型T就会被被调用时的类型所代替,比如swap(a,b)其中a和b是int 型,这时模板函数swap中的形参T就会被int 所代替,模板函数就变为swap(int &a, int &b)。而当swap(c,d)其中c和d是double类型时,模板函数会被替换为swap(double &a, double &b),这样就实现了函数的实现与类型无关的代码。
2、注意:对于函数模板而言不存在 h(int,int) 这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行 h(2,3) 这样的调用,或者int a, b; h(a,b)。

2、类模板的格式为:
    template<class 形参名,class 形参名,…> class 类名
    { ... };
  类模板和函数模板都是以template开始后接模板形参列表组成,模板形参不能为空,一但声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如
template<class T> class A{public: T a; T b; T hy(T c, T &d);};
在类A中声明了两个类型为T的成员变量a和b,还声明了一个返回类型为T带两个参数类型为T的函数hy。
  2、类模板对象的创建:比如一个模板类A,则使用类模板创建对象的方法为A<int> m;在类A后面跟上一个<>尖括号并在里面填上相应的类型,这样的话类A中凡是用到模板形参的地方都会被int 所代替。当类模板有两个模板形参时创建对象的方法为A<int, double> m;类型之间用逗号隔开。
  3、对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;用这种方法把模板形参设置为int是错误的(编译错误:error C2079: 'a' uses undefined class 'A<int>'),类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A<int> m。
  4、在类模板外部定义成员函数的方法为:
template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体},
比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:
 template<class T1,class T2> void A<T1,T2>::h(){}。
注意:当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。
  5、再次提醒注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
有三种类型的模板形参:类型形参,非类型形参和模板形参。
  1、类型形参
    1.1 、类型模板形参:类型形参由关见字class或typename后接说明符构成,如template<class T> void h(T a){};其中T就是一个类型形参,类型形参的名字由用户自已确定。模板形参表示的是一个未知的类型。模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同,即可以用于指定返回类型,变量声明等。
作者原版:1.2、 不能为同一个模板类型形参指定两种不同的类型,比如template<class T>void h(T a, T b){},语句调用h(2, 3.2)将出错,因为该语句给同一模板形参T指定了两种类型,第一个实参2把模板形参T指定为int,而第二个实参3.2把模板形参指定为double,两种类型的形参不一致,会出错。(针对函数模板)
    作者原版:1.2针对函数模板是正确的,但是忽略了类模板。下面将对类模板的情况进行补充。
本人添加1.2补充版(针对于类模板)、当我们声明类对象为:A<int> a,比如template<class T>T g(T a, T b){},语句调用a.g(2, 3.2)在编译时不会出错,但会有警告,因为在声明类对象的时候已经将T转换为int类型,而第二个实参3.2把模板形参指定为double,在运行时,会对3.2进行强制类型转换为3。当我们声明类的对象为:A<double> a,此时就不会有上述的警告,因为从int到double是自动类型转换。

2、非类型形参
    2.1 、非类型模板形参:模板的非类型形参也就是内置类型形参,如template<class T, int a> class B{};其中int a就是非类型的模板形参。
    2.2、 非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
2.3、 非类型模板的形参只能是整型,指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的。
    2.4、 调用非类型模板形参的实参必须是一个常量表达式,即他必须能在编译时计算出结果。
    2.5 、注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
    2.6、 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
    2.7 、sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
    2.8 、当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如template <class T, int a> class A{};如果有int b,这时A<int, b> m;将出错,因为b不是常量,如果const int b,这时A<int, b> m;就是正确的,因为这时b是常量。
2.9 、非类型形参一般不应用于函数模板中,比如有函数模板template<class T, int a> void h(T b){},若使用h(2)调用会出现无法为非类型形参a推演出参数的错误,对这种模板函数可以用显示模板实参来解决,如用h<int, 3>(2)这样就把非类型形参a设置为整数3。显示模板实参在后面介绍。
    2.10、 非类型模板形参的形参和实参间所允许的转换

      1、允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A<b> m;即数组到指针的转换

      2、const修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m; 即从int *到const int *的转换。

      3、提升转换。如:template<int a> class A{}; const short b=2; A<b> m; 即从short到int 的提升转换

      4、整值转换。如:template<unsigned int a> class A{}; A<3> m; 即从int 到unsigned int的转换。

      5、常规转换。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: