C++ Primer第六章习题

前言

从本章开始每一道题都要进行回答。毕设也结束了,今天也拿到了研究生录取通知书,暑假就要迅速吃透这本书,并做好下一步的计划。

练习6.1

实参和形参的区别是什么

实参必须是有定义的,是函数体之外定义好的,形参是函数体之内需要使用的但是未定义的。实参列表无须声明类型,形参列表必须每个参数都要声明类型。

练习6.2

找出函数的错误

(1)函数定义类型是int与返回值类型不匹配
(2)没有声明函数的返回类型
(3)形参列表中不能有同名
(4)函数体必须使用花括号

练习6.3/练习6.4

写一个阶乘函数并调用

阶乘算法很多,这里采取一种跟书上不一样的递归阶乘算法

#include<iostream>
using namespace std;

int fact(int val)
{
	if (val > 1)
		return val * fact(val-1);
	else
		return 1;
}

int main()
{
	cout << "Please input an integer to calculate its fact" << endl;
	int n;
	cin >> n;
	cout << "Its fact is " << fact(n) << endl;
	return 0;

}

练习6.5

#include<iostream>
using namespace std;

//计算输入值的绝对值
int abs(int val)
{
	if (val < 0)
		return 0 - val;
	else 
		return val;
}

int main()
{
	int n;
	cin >> n;
	cout << "The absolute value of the number you just keyed in is " << abs(n) << endl;
	return 0;
}

练习6.6

形参和局部变量:形参是一种局部变量,局部变量是作用域仅限于一个代码块的变量,形参or函数体当中定义的变量都是一种局部变量

局部变量和局部静态变量:局部静态变量的作用域没有发生改变,但是其生命周期被延长至程序运行结束。

练习6.7

第一次被调用的时候返回0,之后返回1

bool oneOrZero()
{
	static bool call = false;
	return call++;
}
	

练习6.9

分离式编译

这里采用g++ 进行编译, linux shell 脚本初次使用进行分离式编译尝试,脚本如下

#########################################################################
# File Name: ex6_9.sh
# Author: wu feisheng
# mail: curtain-@sjtu.edu.cn
# Created Time: 2019年06月22日 星期六 10时13分35秒
#########################################################################
#!/bin/bash

g++ -c fact.cpp #generate fact.o compile
g++ -c ex6_3.cpp #generate ex6_3.o compile

g++ ex6_3.o fact.o -o main.exe

其中fact.cpp:

#include<iostream>
#include "chapter6.h"
using namespace std;

int fact(int val)
{
	if (val > 1)
		return val * fact(val-1);
	else
		return 1;
}

chapter6.h:

#include<iostream>
using namespace std;

int fact(int val);

ex6_3.cpp:

#include<iostream>
#include"chapter6.h"
using namespace std;

int main()
{
	cout << "Please input an integer to calculate its fact" << endl;
	int n;
	cin >> n;
	cout << "Its fact is " << fact(n) << endl;
	return 0;

}

练习 6.10

指针形参

函数如下

void exchange(int *i1, int *i2)
{
	int temp;
	temp = *i1;
	*i1 = *i2;
	*i2 = temp;
}

调用如下

int main()
{
	int i1, i2;
	cin >> i1 >> i2;
	exchange(&i1, &i2);
	cout << "The result of i1 is" << i1 << " the result i2 is" << i2 << endl;
	return 0;
}

练习6.11

用传入引用参数重写reset函数

void reset(int &i)
{
	i=0;
}

int main()
{
	int i;
	cin >> i;
	reset(i);
	cout << i << endl;
	return 0;
}

练习6.12

用传入引用参数重写6.10

void exchange(int &i1, int &i2)
{
	int temp;
	temp = i1;
	i1 = i2;
	i2 = temp;
}
int main()
{
	int i1, i2;
	cin >> i1 >> i2;
	exchange(i1, i2);
	cout << "The result of i1 is" << i1 << " the result i2 is" << i2 << endl;
	return 0;
}

使用引用键入字符是少于使用指针形参的。不用定义指针也更为方便。

练习6.13

两个函数声明的区别

void f(T) 形参将是实参的拷贝
void f(&T) 形参将是实参的引用

练习6.14

形参应该是引用:需要实参在函数中自加
形参不能是引用:实参在函数中作为加数计算和,但加数本身不能改变

练习6.15

s是常量引用因为s所引用的对象是无需被改变的常量对象,occurs是计数变量,需要不断改变;
s是引用是因为s是大的类型string,使用引用更高效,occurs需要改变;c是char是较小的类型,也无需改变。

练习6.16

函数的形参是引用,因此传入参数的时候就只能传入对象/引用,如果将其修改为常量引用const string &s 就可以传入字面值和其他常量引用。

练习6.17

