【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)

目录

6.1 函数基础

静态局部对象 static类型

编程习惯!

6.2 参数传递

编程习惯!

const实参和形参

main函数的形参

数组作为参数

6.3 返回类型和return语句

错误的return

尾置返回类型

6.4 函数重载

重载和const形参

const_cast与重载

作用域对重载的影响

6.5 特殊用途

内联函数inline

6.7 函数指针

概念

写法和用法

函数自动转为函数指针

再看decltype

附:部分习题答案


6.1 函数基础

静态局部对象 static类型

static声明后,变量的生命周期就是:从变量创建到程序结束。

注:static类型变量默认初始化为0

编程习惯!

关于函数的声明和定义:

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第1张图片

小结:函数应该在头文件中声明,在源文件中定义。

附:声明和定义的区别

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第2张图片

6.2 参数传递

编程习惯!

关于引用传递的参数类型:const

const实参和形参

函数不会改变的形参如果不是const类型,则会有很多限制!

  • 被误认为这个参数是可以修改的!
  • 极大限制了函数所能接受的实参类型:
void test1(int& a) // 非const
{
	cout << a << endl;
}

void test2(const int& a) // 非const
{
	cout << a << endl;
}

void test()
{
	test1(10); // 错误!因为不能用字面值和非const引用绑定!
    test2(10); // 正确!因为test2的参数是const型

    const int a = 10;
    test1(a); // 错误!因为不能用const变量和非const引用绑定!
	test2(a); // 正确!因为都是const
    
}

因此,可以发现,如果函数的形参只是int&,那么只有int型的实参才能与之绑定;如果是const int&,那么字面值、int型、const int型都可以与之绑定。

main函数的形参

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第3张图片

数组作为参数

两个特性:

  • 不允许拷贝数组 --> 无法值传递,只能引用传递
  • 使用数组的时候,会转为指针 --> 传一个数组,实际上传的是首元素指针

标准库规范:传递指向首元素和尾元素的指针

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第4张图片

例子:

int &test1(int *a, int index) // 返回引用, 第一个参数是数组首元素的地址
{
	return a[index]; // 返回第index个元素的引用
}

void test()
{
	int a [] = {1,2,3,4,5};

	for (auto i : a)
	{
		cout << i << " ";
	}
	cout << endl;
    
	test1(a, 1) = 9; // 传入数组a,但会自动转为a[0]的地址; 因为左边是一个引用,因此可作为左值,因此可以直接赋值
	for (auto i : a)
	{
		cout << i << " ";
	}
}

6.3 返回类型和return语句

错误的return

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第5张图片

尾置返回类型

记号:->

string a = "wind";

auto test(int *b) -> decltype(a) // auto的类型,就是根据decltype(a)的返回类型得到
{
	return a;
}

int main()
{
	int b[] = { 1,2,3,4 };
	auto c = test(b);
	cout << c;

	return 0;
}

下面三个函数等价:

// 结合decltype,依据模板参数推导返回类型 
template
auto Func1(ArgType1& a, ArgType2& b) -> decltype(a + b) 
{ 
    return (a + b);
}

// 在C++14中可省略箭头返回值部分,直接将函数返回类型设置为auto 
template
auto Func2(ArgType1& a, ArgType2& b)
{ 
    return (a + b);
}

// 也可以直接将返回类型定义为 decltype(auto) 
template
decltype(auto) Func3(ArgType1& a, ArgType2& b)
{
    return (a + b);
}

6.4 函数重载

重载和const形参

只要记住:只有引用指针作为形参,才可以重载!否则,都不行!

并且,会根据传入的顶层const类型去选择使用哪个重载函数:

// 正确
void test(int& i)
{
	cout << " 1 --- " << i << endl;
}

void test(const int& i)
{
	cout << " 2 --- " << i << endl;
}

// 错误
void test1(int i)
{
	cout << " 1 --- " << i << endl;
}

void test1(const int i)
{
	cout << " 2 --- " << i << endl;
}

int main()
{
	const int a = 10;
	test(a); // a是const,因此选择void test(const int& i), 输出 2 --- 10

	return 0;
}

const_cast与重载

const_cast常用在函数重载中!主要作用是:const型和非const型的相互转换。

使用的初衷:通常不希望修改传入的string,因此形参被返回类型都被定义为const,然而有时候需要返回一个非conststring,因此就需要用const_cast过渡一下。

string &test(const string &s1, const string &s2) // 错误,因为传入是const,返回必须是const
{
	return s1.size() > s2.size() ? s1 : s2;
}
正确:
const string &test(const string &s1, const string &s2)
{...}

int main()
{
	string s1 = "wind", s2 = "wind1";
	string &s = test1(s1, s2); // 错误,因为test返回的是const,因此必须是const型接收
    const string &s = test1(s1, s2); // 正确
	cout << s;
	return 0;
}

我们不希望修改s1s2,因此test()要以const类型传入;然而我们希望第13行的s是可以修改的,但又必须用const string去接收,此时就不好办了。因此,要用const_cast过渡一下:

const string &test(const string &s1, const string &s2)
{
	return s1.size() > s2.size() ? s1 : s2;
}

string& test1(string& s1, string& s2)
{
	auto& r = test(const_cast(s1), const_cast(s2));
	return const_cast(r);
}

int main()
{
	string s1 = "wind", s2 = "wind1";

	string &s = test1(s1, s2);
	cout << s;
	return 0;
}

8行,用先用const_cast把非consts1、s2转为const,调用test(),用一个const string &r去接收,然后再用const_castconstr转为非constr

作用域对重载的影响

一句话总结:如果在函数内层作用域中对函数做了重载,那么就会忽略所有函数外的函数重载。

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第6张图片

