C++Primer 第五版 ——《第六章》 函数

目录

实参是形参的初始值,但是并没有规定实参对形参的求值顺序 (183P)

函数的形参列表 

函数的返回类型不能是数组或函数类型, 但可以是指向数组或函数的指针 (184P)

局部以及全局变量的 作用域 (184P)

 函数只能定义一次,但可以声明多次 ( 186P)

含有函数声明的头文件应该被包含到定义函数的源文件中( 186P)

分离式编译 (186P)

指针作为形参(187P)

使用引用避免拷贝 (189P)

使用引用形参从一个函数返回多个值 (190P)

顶层 const 作为函数形参 (191P)

底层 const 作为函数形参 (191P)

尽量使用(const)常量引用 (192P)

数组形参(数组作为函数的参数)(193P)

使用三种方法将数组 传递给函数  (194P)

const 指针 (195P)

数组的引用作为形参  来绑定到数组上 (195P)

传递多维数组(195P)

 

给 main  函数传递数组 (196P)

使用 initializer_list 作为函数的形参 (197P)

返回类型和return语句 (200P)

函数返回局部变量的初始化规则 (201P)

不要返回局部变量的引用或者指针 (201P)

如果函数返回一个类类型指针、引用或对象,可以使用函数调用的结果来调用结果对象的成员 (202P)

返回引用的函数将返回一个左值 (202P)

函数返回花括号包围的值的列表(203P)

主函数 main 的返回值 、EXIT_SUCCESS、EXIT_FAILURE(203P)

函数返回一个数组的指针或引用 (205P)

使用尾置返回类型来 返回数组的指针 或者 引用(204P)

使用 decltype 来声明 函数返回一个数组的指针 (  206P )

定义重载函数 (  206P )

底层const 可以区别函数的重载、但顶层const 不可以(208P)

const_cast 和 重载 (在重载函数中最有用 209P)

调用重载函数的过程 (209P)

重载与作用域  &&  内存作用域中的名称隐藏外层作用域中的同名名称 (210P)

(局部变量不能作为)默认实参 (212P)

使用内敛函数避免函数的开销 (213P)

constexpr 函数 (214P)

把内联函数 和 constexpr 函数放在头文件内

assert 预处理宏 (215P)

NDEBUG 预处理变量 (216P)

当调用重载函数集中的形参都是可转换时 (217P)

确定重载函数中的候选和可行函数的过程 (217P)

 

实参类型匹配 (219P)

函数匹配和const 实参

函数指针 (221)

重载函数的指针 (222P)

函数指针作为形参 (222P)

返回指向函数的指针 (指针函数 223P)

 


函数是什么?

  •  函数可以有0个或多个参数,而且(通常)会产生一个结果。

如何执行函数?

  •  我们通过调用运算符来执行函数。调用运算符的形式是一对圆括号, 它接受一个表达式,该表达式可以是函数或者指向函数的指针( 函数指针); 调用表达式的类型就是函数的返回类型。

int fact(int val)
{
	int ret = 1;
	while (val > 1)
	{
		// 等价于 ret *= val--;
		 ret = ret * val;  --val;
	}
	return ret;
}
int main()
{
	cout << "结果为:" << fact(5) << endl;
	system("pause");
	return 0;
}

调用一个函数会做两件事:

  • 一是,提供实参会初始化该函数对应的形参
  • 二是, 会将控制权交给被调用的函数, 此时主调函数暂时被中断,被调用函数开始执行。

 return 语句也完成两项工作:

  • 一是返回return 语句中的值(如果有的话),
  • 二是 将控制权从被调用函数转移回主调函数。 函数的返回值用于初始化调用表达式的结果,之后继续完成调用所在的表达式的剩余部分。
     

实参是形参的初始值,但是并没有规定实参对形参的求值顺序 (183P)


  • 注意: 尽管实参与形参存在对应关系, 但是并没有规定实参的求值顺序, 编译器能以任意可行的顺序对实参求值
  • 注意: 实参的类型必须与对应的形参类型匹配, 即 在初始化过程中初始值的类型也必须与初始化对象的类型匹配。
int fact(int val)
{
	int ret = 1;
	while (val > 1)
	{
		// 等价于 ret *= val--;
		ret = ret * val;  --val;
	}
	return ret;
}
int main()
{
	fact("hello"); //  实参类型不正确,因为不能将 const char*  转换成int,所以错误。
	system("pause");
	return 0;
}

 


函数的形参列表 


 注意 : 任意两个形参都不能同名, 而且函数最外层作用域中的局部变量也不能使用与函数形参一样的名字.

void max(int x)
{
	int x = 8;  
	cout << x << endl;

}
int main()
{
	max(9);
	system("pause");
	return 0;
}

