C++primer 第六章 函数

6.1 函数基础

6.1 实参和形参的区别的什么?

形参:位于函数的一对圆括号内
实参:是形参的初始化。

6.2 请指出下列函数哪个有错误,为什么?应该如何修改这些错误呢?

(a) int f() {
          string s;
          // ...
          return s;
    }//返回值是string类型
(b) f2(int i) { /* ... */ }//缺少返回类型
(c) int calc(int v1, int v1) { /* ... */ }//两个形参不能同名
(d) double square (double x)  return x * x; //缺少了大括号的块

6.3 编写你自己的fact函数,上机检查是否正确。
6.4 编写一个与用户交互的函数,要求用户输入一个数字,计算生成该数字的阶乘。在main函数中调用该函数。

#include<iostream>
using namespace std;

int fact(int i)
{
    int ret = 1;
    while (i >= 1)
        ret *= i--;
    return ret;    
}

int main()
{
    int i = 0;
    cin >> i;
    cout << fact(i) << endl;

    return 0;
}

6.5 编写一个函数输出其实参的绝对值。

#include<iostream>
using namespace std;

int Abs(int i)
{
    return((i < 0) ? -i : i);
}

int main()
{
    int i = 0;
    cin >> i;
    cout << Abs(i) << endl;

    return 0;
}

6.1.1 局部变量

6.6 说明形参、局部变量以及局部静态变量的区别。编写一个函数,同时达到这三种形式。

形参:形参是局部变量的一种
局部变量:形参和函数体内部定义的变量
局部静态变量:局部变量的声明周期贯穿函数调用及调用之后的时间

6.7 编写一个函数,当它第一次被调用时返回0,以后每次被调用返回值加1。

#include<iostream>
using namespace std;

int count_calls()
{
    static int count = 0;
    return count++;
}

int main()
{
    cout << count_calls() << endl;
    cout << count_calls() << endl;
    cout << count_calls() << endl;
    cout << count_calls() << endl;

    return 0;
}

6.1.2 函数声明

6.8 编写一个名为Chapter6.h 的头文件,令其包含6.1节练习中的函数声明。

#ifndef CHAPTER6_H
#define CHAPTER6_H

int fact(int i);
int Abs(int i);

#endif

6.1.3 分离式编译

6.9 编写你自己的fact.cc 和factMain.cc ,这两个文件都应该包含上一小节的练习中编写的 Chapter6.h 头文件。通过这些文件,理解你的编译器是如何支持分离式编译的。

fact.cpp

#include "Chapter6.h"

int fact(int i)
{
    int ret = 1;
    while (i >= 1)
        ret *= i--;
    return ret;    
}

int Abs(int i)
{
    return((i < 0) ? -i : i);
}

factMain.cpp

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

int main()
{
    cout << fact(5) << endl;

    return 0;
}
hydx@hydx:~/桌面/code/C++primer$ g++ -o a.out fact.cpp factMain.cpp -std=c++11
hydx@hydx:~/桌面/code/C++primer$ ./a.out 

6.2 参数传递

6.2.1 传值参数

6.10 编写一个函数,使用指针形参交换两个整数的值。在代码中调用该函数并输出交换后的结果,以此验证函数的正确性。

#include<iostream>
using namespace std;

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

int main()
{
    int i1 = 5 , i2 = 6;

    cout << "交换前:" << endl;
    cout << "i1 = " << i1 << endl;
    cout << "i2 = " << i2 << endl;

    swap(i1 , i2);
    
    cout << "交换后:" << endl;
    cout << "i1 = " << i1 << endl;
    cout << "i2 = " << i2 << endl;

    return 0;
}

6.2.2 传引用参数

6.11 编写并验证你自己的reset函数,使其作用于引用类型的参数。

#include <iostream>

using namespace std;

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

int main()
{
    int a = 10;
    
    cout << "a = " << a << endl;

    reset(a);

    cout << "a = " << a << endl;    

   return 0;
}
```.

> 6.12 改写6.2.1节练习中的程序,使其引用而非指针交换两个整数的值。你觉得哪种方法更易于使用呢?为什么?

```cpp
#include<iostream>
using namespace std;

void swap(int &i1 , int &i2)
{
    int temp = 0;
    temp = i1;
    i1 = i2;
    i2 = temp;
}

