第八章 函数探幽
2020-02-24 10:33:47 4 举报
AI智能生成
C++ plus
作者其他创作
大纲/内容
函数重载
要求
函数的参数列表——函数特征标。即函数的参数数目和类型不同
例子
#1 void print( const char * str ,int width);
#2 void print( double d ,int width);
#3 void print( long l ,int width);
说明
调用时,根据实参数据不同的类型 使用相应的原型。
print("Pancakes",15);---#1
print(99.0 ,15)---#2
print(99L,15)---#3
print("Pancakes",15);---#1
print(99.0 ,15)---#2
print(99L,15)---#3
如果 print( 99 , 15 ) 没有匹配的函数,如果只有一个print 会强制转化
有函数重载时,显示错误。
有函数重载时,显示错误。
编译器把类型引用和类型本身看成为同一个特征标
void cube( double x)
void cube( double & x) //是一样的,不能通过
void cube( double & x) //是一样的,不能通过
void dribble( char * bits);---#1
void dribble(const char * bits);---#2
void dribble(const char * bits);---#2
是函数重载,根据实参是否为const 类型匹配函数
非const 字符指针参数 可以与#1,#2匹配,这时会调用最匹配的版本。(#1)
返回值不一样,但函数特征标一样的函数不是函数重载
long gronk(int n , float m);
double gronk(int n, float m;//not allow)
double gronk(int n, float m;//not allow)
什么时候使用
函数重载
函数重载
当函数基本上执行相同的任务,但使用不通形式的数据时,采用函数重载
如果只是参数的数量不一样,其它一样,使用默认参数简单些。
名称修饰
含义
根据函数原型中制定的形参类型对每个函数名进行加密
函数模板
创建
代码+说明
template<typename AnyType>
指出创建一个函数模板,类型命名为Anytype。
命名可以是其它 例如T。(一般开头为大写??)
命名可以是其它 例如T。(一般开头为大写??)
作用:告诉编译器如何定义函数。需要交换int 的函数时,编译器按模板模式
创建这样的函数,并用int 代替Anytype。
创建这样的函数,并用int 代替Anytype。
有些实现可以用class替换typename 。考虑兼容问题,一般用typename
void Swap(Anytype &a ,Anytype & b);
如果类型命名为T,应该是void Swap(T &a ,T& b);
Swap不是叫函数,而是叫函数模板(或模板)
int main()
{
{
int a=3,b=4;
Swap(a,b);
double a=3.0,b=4.0;
Swap(a,b);
....
}
Swap(a,b);
double a=3.0,b=4.0;
Swap(a,b);
....
}
第一个Swap()参数是int ,编译器会生成该函数的int 版本
第二个是double ,编译器生成double版本,用double替换Anytype
template<typename Anytype>
void Swap(Anytype &a, Anytype &b )
{
Anytype temp;
temp =a ;
a=b;
b=temp;
}
void Swap(Anytype &a, Anytype &b )
{
Anytype temp;
temp =a ;
a=b;
b=temp;
}
还需要在写一次template<typename Anytype>
可以template<typename T1,typename T2>
Swap(T1,T2)
Swap(T1,T2)
好处
并不能缩短可执行程序,因为在程序内部还是生成了int 的Swap版本和double版本
使生成多个函数定义更简单,更可靠
重载的模板
作用
不是所有类型都使用一种算法。可以使用重载模板
要求
被重载的模板函数特征标要不一样。
代码
代码
// 功能:
#include<iostream>
template<typename T>
void Swap(T &a,T &b);
template<typename T>
void Swap(T *a ,T *b ,int n);
int main()
{
using namespace std;
//swap int
int a=3,b=4;
Swap( a,b);
cout<<"now a="<<a<<",b="<<b<<endl;
//swap array
int ar[5]={1,2,3,4,5},br[5]={6,7,8,9,10};
Swap(ar,br,5);
cout<<"now a[5]=";
for(int i=0;i<5;i++)
cout<<ar[i]<<" ";
cout<<",b[5]=";
for(int i=0;i<5;i++)
cout<<br[i]<<" ";
return 0;
}
template<typename T>
void Swap(T & a,T & b)
{
T temp;
temp = a ;
a = b;
b = temp;
}
template<typename T>
void Swap(T * a,T * b,int n)
{
T temp[n];
for(int i=0;i<n;i++)
{
temp[i]=a[i];
a[i]=b[i];
b[i]=temp[i];
}
}
说明
第一个模板交换基本类型
第二个模板交换数组。
第二个模板交换数组。
并非所有的模板参数都必须是模板参数类型,第二个模板中,最后一个参数为int
T 就是表明了int ,double ,不能int & ,int *???int &,不可以,int * 可以
template<typename T>
void Swap(T a, T b)
....
int main()
{
int a[],b[];
Swap(a,b);
....
}
//T 就是 int * 类型了。
void Swap(T a, T b)
....
int main()
{
int a[],b[];
Swap(a,b);
....
}
//T 就是 int * 类型了。
模板局限性
问题
编写的模板可能不适用于一些类型,无法处理某些类型
eg: a = b;如果 a,b为数组则不可以
解决方案
重载运算符
为特定类型提供具体化的模板定义
显式具体化
无法解决的问题
template<typename T>
void Swap(T & a,T & b)
当a,b为结构的时候,要交换结构中某一个成员如num,不交换name。
参数类型一样,因此无法使用重载模板
当a,b为结构的时候,要交换结构中某一个成员如num,不交换name。
参数类型一样,因此无法使用重载模板
原则
一样的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及他们的重载版本
显式具体化的原型和定义以template<>打头,通过名称指定类型
具体化优先于常规模板,非模板函数优先于具体化和常规模板
原型格式
struct job {
char name[40];
double salary;
int floor;
}
char name[40];
double salary;
int floor;
}
非模板函数
void Swap(job & ,job &);
模板函数
template<typename T>
void Swap(T&,T&);
void Swap(T&,T&);
具体化
原型声明
template<>void Swap<job>(job &,job &);
tempalte<>void Swap(job &,job &);//simple one
tempalte<>void Swap(job &,job &);//simple one
子主题
定义
template<>void Swap<job>(job & j1 , job & j2)
{
double t1;
t1=j1.salary;
j1.salary = j2.salary;
j2.salary = t1;
}
例子
template<typename T>
void Swap(T&,T&);
tempalte<>void Swap(job &,job &);
int main()
{
int a,b;
Swap(a,b);//using void Swap(int & ,int &)第一个
....
job Sue={...};
job John={...};
Swap(Sue,John);//using void Swap(job &,job &)第二个
}
void Swap(T&,T&);
tempalte<>void Swap(job &,job &);
int main()
{
int a,b;
Swap(a,b);//using void Swap(int & ,int &)第一个
....
job Sue={...};
job John={...};
Swap(Sue,John);//using void Swap(job &,job &)第二个
}
实例化和具体化
实例化
含义
代码中包括函数模板本身不会生成函数定义。可以使用模板为特定类型生成函数定义。
编译器使用模板为特定类型生成函数定义——得到模版实例。这就是模板实例化
编译器使用模板为特定类型生成函数定义——得到模版实例。这就是模板实例化
实例化类型
隐身实例化
程序调用Swap()函数时提供了int 参数,编译器才进行函数定义
显式实例化
直接命令编译器创建特定的实例
隐式实例化,显式实例化,显式具体化都称为具体化
如果在一个文件中使用一种类型的显式实例和显式具体化会出错
显式实例化
创建实例化
声明创建
template void Swap<int >(int ,int)
使用Swap()模板创建int类型的函数定义
使用函数创建
Add<double>(m,x);
template<typename T>
double Add(T a,T b)
{
return a+b
}
int m= 6;
double x=10.2;
double y=Add<double>(m,x);
double Add(T a,T b)
{
return a+b
}
int m= 6;
double x=10.2;
double y=Add<double>(m,x);
说明
m的类型是int ,直接Add(x,y)会出错的。
Add<double>()创建一个double类型实例化,将参数m强制转
化为double类型(??m实际上类型不改变?创建的是临时变量??)。
化为double类型(??m实际上类型不改变?创建的是临时变量??)。
但是如果对
int m=5;
double x=14.3;
Swap<doubel>(m,x);
//不可以因为 形参是double&,不能只想int 变量m
int m=5;
double x=14.3;
Swap<doubel>(m,x);
//不可以因为 形参是double&,不能只想int 变量m
概念对比
代码
....
template<typename T>
void Swap(T& ,T &);
template<>void Swap<job>(job &,job &);
int main()
{
tempalte void Swap<char>(char & ,char &);//声明的意思:使用Swap()模板生成int类型的函数定义
short a,b;
...
Swap(a,b);//使用隐式实例化
job m,n;
...
Swap(m,n);//使用显示具体化
char g,h;
Swap(g.h);//使用显示实例化,使用处理显示实例化时生成的模板具体化
...
}
对比
显式具体化:
template<>void Swap(job &,job &);
显式实例化:
template void Swap<char>(char &,char &)
template<>void Swap(job &,job &);
显式实例化:
template void Swap<char>(char &,char &)
编译器选择使用哪个函数版本
书p289-未看
模板函数发展
decltype
作用
template<typename T1,typename T2>
void....{....
T1 x;
T2 y;
?type xpy =x+y;//type未知
void....{....
T1 x;
T2 y;
?type xpy =x+y;//type未知
格式
decltype(x+y) xpy
说明
含义是把x+y类型给xpy
decltype(x+y) xpy;
xpy = x+y;
decltype(x+y) xpy = x+y;
xpy = x+y;
decltype(x+y) xpy = x+y;
经常使用decltype(x+y)可以:
typedef decltype(x+y) xytype;
xytype spy;
xytype arr[10];
typedef decltype(x+y) xytype;
xytype spy;
xytype arr[10];
double x =5.5;
const double &rx=x;
decltype(rx) y=x
const double &rx=x;
decltype(rx) y=x
rx类型给y,包括限定符const ,因为rx是引用,所以y要初始化
遍历核对表
书p296
后置返回类型
作用
template<class T1,class T2>
?type? gt(T1 x,T2 y)
{
.....
retuen x+y;
}
?type? gt(T1 x,T2 y)
{
.....
retuen x+y;
}
说明
decltype(x+y)不可以因为x+y后面才出现
格式
double h(int x ,int y)
auto h(int x,inty) ->double;
auto h(int x,inty) ->double;
auto gt(T1 x,T2 y)->decltype( x+y )
内联函数
原理
常规函数调用时,程序来回在跳跃地址,产生的时间长
内联函数,编译器使用相应的函数代码替换函数调用,使函数的编译代码与其它程序内联起来。
优缺点
提高程序运行速度,但是相对的
如果内联的代码执行时间比函数调用机制长(代码长),不要使用
内联代码(代码短)执行时间短,就可以节省很多时间。
内联函数可以应用于代码短,经常使用的函数。
需要占用更多的内存
程序在10个不同的地方调用同一个内联函数,程序包括10个副本
使用格式
函数声明前加上inline关键字
在函数定义前加上inline 关键字
说明
两个条件之一
通常格式
省略原型,把整个定义放在本应提供原型的地方
代码
include<iostream>
include<example.h>//需要头文件?
inline double square(double x) {return x*x}
include<example.h>//需要头文件?
inline double square(double x) {return x*x}
说明
内联函数也是按值传递给内联函数的。//如果在主函数中,
这个内存将会一直在?
这个内存将会一直在?
区别宏
#define double(X) X*X
可能会出现错误,改进:#define double(X) ((X)*(X))
宏是文本替换,不是值传递
如果C语言中的宏执行了类似函数的功能,考虑转换为C++内联函数
引用变量
概念
引用是已定义变量的别名。
作用
用作函数的参数,函数可以使用原始数据,不是副本。
创建
int rats;
int & rodents =rats;
int & rodents =rats;
说明
&不是地址运算符,是类型标识符的一部分,类似于*。
int & 是指向int 的引用
rats , rodents 可以互换,指向相同的值和内存单元
int rats=1;
int & radents = rats;
radents ++;
cout<<rats<<endl;
cout<<radents<<endl;
int & radents = rats;
radents ++;
cout<<rats<<endl;
cout<<radents<<endl;
结果: 2
2
2
必须在声明引用时初始化。
int & rodnets;
rodents = rats;//invalid
rodents = rats;//invalid
int & rodents = rats ;
int * const pr = &rats;
int * const pr = &rats;
实际上,上代码是下代码的伪装,
引用rodents 扮演的角色与表达式 *pr相同
引用rodents 扮演的角色与表达式 *pr相同
int bunnies=50;
rodents=bunnies;
rodents=bunnies;
结果:rats,rodents 的地址不改变,值都变为了50.
这也是不能够不初始化rodents的原因
引用作为函数参数
作用
传递是按引用传递,允许
被调用函数访问调用函数的变量
被调用函数访问调用函数的变量
格式
原型:void sum(int &);
调用:sum(a);
定义:void sum(int & x)
调用:sum(a);
定义:void sum(int & x)
说明
a,x是一个变量,两个名称
可以用于数组吗??没什么特别意义??数组只能是指针,不能引用
传递引用,如果ra是一个变量的别名,则实参应该是该变量。
refcube( x+3.0);
int refcube(int &a)//invalid
int refcube(int &a)//invalid
但是早期的C++允许这样做
临时变量、引用参数和const
临时变量
早期
情况
实参与引用形参不匹配。(类型、变量)
eg:refcube(x+3);
int refcube(int & ra);
程序创建一个临时的无名变量,并初始化为x+3,ra为临时
变量的引用。
临时变量在函数调用后没有了。ra呢??应该也没有了
eg:refcube(x+3);
int refcube(int & ra);
程序创建一个临时的无名变量,并初始化为x+3,ra为临时
变量的引用。
临时变量在函数调用后没有了。ra呢??应该也没有了
修改原因
long a=3,b=4;
swapr(a ,b);
void swapr(int & a,int & b)
类型不匹配,创建临时变量,初始化为3,4,交换将临时变量交换
实际上,a,b没有交换没有意义。
swapr(a ,b);
void swapr(int & a,int & b)
类型不匹配,创建临时变量,初始化为3,4,交换将临时变量交换
实际上,a,b没有交换没有意义。
现在
情况//??
对于引用参数,什么情况下会产生临时变量了?有两种情况:
但注意:当参数为const 引用时,才允许创建临时变量的情况
但注意:当参数为const 引用时,才允许创建临时变量的情况
1.实参类型正确,不是左值。eg:a+b
左值
左值参数是可被引用的数据对象。eg:变量,数组元素,结构成员,引用和解除引用的指针。
不是左值:字面常量(“”的字符串除外),包含多项的表达式。
不是左值:字面常量(“”的字符串除外),包含多项的表达式。
2.实参类型不正确,可以转换为正确的类型
满足1或2条件,且为const变量。
修改原因
使用const 就表示传递的是值,不修改它们。
临时变量不会造成影响,反而时可处理的参数种类更加广泛
临时变量不会造成影响,反而时可处理的参数种类更加广泛
不会造成影响是 :不会达不到本来的意图
这样子跟不传递引用差不多。???//为什么说尽量用const 引用?,
难道用引用会更加省内存??
难道用引用会更加省内存??
右值引用
例子
double &&rref = std::sqrt(36.00);
double j =15;
double && jref = 2.0* j +18.5;
double j =15;
double && jref = 2.0* j +18.5;
目的
让库设计人员能够提供有些操作更加有效
引用用于结构
作用
引用主要用于结构和类,
就算只是使用值,不用创建副本,可以节省内存和时间
就算只是使用值,不用创建副本,可以节省内存和时间
使用方式
和基本变量一样
声明定义
void sum_stu( student & ftu);
sum_stu(stu);
void sum_stu( student & ftu);
sum_stu(stu);
void sum_stu( student & ftu);
使用
ftu.name
返回结构引用
作用
原型:student & accumulate(student &,student &);
dup = accumulate(team,five)
accumulate()中修改team ,返回team
dup = accumulate(team,five)
accumulate()中修改team ,返回team
如果返回是结构,不是结构的引用,把整个结构
复制到一个临时位置,在将这个拷贝复制给 dup。
复制到一个临时位置,在将这个拷贝复制给 dup。
返回结构引用,直接复制team给dup。
此时dup不是team的别名,两者不一样。
此时dup不是team的别名,两者不一样。
格式
student & sum_stu(....);
返回的结构引用
可以作左值
可以作左值
如上accumulate(team,five) = dup; //valid
相当于 team = dup,没有用函数修改team的必要
避免这种当左值不报错的情况,最好用const 返回
注意
const student & clone2(student &ft)
{
student new_guy;
new_guy = ft;
return new_guy;
}
不要返回临时变量(new_guy)的引用。(返回类型是引用时)
//不要返回指向临时变量的指针。(返回类型是指针时)
因为new_guy 执行完程序后不存在。程序不能执行(改成返回结构就能执行)
避免这种情况,应返回作为形参的引用。或用new分配新的内存空间.(指针应用多)
{
student new_guy;
new_guy = ft;
return new_guy;
}
不要返回临时变量(new_guy)的引用。(返回类型是引用时)
//不要返回指向临时变量的指针。(返回类型是指针时)
因为new_guy 执行完程序后不存在。程序不能执行(改成返回结构就能执行)
避免这种情况,应返回作为形参的引用。或用new分配新的内存空间.(指针应用多)
临时变量????
临时变量的生成时机通常是在函数参数传递时发生类型转换,以及函数返回值时被创建
const student & copy(student & ftu)
{
student *pt;
pt = &ftu ;
return *pt;
}//可以
书中说是可以的,
const student & copy(student & ftu)
{
student *pt;
*pt = ftu ;
return *pt;
}
但是我试了,好像不可以。..
但是我试了,好像不可以。..
引用用于类对象
子主题
可以通过引用,函数把类string,ostream,istream,ifstream等类对象作为参数
用法
与上同 ,把类型变成string 就可以
返回类对象引用
ditto
注意
c-风格字符串用作string对象引用参数
string input;
version(input,"***")
string & version(string & s1,const string &s2)
version(input,"***")
string & version(string & s1,const string &s2)
1.string 类定义了一种 char * 到 string 的转换功能,使c-风格字符串可以初始化为string对象
2.const 可以创建一个临时变量,传递一个指向临时变量的引用
所以 形参类型为const string & ,使用的实参可以是引号字符串字面值,以空字符结尾的char
数组,指向char 的指针变量
数组,指向char 的指针变量
对象、继承、引用
继承
继承含义
将特性从一个类传递到另一个类的语言特性
eg:ofstream对象可以使用ostream的方法
ostream是基类,fstream是派生类。派生类继承基类的方法
继承特征
基类引用可以指向派生类对象,
无需进行强制类型转换
无需进行强制类型转换
调用函数实参——ostream \ ofstream 对象
定义函数形参—— ostream 对象
定义函数形参—— ostream 对象
代码
代码
// 功能:把数组输入到文件中和显示到屏幕中
#include<iostream>
#include<fstream>
#include<stdlib.h>
using namespace std;
void file_in(ostream & os , int eps[] ,int n);
const int LIMIT = 5;
int main()
{
int eps[LIMIT]={1,2,3,4,5};
ofstream fout;
fout.open("output.txt");
if( !fout.is_open())
{
cout<<"open error!"<<endl;
exit(EXIT_FAILURE);
}
file_in (fout,eps,LIMIT);
file_in(cout,eps,LIMIT);
fout.close();
return 0;
}
void file_in(ostream & os , int eps[] ,int n)
{
int i;
for(i=0;i<n;i++)
{
os.width(8);
os<<eps[i];
}
}
exit(0);表示在正常下结束程序
exit(1);表示强制结束程序
这在Win32下正确.
不过为了可移植性好,
最好按照ISO2003标准写成:
exit( EXIT_SUCCESS );
exit( EXIT_FAILURE );
其中EXIT_SUCCESS和EXIT_FAILURE是在
cstdlib中定义的.
exit(1);表示强制结束程序
这在Win32下正确.
不过为了可移植性好,
最好按照ISO2003标准写成:
exit( EXIT_SUCCESS );
exit( EXIT_FAILURE );
其中EXIT_SUCCESS和EXIT_FAILURE是在
cstdlib中定义的.
说明
file_in (fout,eps,LIMIT);
file_in(std::cout,eps,LIMIT);
void file_in(std::ostream & os , int eps[] ,int n)
void file_in(std::ostream & os , int eps[] ,int n)
os 代替 cout 和 fout ofstream对象
os.width(8);——字段宽度,只能用于下一个(一个!)输出
os.precision(2);——后显示两个小数点
os.setf(ios::showpoint)——显示小数点模式,即使是整数。
ios_base::fmtflags initial;
initial = os.set(ios_base:: fixed);//save initial formatting state
.......
os.setf(initial);//restore initial formatting state
initial = os.set(ios_base:: fixed);//save initial formatting state
.......
os.setf(initial);//restore initial formatting state
作用
每个对象都存储了自己的格式化设置
当cout被引用时候,中间的os。setf(....) 设置了cout 的格式化。
需要在函数结束时回复cout原来的格式。不要会一直存储着
当cout被引用时候,中间的os。setf(....) 设置了cout 的格式化。
需要在函数结束时回复cout原来的格式。不要会一直存储着
恢复类对象格式
ios_base::fmtflags initial;
initial = os.set(ios_base:: fixed);//save initial formatting state
.......
os.setf(initial);//restore initial formatting state
initial = os.set(ios_base:: fixed);//save initial formatting state
.......
os.setf(initial);//restore initial formatting state
使用
使用引用参数原因
修改调用函数中的数据对象
通过传递引用不是整个数据对象,提高程序的运行效率
指导原则
传递值而不作修改
按值传递
数据对象小,eg内置数据类型、小型结构
指针
数据对象是数组,指针声明为const 的指针
数组只能使用指针
数据对象是较大的结构 ,const 指针
引用
数据对象是较大的结构 ,const 引用
节省复制结构所需要的时间和空间
数据对象是类对象,用const引用
传递类对象的标准方式是按引用传递
修改调用函数中的数据
指针
数据对象是内置数据类型
数据对象是数组,只能使用指针
结构
引用
结构
类对象
修改是修改类对象的格式把。注意复原
默认参数
含义
函数调用时候省略了实参
好处
提高了使用函数的灵活度
定义方法
int harpo(int n, int m =4 ,int j =5);
harpo(2);
int harpo(int n,int m,int j)
harpo(2);
int harpo(int n,int m,int j)
说明
要在函数原型中设置,函数定义与没有默认参数时完全一样
harpo(2) //same as harpo(2,4,5)
harpo(2,6)//same ad harpo(2,6,5)
harpo(2,6)//same ad harpo(2,6,5)
设置失效的情况
int harpo(int n=1 ,int m ,int j =5)// invaild
原因
实参从左到右传递参数,不允许逃过任何参数
harpo(2,,5)是不要可以de
harpo(2,,5)是不要可以de
要求
必须从右到左添加默认值
其它
传递数组(形参是指针) —— 不可以用sizeof()确定数组的长度,
但是形参是字符串指针的时候,可以用strlen()确定字符串的长度
但是形参是字符串指针的时候,可以用strlen()确定字符串的长度
自由主题
0 条评论
下一页