b=b/5; //b的值变化了,a的值也应一起变化
cout<
return0;
}
a的值开始为1O,b是a的引用,它的值当然也应该是10,当a的值变为100(a。
a的值)时,b的值也随之变为100,在输出a和b的值后,b的值变为20,显然a的值也应为20。
运行记录如下:
100100
2020
3.关于引用的简单说明
(1)引用并不是一种独立的数据类型,它必须与某一种类型的数据相联系。
声明引用时必须指定它代表的是哪个变量,即对它初始化。
(2)引用与其所代表的变量共享同一内存单元,系统并不为引用另外分配存储空间。
实际上,编译系统使引用和其代表的变量具有相同的地址。
(3)当看到&a这样的形式时,怎样区别是声明引用变量还是取地址的操作呢?
请记住,当及a的前面有类型符时(如int &a),它必然是对引用的声明;如果前面没有类型符(如p=&a),此时的&是取地址运算符。
(4)对引用的初始化,可以用一个变量名,也可以用另一个引用。
(5)引用在初始化后不能再被重新声明为另一变量的别名。
实际上,在C++程序中很少使用独立变量的引用,如果要使用某一个变量,就直接使用它的原名,没有必要故意使用它的别名。
前面举的例子只是为了说明引用的特征和基本的用法。
那么有了变量名,为什么还需要一个别名呢?
。
4.将引用作为函数参数
C++之所以增加“引用”,主要是利用它作为函数参数,以扩充函数传递数据的功能。
在C语言中,函数的参数传递有以下两种情况。
(1)将变量名作为实参。
这时传给形参的是变量的值。
传递是单向的,在执行函数期间形参值发生变化并不传回给实参,因为在调用函数时,形参和实参不是同一个存储单元。
下面的程序无法实现两个变量的值互换。
例10无法实现两个变量的值互换的程序。
#include
usingnamespacestd;
voidswap(inta,intb)
{inttemp;
temp=a;
a=b;
b=temp;//实现a和b的值互换
}
intmain()
{ inti=3,j=5;
swap(i,j);
cout<
return0;
}
输出i和j的值仍为3和5。
见图2,图2(a)表示调用函数时的数据传递,图2(b)是执行swap函数体后的情况,a和b值的改变不会影响i和j的值。
图2:
i、j值未交换, 图3:
i、j值交换,
(2)传递变量的指针。
为了解决上面这个问题,在C程序中可以用传递变量地址的方法。
使形参得到一个变量的地址,这时形参指针变量指向实参变量单元。
例11使用指针变量作形参,实现两个变量的值互换。
#include
usingnamespacestd;
voidswap(int*p1,int*p2)
{ inttemp;
temp=*p1;
*p1=*p2;
*p2=temp;
}
intmain()
{ inti=3,j=5;
swap(&i,&j);
cout<
return0;
}
(3)传送变量的别名。
C++把变量的引用作为函数形参,就弥补了上面的不足。
这就是向函数传递数据的第三种方法,即传送变量的别名。
例12利用“引用形参”实现两个变量的值互换。
#include
usingnamespacestd;
voidswap(int&a,int&b)
{inttemp;
temp=a;
a=b;
b=temp;
}
intmain()
{ inti=3,j=5;
swap(i,j);
cout<<"i="<
return0;
}
输出结果为i=5j=3
图4:
别名实现变量的交换
分析使用引用和使用指针变量作函数形参有什么不同?
可以发现:
①不必在swap函数中设立指针变量,指针变量要另外开辟内存单元,其内容是地址。
而引用不是一个独立的变量,不单独占内存单元,在本例中其值为一整数。
②在main函数中调用swap函数时实参不必在变量名前加&以表示地址。
这种传递方式相当于Pascal语言中的“变量形参”,系统传送的是实参的地址而不是实参的值。
③使用指针变量时,为了表示指针变量所指向的变量,必须使用指针运算符。
(如例11程序内swap函数中的。
pl,*p2),而使用引用时,引用就代表该变量,不必使用指针运算符*(见例12程序内swap函数)。
对比例11和例12中的swap函数,可以发现例12中的swap函数比例11中的swap函数简单。
④用引用能完成的工作,用指针也能完成。
但引用比指针的使用直观、方便,直截了当,不必“兜圈子”,容易理解。
有些过去只能用指针来处理的问题,现在可以用引用来代替,从而降低了程序设计的难度。
5.对引用的进一步说明
(1)不能建立void类型的引用,如:
void&a=9;//错误
因为任何实际存在的变量都是属于非void类型的,void的含义是无类型或空类型,void只是在语法上相当于一个类型而已。
(2)不能建立引用的数组。
如:
charc[6]="hello";
char&rc[6]=c;//错误
企图建立一个包含6个元素的引用的数组,这样是不行的,数组名C只代表数组首元素的地址,本身并不是一个占有存储空间的变量。
(3)可以将变量的引用的地址赋给一个指针,此时指针指向的是原来的变量。
如:
inta=3; //定义a是整型变量
int&b=a;//声明b是整型变量的别名
int*P=&b;//指针变量p指向变量a的引用b,相当于指向a,合法
相当于p指向变量a,其作用与下面一行相同,
即:
int*p=&a;
如果输出*p的值,就是b的值,也就是a的值。
但是不能定义指向引用类型的指针变量,不能写成:
int&*p=&a;//企图定义指向引用类型的指针变量P,错误
由于引用不是一种独立的数据类型,因此不能建立指向引用类型的指针变量。
(4)可以建立指针变量的引用。
如:
inti=5; //定义整型变量i,初值为5
int*p=&i //定义指针变量P,指向i
int*&pt=p;//pt是一个指向整型变量的指针变量的引用,初始化为p
从定义的形式可以看出,&pt表示pt是一个变量的引用,它代表一个int,类型的数据对象(即指针变量),如果输出。
pt的值,就是*p的值5。
(5)可以用const对引用加以限定,不允许改变该引用的值。
如:
inti=5; //定义整型变量i,初值为5
constint&a=i;//声明常引用,不允许改变a的值
a=3; //企图改变引用a的值,错误
但是它并不阻止改变引用所代表的变量的值,如
i=3;//合法 此时输出i和a的值都是3。
这一特征在使用引用作为函数形参时是有用的,因为有时希望保护形参的值不被改变,在第3章中将会看到它的应用。
(6)可以用常量或表达式对引用进行初始化,但此时必须用const作声明。
如:
inti=5;
const&a=i+3;//合法
此时编译系统是这样处理的:
生成一个临时变量,用来存放该表达式的值,引用是该临时变量的别名。
系统将"const=&a=i+3"转换为:
inttemp=i+3;//先将表达式的值存放在临时变量temp中
constint&a=temp;//声明a是temp的别名
注意:
此时如果输出引用a的值,将是3而不是3.1415926。
因为从根本来说,只能对变量建立引用。
如果在上面声明引用时不用const,则会发生错误。
如
doubled=3.1415926;//d是double类型变量
int&a=d; //未加const,错误
为什么呢?
若允许这样做的话,如果修改了引用a的值(例如a=6.28;),则临时变量temp的值也变为6,即修改了临时变量temp的值,但不能修改变量d的值,这往往不是用户所希望的,即存在二义性。
与其允许修改引用的值而不能实现用户的目的,还不如不允许修改引用的值。
这就是c++规定对这类引用必须加const的原因。
C++提供的引用机制是非常有用的,尤其用作函数参数时,比用指针简单、易于理解,而且可以减少出错机会,提高程序的执行效率,在许多情况下能代替指针的操作。
在本书的第3章和第4章中会有具体的使用例子。
八、内置函数
调用函数时需要一定的时间,如果有的函数需要频繁使用,则累计所用时间会很长,从而降低程序的执行效率,C++提供一种提高效率的方法,即在编译时将所调用函数的代码嵌入到主函数中。
这种嵌入到主函数中的函数称为内置函数(inlinefunction),又称内嵌函数。
在有些书中把它译成内联函数。
指定内置函数的方法很简单,只须在函数首行的左端加一个关键字inline即可。
例13将函数指定为内置函数。
#include
usingnamespacestd;
inlineintmax(inta,intb,intc)//这是一个内置函数,求3个整数中的最大者
{ if(b>a)a=b;
if(c>a)a=c;
returna;
}
intmain()
{inti=7,j=10,k=25,m;
m=max(i,j,k);
cout<<"max="< return0;
}
由于在定义函数时指定它为内置函数,因此编译系统在遇到函数调用nmax(i,j,k)时,就用max函数体的代码代替max(i,j,k),同时将实参代替形参。
这样,m=max(i,j,k)就被置换成
a=i;b=j;c=k;
if(b>a)a=b;
if(c>a)a=c;
m=a;
内置函数与用#define命令实现的带参宏定义有些相似,但不完全相同。
宏定义是在编译前由预处理程序对其预处理的,它只作简单的字符置换而不作语法检查,往往会出现意想不到的错误。
例14用带参宏定义实现求平方值,
#include
usingnamespacestd;
#definepower(x)x*x
intmain()
{cout<(2)< cout< return0;
}
本来程序编写者希望两个cout语句都输出2的平方值,但运行结果却是:
4(输出power(g)的值)
3(输出power(1+1)的值)
第2个结果显然不是程序设计者所希望的,原因是在进行宏替换时只是简单地将字符“1+l”取代x,因此power(1+1)被置换为1+1*l+1,结果为3。
如果不用#define而用内置函数,也可以达到同样的目的,但避免了上面的副作用。
例14程序可改为:
例15用内置函数实现求平方值。
#inclde
useingnamespacestd;
inlineintpower(intx)//改用内置函数
{ returnx*x;}
intmain()
{cout<(2)< cout< returnO;
}
运行结果是
4(输出power
(2)的值)
4(输出power(1+1)的值)
可以看到:
1、用带参宏定义和内置函数都可以实行置换,但具体的做法不同,用内置函数可以达到用#define宏置换的门的,但不会出现带参宏定义的副作用。
2、使用内置函数可以节省运行时间,但却增加了目标程序的长度。
九、作用域运算符
每一个变量都有其确有效的作用域,只能在变量的作用域内使用该变量,不能直接使用其他作用域中的变量。
见例6。
例16局部变量和全局变量同名。
#include
usingnamespacestd;
floata=13.5;//全局变量a
intmain()
{ inta=5; //主函数内变量a
cout< cout<<:
:
a< return0; }
“:
:
”a表示全局作用域中的变量a。
请注意:
不能用“:
:
”访问函数中的局部变量。
十、字符串变量
除了可以使用字符数组处理字符串外,C++还提供了一种更方便的方法—用字符串类型(string类型)定义字符串变量。
实际上,string并不是C++语言本身具有的基本类型(而char,int,float,double等是C++本身提供的基本类型),它是在c++标准库中声明的一个字符串类,用这种类可以定义对象。
1.定义字符串变量
和其他类型变量一样,字符串变量必须先定义后使用,定义字符串变量要用类名strmg。
如:
stringstringl; //定义stringl为字符串变量
stringstring2="China";//定义string2同时对其初始化
可以看出,这和定义char,int,float,double等类型变量的方法是类似的。
应当注意:
要使用string类的功能时,必须在本文件的开头将c++标准库中的“string”头文件包含进来,即应加上#include//注意头文件名不是"string.h这一点是与定义基本数据类型变量所不同的。
2.对字符串变量的赋值
在定义了字符串变量后,可以用赋值语句对它赋以一个字符串常量,
如:
string1="Canada";
而用字符数组时是不能像下面这样做的
charstr[10];
str="Hello!
";//错误
既可以用字符串常量给字符串变量赋值,也可以用一个字符串变量给另一个字符串变量赋值。
如
string2=stringl;//假设string2.和stringl均已定义为字符串变量
可以对字符串变量中某一字符进行操作,如
stringword="Then";//定义并初始化字符串变量word
word[2]='a'; //修改序号为2的字符,修改后word的值为"Than"
前面已说明,字符串常量以“\o”作为结束符,但将字符串常量存放到字符串变量中时,只存放字符串本身而不包括“\o”。
因此字符串变量word中的字符为"Than”(共4个字符)而不是"Than"再加“\0”。
3.字符串变量的输入输出
可以在输入输出语句中用字符串变量名输入输出字符串,如
cin>>string1;//从键盘输入一个字符串给字符串变量stfingl
cout<4.字符串变量的运算
在用字符数组存放字符串时,字符串的运算要用字符串函数,如strcat(连接),strcmp(比较),strcpy(复制),而对stung类对象,可以不用这些函数,而直接用简单的运算符。
(1)用赋值运算符实现字符串复制
stringl=string2;其作用与“strcpy(string1,string2);”相同。
(2)用加法运算符实现字符串连接
stringstring1="C++";//定义string[并赋初值
stringstring2="Language";//定义string2并赋初值
stnngl=string1十sane2;//连接string1和string2
连接后string1为"C++Language"。
(3)用关系运算符实现字符串比较
可以直接用==(等于)、,(大于)、<(小于)、!
=(不等于)、>=(大于或等于)、<=(小于或等于)等关系运算符来进行字符串的比较。
5.字符串数组
不仅可以用string定义字符串变量,也可以用string定义字符串数组。
如
stringname[5];//定义一个字符串数组,它包含5个字符串元素
stringname[5]={"Zhang","Li","Fun","Wang","Tan"};//定义一个字符中数组并初始化此时name数组的状况如图5所示。
图5
可以看到:
(1)在一个字符串数组中包含若干个(今为5个)元素,每个元素相当于—个字符串变量。
(2)并不要求每个字符串元素具有相同的K度,即使对同一个元素而言,它的长度也是可以变化的,当向某一个元素重新赋值时,其长度就可能发生变化,,
(3)在字符串数组的每一个元素中存放一个字符串,而不是一个字符,这是字符串数组与字符数组的区别。
如果用字符数组存放字符串,一个元素只能存放一个字符,用一个一维字符数组存放一个字符申。
(4)每一个字符中元素中只包含字符串本身的字符而不包括"\0"。
可见用字符串数组存放字符串以及对字符串进行处理是很方便的,使用户感到更加直观,简化了操作,提高了效率。
例17输入3个字符串,要求按字母由小到大顺序输出。
对于将3个整数按由小到大顺序输出,是很容易处理的。
可以按照同样的算法将3个字符串按由小到大顺序输出。
程序如下:
#include
#include
usingnamespacestd;
intmain()
{ stringstring1,string2,string3,temp;
cout<<"pleaseinputthreestrings:
";//这是对用户输入的提示
cin>>string1>>string2>>string3;