int main()
{
    int i1 = 5 , i2 = 6;

    cout << "交换前:" << endl;
    cout << "i1 = " << i1 << endl;
    cout << "i2 = " << i2 << endl;

    swap(i1 , i2);
    cout << "交换后:" << endl;
    cout << "i1 = " << i1 << endl;
    cout << "i2 = " << i2 << endl;

    return 0;
}

使用引用的方法更加容易,因为引用不需要考虑指针,直接起别名,而且还不需要进行拷贝。

6.13 假设 T 是某种类型的名字,说明以下两个函数声明的区别:一个是void f(T), 另一个是 void f(&T)。

void f(T):使用的是拷贝的方法,将实参的值赋给形参,需要额外开辟空间。不能通过改变形参来改变实参
void f(&T):使用的是引用的方法,直接给实参起别名,不需要额外开辟空间。能通过改变形参来改变实参

6.14 举一个形参应该是引用类型的例子,再举一个形参不能是引用类型的例子。

6.15 说明find_char 函数中的三个形参为什么是现在的类型,特别说明为什么s是常量引用而occurs是普通引用?为什么s和occurs是引用类型而c不是?如果令s是普通引用会发生什么情况?如果令occurs是常量引用会发生什么情况?

s:不可修改,且对象可能很大; --》可能会改变实参
occurs:需要修改其次数; --》 occurs将不能改变,++occurs会报错
c:没有另外两种需求。

6.2.3 const形参和实参

6.16 下面的这个函数虽然合法,但是不算特别有用。指出它的局限性并设法改善。

bool is_empty(string& s) { return s.empty(); }

该函数无需改变实参,故将其设置为const比较好,这样也可以传入const类型的字符串,或字符串字面值。

bool is_empty(const string& s) { return s.empty(); }

6.17 编写一个函数,判断string对象中是否含有大写字母。编写另一个函数,把string对象全部改写成小写形式。在这两个函数中你使用的形参类型相同吗?为什么?

#include <iostream>

using namespace std;

void is_upper(const string &s)
{
    for(auto c : s)
        if(isupper(c))
        {
            cout << "含有大写字母" << endl;
            return ;
        }
    cout << "不含大写字母" << endl;
    return ;
}

void to_lower(string &s)
{
    for(auto &c : s)
        c = tolower(c);
    return ;
}

int main()
{
    string s = "";

    cin >> s;
    is_upper(s);

    to_lower(s);
    cout << s << endl;
   
   return 0;
}

不相同,一个需要修改,另一个不需要。

6.18 为下面的函数编写函数声明,从给定的名字中推测函数具备的功能。
(a) 名为 compare 的函数,返回布尔值,两个参数都是 matrix 类的引用。
(b) 名为 change_val 的函数,返回vector的迭代器,有两个参数:一个是int,另一个是vector的迭代器。

(a)bool compare(matrix &m1,matrix &m2);
(b)vector<int>::iterator change_val(int i,vector<int>::iterator);

6.19 假定有如下声明,判断哪个调用合法、哪个调用不合法。对于不合法的函数调用,说明原因。

double calc(double);
int count(const string &, char);
int sum(vector<int>::iterator, vector<int>::iterator, int);
vector<int> vec(10);
(a) calc(23.4, 55.1);//不合法,形参只有一个
(b) count("abcda",'a');//合法
(c) calc(66);//合法
(d) sum(vec.begin(), vec.end(), 3.8);//合法

6.20 引用形参什么时候应该是常量引用?如果形参应该是常量引用,而我们将其设为了普通引用,会发生什么情况?

当实参的值不需要改变时,可以用常量引用。发生:不能把const对象的值或者需要的类型传递给普通参数,且可能会改变传入的值。

6.2.4 数组形参

6.21 编写一个函数,令其接受两个参数:一个是int型的数,另一个是int指针。函数比较int的值和指针所指的值,返回较大的那个。在该函数中指针的类型应该是什么?

#include <iostream>

using namespace std;

int compare(const int i1 , const int *i2)
{
    return ((i1 > *i2) ? i1 : *i2);
}

int main()
{
   int i1 = 0 , i2 = 0;
   cin >> i1 >> i2;

   cout << compare(i1 , &i2) << endl;

   return 0;
}

6.22 编写一个函数,令其交换两个int指针。

