C++ Primer 笔记四

第七章 函数——c++的程序模块

函数在声明中接受参数与在定义中接受参数可以用不一样的变量名。

如:void kk(long j);//声明 void kk(long k){}//定义

在函数参数的过程中,如果基本类型(非复合类型)不匹配,c++会自动强制类型转换,但尽量避免强制类型转换,造成数据破坏

7.3函数与数组

引用的属性和特别之处long kk=10;viod hans(long &kk);void hanshans(const long &kk)//声明变量、定义函数

为了保证被调用函数只使用调用函数的变量值,而不是是修改变量值,我们一般情况下是不用引用的,如果要用引用我们可以将被调用函数中的引用声明为const引用,这样我们只能使用该函数,但是不能修改它的值,这就像是const long*const kk;

void hans(kk+3);//这是错误的,现在的c++ 是不允许将表达式传递给引用,而按值传递是允许的

void hanshans(kk+3);//这是正确的,在现在c++中如果参数定义为const引用时,我们可以给他传递非变量名的参数因为这样//它创建了临时变量,用引用指向临时变量时,但是函数不会修改器临时变量值,这样避免了想修改原函数变量值,但没修改的状况

//用const我们编程时不会去修改原函数的变量。在c++中使用引用时尽量将引用声明为const这样传递的类型可以转换

将引用于用于结构:引用非常适合用于结构和类,c++加入引用主要为了用于这些类型的,而不是基本的内置类型

7.9函数指针

获取函数的地址:只要使用函数名(后面不跟参数)即可

声明函数指针:假如有一个函数为:double pam(int) 则正确的指针类型声明为:double (*pf) (int) 这与pam()的声明类似,只是把pam替换成了(*pf),由于pam是函数,则(*pf)也是函数,而如果(*pf)是函数,则pf是指向函数的指针

由于括号的优先级比*高,则*pf(int)表示,pf是一个返回指针的函数,而(*pf)(int),表示pf是一个指向函数的指针。

正确的声明了函数指针之后,可以将函数的地址赋给它:double pam(int); double (*pf)(int); pf=pam

使用指针来调用函数:(*pf)扮演了函数名的角色,所以使用(*pf)的时候只需要将它看作是函数名即可:double pam(int); double (*pf)(int); pf=pam;

double x = (*pf)(5); 和double x = pf(5);都是正确的

 

第八章 函数

8.1内联函数

内联函数的编译代码和其他程序代码“内联”起来了,也就是说编译器将使用相应的函数代码替换函数调用(有多少次调用就有多少次拷贝),内联函数的运行速度比常规函数快,但是需要占用更多的内存。如果代码的执行时间相对于调用时间短,则可以使用内联函数

函数声明前加上关键字inline,函数定义前加上关键字inline

内联函数和常规函数一样,也是按照值来传递的。

内联和宏

#define SQUARE(x)  x*x

a=SQUARE(5); b= SQUARE(5+2); c= SQUARE(a++);

则只有a能够被正确赋值。宏只是简单地替换,并不能有函数那么智能

8.2引用变量

引用是已定义的变量的别名.修改引用的值和修改变量的值是一样的效果

创建引用变量:int rats; int & rodents = rats; 其中&不是地址操作符,而是表示rodentsrats的引用变量,int &表示是指向int的引用,就像在声明中 char* 表示的是指向char的指针。rats rodents的地址相同。必须在声明引用的时进行初始化。和const类型相似,必须在声明时初始化。

将引用用作函数参数:引用经常被用作函数的参数,使得函数中的变量名成为调用程序中的变量的别名,这种传递参数的方式叫做按引用传递。和按指针传递效果一样。

 

 

例子(交换两变量的值):

 

void swapr(int &a,int &b)

{

int temp = a;

a=b;

b=temp;

}

void swapp(int *a,int *b)

{

int temp=*a;

*a=*b;

*b=temp;

}

void swapp1(int *a,int *b)

{

int *temp=a;

a=b;

b=temp;

}

 

调用方法分别为 int a;  int b;  swapr(a,b);  swap(&a,&b); int swapp1(&a,&b);

第三种方式不行,因为它只是交换了函数内部的指针指向的地址,并没有改变该地址的变量的值。当函数结束时,两个指针变量将会被销毁,则没有对两个变量的值产生任何变化。

引用的属性和特别之处:

