前言:第二章内容没有总结完,继续!(有点长哦,可以分开学)
一.内联函数:
在函数说明前,冠以关键字"inline”,该函数就被声明为内联函数,又称内置函数。每当程序中出现对该函数的调用时,C++编译器使用函数体中的代码插入到调用该函数的语句处,同时用实参数代替形参,以便在程序运行时不再进行函数调用。
内联函数和普通函数在写法上就只有一个区别,运行结果也是一样的,那么普通函数和内联函数有什么区别呢?
每当程序中出现对内联函数的调用时,C++编译器使用函数体中的代码替代函数调用表达式,这样能加快代码的执行,减少调用开销。
注意:
(1) 内联函数在第1次被调用之前必须进行完整的定义,否则编译器将无法知道应该插入什么代码。
(2) 在内联函数体内一般不能含有复杂的控制语句,如for语句和switch语句等。
(3) 若内联函数较长,且调用太频繁时,程序将加长很多。通常只有规模很小(一般为1~5条语句)而使用频繁的函数才定义为内联函数, 这样可大大提高运行速度。
(4) C++的内联函数具有与C中的宏定义 #define相同的作用和相似的机理,但消除了#define的不安全因素。
补充:
1.在现代编译器中,无论是否使用内联标记,编译器都会根据实际优化自动内联某些满足要求的函数。
2.内联标记的函数体过大时,将按普通函数处理。
二.带有默认参数的函数
C++在说明函数原型时,可为参数指定默认参数值,以后调用此函数时,若省略其中某一参数,C++自动地以默认值作为相应参数的值,如果既有函数原型,又有函数定义,则只能放在函数原型中。
例如:
函数原型说明为:
int special(int x=5,float y=5.3);
以下的函数调用都是允许的:
special( ); —> x=5, y=5.3
special(25); —>x=25, y=5.3
special(100,79.8);—>x=100,y=79.8
注意:
(1)在声明函数时,所有指定默认值的参数都必须出现在不指定默认值的参数的右边,否则出错。
例如:
int fun(int i, int j=5, int k); ×
可改为:
int fun(int i, int k, int j=5);
(2) 在函数调用时,若某个参数省略,则其后的参数皆应省略而采用默认值。不允许某个参数省略后,再给其后的参数指定参数值。
例如不允许出现以下调用:
special( ,21,5); ×
(3)如果既有函数原型,又有函数定义,则只能放在函数原型中。如果函数的定义在函数调用之前,且没有函数原型时,则应在函数定义中指定默认值。
(4) 在函数原型中默认参数可以不包含参数的名字
如:
#include<iostream>
using namespace std;
void write( int =5);//没有参数名
void main( )
{
write( );
}
void write(int a)
{ cout<<a; }
三.函数的重载
在传统的C语言中,函数名必须是唯一的,也就是说在同一作用域中,不允许出现同名的函数。而在C++中,允许两个或者两个以上的函数共用一个函数名。
注意:重载函数应在参数个数或参数类型上有所不同,或者二者兼而有之
注意:
(1)若两个函数的参数个数和类型都相同,而只有返回值类型不同,则不允许重载。(因为虽然这两个函数的返回值类型不同,但是参数个数和类型完全相同,编译程序将无法区分这两个函数)
例如:
int mul(int x,int y);
double mul(int x,int y);
(2) 函数的重载与带默认值的函数一起使用时,有可能引起二义性,导致程序报错,例如:
void Drawcircle(int r=0, int x=0, int y=0);
void Drawcircle(int r);
当执行以下的函数调用时:
Drawcircle(20);
编译器无法确定使用哪一个函数。
(3) 在函数调用时,如果给出的实参和形参类型不相符,C++的编译器会自动地做类型转换工作。如果转换成功,则程序继续执行。 但是,在这种情况下,有可能产生不可识别的错误。
例如,有两个函数的原型如下:
void f_a(int x);
void f_a(long x);
如果我们用下面的数据去调用,就会出现不可识别的错误:
int c=f_a(5.56);
例1:
#include<iostream>
using namespace std;
int mul(int x,int y)
{ return x+y;}
int mul(int x,int y,int z)
{ return x+y+z;}
void main()
{
int a=3,b=4,c=5;
cout<<a<< "+" <<b<< "=" <<mul(a,b)<<endl;
cout<<a<< "+" <<b<< "+" <<c<< "=";
cout<<mul(a,b,c)<<endl;
}
例2:
#include<iostream>
#include<math.h>
using namespace std;
int sroot(int );
long int sroot(long int );
double sroot(double );
int main()
{
int i=16;
long int l=1234;
double d=5.67;
cout<<i<<"的二次方根为:"<<sroot(i)<<endl;
cout<<l<<"的二次方根为:"<<sroot(l)<<endl;
cout<<d<<"的二次方根为:"<<sroot(d)<<endl;
return 0;
}
int sroot(int i)
{
return sqrt(float(i));
}
long int sroot(long int l)
{
return sqrt(float(l));
}
double sroot(double d)
{
return sqrt(d);
}
//使用sqrt时,参数不能为整数
四.作用域运算符∷
#include<iostream>
using namespace std;
int avar=10; //全局变量avar
int main()
{ int avar; //局部变量avar
avar=25;
cout<<"avar is" <<avar<<endl;
return 0;
}
运行结果为:avar is 25
那么问题来了:如何才能将全局变量avar的值打印出来呢?
#include<iostream>
using namespace std;
int avar=10; //全局变量avar
int main( )
{ int avar;
avar=25; //局部变量avar
cout<<"local avar ="<<avar<<endl;
cout<<"global avar ="<<::avar<<endl;
return 0;
}
运行结果为:local avar=25 global avar=10
原因是:局部变量在其作用域内具有较高的优先权,它将屏蔽全局变量。所以加上“::”即可。
五.运算符new和delete
C语言使用函数malloc()和free()动态分配内存和释放动态分配的内存。
C++使用运算符new和delete更好、更简单地进行内存的分配和释放。
运算符new用于内存分配的最基本形式为: 指针变量名=new 类型
运算符new就从存储区中为程序分配一块与类型字节数相适应的内存空间,并将该块内存的首地址存于指针变量中。
例如:
int *p=new int [10]//定义一个int 型指针指向一个有10个元素的数组
int *p=new int(10)//定义一个int型的指针指向一个被赋值为10的整形存储单元
运算符delete用于释放new分配的存储空间的使用形式一般为:
delete 指针变量名 ;
delete []p;//释放数组的存储单元
delete p;//释放一个整形数字的存储单元
例1:
#include<iostream>
using namespace std;
int main()
{
int *p; //声明一个整型指针变量p
p=new int; //动态分配一个int型存储区,
//并将首地址赋给p
*p=10;
cout<<*p;
delete p; //释放p指向的存贮空间
return 0;
}
例2:斐波拉契数列
#include<iostream>
using namespace std;
int main()
{
int *F=new int[20];
int i;
F[0]=0;
F[1]=1;
for(i=2;i<20;i++)
{
F[i]=F[i-1]+F[i-2];
}
for(i=0;i<20;i++)
{
cout<<F[i]<<" ";
if((i+1)%5==0)
cout<<endl;
}
delete []F;
return 0;
}
注意:
(1) 使用new分配的空间,使用结束后应该也只能用delete显式地释放,否则这部分空间将不能回收而变成死空间。
(2) new可在为简单变量分配内存空间的同时,进行初始化。其基本形式为:
指针变量名 = new 类型(初值) ,初始值放在"类型”后面的圆括号内。
(3) 使用new可以为数组动态分配内存空间,这时需要在类型名后面缀上数组大小。如:char *p=new char[10];
(4) new可在为简单变量分配内存空间的同时,进行初始化。但不能为数组分配内存空间的同时,进行初始化。
**补充:**如何正确分配动态分配多维数组?
//二维数组
int main()
{
int **array2D;
int m=3;
int n=2;//假定数组第一维长度为m,第二维长度为n
array2D=new int *[m];
for(int i=0;i<m;i++)
{
array2D[i]=new int [n];
}
for(int i=0;i<m;i++)
{
delete []array2D[i];
}
delete array2D;
return 0;
}
//三维数组
int main()
{
int ***array3D;
int m,n,h;
m=n=h=2;//假定数组第一维为m,第二维为n,第三维为h
array3D=new int **[m];
for(int i=0;i<m;i++)
{
array3D[i]=new int *[n];
for(int j=0;j<n;j++)
{
array3D[i][j]=new int [h];
}
}
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
delete array3D[i][j];
delete array3D[i];
}
delete array3D;
return 0;
}
(6) 使用new动态分配内存时,如果没有足够的内存满足分配要求,new将返回空指针(NULL)。 NULL为空指针常数,通常是 0。
例如:如果没有足够的内存满足分配要求,new将返回空指针(NULL)
若动态分配内存成功,将在屏幕上显示: 20
若动态分配内存失败,将在屏幕上显示: allocation failure
#include<iostream>
using namespace std;
int main()
{ int *p;
p=new int;
if (!p)
{ cout <<"allocation failure\n";
return 1;}
*p=20;
cout<<*p;
delete p;
return 0;}
六.引用
概念:建立引用的作用是为变量另起一个名字,变量的引用通常被认为是变量的别名。
声明一个引用的格式如下:
类型 &引用名 = 已定义的变量名;
例如:
int i=5;
int &j = i;
这里,声明了一个整数类型的引用j ,用整型变量i对它进行初始化,这时j就可看做是变量i的引用,即是变量i的别名。也就是说,变量i和引用j占用内存的同一位置。当i变化时,j也随之变化,反之亦然。
#include<iostream>
using namespace std;
int main()
{ int i=10;
int &j=i; //声明j是一个整型变量i的引用
i=30;
cout<<"i="<<i<<" j="<<j<<endl;
j=80;
cout<<"i="<<i<<" j="<<j<<endl;
return 0;
}
运行结果:
i=30 j=30 变量i的值变化了,引用j的值也随着变量i的值一起变化
i=80 j=80 引用j的值变化了,变量i的值也随着引用j的值一起变化
#include<iostream>
using namespace std;
int main()
{ int i=10;
int &j=i; //声明j是一个整型变量i的引用
//这时j就可看作是变量i的别名
cout<<"i="<<i<<" j="<<j<<endl;
cout<<"变量i的地址:"<<&i<<endl;//输出变量i的地址
cout<<"引用j的地址:"<<&j<<endl;//输出引用j的地址
return 0;
}
运行结果:
i=10 j=10
变量i的地址:0012FF7C
引用j的地址:0012FF7C//不同编译器地址不同,但i和j的地址一定相同
变量作为函数参数
该过程并没有实现a,b值的交换(传值调用)
#include <iostream.h>
void swap(int m,int n)
{
int temp;
temp=m; m=n; n=temp;
}
int main()
{ int a=5,b=10;
cout<<"a="<<a<<" b="<<b<<endl;
swap(a,b);
cout<<"a="<<a<<" b="<<b<<endl;
return 0; }
指针作为函数参数
实现交换(传址调用)
#include <iostream.h>
void swap(int *m,int *n)
{
int temp;
temp=*m; *m=*n;
*n=temp;
}
main()
{ int a=5,b=10;
cout<<"a="<<a<<" b="<<b<<endl;
swap(&a,&b);
cout<<"a="<<a<<" b="<<b<<endl;
return 0; }
引用作为函数参数
实现交换(传址调用)
#include <iostream.h>
void swap(int &m,int &n)
{
int temp;
temp=m; m=n; n=temp;
}
main()
{ int a=5,b=10;
cout<<"a="<<a<<" b="<<b<<endl;
swap(a,b);
cout<<"a="<<a<<" b="<<b<<endl;
return 0; }
使用引用返回函数值
#include<iostream>
using namespace std;
int a[ ]={1,3,5,7,9};
int& index(int i)
{ return a[i] ;}
int main()
{
cout<<index(2)<<endl;
index(2)=25;
cout<<index(2);
return 0;
}
运行结果为5 25
注意:
(1) 对变量声明一个引用,编译系统不给它单独分配存储单元,i和j都代表同一变量单元。
(2) 引用并不是一种独立的数据类型,它必须与某一种类型的变量相联系。在声明引用时,必须立即对它进行初始化,不能声明完成后再赋值。
例如下述声明是错误的。
int i=10;
int &j; //错误, 没有指定j代表哪个变量
j=i; //不能声明完成后再赋值
double a;
int &b=a; 这个也是错误的,声明b是一个整型变量的别名,而a不是整型变量
(3) 为引用提供的初始值,可以是一个变量或另一个引用。例如:
int i=5; //定义整型变量i
int &j1=i; //声明j1是整型变量i的引用(别名)
int &j2=j1; //声明j2是整型引用j1的引用(别名)
这样定义后,变量i有两个别名:j1和j2。
(4) 指针是通过地址间接访问某个变量,需要书写间接运算符"”;
引用是通过别名直接访问某个变量。
每次使用引用时,可以不用书写间接运算符"”,因而使用引用可以简化程序。
(5)指针和引用
#include<iostream>
using namespace std;
int main()
{ int i=15; //定义整型变量i,赋初值为15
int *iptr=&i; //定义指针变量iptr,将变量i的地址赋给iptr
int &rptr=i; //声明变量i的引用rptr,rptr是变量i的别名
cout<<″i is ″<<i<<endl; //输出i的值
cout<<″*iptr is ″<<*iptr<<endl; //输出*iptr的值
cout<<″rptr is ″<<rptr<<endl; //输出rptr的值
return 0;
}
运行结果如下:
i is 15
*iptr is 15
rptr is 15
从这个程序可以看出,如果要使用指针变量iptr所指的变量i,必须用"”来间接引用指针;而使用引用rptr所代表的变量i,不必书写间接引用运算符"”。
(5) 引用在初始化后不能再被重新声明为另一个变量的引用(别名)。
例如:
int i,k; //定义i和k是整型变量
int &j=i; //声明j是整型变量i的引用(别名)
j=&k; //错误,企图重新声明j
//是整型变量k的引用(别名)
(6) 尽管引用运算符"&”与地址操作符"&”使用相同的符号,但是它们是不一样的。
引用运算符"&”仅在声明引用时使用。
其他场合使用的"&”都是地址操作符
例如:
int j=5;
int &i=j; //声明引用i,"&”为引用运算符
i=123; //使用引用i,不带引用运算符
int *pi=&i; // 在此,"&”为地址操作符
cout<<π // 在此,"&”为地址操作符
(7)不允许建立void类型的引用
例如:void &r=10;错误
(8)不能建立引用的数组
例如:int a[4]=“abcd”;
int &ra[4]=a;错误
(9)不能建立引用的引用,不能建立指向引用的指针
例如:
int n=3;
int &&r=n;
int &*p=n;
(10)可以建立指针变量的引用
例如:
int i=5; //定义整型变量i,初值为5
int *p=&i; //定义指针变量p,指向i
int* &pt=p; //pt是一个指向整型变量的
指针变量p的引用
如果输出*pt值,就是*p的值5。
(11)可以用const对引用加以限定,不允许改变该引用的值
例如;
int i=5;
const int &a=i;//声明常引用,不允许改变引用a的值
a=3; //错误,企图改变引用a的值
但是这个引用所代表的变量的值可以改变
例如: i=3;
此时输出的i和a的值都是3。
这一特性在用作函数形参,保护形参值不被改变时很有用的。