#include <iostream>

using namespace std;

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

int main()
{
    int i1 = 4 , i2 = 5;
    int *p1 = &i1 , *p2 = &i2;

    cout << "交换前:" << endl;
    cout << "p1->" << *p1 << " 地址: " << p1 << endl;
    cout << "p2->" << *p2 << " 地址: " << p2 << endl;

    swap(p1 , p2);
   
    cout << "交换后:" << endl;
    cout << "p1->" << *p1 << " 地址: " << p1 << endl;
    cout << "p2->" << *p2 << " 地址: " << p2 << endl;

   return 0;
}

6.23 参考本节介绍的几个print函数,根据理解编写你自己的版本。依次调用每个函数使其输入下面定义的i和j:

#include <iostream>

using namespace std;

void print(const int &i , const int *beg , const int *end)
{
    cout << i << endl;

    while (beg != end)
        cout << *beg++ << " ";   
    cout << endl; 
}

int main()
{
    int i = 0 , j[2] = {0 , 1};

    print(i , begin(j) , end(j));
   
   return 0;
}

6.23 描述下面这个函数的行为。如果代码中存在问题,请指出并改正。

void print(const int ia[10])//数组没有拷贝构造,应该是:const int (&ia)[10]
{
	for (size_t i = 0; i != 10; ++i)
		cout << ia[i] << endl;
}

6.2.5 main:处理命令行选项

6.25 编写一个main函数,令其接受两个实参。把实参的内容连接成一个string对象并输出出来。

#include <iostream>
#include<cstring>

using namespace std;

int main(int argc , char *argv[])
{
    cout << strcat(argv[1] , argv[2]) << endl;
   
   return 0;
}

6.26 编写一个程序,使其接受本节所示的选项;输出传递给main函数的实参内容。

#include <iostream>
#include<cstring>

using namespace std;

int main(int argc , char *argv[])
{
    string str;

    for(int i = 0 ; i != argc ; ++i)
        str += string(argv[i]);
    cout << str << endl;
   
   return 0;
}

6.2.6 含有可变形参的函数

6.27 编写一个函数,它的参数是initializer_list类型的对象,函数的功能是计算列表中所有元素的和。

#include <iostream>
#include<initializer_list>

using namespace std;

void sum(initializer_list<int> li)
{
    int sum = 0;
    for(auto beg = li.begin() ; beg != li.end() ; ++beg)
        sum += *beg;   
    cout << sum << endl;
}

int main()
{
    sum({1 , 2 , 3});
   
   return 0;
}

6.28 在error_msg函数的第二个版本中包含ErrCode类型的参数,其中循环内的elem是什么类型?

string 类型

6.29 在范围for循环中使用initializer_list对象时,应该将循环控制变量声明成引用类型吗?为什么?

拷贝代价较大时,用引用类型

6.3.1 无返回值函数

6.3.2 右返回值函数

6.30 编译第200页的str_subrange函数,看看你的编译器是如何处理函数中的错误的。

错误1:error: return-statement with no value, in function returning ‘bool[-fpermissive]
错误2:没检测出来

6.31 什么情况下返回的引用无效?什么情况下返回常量的引用无效?

返回的引用是局部变量时,引用无效;
返回的是局部常量是,引用无效。

6.32 下面的函数合法吗?如果合法,说明其功能;如果不合法,修改其中的错误并解释原因。

int &get(int *array, int index) { return array[index]; }
int main()
{
    int ia[10];
    for (int i = 0; i != 10; ++i)
        get(ia, i) = i;
}

合法,为ia[10]数组赋值,ia[10] = {0,1,2,3,4,5,6,7,8,9}

6.33 编写一个递归函数,输出vector对象的内容。

#include <iostream>
#include <vector>

void read_vi(std::vector<int> & v, std::vector<int>::iterator p)
{
	if(p != v.end())
	{
		std::cout << *p << " ";
        ++p;
		read_vi(v, p);
	}
    else
	{
		std::cout << std::endl;
		return;
	}
}

int main()
{
	std::vector<int> v;
    for (int i = 0; i < 5; ++i)
        v.push_back(i + 1);
		
	read_vi(v, v.begin());

	return 0;
}

6.34 如果factorial 函数的停止条件如下所示,将发生什么?
if (val != 0)