按值传递可以接受多种类型的实参:cube(x+2);cube(x*5)。但是按引用传递不可以接受多种类型的实参,但const引用可接受多种类型的实参。cube(x+2);这种形式,因为表达式x+2并不是变量

如果参数为const引用时,C++允许生成临时变量,即按引用传递参数的函数调用的参数不是左值或者与相应的const引用参数的类型不匹配,则C++将创建类型正确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量。因为如果接受引用的参数的函数的意图是修改作为参数传递的变量,则编译器将会让两个临时变量交换值,而原来的变量的值不变,const变量不允许改变变量的值,因此不会出错,可行。

如:refcube(const double x); int a; 可以接受:refcube(7); refcube(x+2); refcube(a);

尽可能的使用const:使用const可以避免无意中修改数据的编程错误;使用const可以接受const类型的数据,也可以接受非const类型的数据;使用const能使函数正确的生成临时变量,减少错误.

将引用用于结构:

 

struct sysop

{

char name[26]; 

char quote[64];

int used;

}

sysop copycat= use(looper);

const & sysop use(sysop & sysopref)

{

sysopref.used ++;

cout<<”name:”<<sysopref.name<<endl;

cout<<”quote:”<<sysopref.quote<<endl;

cout<<”used:”<<sysopref.used<<endl;

return sysopref;

}

int main()

{

sysop copycat;

sysop looper={“NAME”,”QUOTE”,0};

use(looper);

copycat=use(looper);

}

 

如果函数use()返回的是一个结构,则sysopref的内容将被复制到一个临时返回存储单元中,然后该临时返回存储单元的内容将被复制到copycat中。然而由于use返回的是一个指向looper的引用。这种情况下,looper的内容将被直接复制到copycat中,这是返回指向结构的引用而不是结构的优点:效率更高

返回引用时需要注意的问题:避免返回当函数终止的时候不再存在的内存单元引用

 

const sysop &clone(sysop &sysopref)

{

sysop newguy;

newguy = sysopref;

return newguy;

}

const sysop &clone(sysop &sysopref)

{

sysop *newguy = new sysop;

*newguy = sysopref;

return *newguy;

}

 

左边的那种方式不行,因为在函数结束之后,临时变量newguy就会被销毁。

C++中使用new的方式可以,但是需要使用delete来释放它们,auto_ptr模板可帮助自动完成释放工作。

关于指针的一个小感悟:

int *pt;未初始化的int指针,系统会分配pt的存储空间(没保存int的地址),即并不会分配它指向的int的存储空间,存储空间不存在。

int *pt=new int;系统会分配pt的存储空间,并且会分配它指向的int的存储空间,只是int的值是不确定的。int存储空间存在

int a=5; *pt=a;这个的意思是,把a的值赋给*pt,就是把a的值写到pt指向的int类型的存储空间去。前提是该存储空间应该存在。

pt=&a;这个的意思是,把a的地址赋给pt,就是把a的地址存储到pt的存储空间里面。如果原来分配了int变量的空间,也会被覆盖。

use()返回类型是const sysop &。表示不能使用它返回的引用去修改它指向的引用。不能使用:use(looper).used = 10; 

将引用用于类对象:(使用string类作为例子)

主要说明了不能修改const类型的变量,不能返回已经被销毁的变量的引用

对象、继承和引用:

派生类继承了基类的方法,意味着派生对象可以使用基类对象的方法。

基类引用可以指向派生类对象,而如需进行强制类型转换,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。

何时使用引用参数:

使用引用参数的主要的原因有两个:

ü 程序员能够修改调用函数中的数据对象

ü 通过传递引用而不是整个数据对象,可以提高程序的运行速度。

使用引用、指针、按住传递的一些指导原则:

1.对于使用传递的值而不对其作修改的函数:

ü 如果数据对象很小,如内置数据类型或者小型数据类型,就使用传值调用。

ü 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针

ü 如果数据对象是较大的结构,则使用const指针或const引用,以提高效率,节省空间和时间

ü 如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用。传递类对象参数的标准方式是按引用传递。

2.对于修改调用函数中数据的函数:

ü 如果数据对象时内置数据类型,就使用指针。

ü 如果数据对象是数组,只能使用指针。

ü 如果数据对象是结构,则使用引用或指针

ü 如果数据对象是类对象。则使用引用。

8.3默认参数

通过函数原型来设定默认值,如:char *left(const char*str , int i = 1); i的默认值为1

