C++ Primer 第五版 练习习题陪学
文章目录
第六章
(前面因为一些原因,暂时不想写,想要的可以评论++)
6.40
练习6.40:下面的哪个声明是错误的?为什么?
(a) int ff (int a, int b = 0, int c = 0 ) ;
(b)char *init(int ht = 24,int wd,char bckgrnd);
很明显,a是对的,b是错的;
因为int ht=24 ,定义了默认实参,且位置在第一位;
C++规定一旦某个形参被赋予了默认实参,则它后面的所有形参都必须有默认实参。
6.41
练习6.41:下面的哪个调用是非法的?为什么?哪个调用虽然合法但显然与程序员的初衷不符?为什么?
char init(int ht, int wd = 80,char bckgrnd = ’ ');
(a) init (); (b) init (24,10);© init (14, '');
函数定义了三个形参,且只有第一个形参没有默认值,如果调用该函数的时候,至少需要提供一个实参;
c是合法但违背初衷,实参 '*'是char,字符类型,与int其实是可以自动转换为第二个形参wd所需要的int类型;
6.43
练习6.43:你会把下面的哪个声明和定义放在头文件中?哪个放在源文件中?为什么?
(a)inline bool eq (const BigInt&, const BigInt&) { …)(b) void putvalues (int *arr, int size) ;
如果没有inline是什么关键字,一般来说a是函数的实现,应该放在源文件,b显而易见,是函数声明,所以是放在头文件.
加上inline 表示该函数为内联函数,且内联函数与一般函数不同,内联函数有可能在程序中定义不止一次,此时必须保证在所有源文件中定义完全相同,把内联函数的定义放在头文件中可以确保这一点。
知识点 inline
内联函数:是C++中的一种函数,其主要特点是在编译器编译阶段将函数调用处的代码替换为函数体的实际代码,而不是像普通函数那样执行函数调用。这可以提高程序的执行效率,特别是对于小型、频繁调用的函数。
在函数定义前面加上inline
关键字来定义内联函数
inline int add(int a, int b) {
return a + b;
}
- 内联函数的目的:通常用于性能优化,特别是对于简单、短小的函数。内联函数的主要目的是减少函数调用的开销,因为不需要在运行时创建函数调用帧。
- 编译器替换:编译器会尝试将内联函数的调用处替换为函数体的代码,但不一定每次都会成功内联函数。编译器会根据一些规则和限制来判断是否进行内联。
- 适合内联的函数:通常内联适用于函数体非常简单,且在多个地方频繁调用的情况。大型函数或包含复杂控制流的函数不太适合内联。
- 内联函数的缺点:内联函数可能会导致代码膨胀,因为每次调用都会复制函数体的代码,这可能会增加可执行文件的大小。
- 内联函数的声明:通常,内联函数的定义和声明都应该放在头文件中,以便在多个源文件中使用。这样编译器才能看到内联函数的定义,从而进行内联。
总的来说,就是某个程序如果多次调用这个函数,那么这个函数就应该可以定义成内联函数,如果只是调用一次两次的,那就完全没必要;
6.44
练习6.44:将isShorter函数改写成内联函数。
//比较两个string对象的长度
bool isShorter(const string &s1,const string &s2){
return sl .size( < s2 .size ();
}
改写后:
inline bool isShorter (const string &s1,const string &s2){
return s1.size () < s2.size ( ) ;
}
6.46
练习6.46:能把 isshorter函数定义成constexpr函数吗?如果能,将它改写成constexpr函数;如果不能,说明原因。
知识点 constexpr函数
constexpr
函数是一种 C++ 中的函数,用于在编译时计算结果。constexpr 函数是指能用于常量表达式的函数;
这些函数允许你在编译时执行计算,从而提高程序的性能,并确保一些计算在编译期间就已知。
constexpr
函数的关键特点和用法:
constexpr
修饰符: 用constexpr
修饰函数来指示编译器在编译时执行函数。- 编译时计算:
constexpr
函数通常用于执行在编译期间已知的计算。这些函数的结果在编译时就被确定,而不是在运行时。 - 限制:
constexpr
函数有一些限制。例如,它们通常不能包含动态内存分配(如new
和malloc
),不能使用输入/输出操作,以及不能包含非constexpr
函数调用。 - 用途:
constexpr
函数非常适用于一些常量计算,例如计算斐波那契数列的值、阶乘等。这些函数可以在编译时计算这些常量值,而不需要在运行时重复计算。
编译期常量: constexpr
函数的返回值可以被用于定义编译期常量,如数组大小和枚举常量。
优化性能: 使用 constexpr
函数可以提高程序性能,因为它们在编译时计算结果,而不是在运行时。
在C++中,constexpr
函数的函数体不一定只能有一条 return
语句。事实上,constexpr
函数可以包含多个 return
语句,但有一些限制和规则:
constexpr
函数的函数体可以包含多个return
语句,但这些return
语句必须返回相同的值或相同的表达式。- 所有可能的
return
语句的返回值必须在编译时确定,并且必须相同。这是因为constexpr
函数在编译时执行,编译器必须能够确定函数的返回值。
示例:
constexpr int max(int a, int b) {
if (a > b) {
return a;
}
else {
return b;
}
}
int main(void) {
int constexpr x = max(1, 2);
cout << x << endl;
return 0;
}
6.48
练习6.48:说明下面这个循环的含义,它对assert的使用合理吗?
string s;
while (cin >> s && s != sought) {}//空函数体
assert(cin);
sought 应该是某个string变量,程序执行到assert的原因可能有两个,一是用户终止了输入,二是用户输入的内容正好与sought的内容一样。如果用户尝试终止输入(事实我们总有停止输入结束程序的时候),则assert 的条件为假,输出错误信息,这与程序的原意是不相符的。
知识点 assert()函数
assert
函数是一个用于调试的宏,它用于在程序中插入断言(assertions)。断言是一种用于检查程序中的预期条件是否满足的方式,如果条件不满足,程序将在运行时终止。
assert是一种预处理宏,当assert 的条件为真时什么也不做,当它的条件为假时输出信息并终止程序。
assert需要包含头文件
assert
在发布版本中通常会被禁用(通过宏 NDEBUG
来控制),因此它主要用于调试期间,以帮助发现和解决问题。
6.49
练习6.49:什么是候选函数?什么是可行函数?
知识点 候选函数与可行函数
函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数称为候选函数。
候选函数具备两个典型特征:一是与被调用的函数同名,二是其声明在调用点可见。
函数匹配的第二步是考查本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数。
可行函数也有两个特征:一是其形参数量与本次调用提供的实参数量相等,二是每个实参的类型与对应的形参类型相同或者能转换成形参的类型。
示例:
void foo(int x, double y);
void foo(double x, int y);
void foo(int x, int y);
如果调用 foo(5, 3)
, 候选函数将包括上述三个 foo
函数,但只有最后一个 foo(int x, int y)
能成为可行函数因为它是唯一一个参数匹配的函数,而且能够合法调用。
其他的候选函数在这个具体调用中并不可行。
6.50
练习对于下面的每一个调用列出可行函数。其中哪个函数是最佳匹配?如果调用不合法,是因为没有可匹配的函数还是因为调用具有二义性?
void f();
void f (int) ;
void f(int, int) ;
void f (double,double = 3.14);
(a) f(2.56,42)(b)f(42)© f (42,0)(d)f(2.56,3.14)
f(2.56,42)的可行函数是void f(int, int)和void f(double,double= 3.14)。但是最佳匹配不存在,因为这两个可行函数各有所长。对于这次调用来说,如果只考虑第一个实参2.56,我们发现,void f (double,double = 3.14)能够精确匹配,但是要想匹配第二个参数,int类型的实参42必须转换成double类型。如果考虑第二个实参42,我们发现,void f(int, int)能够精确匹配,但是要想调用void f(int, int)就必须把第一个double类型的实参2.56转换成int类型。最终的结果是这两个可行函数各自在一个实参上实现了更好的匹配,但是把它们比较起来无从判断孰优孰劣,因此编译器将因为这个调用具有二义性而拒绝其请求。
f (42)的可行函数是void f(int)和 void f(double, double = 3.14),其中最佳匹配是void f(int),因为参数无须做任何类型转换。
f(42,0)的可行函数是void f(int, int)和 void f(double,double= 3.14),其中最佳匹配是void f(int, int),因为参数无须做任何类型转换。
f(2.56,3.14)的可行函数是void f(int,int)和 void f(double,double = 3.14),其中最佳匹配是 void f (double, double = 3.14),因为参数无须做任何类型转换。
知识点 最佳匹配
可行函数是指形参数量与本次调用提供的实参数量相等且每个实参的类型都与对应的形参类型相同或者能转换成形参类型的函数。
最佳匹配是指该函数每个实参的匹配都不劣于其他可行函数需要的匹配且至少有一个实参的匹配优于其他可行函数提供的匹配。
6.51
练习6.52:已知有如下声明,
void manip (int, int);double dobj;
请指出下列调用中每个类型转换的等级(参见6.6.1节,第219页)。
(a) manip (‘a’, ‘z’);
(b) manip(55.4, dobj) ;
(a)发生的参数类型转换是类型提升,字符型实参自动提升成整型。
(b)发生的参数类型转换是算术类型转换,双精度浮点型自动转换成整型。
知识点 实参类型到形参类型的转换
在 C++ 中,当你调用函数时,编译器将根据提供的实际参数(实参)和函数的形式参数(形参)来匹配参数。
精确匹配
这是最优先的匹配方式。当实参的类型与形参的类型完全相同时,会发生精确匹配。
例如,如果形参是 int
,则传递整数值作为实参会产生精确匹配。
const 限定匹配
如果实参的类型是 const
限定版本的形参类型,这也被认为是一种匹配。
例如,如果形参是 const int
,则传递 const int
或 int
均匹配。
类型提升匹配
当实参的类型可以被自动提升为形参类型时,匹配也会发生。
例如,如果形参是 double
,传递一个 float
值也会匹配,因为 float
会自动提升为 double
。
算术类型转换
如果实参可以通过标准的算术类型转换(如整数之间的隐式类型转换)匹配形参,匹配会发生。
例如,如果形参是 long
,传递一个 int
值也会匹配。
类类型转换
当形参需要通过用户定义的类型转换函数(如转换构造函数或类型转换运算符)来匹配实参时,匹配会发生。这通常需要明确指定类型转换。
6.54
练习6.54:编写函数的声明,令其接受两个int形参并且返回类型也是int;然后声明一个vector对象,令其元素是指向该函数的指针。
#include<vector>
int func(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main(void) {
vector<decltype(func)*> vF;
vF.push_back(func);
vF.push_back(subtract);
int result1 = vF[0](5, 3);
int result2 = vF[1](8, 2);
std::cout << "Result1: " << result1 << std::endl;//8
std::cout << "Result2: " << result2 << std::endl;//6
return 0;
}
实则上就是利用容器vector,vector接受的类型是函数指针,
需要注意的是:
vector<decltype(func)*> vF;
含义是创建一个名为 vF
的 vector
对象,该向量可以存储与函数 func
具有相同类型的函数指针。
decltype(func)
:decltype
是C++11引入的关键字,它用于获取表达式或变量的类型。在这里,decltype(func)
获取的是函数 func
的类型。
decltype(func)*
意味着存储的元素是函数指针,其类型与 func
函数的类型相同。
所以并不能存放形参是不类型的函数,如:
int subtract(doublea, int b) {
return a - b;
}
6.55
练习6.55:编写4个函数,分别对两个int值执行加、减、乘、除运算;在上题创建的vector对象中保存指向这些值的指针。
#include<iostream>
using namespace std;
#include<vector>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multi(int a, int b) {
return a * b;
}
int divi(int a, int b) {
return a / b;
}
int main(void) {
// 声明函数指针,并使用 decltype 推断类型
decltype(multi)* p3 = multi;
decltype(divi)* p4 = divi;
vector<decltype(add)*> vF;
vF.push_back(add);
vF.push_back(subtract);
vF.push_back(p3);
vF.push_back(p4);
int result1 = vF[2](5, 3);
std::cout << "Result1: " << result1 << std::endl;
return 0;
}
两种方法都是有效的,选择哪一种取决于你的代码风格和具体需求。如果只是存储和使用一次函数指针,方法1可能更简洁。如果你打算多次使用相同的函数指针,方法2可能更容易维护和理解
5.56
练习5.56:构建一个新的函数,以指向4种运算的函数的指针为参数。
#include<iostream>
using namespace std;
#include<vector>
#include<windows.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multi(int a, int b) {
return a * b;
}
int divi(int a, int b) {
return a / b;
}
void four(int a, int b, int(* p)(int, int)) {
cout <<"val:" << p(a, b) << endl;
}
int main(void) {
decltype(multi)* p3 = multi;
decltype(divi)* p4 = divi;
vector<decltype(add)*> vF;
vF.push_back(add);
vF.push_back(subtract);
vF.push_back(p3);
vF.push_back(p4);
int a = 5, b = 6;
for (auto p : vF) {
four(a, b, p);
}
return 0;
}