只有把重载函数放在一个作用域中(例如都在函数外或函数内),才不会被忽略:

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第7张图片

6.5 特殊用途

内联函数inline

主要作用:很多条件判断语句可以通过写一个小的函数去等价表示,这样更加清晰明了,也便于后期修改;然而函数调用是需要占用一定资源的(运行更慢),因此内联函数就用来解决这个问题。

关键字:inline

定义内联函数:

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第8张图片

调用:

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第9张图片

以上表面,对于较大的函数(比如有一百行),即使声明为inline,但编译器也可能不会当做内联函数使用,不然更费时间。

注:对于inline函数和constexpr函数,通常把声明和定义放在头文件中,因为这两个函数在程序中需要被多次定义,而这些定义必须一致!

6.7 函数指针

概念

顾名思义,就是指向函数的指针,这个指针可以代替函数来使用。

写法和用法

声明:

类型 (*指针名称)(形参);

例如:

const string& compare(const string& s1, const string& s2)
{
	return s1.size() > s2.size() ? s1 : s2;
}

void test()
{
	const string& (*p)(const string& s1, const string& s2);
	p = compare;

	cout << p << endl << &compare << endl; // 输出相同

	string b1 = p("wind", "wind1");
	cout << b1 << endl;
}

1行,是一个函数;

8-9行,就是定义一个函数指针p,指向第1行的函数;

注意:函数指针的返回类型、形参必须和函数定义保持一致

函数: const string& compare(const string& s1, const string& s2)

函数指针: const string& (*p)(const string& s1, const string& s2);

指针指向函数:p = compare;

13行,使用函数指针p,代替直接调用函数compare()

注1:函数指针可以初始化为0nullptr(因为本质上就是指针,遵循指针的初始化原则)

注2:重载函数指针时,必须清晰界定选用哪个函数,包括形参和返回类型:

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第10张图片

函数自动转为函数指针

【C++】《C++ Primer》第六章:函数(知识点总结和习题答案)_第11张图片

再看decltype

decltype只会返回类型,不会做任何数据转换!!因此很多时候需要手动转换!例如:

函数指针那里:

const string& compare(const string& s1, const string& s2)
{
	return s1.size() > s2.size() ? s1 : s2;
}

void test()
{
	const string& (*p)(const string& s1, const string& s2);
	p1 = compare;

	string b1 = p1("wind", "wind1");
	cout << b1 << endl;

	decltype(compare) *p2 = p1; // 重点在这里
	cout << &p2 << endl << &p1 << endl; // 地址不一样,不是同一个指针

	string b2 = p2("wind", "wind1");
	cout << b2 << endl;
}

14行,decltype(compare)返回的是一个函数类型,因此需要加上*才能得到指针

附:部分习题答案

练习6-10

#include 
#define _CRT_SECURE_NO_DEPRECATE
using namespace std;
#include 
#include 

void test1(int* a, int* b)
{
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;
}


void test()
{
	int a = 10, b = 20;
	test1(&a, &b);
	cout << a << endl << b << endl;


}


int main()
{
	test();

	return 0;
}

练习6-12

#include 
#define _CRT_SECURE_NO_DEPRECATE
using namespace std;
#include 
#include 

void test1(int& a, int& b)
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}


void test()
{
	int a = 10, b = 20;
	test1(a, b);
	cout << a << endl << b << endl;


}


int main()
{
	test();

	return 0;
}

练习6-33

#include 
#define _CRT_SECURE_NO_DEPRECATE
using namespace std;
#include 
#include 

// 我的答案 -- 写复杂了
vector::iterator test1(vectorv, vector::iterator begin, vector::iterator end)
{
	if (begin + 1 == end)
	{
		cout << *begin << endl;
		return begin;
	}
	cout << *begin << endl;
	return test1(v, begin + 1, end);

}

// 参考答案
void print(vectorv, int index)
{
	int size = v.size();
	if (index != size)
	{
		cout << v[index++] << endl;
		print(v, index);
	}
}

void test()
{
	vectorv;
	for (int i = 0; i < 10; ++i)
	{
		v.push_back(i);
	}

	/// 调用我的答案
	//vector::iterator begin = v.begin();
	//vector::iterator end = v.end();
	//vector::iterator iter = test1(v, begin, end);

	/// 调用参考答案
	int index = 0;
	print(v, index);

}

int main()
{
	test();

	return 0;
}

练习6-36

#include 
#define _CRT_SECURE_NO_DEPRECATE
using namespace std;
#include 
#include 

// 6.36
string(&func())[10];

// 6.37
// 类型别名
typedef string arrT[10];
using arrT1 = string[10];
arrT& func();
arrT1& func();

// 尾置返回类型
auto func()->string(&)[10];

// decltype关键字
string str[10];
decltype(str)& func();


int main()
{
	return 0;
}

练习6-54

#include 
#define _CRT_SECURE_NO_DEPRECATE
using namespace std;
#include 
#include 

void test()
{
	// 函数的声明
	int func(int, int);

	// vector对象的声明
	vector< decltype(func)* >v;

}

int main()
{
	return 0;
}

练习6-55和6-56

#include 
#define _CRT_SECURE_NO_DEPRECATE
using namespace std;
#include 
#include 

int func1(int a, int b)
{
	return a + b;
}

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

void test()
{

	vector< decltype(func1)* >v;
	decltype(func1)* p1 = func1;
	decltype(func1)* p2 = func2;

	v = { p1, p2 };

	for (auto i : v)
	{
		cout << (*i)(1, 1) << endl;
	}

}

int main()
{
	test();

	return 0;
}

你可能感兴趣的:(C++,c++)