编译后:

 

 注意:  不管怎样, 是否设置未命名的形参并不影响调用时提供的实参数量。 即使某个形参不被函数使用, 也必须为它提供一个实参


函数的返回类型不能是数组或函数类型, 但可以是指向数组或函数的指针 (184P)


练习题6.1:

  • 可以说实参是形参的初始值, 第一个实参会初始化第一个形参, 第二个实参会初始化第二个形参,以此类推。
  • 形参是指在函数的参数列表中以声明符声明的, 实参指的是在调用该函数时,提供的实际的值,以初始化形参。

练习题6.2:

a, 函数的返回类型跟 return 返回值的类型不同

b,函数没有返回类型

c, 形参列表中两个形参同名

d,该函数没有函数体

练习题6.3:

int main()
{
	auto absoluteV = [=](int v) {return (v > 0) ? v : v * -1; };
	cout << absoluteV(-2) << endl;
	system("pause");
	return 0;
}

局部以及全局变量的 作用域 (184P)


局部变量的生存期取决于它定义的方式是是什么:

自动对象:

  • 我们把只存在于块执行期间的对象称为自动对象。当块的执行结束后, 块中创建的自动对象就变成未定义的了。
  •  形参是一种自动对象。 函数开始时为形参申请存储空间, 因为形参定义在函数体作用域之内, 所以一旦函数终止, 形参也就被销毁了。
  • 形参自动对象的初始化是由实参来进行的。
  • 对于函数体内的自动对象, 如果该变量本身含有初始值,就用这个初始值初始化,否则,执行默认初始化。如果该变量的类型内置类型,那么默认初始化是未定义的值。 如果该变量的值是类类型, 那么默认初始化的值,根据该类自己决定

局部静态对象:

  •  该对象在程序的执行路径第一次经过对象定义语句时初始化, 并且直到程序终止时才被销毁, 在此期间即使对象所在的函数结束执行也不会对它有所影响。

size_t count_calls()
{
	static size_t ctr; // 如果没有提供初始值,默认初始化为0
	return ++ctr;
}