判断是否有大写字母的函数

bool isHaveUpper(const string &s)
{
	for (auto c : s)
		if (isupper(c))
			return 1;
		
	return 0;
}

转化为小写字母的函数

void toLower(string& s)
{
	for (auto& c : s)
		if (isupper(c))
			c = tolower(c);
}

第一个函数使用常量引用,s不能被改变且不用拷贝,可以传入字面值。
第二个函数使用普通引用,s在函数中被改变,不能传入字面值。

练习6.18

(a)compare函数是比较两个matrix对象的大小

(b) change_val函数使用传入的int值改变迭代器所指位置vector< int >的元素值

ex6_18.h

# include <iostream>
# include <vector>

using namespace std;

bool compare(matrix &m1, matrix &m2);

vector<int>::iterator change_val(int, vector<int>::iterator);

练习6.19

(b)©(d)都合法
(a)问题在于实参数量过多

练习6.20

在无须改变传入实参的情况下都可以用常量引用。如果设为普通引用会有如下问题:
(1)传入实参可能会被改变
(2)无法传入字面值

练习6.21

int compare(const int i, const int* p)
{
	return i > *p ? i : *p;
}

练习6.22

void exchange(int* &p1, int* &p2)
{
	int* temp = p1;
	p1 = p2;
	p2 = temp;
}

注1:函数形参是指针的引用;
注2:引用没有指针(不存在引用的指针因为引用不是对象)

练习6.24

函数想要打印传入数组实参所含的所有元素,但是函数是有问题的。
尽管数组形参期望的大小是10,但是传入的只是指针,传入实参时的数组大小是未知的,数组长度小于10时会发生下标越界,大于10时无法完成期望功能。
建议使用本节介绍的三种管理指针形参的技术。

练习6.25

#include <iostream>
#include <string>

using namespace std;

int main(int argc, char** argv)
{
	string s;
	for (int i = 1; i != argc; ++i)
		s += argv[i];
	cout << "The configuration para is" << endl;
	cout << s << endl;

	return 0;	
}

注1:argv[0]中是程序的名字;
注2:这里使用了string与c风格字符串的接口

练习6.27

#include<iostream>
using namespace std;
int calculate(initializer_list<int> li)
{
        int sum = 0;
        for (auto beg = li.begin();beg != li.end();++beg)
                sum += *beg;
        return sum;
}

int main()
{
        int num1,num2;
        cout << "Please input some integers to sum ";
        cin >> num1 >> num2;
        cout << calculate({num1,num2});
        return 0;
}

练习6.28

const string
总结:引用的类型是其引用值的类型

练习6.29

因为initializer_list 是一个模板类型,其中的对象可能是任意类型,为了避免拷贝,应当声明为引用类型

练习6.30

ex6_30.cpp: 在函数‘bool str_subrange(const string&, const string&)’中:
ex6_30.cpp:18:4: 错误:在返回‘bool’的函数中,返回语句不带返回值 [-fpermissive]
    return;

与书中解释一致,编译器检查出了第一个错误,但没有检查出for循环之后没有return 的错误

练习6.31

什么时候返回的引用无效?

返回值是局部对象的引用无效,因为局部对象在函数完成后,其所占用的储存空间也被释放掉了。
所以,返回值为引用如 const string &时,return 的对象必不能是局部对象。

什么时候返回常量的引用无效?

返回值用作左值的情况下返回常量的引用无效

练习6.32

合法,get函数返回参数中整型数组指定下标的元素引用,在main中用作左值,给一个10大小的整型数组赋值。

练习6.33

void printVec(const vector<int>& veci, int i)
{
	if (i != -1)
	{
		cout << veci[i] << endl;
		printVec(veci, i - 1);
	}
}

int main()
{
	vector<int> vec = { 1,2,3,4,5,6,7,8,9 };
	printVec(vec, vec.size()-1);

}

本题借鉴了https://blog.csdn.net/misayaaaaa/article/details/53998504 的解法。

练习6.34

结论:输出结果不会改变
原因:之前的函数最后一层递归的val为2,返回f(1)*2,其中f(1)=1
修改之后的最后一层递归的val为1,返回f(0)*1,其中f(0)=1。故没有变化

练习6.35

使用val–的时候,带入函数的值是val,之后才是val-1,但是递归函数中关键在于带入参数的改变。使用val–调用函数的参数将永远是val,陷入无穷递归了。

练习6.36

类似返回数组指针

string s[10] = { "hello" };
string (&printStr())[10]
{
	
	return s;
}

练习6.37

//使用类型别名
typedef string str[10];
str& printStr1()
{
	return s;
}


//使用尾置返回类型
auto printStr2()->string(&)[10]
{
	return s;
}

//使用decltype关键字
decltype(s) &printStr3()
{
	return s;
}

