练习6.1:实参和形参的区别是什么?
(标准)
答:
形参出现在函数定义的地方,形参列表可以包含0 、1、 多个形参,多个形参之间以逗号分隔。形参规定了一个函数所接受的数据类型和数量。
实参出现在函数调用的地方,实参的数量和形参一样多。实参的主要作用是初始化形参,并且这种初始化过程是一一对应的,即第一个实参初始化第一个形参,第二个实参初始化第二个形参,以此类推。实参的类型必须与对应的形参类型匹配。
练习6.2:请指出下列函数哪个有错误,为什么?应该如何修改这些错误呢?
略了。
练习6.3:编写你自己的fact函数,上机检查是否正确。
略了。
练习6.4:编写一个与用户交互的函数,要求用户输入一个数字,计算生成数字的阶乘。在main函数中调用该函数。
#include
using namespace std;
int Fact( int Value );
int main()
{
int val = 0, result = 0;
cout << "请输入一个整数来计算它的阶乘:" << endl;
while( cin >> val )
{
if( val >= 0 )
{
result = Fact( val );
cout << val << "的阶乘为:" << result << endl;
}
else
{
cout << "不能计算负数的阶乘。" << endl;
return -1;
}
char c;
cout << "继续吗?Enter y or n" << endl;
cin >> c;
if( !cin || c == 'n' )
break;
cin.clear();
cin.sync();
cout << "请再输入一个整数:" << endl;
}
return 0;
}
//默认实参都大于等于0
int Fact( int Value )
{
int result = 1;
if( Value == 0 )
return 1;
for( int i = 1; i <= Value; ++i )
result *= i;
return result;
}
#include
using namespace std;
unsigned AbsoValue( int val );
int main()
{
int ival;
cout << "请输入一个整数:" << endl;
while( cin >>ival )
{
cout << "这个整数的绝对值是:" << AbsoValue( ival ) << endl;
cout << "请再输入一个整数:" << endl;
}
return 0;
}
unsigned AbsoValue( int val )
{
unsigned result;
result = ( ( val >= 0 )? val : -val );
return result;
}
练习6.6:说明形参、局部变量以及局部静态变量的区别。编写一个函数,同时用到这三种形式。
(标准)
答:
形参和定义在函数体内部的变量统称为局部变量,它们对函数而言是局部的,仅在函数的作用域内可见。函数体内的局部变量又分为普通局部变量和静态局部变量,对于形参和普通局部变量来说,当函数的控制路径经过变量定义语句时创建该对象,当达到定义所在的块末尾时销毁它。我们把只存在于块执行期间的对象称为自动对象。这几个概念的区别是:
形参是一种自动对象,函数开始时为形参申请内存空间,我们用调用函数时提供的实参初始化形参对应的自动对象。
普通变量对应的自动对象在定义该变量的语句处创建自动对象,如果定义语句提供了初始值,则用该初始值初始化;否则,执行默认初始化。
局部静态变量比较特殊,它的生命周期贯穿函数调用之后的时间。局部静态变量对应的对象称为局部静态对象,它的生命周期从定义语句处开始,直到程序结束才终止。
(函数编写就略了)
练习6.7:编写一个函数,当它第一次被调用时返回0,以后每次被调用返回值加1;
#include
using namespace std;
int Count_Call( void );
int main()
{
cout << "第一次调用返回值:" << Count_Call() <
略了。
练习6.9:编写你自己的fact.cc和factMain.cc,这两个文件都应该包含上一小节的练习中编写的Chapter6.h头文件。通过这些文件,理解你的编译器是如何支持分离式编译的。
略了。
练习6.10:编写一个函数,使用指针形参交换两个整数的值。在代码中调用该函数并输出交换后的结果,以此验证函数的正确性。
#include
using namespace std;
void Swap( int *p1, int *p2 );
int main()
{
int a, b;
cout << "请输入两个整数:" << endl;
cin >> a >> b;
cout << "a的值为:" << a << endl;
cout << "b的值为:" << b << endl;
Swap( &a, &b );
cout << "用Swap函数交换后:" << endl;
cout << "a的值为:" << a << endl;
cout << "b的值为:" << b << endl;
return 0;
}
void Swap( int *p1, int *p2 )
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
练习6.11:编写并验证你自己的reset函数,使其作用于引用类型的参数。
略了。
练习6.12:改写练习6.10的程序,使用引用而非指针交换两个整数的值。你觉得哪种方法更易于使用呢?为什么?
#include
using namespace std;
void Swap( int &a, int &b );
int main()
{
int a, b;
cout << "请输入两个整数:" << endl;
cin >> a >> b;
cout << "a的值为:" << a << endl;
cout << "b的值为:" << b << endl;
Swap( a, b );
cout << "用Swap函数交换后:" << endl;
cout << "a的值为:" << a << endl;
cout << "b的值为:" << b << endl;
return 0;
}
void Swap( int &a, int &b )
{
int tmp = a;
a = b;
b = tmp;
}
与使用指针相比,使用引用交换变量的内容的形式看上去更简单些。并且无须额外声明指针变量,也避免了拷贝指针的值。
一个是 void f(T) ,另一个是 void f( &T )
前者形参采用的是传值方式,实参拷贝给形参,后者采用的是传引用方式,形参是实参的引用。
练习6.14:举一个形参应该是引用类型的例子,再举一个形参不能是引用类型的例子。
与值传递相比,引用传递的优势主要体现在3个方面:一是可以直接操作形参所引用的对象;二是使用引用形参可以避免拷贝大的类类型对象或者容器类型对象;三是使用形参可以帮助我们从函数中返回多个值。
练习6.15:说明find_char函数中的三个形参为什么是现在的类型,特别说明为什么s是常量引用而occurs是普通引用?为什么s和occurs是引用类型而c不是?如果令s是普通引用会发生什么情况?如果令occurs是常量引用会发生什么情况?
答:第一个参数const string &s,使用引用是因为避免string的拷贝,string有可能是比较大的string,应该避免拷贝。而且这个是常量引用是因为只需要访问该string对象而不需要对其修改,使用常量引用保证安全性。
第二个参数char c,接受一个字符,很小,值传递,拷贝很小。而且也不需要实参字符对象做任何操作。
第三个参数是常量引用,因为函数需要改变occurs的值表示字符出现次数,所以不能使用常量引用。
练习6.16:下面的这个函数虽然合法,但是不算特别有用。指出它的局限性并设法完善。
局限性:会让人以为string对象是可以修改的,而且,更重要的是,如果只是普通引用,就无法接受常量的实参。故修改为:
bool is_empty( cosnt string &s ) { return s.empty(); }
练习6.17:编写一个函数,判断string对象中是否含有大写字母。编写另一个函数,把string对象全都改写成小写形式。在这两个函数中,你使用的形参类型相同吗?为什么?
#include
#include
using namespace std;
bool Has_Upper( const string &s );
string& Lower_Str( string &s );
int main()
{
string str;
cout << "请输入一个字符串:" << endl;
while( cin >> str )
{
cout << "你输入的字符串是:" << str << endl;
cout << ( ( Has_Upper( str ) == 1 ) ? "含有" : "没有" ) << "大写字母" << endl;
str = Lower_Str( str );
cout << "小写形式是:" << str << endl;
cout << "请输入下一个字符串:" << endl;
}
return 0;
}
bool Has_Upper( const string &s )
{
bool Exist = 0;
for( auto &c : s )
if( isupper( c ) )
{
Exist = 1;
break;
}
return Exist;
}
string& Lower_Str( string &s )
{
for( auto &c : s )
if( isupper( c ) )
c = tolower( c );
return s;
}
练习6.18:为下面的函数编写函数声明,从给定的名字中推测函数具备的功能。
(a)名为compare的函数,返回布尔值,两个参数都是matrix类的引用。
bool compare( const matrix &a, const matrix &b);
(b)名为change_val的函数,返回vector
vector
练习6.19:假定有如下的声明,判断哪个调用合法、哪个调用不合法。对于不合法的函数调用,说明原因。
double calc( double );
int count( const string&, char );
int sum( vector
vector
(a)
calc( 23.4, 55.1 );
不合法,实参数量超过形参数量。
(b)count( "abcda" , 'a' );
合法。
(c) calc(66);
合法。
(d)sum( vec.begin(), vec,end() , 3.8);
合法。
练习6.20:引用形参什么时候应该是常量引用?如果形参应该是常量引用,而我们将其设为了普通引用,会发生什么情况?
答:同练习6.16 。
练习6.21:编写一个函数,令其接受两个参数:一个是int型的数,一个是int指针。函数比较int的值和指针所指的值,返回较大的那个。在该函数中指针的类型应该是什么?
#include
#include
#include
using namespace std;
int myCompare( int, int* );
int main()
{
srand( ( unsigned ) time ( NULL ) );
int i = rand()% 100;
int *ip = &i;
int inp;
cout << "请输入一个数:" << endl;
cin >> inp;
cout << "系统生成的随机数是:" << i << endl;
cout << "你输入的数和系统的随机数较大的是:" << myCompare( inp, ip ) << endl;
return 0;
}
int myCompare( int a, int *p )
{
return ( ( a > *p )? a : *p );
}
练习6.22:编写一个函数,令其交换两个 int 指针。
#include
using namespace std;
void exchange_pointers( int* &, int* &); //交换指针的值,也就是指针保存的地址
int main()
{
int a = 13, b = 5;
int *p1 = &a, *p2 = &b;
cout << "p1保存的地址为:" << p1 << endl;
cout << "p2保存的地址为:" << p2 << endl;
cout << "解引用p1的结果为:" << *p1 << endl;
cout << "解引用p2的结果为:" << *p2 << endl;
exchange_pointers( p1, p2 );
cout << "交换指针的值后:" << endl;
cout << "p1保存的地址为:" << p1 << endl;
cout << "p2保存的地址为:" << p2 << endl;
cout << "解引用p1的结果为:" << *p1 << endl;
cout << "解引用p2的结果为:" << *p2 << endl;
return 0;
}
void exchange_pointers( int* &p1, int* &p2 )
{
int *tmp = p1;
p1 = p2;
p2 = tmp;
}
略了。
练习6.24:描述下面这个函数的行为。如果代码中存在问题,请指出并改正。
行为:原意是打印出一个含有10个元素的int数组的每一个元素。
形参const int ia[10] 只表示我们期望数组含有几个元素,实际上并不一定。所以说,实参可以是小于10个元素的int数组导致访问超出数组下标范围的内存的错误。
修改程序略了。
练习6.25:编写一个main函数,令其接受两个实参。把实参的内容连接成一个string对象并输出出来。
#include
using namespace std;
int main( int argc, char **argv )
{
string str;
cout << "字符串数量:" << argc << endl;
cout << argv[0] << endl;
for( int i = 0; i != argc; ++i )
{
cout << argv[i] << endl;
str += argv[i];
}
cout << str << endl;
return 0;
}
main函数实参要通过命令行输入。
练习6.27:编写一个函数,它的参数是initializer_list
intitializer_list对象中的元素永远是常量值。
#include
using namespace std;
int IL_Sum( initializer_list il );
int main()
{
cout << IL_Sum( { 1, 2, 3, 4, 5 } );
return 0;
}
int IL_Sum( initializer_list il )
{
int sum = 0;
for( auto &i : il )
sum += i;
return sum;
}
const string &类型
练习6.29:在范围for循环中使用initializer_list对象时,应该将循环控制变量声明成引用类型吗?为什么?
引用类型的优势主要是可以直接操作所引用的对象以及避免拷贝较为复杂的类类型对象和容器对象。因为initializer_list对象的元素永远是常量值,所以我们不可能通过设定引用类型来更改循环控制变量的内容。只有当initializer_list的元素是类类型或容器类型才有必要设为引用类型。