如果实参为大于等于0,函数将会多乘以一个1,比如factorial(5),等价于5 * 4 * 3 * 2 * 1 * 1;
如果实参小于0,函数将会不断地调用它自身直到程序栈空间耗尽为止。

6.35 在调用factorial函数时,为什么我们传入的值是 val-1 而非 val–?

()优先级高于–, val–会返回未修改的val内容,使程序陷入无限循环;val–会修改val的内容,使程序运行结果不符合预期。

6.3.3 返回数组指针

6.36 编写一个函数声明,使其返回数组的引用并且该数组包含10个string对象。不用使用尾置返回类型、decltype或者类型别名。
6.37 为上一题的函数再写三个声明,一个使用类型别名,另一个使用尾置返回类型,最后一个使用decltype关键字。你觉得哪种形式最好?为什么?

using std::string;

string (& func(int i))[10];

//类型别名
using arr = string[10];
arr &func(int i);

//尾置返回值
auto func(int i) -> string(&)[10];

//decltype关键字
string arr[10];
decltype(arr) &func(int i);

我认为using更加简洁,明了。

6.38 修改arrPtr函数,使其返回数组的引用。

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

6.4 函数重载

6.39 说明在下面的每组声明中第二条语句是何含义。如果有非法的声明,请指出来。

(a) int calc(int, int);
	int calc(const int, const int);//顶层const不能作为重载条件
(b) int get();
	double get();//返回值类型不能作为重载条件
(c) int *reset(int *);
	double *reset(double *);//合法

6.5 特殊用途语言特性

6.5.1 默认实参

6.40 下面的哪个声明是错误的?为什么?

(a) int ff(int a, int b = 0, int c = 0);//正确
(b) char *init(int ht = 24, int wd, char bckgrnd);//错误,一旦某个形参被赋予了默认值,他后面的形参都必须有默认值

6.41 下面的哪个调用是非法的?为什么?哪个调用虽然合法但显然与程序员的初衷不符?为什么?

char *init(int ht, int wd = 80, char bckgrnd = ' ');
(a) init();//错误,第一个没有给默认实参,必须有实参
(b) init(24,10);//正确
(c) init(14,'*');//‘*’虽然是char类型,但是发生了隐士类型转化,违背了初衷

6.42 给make_plural函数的第二个形参赋予默认实参’s’, 利用新版本的函数输出单词success和failure的单数和复数形式。

#include <iostream>
#include<string>

using std::cin;
using std::cout;
using std::endl;
using std::string;

string make_plural(size_t ctr , const string &word , const string &ending = "s")
{
   return (ctr > 1) ? word + ending : word;
}

int main()
{
   cout << make_plural(2 , "success" , "es") << endl;
   cout << make_plural(2 , "failure") << endl;

   return 0;
}

6.5.2 内敛函数和constexpr函数

6.43 你会把下面的哪个声明和定义放在头文件中?哪个放在源文件中?为什么?

(a) inline bool eq(const BigInt&, const BigInt&) {...}//内敛函数和constexpr函数可以在程序中多次定义,所以通常定义在头文件中
(b) void putValues(int *arr, int size);//函数声明,定义在头文件中

6.44 将6.2.2节的isShorter函数改写成内联函数

inline bool isShorter(const string &s1 , const string &s2)
{
    return s1.size() < s2.size();
}

6.45 回顾在前面的练习中你编写的那些函数,它们应该是内联函数吗?如果是,将它们改写成内联函数;如果不是,说明原因。

内联函数用于规模较小,流程直接,调用频繁的函数

6.46 能把isShorter函数定义成constexpr函数吗?如果能,将它改写成constxpre函数;如果不能,说明原因。

constexpr函数:1、函数的返回类型及所有形参的类型都是字面值类型,函数体中有且只有一条return语句。
constexpr函数体内可以包含其他语句,可以有空语句、类型别名及using声明

6.5.3 调试帮助

6.47 改写6.3.2节练习中使用递归输出vector内容的程序,使其有条件地输出与执行过程有关的信息。例如,每次调用时输出vector对象的大小。分别在打开和关闭调试器的情况下编译并执行这个程序。

#include <iostream>
#include<vector>
#include<cassert>

using std::cin;
using std::cout;
using std::cerr;
using std::endl;
using std::vector;