int main()
{
	for (size_t i = 0; i != 10; ++i)
	{
		cout << count_calls() << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

练习题6.6:

  • 形参是一种普通自动对象,当执行一个函数时,首先对实参求值,然后 为形参申请内存空间 ,然后实参初始化形参, 当该函数终止后,形参分配的内存空间也将被销毁。
  • 局部变量也是一种普通的自动对象, 该对象在 执行路径经过定义语句时,创建该对象, 当到达定义所在的块末尾时销毁它。如果没有初始化这样的对象, 执行的是默认初始化。
  • 静态局部变量,  在程序的执行路径第一次经过对象定义语句时初始化,并直到程序终止时销毁。 在此期间即使该函数结束执行也不会对变量的值有所影响。 如果没有对该对象执行初始化, 将执行值初始化。
size_t count_calls(int val)
{
	int temp = 10 * val;
	cout << "输出temp的值:" << temp << endl;
	static size_t ctr = 22; 
	return ctr*val;
}

int main()
{
	cout << count_calls(5) << endl;
	cout << endl;
	system("pause");
	return 0;
}

练习题6.7:

size_t count_calls()
{
	static size_t ctr; 
	return ctr++;
}

int main()
{
	for (size_t i = 0; i != 10; ++i)
	{
		cout << count_calls() << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

 函数只能定义一次,但可以声明多次 ( 186P)


含有函数声明的头文件应该被包含到定义函数的源文件中( 186P)


分离式编译 (186P)



指针作为形参(187P)


当执行指针拷贝时,拷贝的是指针的值。 拷贝之后 , 两个指针是不同的指针; 即它们在内存中的地址是不相同的。因为指针可以间接地访问所指对象, 可以通过指针修改其所指对象的值。


void reset(int *ip)
{
	*ip = 10;
	ip = nullptr;
}

int main()
{
	int i = 220;
	reset(&i);
	cout << i << endl;
	system("pause");
	return 0;
}

练习题6.10:

void swap( int *q,  int *p)
{
	int temp = *q;
	*q = *p;
	*p = temp;
}

int main()
{
	auto swap_2=[&](int *q,int *p)
	{
		int temp = *q;
		*q = *p;
		*p = temp;
	};
	int i = 100;
	int ii = 22;
	swap_2(&i, &ii);
	cout << i << " " << ii << endl;
	system("pause");
	return 0;
}

使用引用避免拷贝 (189P)


  •  拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO 类型在内) 根本就不支持拷贝操作。
  • 当某种类型不支持拷贝操作时, 函数只能通过引用形参访问该类型对象。
  •  如果函数无须改变引用形参的值,最好将其声明为 常量(const)引用。

使用引用形参从一个函数返回多个值 (190P)


 一个函数只能返回一个值,可以使用引用形参为我们一次返回多个结果。


string::size_type find_char(const string &s, char c, string::size_type &occurs)
{
	auto ret = s.size();
	occurs = 0;
	for (decltype(ret) i = 0; i != s.size(); ++i)
	{
		if (s[i] == c)
		{
			if (ret == s.size())
			{
				ret = i; 
			}
			++occurs;
		}
	}
	return ret;
}
int main()
{
	string s = "hoong";
	string::size_type ctr = 0;
	auto index = find_char(s, 'o', ctr);
	cout << index << " " << ctr << endl;
	system("pause");
	return 0;
}
运行结果: 1、2

练习6.15:

  • 对于查找的字符串s来说, 为了避免拷贝字符串,使用引用类型;同时我们只执行查找操作,无须改变字符串的内容, 所以将其声明为常量引用
  • 对于待查找的字符c来说, 它的类型是char,只占1字节, 拷贝代价低,而且我们不需要改变实参的内容,只把值拷贝给形参即可, 所以不需要使用引用类型
  • 对于字符出现的次数 occurs 来说, 因为需要把函数内对实参值的更改反映在函数外部, 所以必须将其定义成引用类型, 但是不能把它定义成常量引用

顶层 const 作为函数形参 (191P)


int main()
{
	int i = 0;
	int *const p1 = &i; // 不能改变p1的值,是顶层 const
	const int ci = 42; // 不能改变ci的值,是顶层 const
	const int *p2 = &ci; // 允许改变p2 的值,底层const
	const int *const p3 = p2; // 左边底层const,右边顶层 const
	const int &r = ci; // 用于声明引用的const 都是底层 const
	
	system("pause");
	return 0;
}

当一个函数的形参是顶层 const 时, 传入的值是 常量 还是 非常量 都是无关紧要的。

void fcn(const int i)
{ 
	cout << "调用的是 fcn: const int" << endl;
}
void fcn(int i)
{
	cout << "调用的是 fcn: int" << endl;

}
int main()
{
	const int i = 9;
	fcn(i);
	
	system("pause");
	return 0;
}

说明: 在 C++ 语言中,允许我们定义若干具有相同名字的函数, 不过前提是不同函数的形参列表应该有明显的区别。

因为顶层const被忽略掉了, 所以在上面的代码中传入两个fun函数的参数是完全一样的。因此第二个fcn是错误的,尽管形式上有差异,但实际上它的形参和第一个fcn的形参没什么不同。


底层 const 作为函数形参 (191P)


  •  我们可以使用非常量初始化一个底层 const 对象,
  • 但是一个底层const 不能初始化一个非常量对象;
  • 而且,同时一个普通的引用必须用同类型的对象初始化。
int main()
{
	int i = 55;
	const int *cp = &i; // 允许改变cp的值,底层const,但是不能改变所指对象
	const int &r = i; // 底层 const,不能改变所指对象
	const int &r2 = 42; // 底层 const

	int *p = cp; // 错误,如果p 指向cp,就相当于指向了i , 因为cp i 不能改变i, 所以p 指向i的话,可能会修改其值。所以类型不匹配
	int &r3 = r; // 因为 r是 const, 如果 r3 指向r 可能会修改其值, 所以类型不匹配
	int &r4 = 55; // 不能将一个字面值初始化一个非常量引用

	system("pause");
	return 0;
}
void reset(int &i)
{
	cout << "调用的是 int &i" << endl;
}

void reset (int *i)
{
	cout << "调用的是 int *i" << endl;
}

string::size_type find_char(const string &s, char c, string::size_type &occurs)

{
	cout << "调用的是 find_char" << endl;
}
int main()
{
	int i = 55;
	const int ci = i;
	string::size_type ctr = 0;

	reset(&i); // 调用的形参是 int* 的 reset 函数
	reset(&ci); // 不能将一个 普通指针指向一个 const int 对象

	reset(i); // 调用 形参类型是 int& 的reset 函数
	reset(ci); // 不能将一个普通引用绑定到一个 const 对象上
	reset(455); // 不能将一个字面值绑定在一个非常量引用上
	find_char("Hello World!", 'o', ctr); // 正确 该函数的第一个形参是 const 的引用
	system("pause");
	return 0;
}

 要想调用引用版本的reset  只能使用:

  • int 类型的对象, 而不能使用字面值、求值结果为int的表达式、需要转换的对象或者 是 const int 类型的对象。

类似的,要想调用指针版本的 reset 只能使用int*, 我们能传递一个字符串字面值作为 find_char () 函数的第一个实参,这是因为该函数的引用形参是常量引用。 注意:  C++ 允许我们用字面值初始化常量引用

const int &a=42;

 


尽量使用(const)常量引用 (192P)


  • 把函数不会改变的形参定义成普通的引用是一种比较常见的错误;
  • 然而使用普通引用而非const引用也会极大地
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值