48
C++ C++ 语语语语语语语语语语语语语语语语语语语 语语 语语语语语语 语语 语语语

《 C++ 语言程序设计 》 第二次直播课堂 主讲 首都经贸大学 李宁 副教授

  • Upload
    atara

  • View
    140

  • Download
    7

Embed Size (px)

DESCRIPTION

《 C++ 语言程序设计 》 第二次直播课堂 主讲 首都经贸大学 李宁 副教授. 一、关于函数原形和头文件. 函数的定义和函数的原型 例: double cubic ( double d){ // 定义 return d*d*d; } double cubic ( double d); // 原形. 函数原形提供了生成函数调用代码所必须的接口信息,因此: 在函数的调用处之前,需通过函数原形提供该调用函数的接口信息。. 函数定义也能提供同样的接口信息,因此: 如果函数的定义出现在函数调用处之前,函数原形即可省略。. - PowerPoint PPT Presentation

Citation preview

《《 C++C++ 语言程序设计》语言程序设计》第二次直播课堂

主讲 首都经贸大学 李宁 副教授

22

一、关于函数原形和头文件一、关于函数原形和头文件• 函数的定义和函数的原型函数的定义和函数的原型例:例:

double cubicdouble cubic (( double d){ //double d){ // 定义定义 return d*d*d;return d*d*d;}}double cubicdouble cubic (( double d); //double d); // 原形原形

33

• 函数原形提供了生成函数调函数原形提供了生成函数调用代码所必须的接口信息,用代码所必须的接口信息,因此:因此:–在函数的调用处之前,需通过函数原形提供该调用函数的接口信息。

44

• 函数定义也能提供同样的接函数定义也能提供同样的接口信息,因此:口信息,因此:–如果函数的定义出现在函数调用处之前,函数原形即可省略。

55

• 但是,通过函数定义来担负但是,通过函数定义来担负函数原形的任务存在着下列函数原形的任务存在着下列问题:问题:–函数的定义必须出现在函数调用的同一文件中,限制了函数的使用范围;

66

–函数定义必须以源程序的形式呈现,不便于将之作为产品提供;

–不便于实现大系统,不便于分工。

77

• 应尽可能做到函数的实现(定应尽可能做到函数的实现(定义)与函数的使用(调用)的义)与函数的使用(调用)的分离:分离:–首先设计函数原形,作为函数定义和函数调用必须共同遵守的接口标准;–将定义函数的程序和使用函数的程序放在不同文件中。

88

• 头文件以固定的、便于利用的形头文件以固定的、便于利用的形式保存一组函数的调用接口信息,式保存一组函数的调用接口信息,确保了接口信息描述的正确性和确保了接口信息描述的正确性和一致性。一致性。

• 用用 #include#include 命令将头文件插入到命令将头文件插入到需要的程序文件中需要的程序文件中

99

二、关于函数模板二、关于函数模板• 什么情况下不可省略模板实什么情况下不可省略模板实参?参?–无法通过模板函数的参数隐含获得相关信息的模板参数;

–在任何不可省略参数前面的模板参数。

1010

例例 11 ::template<class T1, class T2, class T3>template<class T1, class T2, class T3>

T2 Max(T1 x, T3 y){ T2 Max(T1 x, T3 y){ return T3(T3(x)>T3(y)? x : y); return T3(T3(x)>T3(y)? x : y);} //} // 定义定义

cout<<Max<int,foat>(3,3.5); //cout<<Max<int,foat>(3,3.5); // 调用 调用 提示:未出现在模板函数参数表中提示:未出现在模板函数参数表中的模板参数,其实参不得省略。的模板参数,其实参不得省略。

1111

template <typename Type,template <typename Type, int Rows,int Cols> int Rows,int Cols> void sumAll(Type data[][Cols],void sumAll(Type data[][Cols], Type result[]){ Type result[]){ for(int i=0;i<Rows;i++){ for(int i=0;i<Rows;i++){ result[i]=0; result[i]=0; for(int j=0;j<Cols;j++) for(int j=0;j<Cols;j++) result[i]+=data[i][j];} result[i]+=data[i][j];}} //} // 定义定义

例例 22 (教材例(教材例 5.105.10 ):):

1212

int d[][3]={int d[][3]={ {1,2,3},{2,3,4}, {1,2,3},{2,3,4}, {4,5,6},{6,7,8}}; {4,5,6},{6,7,8}};int r[4];int r[4];sumAll<int,4,3>(d,r);//sumAll<int,4,3>(d,r);// 调用调用

提示:对于普通类型(非虚拟类提示:对于普通类型(非虚拟类型)的模板参数,其实参不得省略。型)的模板参数,其实参不得省略。

1313

template<int Rows,int Cols,template<int Rows,int Cols, typename Type> typename Type>.. .. .. //.. .. .. // 定义定义

.. .. .. .. .. .. sumAll<4,3>(d,r); //sumAll<4,3>(d,r); // 调用调用

提示:通过调整模板参数的顺序,提示:通过调整模板参数的顺序,有可能使更多的模板实参可以省略。有可能使更多的模板实参可以省略。

例例 33 ::

1414

三、关于参数表中的数组三、关于参数表中的数组• 参数表中的数组就是指针参数表中的数组就是指针• 因此,定义参数表中的数组时不因此,定义参数表中的数组时不必给出第一维的大小;必给出第一维的大小;• 因此,对于参数表中的数组,也因此,对于参数表中的数组,也可以定义为指针,对应关系是:可以定义为指针,对应关系是: A[] A[] ←→ ←→ *A; *A;

A[][]… A[][]… ←→ ←→ (*A)[]… (*A)[]…

1515

• 因此,调用这样的函数时,只需因此,调用这样的函数时,只需以数组名作为实参;以数组名作为实参;• 因此,修改形参数组就是在修改因此,修改形参数组就是在修改形参指针所指向的数据,也就是形参指针所指向的数据,也就是在修改实参数组。但这并不违反在修改实参数组。但这并不违反“传值”原则,因为对应于形参“传值”原则,因为对应于形参的实参数组名(指针)并没有被的实参数组名(指针)并没有被修改;修改;

1616

四、动态空间的使用应避免的四、动态空间的使用应避免的问题问题•悬挂访问:通过一个没有初始化悬挂访问:通过一个没有初始化

的指针或空指针访问不存在的数的指针或空指针访问不存在的数据;据;

•存储泄漏:申请的动态空间用完存储泄漏:申请的动态空间用完后被丢弃,没有释放;后被丢弃,没有释放;

1717

•重复释放:同一动态空间被多次重复释放:同一动态空间被多次释放;释放;

•申请操作与释放操作不配套申请操作与释放操作不配套例如用 例如用 malloc() malloc() 申请,用 申请,用 ddeleteelete 释放;或用 释放;或用 new new 申请,申请,用 用 free() free() 释放。释放。

1818

五、两种重要的构造函数五、两种重要的构造函数• 无参构造函数无参构造函数

–指: 未定义参数的构造函数,或 : 所有参数均为可选参数的构造函数。

1919

–如果类中包含一个类对象数据成员,而类的构造函数又没有对之进行初始化,则系统隐含调用该成员所属类的无参构造函数进行初始化。

2020

–对于派生类,构造函数没有对基类进行初始化,则系统隐含调用基类的无参构造函数进行初始化。–每个类至少要有一个构造函数。如果没有定义,系统自动生成一个形如 X(){} 的默认无参构造函数。

2121

–由于无参构造函数有可能被隐含调用,因而类中通常应该有无参构造函数。如果类中已经定义了若干构造函数,应检查其中是否包含无参构造函数,如没有,应添加一个。

2222

class XX{class XX{ int xx;int xx; public:public: XX(int n=0):xx(n){} XX(int n=0):xx(n){}

...... };}; class YY{class YY{

XX xd;XX xd; double yy;double yy; public:public: YY(double d):yy(d){}YY(double d):yy(d){}//YY(…):xd( ),yy(d){}//YY(…):xd( ),yy(d){} ......}; };

隐含调用无参隐含调用无参构造函数例构造函数例 11

2323

class YY{class YY{ XX xd;XX xd; double yy;double yy; public:public: YY(double d):yy(d){}YY(double d):yy(d){} … …};};

class ZZ:public YY{class ZZ:public YY{ long k;long k; public:public: ZZ(long m):k(m){} //ZZ(long m):k(m){} // 错误!错误! … …};};

隐含调用无参隐含调用无参构造函数例构造函数例 22

2424

• 拷贝构造函数拷贝构造函数–指:参数中只包含一个同类对象引用的构造函数。

–用于创建一个与参数所引用对象相同的对象,这种创建对象的方式称为拷贝(复制,克隆)。

2525

–在调用一个具有类对象参数的函数时,系统隐含地以实参对象为参数调用拷贝构造函数,创建形参对象。

–对于返回值为类对象的函数,在用return 返回时,系统隐含地以标 return 语句中的对象为参数调用拷贝构造函数,创建作为返回值的对象。

2626

–每个类至少要有一个拷贝构造函数。如果没有定义,系统自动生成一个默认的拷贝构造函数,它以“浅层复制”的方式构造新对象。

–对于必须通过“深层复制”才能正确构造相同对象的情况,应当定义专门的拷贝构造函数。

2727

假定假定class String{class String{   char *p;char *p;public:public:   String(const char *c){p=c;}String(const char *c){p=c;}   ……};};中没有定义拷贝构造函数,中没有定义拷贝构造函数,

2828

则执行则执行 String s1("String s1("This is a stringThis is a string");"); String s2(s1); String s2(s1);的效果是:的效果是:

2929

TT hh ii ss ii ss aa ss tt rr ii nn gg

p

s2

p

s1 ““浅层复制"示意浅层复制"示意

3030

在 在 String String 中增加如下的拷贝构中增加如下的拷贝构造函数,实现造函数,实现“深层复制”“深层复制” : :String::String(String &s){String::String(String &s){ p=new char[strlen(s.p)+1]; p=new char[strlen(s.p)+1]; strcpy(p,s.p); strcpy(p,s.p);}; }; 这样,执行同样的语句序列的效果这样,执行同样的语句序列的效果是是::

3131

““深层复制”示深层复制”示意意TT hh ii ss ii ss aa ss tt rr ii nn gg

TT hh ii ss ii ss aa ss tt rr ii nn gg

p

s2

p

s1

3232

–如果定义了专门的拷贝构造函数,一般也就需要重载专门的赋值操作符。例如,例如, String String 中就应重载赋中就应重载赋值操作符 值操作符 = = 如下:如下:

3333

String& String::operator =String& String::operator = (const String &s){ (const String &s){ if(p) delete []p; if(p) delete []p; p=new char[strlen(s.p)+1]; p=new char[strlen(s.p)+1]; strcpy(p,s.p); strcpy(p,s.p); return *this; return *this;};};

3434

六、关于操作符的特性六、关于操作符的特性• 操作数个数操作数个数• 优先级和结合性优先级和结合性• 是否要求第一操作数必须是变量是否要求第一操作数必须是变量或视同变量的数据或视同变量的数据

3535

• 是否有副作用是否有副作用(是否修改第一操作数)(是否修改第一操作数)

• 操作结果是否为视同变量的数据操作结果是否为视同变量的数据(是否就是第一操作数)(是否就是第一操作数)

3636

视同常量视同常量+ - == …+ - == …其他其他 视同变量视同变量任意任意**间接访问间接访问 无无&&取地址取地址 ----后减后减 11 视同常量视同常量++++后增后增 11

+= *= …+= *= …复合赋值复合赋值 ==赋值赋值 ----前减前减 11 视同变量视同变量有有变量变量

或或视同变量视同变量

++++前增前增 11操作结果操作结果副作用副作用第一操作数第一操作数操作符操作符操作名称操作名称

3737

七、关于操作符重载七、关于操作符重载•应保持操作符原有的基本语义,应保持操作符原有的基本语义,重载的操作符应体现为原操作重载的操作符应体现为原操作符功能在新的数据类型中的延符功能在新的数据类型中的延伸伸

•应尽量保持操作符原有的特性应尽量保持操作符原有的特性

3838

–优先级、结合性和操作数个数这三个特性自动得以保持

–+= 、 *= 、 ++ (前增 1 )等操作符要求第一操作数必须是变量;当作为非成员函数重载这类操作符时,为了达到同样效果,第一参数应说明为引用

3939

–= 、 += 、 *= 、 ++等具有副作用的操作符,除后增 1 、后减 1 之外,其操作结果视同变量。当重载这类操作符时,为了达到同样效果,返回值应说明为引用, return 语句应返回第一参数(对于非成员函数重载),或返回 *this(对于成员函数重载)

4040

例如,例 例如,例 8.1 8.1 的 的 Fraction Fraction 类类(分数类)中重载了前增(分数类)中重载了前增 11 操作操作符:符:

Fraction &opertor++(){Fraction &opertor++(){ nume+=deno; nume+=deno;

return *this;return *this;

}}

4141

因此执行因此执行 Fraction x(2,3); ++++x;Fraction x(2,3); ++++x;

后,后, x x 变为变为 8/38/3

4242

•操作符的重载应当配套操作符的重载应当配套–如果重载了 + 、 - ,应考虑同时

重载 ++ 、 --–如果重载了 == ,应应考虑同时

重载 !=–如果重载了 > ,应应考虑同时重

载 <……

4343

• 作为成员函数重载还作为非成员作为成员函数重载还作为非成员函数重载?函数重载?–某些操作符只能作为成员函数重载,包括: = 、 [ ] 、 ( ) 、-> 以及所有类型转换操作符 。

4444

–若第一操作数不是同类对象,而是其他数据类型,则只能作为非成员函数重载(如输入输出流操作符 >> 和 << )。

4545

–其他情况既可作为成员函数,也可作为非成员函数重载。–作为非成员函数重载时,可将

其声明为友元。

4646

•使用引用参数还是非引用参数?使用引用参数还是非引用参数?–使用引用参数可减少参数传递过程中的数据复制量

–如果操作符作为非成员函数重载,且该操作符要修改第一操作数(如 += ),则代表第一操作数的第一参数必须是引用参数

4747

–使用非引用参数可利用只需一个实参的构造函数实现类型的自动转换

例:例 例:例 8.1 8.1 中 增加构造函数:中 增加构造函数: Fraction(int n=0,int d=1)Fraction(int n=0,int d=1) :num(n),deno(d){ :num(n),deno(d){ FracSimp(); FracSimp();} }

4848

类中已重载类中已重载 ++ ,原形是:,原形是: Fraction operator+(Fraction&);Fraction operator+(Fraction&);

因此可以完成下面的运算:因此可以完成下面的运算: Fraction x,y(2,5);Fraction x,y(2,5);

x=y+Fraction(2);x=y+Fraction(2);

但如使用非引用参数,后一句也可改为:但如使用非引用参数,后一句也可改为: x=y+2;x=y+2;