对于带参数列表的函数,必须从右向左添加默认值,也就是说,要为某个参数设置默认值,则必须为它右边所有的参数提供默认值,因为实参按照从左到右的顺序依次被赋给相应的形参,不能跳过任何参数。

 

int one(int n,int m =4,int j=5); //valid

int two(int n,int m=5,int j); //invalid

int three(int n=1,int m =4,int j=5); //valid

beers=one(2); //the same as: one(2,4,5);

beers=one(1,2); //the same as: one(1,2,5);

beers=one(1,2,3); //the same as: one(1,2,3);

 

注意:只有原型制定了默认值,函数定义与没有默认参数时完全相同。

8.4函数重载

函数多态(函数重载)能够使多个同名的函数存在,术语多态指的是有多种形式,因此函数多态允许函数可以有多种形式。术语函数重载指的是可以有多个同名的函数,因此对名称进行了重载。

函数重载的关键是函数的参数列表,也称为函数特征标。(并不是返回值,不同返回值不能够进行函数重载)

但是一些看起来彼此不同的特征表不能共存,如:double cube(double x);  和  double cube(double & x);

因为:double x = 3; cuble(3); 与这两个原型都匹配,系统不知道调用哪个原型,因此它们不允许共存。

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

8.5函数模板

函数模板是通用的函数描述,也就是说,他们使用通用类型来定义函数。

 

template<class Any>

void Swap(Any &a,Any &b)

{

Any temp=a;

a=b;

b=temp;

}

template<typename Any>

void Swap(Any &a,Any &b)

{

Any temp=a;

a=b;

b=temp;

}

 

以上两者等价。第一行指出要建立一个模板,并将类型命名为Any

调用的方式为:int i=1;int j=2;Swap(i,j); double x=1.0; double y=2.0; Swap(x,y);

编译器会自动生成函数的int版本和double版本,不需要程序员参与。

重载的模板:可以像重载常规函数定义一样重载模板定义。

 

template<class Any>

void Swap(Any a[],Any b[],int n)

{

Any temp;

for(int i=0;i<n;i++)

{

temp=a[i]

a[i]=b[i];

b[i]=temp;

}

}

template<class Any>

void Swap(Any &a,Any &b)

{

Any temp=a;

a=b;

b=temp;

}

int i=1;int k=2;Swap(i,k);

int d1[]={1,3};int d2={5,4};int n=3;Swap(d1,d2,n);

 

 

显示具体化:即,使用具体化来覆盖常规模板

由于C++允许将一个结构赋给另一个结构,如果Any是一个结构,假设为struct sysop,也可使用第一种方式,则结构中的所有成员都会被交换,但是如果只想交换结构中的name,则需要提供一个具体化函数定义(称为显示具体化)来覆盖常规模板。

1,第三代具体化

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

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

ü “具体化”将覆盖“常规模板”,而“非模板函数(正常函数)”将覆盖“具体化”和“常规模板”

非模板函数(正常函数): void swap(sysop &, sysop&)

模板函数: template<Any> void swap(Any &, Any &)

具体化的模板函数 template<> void swap(sysop &, sysop &)、template<> void swap<sysop>(sysop &, sysop &)

2,早期的具体化方案不再说明

实例化和具体化:

隐式实例化:直接调用swap(x,j);导致编译器生成一个使用int类型的实例,叫做隐式实例化

显示实例化:template void swap<int>(int,int&)声明所需要的类型int,并加上关键字template,编译器会使用swap()

 

模板生成int类型的函数定义。不需要对其进行定义

显示具体化:template<> void swap(int &, int &)或等价声明:template<> void swap<int>(int &, int &)。意思是:不要使用swap()模板来生成函数定义,而应使用独立的、专门的函数定义显式地为int类型生成函数定义,这些原型需要程序员编写函数的定义。

声明:template<> void swap<int>(int &, int &);

定义:template<> void swap<int>(int &, int &){….实现具体过程}

注意:试图在同一个编程单元中使用同一种类型的显示实例化和显示具体化,将会出错

8.6总结

引用变量是一种伪装指针,主要被用作处理类和结构对象的函数的参数。

函数参数的默认值(定义时必须从右向左提供默认值,使用函数时,变量赋值从左往右)

函数的特征标是其函数参数列表,决定了是否可以重载。

函数模板是自动完成重载函数的过程。

posted @ 2014-08-26 11:07  tt_tt--->  阅读(98)  评论(0编辑  收藏  举报