使用decltype关键字更加简单些

练习6.38

int odd[] = { 1,3,5,7,9 };
int even[] = { 0,2,4,6,8 };

decltype(odd)& arrPtr(int i)
{
	return (i % 2) ? odd : even;
}

总结:指针和引用这四种类型是等价的,区别在于引用返回数组名即可,指针类型的函数需要返回数组的地址作为指针的指向

练习6.39

(a) 错误,顶层const作为参数不改变参数类型,因此重复声明了
(b) 错误,返回值类型不同不能认为是函数重载
© 正确,返回值类型为double* 参数类型为double*与第一个参数类型不同,是重载函数

练习6.40

(b)错误,因为第一个形参已经被赋予了默认值,一旦函数的某个形参被赋予了默认值,他后面所有的参数都必须有默认值

练习6.41

(a)非法,第一个形参ht没有传入实参
©合法但是与初衷不符,‘*’是要赋给第三个形参的,但是排列到第二个位置就被转换为int类型赋给wd了

练习6.42

plura.h

#include <iostream>
#include <string>

using namespace std;

string makeplural(size_t, const string&, const string& = "s");

plural.cpp

#include <iostream>
#include <string>

using namespace std;

string makeplural(size_t ctr, const string& word, const string& ending)
{
	return ctr > 1 ? word + ending : word;
}

main.cpp

#include <iostream>
#include <string>
#include "plural.h"

using namespace std;

int main()
{
	cout << "success的复数" << makeplural(2, "success", "es") << " 以及其单数" << makeplural(1, "success", "es") << endl;
	cout << "failure的复数" << makeplural(2, "failure") << " 以及其单数" << makeplural(1, "failure") << endl;
}

练习6.43

(a)的声明和定义都可以放在头文件中,因为是inline函数
(b)的声明放在头文件中,定义放在源文件中

练习6.46

constexpr函数需要遵守的约定:
1、函数的返回类型及所有形参的类型都得是字面值类型 :不满足
2、函数体中有且只有一条return语句:满足
故:不能

练习6.47

#include <iostream>
#include <vector>
#define NDEBUG

using namespace std;


void printVec(const vector<int>& veci, int i)
{
	if (i != -1)
	{
		cout << veci[i] << endl;
		printVec(veci, i - 1);
	}
#ifndef NDEBUG
	cerr << veci.size() << endl;
#endif
}

练习6.48

编译不会出错但是不合理:程序会等待输入,只要有输入cin就为真,故assert不会发挥任何作用

练习6.49

候选函数:本次调用的重载函数集合,即1)与被调函数同名2)其声明在调用点可见
可行函数:候选函数中能被这一组实参调用的函数即1)形参数量与此次调用的实参数量一致2)每个实参的类型与对应形参的类型一样,或者可以转换为形参的类型。

练习6.50-6.51

(a)产生二义性,会发生编译出错
(b)会调用f(int)
©会调用f(int, int)
(d)会调用f(double, double)

#include <iostream>

using namespace std;

void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);

void f()
{
	cout << "The first func" << endl;
}

void f(int i)
{
	cout << "The second func" << endl;
}

void f(int i1, int i2)
{
	cout << "The third func" << endl;
}

void f(double d1, double d2)
{
	cout << "The fourth func" << endl;
}

int main(int argc, char* argv)
{
	//f(2.56, 42);
	f(42);
	f(42, 0);
	f(2.56, 3.14);
	return 0;
}

练习6.52

(a) 第三等级:通过类型提升得到的匹配,char是8位,int是16位,char到int是类型提升
(b) 第四等级:算术类型转换

练习6.54

#include <iostream>
#include <vector>

using namespace std;

int FP(int, int);



vector<int(*)(int,int)> vec;

int main(int argc, char* argv)
{
	vec.push_back(FP);
	return 0;
}

练习 6.55

#include <iostream>
#include <vector>

using namespace std;

int pluse(int, int);
int minuse(int, int);
int multiplex(int, int);
int divide(int, int);


int pluse(int i1, int i2)
{
	return i1 + i2;
}

int minuse(int i1, int i2)
{
	return i1 - i2;
}

int multiplex(int i1, int i2)
{
	return i1 * i2;
}

int divide(int i1, int i2)
{
	return i1 / i2;
}


vector<int(*)(int,int)> vec;

int main(int argc, char* argv)
{
	vec.push_back(pluse);
	vec.push_back(minuse);
	vec.push_back(multiplex);
	vec.push_back(divide);

	return 0;
}

练习6.56

修改main函数为

int main(int argc, char* argv)
{
	vec.push_back(pluse);
	vec.push_back(minuse);
	vec.push_back(multiplex);
	vec.push_back(divide);

	for (auto func : vec)
		cout << func(1, 2) << endl;

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值