void print_vector(vector<int>::iterator beg , vector<int>::iterator end)
{
    #ifndef NDEBUG
        cerr << end - beg << __FILE__ << " : in function " << __func__ <<" at line " << __LINE__ << endl
            << "Compiled on " << __DATE__ << " at " << __TIME__ << endl;
    #endif

    if (beg != end)
    {
        cout << *beg << " ";
        return print_vector(++beg , end);
    }
    cout << endl;
}

int main()
{
    vector<int> v{0 , 1 , 2 , 3 , 4};
    print_vector(v.begin() , v.end());

   return 0;
}

6.48 说明下面这个循环的含义,它对assert的使用合理吗?

string s;
while (cin >> s && s != sought) { } //空函数体
assert(cin);

不合理,assert宏常用于检查“不能发生”的条件。

6.6 函数匹配

6.49 什么是候选函数?什么是可行函数?

候选函数:调用对应的重载函数集,1、函数同名 2、调用点可见
可行函数:候选函数中重新选出的函数 1、数量 2、类型

6.50 已知有第217页对函数 f 的声明,对于下面的每一个调用列出可行函数。其中哪个函数是最佳匹配?如果调用不合法,是因为没有可匹配的函数还是因为调用具有二义性?

(a) f(2.56, 42)//二异性调用
(b) f(42)//f(int)
(c) f(42, 0)//f(int , int)
(d) f(2.56, 3.14)//f(double , double = 3.14)

6.51 编写函数f的4版本,令其各输出一条可以区分的消息。验证上一个练习的答案,如果你的回答错了,反复研究本节内容直到你弄清自己错在何处。

#include <iostream>

using std::cin;
using std::cout;
using std::endl;

void f(int)
{
    cout << "f(int)" << endl;
}

void f(int , int)
{
    cout << "f(int , int)" << endl;
}

void f(double , double)
{
    cout << "f(double , double)" << endl;
}

void f(double , int)
{
    cout << "f(double , int)" << endl;
}

int main()
{
   f(2.56 , 42);
   f(42);
   f(42 , 0);
   f(2.56 , 3.14);

   return 0;
}

6.6.1 实参类型转换

6.52 已知有如下声明:

void manip(int ,int);
double dobj;

请指出下列调用中每个类型转换的等级。

(a) manip('a', 'z');
(b) manip(55.4, dobj);

(a)3等级,通过类型提升;
(b)4等级,通过算术类型转换

6.53 说明下列每组声明中的第二条语句会产生什么影响,并指出哪些不合法(如果有的话)。

(a) int calc(int&, int&); 
	int calc(const int&, const int&); //合法,实参可以为const int类型
(b) int calc(char*, char*);
	int calc(const char*, const char*);//合法,实参可以为const char×类型
(c) int calc(char*, char*);
	int calc(char* const, char* const);//合法,顶层const,声明重复(可以重复声明,不可重复定义)。

6.7 函数指针

6.54 编写函数的声明,令其接受两个int形参并返回类型也是int;然后声明一个vector对象,令其元素是指向该函数的指针。

#include<vector>

using std::vector;

int func(int , int);

typedef int (*func1) (int , int );
using func2 = int(*)(int , int);
using func3 = int(int , int);
typedef decltype(func) *func4;
typedef decltype(func) func5;
using func6 = decltype(func);

vector<func1> v1;
vector<func2> v2;
vector<func3*> v3;
vector<func4> v4;
vector<func5*> v5;
vector<func6*> v6;

6.55 编写4个函数,分别对两个int值执行加、减、乘、除运算;在上一题创建的vector对象中保存指向这些函数的指针。
6.56 调用上述vector对象中的每个元素并输出结果。

#include <iostream>
#include<vector>

using std::cin;
using std::cout;
using std::endl;
using std::vector;

int add(int , int);//加
int sub(int , int);//减
int mul(int , int);//乘
int div1(int , int);//除

int main()
{
    vector<int (*)(int , int)> pv{add , sub , mul , div1};
    for(auto &e : pv)
        cout << e(5 , 1) << endl;

   return 0;
}
int add(int a , int b)
{
    return a+b;
}

int sub(int a , int b)
{
    return a-b;
}

int mul(int a , int b)
{
    return a*b;
}

int div1(int a , int b)
{
    return a/b;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值